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.
@@ -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
- export {};
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
- const __dirname = dirname(fileURLToPath(import.meta.url));
13
- // Load bundled content (generated by scripts/bundle-content.cjs)
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, "&lt;")
29
+ .replace(/>/g, "&gt;")
30
+ .replace(/"/g, "&quot;")
31
+ .replace(/'/g, "&#39;");
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
- throw new Error("content.json not found. Run 'npm run build' from mcp-server to bundle Skills and Subagents.");
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(readFileSync(contentPath, "utf8"));
54
+ return JSON.parse(raw);
20
55
  }
21
- const DATA = loadContent();
22
56
  const RESOURCE_PREFIX = "general-coding-tools-mcp://";
23
- const server = new McpServer({
24
- name: "general-coding-tools-mcp",
25
- version: "1.0.0",
26
- });
27
- // --- Resources: one per skill, skill/reference, and subagent ---
28
- for (const s of DATA.skills) {
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
- if (DATA.content.skills[s.name]?.reference) {
41
- const refUri = `${RESOURCE_PREFIX}skill/${s.id}/reference`;
42
- server.registerResource(`skill-${s.id}-reference`, refUri, {
43
- title: `Skill reference: ${s.name}`,
44
- description: `Reference material for skill ${s.name}`,
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: refUri, mimeType: "text/markdown", text: entry.reference }],
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
- for (const a of DATA.subagents) {
55
- const uri = `${RESOURCE_PREFIX}subagent/${a.id}`;
56
- server.registerResource(`subagent-${a.id}`, uri, {
57
- title: `Subagent: ${a.name}`,
58
- description: `General Coding Tools subagent: ${a.name}`,
59
- mimeType: "text/markdown",
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 entry = DATA.content.subagents[a.name];
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
- // --- Tools ---
68
- server.registerTool("list_skills", {
69
- title: "List skills",
70
- description: "List all available General Coding Tools skills (e.g. systematic-debugging, correctness-audit).",
71
- inputSchema: z.object({}),
72
- }, async () => {
73
- const list = DATA.skills.map((s) => ({ id: s.id, name: s.name, hasReference: s.hasReference }));
74
- return { content: [{ type: "text", text: JSON.stringify(list, null, 2) }] };
75
- });
76
- server.registerTool("list_subagents", {
77
- title: "List subagents",
78
- description: "List all available General Coding Tools subagents (e.g. deep-research, update-docs, verifier).",
79
- inputSchema: z.object({}),
80
- }, async () => {
81
- const list = DATA.subagents.map((a) => ({ id: a.id, name: a.name }));
82
- return { content: [{ type: "text", text: JSON.stringify(list, null, 2) }] };
83
- });
84
- server.registerTool("get_skill", {
85
- title: "Get skill content",
86
- description: "Get the full content of a skill by name (id). Use list_skills to see available names.",
87
- inputSchema: z.object({
88
- name: z.string().describe("Skill id (e.g. systematic-debugging, correctness-audit)"),
89
- include_reference: z.boolean().optional().default(false).describe("Include REFERENCE.md if present"),
90
- }),
91
- }, async ({ name, include_reference }) => {
92
- const skill = DATA.skills.find((s) => s.id === name || s.name === name);
93
- if (!skill) {
94
- return {
95
- content: [{ type: "text", text: `Unknown skill: ${name}. Use list_skills to see available skills.` }],
96
- isError: true,
97
- };
98
- }
99
- const entry = DATA.content.skills[skill.name];
100
- let text = entry.content;
101
- if (include_reference && entry.reference) {
102
- text += "\n\n---\n\n## Reference\n\n" + entry.reference;
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
- return { content: [{ type: "text", text }] };
105
- });
106
- server.registerTool("get_subagent", {
107
- title: "Get subagent content",
108
- description: "Get the full content of a subagent by name (id). Use list_subagents to see available names.",
109
- inputSchema: z.object({
110
- name: z.string().describe("Subagent id (e.g. deep-research, update-docs, verifier)"),
111
- }),
112
- }, async ({ name }) => {
113
- const subagent = DATA.subagents.find((a) => a.id === name || a.name === name);
114
- if (!subagent) {
115
- return {
116
- content: [{ type: "text", text: `Unknown subagent: ${name}. Use list_subagents to see available subagents.` }],
117
- isError: true,
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
- const entry = DATA.content.subagents[subagent.name];
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 (const a of DATA.subagents) {
144
- const promptName = `apply_subagent_${a.id.replace(/-/g, "_")}`;
145
- server.registerPrompt(promptName, {
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
- // --- Run ---
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.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": ["mcp", "cursor", "smithery", "skills", "general-coding-tools"],
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",