feat(mcp): consolidate instance and device listing tools - #7999
Conversation
Replace platform_get_application_hosted_instances, platform_get_application_instances_status, platform_get_application_remote_instances, and platform_list_team_remote_instances with two unified tools: platform_list_hosted_instances and platform_list_remote_instances. Each works both team-wide (paginated) and scoped to an application (unpaginated), matching the dashboard's own status filter groups (running/error/notRunning for instances) and mode filter (autonomous/developer for devices), and returns the same pagination shape as the underlying REST APIs. Also adds a hostedInstanceId filter to list the remote instances assigned to a hosted instance's device group, which had no equivalent tool before.
…instance-device-list-tools
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #7999 +/- ##
==========================================
+ Coverage 75.92% 76.13% +0.20%
==========================================
Files 437 439 +2
Lines 23392 23548 +156
Branches 6217 6272 +55
==========================================
+ Hits 17761 17928 +167
+ Misses 5631 5620 -11
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| handler: async (args, { inject }) => { | ||
| let url = `/api/v1/teams/${args.teamId}/devices` | ||
| const params = [] | ||
| const params = [`page=${args.page || 1}`, `limit=${args.limit || 10}`] |
There was a problem hiding this comment.
Lets use UrlSearch params or better yet the URL class for this logic, it's a pain too look at
| inputSchema: { | ||
| teamId: z.string().describe('The ID or hashid of the team'), | ||
| applicationId: z.string().optional().describe('Restrict results to remote instances assigned to this application. Omit to list every remote instance in the team.'), | ||
| hostedInstanceId: z.string().optional() |
There was a problem hiding this comment.
wouldn't it be better to add a quick phrase on these input params to 'only use when you need to filter by..' or similar than adding Pass applicationId to list only the remote instances assigned to one application, or hostedInstanceId to list only the remote instances assigned to one hosted instance's device group. Omit both to list every remote instance in the team. in the tool description?
| name: device.name, | ||
| ownerType: device.ownerType, | ||
| mode: device.mode, | ||
| status: device.status, |
There was a problem hiding this comment.
device status is the entry stored in the database (which is either the last known or desired state) not the actual live state status. That's why we have two api endpoints for it
There was a problem hiding this comment.
we should add descriptions for this because it can confuse agents the same way it's confusing us
|
|
||
| // Mirrors the runningStates/errorStates/stoppedStates groups in frontend/src/composables/InstanceStates.js, | ||
| // the same grouping the dashboard's own Running/Error/Not Running status filter uses (frontend/src/pages/team/Instances.vue). | ||
| const STATE_GROUPS = { |
There was a problem hiding this comment.
we need a better way of doing this, portable across FE/BE to not mix up domain boundaries and avoid copy pasting (and ultimately maintaining) multiple entries of the same thing.
Not asking to do anything about it now, just saying.
| } | ||
|
|
||
| async function listTeamHostedInstances (args, { inject }) { | ||
| const params = [`page=${args.page || 1}`, `limit=${args.limit || 10}`] |
There was a problem hiding this comment.
UrlSearch params or the URL class for these actions please
| ownerType: device.ownerType, | ||
| mode: device.mode, | ||
| status: device.status, | ||
| onlineStatus: device.onlineStatus, |
There was a problem hiding this comment.
the status field on line 51 and onlineStatus on line 53 of the MCP tool response are two different stored/computed DB values, and neither is the
actual live state.
The distinction in the tool description for platform_get_remote_instance_status is accurate, but the list tool doesn't make this clear.
There was a problem hiding this comment.
onlineStatus is not a live MQTT query. It's computed from lastSeenAt using a 30-minute threshold.
There was a problem hiding this comment.
the platform_get_remote_instance_status tool is the only true retriever of a device's status; we should consider merging that in. we should talk about this entire thing as it's filled with nuances
| If you already know the application, use platform_get_application_remote_instances instead to get a narrower list. | ||
| Pass applicationId to list only the remote instances assigned to one application, or hostedInstanceId to list only the remote instances assigned to one hosted instance's device group. Omit both to list every remote instance in the team. | ||
| You can search by name using the query parameter, filter by mode ("autonomous", i.e. Fleet Mode, or "developer", i.e. Developer Mode), and page through results using page and limit. | ||
| To get the full details of one specific remote instance, call platform_get_remote_instance with its ID.`, |
There was a problem hiding this comment.
we should explain the following: The status field reflects the last-known or desired state stored on the platform. To get the actual live runtime state, call platform_get_remote_instance_status.
…ams` for improved readability and maintainability
Summary
Consolidates 4 overlapping platform automation tools into 2:
platform_list_hosted_instancesandplatform_list_remote_instances. Each works both team-wide (paginated) and scoped to a single application.hostedInstanceIdfilter to list the devices assigned to a specific hosted instance's device group. There was no way to do this before.Details
Removed:
platform_get_application_hosted_instancesplatform_get_application_instances_statusplatform_get_application_remote_instancesplatform_list_team_remote_instancesAdded:
platform_list_hosted_instances—teamId(required), optionalapplicationId,query,state(running/error/notRunning, matching the dashboard's Running/Error/Not Running filter),includeLiveStatus,page,limitplatform_list_remote_instances—teamId(required), optionalapplicationId,hostedInstanceId,query,mode(autonomous/developer, matching the dashboard's Fleet Mode/Developer Mode filter),page,limitTeam-wide instance listing returns
{ count, meta: { page, pageSize, total, pageCount }, instances }; application-scoped listing returns{ count, instances }with no pagination, since the underlying/applications/:id/instancesendpoint has none. It also uses a lighter summary view with no instance type/stack/template, so application-scoped results don't include spec either. Unifying that would need a backend change and isn't part of this PR.Remote instance listing returns the same
{ count, meta, devices }shape at every scope (team, application, or a specific hosted instance's device group), since the devices API supports pagination consistently across all three.Test plan
npx mocha test/unit/forge/ee/lib/mcp/tools/instances_spec.js test/unit/forge/ee/lib/mcp/tools/devices_spec.js— 32/32 passingManually verified via chat
Asking naturally and checking both the arguments the assistant chose and the response shape:
platform_list_hosted_instancesstate: ["running"],includeLiveStatus: truestate: ["notRunning"]state: ["error"]state: ["error", "notRunning"]applicationIdset, response has no paginationplatform_list_remote_instancesmode: "developer"applicationId+mode: "developer"applicationId+mode: "autonomous"hostedInstanceIdsetpage: 1,limit: 10Closes FlowFuse/engineering#184
Closes #7919