From 8fa182bc8f7f9e0ae5113af915a13da56937de58 Mon Sep 17 00:00:00 2001 From: ZJ van de Weg Date: Fri, 11 Sep 2026 13:31:21 -0700 Subject: [PATCH] feat: add MCP tool to suspend a hosted instance Adds platform_suspend_hosted_instance, calling the existing project suspend action route. Marked as a non-destructive write action (destructiveHint: false): suspending stops the running container but keeps the instance's flows, credentials, and settings intact, and it can simply be started again, so it doesn't warrant the stricter destructive-write gating. Also documents the new tool in the Expert chat docs' platform actions reference table. Co-Authored-By: Claude Sonnet 5 --- docs/user/expert/chat.md | 2 +- forge/ee/lib/mcp/tools/instances.js | 17 +++++++++++++++ .../forge/ee/lib/mcp/tools/instances_spec.js | 21 +++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) 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) + }) + }) })