wolfpack-mcp 1.0.94 → 1.0.95

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/README.md CHANGED
@@ -357,11 +357,11 @@ Get a specific radar item.
357
357
 
358
358
  ### Tags
359
359
 
360
- Tags label work items, issues, wiki pages and journal entries, and are filed under nestable groups. Project admins define them; a key with `mcp:tags:manage` lets an admin's agent keep them in step with an external system (for example, one tag per CRM customer account keyed on `external_id`). There is no delete tool — removing a tag is done in the browser.
360
+ Tags label work items, issues, wiki pages and journal entries, and are filed under nestable groups. Project admins define them; a key with `mcp:tags:manage` lets an admin's agent keep them in step with an external system (for example, one tag per CRM customer account keyed on `external_id`). There is no delete tool — archive instead (`update_tag` with `archived: true`); removing a tag outright is done in the browser.
361
361
 
362
362
  #### `list_tags`
363
363
 
364
- List a project's tags with id, name, colour, `externalId` and the group each is filed under. Any key that can read a tagged module may list them.
364
+ List a project's tags with id, name, colour, `externalId`, the group each is filed under and whether it is `archived`. Any key that can read a tagged module may list them.
365
365
 
366
366
  - `project_slug` (optional): Project slug
367
367
 
@@ -375,10 +375,10 @@ List a project's tags with id, name, colour, `externalId` and the group each is
375
375
 
376
376
  #### `update_tag`
377
377
 
378
- Only the fields given change; pass `null` for `colour`, `group` or `external_id` to clear it.
378
+ Only the fields given change; pass `null` for `colour`, `group` or `external_id` to clear it. `archived: true` is the soft delete: the tag stays on the items that carry it and keeps its page, but no picker offers it and it cannot be applied; `archived: false` restores it. A sync uses this for accounts the source system has dropped.
379
379
 
380
380
  - `tag` (required): Tag id or current name
381
- - `name`, `colour`, `group`, `external_id` (optional)
381
+ - `name`, `colour`, `group`, `external_id`, `archived` (optional)
382
382
  - `project_slug` (optional): Project slug
383
383
 
384
384
  #### `list_tag_groups`
