Skip to content

Commit 138f496

Browse files
author
andypalmi
committed
feat(mcp): add platform catalog get-by-id read tools
Add read-only MCP tools to fetch a stack, instance type, template or flow blueprint by id, with unit and equivalence tests. Allow-list their scopes for the expert-mcp platform token.
1 parent 103f9ae commit 138f496

3 files changed

Lines changed: 352 additions & 13 deletions

File tree

forge/ee/lib/mcp/tools/platform.js

Lines changed: 89 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,122 @@
1+
const { z } = require('zod')
2+
3+
const { basePagination, basePaginationKeys, searchQuery, appendQuery } = require('../schemas')
4+
15
module.exports = [
26
{
37
name: 'platform_list_hosted_instance_types',
48
title: 'List Hosted Instance Types',
5-
description: 'FlowFuse platform automation tool: List all available hosted instance types. Use this to find valid projectType values when creating a hosted instance.',
9+
description: 'FlowFuse platform automation tool: List all available hosted instance types. Use this to find valid projectType values when creating a hosted instance. Supports name search, active-state filtering and pagination.',
610
annotations: { readOnlyHint: true, destructiveHint: false },
7-
inputSchema: {},
11+
inputSchema: {
12+
...basePagination,
13+
...searchQuery,
14+
filter: z.enum(['all', 'active', 'inactive']).optional().describe('Which types to include by active state (default active only)')
15+
},
816
handler: async (args, { inject }) => {
9-
const response = await inject({ method: 'GET', url: '/api/v1/project-types' })
17+
const url = appendQuery('/api/v1/project-types', args, [...basePaginationKeys, 'query', 'filter'])
18+
const response = await inject({ method: 'GET', url })
1019
return response
1120
}
1221
},
1322
{
1423
name: 'platform_list_stacks',
1524
title: 'List Stacks',
16-
description: 'FlowFuse platform automation tool: List all available stacks (Node-RED versions). Use this to find valid stack values when creating a hosted instance.',
25+
description: 'FlowFuse platform automation tool: List all available stacks (Node-RED versions). Use this to find valid stack values when creating a hosted instance. Supports name search, filtering and pagination.',
1726
annotations: { readOnlyHint: true, destructiveHint: false },
18-
inputSchema: {},
27+
inputSchema: {
28+
...basePagination,
29+
...searchQuery,
30+
filter: z.string().optional().describe('Filter by active state ("all", "active", "inactive") or by replacement ("replacedBy:<stackId>")'),
31+
projectType: z.string().optional().describe('Only return stacks for this hosted instance type (project-type hashid)')
32+
},
1933
handler: async (args, { inject }) => {
20-
const response = await inject({ method: 'GET', url: '/api/v1/stacks' })
34+
const url = appendQuery('/api/v1/stacks', args, [...basePaginationKeys, 'query', 'filter', 'projectType'])
35+
const response = await inject({ method: 'GET', url })
2136
return response
2237
}
2338
},
2439
{
2540
name: 'platform_list_templates',
2641
title: 'List Templates',
27-
description: 'FlowFuse platform automation tool: List all available templates. When creating a hosted instance, if only one template exists, use it automatically. If multiple templates exist, ask the user which one to use.',
42+
description: 'FlowFuse platform automation tool: List all available templates. When creating a hosted instance, if only one template exists, use it automatically. If multiple templates exist, ask the user which one to use. Supports name search and pagination.',
2843
annotations: { readOnlyHint: true, destructiveHint: false },
29-
inputSchema: {},
44+
inputSchema: {
45+
...basePagination,
46+
...searchQuery
47+
},
3048
handler: async (args, { inject }) => {
31-
const response = await inject({ method: 'GET', url: '/api/v1/templates' })
49+
const url = appendQuery('/api/v1/templates', args, [...basePaginationKeys, 'query'])
50+
const response = await inject({ method: 'GET', url })
3251
return response
3352
}
3453
},
3554
{
3655
name: 'platform_list_blueprints',
3756
title: 'List Blueprints',
38-
description: 'FlowFuse platform automation tool: List all available flow blueprints. Blueprints provide starter flows that can be used when creating a new hosted instance.',
57+
description: 'FlowFuse platform automation tool: List all available flow blueprints. Blueprints provide starter flows that can be used when creating a new hosted instance. Supports name search, active-state filtering and pagination.',
3958
annotations: { readOnlyHint: true, destructiveHint: false },
40-
inputSchema: {},
59+
inputSchema: {
60+
...basePagination,
61+
...searchQuery,
62+
filter: z.enum(['all', 'active', 'inactive']).optional().describe('Which blueprints to include by active state (default active only)'),
63+
team: z.string().optional().describe('Team hashid to also include blueprints specific to that team')
64+
},
4165
handler: async (args, { inject }) => {
42-
const response = await inject({ method: 'GET', url: '/api/v1/flow-blueprints' })
66+
const url = appendQuery('/api/v1/flow-blueprints', args, [...basePaginationKeys, 'query', 'filter', 'team'])
67+
const response = await inject({ method: 'GET', url })
68+
return response
69+
}
70+
},
71+
{
72+
name: 'platform_get_hosted_instance_type',
73+
title: 'Get Hosted Instance Type',
74+
description: 'Get a single hosted instance type (project type) by id.',
75+
annotations: { readOnlyHint: true, destructiveHint: false },
76+
inputSchema: {
77+
instanceTypeId: z.string().describe('Project-type hashid identifying the hosted instance type')
78+
},
79+
handler: async (args, { inject }) => {
80+
const response = await inject({ method: 'GET', url: `/api/v1/project-types/${args.instanceTypeId}` })
81+
return response
82+
}
83+
},
84+
{
85+
name: 'platform_get_stack',
86+
title: 'Get Stack',
87+
description: 'Get a single stack by id.',
88+
annotations: { readOnlyHint: true, destructiveHint: false },
89+
inputSchema: {
90+
stackId: z.string().describe('Stack hashid')
91+
},
92+
handler: async (args, { inject }) => {
93+
const response = await inject({ method: 'GET', url: `/api/v1/stacks/${args.stackId}` })
94+
return response
95+
}
96+
},
97+
{
98+
name: 'platform_get_template',
99+
title: 'Get Template',
100+
description: 'Get a single template by id. Env values are blanked in the response.',
101+
annotations: { readOnlyHint: true, destructiveHint: false },
102+
inputSchema: {
103+
templateId: z.string().describe('Template hashid')
104+
},
105+
handler: async (args, { inject }) => {
106+
const response = await inject({ method: 'GET', url: `/api/v1/templates/${args.templateId}` })
107+
return response
108+
}
109+
},
110+
{
111+
name: 'platform_get_blueprint',
112+
title: 'Get Blueprint',
113+
description: 'Get a single flow blueprint by id.',
114+
annotations: { readOnlyHint: true, destructiveHint: false },
115+
inputSchema: {
116+
flowBlueprintId: z.string().describe('Flow blueprint hashid')
117+
},
118+
handler: async (args, { inject }) => {
119+
const response = await inject({ method: 'GET', url: `/api/v1/flow-blueprints/${args.flowBlueprintId}` })
43120
return response
44121
}
45122
}

forge/routes/auth/permissions.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,12 @@ const IMPLICIT_TOKEN_SCOPES = {
5050
'team:read', // get team details
5151
// platform
5252
'stack:list',
53+
'stack:read',
5354
'flow-blueprint:list',
55+
'flow-blueprint:read',
5456
'project:status',
55-
'template:list'
57+
'template:list',
58+
'template:read'
5659
]
5760
}
5861

0 commit comments

Comments
 (0)