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.
@@ -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 template. Use list_agent_templates to see available templates.',
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
- template_id: { type: 'string', description: 'Template ID to create the agent from' },
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: ['template_id', 'name'],
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 (supplements template instructions). Set to null to clear.',
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: 'list_agent_templates',
655
- description: 'List agent templates available to the organisation. Use template IDs when calling create_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
- template_id: z.string(),
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
- templateId: parsed.template_id,
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 'list_agent_templates': {
1256
+ case 'list_container_images': {
1205
1257
  const parsed = z.object({ org_slug: orgSlugField }).parse(args);
1206
- const templates = await client.listAgentTemplates(resolveOrg(parsed));
1207
- return { content: [{ type: 'text', text: text(templates) }] };
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);
@@ -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: template system prompt, agent instructions, skills, LLM model, and assigned projects. ' +
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 listAgentTemplates(orgSlug) {
765
- return this.api.get(this.withOrgSlug('/agent-builder/templates', orgSlug));
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.string().optional().describe('Filter by linked radar/initiative item'),
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('Radar/initiative item ID to link to, or null to unlink'),
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.string().optional().describe('Radar/initiative item ID to link to'),
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 ID',
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: 'Radar/initiative item ID to link to, or null to unlink',
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: 'Radar/initiative item ID to link to',
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: parsed.project_slug || this.client.getProjectSlug() || undefined,
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: parsed.radar_item_id,
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: parsed.radar_item_id,
2219
- teamSlug: parsed.project_slug || this.client.getProjectSlug() || undefined,
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: [
@@ -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, and case count. ' +
10
- 'Use get_procedure to see the full workflow definition.',
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 (start/end/activity/decision/loop/parallel/wait), ' +
29
- 'position, and data; edges connect nodes via source/target IDs. ' +
30
- 'Accepts UUID or refId (requires project_slug for refId).',
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}. The procedure starts as inactive (isActive=false).',
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
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolfpack-mcp",
3
- "version": "1.0.58",
3
+ "version": "1.0.60",
4
4
  "description": "MCP server for Wolfpack AI-enhanced software delivery tools",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",