wolfpack-mcp 1.0.84 → 1.0.85
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/client.js +16 -0
- package/dist/index.js +4 -2
- package/dist/procedureTools.js +111 -0
- package/dist/serverInitialize.test.js +42 -0
- package/dist/serverInstructions.js +20 -0
- package/dist/serverInstructions.test.js +31 -0
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -891,6 +891,22 @@ export class WolfpackClient {
|
|
|
891
891
|
const { teamSlug, ...body } = data;
|
|
892
892
|
return this.api.post(this.withTeamSlug(`/procedures/${procedureId}/start`, teamSlug), body);
|
|
893
893
|
}
|
|
894
|
+
async listCases(options) {
|
|
895
|
+
const params = new URLSearchParams();
|
|
896
|
+
if (options?.teamSlug)
|
|
897
|
+
params.append('teamSlug', options.teamSlug);
|
|
898
|
+
if (options?.procedureId)
|
|
899
|
+
params.append('procedureId', options.procedureId);
|
|
900
|
+
if (options?.status)
|
|
901
|
+
params.append('status', options.status);
|
|
902
|
+
return this.fetchAllPages('/cases', params);
|
|
903
|
+
}
|
|
904
|
+
async upgradeCase(caseId, teamSlug) {
|
|
905
|
+
return this.api.post(this.withTeamSlug(`/cases/${caseId}/upgrade`, teamSlug), {});
|
|
906
|
+
}
|
|
907
|
+
async upgradeProcedureCases(procedureId, teamSlug) {
|
|
908
|
+
return this.api.post(this.withTeamSlug(`/procedures/${procedureId}/upgrade-cases`, teamSlug), {});
|
|
909
|
+
}
|
|
894
910
|
// ─── Agent Self-Introspection ──────────────────────────────────────────────
|
|
895
911
|
async getSelf() {
|
|
896
912
|
return this.api.get('/self');
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import { AGENT_BUILDER_TOOLS, handleAgentBuilderTool } from './agentBuilderTools
|
|
|
13
13
|
import { PROCEDURE_TOOLS, handleProcedureTool } from './procedureTools.js';
|
|
14
14
|
import { AGENT_SELF_TOOLS, AGENT_MEMORY_TOOLS, handleAgentSelfTool } from './agentSelfTools.js';
|
|
15
15
|
import { resolveRadarItemId } from './resolveRadarItemId.js';
|
|
16
|
+
import { SERVER_INSTRUCTIONS } from './serverInstructions.js';
|
|
16
17
|
import { fetch as proxyFetch } from './proxyFetch.js';
|
|
17
18
|
// Get current package version
|
|
18
19
|
const require = createRequire(import.meta.url);
|
|
@@ -717,6 +718,7 @@ class WolfpackMCPServer {
|
|
|
717
718
|
tools: { listChanged: true },
|
|
718
719
|
logging: {},
|
|
719
720
|
},
|
|
721
|
+
instructions: SERVER_INSTRUCTIONS,
|
|
720
722
|
});
|
|
721
723
|
this.setupHandlers();
|
|
722
724
|
}
|
|
@@ -744,7 +746,7 @@ class WolfpackMCPServer {
|
|
|
744
746
|
'Other document types REQUIRE a prefix and are never bare numbers: ' +
|
|
745
747
|
'#i1 or #issue-1 for issues, #r1 or #roadmap-1 for roadmap/radar, ' +
|
|
746
748
|
'#j1 or #journal-1 for journal, #c1 or #case-1 for cases, #p1 or #proc-1 for procedures. ' +
|
|
747
|
-
'Wiki pages are referenced by path (e.g. /docs/setup). There is no ambiguity: bare #N = work item.',
|
|
749
|
+
'Wiki pages are referenced by path (e.g. /docs/setup). There is no ambiguity: bare #N = work item, and never a GitHub issue or pull request — read a number as a GitHub reference only when the user says GitHub, PR, or gh.',
|
|
748
750
|
inputSchema: {
|
|
749
751
|
type: 'object',
|
|
750
752
|
properties: {},
|
|
@@ -861,7 +863,7 @@ class WolfpackMCPServer {
|
|
|
861
863
|
},
|
|
862
864
|
{
|
|
863
865
|
name: 'get_work_item',
|
|
864
|
-
description: 'Get a specific work item/task by reference number. This is the DEFAULT lookup when a user says "#1" or "work on #42" — bare #N always means a work item
|
|
866
|
+
description: 'Get a specific work item/task by reference number. This is the DEFAULT lookup when a user says "#1" or "work on #42" — bare #N always means a work item, never a GitHub issue: use this tool, not `gh issue view`. ' +
|
|
865
867
|
'Returns full details including description (markdown notes). ' +
|
|
866
868
|
'Call this before updating to see current content. ' +
|
|
867
869
|
'WORKFLOW: When asked to work on an item, check its status and follow the required state transitions ' +
|
package/dist/procedureTools.js
CHANGED
|
@@ -195,6 +195,72 @@ export const PROCEDURE_TOOLS = [
|
|
|
195
195
|
required: ['procedure_id'],
|
|
196
196
|
},
|
|
197
197
|
},
|
|
198
|
+
{
|
|
199
|
+
name: 'list_cases',
|
|
200
|
+
description: 'List cases, newest first, optionally filtered to one procedure and/or one status ' +
|
|
201
|
+
'(running, paused, completed, failed, cancelled). Each case shows the procedure version it ' +
|
|
202
|
+
'runs and `behind: true` when the procedure has since been edited — a running case that is ' +
|
|
203
|
+
'behind can be moved on with upgrade_case.',
|
|
204
|
+
inputSchema: {
|
|
205
|
+
type: 'object',
|
|
206
|
+
properties: {
|
|
207
|
+
procedure_id: {
|
|
208
|
+
type: 'string',
|
|
209
|
+
description: 'Only cases of this procedure (UUID or refId number)',
|
|
210
|
+
},
|
|
211
|
+
status: {
|
|
212
|
+
type: 'string',
|
|
213
|
+
description: 'Only cases in this status, e.g. "running"',
|
|
214
|
+
},
|
|
215
|
+
project_slug: {
|
|
216
|
+
type: 'string',
|
|
217
|
+
description: 'Project slug (required when using a refId, or with access to several projects)',
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: 'upgrade_case',
|
|
224
|
+
description: 'Move a running (or paused) case onto the latest version of its procedure. The case resumes ' +
|
|
225
|
+
'at the node it is waiting at, keeping its variables and earlier activity outputs; nothing ' +
|
|
226
|
+
'before that node runs again. Refused — with the reason — when the case is not running, is ' +
|
|
227
|
+
'already on the current version, or its current node id is not in the new version (cancel ' +
|
|
228
|
+
'the case and start the procedure again from the work item instead).',
|
|
229
|
+
inputSchema: {
|
|
230
|
+
type: 'object',
|
|
231
|
+
properties: {
|
|
232
|
+
case_id: {
|
|
233
|
+
type: 'string',
|
|
234
|
+
description: 'Case UUID or refId number',
|
|
235
|
+
},
|
|
236
|
+
project_slug: {
|
|
237
|
+
type: 'string',
|
|
238
|
+
description: 'Project slug (required when using refId)',
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
required: ['case_id'],
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: 'upgrade_procedure_cases',
|
|
246
|
+
description: 'Upgrade every running case of a procedure that is still on an older version onto the ' +
|
|
247
|
+
'current one (see upgrade_case). Cases are upgraded one at a time; the result lists the cases ' +
|
|
248
|
+
'upgraded and, for each case skipped, why.',
|
|
249
|
+
inputSchema: {
|
|
250
|
+
type: 'object',
|
|
251
|
+
properties: {
|
|
252
|
+
procedure_id: {
|
|
253
|
+
type: 'string',
|
|
254
|
+
description: 'Procedure UUID or refId number',
|
|
255
|
+
},
|
|
256
|
+
project_slug: {
|
|
257
|
+
type: 'string',
|
|
258
|
+
description: 'Project slug (required when using refId)',
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
required: ['procedure_id'],
|
|
262
|
+
},
|
|
263
|
+
},
|
|
198
264
|
];
|
|
199
265
|
export async function handleProcedureTool(name, args, client) {
|
|
200
266
|
const text = (t) => JSON.stringify(t, null, 2);
|
|
@@ -315,6 +381,51 @@ export async function handleProcedureTool(name, args, client) {
|
|
|
315
381
|
],
|
|
316
382
|
};
|
|
317
383
|
}
|
|
384
|
+
case 'list_cases': {
|
|
385
|
+
const parsed = z
|
|
386
|
+
.object({
|
|
387
|
+
procedure_id: z.string().optional(),
|
|
388
|
+
status: z.string().optional(),
|
|
389
|
+
project_slug: z.string().optional(),
|
|
390
|
+
})
|
|
391
|
+
.parse(args);
|
|
392
|
+
const result = await client.listCases({
|
|
393
|
+
procedureId: parsed.procedure_id,
|
|
394
|
+
status: parsed.status,
|
|
395
|
+
teamSlug: parsed.project_slug,
|
|
396
|
+
});
|
|
397
|
+
return { content: [{ type: 'text', text: text(result) }] };
|
|
398
|
+
}
|
|
399
|
+
case 'upgrade_case': {
|
|
400
|
+
const parsed = z
|
|
401
|
+
.object({ case_id: z.string(), project_slug: z.string().optional() })
|
|
402
|
+
.parse(args);
|
|
403
|
+
const result = await client.upgradeCase(parsed.case_id, parsed.project_slug);
|
|
404
|
+
return {
|
|
405
|
+
content: [
|
|
406
|
+
{
|
|
407
|
+
type: 'text',
|
|
408
|
+
text: `Upgraded case #${result.caseRefId} from v${result.fromVersion} to v${result.toVersion}; ` +
|
|
409
|
+
`it resumes at "${result.resumeNodeLabel}" (${result.resumeNodeId})\n\n${text(result)}`,
|
|
410
|
+
},
|
|
411
|
+
],
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
case 'upgrade_procedure_cases': {
|
|
415
|
+
const parsed = z
|
|
416
|
+
.object({ procedure_id: z.string(), project_slug: z.string().optional() })
|
|
417
|
+
.parse(args);
|
|
418
|
+
const result = await client.upgradeProcedureCases(parsed.procedure_id, parsed.project_slug);
|
|
419
|
+
return {
|
|
420
|
+
content: [
|
|
421
|
+
{
|
|
422
|
+
type: 'text',
|
|
423
|
+
text: `Upgraded ${result.upgraded.length} case(s), skipped ${result.skipped.length}\n\n` +
|
|
424
|
+
text(result),
|
|
425
|
+
},
|
|
426
|
+
],
|
|
427
|
+
};
|
|
428
|
+
}
|
|
318
429
|
default:
|
|
319
430
|
return {
|
|
320
431
|
content: [{ type: 'text', text: `Unknown procedure tool: ${name}` }],
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
3
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { SERVER_INSTRUCTIONS } from './serverInstructions.js';
|
|
6
|
+
const entryPoint = fileURLToPath(new URL('./index.ts', import.meta.url));
|
|
7
|
+
// The constant existing is not the fix — a client only sees it if the server
|
|
8
|
+
// hands it back in the `initialize` result (#1373). index.ts starts the server
|
|
9
|
+
// at import, so this drives the real thing over stdio instead.
|
|
10
|
+
//
|
|
11
|
+
// The handshake completes before any tool call, so nothing here needs a live
|
|
12
|
+
// backend: the key only has to look like one for validateConfig to let the
|
|
13
|
+
// process start, and the API URL is pointed at a closed port so the server's
|
|
14
|
+
// background capabilities fetch fails immediately rather than calling out to
|
|
15
|
+
// whatever WOLFPACK_API_URL the developer's own environment is set to.
|
|
16
|
+
const SERVER_ENV = {
|
|
17
|
+
...process.env,
|
|
18
|
+
WOLFPACK_API_KEY: 'wfp_sk_test',
|
|
19
|
+
WOLFPACK_API_URL: 'http://127.0.0.1:1',
|
|
20
|
+
WOLFPACK_PROJECT_SLUG: '',
|
|
21
|
+
WOLFPACK_TEAM_SLUG: '',
|
|
22
|
+
WOLFPACK_ORG_SLUG: '',
|
|
23
|
+
};
|
|
24
|
+
describe('the stdio server initialize', () => {
|
|
25
|
+
it('advertises the reference-convention instructions to the client', async () => {
|
|
26
|
+
const client = new Client({ name: 'test', version: '1.0.0' });
|
|
27
|
+
const transport = new StdioClientTransport({
|
|
28
|
+
// tsx directly, not via npx: the transport kills the process it spawned,
|
|
29
|
+
// and an npx wrapper would leave the server orphaned behind it.
|
|
30
|
+
command: process.execPath,
|
|
31
|
+
args: ['--import', 'tsx', entryPoint],
|
|
32
|
+
env: SERVER_ENV,
|
|
33
|
+
});
|
|
34
|
+
await client.connect(transport);
|
|
35
|
+
try {
|
|
36
|
+
expect(client.getInstructions()).toBe(SERVER_INSTRUCTIONS);
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
await client.close();
|
|
40
|
+
}
|
|
41
|
+
}, 60_000);
|
|
42
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server instructions, returned in the MCP `initialize` result.
|
|
3
|
+
*
|
|
4
|
+
* Clients put this in the system prompt, which makes it the only text that
|
|
5
|
+
* reaches the model *before* it decides what to do. Tool descriptions do not:
|
|
6
|
+
* they are read once the model is already choosing between tools, and "work on
|
|
7
|
+
* #123" in a git repository sends it to `gh issue view` instead (#1373). The
|
|
8
|
+
* reference convention therefore lives here, and names the GitHub reading it is
|
|
9
|
+
* over-ruling rather than only the Wolfpack one.
|
|
10
|
+
*
|
|
11
|
+
* Mirrored in `packages/backend/src/mcp/serverInstructions.ts` for the remote
|
|
12
|
+
* server — the two servers have no shared package, and already keep their tool
|
|
13
|
+
* descriptions in step by hand.
|
|
14
|
+
*/
|
|
15
|
+
export const SERVER_INSTRUCTIONS = `Wolfpack is this project's tracker. Its references override the usual GitHub reading of "#N".
|
|
16
|
+
|
|
17
|
+
- A bare "#123" — "work on #123", "look at #42" — is ALWAYS a Wolfpack work item. Call get_work_item. Do not run \`gh issue view\`, and do not search GitHub for it.
|
|
18
|
+
- Read a number as a GitHub issue or pull request only when the user says GitHub, PR, or gh — "the GitHub issue #123", "PR #123".
|
|
19
|
+
- Every other Wolfpack type needs its prefix and is never a bare number: #i123 issues, #r123 roadmap/initiatives, #j123 journal entries, #c123 cases, #p123 procedures. Wiki pages are addressed by path, e.g. /docs/setup.
|
|
20
|
+
- On its own, "issue" means a Wolfpack issue (#i123). A GitHub issue is only ever called a GitHub issue.`;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { SERVER_INSTRUCTIONS } from './serverInstructions.js';
|
|
3
|
+
describe('SERVER_INSTRUCTIONS', () => {
|
|
4
|
+
it('claims a bare #N for work items before any tool is chosen (#1373)', () => {
|
|
5
|
+
// The whole point of the instructions field: a client puts it in the system
|
|
6
|
+
// prompt, so it is read before the model decides between get_work_item and
|
|
7
|
+
// reaching for the shell.
|
|
8
|
+
expect(SERVER_INSTRUCTIONS).toContain('#123');
|
|
9
|
+
expect(SERVER_INSTRUCTIONS).toContain('work item');
|
|
10
|
+
expect(SERVER_INSTRUCTIONS).toContain('get_work_item');
|
|
11
|
+
});
|
|
12
|
+
it('names the GitHub reading it is over-ruling', () => {
|
|
13
|
+
// "bare #N is not a Wolfpack issue" was already on the tools and did not
|
|
14
|
+
// help: the rival reading is a GitHub one, so it has to be named.
|
|
15
|
+
expect(SERVER_INSTRUCTIONS).toContain('gh issue');
|
|
16
|
+
expect(SERVER_INSTRUCTIONS).toContain('GitHub');
|
|
17
|
+
});
|
|
18
|
+
it('says when a number IS a GitHub reference, so the rule can be applied', () => {
|
|
19
|
+
// A rule with no exception gets ignored the first time the user genuinely
|
|
20
|
+
// means a pull request.
|
|
21
|
+
expect(SERVER_INSTRUCTIONS).toMatch(/only when .*GitHub/i);
|
|
22
|
+
});
|
|
23
|
+
it('covers every prefixed reference type so nothing else is guessed at', () => {
|
|
24
|
+
for (const prefix of ['#i', '#r', '#j', '#c', '#p']) {
|
|
25
|
+
expect(SERVER_INSTRUCTIONS).toContain(`${prefix}123`);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
it('stays short enough to sit in a system prompt', () => {
|
|
29
|
+
expect(SERVER_INSTRUCTIONS.length).toBeLessThan(1200);
|
|
30
|
+
});
|
|
31
|
+
});
|