wolfpack-mcp 1.0.58 → 1.0.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agentBuilderTools.js +63 -11
- package/dist/agentSelfTools.js +1 -1
- package/dist/client.js +12 -2
- package/dist/index.js +49 -12
- package/dist/procedureTools.js +119 -8
- package/dist/resolveRadarItemId.js +22 -0
- package/dist/resolveRadarItemId.test.js +31 -0
- package/package.json +1 -1
|
@@ -37,16 +37,19 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
name: 'create_agent',
|
|
40
|
-
description: 'Create a new agent from a
|
|
40
|
+
description: 'Create a new agent from a container image. Use list_container_images to see available images.',
|
|
41
41
|
inputSchema: {
|
|
42
42
|
type: 'object',
|
|
43
43
|
properties: {
|
|
44
|
-
|
|
44
|
+
container_image_id: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
description: 'Container image ID to create the agent from',
|
|
47
|
+
},
|
|
45
48
|
name: { type: 'string', description: 'Display name for the agent' },
|
|
46
49
|
config: { type: 'object', description: 'Optional agent-specific configuration' },
|
|
47
50
|
...ORG_SLUG_PROP,
|
|
48
51
|
},
|
|
49
|
-
required: ['
|
|
52
|
+
required: ['container_image_id', 'name'],
|
|
50
53
|
},
|
|
51
54
|
},
|
|
52
55
|
{
|
|
@@ -71,7 +74,7 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
71
74
|
},
|
|
72
75
|
instructions: {
|
|
73
76
|
type: 'string',
|
|
74
|
-
description: 'Agent-specific instructions
|
|
77
|
+
description: 'Agent-specific instructions appended to the system prompt. Set to null to clear.',
|
|
75
78
|
},
|
|
76
79
|
llm_provider: {
|
|
77
80
|
type: 'string',
|
|
@@ -91,6 +94,37 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
91
94
|
required: ['agent_id'],
|
|
92
95
|
},
|
|
93
96
|
},
|
|
97
|
+
{
|
|
98
|
+
name: 'get_agent_mcp_selections',
|
|
99
|
+
description: "List the template MCP servers this agent has opted out of. Returns { disabled: string[] } where each string is the MCP server key from the agent's template.",
|
|
100
|
+
inputSchema: {
|
|
101
|
+
type: 'object',
|
|
102
|
+
properties: {
|
|
103
|
+
agent_id: { type: 'string', description: 'Agent profile ID' },
|
|
104
|
+
...ORG_SLUG_PROP,
|
|
105
|
+
},
|
|
106
|
+
required: ['agent_id'],
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: 'update_agent_mcp_selections',
|
|
111
|
+
description: 'Replace the list of template MCP servers this agent has opted out of. ' +
|
|
112
|
+
'Pass an empty array to re-enable every template server. Template servers ' +
|
|
113
|
+
'not in the list stay enabled; those in the list are disabled for this agent.',
|
|
114
|
+
inputSchema: {
|
|
115
|
+
type: 'object',
|
|
116
|
+
properties: {
|
|
117
|
+
agent_id: { type: 'string', description: 'Agent profile ID' },
|
|
118
|
+
disabled: {
|
|
119
|
+
type: 'array',
|
|
120
|
+
items: { type: 'string' },
|
|
121
|
+
description: 'MCP server keys (from the template) to disable for this agent',
|
|
122
|
+
},
|
|
123
|
+
...ORG_SLUG_PROP,
|
|
124
|
+
},
|
|
125
|
+
required: ['agent_id', 'disabled'],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
94
128
|
// ─── Group 2: Project Assignment ─────────────────────────────────────────
|
|
95
129
|
{
|
|
96
130
|
name: 'list_agent_projects',
|
|
@@ -651,8 +685,8 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
651
685
|
},
|
|
652
686
|
// ─── Group 7: Discovery ───────────────────────────────────────────────────
|
|
653
687
|
{
|
|
654
|
-
name: '
|
|
655
|
-
description: 'List agent
|
|
688
|
+
name: 'list_container_images',
|
|
689
|
+
description: 'List agent-capable container images assigned to the organisation. Use image IDs when calling create_agent.',
|
|
656
690
|
inputSchema: { type: 'object', properties: { ...ORG_SLUG_PROP } },
|
|
657
691
|
},
|
|
658
692
|
{
|
|
@@ -704,14 +738,14 @@ export async function handleAgentBuilderTool(name, args, client) {
|
|
|
704
738
|
case 'create_agent': {
|
|
705
739
|
const parsed = z
|
|
706
740
|
.object({
|
|
707
|
-
|
|
741
|
+
container_image_id: z.string(),
|
|
708
742
|
name: z.string(),
|
|
709
743
|
config: z.record(z.unknown()).optional(),
|
|
710
744
|
org_slug: orgSlugField,
|
|
711
745
|
})
|
|
712
746
|
.parse(args);
|
|
713
747
|
const agent = await client.createAgent({
|
|
714
|
-
|
|
748
|
+
containerImageId: parsed.container_image_id,
|
|
715
749
|
name: parsed.name,
|
|
716
750
|
config: parsed.config,
|
|
717
751
|
}, resolveOrg(parsed));
|
|
@@ -763,6 +797,24 @@ export async function handleAgentBuilderTool(name, args, client) {
|
|
|
763
797
|
const agent = await client.updateAgent(agent_id, fields, resolveOrg(parsed));
|
|
764
798
|
return { content: [{ type: 'text', text: `Updated agent\n\n${text(agent)}` }] };
|
|
765
799
|
}
|
|
800
|
+
case 'get_agent_mcp_selections': {
|
|
801
|
+
const parsed = AgentIdSchema.parse(args);
|
|
802
|
+
const selections = await client.getAgentMcpSelections(parsed.agent_id, resolveOrg(parsed));
|
|
803
|
+
return { content: [{ type: 'text', text: text(selections) }] };
|
|
804
|
+
}
|
|
805
|
+
case 'update_agent_mcp_selections': {
|
|
806
|
+
const parsed = z
|
|
807
|
+
.object({
|
|
808
|
+
agent_id: z.string(),
|
|
809
|
+
disabled: z.array(z.string()),
|
|
810
|
+
org_slug: orgSlugField,
|
|
811
|
+
})
|
|
812
|
+
.parse(args);
|
|
813
|
+
const selections = await client.updateAgentMcpSelections(parsed.agent_id, parsed.disabled, resolveOrg(parsed));
|
|
814
|
+
return {
|
|
815
|
+
content: [{ type: 'text', text: `Updated MCP selections\n\n${text(selections)}` }],
|
|
816
|
+
};
|
|
817
|
+
}
|
|
766
818
|
// ─── Project Assignment ─────────────────────────────────────────────────
|
|
767
819
|
case 'list_agent_projects': {
|
|
768
820
|
const parsed = AgentIdSchema.parse(args);
|
|
@@ -1201,10 +1253,10 @@ export async function handleAgentBuilderTool(name, args, client) {
|
|
|
1201
1253
|
return { content: [{ type: 'text', text: `Set secret "${secret.name}"` }] };
|
|
1202
1254
|
}
|
|
1203
1255
|
// ─── Discovery ────────────────────────────────────────────────────────────
|
|
1204
|
-
case '
|
|
1256
|
+
case 'list_container_images': {
|
|
1205
1257
|
const parsed = z.object({ org_slug: orgSlugField }).parse(args);
|
|
1206
|
-
const
|
|
1207
|
-
return { content: [{ type: 'text', text: text(
|
|
1258
|
+
const images = await client.listContainerImages(resolveOrg(parsed));
|
|
1259
|
+
return { content: [{ type: 'text', text: text(images) }] };
|
|
1208
1260
|
}
|
|
1209
1261
|
case 'list_llm_models': {
|
|
1210
1262
|
const parsed = z.object({ org_slug: orgSlugField }).parse(args);
|
package/dist/agentSelfTools.js
CHANGED
|
@@ -6,7 +6,7 @@ function text(data) {
|
|
|
6
6
|
export const AGENT_SELF_TOOLS = [
|
|
7
7
|
{
|
|
8
8
|
name: 'get_self',
|
|
9
|
-
description: 'Get your own agent profile with full composed context:
|
|
9
|
+
description: 'Get your own agent profile with full composed context: system prompt, instructions, skills, LLM model, and assigned projects. ' +
|
|
10
10
|
'Use this to understand how you are configured and what capabilities you have. ' +
|
|
11
11
|
'Essential for self-reflection and improvement proposals.',
|
|
12
12
|
inputSchema: { type: 'object', properties: {} },
|
package/dist/client.js
CHANGED
|
@@ -604,6 +604,12 @@ export class WolfpackClient {
|
|
|
604
604
|
async updateAgent(agentId, body, orgSlug) {
|
|
605
605
|
return this.api.patch(this.withOrgSlug(`/agents/${agentId}`, orgSlug), body);
|
|
606
606
|
}
|
|
607
|
+
async getAgentMcpSelections(agentId, orgSlug) {
|
|
608
|
+
return this.api.get(this.withOrgSlug(`/agents/${agentId}/mcp-selections`, orgSlug));
|
|
609
|
+
}
|
|
610
|
+
async updateAgentMcpSelections(agentId, disabled, orgSlug) {
|
|
611
|
+
return this.api.patch(this.withOrgSlug(`/agents/${agentId}/mcp-selections`, orgSlug), { disabled });
|
|
612
|
+
}
|
|
607
613
|
// ─── Agent Builder: Project Assignment ────────────────────────────────────
|
|
608
614
|
async listAgentProjects(agentId, orgSlug) {
|
|
609
615
|
return this.api.get(this.withOrgSlug(`/agents/${agentId}/projects`, orgSlug));
|
|
@@ -761,8 +767,8 @@ export class WolfpackClient {
|
|
|
761
767
|
}
|
|
762
768
|
}
|
|
763
769
|
// ─── Agent Builder: Discovery ──────────────────────────────────────────────
|
|
764
|
-
async
|
|
765
|
-
return this.api.get(this.withOrgSlug('/agent-builder/
|
|
770
|
+
async listContainerImages(orgSlug) {
|
|
771
|
+
return this.api.get(this.withOrgSlug('/agent-builder/container-images', orgSlug));
|
|
766
772
|
}
|
|
767
773
|
async listLlmModels(orgSlug) {
|
|
768
774
|
return this.api.get(this.withOrgSlug('/agent-builder/llm-models', orgSlug));
|
|
@@ -820,6 +826,10 @@ export class WolfpackClient {
|
|
|
820
826
|
await this.api.delete(this.withTeamSlug(`/procedures/${procedureId}`, teamSlug));
|
|
821
827
|
return { success: true };
|
|
822
828
|
}
|
|
829
|
+
async startCase(procedureId, data) {
|
|
830
|
+
const { teamSlug, ...body } = data;
|
|
831
|
+
return this.api.post(this.withTeamSlug(`/procedures/${procedureId}/start`, teamSlug), body);
|
|
832
|
+
}
|
|
823
833
|
// ─── Agent Self-Introspection ──────────────────────────────────────────────
|
|
824
834
|
async getSelf() {
|
|
825
835
|
return this.api.get('/self');
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { validateConfig, config } from './config.js';
|
|
|
11
11
|
import { AGENT_BUILDER_TOOLS, handleAgentBuilderTool } from './agentBuilderTools.js';
|
|
12
12
|
import { PROCEDURE_TOOLS, handleProcedureTool } from './procedureTools.js';
|
|
13
13
|
import { AGENT_SELF_TOOLS, handleAgentSelfTool } from './agentSelfTools.js';
|
|
14
|
+
import { resolveRadarItemId } from './resolveRadarItemId.js';
|
|
14
15
|
// Get current package version
|
|
15
16
|
const require = createRequire(import.meta.url);
|
|
16
17
|
const packageJson = require('../package.json');
|
|
@@ -76,7 +77,10 @@ const ListWorkItemsSchema = z.object({
|
|
|
76
77
|
.string()
|
|
77
78
|
.optional()
|
|
78
79
|
.describe('Filter by priority level (0-4, higher is more important)'),
|
|
79
|
-
radar_item_id: z.coerce
|
|
80
|
+
radar_item_id: z.coerce
|
|
81
|
+
.string()
|
|
82
|
+
.optional()
|
|
83
|
+
.describe('Filter by linked radar/initiative item. Accepts refId ("5" or "#r5"), UUID, or "none"/"any"/"all".'),
|
|
80
84
|
created_by_id: z.coerce.string().optional().describe('Filter by creator user ID'),
|
|
81
85
|
updated_by_id: z.coerce.string().optional().describe('Filter by last updater user ID'),
|
|
82
86
|
sort_by: z.coerce
|
|
@@ -135,7 +139,7 @@ const UpdateWorkItemSchema = z.object({
|
|
|
135
139
|
.string()
|
|
136
140
|
.nullable()
|
|
137
141
|
.optional()
|
|
138
|
-
.describe('
|
|
142
|
+
.describe('Link to a radar/initiative item. Accepts refId ("5" or "#r5") or UUID. Use null to unlink.'),
|
|
139
143
|
category_id: z
|
|
140
144
|
.string()
|
|
141
145
|
.nullable()
|
|
@@ -288,7 +292,10 @@ const CreateWorkItemSchema = z.object({
|
|
|
288
292
|
.describe('Size estimate: "S" (small), "M" (medium), "L" (large)'),
|
|
289
293
|
leading_user_id: z.string().optional().describe('User ID to assign as leading user'),
|
|
290
294
|
category_id: z.string().optional().describe('Category ID to organize the work item'),
|
|
291
|
-
radar_item_id: z
|
|
295
|
+
radar_item_id: z
|
|
296
|
+
.string()
|
|
297
|
+
.optional()
|
|
298
|
+
.describe('Link to a radar/initiative item. Accepts refId ("5" or "#r5") or UUID.'),
|
|
292
299
|
});
|
|
293
300
|
const CreateIssueSchema = z.object({
|
|
294
301
|
project_slug: z
|
|
@@ -374,6 +381,10 @@ const UpdateWikiPageSchema = z.object({
|
|
|
374
381
|
page_id: z.string().describe('The page slug'),
|
|
375
382
|
title: z.string().optional().describe('Updated title'),
|
|
376
383
|
content: z.string().optional().describe('Updated content (markdown)'),
|
|
384
|
+
tag_ids: z
|
|
385
|
+
.array(z.string())
|
|
386
|
+
.optional()
|
|
387
|
+
.describe('Tag IDs to apply to the page (replaces existing tags)'),
|
|
377
388
|
project_slug: z
|
|
378
389
|
.string()
|
|
379
390
|
.optional()
|
|
@@ -410,6 +421,7 @@ const UpdateJournalEntrySchema = z.object({
|
|
|
410
421
|
entry_id: z.string().describe('The entry refId (number)'),
|
|
411
422
|
title: z.string().optional().describe('Updated title'),
|
|
412
423
|
content: z.string().optional().describe('Updated content (markdown)'),
|
|
424
|
+
tag_ids: z.array(z.string()).optional().describe('Tag IDs to apply (replaces existing tags)'),
|
|
413
425
|
project_slug: z
|
|
414
426
|
.string()
|
|
415
427
|
.optional()
|
|
@@ -703,7 +715,8 @@ class WolfpackMCPServer {
|
|
|
703
715
|
},
|
|
704
716
|
radar_item_id: {
|
|
705
717
|
type: 'string',
|
|
706
|
-
description: 'Filter by linked radar/initiative item
|
|
718
|
+
description: 'Filter by linked radar/initiative item. Accepts refId (e.g. "5" or "#r5"), UUID, ' +
|
|
719
|
+
'or one of "none" (no link), "any" (has any link), "all" (no filter).',
|
|
707
720
|
},
|
|
708
721
|
sort_by: {
|
|
709
722
|
type: 'string',
|
|
@@ -817,7 +830,7 @@ class WolfpackMCPServer {
|
|
|
817
830
|
},
|
|
818
831
|
radar_item_id: {
|
|
819
832
|
type: ['string', 'null'],
|
|
820
|
-
description: '
|
|
833
|
+
description: 'Link to a radar/initiative item. Accepts refId (e.g. "5" or "#r5") or UUID. Use null to unlink.',
|
|
821
834
|
},
|
|
822
835
|
category_id: {
|
|
823
836
|
type: ['string', 'null'],
|
|
@@ -1140,7 +1153,7 @@ class WolfpackMCPServer {
|
|
|
1140
1153
|
},
|
|
1141
1154
|
radar_item_id: {
|
|
1142
1155
|
type: 'string',
|
|
1143
|
-
description: '
|
|
1156
|
+
description: 'Link to a radar/initiative item. Accepts refId (e.g. "5" or "#r5") or UUID.',
|
|
1144
1157
|
},
|
|
1145
1158
|
},
|
|
1146
1159
|
required: ['title'],
|
|
@@ -1307,6 +1320,11 @@ class WolfpackMCPServer {
|
|
|
1307
1320
|
type: 'string',
|
|
1308
1321
|
description: 'Updated content (markdown)',
|
|
1309
1322
|
},
|
|
1323
|
+
tag_ids: {
|
|
1324
|
+
type: 'array',
|
|
1325
|
+
items: { type: 'string' },
|
|
1326
|
+
description: 'Tag IDs to apply to the page (replaces existing tags). Use list tags endpoint to get available tag IDs.',
|
|
1327
|
+
},
|
|
1310
1328
|
project_slug: {
|
|
1311
1329
|
type: 'string',
|
|
1312
1330
|
description: 'Project slug (required for multi-project users, use list_projects to get slugs)',
|
|
@@ -1392,6 +1410,11 @@ class WolfpackMCPServer {
|
|
|
1392
1410
|
type: 'string',
|
|
1393
1411
|
description: 'Updated content (markdown)',
|
|
1394
1412
|
},
|
|
1413
|
+
tag_ids: {
|
|
1414
|
+
type: 'array',
|
|
1415
|
+
items: { type: 'string' },
|
|
1416
|
+
description: 'Tag IDs to apply (replaces existing tags)',
|
|
1417
|
+
},
|
|
1395
1418
|
project_slug: {
|
|
1396
1419
|
type: 'string',
|
|
1397
1420
|
description: 'Project slug (required for multi-project users, use list_projects to get slugs)',
|
|
@@ -1909,14 +1932,19 @@ class WolfpackMCPServer {
|
|
|
1909
1932
|
// Work Item handlers
|
|
1910
1933
|
case 'list_work_items': {
|
|
1911
1934
|
const parsed = ListWorkItemsSchema.parse(args);
|
|
1935
|
+
const teamSlug = parsed.project_slug || this.client.getProjectSlug() || undefined;
|
|
1936
|
+
const RADAR_FILTER_PASSTHROUGH = new Set(['none', 'any', 'all']);
|
|
1937
|
+
const radarItemIdFilter = parsed.radar_item_id && !RADAR_FILTER_PASSTHROUGH.has(parsed.radar_item_id)
|
|
1938
|
+
? await resolveRadarItemId(this.client, parsed.radar_item_id, teamSlug)
|
|
1939
|
+
: parsed.radar_item_id;
|
|
1912
1940
|
const result = await this.client.listWorkItems({
|
|
1913
|
-
teamSlug
|
|
1941
|
+
teamSlug,
|
|
1914
1942
|
status: parsed.status,
|
|
1915
1943
|
assignedToId: parsed.assigned_to_id,
|
|
1916
1944
|
search: parsed.search,
|
|
1917
1945
|
categoryId: parsed.category_id,
|
|
1918
1946
|
priority: parsed.priority,
|
|
1919
|
-
radarItemId:
|
|
1947
|
+
radarItemId: radarItemIdFilter,
|
|
1920
1948
|
createdById: parsed.created_by_id,
|
|
1921
1949
|
updatedById: parsed.updated_by_id,
|
|
1922
1950
|
sortBy: parsed.sort_by,
|
|
@@ -1984,8 +2012,11 @@ class WolfpackMCPServer {
|
|
|
1984
2012
|
fields.status = parsed.status;
|
|
1985
2013
|
if (parsed.leading_user_id !== undefined)
|
|
1986
2014
|
fields.leadingUserId = parsed.leading_user_id;
|
|
1987
|
-
if (parsed.radar_item_id !== undefined)
|
|
1988
|
-
fields.radarItemId = parsed.radar_item_id
|
|
2015
|
+
if (parsed.radar_item_id !== undefined) {
|
|
2016
|
+
fields.radarItemId = parsed.radar_item_id
|
|
2017
|
+
? await resolveRadarItemId(this.client, parsed.radar_item_id, teamSlug)
|
|
2018
|
+
: null;
|
|
2019
|
+
}
|
|
1989
2020
|
if (parsed.category_id !== undefined)
|
|
1990
2021
|
fields.categoryId = parsed.category_id;
|
|
1991
2022
|
if (parsed.priority !== undefined)
|
|
@@ -2207,6 +2238,10 @@ class WolfpackMCPServer {
|
|
|
2207
2238
|
// Create/Update handlers
|
|
2208
2239
|
case 'create_work_item': {
|
|
2209
2240
|
const parsed = CreateWorkItemSchema.parse(args);
|
|
2241
|
+
const teamSlug = parsed.project_slug || this.client.getProjectSlug() || undefined;
|
|
2242
|
+
const radarItemId = parsed.radar_item_id
|
|
2243
|
+
? await resolveRadarItemId(this.client, parsed.radar_item_id, teamSlug)
|
|
2244
|
+
: undefined;
|
|
2210
2245
|
const workItem = await this.client.createWorkItem({
|
|
2211
2246
|
title: parsed.title,
|
|
2212
2247
|
description: parsed.description,
|
|
@@ -2215,8 +2250,8 @@ class WolfpackMCPServer {
|
|
|
2215
2250
|
size: parsed.size,
|
|
2216
2251
|
leadingUserId: parsed.leading_user_id,
|
|
2217
2252
|
categoryId: parsed.category_id,
|
|
2218
|
-
radarItemId
|
|
2219
|
-
teamSlug
|
|
2253
|
+
radarItemId,
|
|
2254
|
+
teamSlug,
|
|
2220
2255
|
});
|
|
2221
2256
|
return {
|
|
2222
2257
|
content: [
|
|
@@ -2333,6 +2368,7 @@ class WolfpackMCPServer {
|
|
|
2333
2368
|
const page = await this.client.updateWikiPage(parsed.page_id, {
|
|
2334
2369
|
title: parsed.title,
|
|
2335
2370
|
content: parsed.content,
|
|
2371
|
+
tagIds: parsed.tag_ids,
|
|
2336
2372
|
}, teamSlug);
|
|
2337
2373
|
return {
|
|
2338
2374
|
content: [
|
|
@@ -2396,6 +2432,7 @@ class WolfpackMCPServer {
|
|
|
2396
2432
|
const entry = await this.client.updateJournalEntry(parsed.entry_id, {
|
|
2397
2433
|
title: parsed.title,
|
|
2398
2434
|
content: parsed.content,
|
|
2435
|
+
tagIds: parsed.tag_ids,
|
|
2399
2436
|
}, teamSlug);
|
|
2400
2437
|
return {
|
|
2401
2438
|
content: [
|
package/dist/procedureTools.js
CHANGED
|
@@ -3,11 +3,37 @@
|
|
|
3
3
|
* Enables agents to list, view, create, update, and delete procedures.
|
|
4
4
|
*/
|
|
5
5
|
import { z } from 'zod';
|
|
6
|
+
const FORM_FIELD_ITEM_SCHEMA = {
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: {
|
|
9
|
+
name: { type: 'string' },
|
|
10
|
+
label: { type: 'string' },
|
|
11
|
+
type: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
enum: ['text', 'number', 'boolean', 'choice', 'date', 'json'],
|
|
14
|
+
},
|
|
15
|
+
required: { type: 'boolean' },
|
|
16
|
+
options: {
|
|
17
|
+
type: 'array',
|
|
18
|
+
items: {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
value: { type: 'string' },
|
|
22
|
+
label: { type: 'string' },
|
|
23
|
+
},
|
|
24
|
+
required: ['value', 'label'],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
multiline: { type: 'boolean' },
|
|
28
|
+
},
|
|
29
|
+
required: ['name', 'label', 'type', 'required'],
|
|
30
|
+
};
|
|
6
31
|
export const PROCEDURE_TOOLS = [
|
|
7
32
|
{
|
|
8
33
|
name: 'list_procedures',
|
|
9
|
-
description: 'List procedures in a project. Returns name, status, version, tags,
|
|
10
|
-
'
|
|
34
|
+
description: 'List procedures in a project. Returns name, status, version, tags, case count, and hasForm ' +
|
|
35
|
+
'(true when the procedure declares an input form that must be provided at start_case time). ' +
|
|
36
|
+
'Use get_procedure to see the full workflow definition and form schema.',
|
|
11
37
|
inputSchema: {
|
|
12
38
|
type: 'object',
|
|
13
39
|
properties: {
|
|
@@ -24,10 +50,12 @@ export const PROCEDURE_TOOLS = [
|
|
|
24
50
|
},
|
|
25
51
|
{
|
|
26
52
|
name: 'get_procedure',
|
|
27
|
-
description: 'Get full details of a procedure including its workflow definition (nodes and edges)
|
|
28
|
-
'The definition uses a graph format: nodes have id, type
|
|
29
|
-
'position, and data; edges connect nodes via
|
|
30
|
-
'
|
|
53
|
+
description: 'Get full details of a procedure including its workflow definition (nodes and edges) and its ' +
|
|
54
|
+
'optional input form (formDefinition). The definition uses a graph format: nodes have id, type ' +
|
|
55
|
+
'(start/end/activity/decision/loop/parallel/wait), position, and data; edges connect nodes via ' +
|
|
56
|
+
'source/target IDs. The formDefinition lists input fields (name, label, type, required, options, ' +
|
|
57
|
+
'multiline) validated at start_case time — inspect it before calling start_case so you know which ' +
|
|
58
|
+
'form_values to supply. Accepts UUID or refId (requires project_slug for refId).',
|
|
31
59
|
inputSchema: {
|
|
32
60
|
type: 'object',
|
|
33
61
|
properties: {
|
|
@@ -48,7 +76,8 @@ export const PROCEDURE_TOOLS = [
|
|
|
48
76
|
description: 'Create a new procedure. If no definition is provided, creates a default start→end workflow. ' +
|
|
49
77
|
'The definition is a graph with nodes and edges. Node types: start, end, activity, decision, loop, parallel, wait. ' +
|
|
50
78
|
'Each node needs id, type, position ({x, y}), and data (type-specific config). ' +
|
|
51
|
-
'Edges connect nodes: {id, source, target}.
|
|
79
|
+
'Edges connect nodes: {id, source, target}. Optionally provide form_definition to declare inputs ' +
|
|
80
|
+
'collected each time the procedure is started. The procedure starts as inactive (isActive=false).',
|
|
52
81
|
inputSchema: {
|
|
53
82
|
type: 'object',
|
|
54
83
|
properties: {
|
|
@@ -58,6 +87,11 @@ export const PROCEDURE_TOOLS = [
|
|
|
58
87
|
type: 'object',
|
|
59
88
|
description: 'Workflow definition: { nodes: [{id, type, position: {x, y}, data: {...}}], edges: [{id, source, target}] }',
|
|
60
89
|
},
|
|
90
|
+
form_definition: {
|
|
91
|
+
type: ['array', 'null'],
|
|
92
|
+
description: 'Input form: array of {name, label, type, required, options?, multiline?}. Types: text, number, boolean, choice, date, json. Required fields must be supplied when start_case runs. Scheduled runs ignore the form.',
|
|
93
|
+
items: FORM_FIELD_ITEM_SCHEMA,
|
|
94
|
+
},
|
|
61
95
|
tags: {
|
|
62
96
|
type: 'array',
|
|
63
97
|
items: { type: 'string' },
|
|
@@ -76,6 +110,7 @@ export const PROCEDURE_TOOLS = [
|
|
|
76
110
|
description: 'Update a procedure. Only provide the fields you want to change. ' +
|
|
77
111
|
'Updating the definition auto-increments the version number. ' +
|
|
78
112
|
'Set isActive to true/false to activate/deactivate the procedure. ' +
|
|
113
|
+
'Pass form_definition to replace the input form (or null to remove it). ' +
|
|
79
114
|
'Accepts UUID or refId (requires project_slug for refId).',
|
|
80
115
|
inputSchema: {
|
|
81
116
|
type: 'object',
|
|
@@ -97,6 +132,11 @@ export const PROCEDURE_TOOLS = [
|
|
|
97
132
|
type: 'object',
|
|
98
133
|
description: 'Updated workflow definition: { nodes: [...], edges: [...] }. Changes increment version.',
|
|
99
134
|
},
|
|
135
|
+
form_definition: {
|
|
136
|
+
type: ['array', 'null'],
|
|
137
|
+
description: 'Replace the input form. Same shape as create_procedure.form_definition. Pass null to remove the form entirely.',
|
|
138
|
+
items: FORM_FIELD_ITEM_SCHEMA,
|
|
139
|
+
},
|
|
100
140
|
is_active: { type: 'boolean', description: 'Activate or deactivate the procedure' },
|
|
101
141
|
tags: {
|
|
102
142
|
type: 'array',
|
|
@@ -111,6 +151,45 @@ export const PROCEDURE_TOOLS = [
|
|
|
111
151
|
required: ['procedure_id'],
|
|
112
152
|
},
|
|
113
153
|
},
|
|
154
|
+
{
|
|
155
|
+
name: 'start_case',
|
|
156
|
+
description: 'Start a new case by running an active procedure. If the procedure has a form_definition ' +
|
|
157
|
+
'(see get_procedure), supply form_values: an object keyed by each field.name. Required fields ' +
|
|
158
|
+
'must be present; values are type-checked and unknown keys are dropped server-side. Returns the ' +
|
|
159
|
+
'caseId and refId of the new case. Scheduled procedures cannot be started this way — they fire ' +
|
|
160
|
+
'on their schedule.',
|
|
161
|
+
inputSchema: {
|
|
162
|
+
type: 'object',
|
|
163
|
+
properties: {
|
|
164
|
+
procedure_id: {
|
|
165
|
+
type: 'string',
|
|
166
|
+
description: 'Procedure UUID or refId number',
|
|
167
|
+
},
|
|
168
|
+
title: {
|
|
169
|
+
type: 'string',
|
|
170
|
+
description: 'Case title (defaults to the procedure name)',
|
|
171
|
+
},
|
|
172
|
+
priority: {
|
|
173
|
+
type: 'number',
|
|
174
|
+
description: 'Priority: 0 (none), 1 (low), 2 (medium), 3 (high), 4 (urgent)',
|
|
175
|
+
},
|
|
176
|
+
additional_information: {
|
|
177
|
+
type: 'string',
|
|
178
|
+
description: 'Free-text context displayed on the case detail page',
|
|
179
|
+
},
|
|
180
|
+
form_values: {
|
|
181
|
+
type: 'object',
|
|
182
|
+
description: "Input values keyed by field name, matching the procedure's form_definition. Validated server-side: missing required fields → 400; wrong types → 400; unknown keys silently dropped.",
|
|
183
|
+
additionalProperties: true,
|
|
184
|
+
},
|
|
185
|
+
project_slug: {
|
|
186
|
+
type: 'string',
|
|
187
|
+
description: 'Project slug (required when using refId)',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
required: ['procedure_id'],
|
|
191
|
+
},
|
|
192
|
+
},
|
|
114
193
|
];
|
|
115
194
|
export async function handleProcedureTool(name, args, client) {
|
|
116
195
|
const text = (t) => JSON.stringify(t, null, 2);
|
|
@@ -152,6 +231,7 @@ export async function handleProcedureTool(name, args, client) {
|
|
|
152
231
|
name: z.string(),
|
|
153
232
|
description: z.string().optional(),
|
|
154
233
|
definition: z.preprocess((v) => (typeof v === 'string' ? JSON.parse(v) : v), z.any().optional()),
|
|
234
|
+
form_definition: z.preprocess((v) => (typeof v === 'string' ? JSON.parse(v) : v), z.any().optional()),
|
|
155
235
|
tags: z.array(z.string()).optional(),
|
|
156
236
|
project_slug: z.string().optional(),
|
|
157
237
|
})
|
|
@@ -160,6 +240,7 @@ export async function handleProcedureTool(name, args, client) {
|
|
|
160
240
|
name: parsed.name,
|
|
161
241
|
description: parsed.description,
|
|
162
242
|
definition: parsed.definition,
|
|
243
|
+
formDefinition: parsed.form_definition,
|
|
163
244
|
tags: parsed.tags,
|
|
164
245
|
teamSlug: parsed.project_slug,
|
|
165
246
|
});
|
|
@@ -180,15 +261,18 @@ export async function handleProcedureTool(name, args, client) {
|
|
|
180
261
|
description: z.string().nullable().optional(),
|
|
181
262
|
information: z.string().nullable().optional(),
|
|
182
263
|
definition: z.preprocess((v) => (typeof v === 'string' ? JSON.parse(v) : v), z.any().optional()),
|
|
264
|
+
form_definition: z.preprocess((v) => (typeof v === 'string' ? JSON.parse(v) : v), z.any().optional()),
|
|
183
265
|
is_active: z.preprocess((v) => (typeof v === 'string' ? v === 'true' : v), z.boolean().optional()),
|
|
184
266
|
tags: z.array(z.string()).optional(),
|
|
185
267
|
project_slug: z.string().optional(),
|
|
186
268
|
})
|
|
187
269
|
.parse(args);
|
|
188
|
-
const { procedure_id, project_slug, is_active, ...rest } = parsed;
|
|
270
|
+
const { procedure_id, project_slug, is_active, form_definition, ...rest } = parsed;
|
|
189
271
|
const data = { ...rest };
|
|
190
272
|
if (is_active !== undefined)
|
|
191
273
|
data.isActive = is_active;
|
|
274
|
+
if (form_definition !== undefined)
|
|
275
|
+
data.formDefinition = form_definition;
|
|
192
276
|
const procedure = await client.updateProcedure(procedure_id, data, project_slug);
|
|
193
277
|
return {
|
|
194
278
|
content: [
|
|
@@ -199,6 +283,33 @@ export async function handleProcedureTool(name, args, client) {
|
|
|
199
283
|
],
|
|
200
284
|
};
|
|
201
285
|
}
|
|
286
|
+
case 'start_case': {
|
|
287
|
+
const parsed = z
|
|
288
|
+
.object({
|
|
289
|
+
procedure_id: z.string(),
|
|
290
|
+
title: z.string().optional(),
|
|
291
|
+
priority: z.coerce.number().optional(),
|
|
292
|
+
additional_information: z.string().optional(),
|
|
293
|
+
form_values: z.preprocess((v) => (typeof v === 'string' ? JSON.parse(v) : v), z.record(z.unknown()).optional()),
|
|
294
|
+
project_slug: z.string().optional(),
|
|
295
|
+
})
|
|
296
|
+
.parse(args);
|
|
297
|
+
const result = await client.startCase(parsed.procedure_id, {
|
|
298
|
+
title: parsed.title,
|
|
299
|
+
priority: parsed.priority,
|
|
300
|
+
additionalInformation: parsed.additional_information,
|
|
301
|
+
formValues: parsed.form_values,
|
|
302
|
+
teamSlug: parsed.project_slug,
|
|
303
|
+
});
|
|
304
|
+
return {
|
|
305
|
+
content: [
|
|
306
|
+
{
|
|
307
|
+
type: 'text',
|
|
308
|
+
text: `Started case #${result.refId} (${result.caseId})\n\n${text(result)}`,
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
};
|
|
312
|
+
}
|
|
202
313
|
default:
|
|
203
314
|
return {
|
|
204
315
|
content: [{ type: 'text', text: `Unknown procedure tool: ${name}` }],
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// UUID format check — hyphenated 36-char hex; version nibble unconstrained to stay tolerant.
|
|
2
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3
|
+
// Accept "#r5", "#roadmap-5", "#5", or plain "5" — capture the numeric refId.
|
|
4
|
+
const RADAR_REF_PATTERN = /^#?(?:r|roadmap-)?(\d+)$/i;
|
|
5
|
+
// Resolve a user-supplied radar item reference (UUID, refId, or prefixed refId) to a UUID.
|
|
6
|
+
// stripUuids hides internal UUIDs from agents in responses, so agents pass refIds; the
|
|
7
|
+
// work-item create/update/filter endpoints require a UUID, so we translate at the MCP boundary.
|
|
8
|
+
export async function resolveRadarItemId(client, input, teamSlug) {
|
|
9
|
+
const trimmed = input.trim();
|
|
10
|
+
if (UUID_PATTERN.test(trimmed))
|
|
11
|
+
return trimmed;
|
|
12
|
+
const refMatch = trimmed.match(RADAR_REF_PATTERN);
|
|
13
|
+
if (!refMatch) {
|
|
14
|
+
throw new Error(`Invalid radar_item_id "${input}": expected a UUID, refId number, or prefixed refId like "#r5".`);
|
|
15
|
+
}
|
|
16
|
+
const refId = refMatch[1];
|
|
17
|
+
const radarItem = await client.getRadarItem(refId, teamSlug);
|
|
18
|
+
if (!radarItem) {
|
|
19
|
+
throw new Error(`Radar/initiative item #r${refId} not found.`);
|
|
20
|
+
}
|
|
21
|
+
return radarItem.id;
|
|
22
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { resolveRadarItemId } from './resolveRadarItemId.js';
|
|
3
|
+
const UUID = '11111111-2222-4333-8444-555555555555';
|
|
4
|
+
function makeClient(item) {
|
|
5
|
+
return {
|
|
6
|
+
getRadarItem: vi.fn(async () => item),
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
describe('resolveRadarItemId', () => {
|
|
10
|
+
it('passes UUIDs through without lookup', async () => {
|
|
11
|
+
const client = makeClient(null);
|
|
12
|
+
const result = await resolveRadarItemId(client, UUID);
|
|
13
|
+
expect(result).toBe(UUID);
|
|
14
|
+
expect(client.getRadarItem).not.toHaveBeenCalled();
|
|
15
|
+
});
|
|
16
|
+
it.each(['5', '#5', '#r5', '#roadmap-5', ' 5 '])('resolves refId form %s to UUID via getRadarItem', async (input) => {
|
|
17
|
+
const client = makeClient({ id: UUID, refId: 5 });
|
|
18
|
+
const result = await resolveRadarItemId(client, input, 'my-team');
|
|
19
|
+
expect(result).toBe(UUID);
|
|
20
|
+
expect(client.getRadarItem).toHaveBeenCalledWith('5', 'my-team');
|
|
21
|
+
});
|
|
22
|
+
it('throws when refId cannot be resolved', async () => {
|
|
23
|
+
const client = makeClient(null);
|
|
24
|
+
await expect(resolveRadarItemId(client, '#r99')).rejects.toThrow(/#r99 not found/);
|
|
25
|
+
});
|
|
26
|
+
it('rejects malformed input', async () => {
|
|
27
|
+
const client = makeClient(null);
|
|
28
|
+
await expect(resolveRadarItemId(client, 'not-a-ref')).rejects.toThrow(/Invalid radar_item_id/);
|
|
29
|
+
expect(client.getRadarItem).not.toHaveBeenCalled();
|
|
30
|
+
});
|
|
31
|
+
});
|