diff --git a/docs/user/expert/chat.md b/docs/user/expert/chat.md index 63592c0d0f..c493229faa 100644 --- a/docs/user/expert/chat.md +++ b/docs/user/expert/chat.md @@ -158,7 +158,7 @@ For example, if you ask Expert to "add three nodes" with Write set to *Ask*, it | Action type | What it covers | Actions | |---|---|---| | **Read** | View only, no changes | List Applications, Get Application, Get Application Hosted Instances, Get Application Remote Instances, Get Application Instances Status, Get Application Audit Log, List Teams, Get Team, Get Hosted Instance, Get Hosted Instance Status, Get Hosted Instance Logs, Check Hosted Instance Name Availability, List Team Remote Instances, Get Remote Instance, Get Remote Instance Status, List Hosted Instance Snapshots, List Remote Instance Snapshots, List Hosted Instance Types, List Stacks, List Templates, List Blueprints, Open Hosted Instance, Open Hosted Instance Editor | -| **Write** | Create or change resources | Create Application, Create Hosted Instance, Create Remote Instance, Assign Remote Instance To Application, Create Hosted Instance Snapshot, Create Remote Instance Snapshot | +| **Write** | Create or change resources | Create Application, Create Hosted Instance, Create Remote Instance, Assign Remote Instance To Application, Create Hosted Instance Snapshot, Create Remote Instance Snapshot, Suspend Hosted Instance | #### Context: What the Expert Can See diff --git a/forge/ee/lib/mcp/tools/instances.js b/forge/ee/lib/mcp/tools/instances.js index 3f5cefa1d6..3ee731f9b0 100644 --- a/forge/ee/lib/mcp/tools/instances.js +++ b/forge/ee/lib/mcp/tools/instances.js @@ -357,6 +357,23 @@ module.exports = [ const response = await inject({ method: 'GET', url: `/api/v1/teams/${args.teamId}/dashboard-instances` }) return response } + }, + { + name: 'platform_suspend_hosted_instance', + title: 'Suspend Hosted Instance', + description: `FlowFuse platform automation tool: + Suspends a hosted instance, stopping its running Node-RED runtime. + Credentials, flow and packages are stored securely, and will continue execution when the hosted instance is started again. + Use this to stop an instance from running (e.g. to save on resources) without deleting it. + Check platform_get_hosted_instance_status first if you need to confirm the instance is currently running before suspending it.`, + annotations: { readOnlyHint: false, destructiveHint: false }, + inputSchema: { + hostedInstanceId: z.string().describe('The ID or hashid of the hosted instance') + }, + handler: async (args, { inject }) => { + const response = await inject({ method: 'POST', url: `/api/v1/projects/${args.hostedInstanceId}/actions/suspend` }) + return response + } } ] diff --git a/test/unit/forge/ee/lib/mcp/tools/instances_spec.js b/test/unit/forge/ee/lib/mcp/tools/instances_spec.js index abaaa2a839..6e3171e0e4 100644 --- a/test/unit/forge/ee/lib/mcp/tools/instances_spec.js +++ b/test/unit/forge/ee/lib/mcp/tools/instances_spec.js @@ -585,4 +585,25 @@ describe('MCP Instances Tools', function () { response.should.equal(routeResponse) }) }) + + describe('platform_suspend_hosted_instance', function () { + const tool = getTool('platform_suspend_hosted_instance') + + it('posts to the suspend action endpoint for the given instance', async function () { + const suspendResponse = { statusCode: 200, json: () => ({ status: 'okay' }) } + inject.withArgs({ method: 'POST', url: '/api/v1/projects/instance1/actions/suspend' }).resolves(suspendResponse) + + const response = await tool.handler({ hostedInstanceId: 'instance1' }, { inject }) + + response.should.equal(suspendResponse) + }) + + it('passes through an error response', async function () { + const errorResponse = { statusCode: 400, json: () => ({ code: 'project_suspended' }) } + inject.resolves(errorResponse) + + const response = await tool.handler({ hostedInstanceId: 'instance1' }, { inject }) + response.should.equal(errorResponse) + }) + }) })