wolfpack-mcp 1.0.59 → 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',
@@ -682,8 +685,8 @@ export const AGENT_BUILDER_TOOLS = [
682
685
  },
683
686
  // ─── Group 7: Discovery ───────────────────────────────────────────────────
684
687
  {
685
- name: 'list_agent_templates',
686
- 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.',
687
690
  inputSchema: { type: 'object', properties: { ...ORG_SLUG_PROP } },
688
691
  },
689
692
  {
@@ -735,14 +738,14 @@ export async function handleAgentBuilderTool(name, args, client) {
735
738
  case 'create_agent': {
736
739
  const parsed = z
737
740
  .object({
738
- template_id: z.string(),
741
+ container_image_id: z.string(),
739
742
  name: z.string(),
740
743
  config: z.record(z.unknown()).optional(),
741
744
  org_slug: orgSlugField,
742
745
  })
743
746
  .parse(args);
744
747
  const agent = await client.createAgent({
745
- templateId: parsed.template_id,
748
+ containerImageId: parsed.container_image_id,
746
749
  name: parsed.name,
747
750
  config: parsed.config,
748
751
  }, resolveOrg(parsed));
@@ -1250,10 +1253,10 @@ export async function handleAgentBuilderTool(name, args, client) {
1250
1253
  return { content: [{ type: 'text', text: `Set secret "${secret.name}"` }] };
1251
1254
  }
1252
1255
  // ─── Discovery ────────────────────────────────────────────────────────────
1253
- case 'list_agent_templates': {
1256
+ case 'list_container_images': {
1254
1257
  const parsed = z.object({ org_slug: orgSlugField }).parse(args);
1255
- const templates = await client.listAgentTemplates(resolveOrg(parsed));
1256
- return { content: [{ type: 'text', text: text(templates) }] };
1258
+ const images = await client.listContainerImages(resolveOrg(parsed));
1259
+ return { content: [{ type: 'text', text: text(images) }] };
1257
1260
  }
1258
1261
  case 'list_llm_models': {
1259
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
@@ -767,8 +767,8 @@ export class WolfpackClient {
767
767
  }
768
768
  }
769
769
  // ─── Agent Builder: Discovery ──────────────────────────────────────────────
770
- async listAgentTemplates(orgSlug) {
771
- 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));
772
772
  }
773
773
  async listLlmModels(orgSlug) {
774
774
  return this.api.get(this.withOrgSlug('/agent-builder/llm-models', orgSlug));
@@ -826,6 +826,10 @@ export class WolfpackClient {
826
826
  await this.api.delete(this.withTeamSlug(`/procedures/${procedureId}`, teamSlug));
827
827
  return { success: true };
828
828
  }
829
+ async startCase(procedureId, data) {
830
+ const { teamSlug, ...body } = data;
831
+ return this.api.post(this.withTeamSlug(`/procedures/${procedureId}/start`, teamSlug), body);
832
+ }
829
833
  // ─── Agent Self-Introspection ──────────────────────────────────────────────
830
834
  async getSelf() {
831
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
@@ -708,7 +715,8 @@ class WolfpackMCPServer {
708
715
  },
709
716
  radar_item_id: {
710
717
  type: 'string',
711
- 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).',
712
720
  },
713
721
  sort_by: {
714
722
  type: 'string',
@@ -822,7 +830,7 @@ class WolfpackMCPServer {
822
830
  },
823
831
  radar_item_id: {
824
832
  type: ['string', 'null'],
825
- 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.',
826
834
  },
827
835
  category_id: {
828
836
  type: ['string', 'null'],
@@ -1145,7 +1153,7 @@ class WolfpackMCPServer {
1145
1153
  },
1146
1154
  radar_item_id: {
1147
1155
  type: 'string',
1148
- 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.',
1149
1157
  },
1150
1158
  },
1151
1159
  required: ['title'],
@@ -1924,14 +1932,19 @@ class WolfpackMCPServer {
1924
1932
  // Work Item handlers
1925
1933
  case 'list_work_items': {
1926
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;
1927
1940
  const result = await this.client.listWorkItems({
1928
- teamSlug: parsed.project_slug || this.client.getProjectSlug() || undefined,
1941
+ teamSlug,
1929
1942
  status: parsed.status,
1930
1943
  assignedToId: parsed.assigned_to_id,
1931
1944
  search: parsed.search,
1932
1945
  categoryId: parsed.category_id,
1933
1946
  priority: parsed.priority,
1934
- radarItemId: parsed.radar_item_id,
1947
+ radarItemId: radarItemIdFilter,
1935
1948
  createdById: parsed.created_by_id,
1936
1949
  updatedById: parsed.updated_by_id,
1937
1950
  sortBy: parsed.sort_by,
@@ -1999,8 +2012,11 @@ class WolfpackMCPServer {
1999
2012
  fields.status = parsed.status;
2000
2013
  if (parsed.leading_user_id !== undefined)
2001
2014
  fields.leadingUserId = parsed.leading_user_id;
2002
- if (parsed.radar_item_id !== undefined)
2003
- 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
+ }
2004
2020
  if (parsed.category_id !== undefined)
2005
2021
  fields.categoryId = parsed.category_id;
2006
2022
  if (parsed.priority !== undefined)
@@ -2222,6 +2238,10 @@ class WolfpackMCPServer {
2222
2238
  // Create/Update handlers
2223
2239
  case 'create_work_item': {
2224
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;
2225
2245
  const workItem = await this.client.createWorkItem({
2226
2246
  title: parsed.title,
2227
2247
  description: parsed.description,
@@ -2230,8 +2250,8 @@ class WolfpackMCPServer {
2230
2250
  size: parsed.size,
2231
2251
  leadingUserId: parsed.leading_user_id,
2232
2252
  categoryId: parsed.category_id,
2233
- radarItemId: parsed.radar_item_id,
2234
- teamSlug: parsed.project_slug || this.client.getProjectSlug() || undefined,
2253
+ radarItemId,
2254
+ teamSlug,
2235
2255
  });
2236
2256
  return {
2237
2257
  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.59",
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",