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.
- package/dist/agentBuilderTools.js +14 -11
- package/dist/agentSelfTools.js +1 -1
- package/dist/client.js +6 -2
- package/dist/index.js +32 -12
- package/dist/procedureTools.js +119 -8
- package/dist/resolveRadarItemId.js +22 -0
- package/dist/resolveRadarItemId.test.js +31 -0
- package/package.json +1 -1
|
@@ -37,16 +37,19 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
name: 'create_agent',
|
|
40
|
-
description: 'Create a new agent from a
|
|
40
|
+
description: 'Create a new agent from a container image. Use list_container_images to see available images.',
|
|
41
41
|
inputSchema: {
|
|
42
42
|
type: 'object',
|
|
43
43
|
properties: {
|
|
44
|
-
|
|
44
|
+
container_image_id: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
description: 'Container image ID to create the agent from',
|
|
47
|
+
},
|
|
45
48
|
name: { type: 'string', description: 'Display name for the agent' },
|
|
46
49
|
config: { type: 'object', description: 'Optional agent-specific configuration' },
|
|
47
50
|
...ORG_SLUG_PROP,
|
|
48
51
|
},
|
|
49
|
-
required: ['
|
|
52
|
+
required: ['container_image_id', 'name'],
|
|
50
53
|
},
|
|
51
54
|
},
|
|
52
55
|
{
|
|
@@ -71,7 +74,7 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
71
74
|
},
|
|
72
75
|
instructions: {
|
|
73
76
|
type: 'string',
|
|
74
|
-
description: 'Agent-specific instructions
|
|
77
|
+
description: 'Agent-specific instructions appended to the system prompt. Set to null to clear.',
|
|
75
78
|
},
|
|
76
79
|
llm_provider: {
|
|
77
80
|
type: 'string',
|
|
@@ -682,8 +685,8 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
682
685
|
},
|
|
683
686
|
// ─── Group 7: Discovery ───────────────────────────────────────────────────
|
|
684
687
|
{
|
|
685
|
-
name: '
|
|
686
|
-
description: 'List agent
|
|
688
|
+
name: 'list_container_images',
|
|
689
|
+
description: 'List agent-capable container images assigned to the organisation. Use image IDs when calling create_agent.',
|
|
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
|
-
|
|
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
|
-
|
|
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 '
|
|
1256
|
+
case 'list_container_images': {
|
|
1254
1257
|
const parsed = z.object({ org_slug: orgSlugField }).parse(args);
|
|
1255
|
-
const
|
|
1256
|
-
return { content: [{ type: 'text', text: text(
|
|
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);
|
package/dist/agentSelfTools.js
CHANGED
|
@@ -6,7 +6,7 @@ function text(data) {
|
|
|
6
6
|
export const AGENT_SELF_TOOLS = [
|
|
7
7
|
{
|
|
8
8
|
name: 'get_self',
|
|
9
|
-
description: 'Get your own agent profile with full composed context:
|
|
9
|
+
description: 'Get your own agent profile with full composed context: system prompt, instructions, skills, LLM model, and assigned projects. ' +
|
|
10
10
|
'Use this to understand how you are configured and what capabilities you have. ' +
|
|
11
11
|
'Essential for self-reflection and improvement proposals.',
|
|
12
12
|
inputSchema: { type: 'object', properties: {} },
|
package/dist/client.js
CHANGED
|
@@ -767,8 +767,8 @@ export class WolfpackClient {
|
|
|
767
767
|
}
|
|
768
768
|
}
|
|
769
769
|
// ─── Agent Builder: Discovery ──────────────────────────────────────────────
|
|
770
|
-
async
|
|
771
|
-
return this.api.get(this.withOrgSlug('/agent-builder/
|
|
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
|
|
80
|
+
radar_item_id: z.coerce
|
|
81
|
+
.string()
|
|
82
|
+
.optional()
|
|
83
|
+
.describe('Filter by linked radar/initiative item. Accepts refId ("5" or "#r5"), UUID, or "none"/"any"/"all".'),
|
|
80
84
|
created_by_id: z.coerce.string().optional().describe('Filter by creator user ID'),
|
|
81
85
|
updated_by_id: z.coerce.string().optional().describe('Filter by last updater user ID'),
|
|
82
86
|
sort_by: z.coerce
|
|
@@ -135,7 +139,7 @@ const UpdateWorkItemSchema = z.object({
|
|
|
135
139
|
.string()
|
|
136
140
|
.nullable()
|
|
137
141
|
.optional()
|
|
138
|
-
.describe('
|
|
142
|
+
.describe('Link to a radar/initiative item. Accepts refId ("5" or "#r5") or UUID. Use null to unlink.'),
|
|
139
143
|
category_id: z
|
|
140
144
|
.string()
|
|
141
145
|
.nullable()
|
|
@@ -288,7 +292,10 @@ const CreateWorkItemSchema = z.object({
|
|
|
288
292
|
.describe('Size estimate: "S" (small), "M" (medium), "L" (large)'),
|
|
289
293
|
leading_user_id: z.string().optional().describe('User ID to assign as leading user'),
|
|
290
294
|
category_id: z.string().optional().describe('Category ID to organize the work item'),
|
|
291
|
-
radar_item_id: z
|
|
295
|
+
radar_item_id: z
|
|
296
|
+
.string()
|
|
297
|
+
.optional()
|
|
298
|
+
.describe('Link to a radar/initiative item. Accepts refId ("5" or "#r5") or UUID.'),
|
|
292
299
|
});
|
|
293
300
|
const CreateIssueSchema = z.object({
|
|
294
301
|
project_slug: z
|
|
@@ -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
|
|
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: '
|
|
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: '
|
|
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
|
|
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:
|
|
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
|
|
2234
|
-
teamSlug
|
|
2253
|
+
radarItemId,
|
|
2254
|
+
teamSlug,
|
|
2235
2255
|
});
|
|
2236
2256
|
return {
|
|
2237
2257
|
content: [
|
package/dist/procedureTools.js
CHANGED
|
@@ -3,11 +3,37 @@
|
|
|
3
3
|
* Enables agents to list, view, create, update, and delete procedures.
|
|
4
4
|
*/
|
|
5
5
|
import { z } from 'zod';
|
|
6
|
+
const FORM_FIELD_ITEM_SCHEMA = {
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: {
|
|
9
|
+
name: { type: 'string' },
|
|
10
|
+
label: { type: 'string' },
|
|
11
|
+
type: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
enum: ['text', 'number', 'boolean', 'choice', 'date', 'json'],
|
|
14
|
+
},
|
|
15
|
+
required: { type: 'boolean' },
|
|
16
|
+
options: {
|
|
17
|
+
type: 'array',
|
|
18
|
+
items: {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
value: { type: 'string' },
|
|
22
|
+
label: { type: 'string' },
|
|
23
|
+
},
|
|
24
|
+
required: ['value', 'label'],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
multiline: { type: 'boolean' },
|
|
28
|
+
},
|
|
29
|
+
required: ['name', 'label', 'type', 'required'],
|
|
30
|
+
};
|
|
6
31
|
export const PROCEDURE_TOOLS = [
|
|
7
32
|
{
|
|
8
33
|
name: 'list_procedures',
|
|
9
|
-
description: 'List procedures in a project. Returns name, status, version, tags,
|
|
10
|
-
'
|
|
34
|
+
description: 'List procedures in a project. Returns name, status, version, tags, case count, and hasForm ' +
|
|
35
|
+
'(true when the procedure declares an input form that must be provided at start_case time). ' +
|
|
36
|
+
'Use get_procedure to see the full workflow definition and form schema.',
|
|
11
37
|
inputSchema: {
|
|
12
38
|
type: 'object',
|
|
13
39
|
properties: {
|
|
@@ -24,10 +50,12 @@ export const PROCEDURE_TOOLS = [
|
|
|
24
50
|
},
|
|
25
51
|
{
|
|
26
52
|
name: 'get_procedure',
|
|
27
|
-
description: 'Get full details of a procedure including its workflow definition (nodes and edges)
|
|
28
|
-
'The definition uses a graph format: nodes have id, type
|
|
29
|
-
'position, and data; edges connect nodes via
|
|
30
|
-
'
|
|
53
|
+
description: 'Get full details of a procedure including its workflow definition (nodes and edges) and its ' +
|
|
54
|
+
'optional input form (formDefinition). The definition uses a graph format: nodes have id, type ' +
|
|
55
|
+
'(start/end/activity/decision/loop/parallel/wait), position, and data; edges connect nodes via ' +
|
|
56
|
+
'source/target IDs. The formDefinition lists input fields (name, label, type, required, options, ' +
|
|
57
|
+
'multiline) validated at start_case time — inspect it before calling start_case so you know which ' +
|
|
58
|
+
'form_values to supply. Accepts UUID or refId (requires project_slug for refId).',
|
|
31
59
|
inputSchema: {
|
|
32
60
|
type: 'object',
|
|
33
61
|
properties: {
|
|
@@ -48,7 +76,8 @@ export const PROCEDURE_TOOLS = [
|
|
|
48
76
|
description: 'Create a new procedure. If no definition is provided, creates a default start→end workflow. ' +
|
|
49
77
|
'The definition is a graph with nodes and edges. Node types: start, end, activity, decision, loop, parallel, wait. ' +
|
|
50
78
|
'Each node needs id, type, position ({x, y}), and data (type-specific config). ' +
|
|
51
|
-
'Edges connect nodes: {id, source, target}.
|
|
79
|
+
'Edges connect nodes: {id, source, target}. Optionally provide form_definition to declare inputs ' +
|
|
80
|
+
'collected each time the procedure is started. The procedure starts as inactive (isActive=false).',
|
|
52
81
|
inputSchema: {
|
|
53
82
|
type: 'object',
|
|
54
83
|
properties: {
|
|
@@ -58,6 +87,11 @@ export const PROCEDURE_TOOLS = [
|
|
|
58
87
|
type: 'object',
|
|
59
88
|
description: 'Workflow definition: { nodes: [{id, type, position: {x, y}, data: {...}}], edges: [{id, source, target}] }',
|
|
60
89
|
},
|
|
90
|
+
form_definition: {
|
|
91
|
+
type: ['array', 'null'],
|
|
92
|
+
description: 'Input form: array of {name, label, type, required, options?, multiline?}. Types: text, number, boolean, choice, date, json. Required fields must be supplied when start_case runs. Scheduled runs ignore the form.',
|
|
93
|
+
items: FORM_FIELD_ITEM_SCHEMA,
|
|
94
|
+
},
|
|
61
95
|
tags: {
|
|
62
96
|
type: 'array',
|
|
63
97
|
items: { type: 'string' },
|
|
@@ -76,6 +110,7 @@ export const PROCEDURE_TOOLS = [
|
|
|
76
110
|
description: 'Update a procedure. Only provide the fields you want to change. ' +
|
|
77
111
|
'Updating the definition auto-increments the version number. ' +
|
|
78
112
|
'Set isActive to true/false to activate/deactivate the procedure. ' +
|
|
113
|
+
'Pass form_definition to replace the input form (or null to remove it). ' +
|
|
79
114
|
'Accepts UUID or refId (requires project_slug for refId).',
|
|
80
115
|
inputSchema: {
|
|
81
116
|
type: 'object',
|
|
@@ -97,6 +132,11 @@ export const PROCEDURE_TOOLS = [
|
|
|
97
132
|
type: 'object',
|
|
98
133
|
description: 'Updated workflow definition: { nodes: [...], edges: [...] }. Changes increment version.',
|
|
99
134
|
},
|
|
135
|
+
form_definition: {
|
|
136
|
+
type: ['array', 'null'],
|
|
137
|
+
description: 'Replace the input form. Same shape as create_procedure.form_definition. Pass null to remove the form entirely.',
|
|
138
|
+
items: FORM_FIELD_ITEM_SCHEMA,
|
|
139
|
+
},
|
|
100
140
|
is_active: { type: 'boolean', description: 'Activate or deactivate the procedure' },
|
|
101
141
|
tags: {
|
|
102
142
|
type: 'array',
|
|
@@ -111,6 +151,45 @@ export const PROCEDURE_TOOLS = [
|
|
|
111
151
|
required: ['procedure_id'],
|
|
112
152
|
},
|
|
113
153
|
},
|
|
154
|
+
{
|
|
155
|
+
name: 'start_case',
|
|
156
|
+
description: 'Start a new case by running an active procedure. If the procedure has a form_definition ' +
|
|
157
|
+
'(see get_procedure), supply form_values: an object keyed by each field.name. Required fields ' +
|
|
158
|
+
'must be present; values are type-checked and unknown keys are dropped server-side. Returns the ' +
|
|
159
|
+
'caseId and refId of the new case. Scheduled procedures cannot be started this way — they fire ' +
|
|
160
|
+
'on their schedule.',
|
|
161
|
+
inputSchema: {
|
|
162
|
+
type: 'object',
|
|
163
|
+
properties: {
|
|
164
|
+
procedure_id: {
|
|
165
|
+
type: 'string',
|
|
166
|
+
description: 'Procedure UUID or refId number',
|
|
167
|
+
},
|
|
168
|
+
title: {
|
|
169
|
+
type: 'string',
|
|
170
|
+
description: 'Case title (defaults to the procedure name)',
|
|
171
|
+
},
|
|
172
|
+
priority: {
|
|
173
|
+
type: 'number',
|
|
174
|
+
description: 'Priority: 0 (none), 1 (low), 2 (medium), 3 (high), 4 (urgent)',
|
|
175
|
+
},
|
|
176
|
+
additional_information: {
|
|
177
|
+
type: 'string',
|
|
178
|
+
description: 'Free-text context displayed on the case detail page',
|
|
179
|
+
},
|
|
180
|
+
form_values: {
|
|
181
|
+
type: 'object',
|
|
182
|
+
description: "Input values keyed by field name, matching the procedure's form_definition. Validated server-side: missing required fields → 400; wrong types → 400; unknown keys silently dropped.",
|
|
183
|
+
additionalProperties: true,
|
|
184
|
+
},
|
|
185
|
+
project_slug: {
|
|
186
|
+
type: 'string',
|
|
187
|
+
description: 'Project slug (required when using refId)',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
required: ['procedure_id'],
|
|
191
|
+
},
|
|
192
|
+
},
|
|
114
193
|
];
|
|
115
194
|
export async function handleProcedureTool(name, args, client) {
|
|
116
195
|
const text = (t) => JSON.stringify(t, null, 2);
|
|
@@ -152,6 +231,7 @@ export async function handleProcedureTool(name, args, client) {
|
|
|
152
231
|
name: z.string(),
|
|
153
232
|
description: z.string().optional(),
|
|
154
233
|
definition: z.preprocess((v) => (typeof v === 'string' ? JSON.parse(v) : v), z.any().optional()),
|
|
234
|
+
form_definition: z.preprocess((v) => (typeof v === 'string' ? JSON.parse(v) : v), z.any().optional()),
|
|
155
235
|
tags: z.array(z.string()).optional(),
|
|
156
236
|
project_slug: z.string().optional(),
|
|
157
237
|
})
|
|
@@ -160,6 +240,7 @@ export async function handleProcedureTool(name, args, client) {
|
|
|
160
240
|
name: parsed.name,
|
|
161
241
|
description: parsed.description,
|
|
162
242
|
definition: parsed.definition,
|
|
243
|
+
formDefinition: parsed.form_definition,
|
|
163
244
|
tags: parsed.tags,
|
|
164
245
|
teamSlug: parsed.project_slug,
|
|
165
246
|
});
|
|
@@ -180,15 +261,18 @@ export async function handleProcedureTool(name, args, client) {
|
|
|
180
261
|
description: z.string().nullable().optional(),
|
|
181
262
|
information: z.string().nullable().optional(),
|
|
182
263
|
definition: z.preprocess((v) => (typeof v === 'string' ? JSON.parse(v) : v), z.any().optional()),
|
|
264
|
+
form_definition: z.preprocess((v) => (typeof v === 'string' ? JSON.parse(v) : v), z.any().optional()),
|
|
183
265
|
is_active: z.preprocess((v) => (typeof v === 'string' ? v === 'true' : v), z.boolean().optional()),
|
|
184
266
|
tags: z.array(z.string()).optional(),
|
|
185
267
|
project_slug: z.string().optional(),
|
|
186
268
|
})
|
|
187
269
|
.parse(args);
|
|
188
|
-
const { procedure_id, project_slug, is_active, ...rest } = parsed;
|
|
270
|
+
const { procedure_id, project_slug, is_active, form_definition, ...rest } = parsed;
|
|
189
271
|
const data = { ...rest };
|
|
190
272
|
if (is_active !== undefined)
|
|
191
273
|
data.isActive = is_active;
|
|
274
|
+
if (form_definition !== undefined)
|
|
275
|
+
data.formDefinition = form_definition;
|
|
192
276
|
const procedure = await client.updateProcedure(procedure_id, data, project_slug);
|
|
193
277
|
return {
|
|
194
278
|
content: [
|
|
@@ -199,6 +283,33 @@ export async function handleProcedureTool(name, args, client) {
|
|
|
199
283
|
],
|
|
200
284
|
};
|
|
201
285
|
}
|
|
286
|
+
case 'start_case': {
|
|
287
|
+
const parsed = z
|
|
288
|
+
.object({
|
|
289
|
+
procedure_id: z.string(),
|
|
290
|
+
title: z.string().optional(),
|
|
291
|
+
priority: z.coerce.number().optional(),
|
|
292
|
+
additional_information: z.string().optional(),
|
|
293
|
+
form_values: z.preprocess((v) => (typeof v === 'string' ? JSON.parse(v) : v), z.record(z.unknown()).optional()),
|
|
294
|
+
project_slug: z.string().optional(),
|
|
295
|
+
})
|
|
296
|
+
.parse(args);
|
|
297
|
+
const result = await client.startCase(parsed.procedure_id, {
|
|
298
|
+
title: parsed.title,
|
|
299
|
+
priority: parsed.priority,
|
|
300
|
+
additionalInformation: parsed.additional_information,
|
|
301
|
+
formValues: parsed.form_values,
|
|
302
|
+
teamSlug: parsed.project_slug,
|
|
303
|
+
});
|
|
304
|
+
return {
|
|
305
|
+
content: [
|
|
306
|
+
{
|
|
307
|
+
type: 'text',
|
|
308
|
+
text: `Started case #${result.refId} (${result.caseId})\n\n${text(result)}`,
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
};
|
|
312
|
+
}
|
|
202
313
|
default:
|
|
203
314
|
return {
|
|
204
315
|
content: [{ type: 'text', text: `Unknown procedure tool: ${name}` }],
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// UUID format check — hyphenated 36-char hex; version nibble unconstrained to stay tolerant.
|
|
2
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3
|
+
// Accept "#r5", "#roadmap-5", "#5", or plain "5" — capture the numeric refId.
|
|
4
|
+
const RADAR_REF_PATTERN = /^#?(?:r|roadmap-)?(\d+)$/i;
|
|
5
|
+
// Resolve a user-supplied radar item reference (UUID, refId, or prefixed refId) to a UUID.
|
|
6
|
+
// stripUuids hides internal UUIDs from agents in responses, so agents pass refIds; the
|
|
7
|
+
// work-item create/update/filter endpoints require a UUID, so we translate at the MCP boundary.
|
|
8
|
+
export async function resolveRadarItemId(client, input, teamSlug) {
|
|
9
|
+
const trimmed = input.trim();
|
|
10
|
+
if (UUID_PATTERN.test(trimmed))
|
|
11
|
+
return trimmed;
|
|
12
|
+
const refMatch = trimmed.match(RADAR_REF_PATTERN);
|
|
13
|
+
if (!refMatch) {
|
|
14
|
+
throw new Error(`Invalid radar_item_id "${input}": expected a UUID, refId number, or prefixed refId like "#r5".`);
|
|
15
|
+
}
|
|
16
|
+
const refId = refMatch[1];
|
|
17
|
+
const radarItem = await client.getRadarItem(refId, teamSlug);
|
|
18
|
+
if (!radarItem) {
|
|
19
|
+
throw new Error(`Radar/initiative item #r${refId} not found.`);
|
|
20
|
+
}
|
|
21
|
+
return radarItem.id;
|
|
22
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { resolveRadarItemId } from './resolveRadarItemId.js';
|
|
3
|
+
const UUID = '11111111-2222-4333-8444-555555555555';
|
|
4
|
+
function makeClient(item) {
|
|
5
|
+
return {
|
|
6
|
+
getRadarItem: vi.fn(async () => item),
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
describe('resolveRadarItemId', () => {
|
|
10
|
+
it('passes UUIDs through without lookup', async () => {
|
|
11
|
+
const client = makeClient(null);
|
|
12
|
+
const result = await resolveRadarItemId(client, UUID);
|
|
13
|
+
expect(result).toBe(UUID);
|
|
14
|
+
expect(client.getRadarItem).not.toHaveBeenCalled();
|
|
15
|
+
});
|
|
16
|
+
it.each(['5', '#5', '#r5', '#roadmap-5', ' 5 '])('resolves refId form %s to UUID via getRadarItem', async (input) => {
|
|
17
|
+
const client = makeClient({ id: UUID, refId: 5 });
|
|
18
|
+
const result = await resolveRadarItemId(client, input, 'my-team');
|
|
19
|
+
expect(result).toBe(UUID);
|
|
20
|
+
expect(client.getRadarItem).toHaveBeenCalledWith('5', 'my-team');
|
|
21
|
+
});
|
|
22
|
+
it('throws when refId cannot be resolved', async () => {
|
|
23
|
+
const client = makeClient(null);
|
|
24
|
+
await expect(resolveRadarItemId(client, '#r99')).rejects.toThrow(/#r99 not found/);
|
|
25
|
+
});
|
|
26
|
+
it('rejects malformed input', async () => {
|
|
27
|
+
const client = makeClient(null);
|
|
28
|
+
await expect(resolveRadarItemId(client, 'not-a-ref')).rejects.toThrow(/Invalid radar_item_id/);
|
|
29
|
+
expect(client.getRadarItem).not.toHaveBeenCalled();
|
|
30
|
+
});
|
|
31
|
+
});
|