wolfpack-mcp 1.0.59 → 1.0.61

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
@@ -469,6 +469,14 @@ export class WolfpackClient {
469
469
  const query = params.toString();
470
470
  return this.api.get(`/categories${query ? `?${query}` : ''}`);
471
471
  }
472
+ // Tag methods
473
+ async listTags(teamSlug) {
474
+ const params = new URLSearchParams();
475
+ if (teamSlug)
476
+ params.append('teamSlug', teamSlug);
477
+ const query = params.toString();
478
+ return this.api.get(`/tags${query ? `?${query}` : ''}`);
479
+ }
472
480
  /**
473
481
  * Upload an image and get back a URL that can be used in markdown content.
474
482
  */
@@ -483,6 +491,14 @@ export class WolfpackClient {
483
491
  const { buffer, contentType } = await this.api.getBuffer(`/images/${team}/${filename}`);
484
492
  return { base64: buffer.toString('base64'), mimeType: contentType };
485
493
  }
494
+ /**
495
+ * Download a stored file (new /api/files/{fileId}/{filename} URL shape) and
496
+ * return raw base64 + mimeType.
497
+ */
498
+ async downloadFile(fileId) {
499
+ const { buffer, contentType } = await this.api.getBuffer(`/files/${fileId}`);
500
+ return { base64: buffer.toString('base64'), mimeType: contentType };
501
+ }
486
502
  /**
487
503
  * Download an issue attachment image and return raw base64 + mimeType.
488
504
  */
@@ -767,8 +783,8 @@ export class WolfpackClient {
767
783
  }
768
784
  }
769
785
  // ─── Agent Builder: Discovery ──────────────────────────────────────────────
