ccs-mcp-server 1.0.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.
Files changed (3) hide show
  1. package/README.md +108 -0
  2. package/package.json +42 -0
  3. package/src/index.js +426 -0
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # CCS Runtime Evidence MCP Server
2
+
3
+ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that brings **CCS runtime verification** to any MCP-compatible client — Claude Desktop, Cursor, Windsurf, ChatGPT apps, and more.
4
+
5
+ It verifies AI agent tool calls at runtime, blocks unsafe ones by default, and issues **tamper-evident evidence records** for every call (allowed and denied).
6
+
7
+ > **This is a runtime enforcement layer, not a static scanner.** It runs on every live tool call — not once on source code.
8
+
9
+ ## Tools
10
+
11
+ | Tool | What it does |
12
+ |---|---|
13
+ | `verify_tool_call` | Verify a tool call across 7 CCS dimensions + semantic attack-chain analysis + math overflow detection. Returns `verdict: allowed/denied`. **Blocks by default.** |
14
+ | `issue_evidence` | Issue a cryptographically bound CCS evidence record (`content_hash` + `evidence_hash`) for a call. Independently verifiable. |
15
+ | `audit_mcp_config` | Audit an MCP configuration JSON for security risks: plain HTTP, weak secrets, disabled TLS, missing commands. |
16
+
17
+ ## Quick Start
18
+
19
+ ### Install and run with npx
20
+
21
+ ```bash
22
+ npx @correctover/ccs-mcp-server
23
+ ```
24
+
25
+ ### Claude Desktop / Cursor configuration
26
+
27
+ Add to your MCP client settings:
28
+
29
+ ```json
30
+ {
31
+ "mcpServers": {
32
+ "ccs-runtime-evidence": {
33
+ "command": "npx",
34
+ "args": ["-y", "@correctover/ccs-mcp-server"]
35
+ }
36
+ }
37
+ }
38
+ ```
39
+
40
+ ## What it detects
41
+
42
+ - **Command injection**: shell metacharacters, `$()`, backticks, dangerous commands
43
+ - **Path traversal**: `../`, `/etc/passwd`, `/proc/self/`, Windows system paths
44
+ - **SSRF**: localhost, link-local (169.254.x.x), private IP ranges
45
+ - **Prompt injection**: "ignore previous instructions", system prompt extraction, special tokens
46
+ - **SQL injection**: UNION SELECT, `OR 1=1`, `DROP TABLE`
47
+ - **Attack chains**: multi-step combinations (e.g. path traversal + SSRF = exfiltration chain)
48
+ - **Math safety**: integer overflow (>2^53-1), NaN/Infinity, DoS strings
49
+ - **Caller identity**: agent ID verification against allowlist
50
+ - **Schema violations**: type, enum, range, required field checks
51
+ - **MCP config risks**: plain HTTP, weak secrets, `--insecure`, disabled TLS
52
+
53
+ ## Evidence format
54
+
55
+ Every call produces evidence like:
56
+
57
+ ```json
58
+ {
59
+ "evidence_type": "ccs.tool_call.verification",
60
+ "evidence_id": "uuid-v4",
61
+ "tool": "shell.exec",
62
+ "caller": "agent-001",
63
+ "verdict": "denied",
64
+ "mode": "block",
65
+ "params_hash": "sha256:...",
66
+ "policy_hash": "sha256:...",
67
+ "content_hash": "sha256:...",
68
+ "evidence_hash": "sha256:...",
69
+ "dimensions": {
70
+ "Structure": {"status": "pass"},
71
+ "Schema": {"status": "pass"},
72
+ "Security": {"status": "fail", "reason": "cmd: command_injection; cmd: path_traversal; cmd: ssrf"},
73
+ "Identity": {"status": "pass"}
74
+ },
75
+ "semantic_analysis": {
76
+ "attack_chains": [{"chain": "exfil_chain", "severity": "critical"}],
77
+ "semantic_severity": "critical"
78
+ },
79
+ "issued_at": "2026-08-22T12:00:00.000Z"
80
+ }
81
+ ```
82
+
83
+ The dual-hash design (`content_hash` + `evidence_hash`) means any tampering is detectable by any third party.
84
+
85
+ ## About CCS
86
+
87
+ CCS (Correctover Conformance Shape) is a 7-dimension runtime verification standard for AI agent tool calls:
88
+
89
+ 1. **Structure** — valid tool name, argument format, nesting depth, payload size
90
+ 2. **Schema** — type, required fields, enums, ranges, string lengths
91
+ 3. **Security** — injection, traversal, SSRF, prompt injection, SQL injection
92
+ 4. **Identity** — caller agent ID verification
93
+ 5. **Integrity** — request hash validation
94
+ 6. **Latency** — execution time budget (warn)
95
+ 7. **Cost** — cost budget (warn)
96
+
97
+ Plus: semantic intent analysis (attack chains, obfuscation signals, privilege escalation) and mathematical verification (overflow, NaN, DoS).
98
+
99
+ ## Links
100
+
101
+ - npm: https://www.npmjs.com/package/@correctover/ccs-mcp-server
102
+ - Website: https://correctover.com
103
+ - IETF Draft: https://www.ietf.org/archive/id/draft-correctover-ccs-05.txt
104
+ - SkillHub: https://skillhub.cn
105
+
106
+ ## License
107
+
108
+ Proprietary Commercial License. Reference implementation for CCS standard evaluation.
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "ccs-mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "CCS Runtime Evidence MCP Server — verify AI agent tool calls, issue tamper-evident evidence, audit MCP configs. For Claude Desktop, Cursor, Windsurf, and any MCP client.",
5
+ "type": "commonjs",
6
+ "bin": {
7
+ "ccs-mcp-server": "src/index.js"
8
+ },
9
+ "main": "src/index.js",
10
+ "files": ["src", "README.md", "LICENSE"],
11
+ "keywords": [
12
+ "mcp",
13
+ "model-context-protocol",
14
+ "mcp-server",
15
+ "mcp-security",
16
+ "ai-agent-security",
17
+ "runtime-verification",
18
+ "ccs",
19
+ "correctover",
20
+ "audit",
21
+ "evidence",
22
+ "supply-chain-security",
23
+ "agent-security",
24
+ "security",
25
+ "developer-tools"
26
+ ],
27
+ "license": "SEE LICENSE IN LICENSE",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/Correctover/ccs-mcp-server.git"
31
+ },
32
+ "homepage": "https://correctover.com",
33
+ "engines": {
34
+ "node": ">=18"
35
+ },
36
+ "dependencies": {},
37
+ "mcp": {
38
+ "name": "ccs-runtime-evidence",
39
+ "displayName": "CCS Runtime Evidence",
40
+ "description": "Verify AI agent tool calls and issue tamper-evident CCS evidence records. Blocks command injection, path traversal, SSRF, SQL injection; validates caller identity, schema, cost/latency budgets; produces cryptographically bound evidence."
41
+ }
42
+ }
package/src/index.js ADDED
@@ -0,0 +1,426 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CCS Runtime Evidence MCP Server v1.0.0
4
+ *
5
+ * A standalone Model Context Protocol (MCP) server that exposes CCS runtime
6
+ * verification as MCP tools, so any MCP-compatible client (Claude Desktop,
7
+ * Cursor, Windsurf, ChatGPT apps, etc.) can verify AI agent tool calls and
8
+ * obtain tamper-evident evidence records.
9
+ *
10
+ * Tools exposed:
11
+ * verify_tool_call Verify a tool call against CCS 7 dimensions + semantic + math
12
+ * issue_evidence Issue a signed CCS evidence record (allow + deny)
13
+ * audit_mcp_config Audit an MCP server configuration for security risks
14
+ *
15
+ * Transport: stdio (per MCP spec). To run:
16
+ * npx @correctover/ccs-mcp-server
17
+ *
18
+ * License: Proprietary Commercial License (reference implementation).
19
+ *
20
+ * This server is a self-contained JavaScript implementation of the CCS
21
+ * runtime verification reference (mirroring the Python SkillHub distribution
22
+ * ccs-runtime-verifier). Zero runtime dependencies.
23
+ */
24
+
25
+ "use strict";
26
+
27
+ const crypto = require("crypto");
28
+ const readline = require("readline");
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // CCS Verifier Core (pure stdlib Node.js)
32
+ // ---------------------------------------------------------------------------
33
+
34
+ const CCS_VERSION = "1.0.0";
35
+
36
+ const DEFAULT_POLICY = {
37
+ mode: "block",
38
+ max_nesting_depth: 8,
39
+ max_argument_bytes: 65536,
40
+ max_string_length: 16384,
41
+ latency_budget_us: 5000000,
42
+ cost_budget: 0.05,
43
+ allowed_tools: [],
44
+ denied_tools: [],
45
+ tool_schemas: {},
46
+ require_caller_identity: true,
47
+ require_request_hash: false,
48
+ allowed_callers: [],
49
+ enable_semantic_analysis: true,
50
+ enable_math_verification: true,
51
+ };
52
+
53
+ const INJECTION_PATTERNS = {
54
+ command_injection: [
55
+ /[;&|`$]\s*(rm|curl|wget|bash|sh|nc|cat|chmod|eval|exec)\b/i,
56
+ /\$\([^)]+\)/,
57
+ /`[^`]+`/,
58
+ /\|\|\s*\w+/,
59
+ ],
60
+ path_traversal: [
61
+ /\.\.[\/\\]/,
62
+ /\/etc\/(passwd|shadow|hosts)/,
63
+ /\/proc\/self\//,
64
+ /\\windows\\system32/i,
65
+ ],
66
+ ssrf: [
67
+ /https?:\/\/(127\.0\.0\.1|localhost|0\.0\.0\.0|169\.254\.169\.254)/i,
68
+ /https?:\/\/10\.\d+\.\d+\.\d+/,
69
+ /https?:\/\/192\.168\.\d+\.\d+/,
70
+ /https?:\/\/172\.(1[6-9]|2\d|3[01])\.\d+\.\d+/,
71
+ ],
72
+ prompt_injection: [
73
+ /ignore\s+(all\s+)?(previous|above|prior)\s+(instructions?|prompts?)/i,
74
+ /disregard\s+(all\s+)?(previous|above)/i,
75
+ /you\s+are\s+now\s+(?:a|an|the)\s+/i,
76
+ /system\s*prompt/i,
77
+ /<\|im_start\|>|<\|im_end\|>/,
78
+ ],
79
+ sql_injection: [
80
+ /(\bunion\b.*\bselect\b)/i,
81
+ /(\bor\b\s+1\s*=\s*1)/i,
82
+ /(;\s*drop\s+table)/i,
83
+ ],
84
+ };
85
+
86
+ const TOOL_RISK_PROFILES = {
87
+ shell: 0.9, exec: 0.9, system: 0.95,
88
+ filesystem: 0.6, file: 0.6,
89
+ network: 0.7, http: 0.7,
90
+ database: 0.75, sql: 0.8,
91
+ read: 0.2, math: 0.1,
92
+ };
93
+
94
+ const DANGEROUS_COMBOS = [
95
+ { name: "exfil_chain", indicators: ["path_traversal", "ssrf"], severity: "critical" },
96
+ { name: "rce_chain", indicators: ["command_injection", "path_traversal"], severity: "critical" },
97
+ { name: "sql_exfil", indicators: ["sql_injection", "ssrf"], severity: "critical" },
98
+ { name: "prompt_hijack", indicators: ["prompt_injection", "command_injection"], severity: "critical" },
99
+ ];
100
+
101
+ function deepMerge(base, override) {
102
+ const result = { ...base };
103
+ for (const [k, v] of Object.entries(override || {})) {
104
+ if (v && typeof v === "object" && !Array.isArray(v) && result[k] && typeof result[k] === "object") {
105
+ result[k] = deepMerge(result[k], v);
106
+ } else {
107
+ result[k] = v;
108
+ }
109
+ }
110
+ return result;
111
+ }
112
+
113
+ function canonical(obj) {
114
+ return JSON.stringify(obj, Object.keys(obj).sort());
115
+ }
116
+
117
+ function sha256(s) {
118
+ return "sha256:" + crypto.createHash("sha256").update(s).digest("hex");
119
+ }
120
+
121
+ function scanValue(val, findings, path = "<root>") {
122
+ if (typeof val === "string") {
123
+ for (const [cat, pats] of Object.entries(INJECTION_PATTERNS)) {
124
+ for (const pat of pats) {
125
+ if (pat.test(val)) {
126
+ findings.push(`${path}: ${cat}`);
127
+ break;
128
+ }
129
+ }
130
+ }
131
+ if (val.length > 1e7) findings.push(`${path}: dos_string`);
132
+ } else if (typeof val === "number") {
133
+ if (Number.isNaN(val) || !Number.isFinite(val)) findings.push(`${path}: nan_or_infinity`);
134
+ else if (Math.abs(val) > Number.MAX_SAFE_INTEGER) findings.push(`${path}: integer_overflow`);
135
+ else if (Math.abs(val) > 1e9) findings.push(`${path}: unreasonable_magnitude`);
136
+ else if (val < 0 && !/offset|delta|diff|change/i.test(path)) findings.push(`${path}: negative_value`);
137
+ } else if (val && typeof val === "object") {
138
+ if (Array.isArray(val)) {
139
+ val.forEach((v, i) => scanValue(v, findings, `${path}[${i}]`));
140
+ } else {
141
+ for (const [k, v] of Object.entries(val)) scanValue(v, findings, path === "<root>" ? k : `${path}.${k}`);
142
+ }
143
+ }
144
+ }
145
+
146
+ function structureCheck(call, policy) {
147
+ const tool = call.tool;
148
+ if (!tool || typeof tool !== "string") return { ok: false, reason: "missing or invalid 'tool' field" };
149
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*$/.test(tool))
150
+ return { ok: false, reason: `tool name '${tool}' has invalid format` };
151
+ if (call.arguments !== undefined && typeof call.arguments !== "object")
152
+ return { ok: false, reason: "'arguments' must be an object or array" };
153
+ if (policy.denied_tools?.includes(tool)) return { ok: false, reason: `tool '${tool}' is denied` };
154
+ if (policy.allowed_tools?.length && !policy.allowed_tools.includes(tool))
155
+ return { ok: false, reason: `tool '${tool}' not in allowed list` };
156
+ const argBytes = Buffer.byteLength(JSON.stringify(call.arguments || {}));
157
+ if (argBytes > policy.max_argument_bytes)
158
+ return { ok: false, reason: `argument size ${argBytes} exceeds limit ${policy.max_argument_bytes}` };
159
+ return { ok: true };
160
+ }
161
+
162
+ function securityCheck(call) {
163
+ const findings = [];
164
+ scanValue(call.arguments || {}, findings);
165
+ if (findings.length) return { ok: false, reason: findings.slice(0, 5).join("; "), findings };
166
+ return { ok: true, findings: [] };
167
+ }
168
+
169
+ function identityCheck(call, policy) {
170
+ if (!policy.require_caller_identity) return { ok: true };
171
+ const caller = call.caller;
172
+ if (!caller || typeof caller !== "object") return { ok: false, reason: "missing 'caller' object" };
173
+ if (!caller.agent_id) return { ok: false, reason: "caller missing 'agent_id'" };
174
+ if (policy.allowed_callers?.length && !policy.allowed_callers.includes(caller.agent_id))
175
+ return { ok: false, reason: `caller '${caller.agent_id}' not in allowed_callers` };
176
+ return { ok: true };
177
+ }
178
+
179
+ function schemaCheck(call, policy) {
180
+ const schema = policy.tool_schemas?.[call.tool];
181
+ if (!schema || !call.arguments || typeof call.arguments !== "object") return { ok: true };
182
+ for (const [field, def] of Object.entries(schema.properties || {})) {
183
+ if (!(field in call.arguments)) {
184
+ if ((schema.required || []).includes(field))
185
+ return { ok: false, reason: `missing required field '${field}'` };
186
+ continue;
187
+ }
188
+ const val = call.arguments[field];
189
+ if (def.type === "string" && typeof val !== "string") return { ok: false, reason: `field '${field}' must be string` };
190
+ if (def.type === "number" && typeof val !== "number") return { ok: false, reason: `field '${field}' must be number` };
191
+ if (def.type === "integer" && !Number.isInteger(val)) return { ok: false, reason: `field '${field}' must be integer` };
192
+ if (def.type === "boolean" && typeof val !== "boolean") return { ok: false, reason: `field '${field}' must be boolean` };
193
+ if (def.type === "array" && !Array.isArray(val)) return { ok: false, reason: `field '${field}' must be array` };
194
+ if (typeof val === "string" && val.length > policy.max_string_length)
195
+ return { ok: false, reason: `field '${field}' exceeds max length` };
196
+ if (def.enum && !def.enum.includes(val)) return { ok: false, reason: `field '${field}' not in enum` };
197
+ }
198
+ return { ok: true };
199
+ }
200
+
201
+ function semanticAnalysis(tool, secFindings) {
202
+ const ns = (tool || "").split(".")[0].toLowerCase();
203
+ const baseRisk = TOOL_RISK_PROFILES[ns] ?? 0.4;
204
+ const cats = new Set();
205
+ for (const f of secFindings) {
206
+ const m = f.split(": ").pop();
207
+ if (INJECTION_PATTERNS[m]) cats.add(m);
208
+ }
209
+ const chains = DANGEROUS_COMBOS
210
+ .filter((c) => c.indicators.every((i) => cats.has(i)))
211
+ .map((c) => ({ chain: c.name, severity: c.severity }));
212
+ let score = baseRisk + cats.size * 0.15;
213
+ let severity = "info";
214
+ if (chains.some((c) => c.severity === "critical")) severity = "critical";
215
+ else if (score >= 0.9) severity = "critical";
216
+ else if (score >= 0.7) severity = "high";
217
+ else if (score >= 0.5) severity = "medium";
218
+ else if (score >= 0.3) severity = "low";
219
+ return { tool_namespace: ns, base_risk: baseRisk, attack_chains: chains,
220
+ finding_categories: [...cats], semantic_severity: severity };
221
+ }
222
+
223
+ function verifyCall(call, policyOverride) {
224
+ const policy = deepMerge(DEFAULT_POLICY, policyOverride || {});
225
+ const dimensions = {};
226
+
227
+ const checks = [
228
+ ["Structure", () => structureCheck(call, policy)],
229
+ ["Schema", () => schemaCheck(call, policy)],
230
+ ["Security", () => securityCheck(call)],
231
+ ["Identity", () => identityCheck(call, policy)],
232
+ ];
233
+
234
+ let secFindings = [];
235
+ for (const [name, fn] of checks) {
236
+ const r = fn();
237
+ dimensions[name] = { status: r.ok ? "pass" : "fail" };
238
+ if (!r.ok) dimensions[name].reason = r.reason;
239
+ if (name === "Security") secFindings = r.findings || [];
240
+ }
241
+
242
+ // Latency/Cost (warn-only)
243
+ const meta = call.metadata || {};
244
+ dimensions.Latency = { status: "pass" };
245
+ if (meta.execution_time_us != null && policy.latency_budget_us && meta.execution_time_us > policy.latency_budget_us)
246
+ dimensions.Latency = { status: "warn", reason: `latency ${meta.execution_time_us}us exceeds budget` };
247
+ dimensions.Cost = { status: "pass" };
248
+ if (meta.cost != null && policy.cost_budget && meta.cost > policy.cost_budget)
249
+ dimensions.Cost = { status: "warn", reason: `cost ${meta.cost} exceeds budget` };
250
+ dimensions.Integrity = { status: "pass" };
251
+
252
+ const enforced = ["Structure", "Schema", "Security", "Identity"];
253
+ const verdict = (policy.mode === "audit" || enforced.every((d) => dimensions[d].status === "pass")) ? "allowed" : "denied";
254
+
255
+ const paramsHash = sha256(canonical(call.arguments || {}));
256
+ const policyHash = sha256(canonical(policy));
257
+
258
+ const semantic = policy.enable_semantic_analysis ? semanticAnalysis(call.tool, secFindings) : undefined;
259
+
260
+ const content = {
261
+ tool: call.tool, caller: call.caller?.agent_id || "unknown", verdict, mode: policy.mode,
262
+ params_hash: paramsHash, policy_hash: policyHash, dimensions,
263
+ };
264
+ const contentHash = sha256(canonical(content));
265
+
266
+ const evidence = {
267
+ evidence_type: "ccs.tool_call.verification",
268
+ evidence_version: CCS_VERSION,
269
+ evidence_id: crypto.randomUUID(),
270
+ tool: call.tool,
271
+ caller: call.caller?.agent_id || "unknown",
272
+ verdict,
273
+ mode: policy.mode,
274
+ params_hash: paramsHash,
275
+ policy_hash: policyHash,
276
+ content_hash: contentHash,
277
+ dimensions,
278
+ issued_at: new Date().toISOString(),
279
+ };
280
+ if (semantic) evidence.semantic_analysis = semantic;
281
+ if (call.parent_evidence_hash) evidence.parent_evidence_hash = call.parent_evidence_hash;
282
+
283
+ // Finalize evidence_hash
284
+ const hashBody = { ...evidence };
285
+ evidence.evidence_hash = sha256(canonical(hashBody));
286
+ return evidence;
287
+ }
288
+
289
+ function auditMcpConfig(config) {
290
+ const issues = [];
291
+ const servers = config.mcpServers || config.servers || {};
292
+ for (const [name, srv] of Object.entries(servers)) {
293
+ if (!srv.command && !srv.url) issues.push({ server: name, severity: "high", issue: "no command or url" });
294
+ if (srv.url && /^http:\/\//i.test(srv.url)) issues.push({ server: name, severity: "high", issue: "HTTP url (not HTTPS)" });
295
+ if (srv.env) {
296
+ for (const k of Object.keys(srv.env)) {
297
+ if (/KEY|TOKEN|SECRET|PASSWORD/i.test(k) && typeof srv.env[k] === "string" && srv.env[k].length < 8)
298
+ issues.push({ server: name, severity: "medium", issue: `weak ${k}` });
299
+ }
300
+ }
301
+ if (srv.args?.some((a) => /--insecure|--no-verify|-k$/i.test(a)))
302
+ issues.push({ server: name, severity: "critical", issue: "TLS verification disabled" });
303
+ }
304
+ return { total_servers: Object.keys(servers).length, issues,
305
+ risk: issues.some((i) => i.severity === "critical") ? "critical" :
306
+ issues.some((i) => i.severity === "high") ? "high" :
307
+ issues.length ? "medium" : "low" };
308
+ }
309
+
310
+ // ---------------------------------------------------------------------------
311
+ // MCP Protocol (stdio JSON-RPC 2.0)
312
+ // ---------------------------------------------------------------------------
313
+
314
+ const PROTOCOL_VERSION = "2024-11-05";
315
+ const SERVER_INFO = { name: "ccs-runtime-evidence", version: CCS_VERSION, title: "CCS Runtime Evidence" };
316
+
317
+ const TOOLS = [
318
+ {
319
+ name: "verify_tool_call",
320
+ description: "Verify an AI agent tool call against CCS 7 dimensions (Structure, Schema, Security, Identity, Integrity, Latency, Cost) plus semantic attack-chain analysis and math overflow detection. Returns verdict (allowed/denied) with detailed findings. DEFAULT MODE BLOCKS UNSAFE CALLS.",
321
+ inputSchema: {
322
+ type: "object",
323
+ properties: {
324
+ tool: { type: "string", description: "Tool name as namespace.action, e.g. 'shell.exec' or 'read.file'" },
325
+ arguments: { type: "object", description: "Tool call arguments to verify", additionalProperties: true },
326
+ caller: { type: "object", description: "Caller identity, e.g. {agent_id: 'agent-001'}",
327
+ properties: { agent_id: { type: "string" } }, required: ["agent_id"] },
328
+ metadata: { type: "object", description: "Optional metadata (execution_time_us, cost, request_id, etc.)", additionalProperties: true },
329
+ policy: { type: "object", description: "Optional CCS policy override (allowed_tools, denied_tools, mode, budgets, etc.)", additionalProperties: true },
330
+ },
331
+ required: ["tool"],
332
+ },
333
+ },
334
+ {
335
+ name: "issue_evidence",
336
+ description: "Issue a CCS evidence record for a tool call. Evidence is cryptographically bound (content_hash + evidence_hash), tamper-evident, independently verifiable. Issued for allowed AND denied calls.",
337
+ inputSchema: {
338
+ type: "object",
339
+ properties: {
340
+ tool: { type: "string" },
341
+ arguments: { type: "object", additionalProperties: true },
342
+ caller: { type: "object", properties: { agent_id: { type: "string" } }, required: ["agent_id"] },
343
+ metadata: { type: "object", additionalProperties: true },
344
+ policy: { type: "object", additionalProperties: true },
345
+ },
346
+ required: ["tool", "caller"],
347
+ },
348
+ },
349
+ {
350
+ name: "audit_mcp_config",
351
+ description: "Audit an MCP client/server configuration JSON for security risks: plain HTTP, weak secrets, disabled TLS, missing commands. Returns structured issues with severity.",
352
+ inputSchema: {
353
+ type: "object",
354
+ properties: {
355
+ config: { type: "object", description: "MCP configuration JSON (e.g. Claude Desktop config)", additionalProperties: true },
356
+ },
357
+ required: ["config"],
358
+ },
359
+ },
360
+ ];
361
+
362
+ function handleRequest(req) {
363
+ const { method, params, id } = req;
364
+ if (method === "initialize") {
365
+ return {
366
+ jsonrpc: "2.0", id,
367
+ result: {
368
+ protocolVersion: params?.protocolVersion || PROTOCOL_VERSION,
369
+ capabilities: { tools: {} },
370
+ serverInfo: SERVER_INFO,
371
+ },
372
+ };
373
+ }
374
+ if (method === "notifications/initialized" || method === "initialized") return null;
375
+ if (method === "ping") return { jsonrpc: "2.0", id, result: {} };
376
+ if (method === "tools/list") return { jsonrpc: "2.0", id, result: { tools: TOOLS } };
377
+ if (method === "tools/call") {
378
+ const name = params?.name;
379
+ const args = params?.arguments || {};
380
+ try {
381
+ let result;
382
+ if (name === "verify_tool_call") {
383
+ const ev = verifyCall(args, args.policy);
384
+ result = {
385
+ content: [{ type: "text", text: JSON.stringify({
386
+ verdict: ev.verdict, blocked: ev.verdict === "denied",
387
+ tool: ev.tool, caller: ev.caller,
388
+ dimensions: ev.dimensions, semantic_analysis: ev.semantic_analysis,
389
+ evidence_id: ev.evidence_id, evidence_hash: ev.evidence_hash,
390
+ }, null, 2) }],
391
+ isError: ev.verdict === "denied",
392
+ };
393
+ } else if (name === "issue_evidence") {
394
+ const ev = verifyCall(args, args.policy);
395
+ result = { content: [{ type: "text", text: JSON.stringify(ev, null, 2) }] };
396
+ } else if (name === "audit_mcp_config") {
397
+ const report = auditMcpConfig(args.config || {});
398
+ result = { content: [{ type: "text", text: JSON.stringify(report, null, 2) }] };
399
+ } else {
400
+ result = { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
401
+ }
402
+ return { jsonrpc: "2.0", id, result };
403
+ } catch (e) {
404
+ return { jsonrpc: "2.0", id,
405
+ result: { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true } };
406
+ }
407
+ }
408
+ if (id !== undefined) return { jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${method}` } };
409
+ return null;
410
+ }
411
+
412
+ function main() {
413
+ process.stderr.write(`[ccs-mcp-server] v${CCS_VERSION} starting on stdio\n`);
414
+ const rl = readline.createInterface({ input: process.stdin });
415
+ rl.on("line", (line) => {
416
+ if (!line.trim()) return;
417
+ let req;
418
+ try { req = JSON.parse(line); } catch { return; }
419
+ const res = handleRequest(req);
420
+ if (res) process.stdout.write(JSON.stringify(res) + "\n");
421
+ });
422
+ rl.on("close", () => process.exit(0));
423
+ }
424
+
425
+ if (require.main === module) main();
426
+ module.exports = { verifyCall, auditMcpConfig };