general-coding-tools-mcp 1.0.0 → 1.0.2
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/content.json.sha256 +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +179 -131
- package/package.json +8 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3dd6626a96cf9447752a73c7ba0ce2c8fa696e640c3f55ebefb41e6788781af1
|
package/dist/index.d.ts
CHANGED
|
@@ -3,4 +3,7 @@
|
|
|
3
3
|
* General Coding Tools MCP Server
|
|
4
4
|
* Exposes skills and subagents as MCP resources and tools for use in Cursor, Claude, and Smithery.
|
|
5
5
|
*/
|
|
6
|
-
|
|
6
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
+
/** Returns a server instance for Smithery capability scanning. Loads real content when dist/content.json exists (e.g. when scan runs from project dir), otherwise uses empty content so the scan still succeeds. */
|
|
8
|
+
export declare function createSandboxServer(): McpServer;
|
|
9
|
+
export default createSandboxServer;
|
package/dist/index.js
CHANGED
|
@@ -7,159 +7,207 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
7
7
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
import { readFileSync, existsSync } from "fs";
|
|
10
|
+
import { createHash } from "crypto";
|
|
10
11
|
import { fileURLToPath } from "url";
|
|
11
12
|
import { dirname, join } from "path";
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
// Support both ESM (import.meta.url) and CJS bundles (e.g. Smithery stdio scan) where it may be undefined
|
|
14
|
+
function getContentDir() {
|
|
15
|
+
if (typeof import.meta !== "undefined" && import.meta.url) {
|
|
16
|
+
return dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
}
|
|
18
|
+
if (typeof process !== "undefined" && process.cwd) {
|
|
19
|
+
return join(process.cwd(), "dist");
|
|
20
|
+
}
|
|
21
|
+
return ".";
|
|
22
|
+
}
|
|
23
|
+
const __dirname = getContentDir();
|
|
24
|
+
/** Escape HTML special chars so embedded user input cannot inject script if client renders as HTML. */
|
|
25
|
+
function escapeForEmbedding(s) {
|
|
26
|
+
return s
|
|
27
|
+
.replace(/&/g, "&")
|
|
28
|
+
.replace(/</g, "<")
|
|
29
|
+
.replace(/>/g, ">")
|
|
30
|
+
.replace(/"/g, """)
|
|
31
|
+
.replace(/'/g, "'");
|
|
32
|
+
}
|
|
33
|
+
const EMPTY_CONTENT = {
|
|
34
|
+
skills: [],
|
|
35
|
+
subagents: [],
|
|
36
|
+
content: { skills: {}, subagents: {} },
|
|
37
|
+
};
|
|
38
|
+
// Load bundled content (generated by scripts/bundle-content.cjs); verify integrity via SHA-256
|
|
14
39
|
function loadContent() {
|
|
15
40
|
const contentPath = join(__dirname, "content.json");
|
|
41
|
+
const hashPath = join(__dirname, "content.json.sha256");
|
|
16
42
|
if (!existsSync(contentPath)) {
|
|
17
|
-
|
|
43
|
+
// Allow running without content.json for capability scanning (e.g. Smithery build)
|
|
44
|
+
return EMPTY_CONTENT;
|
|
45
|
+
}
|
|
46
|
+
const raw = readFileSync(contentPath, "utf8");
|
|
47
|
+
if (existsSync(hashPath)) {
|
|
48
|
+
const expectedHash = readFileSync(hashPath, "utf8").trim();
|
|
49
|
+
const actualHash = createHash("sha256").update(raw, "utf8").digest("hex");
|
|
50
|
+
if (expectedHash !== actualHash) {
|
|
51
|
+
throw new Error("content.json integrity check failed (hash mismatch). Rebuild with 'npm run build' or ensure the file was not modified.");
|
|
52
|
+
}
|
|
18
53
|
}
|
|
19
|
-
return JSON.parse(
|
|
54
|
+
return JSON.parse(raw);
|
|
20
55
|
}
|
|
21
|
-
const DATA = loadContent();
|
|
22
56
|
const RESOURCE_PREFIX = "general-coding-tools-mcp://";
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const uri = `${RESOURCE_PREFIX}skill/${s.id}`;
|
|
30
|
-
server.registerResource(`skill-${s.id}`, uri, {
|
|
31
|
-
title: `Skill: ${s.name}`,
|
|
32
|
-
description: `General Coding Tools skill: ${s.name}`,
|
|
33
|
-
mimeType: "text/markdown",
|
|
34
|
-
}, async () => {
|
|
35
|
-
const entry = DATA.content.skills[s.name];
|
|
36
|
-
return {
|
|
37
|
-
contents: [{ uri, mimeType: "text/markdown", text: entry.content }],
|
|
38
|
-
};
|
|
57
|
+
/** Create an MCP server with the given content (or load from disk if not provided). Used for normal run and for Smithery createSandboxServer. */
|
|
58
|
+
function createServer(contentOverride) {
|
|
59
|
+
const DATA = contentOverride ?? loadContent();
|
|
60
|
+
const server = new McpServer({
|
|
61
|
+
name: "general-coding-tools-mcp",
|
|
62
|
+
version: "1.0.0",
|
|
39
63
|
});
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
64
|
+
// --- Resources: one per skill, skill/reference, and subagent ---
|
|
65
|
+
for (const s of DATA.skills) {
|
|
66
|
+
const uri = `${RESOURCE_PREFIX}skill/${s.id}`;
|
|
67
|
+
server.registerResource(`skill-${s.id}`, uri, {
|
|
68
|
+
title: `Skill: ${s.name}`,
|
|
69
|
+
description: `General Coding Tools skill: ${s.name}`,
|
|
45
70
|
mimeType: "text/markdown",
|
|
46
71
|
}, async () => {
|
|
47
72
|
const entry = DATA.content.skills[s.name];
|
|
48
73
|
return {
|
|
49
|
-
contents: [{ uri
|
|
74
|
+
contents: [{ uri, mimeType: "text/markdown", text: entry.content }],
|
|
50
75
|
};
|
|
51
76
|
});
|
|
77
|
+
if (DATA.content.skills[s.name]?.reference) {
|
|
78
|
+
const refUri = `${RESOURCE_PREFIX}skill/${s.id}/reference`;
|
|
79
|
+
server.registerResource(`skill-${s.id}-reference`, refUri, {
|
|
80
|
+
title: `Skill reference: ${s.name}`,
|
|
81
|
+
description: `Reference material for skill ${s.name}`,
|
|
82
|
+
mimeType: "text/markdown",
|
|
83
|
+
}, async () => {
|
|
84
|
+
const entry = DATA.content.skills[s.name];
|
|
85
|
+
return {
|
|
86
|
+
contents: [{ uri: refUri, mimeType: "text/markdown", text: entry.reference }],
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
}
|
|
52
90
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
91
|
+
for (const a of DATA.subagents) {
|
|
92
|
+
const uri = `${RESOURCE_PREFIX}subagent/${a.id}`;
|
|
93
|
+
server.registerResource(`subagent-${a.id}`, uri, {
|
|
94
|
+
title: `Subagent: ${a.name}`,
|
|
95
|
+
description: `General Coding Tools subagent: ${a.name}`,
|
|
96
|
+
mimeType: "text/markdown",
|
|
97
|
+
}, async () => {
|
|
98
|
+
const entry = DATA.content.subagents[a.name];
|
|
99
|
+
return {
|
|
100
|
+
contents: [{ uri, mimeType: "text/markdown", text: entry.content }],
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
// --- Tools ---
|
|
105
|
+
server.registerTool("list_skills", {
|
|
106
|
+
title: "List skills",
|
|
107
|
+
description: "List all available General Coding Tools skills (e.g. systematic-debugging, correctness-audit).",
|
|
108
|
+
inputSchema: z.object({}),
|
|
60
109
|
}, async () => {
|
|
61
|
-
const
|
|
62
|
-
return {
|
|
63
|
-
contents: [{ uri, mimeType: "text/markdown", text: entry.content }],
|
|
64
|
-
};
|
|
110
|
+
const list = DATA.skills.map((s) => ({ id: s.id, name: s.name, hasReference: s.hasReference }));
|
|
111
|
+
return { content: [{ type: "text", text: JSON.stringify(list, null, 2) }] };
|
|
65
112
|
});
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
},
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
113
|
+
server.registerTool("list_subagents", {
|
|
114
|
+
title: "List subagents",
|
|
115
|
+
description: "List all available General Coding Tools subagents (e.g. deep-research, update-docs, verifier).",
|
|
116
|
+
inputSchema: z.object({}),
|
|
117
|
+
}, async () => {
|
|
118
|
+
const list = DATA.subagents.map((a) => ({ id: a.id, name: a.name }));
|
|
119
|
+
return { content: [{ type: "text", text: JSON.stringify(list, null, 2) }] };
|
|
120
|
+
});
|
|
121
|
+
server.registerTool("get_skill", {
|
|
122
|
+
title: "Get skill content",
|
|
123
|
+
description: "Get the full content of a skill by name (id). Use list_skills to see available names.",
|
|
124
|
+
inputSchema: z.object({
|
|
125
|
+
name: z.string().max(200).describe("Skill id (e.g. systematic-debugging, correctness-audit)"),
|
|
126
|
+
include_reference: z.boolean().optional().default(false).describe("Include REFERENCE.md if present"),
|
|
127
|
+
}),
|
|
128
|
+
}, async ({ name, include_reference }) => {
|
|
129
|
+
const skill = DATA.skills.find((s) => s.id === name || s.name === name);
|
|
130
|
+
if (!skill) {
|
|
131
|
+
return {
|
|
132
|
+
content: [{ type: "text", text: `Unknown skill: ${escapeForEmbedding(name)}. Use list_skills to see available skills.` }],
|
|
133
|
+
isError: true,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const entry = DATA.content.skills[skill.name];
|
|
137
|
+
let text = entry.content;
|
|
138
|
+
if (include_reference && entry.reference) {
|
|
139
|
+
text += "\n\n---\n\n## Reference\n\n" + entry.reference;
|
|
140
|
+
}
|
|
141
|
+
return { content: [{ type: "text", text }] };
|
|
142
|
+
});
|
|
143
|
+
server.registerTool("get_subagent", {
|
|
144
|
+
title: "Get subagent content",
|
|
145
|
+
description: "Get the full content of a subagent by name (id). Use list_subagents to see available names.",
|
|
146
|
+
inputSchema: z.object({
|
|
147
|
+
name: z.string().max(200).describe("Subagent id (e.g. deep-research, update-docs, verifier)"),
|
|
148
|
+
}),
|
|
149
|
+
}, async ({ name }) => {
|
|
150
|
+
const subagent = DATA.subagents.find((a) => a.id === name || a.name === name);
|
|
151
|
+
if (!subagent) {
|
|
152
|
+
return {
|
|
153
|
+
content: [{ type: "text", text: `Unknown subagent: ${escapeForEmbedding(name)}. Use list_subagents to see available subagents.` }],
|
|
154
|
+
isError: true,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const entry = DATA.content.subagents[subagent.name];
|
|
158
|
+
return { content: [{ type: "text", text: entry.content }] };
|
|
159
|
+
});
|
|
160
|
+
// --- Prompts: apply skill / subagent with user message ---
|
|
161
|
+
for (const s of DATA.skills) {
|
|
162
|
+
const promptName = `apply_skill_${s.id.replace(/-/g, "_")}`;
|
|
163
|
+
server.registerPrompt(promptName, {
|
|
164
|
+
title: `Apply skill: ${s.name}`,
|
|
165
|
+
description: `Apply the "${s.name}" skill. Use when the user wants to follow this skill's process.`,
|
|
166
|
+
argsSchema: {
|
|
167
|
+
user_message: z.string().describe("What the user asked or the current task"),
|
|
168
|
+
},
|
|
169
|
+
}, async ({ user_message }) => {
|
|
170
|
+
const entry = DATA.content.skills[s.name];
|
|
171
|
+
const safeMessage = escapeForEmbedding(String(user_message ?? "(no message provided)"));
|
|
172
|
+
const text = `I will follow the **${s.name}** skill.\n\n---\n\n${entry.content}\n\n---\n\nUser request: ${safeMessage}`;
|
|
173
|
+
return {
|
|
174
|
+
messages: [
|
|
175
|
+
{ role: "user", content: { type: "text", text: String(user_message ?? "") } },
|
|
176
|
+
{ role: "assistant", content: { type: "text", text } },
|
|
177
|
+
],
|
|
178
|
+
};
|
|
179
|
+
});
|
|
103
180
|
}
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
server.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}, async ({
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
181
|
+
for (const a of DATA.subagents) {
|
|
182
|
+
const promptName = `apply_subagent_${a.id.replace(/-/g, "_")}`;
|
|
183
|
+
server.registerPrompt(promptName, {
|
|
184
|
+
title: `Apply subagent: ${a.name}`,
|
|
185
|
+
description: `Apply the "${a.name}" subagent. Use when the user wants this agent's behavior.`,
|
|
186
|
+
argsSchema: {
|
|
187
|
+
user_message: z.string().describe("What the user asked or the current task"),
|
|
188
|
+
},
|
|
189
|
+
}, async ({ user_message }) => {
|
|
190
|
+
const entry = DATA.content.subagents[a.name];
|
|
191
|
+
const safeMessage = escapeForEmbedding(String(user_message ?? "(no message provided)"));
|
|
192
|
+
const text = `I will follow the **${a.name}** subagent.\n\n---\n\n${entry.content}\n\n---\n\nUser request: ${safeMessage}`;
|
|
193
|
+
return {
|
|
194
|
+
messages: [
|
|
195
|
+
{ role: "user", content: { type: "text", text: String(user_message ?? "") } },
|
|
196
|
+
{ role: "assistant", content: { type: "text", text } },
|
|
197
|
+
],
|
|
198
|
+
};
|
|
199
|
+
});
|
|
119
200
|
}
|
|
120
|
-
|
|
121
|
-
return { content: [{ type: "text", text: entry.content }] };
|
|
122
|
-
});
|
|
123
|
-
// --- Prompts: apply skill / subagent with user message ---
|
|
124
|
-
for (const s of DATA.skills) {
|
|
125
|
-
const promptName = `apply_skill_${s.id.replace(/-/g, "_")}`;
|
|
126
|
-
server.registerPrompt(promptName, {
|
|
127
|
-
title: `Apply skill: ${s.name}`,
|
|
128
|
-
description: `Apply the "${s.name}" skill. Use when the user wants to follow this skill's process.`,
|
|
129
|
-
argsSchema: {
|
|
130
|
-
user_message: z.string().describe("What the user asked or the current task"),
|
|
131
|
-
},
|
|
132
|
-
}, async ({ user_message }) => {
|
|
133
|
-
const entry = DATA.content.skills[s.name];
|
|
134
|
-
const text = `I will follow the **${s.name}** skill.\n\n---\n\n${entry.content}\n\n---\n\nUser request: ${user_message ?? "(no message provided)"}`;
|
|
135
|
-
return {
|
|
136
|
-
messages: [
|
|
137
|
-
{ role: "user", content: { type: "text", text: String(user_message ?? "") } },
|
|
138
|
-
{ role: "assistant", content: { type: "text", text } },
|
|
139
|
-
],
|
|
140
|
-
};
|
|
141
|
-
});
|
|
201
|
+
return server;
|
|
142
202
|
}
|
|
143
|
-
for (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
title: `Apply subagent: ${a.name}`,
|
|
147
|
-
description: `Apply the "${a.name}" subagent. Use when the user wants this agent's behavior.`,
|
|
148
|
-
argsSchema: {
|
|
149
|
-
user_message: z.string().describe("What the user asked or the current task"),
|
|
150
|
-
},
|
|
151
|
-
}, async ({ user_message }) => {
|
|
152
|
-
const entry = DATA.content.subagents[a.name];
|
|
153
|
-
const text = `I will follow the **${a.name}** subagent.\n\n---\n\n${entry.content}\n\n---\n\nUser request: ${user_message ?? "(no message provided)"}`;
|
|
154
|
-
return {
|
|
155
|
-
messages: [
|
|
156
|
-
{ role: "user", content: { type: "text", text: String(user_message ?? "") } },
|
|
157
|
-
{ role: "assistant", content: { type: "text", text } },
|
|
158
|
-
],
|
|
159
|
-
};
|
|
160
|
-
});
|
|
203
|
+
/** Returns a server instance for Smithery capability scanning. Loads real content when dist/content.json exists (e.g. when scan runs from project dir), otherwise uses empty content so the scan still succeeds. */
|
|
204
|
+
export function createSandboxServer() {
|
|
205
|
+
return createServer();
|
|
161
206
|
}
|
|
162
|
-
//
|
|
207
|
+
// Default export for Smithery CLI (expects default to be a function that returns the server)
|
|
208
|
+
export default createSandboxServer;
|
|
209
|
+
// --- Run when executed as main ---
|
|
210
|
+
const server = createServer();
|
|
163
211
|
async function main() {
|
|
164
212
|
const transport = new StdioServerTransport();
|
|
165
213
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "general-coding-tools-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "MCP server exposing General Coding Tools (skills and subagents) for use in Cursor, Claude, and Smithery",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -19,7 +19,13 @@
|
|
|
19
19
|
"engines": {
|
|
20
20
|
"node": ">=18"
|
|
21
21
|
},
|
|
22
|
-
"keywords": [
|
|
22
|
+
"keywords": [
|
|
23
|
+
"mcp",
|
|
24
|
+
"cursor",
|
|
25
|
+
"smithery",
|
|
26
|
+
"skills",
|
|
27
|
+
"general-coding-tools"
|
|
28
|
+
],
|
|
23
29
|
"license": "MIT",
|
|
24
30
|
"dependencies": {
|
|
25
31
|
"@modelcontextprotocol/sdk": "^1.26.0",
|