770
- async listAgentTemplates(orgSlug) {
771
- return this.api.get(this.withOrgSlug('/agent-builder/templates', orgSlug));
786
+ async listContainerImages(orgSlug) {
787
+ return this.api.get(this.withOrgSlug('/agent-builder/container-images', orgSlug));
772
788
  }
773
789
  async listLlmModels(orgSlug) {
774
790
  return this.api.get(this.withOrgSlug('/agent-builder/llm-models', orgSlug));
@@ -826,6 +842,10 @@ export class WolfpackClient {
826
842
  await this.api.delete(this.withTeamSlug(`/procedures/${procedureId}`, teamSlug));
827
843
  return { success: true };
828
844
  }
845
+ async startCase(procedureId, data) {
846
+ const { teamSlug, ...body } = data;
847
+ return this.api.post(this.withTeamSlug(`/procedures/${procedureId}/start`, teamSlug), body);
848
+ }
829
849
  // ─── Agent Self-Introspection ──────────────────────────────────────────────
830
850
  async getSelf() {
831
851
  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()
@@ -162,7 +166,10 @@ const UpdateWorkItemSchema = z.object({
162
166
  .nullable()
163
167
  .optional()
164
168
  .describe('User ID for third assisting user, or null to remove'),
165
- tag_ids: z.array(z.string()).optional().describe('Tag IDs to apply (replaces existing tags)'),
169
+ tags: z
170
+ .array(z.string())
171
+ .optional()
172
+ .describe('Tag names to apply (replaces existing tags). Use list_tags to see available tags.'),
166
173
  closing_comment: z
167
174
  .string()
168
175
  .nullable()
@@ -288,7 +295,10 @@ const CreateWorkItemSchema = z.object({
288
295
  .describe('Size estimate: "S" (small), "M" (medium), "L" (large)'),
289
296
  leading_user_id: z.string().optional().describe('User ID to assign as leading user'),
290
297
  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'),
298
+ radar_item_id: z
299
+ .string()
300
+ .optional()
301
+ .describe('Link to a radar/initiative item. Accepts refId ("5" or "#r5") or UUID.'),
292
302
  });
293
303
  const CreateIssueSchema = z.object({
294
304
  project_slug: z
@@ -481,7 +491,7 @@ const UploadImageSchema = z.object({
481
491
  const DownloadImageSchema = z.object({
482
492
  image_url: z
483
493
  .string()
484
- .describe('The image URL path found in content fields (e.g. "/api/files/images/{team}/{filename}").'),
494
+ .describe('The image URL path found in content fields (e.g. "/api/files/{fileId}/{filename}" or legacy "/api/files/images/{team}/{filename}").'),
485
495
  });
486
496
  // Skill schemas
487
497
  const GetSkillSchema = z.object({
@@ -498,6 +508,13 @@ const ListCategoriesSchema = z.object({
498
508
  .optional()
499
509
  .describe('Project slug (required for multi-project users, use list_projects to get slugs)'),
500
510
  });
511
+ // Tag schemas
512
+ const ListTagsSchema = z.object({
513
+ project_slug: z
514
+ .string()
515
+ .optional()
516
+ .describe('Project slug (required for multi-project users, use list_projects to get slugs)'),
517
+ });
501
518
  // Team member schemas
502
519
  const ListTeamMembersSchema = z.object({
503
520
  project_slug: z
@@ -708,7 +725,8 @@ class WolfpackMCPServer {
708
725
  },
709
726
  radar_item_id: {
710
727
  type: 'string',
711
- description: 'Filter by linked radar/initiative item ID',
728
+ description: 'Filter by linked radar/initiative item. Accepts refId (e.g. "5" or "#r5"), UUID, ' +
729
+ 'or one of "none" (no link), "any" (has any link), "all" (no filter).',
712
730
  },
713
731
  sort_by: {
714
732
  type: 'string',
@@ -740,7 +758,7 @@ class WolfpackMCPServer {
740
758
  '(pending→pull first, new→doing, blocked/ready/completed/closed→new→doing, then review when done). ' +
741
759
  'PLANNING: Check if the description contains a plan (markdown checklist). If not, APPEND one using update_work_progress - preserve all original description text and add your plan below a "---" separator. ' +
742
760
  'FORMS: Procedure-created work items may include formDefinition (field definitions with name, label, type, required, options) and formValues (current values). Use submit_work_item_form to fill in form values. ' +
743
- 'IMAGES: Image references in the description (e.g. `![alt](/api/files/images/...)`) can be viewed using the download_image tool.',
761
+ 'IMAGES: Image references in the description (e.g. `![alt](/api/files/...)`) can be viewed using the download_image tool.',
744
762
  inputSchema: {
745
763
  type: 'object',
746
764
  properties: {
@@ -782,7 +800,7 @@ class WolfpackMCPServer {
782
800
  name: 'update_work_item',
783
801
  description: 'Update one or more fields on a work item. Only provide the fields you want to change. ' +
784
802
  'Supports: title, description, status, leading_user_id (assignee), assisting_user1/2/3_id, ' +
785
- 'radar_item_id (initiative), category_id, priority (0-4), size (S/M/L), tag_ids, and closing_comment. ' +
803
+ 'radar_item_id (initiative), category_id, priority (0-4), size (S/M/L), tags, and closing_comment. ' +
786
804
  'STATUS WORKFLOW: "pending" (backlog) → "new" (to do) → "doing" (in progress) → "review" (work done) → "ready" (awaiting deployment) → "completed" (deployed). ' +
787
805
  'Use "blocked" when work cannot proceed. For "pending" items use pull_work_item first. ' +
788
806
  'When moving to "review", add a completion comment. When moving to "blocked", add a comment explaining the blocker.',
@@ -822,7 +840,7 @@ class WolfpackMCPServer {
822
840
  },
823
841
  radar_item_id: {
824
842
  type: ['string', 'null'],
825
- description: 'Radar/initiative item ID to link to, or null to unlink',
843
+ description: 'Link to a radar/initiative item. Accepts refId (e.g. "5" or "#r5") or UUID. Use null to unlink.',
826
844
  },
827
845
  category_id: {
828
846
  type: ['string', 'null'],
@@ -849,10 +867,10 @@ class WolfpackMCPServer {
849
867
  type: ['string', 'null'],
850
868
  description: 'User ID for third assisting user, or null to remove',
851
869
  },
852
- tag_ids: {
870
+ tags: {
853
871
  type: 'array',
854
872
  items: { type: 'string' },
855
- description: 'Tag IDs to apply (replaces existing tags)',
873
+ description: 'Tag names to apply (replaces existing tags). Use list_tags to see available tags.',
856
874
  },
857
875
  closing_comment: {
858
876
  type: ['string', 'null'],
@@ -945,7 +963,7 @@ class WolfpackMCPServer {
945
963
  {
946
964
  name: 'get_radar_item',
947
965
  description: 'Get a specific radar item (initiative) by reference number, including its work items and participants. ' +
948
- 'IMAGES: Image references in the description (e.g. `![alt](/api/files/images/...)`) can be viewed using the download_image tool.',
966
+ 'IMAGES: Image references in the description (e.g. `![alt](/api/files/...)`) can be viewed using the download_image tool.',
949
967
  inputSchema: {
950
968
  type: 'object',
951
969
  properties: {
@@ -1078,7 +1096,7 @@ class WolfpackMCPServer {
1078
1096
  description: 'Get a specific issue by its PREFIXED reference #iN (e.g. #i5). ' +
1079
1097
  'IMPORTANT: A bare #N (without prefix) is ALWAYS a work item, NOT an issue. Only use this tool when the user explicitly says "issue" or uses the #i prefix. ' +
1080
1098
  'Returns full details including comment and attachment counts. ' +
1081
- 'IMAGES: Image references in the description (e.g. `![alt](/api/files/images/...)`) can be viewed using the download_image tool.',
1099
+ 'IMAGES: Image references in the description (e.g. `![alt](/api/files/...)`) can be viewed using the download_image tool.',
1082
1100
  inputSchema: {
1083
1101
  type: 'object',
1084
1102
  properties: {
@@ -1145,7 +1163,7 @@ class WolfpackMCPServer {
1145
1163
  },
1146
1164
  radar_item_id: {
1147
1165
  type: 'string',
1148
- description: 'Radar/initiative item ID to link to',
1166
+ description: 'Link to a radar/initiative item. Accepts refId (e.g. "5" or "#r5") or UUID.',
1149
1167
  },
1150
1168
  },
1151
1169
  required: ['title'],
@@ -1263,7 +1281,7 @@ class WolfpackMCPServer {
1263
1281
  {
1264
1282
  name: 'get_wiki_page',
1265
1283
  description: 'Get a specific wiki/documentation page by slug (URL path). Returns full content in markdown. ' +
1266
- 'IMAGES: Image references in the content (e.g. `![alt](/api/files/images/...)`) can be viewed using the download_image tool.',
1284
+ 'IMAGES: Image references in the content (e.g. `![alt](/api/files/...)`) can be viewed using the download_image tool.',
1267
1285
  inputSchema: {
1268
1286
  type: 'object',
1269
1287
  properties: {
@@ -1315,7 +1333,7 @@ class WolfpackMCPServer {
1315
1333
  tag_ids: {
1316
1334
  type: 'array',
1317
1335
  items: { type: 'string' },
1318
- description: 'Tag IDs to apply to the page (replaces existing tags). Use list tags endpoint to get available tag IDs.',
1336
+ description: 'Tag IDs to apply to the page (replaces existing tags)',
1319
1337
  },
1320
1338
  project_slug: {
1321
1339
  type: 'string',
@@ -1356,7 +1374,7 @@ class WolfpackMCPServer {
1356
1374
  {
1357
1375
  name: 'get_journal_entry',
1358
1376
  description: 'Get a specific journal/log entry by reference number. Returns full content. ' +
1359
- 'IMAGES: Image references in the content (e.g. `![alt](/api/files/images/...)`) can be viewed using the download_image tool.',
1377
+ 'IMAGES: Image references in the content (e.g. `![alt](/api/files/...)`) can be viewed using the download_image tool.',
1360
1378
  inputSchema: {
1361
1379
  type: 'object',
1362
1380
  properties: {
@@ -1419,7 +1437,7 @@ class WolfpackMCPServer {
1419
1437
  {
1420
1438
  name: 'list_work_item_comments',
1421
1439
  description: 'List all comments on a work item. Returns comments in chronological order. Requires mcp:comments:read permission. ' +
1422
- 'IMAGES: Image references in comments (e.g. `![alt](/api/files/images/...)`) can be viewed using the download_image tool.',
1440
+ 'IMAGES: Image references in comments (e.g. `![alt](/api/files/...)`) can be viewed using the download_image tool.',
1423
1441
  inputSchema: {
1424
1442
  type: 'object',
1425
1443
  properties: {
@@ -1435,7 +1453,7 @@ class WolfpackMCPServer {
1435
1453
  {
1436
1454
  name: 'list_issue_comments',
1437
1455
  description: 'List all comments on an issue. Returns comments in chronological order. Requires mcp:comments:read permission. ' +
1438
- 'IMAGES: Image references in comments (e.g. `![alt](/api/files/images/...)`) can be viewed using the download_image tool.',
1456
+ 'IMAGES: Image references in comments (e.g. `![alt](/api/files/...)`) can be viewed using the download_image tool.',
1439
1457
  inputSchema: {
1440
1458
  type: 'object',
1441
1459
  properties: {
@@ -1555,7 +1573,7 @@ class WolfpackMCPServer {
1555
1573
  {
1556
1574
  name: 'download_image',
1557
1575
  description: 'Download and view an image referenced in content fields. ' +
1558
- 'Content from work items, issues, wiki pages, journal entries, and comments may contain image references like `![alt](/api/files/images/...)`. ' +
1576
+ 'Content from work items, issues, wiki pages, journal entries, and comments may contain image references like `![alt](/api/files/{fileId}/...)` or the legacy `![alt](/api/files/images/...)`. ' +
1559
1577
  'Issues may also have attachment images with URLs like `/api/teams/{team}/issues/{refId}/attachments/{id}`. ' +
1560
1578
  'Use this tool with that URL path to download and view the image. ' +
1561
1579
  'Requires mcp:images:read permission.',
@@ -1564,7 +1582,7 @@ class WolfpackMCPServer {
1564
1582
  properties: {
1565
1583
  image_url: {
1566
1584
  type: 'string',
1567
- description: 'The image URL path found in content fields (e.g. "/api/files/images/{team}/{filename}" or "/api/teams/{team}/issues/{refId}/attachments/{id}").',
1585
+ description: 'The image URL path found in content fields (e.g. "/api/files/{fileId}/{filename}", legacy "/api/files/images/{team}/{filename}", or "/api/teams/{team}/issues/{refId}/attachments/{id}").',
1568
1586
  },
1569
1587
  },
1570
1588
  required: ['image_url'],
@@ -1585,6 +1603,21 @@ class WolfpackMCPServer {
1585
1603
  },
1586
1604
  },
1587
1605
  },
1606
+ // Tag tools
1607
+ {
1608
+ name: 'list_tags',
1609
+ description: 'List all tags available in a project. Returns tag names and colours. ' +
1610
+ 'Work items are tagged by name via update_work_item.',
1611
+ inputSchema: {
1612
+ type: 'object',
1613
+ properties: {
1614
+ project_slug: {
1615
+ type: 'string',
1616
+ description: 'Project slug (required for multi-project users, use list_projects to get slugs)',
1617
+ },
1618
+ },
1619
+ },
1620
+ },
1588
1621
  // Team member tools
1589
1622
  {
1590
1623
  name: 'list_team_members',
@@ -1924,14 +1957,19 @@ class WolfpackMCPServer {
1924
1957
  // Work Item handlers
1925
1958
  case 'list_work_items': {
1926
1959
  const parsed = ListWorkItemsSchema.parse(args);
1960
+ const teamSlug = parsed.project_slug || this.client.getProjectSlug() || undefined;
1961
+ const RADAR_FILTER_PASSTHROUGH = new Set(['none', 'any', 'all']);
1962
+ const radarItemIdFilter = parsed.radar_item_id && !RADAR_FILTER_PASSTHROUGH.has(parsed.radar_item_id)
1963
+ ? await resolveRadarItemId(this.client, parsed.radar_item_id, teamSlug)
1964
+ : parsed.radar_item_id;
1927
1965
  const result = await this.client.listWorkItems({
1928
- teamSlug: parsed.project_slug || this.client.getProjectSlug() || undefined,
1966
+ teamSlug,
1929
1967
  status: parsed.status,
1930
1968
  assignedToId: parsed.assigned_to_id,
1931
1969
  search: parsed.search,
1932
1970
  categoryId: parsed.category_id,
1933
1971
  priority: parsed.priority,
1934
- radarItemId: parsed.radar_item_id,
1972
+ radarItemId: radarItemIdFilter,
1935
1973
  createdById: parsed.created_by_id,
1936
1974
  updatedById: parsed.updated_by_id,
1937
1975
  sortBy: parsed.sort_by,
@@ -1999,8 +2037,11 @@ class WolfpackMCPServer {
1999
2037
  fields.status = parsed.status;
2000
2038
  if (parsed.leading_user_id !== undefined)
2001
2039
  fields.leadingUserId = parsed.leading_user_id;
2002
- if (parsed.radar_item_id !== undefined)
2003
- fields.radarItemId = parsed.radar_item_id;
2040
+ if (parsed.radar_item_id !== undefined) {
2041
+ fields.radarItemId = parsed.radar_item_id
2042
+ ? await resolveRadarItemId(this.client, parsed.radar_item_id, teamSlug)
2043
+ : null;
2044
+ }
2004
2045
  if (parsed.category_id !== undefined)
2005
2046
  fields.categoryId = parsed.category_id;
2006
2047
  if (parsed.priority !== undefined)
@@ -2013,8 +2054,8 @@ class WolfpackMCPServer {
2013
2054
  fields.assistingUser2Id = parsed.assisting_user2_id;
2014
2055
  if (parsed.assisting_user3_id !== undefined)
2015
2056
  fields.assistingUser3Id = parsed.assisting_user3_id;
2016
- if (parsed.tag_ids !== undefined)
2017
- fields.tagIds = parsed.tag_ids;
2057
+ if (parsed.tags !== undefined)
2058
+ fields.tags = parsed.tags;
2018
2059
  if (parsed.closing_comment !== undefined)
2019
2060
  fields.closingComment = parsed.closing_comment;
2020
2061
  const workItem = await this.client.updateWorkItem(parsed.work_item_id, fields, teamSlug);
@@ -2061,7 +2102,7 @@ class WolfpackMCPServer {
2061
2102
  changes.push(parsed.assisting_user3_id
2062
2103
  ? `assisting user 3 updated`
2063
2104
  : 'assisting user 3 removed');
2064
- if (parsed.tag_ids !== undefined)
2105
+ if (parsed.tags !== undefined)
2065
2106
  changes.push('tags updated');
2066
2107
  if (parsed.closing_comment !== undefined)
2067
2108
  changes.push('closing comment set');
@@ -2222,6 +2263,10 @@ class WolfpackMCPServer {
2222
2263
  // Create/Update handlers
2223
2264
  case 'create_work_item': {
2224
2265
  const parsed = CreateWorkItemSchema.parse(args);
2266
+ const teamSlug = parsed.project_slug || this.client.getProjectSlug() || undefined;
2267
+ const radarItemId = parsed.radar_item_id
2268
+ ? await resolveRadarItemId(this.client, parsed.radar_item_id, teamSlug)
2269
+ : undefined;
2225
2270
  const workItem = await this.client.createWorkItem({
2226
2271
  title: parsed.title,
2227
2272
  description: parsed.description,
@@ -2230,8 +2275,8 @@ class WolfpackMCPServer {
2230
2275
  size: parsed.size,
2231
2276
  leadingUserId: parsed.leading_user_id,
2232
2277
  categoryId: parsed.category_id,
2233
- radarItemId: parsed.radar_item_id,
2234
- teamSlug: parsed.project_slug || this.client.getProjectSlug() || undefined,
2278
+ radarItemId,
2279
+ teamSlug,
2235
2280
  });
2236
2281
  return {
2237
2282
  content: [
@@ -2537,13 +2582,18 @@ class WolfpackMCPServer {
2537
2582
  }
2538
2583
  case 'download_image': {
2539
2584
  const parsed = DownloadImageSchema.parse(args);
2540
- // Try S3-stored image pattern: /api/files/images/{team}/{filename}
2585
+ // Try stored-file pattern (#1439): /api/files/{fileId}/{filename}
2586
+ const storedFileMatch = parsed.image_url.match(/\/api\/files\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:\/|$)/i);
2587
+ // Try legacy S3-stored image pattern: /api/files/images/{team}/{filename}
2541
2588
  const s3Match = parsed.image_url.match(/\/api\/files\/images\/([^/]+)\/(.+)$/);
2542
2589
  // Try issue attachment pattern: /api/teams/{team}/issues/{refId}/attachments/{id}
2543
2590
  const attachmentMatch = parsed.image_url.match(/\/api\/teams\/([^/]+)\/issues\/(\d+)\/attachments\/([^/]+)$/);
2544
2591
  let base64;
2545
2592
  let mimeType;
2546
- if (s3Match) {
2593
+ if (storedFileMatch) {
2594
+ ({ base64, mimeType } = await this.client.downloadFile(storedFileMatch[1]));
2595
+ }
2596
+ else if (s3Match) {
2547
2597
  const [, team, filename] = s3Match;
2548
2598
  ({ base64, mimeType } = await this.client.downloadImage(team, filename));
2549
2599
  }
@@ -2552,7 +2602,7 @@ class WolfpackMCPServer {
2552
2602
  ({ base64, mimeType } = await this.client.downloadIssueAttachment(team, refId, attachmentId));
2553
2603
  }
2554
2604
  else {
2555
- throw new Error('Invalid image URL. Expected format: /api/files/images/{team}/{filename} or /api/teams/{team}/issues/{refId}/attachments/{id}');
2605
+ throw new Error('Invalid image URL. Expected format: /api/files/{fileId}/{filename}, /api/files/images/{team}/{filename} or /api/teams/{team}/issues/{refId}/attachments/{id}');
2556
2606
  }
2557
2607
  return {
2558
2608
  content: [
@@ -2572,6 +2622,14 @@ class WolfpackMCPServer {
2572
2622
  content: [{ type: 'text', text: JSON.stringify(categories, null, 2) }],
2573
2623
  };
2574
2624
  }
2625
+ // Tag handlers
2626
+ case 'list_tags': {
2627
+ const parsed = ListTagsSchema.parse(args);
2628
+ const tags = await this.client.listTags(parsed.project_slug || this.client.getProjectSlug() || undefined);
2629
+ return {
2630
+ content: [{ type: 'text', text: JSON.stringify(tags, null, 2) }],
2631
+ };
2632
+ }
2575
2633
  // Team member handlers
2576
2634
  case 'list_team_members': {
2577
2635
  const parsed = ListTeamMembersSchema.parse(args);
@@ -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.61",
4
4
  "description": "MCP server for Wolfpack AI-enhanced software delivery tools",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -1,21 +0,0 @@
1
- const IMAGE_URL_REGEX = /!\[([^\]]*)\]\(\/api\/files\/images\/([^/]+)\/([^)]+)\)/g;
2
- /**
3
- * Processes markdown content to replace image URLs with base64 data URIs.
4
- * Failed fetches or oversized images leave the original URL intact.
5
- */
6
- export async function processContentImages(content, client) {
7
- if (!content)
8
- return content;
9
- const matches = [...content.matchAll(IMAGE_URL_REGEX)];
10
- if (matches.length === 0)
11
- return content;
12
- let result = content;
13
- for (const match of matches) {
14
- const [fullMatch, alt, team, filename] = match;
15
- const dataUri = await client.fetchImageAsBase64(team, filename);
16
- if (dataUri) {
17
- result = result.replace(fullMatch, `![${alt}](${dataUri})`);
18
- }
19
- }
20
- return result;
21
- }