wolfpack-mcp 1.0.105 → 1.0.106
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 +126 -0
- package/dist/agentObserveTools.js +17 -2
- package/dist/agentSelfTools.js +25 -0
- package/dist/browserTools.js +18 -0
- package/dist/index.js +14 -19
- package/dist/stdioToolPermissions.test.js +24 -0
- package/dist/toolCatalogue.js +32 -10
- package/dist/toolCatalogue.test.js +37 -3
- package/package.json +2 -1
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent builder MCP tool definitions and handlers.
|
|
3
3
|
* Registered only when the API key has the `agent_builder` capability.
|
|
4
|
+
*
|
|
5
|
+
* Each entry declares the scope its route already checks, so a key granted the
|
|
6
|
+
* capability on `mcp:agents:read` is offered the reads and not the writes it
|
|
7
|
+
* would be refused (#2493). `list_organisations` is the one `permission: null`
|
|
8
|
+
* here: `GET /mcp/organisations` checks no scope, being the discovery call the
|
|
9
|
+
* rest of the family needs to name an organisation at all.
|
|
4
10
|
*/
|
|
5
11
|
import { z } from 'zod';
|
|
6
12
|
import { config } from './config.js';
|
|
@@ -34,12 +40,18 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
34
40
|
// ─── Group 0: Organisations ─────────────────────────────────────────────
|
|
35
41
|
{
|
|
36
42
|
name: 'list_organisations',
|
|
43
|
+
permission: null,
|
|
44
|
+
capability: 'agent_builder',
|
|
45
|
+
stdioOnly: true,
|
|
37
46
|
description: 'List organisations you belong to. Use org_slug parameter in other agent builder tools when you belong to multiple organisations.',
|
|
38
47
|
inputSchema: { type: 'object', properties: {} },
|
|
39
48
|
},
|
|
40
49
|
// ─── Group 1: Agent CRUD ─────────────────────────────────────────────────
|
|
41
50
|
{
|
|
42
51
|
name: 'list_agents',
|
|
52
|
+
permission: 'mcp:agents:read',
|
|
53
|
+
capability: 'agent_builder',
|
|
54
|
+
stdioOnly: true,
|
|
43
55
|
description: 'List agents. Returns name, status, template, and assigned projects. ' +
|
|
44
56
|
'scope "org" (the default) lists the organisation\'s agents; scope "personal" lists your own ' +
|
|
45
57
|
'My Agents from My Space, which belong to no organisation. Call it twice to see both.',
|
|
@@ -57,6 +69,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
57
69
|
},
|
|
58
70
|
{
|
|
59
71
|
name: 'get_agent',
|
|
72
|
+
permission: 'mcp:agents:read',
|
|
73
|
+
capability: 'agent_builder',
|
|
74
|
+
stdioOnly: true,
|
|
60
75
|
description: 'Get full details for an agent including config, instructions, LLM model, and linked skills. ' +
|
|
61
76
|
'Takes an organisation agent or one of your own personal agents.',
|
|
62
77
|
inputSchema: {
|
|
@@ -70,6 +85,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
70
85
|
},
|
|
71
86
|
{
|
|
72
87
|
name: 'create_agent',
|
|
88
|
+
permission: 'mcp:agents:create',
|
|
89
|
+
capability: 'agent_builder',
|
|
90
|
+
stdioOnly: true,
|
|
73
91
|
description: 'Create a new agent from a container image. Use list_container_images to see available images. ' +
|
|
74
92
|
'With personal=true, creates one of your own My Agents instead of an organisation agent: ' +
|
|
75
93
|
'it needs a handle, and only an image opened to personal use (list_container_images with ' +
|
|
@@ -98,6 +116,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
98
116
|
},
|
|
99
117
|
{
|
|
100
118
|
name: 'update_agent',
|
|
119
|
+
permission: 'mcp:agents:update',
|
|
120
|
+
capability: 'agent_builder',
|
|
121
|
+
stdioOnly: true,
|
|
101
122
|
description: "Update an agent's config, instructions, or LLM model. Omit fields to leave them unchanged.",
|
|
102
123
|
inputSchema: {
|
|
103
124
|
type: 'object',
|
|
@@ -144,6 +165,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
144
165
|
},
|
|
145
166
|
{
|
|
146
167
|
name: 'list_agent_aliases',
|
|
168
|
+
permission: 'mcp:agents:read',
|
|
169
|
+
capability: 'agent_builder',
|
|
170
|
+
stdioOnly: true,
|
|
147
171
|
description: 'List the aliases of an agent. Aliases share the source agent definition but have their own identity and queue.',
|
|
148
172
|
inputSchema: {
|
|
149
173
|
type: 'object',
|
|
@@ -156,6 +180,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
156
180
|
},
|
|
157
181
|
{
|
|
158
182
|
name: 'create_agent_alias',
|
|
183
|
+
permission: 'mcp:agents:create',
|
|
184
|
+
capability: 'agent_builder',
|
|
185
|
+
stdioOnly: true,
|
|
159
186
|
description: 'Create an alias of an agent, so the same definition can run work in parallel. ' +
|
|
160
187
|
'An agent runs one session at a time, so N concurrent workers means N aliases. ' +
|
|
161
188
|
'The alias live-tracks the source for container image, prompts, instructions, LLM, tools, skills and tasks — edit those once on the source. ' +
|
|
@@ -173,6 +200,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
173
200
|
},
|
|
174
201
|
{
|
|
175
202
|
name: 'get_agent_mcp_selections',
|
|
203
|
+
permission: 'mcp:agents:read',
|
|
204
|
+
capability: 'agent_builder',
|
|
205
|
+
stdioOnly: true,
|
|
176
206
|
description: "List the template MCP servers this agent has opted out of. Returns { disabled: string[] } where each string is the MCP server key from the agent's template.",
|
|
177
207
|
inputSchema: {
|
|
178
208
|
type: 'object',
|
|
@@ -185,6 +215,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
185
215
|
},
|
|
186
216
|
{
|
|
187
217
|
name: 'update_agent_mcp_selections',
|
|
218
|
+
permission: 'mcp:agents:update',
|
|
219
|
+
capability: 'agent_builder',
|
|
220
|
+
stdioOnly: true,
|
|
188
221
|
description: 'Replace the list of template MCP servers this agent has opted out of. ' +
|
|
189
222
|
'Pass an empty array to re-enable every template server. Template servers ' +
|
|
190
223
|
'not in the list stay enabled; those in the list are disabled for this agent.',
|
|
@@ -205,6 +238,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
205
238
|
// ─── Group 2: Project Assignment ─────────────────────────────────────────
|
|
206
239
|
{
|
|
207
240
|
name: 'list_agent_projects',
|
|
241
|
+
permission: 'mcp:agents:read',
|
|
242
|
+
capability: 'agent_builder',
|
|
243
|
+
stdioOnly: true,
|
|
208
244
|
description: 'List projects assigned to an agent.',
|
|
209
245
|
inputSchema: {
|
|
210
246
|
type: 'object',
|
|
@@ -217,6 +253,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
217
253
|
},
|
|
218
254
|
{
|
|
219
255
|
name: 'assign_agent_to_project',
|
|
256
|
+
permission: 'mcp:agents:update',
|
|
257
|
+
capability: 'agent_builder',
|
|
258
|
+
stdioOnly: true,
|
|
220
259
|
description: 'Assign an agent to a project by slug.',
|
|
221
260
|
inputSchema: {
|
|
222
261
|
type: 'object',
|
|
@@ -230,6 +269,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
230
269
|
},
|
|
231
270
|
{
|
|
232
271
|
name: 'remove_agent_from_project',
|
|
272
|
+
permission: 'mcp:agents:update',
|
|
273
|
+
capability: 'agent_builder',
|
|
274
|
+
stdioOnly: true,
|
|
233
275
|
description: 'Remove an agent from a project.',
|
|
234
276
|
inputSchema: {
|
|
235
277
|
type: 'object',
|
|
@@ -244,6 +286,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
244
286
|
// ─── Group 3: Sessions ────────────────────────────────────────────────────
|
|
245
287
|
{
|
|
246
288
|
name: 'list_agent_sessions',
|
|
289
|
+
permission: 'mcp:agents:read',
|
|
290
|
+
capability: 'agent_builder',
|
|
291
|
+
stdioOnly: true,
|
|
247
292
|
description: 'List sessions for an agent. Filter by status: running, stopped, failed.',
|
|
248
293
|
inputSchema: {
|
|
249
294
|
type: 'object',
|
|
@@ -263,6 +308,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
263
308
|
},
|
|
264
309
|
{
|
|
265
310
|
name: 'get_agent_session',
|
|
311
|
+
permission: 'mcp:agents:read',
|
|
312
|
+
capability: 'agent_builder',
|
|
313
|
+
stdioOnly: true,
|
|
266
314
|
description: 'Get session metadata and recent events (last 20). Does NOT include full conversation — use get_agent_session_conversation for that.',
|
|
267
315
|
inputSchema: {
|
|
268
316
|
type: 'object',
|
|
@@ -276,6 +324,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
276
324
|
},
|
|
277
325
|
{
|
|
278
326
|
name: 'get_agent_session_conversation',
|
|
327
|
+
permission: 'mcp:agents:read',
|
|
328
|
+
capability: 'agent_builder',
|
|
329
|
+
stdioOnly: true,
|
|
279
330
|
description: 'Get the full conversation history for a session (paginated). Can be large for long-running sessions.',
|
|
280
331
|
inputSchema: {
|
|
281
332
|
type: 'object',
|
|
@@ -291,6 +342,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
291
342
|
},
|
|
292
343
|
{
|
|
293
344
|
name: 'run_agent',
|
|
345
|
+
permission: 'mcp:agents:create',
|
|
346
|
+
capability: 'agent_builder',
|
|
347
|
+
stdioOnly: true,
|
|
294
348
|
description: 'Queue an ad-hoc session with an inline prompt. This is async — it returns a queue entry ID immediately. ' +
|
|
295
349
|
'Use list_agent_queue or list_agent_sessions to track progress.',
|
|
296
350
|
inputSchema: {
|
|
@@ -314,6 +368,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
314
368
|
},
|
|
315
369
|
{
|
|
316
370
|
name: 'stop_agent_session',
|
|
371
|
+
permission: 'mcp:agents:update',
|
|
372
|
+
capability: 'agent_builder',
|
|
373
|
+
stdioOnly: true,
|
|
317
374
|
description: 'Stop a running agent session.',
|
|
318
375
|
inputSchema: {
|
|
319
376
|
type: 'object',
|
|
@@ -327,6 +384,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
327
384
|
},
|
|
328
385
|
{
|
|
329
386
|
name: 'resume_agent_session',
|
|
387
|
+
permission: 'mcp:agents:create',
|
|
388
|
+
capability: 'agent_builder',
|
|
389
|
+
stdioOnly: true,
|
|
330
390
|
description: 'Resume a completed session with a follow-up prompt. The agent continues with full prior context.',
|
|
331
391
|
inputSchema: {
|
|
332
392
|
type: 'object',
|
|
@@ -342,6 +402,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
342
402
|
// ─── Group 4: Tasks & Queue ───────────────────────────────────────────────
|
|
343
403
|
{
|
|
344
404
|
name: 'list_agent_tasks',
|
|
405
|
+
permission: 'mcp:agents:read',
|
|
406
|
+
capability: 'agent_builder',
|
|
407
|
+
stdioOnly: true,
|
|
345
408
|
description: 'List defined tasks for an agent.',
|
|
346
409
|
inputSchema: {
|
|
347
410
|
type: 'object',
|
|
@@ -354,6 +417,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
354
417
|
},
|
|
355
418
|
{
|
|
356
419
|
name: 'get_agent_task',
|
|
420
|
+
permission: 'mcp:agents:read',
|
|
421
|
+
capability: 'agent_builder',
|
|
422
|
+
stdioOnly: true,
|
|
357
423
|
description: 'Get details of a specific agent task.',
|
|
358
424
|
inputSchema: {
|
|
359
425
|
type: 'object',
|
|
@@ -367,6 +433,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
367
433
|
},
|
|
368
434
|
{
|
|
369
435
|
name: 'create_agent_task',
|
|
436
|
+
permission: 'mcp:agents:create',
|
|
437
|
+
capability: 'agent_builder',
|
|
438
|
+
stdioOnly: true,
|
|
370
439
|
description: 'Create a new task for an agent.',
|
|
371
440
|
inputSchema: {
|
|
372
441
|
type: 'object',
|
|
@@ -404,6 +473,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
404
473
|
},
|
|
405
474
|
{
|
|
406
475
|
name: 'update_agent_task',
|
|
476
|
+
permission: 'mcp:agents:update',
|
|
477
|
+
capability: 'agent_builder',
|
|
478
|
+
stdioOnly: true,
|
|
407
479
|
description: "Update a task's prompt, sort order, model pin, or disabled tools/MCP servers.",
|
|
408
480
|
inputSchema: {
|
|
409
481
|
type: 'object',
|
|
@@ -440,6 +512,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
440
512
|
},
|
|
441
513
|
{
|
|
442
514
|
name: 'run_agent_task',
|
|
515
|
+
permission: 'mcp:agents:create',
|
|
516
|
+
capability: 'agent_builder',
|
|
517
|
+
stdioOnly: true,
|
|
443
518
|
description: 'Queue a saved task for immediate execution. Async — returns a queue entry ID. ' +
|
|
444
519
|
'Use list_agent_queue to track progress.',
|
|
445
520
|
inputSchema: {
|
|
@@ -469,6 +544,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
469
544
|
},
|
|
470
545
|
{
|
|
471
546
|
name: 'list_agent_queue',
|
|
547
|
+
permission: 'mcp:agents:read',
|
|
548
|
+
capability: 'agent_builder',
|
|
549
|
+
stdioOnly: true,
|
|
472
550
|
description: 'List queue entries for an agent. Use this to track progress after run_agent or run_agent_task.',
|
|
473
551
|
inputSchema: {
|
|
474
552
|
type: 'object',
|
|
@@ -486,6 +564,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
486
564
|
},
|
|
487
565
|
{
|
|
488
566
|
name: 'cancel_queue_entry',
|
|
567
|
+
permission: 'mcp:agents:update',
|
|
568
|
+
capability: 'agent_builder',
|
|
569
|
+
stdioOnly: true,
|
|
489
570
|
description: 'Cancel a queued (not yet running) queue entry.',
|
|
490
571
|
inputSchema: {
|
|
491
572
|
type: 'object',
|
|
@@ -500,6 +581,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
500
581
|
// ─── Group 4b: Schedules ─────────────────────────────────────────────────
|
|
501
582
|
{
|
|
502
583
|
name: 'list_agent_schedules',
|
|
584
|
+
permission: 'mcp:agents:read',
|
|
585
|
+
capability: 'agent_builder',
|
|
586
|
+
stdioOnly: true,
|
|
503
587
|
description: 'List all schedules for an agent. Each schedule defines when and how often the agent wakes up, ' +
|
|
504
588
|
'and which tasks to run.',
|
|
505
589
|
inputSchema: {
|
|
@@ -513,6 +597,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
513
597
|
},
|
|
514
598
|
{
|
|
515
599
|
name: 'create_agent_schedule',
|
|
600
|
+
permission: 'mcp:agents:create',
|
|
601
|
+
capability: 'agent_builder',
|
|
602
|
+
stdioOnly: true,
|
|
516
603
|
description: 'Create a new schedule for an agent. A schedule defines frequency, working hours, timezone, ' +
|
|
517
604
|
'and which tasks to run. Schedules are created disabled by default.',
|
|
518
605
|
inputSchema: {
|
|
@@ -580,6 +667,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
580
667
|
},
|
|
581
668
|
{
|
|
582
669
|
name: 'update_agent_schedule',
|
|
670
|
+
permission: 'mcp:agents:update',
|
|
671
|
+
capability: 'agent_builder',
|
|
672
|
+
stdioOnly: true,
|
|
583
673
|
description: "Update a schedule's configuration or task assignments.",
|
|
584
674
|
inputSchema: {
|
|
585
675
|
type: 'object',
|
|
@@ -641,6 +731,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
641
731
|
// ─── Group 5: Skills (authoring) ─────────────────────────────────────────
|
|
642
732
|
{
|
|
643
733
|
name: 'create_skill',
|
|
734
|
+
permission: 'mcp:skills:create',
|
|
735
|
+
capability: 'agent_builder',
|
|
736
|
+
stdioOnly: true,
|
|
644
737
|
description: 'Create a skill. By default creates an org-level skill. ' +
|
|
645
738
|
'Pass agent_id to create an agent-private skill scoped to that agent only.',
|
|
646
739
|
inputSchema: {
|
|
@@ -669,6 +762,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
669
762
|
},
|
|
670
763
|
{
|
|
671
764
|
name: 'update_skill',
|
|
765
|
+
permission: 'mcp:skills:update',
|
|
766
|
+
capability: 'agent_builder',
|
|
767
|
+
stdioOnly: true,
|
|
672
768
|
description: "Update a skill's instructions or description.",
|
|
673
769
|
inputSchema: {
|
|
674
770
|
type: 'object',
|
|
@@ -684,6 +780,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
684
780
|
},
|
|
685
781
|
{
|
|
686
782
|
name: 'create_skill_resource',
|
|
783
|
+
permission: 'mcp:skills:update',
|
|
784
|
+
capability: 'agent_builder',
|
|
785
|
+
stdioOnly: true,
|
|
687
786
|
description: 'Add a text resource to a skill (script, reference, or asset).',
|
|
688
787
|
inputSchema: {
|
|
689
788
|
type: 'object',
|
|
@@ -704,6 +803,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
704
803
|
},
|
|
705
804
|
{
|
|
706
805
|
name: 'update_skill_resource',
|
|
806
|
+
permission: 'mcp:skills:update',
|
|
807
|
+
capability: 'agent_builder',
|
|
808
|
+
stdioOnly: true,
|
|
707
809
|
description: "Update a skill resource's content.",
|
|
708
810
|
inputSchema: {
|
|
709
811
|
type: 'object',
|
|
@@ -720,6 +822,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
720
822
|
},
|
|
721
823
|
{
|
|
722
824
|
name: 'set_agent_skills',
|
|
825
|
+
permission: 'mcp:agents:update',
|
|
826
|
+
capability: 'agent_builder',
|
|
827
|
+
stdioOnly: true,
|
|
723
828
|
description: 'Replace the full set of skills assigned to an agent. ' +
|
|
724
829
|
'Use list_org_skills to find skill IDs, then pass all desired skill IDs here.',
|
|
725
830
|
inputSchema: {
|
|
@@ -739,6 +844,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
739
844
|
// ─── Group 5b: Skills (reading) ─────────────────────────────────────────
|
|
740
845
|
{
|
|
741
846
|
name: 'list_agent_skills',
|
|
847
|
+
permission: 'mcp:skills:read',
|
|
848
|
+
capability: 'agent_builder',
|
|
849
|
+
stdioOnly: true,
|
|
742
850
|
description: 'List all skills for a specific agent: agent-private skills + linked org skills + built-in skills. ' +
|
|
743
851
|
'Returns full skill objects with IDs. Agent-scoped skills have agentProfileId set. ' +
|
|
744
852
|
'Use this to see what skills an agent has and get IDs for update_skill.',
|
|
@@ -752,6 +860,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
752
860
|
},
|
|
753
861
|
{
|
|
754
862
|
name: 'get_skill_detail',
|
|
863
|
+
permission: 'mcp:skills:read',
|
|
864
|
+
capability: 'agent_builder',
|
|
865
|
+
stdioOnly: true,
|
|
755
866
|
description: 'Get the full content of a skill by name. ' +
|
|
756
867
|
'Returns instructions, metadata, and a list of attached resources. ' +
|
|
757
868
|
'With agent_id: resolves agent-private → org → system (use after list_agent_skills). ' +
|
|
@@ -774,6 +885,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
774
885
|
},
|
|
775
886
|
{
|
|
776
887
|
name: 'list_org_skills',
|
|
888
|
+
permission: 'mcp:skills:read',
|
|
889
|
+
capability: 'agent_builder',
|
|
890
|
+
stdioOnly: true,
|
|
777
891
|
description: 'List all skills in the org skill library (org-level + system-level). ' +
|
|
778
892
|
'Returns ID, name, description, and whether the skill is built-in. ' +
|
|
779
893
|
'Use skill IDs with set_agent_skills to assign skills to an agent.',
|
|
@@ -782,6 +896,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
782
896
|
// ─── Group 6: Secrets ─────────────────────────────────────────────────────
|
|
783
897
|
{
|
|
784
898
|
name: 'list_agent_secrets',
|
|
899
|
+
permission: 'mcp:agents:read',
|
|
900
|
+
capability: 'agent_builder',
|
|
901
|
+
stdioOnly: true,
|
|
785
902
|
description: 'List secret names for an agent, with the note recording where each value came from. ' +
|
|
786
903
|
'Values are never returned.',
|
|
787
904
|
inputSchema: {
|
|
@@ -795,6 +912,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
795
912
|
},
|
|
796
913
|
{
|
|
797
914
|
name: 'set_agent_secret',
|
|
915
|
+
permission: 'mcp:agents:create',
|
|
916
|
+
capability: 'agent_builder',
|
|
917
|
+
stdioOnly: true,
|
|
798
918
|
description: 'Create or update a secret for an agent. ' +
|
|
799
919
|
'Name must be uppercase letters, digits, and underscores (e.g. MY_API_KEY). ' +
|
|
800
920
|
'Omit value to change only shared_with_aliases or comment on an existing secret.',
|
|
@@ -822,6 +942,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
822
942
|
// ─── Group 7: Discovery ───────────────────────────────────────────────────
|
|
823
943
|
{
|
|
824
944
|
name: 'list_container_images',
|
|
945
|
+
permission: 'mcp:agents:read',
|
|
946
|
+
capability: 'agent_builder',
|
|
947
|
+
stdioOnly: true,
|
|
825
948
|
description: 'List agent-capable container images assigned to the organisation. Use image IDs when calling create_agent. ' +
|
|
826
949
|
'With personal=true, lists the images a personal agent may run instead — the bring-your-own-token ' +
|
|
827
950
|
'images opened to personal use, which are not an organisation catalogue.',
|
|
@@ -838,6 +961,9 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
838
961
|
},
|
|
839
962
|
{
|
|
840
963
|
name: 'list_llm_models',
|
|
964
|
+
permission: 'mcp:agents:read',
|
|
965
|
+
capability: 'agent_builder',
|
|
966
|
+
stdioOnly: true,
|
|
841
967
|
description: 'List LLM providers and models available to the organisation. ' +
|
|
842
968
|
'Use provider, model and family IDs when calling create_agent, update_agent, ' +
|
|
843
969
|
'create_agent_task or update_agent_task.',
|
|
@@ -4,8 +4,11 @@
|
|
|
4
4
|
* Registered only when the API key has the `agent_observer` capability, and
|
|
5
5
|
* stdio only — like the agent-self, agent-memory and agent-builder families,
|
|
6
6
|
* these are tools an agent uses about the fleet it runs in, not tools an
|
|
7
|
-
* integration calls over the remote transport, so they
|
|
8
|
-
*
|
|
7
|
+
* integration calls over the remote transport, so they are `stdioOnly` and have
|
|
8
|
+
* no generated remote copy to drift from. They are still catalogue entries
|
|
9
|
+
* (#2493): declaring the scope is what lets the stdio tool list tell the two
|
|
10
|
+
* observation tiers apart, and a tier a key cannot call is a tier it is not told
|
|
11
|
+
* it has.
|
|
9
12
|
*
|
|
10
13
|
* The backend decides what comes back: a session is visible to an observer only
|
|
11
14
|
* where the observer is a member of a project the run was scoped to. Nothing in
|
|
@@ -22,6 +25,9 @@ const PROJECT_BOUND = 'You see a session only if it ran in a project you are a m
|
|
|
22
25
|
export const AGENT_OBSERVE_TOOLS = [
|
|
23
26
|
{
|
|
24
27
|
name: 'list_observed_sessions',
|
|
28
|
+
permission: 'mcp:agents:observe',
|
|
29
|
+
capability: 'agent_observer',
|
|
30
|
+
stdioOnly: true,
|
|
25
31
|
description: "List other agents' sessions across the projects you are a member of, newest first. " +
|
|
26
32
|
'Returns status, trigger, timings, error text, token use and the projects each run was ' +
|
|
27
33
|
'scoped to — no transcripts, no container logs, no system prompts. ' +
|
|
@@ -50,6 +56,9 @@ export const AGENT_OBSERVE_TOOLS = [
|
|
|
50
56
|
},
|
|
51
57
|
{
|
|
52
58
|
name: 'get_observed_session',
|
|
59
|
+
permission: 'mcp:agents:observe',
|
|
60
|
+
capability: 'agent_observer',
|
|
61
|
+
stdioOnly: true,
|
|
53
62
|
description: 'Read one observed session by id. Same fields as the listing. ' +
|
|
54
63
|
'Not found if the session ran in no project of yours.',
|
|
55
64
|
inputSchema: {
|
|
@@ -62,6 +71,9 @@ export const AGENT_OBSERVE_TOOLS = [
|
|
|
62
71
|
},
|
|
63
72
|
{
|
|
64
73
|
name: 'get_observed_session_events',
|
|
74
|
+
permission: 'mcp:agents:observe',
|
|
75
|
+
capability: 'agent_observer',
|
|
76
|
+
stdioOnly: true,
|
|
65
77
|
description: "Read an observed session's activity log: which tools ran, in order, with the one-line " +
|
|
66
78
|
'summary each hook wrote. This is how a run looks from outside — the same tool repeating, ' +
|
|
67
79
|
'or nothing at all for a long time, is what "stuck" looks like. ' +
|
|
@@ -78,6 +90,9 @@ export const AGENT_OBSERVE_TOOLS = [
|
|
|
78
90
|
},
|
|
79
91
|
{
|
|
80
92
|
name: 'get_observed_session_conversation',
|
|
93
|
+
permission: 'mcp:agents:observe_transcripts',
|
|
94
|
+
capability: 'agent_observer',
|
|
95
|
+
stdioOnly: true,
|
|
81
96
|
description: "Read an observed session's conversation. Requires the separate " +
|
|
82
97
|
'mcp:agents:observe_transcripts permission, which most observers do not have and do not ' +
|
|
83
98
|
'need: a transcript carries repository contents, shell output and the agent’s system ' +
|
package/dist/agentSelfTools.js
CHANGED
|
@@ -3,9 +3,22 @@ function text(data) {
|
|
|
3
3
|
return JSON.stringify(data, null, 2);
|
|
4
4
|
}
|
|
5
5
|
// ─── Tool definitions ──────────────────────────────────────────────────────────
|
|
6
|
+
/**
|
|
7
|
+
* An agent reading and recording its own context, stdio only.
|
|
8
|
+
*
|
|
9
|
+
* `permission: null` throughout, and it is a decision rather than a gap (#2493):
|
|
10
|
+
* `McpSelfController` calls no `checkMcpPermission` at all, because the gate is
|
|
11
|
+
* being an agent — every route resolves an `AgentProfile` from the caller and
|
|
12
|
+
* refuses anyone else — and no `MCP_PERMISSIONS` member names that. The
|
|
13
|
+
* capability is what withholds these, and for memory it is withheld from an agent
|
|
14
|
+
* whose memory is off (#1776).
|
|
15
|
+
*/
|
|
6
16
|
export const AGENT_SELF_TOOLS = [
|
|
7
17
|
{
|
|
8
18
|
name: 'get_self',
|
|
19
|
+
permission: null,
|
|
20
|
+
capability: 'agent_self',
|
|
21
|
+
stdioOnly: true,
|
|
9
22
|
description: 'Get your own agent profile with full composed context: system prompt, instructions, skills, LLM model, and assigned projects. ' +
|
|
10
23
|
'Use this to understand how you are configured and what capabilities you have. ' +
|
|
11
24
|
'Essential for self-reflection and improvement proposals.',
|
|
@@ -13,6 +26,9 @@ export const AGENT_SELF_TOOLS = [
|
|
|
13
26
|
},
|
|
14
27
|
{
|
|
15
28
|
name: 'get_own_sessions',
|
|
29
|
+
permission: null,
|
|
30
|
+
capability: 'agent_self',
|
|
31
|
+
stdioOnly: true,
|
|
16
32
|
description: 'List your own recent sessions. Returns session metadata including status, trigger type, and timestamps. ' +
|
|
17
33
|
'Use this to review your recent activity for retrospective analysis.',
|
|
18
34
|
inputSchema: {
|
|
@@ -31,6 +47,9 @@ export const AGENT_SELF_TOOLS = [
|
|
|
31
47
|
export const AGENT_MEMORY_TOOLS = [
|
|
32
48
|
{
|
|
33
49
|
name: 'list_memories',
|
|
50
|
+
permission: null,
|
|
51
|
+
capability: 'agent_memory',
|
|
52
|
+
stdioOnly: true,
|
|
34
53
|
description: 'List the persistent memory entries this session can see: your global ones, plus those scoped to the projects this session runs in. ' +
|
|
35
54
|
'Memory persists across sessions and is scoped to you. ' +
|
|
36
55
|
'Use this to recall observations, patterns, and notes from previous work.',
|
|
@@ -38,6 +57,9 @@ export const AGENT_MEMORY_TOOLS = [
|
|
|
38
57
|
},
|
|
39
58
|
{
|
|
40
59
|
name: 'get_memory',
|
|
60
|
+
permission: null,
|
|
61
|
+
capability: 'agent_memory',
|
|
62
|
+
stdioOnly: true,
|
|
41
63
|
description: 'Get a specific memory entry by key. Returns the content and metadata for a single memory entry.',
|
|
42
64
|
inputSchema: {
|
|
43
65
|
type: 'object',
|
|
@@ -49,6 +71,9 @@ export const AGENT_MEMORY_TOOLS = [
|
|
|
49
71
|
},
|
|
50
72
|
{
|
|
51
73
|
name: 'save_memory',
|
|
74
|
+
permission: null,
|
|
75
|
+
capability: 'agent_memory',
|
|
76
|
+
stdioOnly: true,
|
|
52
77
|
description: 'Save a persistent memory entry by key. Creates the entry if it does not exist, or updates it if it does. ' +
|
|
53
78
|
'Use this to persist observations, learnings, and notes across sessions. ' +
|
|
54
79
|
'To clear a memory entry, save it with empty content. ' +
|
package/dist/browserTools.js
CHANGED
|
@@ -14,6 +14,9 @@ function text(data) {
|
|
|
14
14
|
export const BROWSER_TOOLS = [
|
|
15
15
|
{
|
|
16
16
|
name: 'browser_request_control',
|
|
17
|
+
permission: 'mcp:browser:control',
|
|
18
|
+
capability: 'browser_control',
|
|
19
|
+
stdioOnly: true,
|
|
17
20
|
description: 'Ask the person you are chatting with for permission to drive their browser. ' +
|
|
18
21
|
'They see your reason in the conversation with Allow and Deny buttons, and this call waits for their answer. ' +
|
|
19
22
|
'You MUST call this and be granted control before any other browser tool will work — there is no way around it, ' +
|
|
@@ -32,6 +35,9 @@ export const BROWSER_TOOLS = [
|
|
|
32
35
|
},
|
|
33
36
|
{
|
|
34
37
|
name: 'browser_snapshot',
|
|
38
|
+
permission: 'mcp:browser:control',
|
|
39
|
+
capability: 'browser_control',
|
|
40
|
+
stdioOnly: true,
|
|
35
41
|
description: 'See the page: its URL, title, visible text, and the interactive elements you can act on. ' +
|
|
36
42
|
'Each element comes back with a "ref" — pass that ref to browser_click or browser_type. ' +
|
|
37
43
|
'Refs are only valid until the page changes, so take a fresh snapshot after every action.',
|
|
@@ -39,6 +45,9 @@ export const BROWSER_TOOLS = [
|
|
|
39
45
|
},
|
|
40
46
|
{
|
|
41
47
|
name: 'browser_click',
|
|
48
|
+
permission: 'mcp:browser:control',
|
|
49
|
+
capability: 'browser_control',
|
|
50
|
+
stdioOnly: true,
|
|
42
51
|
description: 'Click an element on the page. The visitor sees the pointer travel to it before it is clicked.',
|
|
43
52
|
inputSchema: {
|
|
44
53
|
type: 'object',
|
|
@@ -50,6 +59,9 @@ export const BROWSER_TOOLS = [
|
|
|
50
59
|
},
|
|
51
60
|
{
|
|
52
61
|
name: 'browser_type',
|
|
62
|
+
permission: 'mcp:browser:control',
|
|
63
|
+
capability: 'browser_control',
|
|
64
|
+
stdioOnly: true,
|
|
53
65
|
description: 'Type text into an input, textarea or select on the page, replacing whatever it holds.',
|
|
54
66
|
inputSchema: {
|
|
55
67
|
type: 'object',
|
|
@@ -62,6 +74,9 @@ export const BROWSER_TOOLS = [
|
|
|
62
74
|
},
|
|
63
75
|
{
|
|
64
76
|
name: 'browser_scroll',
|
|
77
|
+
permission: 'mcp:browser:control',
|
|
78
|
+
capability: 'browser_control',
|
|
79
|
+
stdioOnly: true,
|
|
65
80
|
description: 'Scroll the page vertically to bring more of it into view.',
|
|
66
81
|
inputSchema: {
|
|
67
82
|
type: 'object',
|
|
@@ -76,6 +91,9 @@ export const BROWSER_TOOLS = [
|
|
|
76
91
|
},
|
|
77
92
|
{
|
|
78
93
|
name: 'browser_release_control',
|
|
94
|
+
permission: 'mcp:browser:control',
|
|
95
|
+
capability: 'browser_control',
|
|
96
|
+
stdioOnly: true,
|
|
79
97
|
description: 'Hand the browser back when you are done, so the visitor stops seeing the "agent in control" frame on their screen. ' +
|
|
80
98
|
'Always do this once the task is finished. They can also stop you themselves at any time.',
|
|
81
99
|
inputSchema: { type: 'object', properties: {} },
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
-
import { CallToolRequestSchema, ListToolsRequestSchema
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { createRequire } from 'module';
|
|
7
7
|
import { readFile, stat } from 'fs/promises';
|
|
@@ -10,12 +10,12 @@ import { WolfpackClient } from './client.js';
|
|
|
10
10
|
import { productName } from './brand.js';
|
|
11
11
|
import { allTasksChecked, getWorkItemReminders } from './workItemReminders.js';
|
|
12
12
|
import { validateConfig, config } from './config.js';
|
|
13
|
-
import {
|
|
13
|
+
import { handleAgentBuilderTool } from './agentBuilderTools.js';
|
|
14
14
|
import { handleProcedureTool } from './procedureTools.js';
|
|
15
15
|
import { stdioTools, toolNamesFor, withProductName } from './toolCatalogue.js';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
16
|
+
import { handleAgentSelfTool } from './agentSelfTools.js';
|
|
17
|
+
import { handleAgentObserveTool } from './agentObserveTools.js';
|
|
18
|
+
import { handleBrowserTool } from './browserTools.js';
|
|
19
19
|
import { resolveRadarItemId } from './resolveRadarItemId.js';
|
|
20
20
|
import { SERVER_INSTRUCTIONS } from './serverInstructions.js';
|
|
21
21
|
import { fetch as proxyFetch } from './proxyFetch.js';
|
|
@@ -864,16 +864,11 @@ class WolfpackMCPServer {
|
|
|
864
864
|
await this.fetchCapabilities();
|
|
865
865
|
}
|
|
866
866
|
return {
|
|
867
|
-
//
|
|
868
|
-
//
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
...(this.capabilities.includes('agent_memory') ? AGENT_MEMORY_TOOLS : []),
|
|
873
|
-
...(this.capabilities.includes('agent_observer') ? AGENT_OBSERVE_TOOLS : []),
|
|
874
|
-
...(this.capabilities.includes('agent_builder') ? AGENT_BUILDER_TOOLS : []),
|
|
875
|
-
...(this.capabilities.includes('browser_control') ? BROWSER_TOOLS : []),
|
|
876
|
-
], productName),
|
|
867
|
+
// Every family is a catalogue entry, so one call covers them all — gated on
|
|
868
|
+
// the capability and on the key's own scopes (#2493). The catalogue carries a
|
|
869
|
+
// token where the product's name belongs (#2466); it is filled in here, as the
|
|
870
|
+
// tools are advertised.
|
|
871
|
+
tools: withProductName(stdioTools(this.capabilities, this.permissions), productName),
|
|
877
872
|
};
|
|
878
873
|
});
|
|
879
874
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -2037,14 +2032,14 @@ class WolfpackMCPServer {
|
|
|
2037
2032
|
}
|
|
2038
2033
|
// Check agent self tools
|
|
2039
2034
|
if (this.capabilities.includes('agent_self')) {
|
|
2040
|
-
const selfToolNames =
|
|
2035
|
+
const selfToolNames = toolNamesFor('agent_self');
|
|
2041
2036
|
if (selfToolNames.includes(name)) {
|
|
2042
2037
|
return handleAgentSelfTool(name, args, this.client);
|
|
2043
2038
|
}
|
|
2044
2039
|
}
|
|
2045
2040
|
// Check memory tools (separately gated — #1776)
|
|
2046
2041
|
if (this.capabilities.includes('agent_memory')) {
|
|
2047
|
-
const memoryToolNames =
|
|
2042
|
+
const memoryToolNames = toolNamesFor('agent_memory');
|
|
2048
2043
|
if (memoryToolNames.includes(name)) {
|
|
2049
2044
|
return handleAgentSelfTool(name, args, this.client);
|
|
2050
2045
|
}
|
|
@@ -2052,14 +2047,14 @@ class WolfpackMCPServer {
|
|
|
2052
2047
|
// Check observation tools (#2388 — other agents' runs, in the
|
|
2053
2048
|
// observer's own projects)
|
|
2054
2049
|
if (this.capabilities.includes('agent_observer')) {
|
|
2055
|
-
const observeToolNames =
|
|
2050
|
+
const observeToolNames = toolNamesFor('agent_observer');
|
|
2056
2051
|
if (observeToolNames.includes(name)) {
|
|
2057
2052
|
return handleAgentObserveTool(name, args, this.client);
|
|
2058
2053
|
}
|
|
2059
2054
|
}
|
|
2060
2055
|
// Check browser control tools (#2288 — chat sessions only)
|
|
2061
2056
|
if (this.capabilities.includes('browser_control')) {
|
|
2062
|
-
const browserToolNames =
|
|
2057
|
+
const browserToolNames = toolNamesFor('browser_control');
|
|
2063
2058
|
if (browserToolNames.includes(name)) {
|
|
2064
2059
|
return handleBrowserTool(name, args, this.client);
|
|
2065
2060
|
}
|
|
@@ -67,6 +67,30 @@ describe('the tool list served over stdio', () => {
|
|
|
67
67
|
expect(names).not.toContain('update_work_item');
|
|
68
68
|
expect(names).not.toContain('create_work_item_comment');
|
|
69
69
|
}, 60_000);
|
|
70
|
+
it('offers an observer key the tiers of observation its scopes reach', async () => {
|
|
71
|
+
// #2493 — the observe family was appended after `stdioTools` and filtered on
|
|
72
|
+
// the capability alone, so a coach holding `observe` was advertised the
|
|
73
|
+
// transcript tool and refused on every call. The two scopes are one
|
|
74
|
+
// capability by design (#2388), which is exactly why the tool list has to
|
|
75
|
+
// tell them apart.
|
|
76
|
+
const names = await toolNamesFrom(await stubBackend({
|
|
77
|
+
capabilities: ['agent_observer'],
|
|
78
|
+
permissions: ['mcp:agents:observe'],
|
|
79
|
+
}));
|
|
80
|
+
expect(names).toContain('list_observed_sessions');
|
|
81
|
+
expect(names).toContain('get_observed_session_events');
|
|
82
|
+
expect(names).not.toContain('get_observed_session_conversation');
|
|
83
|
+
}, 60_000);
|
|
84
|
+
it('offers a read-only builder key its reads and none of its writes', async () => {
|
|
85
|
+
// #2493, the same gap on the largest family: `agent_builder` is granted on
|
|
86
|
+
// `mcp:agents:read` alone, so every write in it was advertised to a key that
|
|
87
|
+
// cannot call one.
|
|
88
|
+
const names = await toolNamesFrom(await stubBackend({ capabilities: ['agent_builder'], permissions: ['mcp:agents:read'] }));
|
|
89
|
+
expect(names).toContain('list_agents');
|
|
90
|
+
expect(names).toContain('list_organisations');
|
|
91
|
+
expect(names).not.toContain('create_agent');
|
|
92
|
+
expect(names).not.toContain('update_agent');
|
|
93
|
+
}, 60_000);
|
|
70
94
|
it('is not stripped when the capabilities round trip fails', async () => {
|
|
71
95
|
// #1488 the other way up: a gateway outage means the key's scopes are
|
|
72
96
|
// unknown, not that the key holds none. Stripping the list on a 503 would
|
package/dist/toolCatalogue.js
CHANGED
|
@@ -16,11 +16,17 @@
|
|
|
16
16
|
* `rootDir: ./src`. A generated, never-hand-edited copy is what one source of
|
|
17
17
|
* truth looks like across that boundary.
|
|
18
18
|
*
|
|
19
|
-
* The agent-
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* no
|
|
19
|
+
* The agent-self, agent-memory, agent-observer, agent-builder and browser-control
|
|
20
|
+
* families stay in the files that hold their handlers, and are appended to the
|
|
21
|
+
* catalogue from there. They exist on the stdio transport only, which `stdioOnly`
|
|
22
|
+
* records — but being stdio-only is no reason to declare no scope, and while they
|
|
23
|
+
* sat outside the catalogue they declared none, so neither the scope filter
|
|
24
|
+
* (#2428) nor the scope-is-required rule (#2345) reached them (#2493).
|
|
23
25
|
*/
|
|
26
|
+
import { AGENT_SELF_TOOLS, AGENT_MEMORY_TOOLS } from './agentSelfTools.js';
|
|
27
|
+
import { AGENT_OBSERVE_TOOLS } from './agentObserveTools.js';
|
|
28
|
+
import { AGENT_BUILDER_TOOLS } from './agentBuilderTools.js';
|
|
29
|
+
import { BROWSER_TOOLS } from './browserTools.js';
|
|
24
30
|
/**
|
|
25
31
|
* The token a description carries where the product's name belongs (#2466).
|
|
26
32
|
*
|
|
@@ -74,7 +80,8 @@ const DEFINITION_DESCRIPTION = 'Workflow definition: { nodes: [{id, type, positi
|
|
|
74
80
|
'setVariable, createPage, createJournalEntry, llmPrompt, jsonata, agentTask, runContainer), data.label, and ' +
|
|
75
81
|
"data.config holding that type's settings — the step body belongs in data.config.description, not data.description. " +
|
|
76
82
|
'Use activityType "task" for a step a person just reads and ticks off.';
|
|
77
|
-
|
|
83
|
+
/** The tools both transports serve. */
|
|
84
|
+
const SHARED_TOOLS = [
|
|
78
85
|
// Project tools
|
|
79
86
|
{
|
|
80
87
|
name: 'list_projects',
|
|
@@ -2270,6 +2277,14 @@ export const TOOL_CATALOGUE = [
|
|
|
2270
2277
|
},
|
|
2271
2278
|
},
|
|
2272
2279
|
];
|
|
2280
|
+
export const TOOL_CATALOGUE = [
|
|
2281
|
+
...SHARED_TOOLS,
|
|
2282
|
+
...AGENT_SELF_TOOLS,
|
|
2283
|
+
...AGENT_MEMORY_TOOLS,
|
|
2284
|
+
...AGENT_OBSERVE_TOOLS,
|
|
2285
|
+
...AGENT_BUILDER_TOOLS,
|
|
2286
|
+
...BROWSER_TOOLS,
|
|
2287
|
+
];
|
|
2273
2288
|
const toTool = ({ name, description, inputSchema }) => ({
|
|
2274
2289
|
name,
|
|
2275
2290
|
description,
|
|
@@ -2299,13 +2314,20 @@ export function toolNamesFor(capability) {
|
|
|
2299
2314
|
return TOOL_CATALOGUE.filter((t) => t.capability === capability).map((t) => t.name);
|
|
2300
2315
|
}
|
|
2301
2316
|
/**
|
|
2302
|
-
* The catalogue as the remote transport advertises it: every tool
|
|
2303
|
-
* `remote` override where one is set. This is what
|
|
2317
|
+
* The catalogue as the remote transport advertises it: every tool but the
|
|
2318
|
+
* stdio-only ones, taking the `remote` override where one is set. This is what
|
|
2319
|
+
* the generated backend copy holds.
|
|
2304
2320
|
*/
|
|
2305
2321
|
export function remoteTools() {
|
|
2306
|
-
return TOOL_CATALOGUE.map((t) => ({ ...toTool(t), ...t.remote }));
|
|
2322
|
+
return TOOL_CATALOGUE.filter((t) => !t.stdioOnly).map((t) => ({ ...toTool(t), ...t.remote }));
|
|
2307
2323
|
}
|
|
2308
|
-
/**
|
|
2309
|
-
|
|
2324
|
+
/**
|
|
2325
|
+
* The scope each tool requires, derived from the catalogue. The stdio-only tools
|
|
2326
|
+
* are in here too, though the remote server can never dispatch one: the scope is a
|
|
2327
|
+
* fact about the tool rather than about a transport, and this map is what the
|
|
2328
|
+
* quarterly prompt-injection probe reads to tell an induced tool that is inside
|
|
2329
|
+
* the agent's grant from one that is outside it (#2493).
|
|
2330
|
+
*/
|
|
2331
|
+
export function toolPermissions() {
|
|
2310
2332
|
return Object.fromEntries(TOOL_CATALOGUE.map((t) => [t.name, t.permission]));
|
|
2311
2333
|
}
|
|
@@ -2,8 +2,11 @@ import { describe, it, expect } from 'vitest';
|
|
|
2
2
|
import { readFileSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import * as catalogue from './toolCatalogue.js';
|
|
5
|
-
import { TOOL_CATALOGUE, remoteTools, stdioTools } from './toolCatalogue.js';
|
|
5
|
+
import { TOOL_CATALOGUE, remoteTools, toolPermissions, stdioTools, toolNamesFor, } from './toolCatalogue.js';
|
|
6
6
|
import { BROWSER_TOOLS } from './browserTools.js';
|
|
7
|
+
import { AGENT_SELF_TOOLS, AGENT_MEMORY_TOOLS } from './agentSelfTools.js';
|
|
8
|
+
import { AGENT_OBSERVE_TOOLS } from './agentObserveTools.js';
|
|
9
|
+
import { AGENT_BUILDER_TOOLS } from './agentBuilderTools.js';
|
|
7
10
|
// @ts-expect-error — a plain .mjs build script, deliberately not part of this program
|
|
8
11
|
import { render, GENERATED_PATH } from '../../../scripts/generate-mcp-catalogue.mjs';
|
|
9
12
|
const repoFile = (path) => readFileSync(join(__dirname, '../../..', path), 'utf8');
|
|
@@ -80,11 +83,42 @@ describe('the MCP tool catalogue', () => {
|
|
|
80
83
|
expect(readOnly).not.toContain('create_issue');
|
|
81
84
|
});
|
|
82
85
|
it('keeps a tool that deliberately requires no scope of its own', () => {
|
|
83
|
-
// `permission: null` is a decision, not a gap, so an empty key still gets these
|
|
84
|
-
|
|
86
|
+
// `permission: null` is a decision, not a gap, so an empty key still gets these
|
|
87
|
+
// — the ones no capability gates, which is every family a plain key can reach.
|
|
88
|
+
const unscoped = TOOL_CATALOGUE.filter((t) => t.permission === null && !t.capability).map((t) => t.name);
|
|
85
89
|
expect(unscoped.length).toBeGreaterThan(0);
|
|
86
90
|
expect(stdioTools([], []).map((t) => t.name)).toEqual(unscoped);
|
|
87
91
|
});
|
|
92
|
+
it('serves a stdio-only tool over stdio alone, and still declares its scope', () => {
|
|
93
|
+
// #2493 — the five families used to be appended after `stdioTools`, so they
|
|
94
|
+
// reached neither the scope filter nor the scope-is-required rule. They are
|
|
95
|
+
// catalogue entries now, which must not put them in front of a remote client:
|
|
96
|
+
// the remote server has no dispatch case for one, and advertising it would be
|
|
97
|
+
// a promise the product cannot keep.
|
|
98
|
+
const stdioOnly = TOOL_CATALOGUE.filter((t) => t.stdioOnly);
|
|
99
|
+
expect(stdioOnly.map((t) => t.name)).toContain('get_observed_session_conversation');
|
|
100
|
+
expect(remoteTools().map((t) => t.name)).not.toContain('get_observed_session_conversation');
|
|
101
|
+
// The scope still reaches the generated map, which is what the quarterly
|
|
102
|
+
// prompt-injection probe reads to judge whether an induced tool is in scope.
|
|
103
|
+
const permissions = toolPermissions();
|
|
104
|
+
for (const tool of stdioOnly) {
|
|
105
|
+
expect(permissions[tool.name], tool.name).toBe(tool.permission);
|
|
106
|
+
expect(tool.capability, tool.name).toBeDefined();
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
it.each([
|
|
110
|
+
['agent_self', AGENT_SELF_TOOLS],
|
|
111
|
+
['agent_memory', AGENT_MEMORY_TOOLS],
|
|
112
|
+
['agent_observer', AGENT_OBSERVE_TOOLS],
|
|
113
|
+
['agent_builder', AGENT_BUILDER_TOOLS],
|
|
114
|
+
['browser_control', BROWSER_TOOLS],
|
|
115
|
+
])('names the whole %s family for the stdio dispatch to route on', (capability, family) => {
|
|
116
|
+
// The stdio server routes a call to a family's handler by asking the catalogue
|
|
117
|
+
// for that capability's names, rather than by importing the array (#2493). A
|
|
118
|
+
// tool whose `capability` does not match the file it lives in would then be
|
|
119
|
+
// advertised and dispatched to the wrong handler — or to none.
|
|
120
|
+
expect(toolNamesFor(capability)).toEqual(family.map((t) => t.name));
|
|
121
|
+
});
|
|
88
122
|
it('honours a wildcard the way the server does when it checks the call', () => {
|
|
89
123
|
// Filtering has to read a scope exactly as `checkMcpPermission` does, or a
|
|
90
124
|
// key on `mcp:*` loses tools it is entitled to call.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wolfpack-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.106",
|
|
4
4
|
"description": "MCP server for Wolfpack AI-enhanced software delivery tools",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"clean": "",
|
|
15
15
|
"build": "tsc",
|
|
16
16
|
"dev": "tsx watch src/index.ts",
|
|
17
|
+
"generate:catalogue": "tsx ../../scripts/generate-mcp-catalogue.mjs",
|
|
17
18
|
"start": "node dist/index.js",
|
|
18
19
|
"test": "vitest",
|
|
19
20
|
"lint": "eslint . --max-warnings 0",
|