general-coding-tools-mcp 1.0.0 → 1.0.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.
- package/dist/content.json.sha256 +1 -0
- package/dist/index.js +29 -8
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3dd6626a96cf9447752a73c7ba0ce2c8fa696e640c3f55ebefb41e6788781af1
|
package/dist/index.js
CHANGED
|
@@ -7,16 +7,35 @@ 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
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
|
|
14
|
+
/** Escape HTML special chars so embedded user input cannot inject script if client renders as HTML. */
|
|
15
|
+
function escapeForEmbedding(s) {
|
|
16
|
+
return s
|
|
17
|
+
.replace(/&/g, "&")
|
|
18
|
+
.replace(/</g, "<")
|
|
19
|
+
.replace(/>/g, ">")
|
|
20
|
+
.replace(/"/g, """)
|
|
21
|
+
.replace(/'/g, "'");
|
|
22
|
+
}
|
|
23
|
+
// Load bundled content (generated by scripts/bundle-content.cjs); verify integrity via SHA-256
|
|
14
24
|
function loadContent() {
|
|
15
25
|
const contentPath = join(__dirname, "content.json");
|
|
26
|
+
const hashPath = join(__dirname, "content.json.sha256");
|
|
16
27
|
if (!existsSync(contentPath)) {
|
|
17
28
|
throw new Error("content.json not found. Run 'npm run build' from mcp-server to bundle Skills and Subagents.");
|
|
18
29
|
}
|
|
19
|
-
|
|
30
|
+
const raw = readFileSync(contentPath, "utf8");
|
|
31
|
+
if (existsSync(hashPath)) {
|
|
32
|
+
const expectedHash = readFileSync(hashPath, "utf8").trim();
|
|
33
|
+
const actualHash = createHash("sha256").update(raw, "utf8").digest("hex");
|
|
34
|
+
if (expectedHash !== actualHash) {
|
|
35
|
+
throw new Error("content.json integrity check failed (hash mismatch). Rebuild with 'npm run build' or ensure the file was not modified.");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return JSON.parse(raw);
|
|
20
39
|
}
|
|
21
40
|
const DATA = loadContent();
|
|
22
41
|
const RESOURCE_PREFIX = "general-coding-tools-mcp://";
|
|
@@ -85,14 +104,14 @@ server.registerTool("get_skill", {
|
|
|
85
104
|
title: "Get skill content",
|
|
86
105
|
description: "Get the full content of a skill by name (id). Use list_skills to see available names.",
|
|
87
106
|
inputSchema: z.object({
|
|
88
|
-
name: z.string().describe("Skill id (e.g. systematic-debugging, correctness-audit)"),
|
|
107
|
+
name: z.string().max(200).describe("Skill id (e.g. systematic-debugging, correctness-audit)"),
|
|
89
108
|
include_reference: z.boolean().optional().default(false).describe("Include REFERENCE.md if present"),
|
|
90
109
|
}),
|
|
91
110
|
}, async ({ name, include_reference }) => {
|
|
92
111
|
const skill = DATA.skills.find((s) => s.id === name || s.name === name);
|
|
93
112
|
if (!skill) {
|
|
94
113
|
return {
|
|
95
|
-
content: [{ type: "text", text: `Unknown skill: ${name}. Use list_skills to see available skills.` }],
|
|
114
|
+
content: [{ type: "text", text: `Unknown skill: ${escapeForEmbedding(name)}. Use list_skills to see available skills.` }],
|
|
96
115
|
isError: true,
|
|
97
116
|
};
|
|
98
117
|
}
|
|
@@ -107,13 +126,13 @@ server.registerTool("get_subagent", {
|
|
|
107
126
|
title: "Get subagent content",
|
|
108
127
|
description: "Get the full content of a subagent by name (id). Use list_subagents to see available names.",
|
|
109
128
|
inputSchema: z.object({
|
|
110
|
-
name: z.string().describe("Subagent id (e.g. deep-research, update-docs, verifier)"),
|
|
129
|
+
name: z.string().max(200).describe("Subagent id (e.g. deep-research, update-docs, verifier)"),
|
|
111
130
|
}),
|
|
112
131
|
}, async ({ name }) => {
|
|
113
132
|
const subagent = DATA.subagents.find((a) => a.id === name || a.name === name);
|
|
114
133
|
if (!subagent) {
|
|
115
134
|
return {
|
|
116
|
-
content: [{ type: "text", text: `Unknown subagent: ${name}. Use list_subagents to see available subagents.` }],
|
|
135
|
+
content: [{ type: "text", text: `Unknown subagent: ${escapeForEmbedding(name)}. Use list_subagents to see available subagents.` }],
|
|
117
136
|
isError: true,
|
|
118
137
|
};
|
|
119
138
|
}
|
|
@@ -131,7 +150,8 @@ for (const s of DATA.skills) {
|
|
|
131
150
|
},
|
|
132
151
|
}, async ({ user_message }) => {
|
|
133
152
|
const entry = DATA.content.skills[s.name];
|
|
134
|
-
const
|
|
153
|
+
const safeMessage = escapeForEmbedding(String(user_message ?? "(no message provided)"));
|
|
154
|
+
const text = `I will follow the **${s.name}** skill.\n\n---\n\n${entry.content}\n\n---\n\nUser request: ${safeMessage}`;
|
|
135
155
|
return {
|
|
136
156
|
messages: [
|
|
137
157
|
{ role: "user", content: { type: "text", text: String(user_message ?? "") } },
|
|
@@ -150,7 +170,8 @@ for (const a of DATA.subagents) {
|
|
|
150
170
|
},
|
|
151
171
|
}, async ({ user_message }) => {
|
|
152
172
|
const entry = DATA.content.subagents[a.name];
|
|
153
|
-
const
|
|
173
|
+
const safeMessage = escapeForEmbedding(String(user_message ?? "(no message provided)"));
|
|
174
|
+
const text = `I will follow the **${a.name}** subagent.\n\n---\n\n${entry.content}\n\n---\n\nUser request: ${safeMessage}`;
|
|
154
175
|
return {
|
|
155
176
|
messages: [
|
|
156
177
|
{ role: "user", content: { type: "text", text: String(user_message ?? "") } },
|
package/package.json
CHANGED