@@ -40,12 +40,25 @@ export const AGENT_BUILDER_TOOLS = [
40
40
  // ─── Group 1: Agent CRUD ─────────────────────────────────────────────────
41
41
  {
42
42
  name: 'list_agents',
43
- description: 'List all agents in the organisation. Returns name, status, template, and assigned projects.',
44
- inputSchema: { type: 'object', properties: { ...ORG_SLUG_PROP } },
43
+ description: 'List agents. Returns name, status, template, and assigned projects. ' +
44
+ 'scope "org" (the default) lists the organisation\'s agents; scope "personal" lists your own ' +
45
+ 'My Agents from My Space, which belong to no organisation. Call it twice to see both.',
46
+ inputSchema: {
47
+ type: 'object',
48
+ properties: {
49
+ scope: {
50
+ type: 'string',
51
+ enum: ['org', 'personal'],
52
+ description: "Which agents to list: the organisation's (default) or your own personal ones.",
53
+ },
54
+ ...ORG_SLUG_PROP,
55
+ },
56
+ },
45
57
  },
46
58
  {
47
59
  name: 'get_agent',
48
- description: 'Get full details for an agent including config, instructions, LLM model, and linked skills.',
60
+ description: 'Get full details for an agent including config, instructions, LLM model, and linked skills. ' +
61
+ 'Takes an organisation agent or one of your own personal agents.',
49
62
  inputSchema: {
50
63
  type: 'object',
51
64
  properties: {
@@ -57,7 +70,10 @@ export const AGENT_BUILDER_TOOLS = [
57
70
  },
58
71
  {
59
72
  name: 'create_agent',
60
- description: 'Create a new agent from a container image. Use list_container_images to see available images.',
73
+ description: 'Create a new agent from a container image. Use list_container_images to see available images. ' +
74
+ 'With personal=true, creates one of your own My Agents instead of an organisation agent: ' +
75
+ 'it needs a handle, and only an image opened to personal use (list_container_images with ' +
76
+ 'personal=true) qualifies.',
61
77
  inputSchema: {
62
78
  type: 'object',
63
79
  properties: {
@@ -67,6 +83,14 @@ export const AGENT_BUILDER_TOOLS = [
67
83
  },
68
84
  name: { type: 'string', description: 'Display name for the agent' },
69
85
  config: { type: 'object', description: 'Optional agent-specific configuration' },
86
+ personal: {
87
+ type: 'boolean',
88
+ description: 'Create one of your own personal agents (My Space > My Agents) rather than an organisation agent.',
89
+ },
90
+ handle: {
91
+ type: 'string',
92
+ description: 'Handle for a personal agent — it becomes "@<your username>/<handle>". Required with personal=true, ignored otherwise.',
93
+ },
70
94
  ...ORG_SLUG_PROP,
71
95
  },
72
96
  required: ['container_image_id', 'name'],
@@ -798,8 +822,19 @@ export const AGENT_BUILDER_TOOLS = [
798
822
  // ─── Group 7: Discovery ───────────────────────────────────────────────────
799
823
  {
800
824
  name: 'list_container_images',
801
- description: 'List agent-capable container images assigned to the organisation. Use image IDs when calling create_agent.',
802
- inputSchema: { type: 'object', properties: { ...ORG_SLUG_PROP } },
825
+ description: 'List agent-capable container images assigned to the organisation. Use image IDs when calling create_agent. ' +
826
+ 'With personal=true, lists the images a personal agent may run instead — the bring-your-own-token ' +
827
+ 'images opened to personal use, which are not an organisation catalogue.',
828
+ inputSchema: {
829
+ type: 'object',
830
+ properties: {
831
+ personal: {
832
+ type: 'boolean',
833
+ description: 'List the images available to personal agents rather than the organisation catalogue.',
834
+ },
835
+ ...ORG_SLUG_PROP,
836
+ },
837
+ },
803
838
  },
804
839
  {
805
840
  name: 'list_llm_models',
@@ -837,8 +872,10 @@ export async function handleAgentBuilderTool(name, args, client) {
837
872
  }
838
873
  // ─── Agent CRUD ────────────────────────────────────────────────────────
839
874
  case 'list_agents': {
840
- const parsed = z.object({ org_slug: orgSlugField }).parse(args);
841
- const agents = await client.listAgents(resolveOrg(parsed));
875
+ const parsed = z
876
+ .object({ scope: z.enum(['org', 'personal']).optional(), org_slug: orgSlugField })
877
+ .parse(args);
878
+ const agents = await client.listAgents(resolveOrg(parsed), parsed.scope);
842
879
  return { content: [{ type: 'text', text: text(agents) }] };
843
880
  }
844
881
  case 'get_agent': {
@@ -854,13 +891,21 @@ export async function handleAgentBuilderTool(name, args, client) {
854
891
  container_image_id: z.string(),
855
892
  name: z.string(),
856
893
  config: z.record(z.unknown()).optional(),
894
+ personal: z.boolean().optional(),
895
+ handle: z.string().optional(),
857
896
  org_slug: orgSlugField,
897
+ })
898
+ .refine((v) => !v.personal || !!v.handle, {
899
+ message: 'handle is required with personal=true',
900
+ path: ['handle'],
858
901
  })
859
902
  .parse(args);
860
903
  const agent = await client.createAgent({
861
904
  containerImageId: parsed.container_image_id,
862
905
  name: parsed.name,
863
906
  config: parsed.config,
907
+ personal: parsed.personal,
908
+ handleName: parsed.handle,
864
909
  }, resolveOrg(parsed));
865
910
  return {
866
911
  content: [
@@ -1427,8 +1472,10 @@ export async function handleAgentBuilderTool(name, args, client) {
1427
1472
  }
1428
1473
  // ─── Discovery ────────────────────────────────────────────────────────────
1429
1474
  case 'list_container_images': {
1430
- const parsed = z.object({ org_slug: orgSlugField }).parse(args);
1431
- const images = await client.listContainerImages(resolveOrg(parsed));
1475
+ const parsed = z
1476
+ .object({ personal: z.boolean().optional(), org_slug: orgSlugField })
1477
+ .parse(args);
1478
+ const images = await client.listContainerImages(resolveOrg(parsed), parsed.personal);
1432
1479
  return { content: [{ type: 'text', text: text(images) }] };
1433
1480
  }
1434
1481
  case 'list_llm_models': {
@@ -144,3 +144,51 @@ describe('schedule pre-flight over MCP (#2083)', () => {
144
144
  expect(Object.keys(properties)).not.toContain('scheduling_enabled');
145
145
  });
146
146
  });
147
+ // #2273 — My Agents reached over MCP. There is no second tool set: a personal
148
+ // agent is addressed by the same tools and the same agent_id as an org one, so
149
+ // what these cover is the three tools that had to learn the difference.
150
+ describe('personal agents over MCP (#2273)', () => {
151
+ function fake(method, result) {
152
+ const fn = vi.fn().mockResolvedValue(result);
153
+ return { client: { [method]: fn }, fn };
154
+ }
155
+ it('list_agents offers both scopes and defaults to neither being sent', async () => {
156
+ const scope = toolNamed('list_agents').inputSchema.properties.scope;
157
+ expect(scope.enum).toEqual(['org', 'personal']);
158
+ const { client, fn } = fake('listAgents', []);
159
+ await handleAgentBuilderTool('list_agents', {}, client);
160
+ expect(fn.mock.calls[0][1]).toBeUndefined();
161
+ });
162
+ it('list_agents passes the personal scope through', async () => {
163
+ const { client, fn } = fake('listAgents', [{ id: 'agent-1' }]);
164
+ await handleAgentBuilderTool('list_agents', { scope: 'personal' }, client);
165
+ expect(fn.mock.calls[0][1]).toBe('personal');
166
+ });
167
+ it('create_agent sends the personal flag and the handle', async () => {
168
+ const { client, fn } = fake('createAgent', { userId: 'agent_1' });
169
+ await handleAgentBuilderTool('create_agent', {
170
+ container_image_id: 'image-1',
171
+ name: 'Raiven',
172
+ personal: true,
173
+ handle: 'raiven',
174
+ }, client);
175
+ expect(fn.mock.calls[0][0]).toMatchObject({ personal: true, handleName: 'raiven' });
176
+ });
177
+ // A personal agent's handle is required, so say so here rather than spending a
178
+ // round trip to learn it from the backend.
179
+ it('create_agent refuses personal without a handle', async () => {
180
+ const { client, fn } = fake('createAgent', { userId: 'agent_1' });
181
+ await expect(handleAgentBuilderTool('create_agent', { container_image_id: 'image-1', name: 'Raiven', personal: true }, client)).rejects.toThrow('handle is required with personal=true');
182
+ expect(fn).not.toHaveBeenCalled();
183
+ });
184
+ it('create_agent stays organisational when the flag is left off', async () => {
185
+ const { client, fn } = fake('createAgent', { userId: 'agent_1' });
186
+ await handleAgentBuilderTool('create_agent', { container_image_id: 'image-1', name: 'Scout' }, client);
187
+ expect(fn.mock.calls[0][0].personal).toBeUndefined();
188
+ });
189
+ it('list_container_images asks for the personal catalogue', async () => {
190
+ const { client, fn } = fake('listContainerImages', []);
191
+ await handleAgentBuilderTool('list_container_images', { personal: true }, client);
192
+ expect(fn.mock.calls[0][1]).toBe(true);
193
+ });
194
+ });
package/dist/client.js CHANGED
@@ -673,8 +673,9 @@ export class WolfpackClient {
673
673
  return response.organisations;
674
674
  }
675
675
  // ─── Agent Builder: Agent CRUD ─────────────────────────────────────────────
676
- async listAgents(orgSlug) {
677
- return this.api.get(this.withOrgSlug('/agents', orgSlug));
676
+ async listAgents(orgSlug, scope) {
677
+ const path = scope === 'personal' ? '/agents?scope=personal' : '/agents';
678
+ return this.api.get(this.withOrgSlug(path, orgSlug));
678
679
  }
679
680
  async getAgent(agentId, orgSlug) {
680
681
  try {
@@ -866,8 +867,11 @@ export class WolfpackClient {
866
867
  }
867
868
  }
868
869
  // ─── Agent Builder: Discovery ──────────────────────────────────────────────
869
- async listContainerImages(orgSlug) {
870
- return this.api.get(this.withOrgSlug('/agent-builder/container-images', orgSlug));
870
+ async listContainerImages(orgSlug, personal) {
871
+ const path = personal
872
+ ? '/agent-builder/container-images?personal=true'
873
+ : '/agent-builder/container-images';
874
+ return this.api.get(this.withOrgSlug(path, orgSlug));
871
875
  }
872
876
  async listLlmModels(orgSlug) {
873
877
  return this.api.get(this.withOrgSlug('/agent-builder/llm-models', orgSlug));
package/dist/index.js CHANGED
@@ -627,6 +627,10 @@ const UpdateTagSchema = z.object({
627
627
  .optional()
628
628
  .describe('Tag group name or id, or null to move the tag out of its group'),
629
629
  external_id: z.string().nullable().optional().describe('External id, or null to clear'),
630
+ archived: z
631
+ .boolean()
632
+ .optional()
633
+ .describe('true archives the tag (soft delete), false restores it'),
630
634
  });
631
635
  const CreateTagGroupSchema = z.object({
632
636
  project_slug: z
@@ -863,6 +867,9 @@ class WolfpackMCPServer {
863
867
  'assigned_to_id you pass; the response says so when this applies. ' +
864
868
  'AGENTS: when more than one item is claimable, the response also states the order to take ' +
865
869
  'them in (bug fixes first, then higher priority, then oldest) and which to pull next. ' +
870
+ 'AGENTS: blockedBy on each item names the prerequisites of it that have not finished. An item ' +
871
+ 'with a non-empty blockedBy is NOT claimable however approved it is — take the work it names ' +
872
+ 'first; the response says so too. ' +
866
873
  'TERMINOLOGY: "board" and "kanban" are synonymous - both refer to the Kanban board of work items. ' +
867
874
  'The board has columns: "new" (to do), "doing" (in progress), "review" (pending review), "ready" (code done, awaiting deployment), "blocked", "completed" (deployed). ' +
868
875
  'The "backlog" or "pending" status represents items not yet on the board. ' +
@@ -1986,8 +1993,8 @@ class WolfpackMCPServer {
1986
1993
  // Tag tools
1987
1994
  {
1988
1995
  name: 'list_tags',
1989
- description: 'List all tags available in a project: id, name, colour, external id and the group each is filed under. ' +
1990
- 'Items are tagged by name via update_work_item / update_issue. ' +
1996
+ description: 'List all tags of a project: id, name, colour, external id, the group each is filed under, and whether it is archived. ' +
1997
+ 'Items are tagged by name via update_work_item / update_issue; archived tags cannot be applied. ' +
1991
1998
  'A sync from an external system (e.g. a CRM) keys on externalId.',
1992
1999
  inputSchema: {
1993
2000
  type: 'object',
@@ -2026,8 +2033,9 @@ class WolfpackMCPServer {
2026
2033
  },
2027
2034
  {
2028
2035
  name: 'update_tag',
2029
- description: 'Rename, recolour, regroup or re-key a tag. Project admins only (mcp:tags:manage). ' +
2030
- 'Only the fields given change; pass null for colour, group or external_id to clear it.',
2036
+ description: 'Rename, recolour, regroup, re-key or archive a tag. Project admins only (mcp:tags:manage). ' +
2037
+ 'Only the fields given change; pass null for colour, group or external_id to clear it. ' +
2038
+ 'archived: true is the soft delete — the tag stays on the items that carry it but no picker offers it; archived: false restores it.',
2031
2039
  inputSchema: {
2032
2040
  type: 'object',
2033
2041
  properties: {
@@ -2046,6 +2054,10 @@ class WolfpackMCPServer {
2046
2054
  type: ['string', 'null'],
2047
2055
  description: 'External id, or null to clear',
2048
2056
  },
2057
+ archived: {
2058
+ type: 'boolean',
2059
+ description: 'true archives the tag (soft delete), false restores it',
2060
+ },
2049
2061
  },
2050
2062
  required: ['tag'],
2051
2063
  },
@@ -3256,6 +3268,7 @@ class WolfpackMCPServer {
3256
3268
  colour: parsed.colour,
3257
3269
  group: parsed.group,
3258
3270
  externalId: parsed.external_id,
3271
+ archived: parsed.archived,
3259
3272
  }, parsed.project_slug || this.client.getProjectSlug() || undefined);
3260
3273
  return {
3261
3274
  content: [{ type: 'text', text: JSON.stringify(tag, null, 2) }],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolfpack-mcp",
3
- "version": "1.0.94",
3
+ "version": "1.0.95",
4
4
  "description": "MCP server for Wolfpack AI-enhanced software delivery tools",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",