pm-claude-skills 29.3.0 → 29.3.1

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.
Files changed (3) hide show
  1. package/icon.svg +11 -0
  2. package/mcp/server.mjs +133 -23
  3. package/package.json +5 -3
package/icon.svg ADDED
@@ -0,0 +1,11 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256" role="img" aria-label="PM Skills">
2
+ <defs>
3
+ <linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
4
+ <stop offset="0" stop-color="#e0855f"/>
5
+ <stop offset="1" stop-color="#d9605a"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <rect width="256" height="256" rx="56" fill="#0d0f14"/>
9
+ <rect x="18" y="18" width="220" height="220" rx="44" fill="url(#g)"/>
10
+ <text x="128" y="170" font-family="-apple-system, Segoe UI, Inter, Roboto, sans-serif" font-size="120" font-weight="800" text-anchor="middle" fill="#1a1207">PM</text>
11
+ </svg>
package/mcp/server.mjs CHANGED
@@ -81,46 +81,146 @@ const WORKFLOWS = loadWorkflows();
81
81
  const wfById = new Map(WORKFLOWS.map((w) => [w.id, w]));
82
82
 
83
83
  // ── Tools ───────────────────────────────────────────────────────────────────
84
+ // Reusable output-schema fragments (so tools declare structured output, not just text).
85
+ const SKILL_ITEM = {
86
+ type: 'object',
87
+ properties: {
88
+ name: { type: 'string', description: 'The skill id (use with get_skill).' },
89
+ title: { type: 'string', description: 'Human-readable title.' },
90
+ tier: { type: 'string', description: 'Maturity tier: production | stable | experimental.' },
91
+ description: { type: 'string', description: 'One-line summary of what the skill does.' },
92
+ },
93
+ required: ['name', 'title', 'description'],
94
+ };
95
+ // All tools are read-only lookups over the bundled library — non-destructive, idempotent, closed-world.
96
+ const READONLY = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
97
+
84
98
  const TOOLS = [
85
99
  {
86
100
  name: 'list_skills',
87
- description: 'List available professional skills (name, title, tier, one-line description). Optionally filter by tier.',
101
+ title: 'List skills',
102
+ description: 'List available professional skills (name, title, tier, one-line description). Optionally filter by maturity tier.',
88
103
  inputSchema: {
89
104
  type: 'object',
90
- properties: { tier: { type: 'string', enum: ['production', 'stable', 'experimental'], description: 'Only skills in this maturity tier.' } },
105
+ additionalProperties: false,
106
+ properties: { tier: { type: 'string', enum: ['production', 'stable', 'experimental'], description: 'Optional. Only return skills in this maturity tier.' } },
91
107
  },
108
+ outputSchema: {
109
+ type: 'object',
110
+ properties: { count: { type: 'integer', description: 'Number of skills returned.' }, skills: { type: 'array', description: 'The matching skills.', items: SKILL_ITEM } },
111
+ required: ['count', 'skills'],
112
+ },
113
+ annotations: { title: 'List skills', ...READONLY },
92
114
  },
93
115
  {
94
116
  name: 'search_skills',
95
- description: 'Search skills by keyword across name, description, and body. Returns the best matches.',
117
+ title: 'Search skills',
118
+ description: 'Search skills by keyword across name, description, and body. Returns the best-matching skills, ranked.',
96
119
  inputSchema: {
97
120
  type: 'object',
98
- properties: { query: { type: 'string', description: 'Keywords describing the task, e.g. "prioritise backlog" or "customer churn".' }, limit: { type: 'number', description: 'Max results (default 10).' } },
121
+ additionalProperties: false,
122
+ properties: {
123
+ query: { type: 'string', description: 'Keywords describing the task, e.g. "prioritise backlog" or "customer churn".' },
124
+ limit: { type: 'integer', minimum: 1, maximum: 50, description: 'Maximum number of results to return (default 10).' },
125
+ },
99
126
  required: ['query'],
100
127
  },
128
+ outputSchema: {
129
+ type: 'object',
130
+ properties: { query: { type: 'string', description: 'The query that was searched.' }, matches: { type: 'array', description: 'Matching skills, best first.', items: SKILL_ITEM } },
131
+ required: ['query', 'matches'],
132
+ },
133
+ annotations: { title: 'Search skills', ...READONLY },
101
134
  },
102
135
  {
103
136
  name: 'get_skill',
137
+ title: 'Get a skill',
104
138
  description: 'Get the full instructions (the SKILL.md body) for one skill by name. Apply these instructions to the user\'s task.',
105
- inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'The exact skill name, e.g. "rice-prioritisation".' } }, required: ['name'] },
139
+ inputSchema: {
140
+ type: 'object',
141
+ additionalProperties: false,
142
+ properties: { name: { type: 'string', description: 'The exact skill id, e.g. "rice-prioritisation" (from list_skills / search_skills).' } },
143
+ required: ['name'],
144
+ },
145
+ outputSchema: {
146
+ type: 'object',
147
+ properties: { name: { type: 'string', description: 'The skill id.' }, title: { type: 'string', description: 'Human-readable title.' }, instructions: { type: 'string', description: 'The full SKILL.md body to apply to the task.' } },
148
+ required: ['name', 'instructions'],
149
+ },
150
+ annotations: { title: 'Get a skill', ...READONLY },
106
151
  },
