Skip to content

feat(mcp): consolidate instance and device listing tools - #7999

Merged
cstns merged 5 commits into
mainfrom
feat/mcp-consolidate-instance-device-list-tools
Jul 29, 2026
Merged

feat(mcp): consolidate instance and device listing tools#7999
cstns merged 5 commits into
mainfrom
feat/mcp-consolidate-instance-device-list-tools

Conversation

@andypalmi

@andypalmi andypalmi commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Consolidates 4 overlapping platform automation tools into 2: platform_list_hosted_instances and platform_list_remote_instances. Each works both team-wide (paginated) and scoped to a single application.

  • No effect on end users. This only changes the tools available to the assistant, not any user-facing API or UI.
  • Fewer tool calls per chat. "What instances are running" previously needed a list call plus a separate status call; now one call returns both together. Team-wide listing also includes each instance's spec (instance type, stack, template), which the old endpoint never returned.
  • Adds a hostedInstanceId filter 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_instances
  • platform_get_application_instances_status
  • platform_get_application_remote_instances
  • platform_list_team_remote_instances

Added:

  • platform_list_hosted_instancesteamId (required), optional applicationId, query, state (running / error / notRunning, matching the dashboard's Running/Error/Not Running filter), includeLiveStatus, page, limit
  • platform_list_remote_instancesteamId (required), optional applicationId, hostedInstanceId, query, mode (autonomous / developer, matching the dashboard's Fleet Mode/Developer Mode filter), page, limit

Team-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/instances endpoint 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 passing
Manually verified via chat

Asking naturally and checking both the arguments the assistant chose and the response shape:

platform_list_hosted_instances

  • "What hosted instances are running?" (team-wide) → state: ["running"], includeLiveStatus: true
  • "Which instances are not running?" (team-wide) → state: ["notRunning"]
  • "Show me the instances that have crashed" (team-wide) → state: ["error"]
  • "Show me instances that are stopped or crashed" (team-wide) → state: ["error", "notRunning"]
  • "List the hosted instances in this application" (scoped to one application) → applicationId set, response has no pagination

platform_list_remote_instances

  • "What devices are in developer mode?" (team-wide) → mode: "developer"
  • "What devices are in developer mode in this application?" (scoped to one application) → applicationId + mode: "developer"
  • "Are any devices in fleet mode in this application?" (scoped to one application) → applicationId + mode: "autonomous"
  • "What devices are assigned to this instance?" (scoped to one hosted instance) → hostedInstanceId set
  • "List the devices in this team" (team-wide, no filters) → defaults to page: 1, limit: 10

Closes FlowFuse/engineering#184
Closes #7919

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.
@andypalmi
andypalmi requested a review from cstns July 29, 2026 09:40
@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.14286% with 39 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.13%. Comparing base (686d6dd) to head (d49ea47).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
forge/ee/lib/mcp/tools/devices.js 72.72% 12 Missing ⚠️
forge/ee/lib/mcp/tools/snapshots.js 0.00% 10 Missing ⚠️
forge/ee/lib/mcp/tools/applications.js 0.00% 8 Missing ⚠️
forge/ee/lib/mcp/tools/instances.js 88.13% 7 Missing ⚠️
forge/ee/lib/mcp/toolLoader.js 0.00% 2 Missing ⚠️
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     
Flag Coverage Δ
backend 76.13% <72.14%> (+0.20%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread forge/ee/lib/mcp/tools/devices.js Outdated
handler: async (args, { inject }) => {
let url = `/api/v1/teams/${args.teamId}/devices`
const params = []
const params = [`page=${args.page || 1}`, `limit=${args.limit || 10}`]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Comment thread forge/ee/lib/mcp/tools/devices.js Outdated
name: device.name,
ownerType: device.ownerType,
mode: device.mode,
status: device.status,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread forge/ee/lib/mcp/tools/instances.js Outdated
}

async function listTeamHostedInstances (args, { inject }) {
const params = [`page=${args.page || 1}`, `limit=${args.limit || 10}`]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UrlSearch params or the URL class for these actions please

Comment thread forge/ee/lib/mcp/tools/devices.js Outdated
ownerType: device.ownerType,
mode: device.mode,
status: device.status,
onlineStatus: device.onlineStatus,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onlineStatus is not a live MQTT query. It's computed from lastSeenAt using a 30-minute threshold.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
@andypalmi
andypalmi requested a review from cstns July 29, 2026 13:02
@cstns
cstns merged commit d4e6d57 into main Jul 29, 2026
29 checks passed
@cstns
cstns deleted the feat/mcp-consolidate-instance-device-list-tools branch July 29, 2026 13:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Agent truncates results when listing large sets of instances

2 participants