Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions forge/ee/lib/mcp/tools/applications.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ module.exports = [
description: `FlowFuse platform automation tool:
Gets all the hosted instances that live inside an application.
A hosted instance is a Node-RED that runs on the same environment as the FlowFuse platform.
Use this to see which hosted instances an application has. Each result includes the instance name, URL, and basic settings.
To get the full details of one specific hosted instance, call platform_get_hosted_instance with its ID.
Use this to see which hosted instances an application has. Each result includes the instance id, name, URL, and basic settings.
This list does not include an instance's specification (its instance type, stack, or template). To read an instance's specification, for example to duplicate it, call platform_get_hosted_instance with the instance id.
To check if hosted instances are currently running or stopped, call platform_get_application_instances_status instead.`,
annotations: { readOnlyHint: true, destructiveHint: false },
inputSchema: {
Expand Down
9 changes: 5 additions & 4 deletions forge/ee/lib/mcp/tools/instances.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ module.exports = [
Gets the full details of one specific hosted instance.
A hosted instance is a Node-RED that runs on the same environment as the FlowFuse platform.
Use this when you already have a hosted instance ID and need to know everything about it:
its name, URL, settings, what application and team it belongs to, and its current state.
its name, URL, settings, what application and team it belongs to, its current state, and its specification (the instance type, stack, and template it uses).
Read the specification from this tool whenever you need to know or compare what an existing instance is running, for example to duplicate it.
If you need to list all hosted instances first, call platform_get_application_hosted_instances.
To check the live running status, call platform_get_hosted_instance_status instead.`,
annotations: { readOnlyHint: true, destructiveHint: false },
Expand Down Expand Up @@ -89,8 +90,8 @@ module.exports = [
description: `FlowFuse platform automation tool:
Creates a new hosted Node-RED instance inside an application. The instance starts automatically after creation.
Before calling this tool, gather the required parameters:
1. Call platform_list_hosted_instance_types first to see what instance types are available on this platform, then ask the user which one they want.
2. If they want a specific Node-RED version or stack, or just the latest. Call platform_list_stacks to get the options. If the user has no preference, use the latest available stack.
1. Call platform_list_hosted_instance_types first to see what instance types (and their stacks) this team can create, then ask the user which one they want.
2. If they want a specific Node-RED version or stack, or just the latest. Use platform_list_hosted_instance_types with the chosen projectType to see its stacks. If the user has no preference, use the instance type's defaultStack, which is the latest recommended version.
3. If they want to start from a blueprint (pre-built starter flows). Call platform_list_blueprints to show them what is available. This is optional.
4. Call platform_list_templates to get the template. If only one template exists, use it automatically. If there are multiple, ask the user which one to use.
5. Call platform_check_hosted_instance_name_availability to make sure the chosen name is not already taken.
Expand All @@ -101,7 +102,7 @@ module.exports = [
name: z.string().regex(/^[a-zA-Z][a-zA-Z0-9-]*$/).describe('Name for the new hosted instance. When generating a name, always use hyphens to separate multiple words (e.g. "my-new-instance" not "my new instance").'),
applicationId: z.string().describe('The ID or hashid of the application'),
projectType: z.string().describe('The ID of the hosted instance type (use platform_list_hosted_instance_types to find valid values)'),
stack: z.string().describe('The ID of the stack (use platform_list_stacks to find valid values)'),
stack: z.string().describe('The ID of the stack (use platform_list_hosted_instance_types to find valid values)'),
template: z.string().describe('The ID of the template (use platform_list_templates to find valid values)'),
flowBlueprintId: z.string().optional().describe('Optional blueprint ID to initialize the hosted instance with starter flows (use platform_list_blueprints to find valid values)')
},
Expand Down
86 changes: 71 additions & 15 deletions forge/ee/lib/mcp/tools/platform.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,80 @@
const { z } = require('zod')

function getProperty (properties, key) {
let value = properties
for (const part of key.split('.')) {
if (value === undefined || value === null || !Object.hasOwn(value, part)) {
return undefined
}
value = value[part]
}
return value
}

function getTeamProperty (team, key, defaultValue) {
const teamValue = getProperty(team.properties, key)
if (teamValue !== undefined) {
return teamValue
}
const teamTypeValue = getProperty(team.type?.properties, key)
return teamTypeValue !== undefined ? teamTypeValue : defaultValue
}

module.exports = [
{
name: 'platform_list_hosted_instance_types',
title: 'List Hosted Instance Types',
description: 'FlowFuse platform automation tool: List all available hosted instance types. Use this to find valid projectType values when creating a hosted instance.',
annotations: { readOnlyHint: true, destructiveHint: false },
inputSchema: {},
handler: async (args, { inject }) => {
const response = await inject({ method: 'GET', url: '/api/v1/project-types' })
return response
}
},
{
name: 'platform_list_stacks',
title: 'List Stacks',
description: 'FlowFuse platform automation tool: List all available stacks (Node-RED versions). Use this to find valid stack values when creating a hosted instance.',
description: `FlowFuse platform automation tool:
Comment thread
andypalmi marked this conversation as resolved.
Lists the hosted instance types available to a team, each with its stacks (Node-RED versions) nested inside.
Use this to find valid projectType and stack values when creating a hosted instance.
Each type is flagged available (allowed for the team's plan) and creatable (the team can create a new instance of this type right now, within its limits).
By default only creatable types are returned - pass creatableOnly: false to also see types the team cannot currently create.
Pass projectType to look up one specific instance type and only see its stacks.
Each type includes a defaultStack, which is the recommended (latest) stack for that type.`,
annotations: { readOnlyHint: true, destructiveHint: false },
inputSchema: {},
inputSchema: {
teamId: z.string().describe('The ID or hashid of the team to check instance type availability for'),
projectType: z.string().optional().describe('Optional ID of one hosted instance type to look up, to only return that type and its stacks'),
creatableOnly: z.boolean().default(true).optional().describe('Whether to only include instance types the team can currently create. Defaults to true.')
},
handler: async (args, { inject }) => {
const response = await inject({ method: 'GET', url: '/api/v1/stacks' })
return response
const teamResponse = await inject({ method: 'GET', url: `/api/v1/teams/${args.teamId}` })
if (teamResponse.statusCode >= 400) {
return { content: teamResponse.json(), code: teamResponse.statusCode, isError: true }
}
const team = teamResponse.json()

const typesResponse = await inject({ method: 'GET', url: '/api/v1/project-types' })
if (typesResponse.statusCode >= 400) {
return { content: typesResponse.json(), code: typesResponse.statusCode, isError: true }
}

let types = typesResponse.json().types
if (args.projectType) {
types = types.filter(type => type.id === args.projectType)
}

const creatableOnly = args.creatableOnly !== false
const decoratedTypes = []
for (const type of types) {
const available = getTeamProperty(team, `instances.${type.id}.active`, false)
const creatableForTeam = getTeamProperty(team, `instances.${type.id}.creatable`, true)
const limit = getTeamProperty(team, `instances.${type.id}.limit`, null)
const existingCount = team.instanceCountByType?.[type.id] || 0
const withinLimit = limit === null || limit === undefined || limit < 0 || existingCount < limit
const creatable = available && creatableForTeam && withinLimit

if (creatableOnly && !creatable) {
continue
}

const stacksResponse = await inject({ method: 'GET', url: `/api/v1/stacks?projectType=${type.id}` })
const stacks = stacksResponse.statusCode < 400 ? stacksResponse.json().stacks : []

decoratedTypes.push({ ...type, available, creatable, stacks })
}

return { types: decoratedTypes }
}
},
{
Expand Down
Loading