107
152
  {
108
153
  name: 'list_workflows',
154
+ title: 'List workflow recipes',
109
155
  description: 'List workflow recipes — named chains that run several skills in sequence, passing each output forward (e.g. ship-a-feature, close-the-quarter). Use when a task spans multiple steps end to end.',
110
- inputSchema: { type: 'object', properties: {} },
156
+ inputSchema: { type: 'object', additionalProperties: false, properties: {} },
157
+ outputSchema: {
158
+ type: 'object',
159
+ properties: {
160
+ workflows: {
161
+ type: 'array', description: 'Available workflow recipes.',
162
+ items: {
163
+ type: 'object',
164
+ properties: {
165
+ id: { type: 'string', description: 'Recipe id (use with get_workflow).' },
166
+ name: { type: 'string', description: 'Recipe name.' },
167
+ lifecycle: { type: 'string', description: 'The lifecycle stages it spans.' },
168
+ summary: { type: 'string', description: 'What the recipe accomplishes.' },
169
+ skills: { type: 'array', items: { type: 'string' }, description: 'The ordered skill ids in the chain.' },
170
+ },
171
+ required: ['id', 'name', 'skills'],
172
+ },
173
+ },
174
+ },
175
+ required: ['workflows'],
176
+ },
177
+ annotations: { title: 'List workflow recipes', ...READONLY },
111
178
  },
112
179
  {
113
180
  name: 'get_workflow',
181
+ title: 'Get a workflow recipe',
114
182
  description: 'Get one workflow recipe by id: the ordered list of skills to run and what each produces. Run each step in order with get_skill, carrying every output forward as context for the next.',
115
- inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'The workflow id, e.g. "ship-a-feature".' } }, required: ['id'] },
183
+ inputSchema: {
184
+ type: 'object',
185
+ additionalProperties: false,
186
+ properties: { id: { type: 'string', description: 'The workflow id, e.g. "ship-a-feature" (from list_workflows).' } },
187
+ required: ['id'],
188
+ },
189
+ outputSchema: {
190
+ type: 'object',
191
+ properties: {
192
+ id: { type: 'string', description: 'Recipe id.' },
193
+ name: { type: 'string', description: 'Recipe name.' },
194
+ lifecycle: { type: 'string', description: 'The lifecycle stages it spans.' },
195
+ summary: { type: 'string', description: 'What the recipe accomplishes.' },
196
+ steps: {
197
+ type: 'array', description: 'Ordered steps; run each with get_skill, carrying output forward.',
198
+ items: {
199
+ type: 'object',
200
+ properties: {
201
+ skill: { type: 'string', description: 'The skill id to run at this step.' },
202
+ produces: { type: 'string', description: 'What this step produces.' },
203
+ passes: { type: ['string', 'null'], description: 'What to carry forward to the next step.' },
204
+ },
205
+ required: ['skill'],
206
+ },
207
+ },
208
+ },
209
+ required: ['id', 'name', 'steps'],
210
+ },
211
+ annotations: { title: 'Get a workflow recipe', ...READONLY },
116
212
  },
