clawskills-mcp 0.5.0 → 0.6.0
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 +11 -0
- package/dist/index.js +111 -2
- package/dist/index.test.js +29 -3
- package/package.json +1 -1
- package/playbooks/hubspot-asana-onboarding.md +14 -0
- package/playbooks/salesforce-hubspot-lead-sync.md +14 -0
- package/playbooks/zendesk-jira-bug-escalation.md +14 -0
- package/skills/ROADMAP.md +2 -2
package/README.md
CHANGED
|
@@ -49,6 +49,9 @@ docker run --rm -i clawskills-mcp
|
|
|
49
49
|
| `list_playbooks` | List all available cross-tool workflow playbooks |
|
|
50
50
|
| `get_playbook` | Fetch a full playbook by slug |
|
|
51
51
|
| `search_playbooks` | Full-text search across all workflow playbooks |
|
|
52
|
+
| `search_clawskills` | Unified search across both skills and playbooks |
|
|
53
|
+
|
|
54
|
+
For most user queries, start with `search_clawskills`. It biases toward playbooks for workflow-shaped searches like `closed won onboarding`, `zendesk jira escalation`, or `lead sync`.
|
|
52
55
|
|
|
53
56
|
### `list_skills`
|
|
54
57
|
|
|
@@ -92,6 +95,14 @@ No arguments. Returns all available playbooks.
|
|
|
92
95
|
|
|
93
96
|
Returns matching excerpts (±3 lines of context) grouped by playbook, capped at 5 matches per playbook.
|
|
94
97
|
|
|
98
|
+
### `search_clawskills`
|
|
99
|
+
|
|
100
|
+
| Argument | Type | Required | Description |
|
|
101
|
+
|----------|------|----------|-------------|
|
|
102
|
+
| `query` | string | yes | Search string, e.g. `"closed won onboarding"`, `"zendesk jira escalation"`, `"lead sync"` |
|
|
103
|
+
|
|
104
|
+
Returns combined results across skills and playbooks. Workflow-oriented queries rank playbooks ahead of generic skill matches.
|
|
105
|
+
|
|
95
106
|
---
|
|
96
107
|
|
|
97
108
|
## Configuration
|
package/dist/index.js
CHANGED
|
@@ -148,9 +148,55 @@ function findSkill(skills, name) {
|
|
|
148
148
|
function findPlaybook(playbooks, name) {
|
|
149
149
|
return findSkill(playbooks, name);
|
|
150
150
|
}
|
|
151
|
+
function parseFrontmatter(content) {
|
|
152
|
+
if (!content.startsWith("---\n"))
|
|
153
|
+
return {};
|
|
154
|
+
const end = content.indexOf("\n---\n", 4);
|
|
155
|
+
if (end === -1)
|
|
156
|
+
return {};
|
|
157
|
+
const block = content.slice(4, end);
|
|
158
|
+
const metadata = {};
|
|
159
|
+
let currentKey = null;
|
|
160
|
+
for (const rawLine of block.split("\n")) {
|
|
161
|
+
const line = rawLine.trimEnd();
|
|
162
|
+
const keyMatch = line.match(/^([a-zA-Z0-9_]+):\s*(.*)$/);
|
|
163
|
+
if (keyMatch) {
|
|
164
|
+
const [, key, value] = keyMatch;
|
|
165
|
+
if (value === "") {
|
|
166
|
+
metadata[key] = [];
|
|
167
|
+
currentKey = key;
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
metadata[key] = value.trim();
|
|
171
|
+
currentKey = null;
|
|
172
|
+
}
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const listMatch = line.match(/^\s*-\s+(.*)$/);
|
|
176
|
+
if (listMatch && currentKey) {
|
|
177
|
+
const current = metadata[currentKey];
|
|
178
|
+
if (Array.isArray(current)) {
|
|
179
|
+
current.push(listMatch[1].trim());
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return metadata;
|
|
184
|
+
}
|
|
185
|
+
function getPlaybookMetadata(content) {
|
|
186
|
+
const frontmatter = parseFrontmatter(content);
|
|
187
|
+
const systems = Array.isArray(frontmatter.systems) ? frontmatter.systems : [];
|
|
188
|
+
const tags = Array.isArray(frontmatter.tags) ? frontmatter.tags : [];
|
|
189
|
+
return {
|
|
190
|
+
title: typeof frontmatter.title === "string" ? frontmatter.title : undefined,
|
|
191
|
+
systems,
|
|
192
|
+
tags,
|
|
193
|
+
triggerType: typeof frontmatter.trigger_type === "string" ? frontmatter.trigger_type : undefined,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
151
196
|
function searchDocuments(documents, query) {
|
|
152
197
|
const results = [];
|
|
153
198
|
const lowerQuery = query.toLowerCase();
|
|
199
|
+
const queryTerms = lowerQuery.split(/[^a-z0-9]+/).filter(Boolean);
|
|
154
200
|
const CONTEXT_LINES = 3;
|
|
155
201
|
const MAX_PER_SKILL = 5;
|
|
156
202
|
for (const [name, content] of documents.entries()) {
|
|
@@ -158,7 +204,10 @@ function searchDocuments(documents, query) {
|
|
|
158
204
|
const excerpts = [];
|
|
159
205
|
const usedRanges = [];
|
|
160
206
|
for (let i = 0; i < lines.length; i++) {
|
|
161
|
-
|
|
207
|
+
const lowerLine = lines[i].toLowerCase();
|
|
208
|
+
const matchesExact = lowerLine.includes(lowerQuery);
|
|
209
|
+
const matchesTerm = queryTerms.some((term) => lowerLine.includes(term));
|
|
210
|
+
if (!matchesExact && !matchesTerm)
|
|
162
211
|
continue;
|
|
163
212
|
const start = Math.max(0, i - CONTEXT_LINES);
|
|
164
213
|
const end = Math.min(lines.length - 1, i + CONTEXT_LINES);
|
|
@@ -184,6 +233,36 @@ function searchSkills(skills, query) {
|
|
|
184
233
|
function searchPlaybooks(playbooks, query) {
|
|
185
234
|
return searchDocuments(playbooks, query);
|
|
186
235
|
}
|
|
236
|
+
function searchClawskills(skills, playbooks, query) {
|
|
237
|
+
const normalizedQuery = query.toLowerCase();
|
|
238
|
+
const queryTerms = normalizedQuery.split(/[^a-z0-9]+/).filter(Boolean);
|
|
239
|
+
const workflowHints = new Set([
|
|
240
|
+
"sync", "onboarding", "closed", "won", "handoff", "escalate",
|
|
241
|
+
"escalation", "workflow", "playbook", "incident", "notify",
|
|
242
|
+
]);
|
|
243
|
+
const workflowShaped = queryTerms.some((term) => workflowHints.has(term));
|
|
244
|
+
const skillResults = searchSkills(skills, query).map((result) => ({
|
|
245
|
+
...result,
|
|
246
|
+
contentType: "skill",
|
|
247
|
+
score: result.excerpts.length * 10 + (workflowShaped ? 0 : 5),
|
|
248
|
+
}));
|
|
249
|
+
const playbookResults = searchPlaybooks(playbooks, query).map((result) => {
|
|
250
|
+
const metadata = getPlaybookMetadata(playbooks.get(result.name));
|
|
251
|
+
const metadataText = [metadata.title, ...metadata.systems, ...metadata.tags, metadata.triggerType]
|
|
252
|
+
.filter(Boolean)
|
|
253
|
+
.join(" ")
|
|
254
|
+
.toLowerCase();
|
|
255
|
+
const metadataMatches = queryTerms.filter((term) => metadataText.includes(term)).length;
|
|
256
|
+
return {
|
|
257
|
+
...result,
|
|
258
|
+
contentType: "playbook",
|
|
259
|
+
score: result.excerpts.length * 10 + metadataMatches * 5 + (workflowShaped ? 15 : 0),
|
|
260
|
+
};
|
|
261
|
+
});
|
|
262
|
+
return [...playbookResults, ...skillResults]
|
|
263
|
+
.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name))
|
|
264
|
+
.slice(0, 12);
|
|
265
|
+
}
|
|
187
266
|
// ---------------------------------------------------------------------------
|
|
188
267
|
// Skill summary (first meaningful line)
|
|
189
268
|
// ---------------------------------------------------------------------------
|
|
@@ -198,7 +277,7 @@ function skillSummary(content) {
|
|
|
198
277
|
// ---------------------------------------------------------------------------
|
|
199
278
|
// Exports for testing
|
|
200
279
|
// ---------------------------------------------------------------------------
|
|
201
|
-
export { resolveSkillsDir, resolvePlaybooksDir, resolvePackageVersion, loadSkills, loadPlaybooks, extractSection, findSkill, findPlaybook, searchSkills, searchPlaybooks, skillSummary, };
|
|
280
|
+
export { resolveSkillsDir, resolvePlaybooksDir, resolvePackageVersion, loadSkills, loadPlaybooks, extractSection, findSkill, findPlaybook, searchSkills, searchPlaybooks, searchClawskills, getPlaybookMetadata, skillSummary, };
|
|
202
281
|
// ---------------------------------------------------------------------------
|
|
203
282
|
// MCP Server setup
|
|
204
283
|
// ---------------------------------------------------------------------------
|
|
@@ -269,6 +348,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
269
348
|
required: ["query"],
|
|
270
349
|
},
|
|
271
350
|
},
|
|
351
|
+
{
|
|
352
|
+
name: "search_clawskills",
|
|
353
|
+
description: "Search across both skills and playbooks. For workflow-shaped queries, playbooks are ranked ahead of generic skill matches.",
|
|
354
|
+
inputSchema: {
|
|
355
|
+
type: "object",
|
|
356
|
+
properties: {
|
|
357
|
+
query: { type: "string", description: "Search query, e.g. 'closed won onboarding', 'zendesk jira escalation', 'lead sync'" },
|
|
358
|
+
},
|
|
359
|
+
required: ["query"],
|
|
360
|
+
},
|
|
361
|
+
},
|
|
272
362
|
],
|
|
273
363
|
}));
|
|
274
364
|
// Call tools
|
|
@@ -276,6 +366,7 @@ const GetSkillSchema = z.object({ name: z.string(), section: z.string().optional
|
|
|
276
366
|
const GetPlaybookSchema = z.object({ name: z.string() });
|
|
277
367
|
const SearchSkillsSchema = z.object({ query: z.string() });
|
|
278
368
|
const SearchPlaybooksSchema = z.object({ query: z.string() });
|
|
369
|
+
const SearchClawskillsSchema = z.object({ query: z.string() });
|
|
279
370
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
280
371
|
const { name, arguments: args } = request.params;
|
|
281
372
|
if (name === "list_skills") {
|
|
@@ -373,6 +464,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
373
464
|
.join("\n\n---\n\n");
|
|
374
465
|
return { content: [{ type: "text", text: `Playbook search results for "${query}":\n\n${output}` }] };
|
|
375
466
|
}
|
|
467
|
+
if (name === "search_clawskills") {
|
|
468
|
+
const parsed = SearchClawskillsSchema.safeParse(args);
|
|
469
|
+
if (!parsed.success) {
|
|
470
|
+
return { content: [{ type: "text", text: `Invalid arguments: ${parsed.error.message}` }], isError: true };
|
|
471
|
+
}
|
|
472
|
+
const { query } = parsed.data;
|
|
473
|
+
const results = searchClawskills(skills, playbooks, query);
|
|
474
|
+
if (results.length === 0) {
|
|
475
|
+
return { content: [{ type: "text", text: `No results found for "${query}".` }] };
|
|
476
|
+
}
|
|
477
|
+
const output = results
|
|
478
|
+
.map(({ name: itemName, contentType, excerpts }) => {
|
|
479
|
+
const blocks = excerpts.map((e, i) => `--- match ${i + 1} ---\n${e}`).join("\n\n");
|
|
480
|
+
return `### [${contentType}] ${itemName}\n\n${blocks}`;
|
|
481
|
+
})
|
|
482
|
+
.join("\n\n---\n\n");
|
|
483
|
+
return { content: [{ type: "text", text: `Unified search results for "${query}":\n\n${output}` }] };
|
|
484
|
+
}
|
|
376
485
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
377
486
|
});
|
|
378
487
|
// ---------------------------------------------------------------------------
|
package/dist/index.test.js
CHANGED
|
@@ -3,7 +3,7 @@ import * as fs from "fs";
|
|
|
3
3
|
import * as os from "os";
|
|
4
4
|
import * as path from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
|
-
import { resolveSkillsDir, resolvePlaybooksDir, resolvePackageVersion, loadSkills, loadPlaybooks, extractSection, findSkill, findPlaybook, searchSkills, searchPlaybooks, skillSummary, } from "./index.js";
|
|
6
|
+
import { resolveSkillsDir, resolvePlaybooksDir, resolvePackageVersion, loadSkills, loadPlaybooks, extractSection, findSkill, findPlaybook, searchSkills, searchPlaybooks, searchClawskills, getPlaybookMetadata, skillSummary, } from "./index.js";
|
|
7
7
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
8
|
const REAL_SKILLS_DIR = path.resolve(__dirname, "../../skills");
|
|
9
9
|
const REAL_PLAYBOOKS_DIR = path.resolve(__dirname, "../../playbooks");
|
|
@@ -56,8 +56,8 @@ let tempPlaybooksDir;
|
|
|
56
56
|
beforeAll(() => {
|
|
57
57
|
tempDir = makeTempSkillsDir({ github: SAMPLE_CONTENT, salesforce: "# Salesforce\nCRM platform.\n## Overview\nSalesforce is a CRM." });
|
|
58
58
|
tempPlaybooksDir = makeTempPlaybooksDir({
|
|
59
|
-
"zendesk-jira": "# Zendesk -> Jira\n\nEscalate bugs from support into engineering.\n\n## Idempotency\n\nUse zendesk:{ticket_id}.",
|
|
60
|
-
"hubspot-asana": "# HubSpot -> Asana\n\nCreate onboarding work from closed won deals.",
|
|
59
|
+
"zendesk-jira": "---\ntitle: Zendesk -> Jira\nsystems:\n - zendesk\n - jira\ntags:\n - support\n - escalation\ntrigger_type: webhook\n---\n# Zendesk -> Jira\n\nEscalate bugs from support into engineering.\n\n## Idempotency\n\nUse zendesk:{ticket_id}.",
|
|
60
|
+
"hubspot-asana": "---\ntitle: HubSpot -> Asana\nsystems:\n - hubspot\n - asana\ntags:\n - onboarding\n - handoff\ntrigger_type: webhook\n---\n# HubSpot -> Asana\n\nCreate onboarding work from closed won deals.",
|
|
61
61
|
});
|
|
62
62
|
});
|
|
63
63
|
afterAll(() => {
|
|
@@ -229,6 +229,32 @@ describe("searchPlaybooks", () => {
|
|
|
229
229
|
expect(results[0].name).toBe("zendesk-jira");
|
|
230
230
|
});
|
|
231
231
|
});
|
|
232
|
+
describe("getPlaybookMetadata", () => {
|
|
233
|
+
it("parses frontmatter metadata from playbooks", () => {
|
|
234
|
+
const playbooks = loadPlaybooks(tempPlaybooksDir);
|
|
235
|
+
const metadata = getPlaybookMetadata(playbooks.get("zendesk-jira"));
|
|
236
|
+
expect(metadata.systems).toContain("zendesk");
|
|
237
|
+
expect(metadata.tags).toContain("escalation");
|
|
238
|
+
expect(metadata.triggerType).toBe("webhook");
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
describe("searchClawskills", () => {
|
|
242
|
+
it("prefers playbooks for workflow-shaped queries", () => {
|
|
243
|
+
const skills = new Map([["hubspot", "HubSpot CRM API.\nUse for contacts and deals."]]);
|
|
244
|
+
const playbooks = loadPlaybooks(tempPlaybooksDir);
|
|
245
|
+
const results = searchClawskills(skills, playbooks, "closed won onboarding");
|
|
246
|
+
expect(results.length).toBeGreaterThan(0);
|
|
247
|
+
expect(results[0].contentType).toBe("playbook");
|
|
248
|
+
expect(results[0].name).toBe("hubspot-asana");
|
|
249
|
+
});
|
|
250
|
+
it("still returns skill hits for non-workflow queries", () => {
|
|
251
|
+
const skills = loadSkills(tempDir);
|
|
252
|
+
const playbooks = loadPlaybooks(tempPlaybooksDir);
|
|
253
|
+
const results = searchClawskills(skills, playbooks, "Personal Access Token");
|
|
254
|
+
expect(results.length).toBeGreaterThan(0);
|
|
255
|
+
expect(results.some((result) => result.contentType === "skill" && result.name === "github")).toBe(true);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
232
258
|
// ---------------------------------------------------------------------------
|
|
233
259
|
// skillSummary
|
|
234
260
|
// ---------------------------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: HubSpot Deal Won -> Asana Onboarding Kickoff
|
|
3
|
+
systems:
|
|
4
|
+
- hubspot
|
|
5
|
+
- asana
|
|
6
|
+
tags:
|
|
7
|
+
- crm
|
|
8
|
+
- onboarding
|
|
9
|
+
- handoff
|
|
10
|
+
- delivery
|
|
11
|
+
- revops
|
|
12
|
+
trigger_type: webhook
|
|
13
|
+
---
|
|
14
|
+
|
|
1
15
|
# HubSpot Deal Won -> Asana Onboarding Kickoff
|
|
2
16
|
|
|
3
17
|
## Goal
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Salesforce Lead -> HubSpot Contact Sync
|
|
3
|
+
systems:
|
|
4
|
+
- salesforce
|
|
5
|
+
- hubspot
|
|
6
|
+
tags:
|
|
7
|
+
- crm
|
|
8
|
+
- sync
|
|
9
|
+
- marketing
|
|
10
|
+
- lead
|
|
11
|
+
- revops
|
|
12
|
+
trigger_type: scheduled_sync
|
|
13
|
+
---
|
|
14
|
+
|
|
1
15
|
# Salesforce Lead -> HubSpot Contact Sync
|
|
2
16
|
|
|
3
17
|
## Goal
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Zendesk Ticket -> Jira Bug Escalation
|
|
3
|
+
systems:
|
|
4
|
+
- zendesk
|
|
5
|
+
- jira
|
|
6
|
+
tags:
|
|
7
|
+
- support
|
|
8
|
+
- engineering
|
|
9
|
+
- escalation
|
|
10
|
+
- incident
|
|
11
|
+
- handoff
|
|
12
|
+
trigger_type: webhook_or_incremental_sync
|
|
13
|
+
---
|
|
14
|
+
|
|
1
15
|
# Zendesk Ticket -> Jira Bug Escalation
|
|
2
16
|
|
|
3
17
|
## Goal
|
package/skills/ROADMAP.md
CHANGED
|
@@ -19,7 +19,7 @@ This skill set enables end-to-end automation and integration across the most com
|
|
|
19
19
|
| Layer | Status |
|
|
20
20
|
|-------|--------|
|
|
21
21
|
| **14 skill docs** — Monday.com, Salesforce, Jira, Dynamics 365, HubSpot, ServiceNow, Zendesk, Asana, GitHub, Figma, Slack, Stripe, Notion, Linear | ✅ Live |
|
|
22
|
-
| **MCP server** — `clawskills-mcp` v0.
|
|
22
|
+
| **MCP server** — `clawskills-mcp` v0.5.0 on npm; `list_skills`, `get_skill`, `search_skills` | ✅ Live |
|
|
23
23
|
| **CI pipeline** — build + test on every push/PR via `ci.yml` | ✅ Live |
|
|
24
24
|
| **Release automation** — `release.yml` workflow_dispatch → bump, tag, npm publish via OIDC | ✅ Live |
|
|
25
25
|
| **Test suite** — Vitest unit tests + real-skills validation (required sections, ≥5 KB, all 14 tools) | ✅ Live |
|
|
@@ -83,7 +83,7 @@ The repo now needs tighter alignment between the canonical `skills/` directory,
|
|
|
83
83
|
| Channel | Status | Next action |
|
|
84
84
|
|---------|--------|-------------|
|
|
85
85
|
| GitHub (public) | Ready to flip | Make repo public |
|
|
86
|
-
| npm (`clawskills-mcp`) | ✅ Live — v0.
|
|
86
|
+
| npm (`clawskills-mcp`) | ✅ Live — v0.5.0 | Maintain via `release.yml` |
|
|
87
87
|
| MCP registry listings | Not submitted | Submit to glama.ai, mcp.so, smithery.ai after public launch |
|
|
88
88
|
| Docs website | Planned | Mintlify — fastest path to SEO-friendly browsable docs |
|
|
89
89
|
| Lane A vs B | TBD | Revisit after first traction signals from public launch |
|