clawskills-mcp 0.4.0 → 0.5.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 +27 -4
- package/dist/index.js +115 -5
- package/dist/index.test.js +81 -2
- package/package.json +3 -2
- package/playbooks/INDEX.md +32 -0
- package/playbooks/hubspot-asana-onboarding.md +76 -0
- package/playbooks/salesforce-hubspot-lead-sync.md +78 -0
- package/playbooks/zendesk-jira-bug-escalation.md +79 -0
- package/skills/INDEX.md +1 -0
- package/skills/ROADMAP.md +40 -3
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
An MCP (Model Context Protocol) server that exposes [ClawSkills](https://github.com/Shanksg/clawskills) API integration skill docs to AI agents.
|
|
4
4
|
|
|
5
|
-
ClawSkills currently contains 14 structured Markdown skill docs
|
|
5
|
+
ClawSkills currently contains 14 structured Markdown skill docs plus workflow playbooks for cross-tool automations. Each skill tracks its own API version and `Last validated` date, while `clawskills-mcp` has its own npm version for the server itself.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -46,6 +46,9 @@ docker run --rm -i clawskills-mcp
|
|
|
46
46
|
| `list_skills` | List all available skill slugs with descriptions |
|
|
47
47
|
| `get_skill` | Fetch a full skill doc or a specific section |
|
|
48
48
|
| `search_skills` | Full-text search across all skill docs |
|
|
49
|
+
| `list_playbooks` | List all available cross-tool workflow playbooks |
|
|
50
|
+
| `get_playbook` | Fetch a full playbook by slug |
|
|
51
|
+
| `search_playbooks` | Full-text search across all workflow playbooks |
|
|
49
52
|
|
|
50
53
|
### `list_skills`
|
|
51
54
|
|
|
@@ -71,17 +74,36 @@ Examples:
|
|
|
71
74
|
|
|
72
75
|
Returns matching excerpts (±3 lines of context) grouped by skill, capped at 5 matches per skill.
|
|
73
76
|
|
|
77
|
+
### `list_playbooks`
|
|
78
|
+
|
|
79
|
+
No arguments. Returns all available playbooks.
|
|
80
|
+
|
|
81
|
+
### `get_playbook`
|
|
82
|
+
|
|
83
|
+
| Argument | Type | Required | Description |
|
|
84
|
+
|----------|------|----------|-------------|
|
|
85
|
+
| `name` | string | yes | Playbook slug: `zendesk-jira-bug-escalation`, `hubspot-asana-onboarding`, etc. Fuzzy matched. |
|
|
86
|
+
|
|
87
|
+
### `search_playbooks`
|
|
88
|
+
|
|
89
|
+
| Argument | Type | Required | Description |
|
|
90
|
+
|----------|------|----------|-------------|
|
|
91
|
+
| `query` | string | yes | Search string, e.g. `"idempotency"`, `"rollback"`, `"closed won"` |
|
|
92
|
+
|
|
93
|
+
Returns matching excerpts (±3 lines of context) grouped by playbook, capped at 5 matches per playbook.
|
|
94
|
+
|
|
74
95
|
---
|
|
75
96
|
|
|
76
97
|
## Configuration
|
|
77
98
|
|
|
78
|
-
Skills are bundled inside the npm package — no
|
|
99
|
+
Skills and playbooks are bundled inside the npm package — no extra configuration is needed for the standard `npx` setup.
|
|
79
100
|
|
|
80
|
-
`SKILLS_DIR`
|
|
101
|
+
`SKILLS_DIR` and `PLAYBOOKS_DIR` are available if you want to point the server at custom or local content directories:
|
|
81
102
|
|
|
82
103
|
| Env var | Default | Description |
|
|
83
104
|
|---------|---------|-------------|
|
|
84
105
|
| `SKILLS_DIR` | bundled `skills/` inside the package | Absolute path to a directory of `{slug}/skill.md` files |
|
|
106
|
+
| `PLAYBOOKS_DIR` | bundled `playbooks/` inside the package | Absolute path to a directory of `{slug}.md` playbook files |
|
|
85
107
|
|
|
86
108
|
```json
|
|
87
109
|
{
|
|
@@ -90,7 +112,8 @@ Skills are bundled inside the npm package — no `SKILLS_DIR` configuration need
|
|
|
90
112
|
"command": "npx",
|
|
91
113
|
"args": ["-y", "clawskills-mcp"],
|
|
92
114
|
"env": {
|
|
93
|
-
"SKILLS_DIR": "/path/to/your/skills"
|
|
115
|
+
"SKILLS_DIR": "/path/to/your/skills",
|
|
116
|
+
"PLAYBOOKS_DIR": "/path/to/your/playbooks"
|
|
94
117
|
}
|
|
95
118
|
}
|
|
96
119
|
}
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,19 @@ function resolveSkillsDir() {
|
|
|
25
25
|
}
|
|
26
26
|
return candidates[0];
|
|
27
27
|
}
|
|
28
|
+
function resolvePlaybooksDir() {
|
|
29
|
+
if (process.env.PLAYBOOKS_DIR)
|
|
30
|
+
return process.env.PLAYBOOKS_DIR;
|
|
31
|
+
const candidates = [
|
|
32
|
+
path.resolve(__dirname, "../../playbooks"),
|
|
33
|
+
path.resolve(__dirname, "../playbooks"),
|
|
34
|
+
];
|
|
35
|
+
for (const candidate of candidates) {
|
|
36
|
+
if (fs.existsSync(candidate))
|
|
37
|
+
return candidate;
|
|
38
|
+
}
|
|
39
|
+
return candidates[0];
|
|
40
|
+
}
|
|
28
41
|
function resolvePackageVersion() {
|
|
29
42
|
const packageJsonPath = path.resolve(__dirname, "../package.json");
|
|
30
43
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
@@ -49,6 +62,23 @@ function loadSkills(skillsDir) {
|
|
|
49
62
|
process.stderr.write(`[clawskills-mcp] Loaded ${skills.size} skills: ${[...skills.keys()].join(", ")}\n`);
|
|
50
63
|
return skills;
|
|
51
64
|
}
|
|
65
|
+
function loadPlaybooks(playbooksDir) {
|
|
66
|
+
const playbooks = new Map();
|
|
67
|
+
if (!fs.existsSync(playbooksDir)) {
|
|
68
|
+
process.stderr.write(`[clawskills-mcp] Playbooks directory not found: ${playbooksDir}\n`);
|
|
69
|
+
return playbooks;
|
|
70
|
+
}
|
|
71
|
+
const entries = fs.readdirSync(playbooksDir, { withFileTypes: true });
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name === "INDEX.md")
|
|
74
|
+
continue;
|
|
75
|
+
const slug = entry.name.replace(/\.md$/, "");
|
|
76
|
+
const content = fs.readFileSync(path.join(playbooksDir, entry.name), "utf-8");
|
|
77
|
+
playbooks.set(slug, content);
|
|
78
|
+
}
|
|
79
|
+
process.stderr.write(`[clawskills-mcp] Loaded ${playbooks.size} playbooks: ${[...playbooks.keys()].join(", ")}\n`);
|
|
80
|
+
return playbooks;
|
|
81
|
+
}
|
|
52
82
|
// ---------------------------------------------------------------------------
|
|
53
83
|
// Section extraction
|
|
54
84
|
// ---------------------------------------------------------------------------
|
|
@@ -115,12 +145,15 @@ function findSkill(skills, name) {
|
|
|
115
145
|
}
|
|
116
146
|
return undefined;
|
|
117
147
|
}
|
|
118
|
-
function
|
|
148
|
+
function findPlaybook(playbooks, name) {
|
|
149
|
+
return findSkill(playbooks, name);
|
|
150
|
+
}
|
|
151
|
+
function searchDocuments(documents, query) {
|
|
119
152
|
const results = [];
|
|
120
153
|
const lowerQuery = query.toLowerCase();
|
|
121
154
|
const CONTEXT_LINES = 3;
|
|
122
155
|
const MAX_PER_SKILL = 5;
|
|
123
|
-
for (const [
|
|
156
|
+
for (const [name, content] of documents.entries()) {
|
|
124
157
|
const lines = content.split("\n");
|
|
125
158
|
const excerpts = [];
|
|
126
159
|
const usedRanges = [];
|
|
@@ -140,11 +173,17 @@ function searchSkills(skills, query) {
|
|
|
140
173
|
break;
|
|
141
174
|
}
|
|
142
175
|
if (excerpts.length > 0) {
|
|
143
|
-
results.push({
|
|
176
|
+
results.push({ name, excerpts });
|
|
144
177
|
}
|
|
145
178
|
}
|
|
146
179
|
return results;
|
|
147
180
|
}
|
|
181
|
+
function searchSkills(skills, query) {
|
|
182
|
+
return searchDocuments(skills, query);
|
|
183
|
+
}
|
|
184
|
+
function searchPlaybooks(playbooks, query) {
|
|
185
|
+
return searchDocuments(playbooks, query);
|
|
186
|
+
}
|
|
148
187
|
// ---------------------------------------------------------------------------
|
|
149
188
|
// Skill summary (first meaningful line)
|
|
150
189
|
// ---------------------------------------------------------------------------
|
|
@@ -159,13 +198,15 @@ function skillSummary(content) {
|
|
|
159
198
|
// ---------------------------------------------------------------------------
|
|
160
199
|
// Exports for testing
|
|
161
200
|
// ---------------------------------------------------------------------------
|
|
162
|
-
export { resolveSkillsDir, resolvePackageVersion, loadSkills, extractSection, findSkill, searchSkills, skillSummary };
|
|
201
|
+
export { resolveSkillsDir, resolvePlaybooksDir, resolvePackageVersion, loadSkills, loadPlaybooks, extractSection, findSkill, findPlaybook, searchSkills, searchPlaybooks, skillSummary, };
|
|
163
202
|
// ---------------------------------------------------------------------------
|
|
164
203
|
// MCP Server setup
|
|
165
204
|
// ---------------------------------------------------------------------------
|
|
166
205
|
const SKILLS_DIR = resolveSkillsDir();
|
|
206
|
+
const PLAYBOOKS_DIR = resolvePlaybooksDir();
|
|
167
207
|
const PACKAGE_VERSION = resolvePackageVersion();
|
|
168
208
|
const skills = loadSkills(SKILLS_DIR);
|
|
209
|
+
const playbooks = loadPlaybooks(PLAYBOOKS_DIR);
|
|
169
210
|
const server = new Server({ name: "clawskills-mcp", version: PACKAGE_VERSION }, { capabilities: { tools: {} } });
|
|
170
211
|
// List tools
|
|
171
212
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
@@ -201,11 +242,40 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
201
242
|
required: ["query"],
|
|
202
243
|
},
|
|
203
244
|
},
|
|
245
|
+
{
|
|
246
|
+
name: "list_playbooks",
|
|
247
|
+
description: "List all available ClawSkills workflow playbooks.",
|
|
248
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: "get_playbook",
|
|
252
|
+
description: "Retrieve a ClawSkills workflow playbook by name (slug).",
|
|
253
|
+
inputSchema: {
|
|
254
|
+
type: "object",
|
|
255
|
+
properties: {
|
|
256
|
+
name: { type: "string", description: "Playbook slug, e.g. 'zendesk-jira-bug-escalation'" },
|
|
257
|
+
},
|
|
258
|
+
required: ["name"],
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: "search_playbooks",
|
|
263
|
+
description: "Search across workflow playbooks for a query string and return matching excerpts with context.",
|
|
264
|
+
inputSchema: {
|
|
265
|
+
type: "object",
|
|
266
|
+
properties: {
|
|
267
|
+
query: { type: "string", description: "Search query, e.g. 'idempotency', 'rollback', 'customer impact'" },
|
|
268
|
+
},
|
|
269
|
+
required: ["query"],
|
|
270
|
+
},
|
|
271
|
+
},
|
|
204
272
|
],
|
|
205
273
|
}));
|
|
206
274
|
// Call tools
|
|
207
275
|
const GetSkillSchema = z.object({ name: z.string(), section: z.string().optional() });
|
|
276
|
+
const GetPlaybookSchema = z.object({ name: z.string() });
|
|
208
277
|
const SearchSkillsSchema = z.object({ query: z.string() });
|
|
278
|
+
const SearchPlaybooksSchema = z.object({ query: z.string() });
|
|
209
279
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
210
280
|
const { name, arguments: args } = request.params;
|
|
211
281
|
if (name === "list_skills") {
|
|
@@ -218,6 +288,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
218
288
|
});
|
|
219
289
|
return { content: [{ type: "text", text: `Available skills (${skills.size}):\n\n${lines.join("\n")}` }] };
|
|
220
290
|
}
|
|
291
|
+
if (name === "list_playbooks") {
|
|
292
|
+
if (playbooks.size === 0) {
|
|
293
|
+
return { content: [{ type: "text", text: "No playbooks loaded. Check PLAYBOOKS_DIR." }] };
|
|
294
|
+
}
|
|
295
|
+
const lines = [...playbooks.entries()].map(([slug, content]) => `• **${slug}** — ${skillSummary(content)}`);
|
|
296
|
+
return { content: [{ type: "text", text: `Available playbooks (${playbooks.size}):\n\n${lines.join("\n")}` }] };
|
|
297
|
+
}
|
|
221
298
|
if (name === "get_skill") {
|
|
222
299
|
const parsed = GetSkillSchema.safeParse(args);
|
|
223
300
|
if (!parsed.success) {
|
|
@@ -256,13 +333,46 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
256
333
|
return { content: [{ type: "text", text: `No results found for "${query}".` }] };
|
|
257
334
|
}
|
|
258
335
|
const output = results
|
|
259
|
-
.map(({ skill, excerpts }) => {
|
|
336
|
+
.map(({ name: skill, excerpts }) => {
|
|
260
337
|
const blocks = excerpts.map((e, i) => `--- match ${i + 1} ---\n${e}`).join("\n\n");
|
|
261
338
|
return `### ${skill}\n\n${blocks}`;
|
|
262
339
|
})
|
|
263
340
|
.join("\n\n---\n\n");
|
|
264
341
|
return { content: [{ type: "text", text: `Search results for "${query}":\n\n${output}` }] };
|
|
265
342
|
}
|
|
343
|
+
if (name === "get_playbook") {
|
|
344
|
+
const parsed = GetPlaybookSchema.safeParse(args);
|
|
345
|
+
if (!parsed.success) {
|
|
346
|
+
return { content: [{ type: "text", text: `Invalid arguments: ${parsed.error.message}` }], isError: true };
|
|
347
|
+
}
|
|
348
|
+
const slug = findPlaybook(playbooks, parsed.data.name);
|
|
349
|
+
if (!slug) {
|
|
350
|
+
const available = [...playbooks.keys()].join(", ");
|
|
351
|
+
return {
|
|
352
|
+
content: [{ type: "text", text: `Playbook "${parsed.data.name}" not found. Available playbooks: ${available}` }],
|
|
353
|
+
isError: true,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
return { content: [{ type: "text", text: playbooks.get(slug) }] };
|
|
357
|
+
}
|
|
358
|
+
if (name === "search_playbooks") {
|
|
359
|
+
const parsed = SearchPlaybooksSchema.safeParse(args);
|
|
360
|
+
if (!parsed.success) {
|
|
361
|
+
return { content: [{ type: "text", text: `Invalid arguments: ${parsed.error.message}` }], isError: true };
|
|
362
|
+
}
|
|
363
|
+
const { query } = parsed.data;
|
|
364
|
+
const results = searchPlaybooks(playbooks, query);
|
|
365
|
+
if (results.length === 0) {
|
|
366
|
+
return { content: [{ type: "text", text: `No playbook results found for "${query}".` }] };
|
|
367
|
+
}
|
|
368
|
+
const output = results
|
|
369
|
+
.map(({ name: playbook, excerpts }) => {
|
|
370
|
+
const blocks = excerpts.map((e, i) => `--- match ${i + 1} ---\n${e}`).join("\n\n");
|
|
371
|
+
return `### ${playbook}\n\n${blocks}`;
|
|
372
|
+
})
|
|
373
|
+
.join("\n\n---\n\n");
|
|
374
|
+
return { content: [{ type: "text", text: `Playbook search results for "${query}":\n\n${output}` }] };
|
|
375
|
+
}
|
|
266
376
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
267
377
|
});
|
|
268
378
|
// ---------------------------------------------------------------------------
|
package/dist/index.test.js
CHANGED
|
@@ -3,9 +3,10 @@ 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, resolvePackageVersion, loadSkills, extractSection, findSkill, searchSkills, skillSummary } from "./index.js";
|
|
6
|
+
import { resolveSkillsDir, resolvePlaybooksDir, resolvePackageVersion, loadSkills, loadPlaybooks, extractSection, findSkill, findPlaybook, searchSkills, searchPlaybooks, 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
|
+
const REAL_PLAYBOOKS_DIR = path.resolve(__dirname, "../../playbooks");
|
|
9
10
|
// ---------------------------------------------------------------------------
|
|
10
11
|
// Helpers
|
|
11
12
|
// ---------------------------------------------------------------------------
|
|
@@ -18,6 +19,13 @@ function makeTempSkillsDir(skills) {
|
|
|
18
19
|
}
|
|
19
20
|
return dir;
|
|
20
21
|
}
|
|
22
|
+
function makeTempPlaybooksDir(playbooks) {
|
|
23
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "clawskills-playbooks-test-"));
|
|
24
|
+
for (const [slug, content] of Object.entries(playbooks)) {
|
|
25
|
+
fs.writeFileSync(path.join(dir, `${slug}.md`), content, "utf-8");
|
|
26
|
+
}
|
|
27
|
+
return dir;
|
|
28
|
+
}
|
|
21
29
|
const SAMPLE_CONTENT = `# GitHub Skill
|
|
22
30
|
|
|
23
31
|
A skill doc for GitHub API.
|
|
@@ -44,11 +52,17 @@ Scopes: repo, read:user, etc.
|
|
|
44
52
|
POST /repos/{owner}/{repo}/issues
|
|
45
53
|
`;
|
|
46
54
|
let tempDir;
|
|
55
|
+
let tempPlaybooksDir;
|
|
47
56
|
beforeAll(() => {
|
|
48
57
|
tempDir = makeTempSkillsDir({ github: SAMPLE_CONTENT, salesforce: "# Salesforce\nCRM platform.\n## Overview\nSalesforce is a CRM." });
|
|
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.",
|
|
61
|
+
});
|
|
49
62
|
});
|
|
50
63
|
afterAll(() => {
|
|
51
64
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
65
|
+
fs.rmSync(tempPlaybooksDir, { recursive: true, force: true });
|
|
52
66
|
});
|
|
53
67
|
// ---------------------------------------------------------------------------
|
|
54
68
|
// loadSkills
|
|
@@ -86,6 +100,12 @@ describe("resolveSkillsDir", () => {
|
|
|
86
100
|
expect(loadSkills(resolveSkillsDir()).has("stripe")).toBe(true);
|
|
87
101
|
});
|
|
88
102
|
});
|
|
103
|
+
describe("resolvePlaybooksDir", () => {
|
|
104
|
+
it("prefers the canonical repo-root playbooks directory", () => {
|
|
105
|
+
expect(path.basename(resolvePlaybooksDir())).toBe("playbooks");
|
|
106
|
+
expect(loadPlaybooks(resolvePlaybooksDir()).has("zendesk-jira-bug-escalation")).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
89
109
|
describe("resolvePackageVersion", () => {
|
|
90
110
|
it("matches package.json", () => {
|
|
91
111
|
const packageJsonPath = path.resolve(__dirname, "../package.json");
|
|
@@ -94,6 +114,20 @@ describe("resolvePackageVersion", () => {
|
|
|
94
114
|
});
|
|
95
115
|
});
|
|
96
116
|
// ---------------------------------------------------------------------------
|
|
117
|
+
// loadPlaybooks
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
describe("loadPlaybooks", () => {
|
|
120
|
+
it("loads markdown files from a directory", () => {
|
|
121
|
+
const playbooks = loadPlaybooks(tempPlaybooksDir);
|
|
122
|
+
expect(playbooks.size).toBe(2);
|
|
123
|
+
expect(playbooks.has("zendesk-jira")).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
it("returns empty map for missing directory", () => {
|
|
126
|
+
const playbooks = loadPlaybooks("/nonexistent/playbooks/path");
|
|
127
|
+
expect(playbooks.size).toBe(0);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
97
131
|
// findSkill
|
|
98
132
|
// ---------------------------------------------------------------------------
|
|
99
133
|
describe("findSkill", () => {
|
|
@@ -119,6 +153,18 @@ describe("findSkill", () => {
|
|
|
119
153
|
expect(findSkill(skills, "zzznomatch")).toBeUndefined();
|
|
120
154
|
});
|
|
121
155
|
});
|
|
156
|
+
describe("findPlaybook", () => {
|
|
157
|
+
let playbooks;
|
|
158
|
+
beforeAll(() => {
|
|
159
|
+
playbooks = loadPlaybooks(tempPlaybooksDir);
|
|
160
|
+
});
|
|
161
|
+
it("returns key for exact match", () => {
|
|
162
|
+
expect(findPlaybook(playbooks, "zendesk-jira")).toBe("zendesk-jira");
|
|
163
|
+
});
|
|
164
|
+
it("matches normalized input", () => {
|
|
165
|
+
expect(findPlaybook(playbooks, "zendesk jira")).toBe("zendesk-jira");
|
|
166
|
+
});
|
|
167
|
+
});
|
|
122
168
|
// ---------------------------------------------------------------------------
|
|
123
169
|
// extractSection
|
|
124
170
|
// ---------------------------------------------------------------------------
|
|
@@ -153,7 +199,7 @@ describe("searchSkills", () => {
|
|
|
153
199
|
it("returns results for a matching query", () => {
|
|
154
200
|
const results = searchSkills(skills, "Personal Access Token");
|
|
155
201
|
expect(results.length).toBeGreaterThan(0);
|
|
156
|
-
expect(results[0].
|
|
202
|
+
expect(results[0].name).toBe("github");
|
|
157
203
|
expect(results[0].excerpts.length).toBeGreaterThan(0);
|
|
158
204
|
});
|
|
159
205
|
it("is case-insensitive", () => {
|
|
@@ -172,6 +218,17 @@ describe("searchSkills", () => {
|
|
|
172
218
|
expect(results[0].excerpts.length).toBeLessThanOrEqual(5);
|
|
173
219
|
});
|
|
174
220
|
});
|
|
221
|
+
describe("searchPlaybooks", () => {
|
|
222
|
+
let playbooks;
|
|
223
|
+
beforeAll(() => {
|
|
224
|
+
playbooks = loadPlaybooks(tempPlaybooksDir);
|
|
225
|
+
});
|
|
226
|
+
it("returns results for a matching query", () => {
|
|
227
|
+
const results = searchPlaybooks(playbooks, "idempotency");
|
|
228
|
+
expect(results.length).toBeGreaterThan(0);
|
|
229
|
+
expect(results[0].name).toBe("zendesk-jira");
|
|
230
|
+
});
|
|
231
|
+
});
|
|
175
232
|
// ---------------------------------------------------------------------------
|
|
176
233
|
// skillSummary
|
|
177
234
|
// ---------------------------------------------------------------------------
|
|
@@ -234,3 +291,25 @@ describe("real skills", () => {
|
|
|
234
291
|
}
|
|
235
292
|
});
|
|
236
293
|
});
|
|
294
|
+
describe("real playbooks", () => {
|
|
295
|
+
let playbooks;
|
|
296
|
+
beforeAll(() => {
|
|
297
|
+
playbooks = loadPlaybooks(REAL_PLAYBOOKS_DIR);
|
|
298
|
+
});
|
|
299
|
+
it("loads all expected playbooks", () => {
|
|
300
|
+
const knownPlaybooks = [
|
|
301
|
+
"hubspot-asana-onboarding",
|
|
302
|
+
"salesforce-hubspot-lead-sync",
|
|
303
|
+
"zendesk-jira-bug-escalation",
|
|
304
|
+
];
|
|
305
|
+
for (const slug of knownPlaybooks) {
|
|
306
|
+
expect(playbooks.has(slug), `Missing playbook: ${slug}`).toBe(true);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
it("every playbook has a non-empty summary line", () => {
|
|
310
|
+
for (const [slug, content] of playbooks.entries()) {
|
|
311
|
+
const summary = skillSummary(content);
|
|
312
|
+
expect(summary, `${slug}: empty summary`).not.toBe("");
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawskills-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "MCP server exposing ClawSkills API integration skill docs to AI agents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ClawSkills Contributors",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
32
|
"dist/",
|
|
33
|
-
"skills/"
|
|
33
|
+
"skills/",
|
|
34
|
+
"playbooks/"
|
|
34
35
|
],
|
|
35
36
|
"scripts": {
|
|
36
37
|
"sync:skills": "node scripts/sync-skills.mjs",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Playbooks Index
|
|
2
|
+
|
|
3
|
+
Workflow playbooks are end-to-end guides for multi-system automations. They complement the per-tool `skill.md` files by defining the orchestration layer: trigger, source of truth, field mapping, idempotency, failure policy, and operational checks.
|
|
4
|
+
|
|
5
|
+
## Available playbooks
|
|
6
|
+
|
|
7
|
+
| Playbook | Systems | Primary use case |
|
|
8
|
+
|----------|---------|------------------|
|
|
9
|
+
| [Zendesk Ticket -> Jira Bug escalation](./zendesk-jira-bug-escalation.md) | Zendesk, Jira | Escalate support-confirmed defects into engineering workflow |
|
|
10
|
+
| [HubSpot Deal Won -> Asana onboarding kickoff](./hubspot-asana-onboarding.md) | HubSpot, Asana | Start implementation or onboarding work when a deal closes |
|
|
11
|
+
| [Salesforce Lead -> HubSpot Contact sync](./salesforce-hubspot-lead-sync.md) | Salesforce, HubSpot | Mirror new/updated leads into marketing automation without duplicates |
|
|
12
|
+
|
|
13
|
+
## Playbook template
|
|
14
|
+
|
|
15
|
+
Each playbook should define:
|
|
16
|
+
- Business trigger
|
|
17
|
+
- Systems involved
|
|
18
|
+
- Source of truth
|
|
19
|
+
- Sequence of operations
|
|
20
|
+
- Field mapping
|
|
21
|
+
- Idempotency and dedup keys
|
|
22
|
+
- Retry / partial-failure policy
|
|
23
|
+
- Reconciliation / rollback guidance
|
|
24
|
+
- Observability checks
|
|
25
|
+
|
|
26
|
+
## Next candidates
|
|
27
|
+
|
|
28
|
+
- Slack incident -> Jira issue
|
|
29
|
+
- GitHub PR -> Slack notification
|
|
30
|
+
- Zendesk -> Salesforce case sync
|
|
31
|
+
- Stripe payment failed -> HubSpot task
|
|
32
|
+
- Notion request -> Asana task
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# HubSpot Deal Won -> Asana Onboarding Kickoff
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Create onboarding or implementation work in Asana as soon as a HubSpot Deal is marked closed won.
|
|
6
|
+
|
|
7
|
+
## Systems involved
|
|
8
|
+
|
|
9
|
+
- HubSpot: sales pipeline, deal state, customer/account context
|
|
10
|
+
- Asana: delivery execution, onboarding tasks, project coordination
|
|
11
|
+
|
|
12
|
+
## Trigger
|
|
13
|
+
|
|
14
|
+
- HubSpot webhook `deal.propertyChange` where `dealstage` moves into the closed-won stage
|
|
15
|
+
|
|
16
|
+
## Source of truth
|
|
17
|
+
|
|
18
|
+
- Commercial state and customer/account metadata: HubSpot
|
|
19
|
+
- Delivery execution and task/project state: Asana
|
|
20
|
+
- Workflow linkage: integration database or HubSpot custom property storing the Asana `gid`
|
|
21
|
+
|
|
22
|
+
## Sequence
|
|
23
|
+
|
|
24
|
+
1. Receive the HubSpot webhook and verify it corresponds to the target pipeline/stage.
|
|
25
|
+
2. Fetch the Deal, associated Company, and primary Contact.
|
|
26
|
+
3. Check whether an Asana task/project already exists for the Deal ID.
|
|
27
|
+
4. Create the onboarding project or kickoff task in Asana.
|
|
28
|
+
5. Populate notes and custom fields with customer, owner, ARR, plan, and target launch details.
|
|
29
|
+
6. Persist the Asana `gid` back to HubSpot or your integration store.
|
|
30
|
+
7. Notify the implementation owner or CSM if required.
|
|
31
|
+
|
|
32
|
+
## Field mapping
|
|
33
|
+
|
|
34
|
+
| HubSpot | Asana |
|
|
35
|
+
|---------|-------|
|
|
36
|
+
| `dealId` | external mapping / custom field |
|
|
37
|
+
| `dealname` | task/project name |
|
|
38
|
+
| associated company name | notes / title suffix |
|
|
39
|
+
| deal owner | assignee |
|
|
40
|
+
| close date | kickoff anchor or due date |
|
|
41
|
+
| ARR / amount | custom field |
|
|
42
|
+
| onboarding tier | custom field / template choice |
|
|
43
|
+
|
|
44
|
+
## Idempotency
|
|
45
|
+
|
|
46
|
+
- Dedup key: HubSpot Deal ID
|
|
47
|
+
- Webhook delivery is at-least-once, so check your mapping store before creation
|
|
48
|
+
- If using project templates, ensure only one project is generated per Deal ID
|
|
49
|
+
|
|
50
|
+
## Retry and partial failure policy
|
|
51
|
+
|
|
52
|
+
- Retry transient Asana failures with bounded backoff
|
|
53
|
+
- If Asana create succeeds but write-back to HubSpot fails, recover by searching the mapping store on the next run
|
|
54
|
+
- Validation errors should move the record into a manual review queue with the Deal ID and failing field
|
|
55
|
+
|
|
56
|
+
## Reconciliation
|
|
57
|
+
|
|
58
|
+
- Daily job: compare all closed-won deals in the last 30 days against Asana kickoff artifacts
|
|
59
|
+
- Backfill missing Asana `gid` values into HubSpot
|
|
60
|
+
- Flag onboarding projects with no matching active deal
|
|
61
|
+
|
|
62
|
+
## Rollback guidance
|
|
63
|
+
|
|
64
|
+
- If a Deal reopens or is marked closed-lost shortly after creation, archive or cancel the Asana project/task rather than deleting it
|
|
65
|
+
- Preserve the linkage so repeated stage churn does not create duplicates
|
|
66
|
+
|
|
67
|
+
## Observability
|
|
68
|
+
|
|
69
|
+
- Count created onboarding projects/tasks per day
|
|
70
|
+
- Alert on webhook processing failures and duplicate prevention hits
|
|
71
|
+
- Track time from closed won to Asana kickoff creation
|
|
72
|
+
|
|
73
|
+
## Related skills
|
|
74
|
+
|
|
75
|
+
- [`skills/hubspot/skill.md`](../skills/hubspot/skill.md)
|
|
76
|
+
- [`skills/asana/skill.md`](../skills/asana/skill.md)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Salesforce Lead -> HubSpot Contact Sync
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Mirror new or updated Salesforce Leads into HubSpot Contacts so marketing and lifecycle workflows stay current without creating duplicates.
|
|
6
|
+
|
|
7
|
+
## Systems involved
|
|
8
|
+
|
|
9
|
+
- Salesforce: CRM of record for lead capture and sales qualification
|
|
10
|
+
- HubSpot: marketing automation, nurture, lifecycle segmentation
|
|
11
|
+
|
|
12
|
+
## Trigger
|
|
13
|
+
|
|
14
|
+
- Scheduled incremental sync using Salesforce `LastModifiedDate`
|
|
15
|
+
- Optional near-real-time trigger from Salesforce CDC or platform events
|
|
16
|
+
|
|
17
|
+
## Source of truth
|
|
18
|
+
|
|
19
|
+
- Lead identity and sales-owned fields: Salesforce
|
|
20
|
+
- Marketing engagement and automation state: HubSpot
|
|
21
|
+
- Identity dedup anchor: email, with explicit mapping store as a secondary safeguard
|
|
22
|
+
|
|
23
|
+
## Sequence
|
|
24
|
+
|
|
25
|
+
1. Query Salesforce Leads modified since the last sync watermark, with a small overlap window.
|
|
26
|
+
2. Skip converted Leads and Leads with no email.
|
|
27
|
+
3. Search HubSpot Contacts by email.
|
|
28
|
+
4. If a Contact exists, update it with mapped fields.
|
|
29
|
+
5. If no Contact exists, create one and persist the mapping.
|
|
30
|
+
6. Record sync outcome per Lead so failed rows can be retried independently.
|
|
31
|
+
7. Advance the watermark only after all rows are processed or quarantined.
|
|
32
|
+
|
|
33
|
+
## Field mapping
|
|
34
|
+
|
|
35
|
+
| Salesforce Lead | HubSpot Contact |
|
|
36
|
+
|-----------------|-----------------|
|
|
37
|
+
| `Lead.Id` | custom property `salesforce_lead_id` or integration DB |
|
|
38
|
+
| `Email` | `email` |
|
|
39
|
+
| `FirstName` | `firstname` |
|
|
40
|
+
| `LastName` | `lastname` |
|
|
41
|
+
| `Company` | `company` |
|
|
42
|
+
| `Phone` | `phone` |
|
|
43
|
+
| `LeadSource` | custom source property |
|
|
44
|
+
| `Status` | derived `lifecyclestage` mapping |
|
|
45
|
+
|
|
46
|
+
## Idempotency
|
|
47
|
+
|
|
48
|
+
- Primary dedup key: normalized email
|
|
49
|
+
- Secondary correlation key: Salesforce Lead ID
|
|
50
|
+
- Do not use HubSpot batch create blindly for unknown contacts; search first
|
|
51
|
+
|
|
52
|
+
## Retry and partial failure policy
|
|
53
|
+
|
|
54
|
+
- Retry transient HubSpot rate-limit and availability errors per shard
|
|
55
|
+
- Quarantine `400` validation failures with Lead ID, email, and failing field names
|
|
56
|
+
- If one row fails, continue the rest of the batch and surface a remediation report
|
|
57
|
+
|
|
58
|
+
## Reconciliation
|
|
59
|
+
|
|
60
|
+
- Daily compare: Salesforce Leads updated in the last 24 hours vs HubSpot Contacts carrying `salesforce_lead_id`
|
|
61
|
+
- Repair records where the ID mapping is missing but email matches
|
|
62
|
+
- Flag email collisions where multiple Salesforce Leads compete for one HubSpot Contact
|
|
63
|
+
|
|
64
|
+
## Rollback guidance
|
|
65
|
+
|
|
66
|
+
- Avoid destructive deletes across systems
|
|
67
|
+
- If a bad sync overwrites HubSpot fields, restore from HubSpot history or your sync audit log and re-run the corrected mapping
|
|
68
|
+
|
|
69
|
+
## Observability
|
|
70
|
+
|
|
71
|
+
- Track processed, created, updated, quarantined, and retried rows per run
|
|
72
|
+
- Alert on spikes in duplicate resolution or validation failures
|
|
73
|
+
- Monitor watermark lag so the sync does not silently fall behind
|
|
74
|
+
|
|
75
|
+
## Related skills
|
|
76
|
+
|
|
77
|
+
- [`skills/salesforce/skill.md`](../skills/salesforce/skill.md)
|
|
78
|
+
- [`skills/hubspot/skill.md`](../skills/hubspot/skill.md)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Zendesk Ticket -> Jira Bug Escalation
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Escalate a Zendesk support ticket into Jira when support determines the issue is a product defect that engineering must track and resolve.
|
|
6
|
+
|
|
7
|
+
## Systems involved
|
|
8
|
+
|
|
9
|
+
- Zendesk: intake, customer communication, support triage
|
|
10
|
+
- Jira: engineering backlog, bug lifecycle, fix tracking
|
|
11
|
+
|
|
12
|
+
## Trigger
|
|
13
|
+
|
|
14
|
+
Any of:
|
|
15
|
+
- Zendesk ticket gets tag `engineering`
|
|
16
|
+
- Custom field `issue_type` changes to `bug`
|
|
17
|
+
- Ticket priority becomes `urgent` and product area is set
|
|
18
|
+
|
|
19
|
+
## Source of truth
|
|
20
|
+
|
|
21
|
+
- Customer communication and ticket state: Zendesk
|
|
22
|
+
- Engineering execution state: Jira
|
|
23
|
+
- Escalation linkage: integration database or mirrored external ID field
|
|
24
|
+
|
|
25
|
+
## Sequence
|
|
26
|
+
|
|
27
|
+
1. Receive the Zendesk webhook or consume the ticket via Incremental Export.
|
|
28
|
+
2. Build a deterministic correlation key: `zendesk:{ticket_id}`.
|
|
29
|
+
3. Search Jira for an existing issue with that correlation key in a custom field or label.
|
|
30
|
+
4. If no issue exists, create a Jira Bug with summary, reproduction notes, impact, and Zendesk URL.
|
|
31
|
+
5. If an issue already exists, update priority, labels, assignee, and current reproduction context.
|
|
32
|
+
6. Write the Jira issue key back to Zendesk as an internal note or custom field.
|
|
33
|
+
7. Optionally mirror selected Jira status changes back to Zendesk after state mapping is finalized.
|
|
34
|
+
|
|
35
|
+
## Field mapping
|
|
36
|
+
|
|
37
|
+
| Zendesk | Jira |
|
|
38
|
+
|---------|------|
|
|
39
|
+
| `ticket.id` | custom external ID / label |
|
|
40
|
+
| `subject` | `summary` |
|
|
41
|
+
| latest public comment | `description` |
|
|
42
|
+
| `priority` | `priority` |
|
|
43
|
+
| `tags` | `labels` |
|
|
44
|
+
| requester org / account | custom customer field or component |
|
|
45
|
+
| product area custom field | component / label |
|
|
46
|
+
|
|
47
|
+
## Idempotency
|
|
48
|
+
|
|
49
|
+
- Dedup key: `zendesk:{ticket_id}`
|
|
50
|
+
- Before create, always search Jira by dedup key
|
|
51
|
+
- Zendesk webhooks are at-least-once delivery, so the create path must be safe to replay
|
|
52
|
+
|
|
53
|
+
## Retry and partial failure policy
|
|
54
|
+
|
|
55
|
+
- Retry transient Jira failures (`429`, `503`) with bounded exponential backoff
|
|
56
|
+
- Do not retry `400` field validation errors blindly; quarantine and surface them
|
|
57
|
+
- If Jira create succeeds but Zendesk write-back fails, reconcile by searching Jira with the dedup key on the next run
|
|
58
|
+
|
|
59
|
+
## Reconciliation
|
|
60
|
+
|
|
61
|
+
- Daily job: compare Zendesk tickets tagged `engineering` against Jira bugs with the dedup key
|
|
62
|
+
- Repair missing backlinks in Zendesk
|
|
63
|
+
- Flag Jira issues missing an active Zendesk ticket counterpart
|
|
64
|
+
|
|
65
|
+
## Rollback guidance
|
|
66
|
+
|
|
67
|
+
- If a Jira issue was created in error, transition it to a closed/cancelled state instead of deleting it
|
|
68
|
+
- Remove the escalation tag or set a custom field in Zendesk to prevent automatic recreation
|
|
69
|
+
|
|
70
|
+
## Observability
|
|
71
|
+
|
|
72
|
+
- Count escalations per day
|
|
73
|
+
- Alert on create failures, write-back failures, or rising duplicate-detection rates
|
|
74
|
+
- Track mean time from Zendesk escalation to Jira issue creation
|
|
75
|
+
|
|
76
|
+
## Related skills
|
|
77
|
+
|
|
78
|
+
- [`skills/zendesk/skill.md`](../skills/zendesk/skill.md)
|
|
79
|
+
- [`skills/jira/skill.md`](../skills/jira/skill.md)
|
package/skills/INDEX.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
A curated library of integration skill documents for the most common SaaS platforms used in go-to-market and operations workflows. Each skill doc teaches an agent or automation builder how to reliably use that platform's API for high-value, production-grade workflows.
|
|
4
4
|
|
|
5
5
|
→ See [ROADMAP.md](./ROADMAP.md) for the prioritized build plan, phased milestones, and governance model.
|
|
6
|
+
→ Cross-tool workflow guides now live in [`../playbooks/INDEX.md`](../playbooks/INDEX.md).
|
|
6
7
|
|
|
7
8
|
---
|
|
8
9
|
|
package/skills/ROADMAP.md
CHANGED
|
@@ -12,14 +12,14 @@ This skill set enables end-to-end automation and integration across the most com
|
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
-
## Current state (as of 2026-03-
|
|
15
|
+
## Current state (as of 2026-03-12)
|
|
16
16
|
|
|
17
17
|
### What's shipped
|
|
18
18
|
|
|
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.4.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.4.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 |
|
|
@@ -108,6 +108,43 @@ Each recipe lives as a `## Cross-tool recipe` appendix section in the relevant p
|
|
|
108
108
|
|
|
109
109
|
---
|
|
110
110
|
|
|
111
|
+
### Priority 5 — Workflow playbooks and retrieval UX
|
|
112
|
+
|
|
113
|
+
The next product layer is workflow-first guidance, not only tool-by-tool references. This means shipping opinionated playbooks that show how multiple systems fit together operationally, then exposing them cleanly through MCP.
|
|
114
|
+
|
|
115
|
+
**Backlog:**
|
|
116
|
+
- Add a `playbooks/` directory with high-value cross-tool workflows
|
|
117
|
+
- Ship 3 flagship playbooks first:
|
|
118
|
+
- Zendesk Ticket -> Jira Bug escalation
|
|
119
|
+
- HubSpot Deal Won -> Asana onboarding kickoff
|
|
120
|
+
- Salesforce Lead -> HubSpot Contact sync
|
|
121
|
+
- Expand to additional high-frequency workflows:
|
|
122
|
+
- Slack incident -> Jira issue
|
|
123
|
+
- GitHub PR -> Slack notification
|
|
124
|
+
- Zendesk -> Salesforce case sync
|
|
125
|
+
- Stripe payment failed -> HubSpot task
|
|
126
|
+
- Notion request -> Asana task
|
|
127
|
+
- Standardize each playbook format:
|
|
128
|
+
- business trigger
|
|
129
|
+
- systems involved
|
|
130
|
+
- source-of-truth decision
|
|
131
|
+
- field mapping
|
|
132
|
+
- idempotency strategy
|
|
133
|
+
- retry and partial-failure policy
|
|
134
|
+
- reconciliation and rollback guidance
|
|
135
|
+
- observability / alerting checks
|
|
136
|
+
- Expose playbooks through MCP after the doc layer stabilizes:
|
|
137
|
+
- `list_playbooks`
|
|
138
|
+
- `get_playbook`
|
|
139
|
+
- search/ranking that prefers recipes and playbooks over generic mentions
|
|
140
|
+
|
|
141
|
+
**Acceptance criteria:**
|
|
142
|
+
- [ ] At least 3 production-grade playbooks checked into the repo
|
|
143
|
+
- [ ] README and INDEX reference the playbooks layer
|
|
144
|
+
- [ ] MCP server plan defined for playbook retrieval and discovery
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
111
148
|
## Prioritization framework
|
|
112
149
|
|
|
113
150
|
Each skill/recipe is scored on five dimensions (1–5 each):
|