117
213
  ];
118
214
 
215
+ // Each tool returns { text, structured }: human-readable text content (back-compat) plus a
216
+ // structured object matching the tool's outputSchema.
217
+ const skillItem = (s) => ({ name: s.name, title: s.title, tier: s.tier, description: s.description });
218
+
119
219
  function runTool(name, args = {}) {
120
220
  if (name === 'list_skills') {
121
221
  const list = SKILLS.filter((s) => !args.tier || s.tier === args.tier);
122
- const text = list.map((s) => `- ${s.name} [${s.tier}] — ${s.description}`).join('\n');
123
- return `${list.length} skills:\n${text}`;
222
+ const text = `${list.length} skills:\n` + list.map((s) => `- ${s.name} [${s.tier}] — ${s.description}`).join('\n');
223
+ return { text, structured: { count: list.length, skills: list.map(skillItem) } };
124
224
  }
125
225
  if (name === 'search_skills') {
126
226
  const q = String(args.query || '').toLowerCase().trim();
@@ -136,27 +236,27 @@ function runTool(name, args = {}) {
136
236
  }
137
237
  return { s, score };
138
238
  }).filter((x) => x.score > 0).sort((a, b) => b.score - a.score).slice(0, Math.max(1, Math.min(args.limit || 10, 50)));
139
- if (!scored.length) return `No skills matched "${args.query}".`;
140
- return scored.map(({ s }) => `- ${s.name} [${s.tier}] — ${s.description}`).join('\n');
239
+ const matches = scored.map(({ s }) => skillItem(s));
240
+ const text = matches.length ? matches.map((s) => `- ${s.name} [${s.tier}] — ${s.description}`).join('\n') : `No skills matched "${args.query}".`;
241
+ return { text, structured: { query: String(args.query || ''), matches } };
141
242
  }
142
243
  if (name === 'get_skill') {
143
244
  const s = byName.get(String(args.name || '').trim());
144
245
  if (!s) throw new Error(`Unknown skill "${args.name}". Use search_skills or list_skills to find one.`);
145
- return s.body;
246
+ return { text: s.body, structured: { name: s.name, title: s.title, instructions: s.body } };
146
247
  }
147
248
  if (name === 'list_workflows') {
148
- if (!WORKFLOWS.length) return 'No workflow recipes are available in this install.';
149
- return WORKFLOWS.map((w) =>
150
- `- ${w.id} (${w.lifecycle}) ${w.summary}\n chain: ${w.steps.map((s) => s.skill).join(' → ')}`
151
- ).join('\n');
249
+ const text = WORKFLOWS.length
250
+ ? WORKFLOWS.map((w) => `- ${w.id} (${w.lifecycle}) — ${w.summary}\n chain: ${w.steps.map((s) => s.skill).join(' → ')}`).join('\n')
251
+ : 'No workflow recipes are available in this install.';
252
+ return { text, structured: { workflows: WORKFLOWS.map((w) => ({ id: w.id, name: w.name, lifecycle: w.lifecycle, summary: w.summary, skills: w.steps.map((s) => s.skill) })) } };
152
253
  }
153
254
  if (name === 'get_workflow') {
154
255
  const w = wfById.get(String(args.id || '').trim());
155
256
  if (!w) throw new Error(`Unknown workflow "${args.id}". Use list_workflows to see available recipes.`);
156
- const steps = w.steps.map((s, i) =>
157
- `${i + 1}. get_skill("${s.skill}") → produces ${s.produces}.${s.passes ? ` Pass forward: ${s.passes}.` : ''}`
158
- ).join('\n');
159
- return `Workflow: ${w.name} (${w.lifecycle})\n${w.summary}\n\nRun these in order, carrying each output forward as context for the next:\n${steps}`;
257
+ const steps = w.steps.map((s, i) => `${i + 1}. get_skill("${s.skill}") → produces ${s.produces}.${s.passes ? ` Pass forward: ${s.passes}.` : ''}`).join('\n');
258
+ const text = `Workflow: ${w.name} (${w.lifecycle})\n${w.summary}\n\nRun these in order, carrying each output forward as context for the next:\n${steps}`;
259
+ return { text, structured: { id: w.id, name: w.name, lifecycle: w.lifecycle, summary: w.summary, steps: w.steps.map((s) => ({ skill: s.skill, produces: s.produces, passes: s.passes ?? null })) } };
160
260
  }
161
261
  throw new Error(`Unknown tool: ${name}`);
162
262
  }
@@ -175,7 +275,17 @@ function handle(msg) {
175
275
  return reply(id, {
176
276
  protocolVersion: (params && params.protocolVersion) || '2024-11-05',
177
277
  capabilities: { tools: {}, prompts: {}, resources: {} },
178
- serverInfo: { name: SERVER_NAME, version: VERSION },
278
+ serverInfo: {
279
+ name: SERVER_NAME,
280
+ title: 'PM Skills — Professional Agent Skills',
281
+ version: VERSION,
282
+ websiteUrl: 'https://mohitagw15856.github.io/pm-claude-skills/',
283
+ icons: [{ src: 'https://raw.githubusercontent.com/mohitagw15856/pm-claude-skills/main/icon.svg', mimeType: 'image/svg+xml', sizes: ['any'] }],
284
+ },
285
+ instructions:
286
+ 'A library of professional Agent Skills (PRDs, launches, postmortems, compliance, growth, and more) plus multi-skill workflow recipes. ' +
287
+ 'To answer a professional task: call search_skills (or list_skills) to find the right skill, then get_skill to fetch its instructions and apply them to the user\'s input. ' +
288
+ 'For multi-step work that spans discovery → decision → build → ship, use list_workflows / get_workflow and run the chained skills in order, carrying each output forward.',
179
289
  });
180
290
  case 'tools/list':
181
291
  return reply(id, { tools: TOOLS });
@@ -211,8 +321,8 @@ function handle(msg) {
211
321
  case 'tools/call': {
212
322
  const toolName = params && params.name;
213
323
  try {
214
- const text = runTool(toolName, (params && params.arguments) || {});
215
- return reply(id, { content: [{ type: 'text', text }] });
324
+ const { text, structured } = runTool(toolName, (params && params.arguments) || {});
325
+ return reply(id, { content: [{ type: 'text', text }], structuredContent: structured });
216
326
  } catch (e) {
217
327
  return reply(id, { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true });
218
328
  }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "pm-claude-skills",
3
- "version": "29.3.0",
3
+ "version": "29.3.1",
4
4
  "type": "module",
5
5
  "mcpName": "io.github.mohitagw15856/pm-claude-skills",
6
- "description": "232 professional Agent Skills (SKILL.md) + subagents + slash commands for Claude, ChatGPT, Gemini, Cursor, Codex & Hermes. It's a CLI, not a library: run `npx pm-claude-skills add --agent <tool>` to install into your AI tool — no `npm install` needed. Or try any skill free at https://mohitagw15856.github.io/pm-claude-skills/",
6
+ "description": "238 professional Agent Skills (SKILL.md) + subagents + slash commands for Claude, ChatGPT, Gemini, Cursor, Codex & Hermes. It's a CLI, not a library: run `npx pm-claude-skills add --agent <tool>` to install into your AI tool — no `npm install` needed. Or try any skill free at https://mohitagw15856.github.io/pm-claude-skills/",
7
7
  "keywords": [
8
8
  "claude",
9
9
  "claude-code",
@@ -22,7 +22,8 @@
22
22
  "product-management"
23
23
  ],
24
24
  "license": "MIT",
25
- "homepage": "https://github.com/mohitagw15856/pm-claude-skills",
25
+ "homepage": "https://mohitagw15856.github.io/pm-claude-skills/",
26
+ "icon": "icon.svg",
26
27
  "repository": {
27
28
  "type": "git",
28
29
  "url": "https://github.com/mohitagw15856/pm-claude-skills.git"
@@ -40,6 +41,7 @@
40
41
  "pm-claude-skills-mcp": "mcp/server.mjs"
41
42
  },
42
43
  "files": [
44
+ "icon.svg",
43
45
  "bin/",
44
46
  "mcp/",
45
47
  "skills/",