ccs-mcp-server 1.0.0 → 1.1.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 (2) hide show
  1. package/package.json +7 -3
  2. package/src/index.js +131 -1
package/package.json CHANGED
@@ -1,13 +1,17 @@
1
1
  {
2
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.",
3
+ "version": "1.1.0",
4
+ "description": "CCS Runtime Evidence MCP Server — verify AI agent tool calls, issue tamper-evident evidence, audit MCP configs, and bind execution to declared intent (cross-model amount drift protection). For Claude Desktop, Cursor, Windsurf, and any MCP client.",
5
5
  "type": "commonjs",
6
6
  "bin": {
7
7
  "ccs-mcp-server": "src/index.js"
8
8
  },
9
9
  "main": "src/index.js",
10
- "files": ["src", "README.md", "LICENSE"],
10
+ "files": [
11
+ "src",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
11
15
  "keywords": [
12
16
  "mcp",
13
17
  "model-context-protocol",
package/src/index.js CHANGED
@@ -31,7 +31,7 @@ const readline = require("readline");
31
31
  // CCS Verifier Core (pure stdlib Node.js)
32
32
  // ---------------------------------------------------------------------------
33
33
 
34
- const CCS_VERSION = "1.0.0";
34
+ const CCS_VERSION = "1.1.0";
35
35
 
36
36
  const DEFAULT_POLICY = {
37
37
  mode: "block",
@@ -307,6 +307,82 @@ function auditMcpConfig(config) {
307
307
  issues.length ? "medium" : "low" };
308
308
  }
309
309
 
310
+ // ---------------------------------------------------------------------------
311
+ // Intent Binding (L4) — zero-LLM, deterministic, microsecond
312
+ // ---------------------------------------------------------------------------
313
+ // Agent framework declares structured intent before tool execution.
314
+ // CCS verifies actual args match declared intent with zero tolerance.
315
+ // This catches cross-model amount drift (planner says 100, executor writes 10000)
316
+ // without any NLP or LLM call.
317
+
318
+ function deepEqual(a, b) {
319
+ if (a === b) return true;
320
+ if (typeof a !== typeof b) {
321
+ // numeric normalization: 100 == 100.0 == 1e2
322
+ if (typeof a === "number" && typeof b === "string") {
323
+ const n = Number(b);
324
+ return Number.isFinite(n) && a === n;
325
+ }
326
+ if (typeof b === "number" && typeof a === "string") {
327
+ const n = Number(a);
328
+ return Number.isFinite(n) && b === n;
329
+ }
330
+ return false;
331
+ }
332
+ if (typeof a !== "object" || a === null || b === null) return false;
333
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
334
+ const ka = Object.keys(a), kb = Object.keys(b);
335
+ if (ka.length !== kb.length) return false;
336
+ return ka.every((k) => deepEqual(a[k], b[k]));
337
+ }
338
+
339
+ function verifyIntentBinding(intent, toolName, toolArgs, now = Date.now()) {
340
+ // 1. TTL check
341
+ if (intent.ttl_ms != null && now - intent.issued_at > intent.ttl_ms) {
342
+ return {
343
+ allowed: false,
344
+ reason: { type: "intent_expired", expected: intent.issued_at + intent.ttl_ms, actual: now },
345
+ intent_hash: crypto.createHash("sha256").update(canonical(intent)).digest("hex"),
346
+ };
347
+ }
348
+
349
+ // 2. Field-by-field comparison
350
+ const fields = intent.fields || {};
351
+ for (const [field, spec] of Object.entries(fields)) {
352
+ const actual = toolArgs[field];
353
+ const expected = spec.value;
354
+ const mode = spec.binding_mode || "exact";
355
+ let match = false;
356
+ if (mode === "exact") {
357
+ match = deepEqual(expected, actual);
358
+ } else if (mode === "numeric_tolerance") {
359
+ if (typeof expected === "number" && typeof actual === "number") {
360
+ match = Math.abs(expected - actual) <= (spec.tolerance || 0);
361
+ }
362
+ } else if (mode === "pattern") {
363
+ if (typeof actual === "string") {
364
+ try { match = new RegExp(spec.pattern).test(actual); } catch { match = false; }
365
+ }
366
+ }
367
+ if (!match) {
368
+ return {
369
+ allowed: false,
370
+ reason: {
371
+ type: "intent_arg_mismatch",
372
+ field, binding_mode: mode,
373
+ expected, actual,
374
+ },
375
+ intent_hash: crypto.createHash("sha256").update(canonical(intent)).digest("hex"),
376
+ };
377
+ }
378
+ }
379
+
380
+ return {
381
+ allowed: true,
382
+ intent_hash: crypto.createHash("sha256").update(canonical(intent)).digest("hex"),
383
+ };
384
+ }
385
+
310
386
  // ---------------------------------------------------------------------------
311
387
  // MCP Protocol (stdio JSON-RPC 2.0)
312
388
  // ---------------------------------------------------------------------------
@@ -357,6 +433,44 @@ const TOOLS = [
357
433
  required: ["config"],
358
434
  },
359
435
  },
436
+ {
437
+ name: "verify_intent_binding",
438
+ description: "Verify that actual tool call arguments match a declared intent with zero tolerance. Catches cross-model parameter drift (planner says amount=100, executor writes amount=10000). No LLM needed — pure deterministic comparison. Supports exact match, numeric tolerance, and regex pattern binding modes.",
439
+ inputSchema: {
440
+ type: "object",
441
+ properties: {
442
+ intent: {
443
+ type: "object",
444
+ description: "Structured intent declaration from the agent framework",
445
+ properties: {
446
+ intent_id: { type: "string", description: "Unique intent identifier (UUID v7 recommended)" },
447
+ intent_type: { type: "string", description: "e.g. payment, file_write, api_call" },
448
+ fields: {
449
+ type: "object",
450
+ description: "Map of field name to binding specification",
451
+ additionalProperties: {
452
+ type: "object",
453
+ properties: {
454
+ value: { description: "Expected value" },
455
+ binding_mode: { type: "string", enum: ["exact", "numeric_tolerance", "pattern"] },
456
+ tolerance: { type: "number", description: "Tolerance for numeric_tolerance mode" },
457
+ pattern: { type: "string", description: "Regex for pattern mode" },
458
+ },
459
+ required: ["value"],
460
+ },
461
+ },
462
+ issued_at: { type: "integer", description: "Unix ms when intent was issued" },
463
+ ttl_ms: { type: "integer", description: "Time-to-live in ms (default 30000)" },
464
+ evidence_ref: { type: "string", description: "Optional hash of external evidence (invoice, authorization VC)" },
465
+ },
466
+ required: ["intent_id", "intent_type", "fields", "issued_at"],
467
+ },
468
+ tool: { type: "string", description: "Tool being called, e.g. payments.send" },
469
+ arguments: { type: "object", description: "Actual tool call arguments", additionalProperties: true },
470
+ },
471
+ required: ["intent", "tool", "arguments"],
472
+ },
473
+ },
360
474
  ];
361
475
 
362
476
  function handleRequest(req) {
@@ -396,6 +510,22 @@ function handleRequest(req) {
396
510
  } else if (name === "audit_mcp_config") {
397
511
  const report = auditMcpConfig(args.config || {});
398
512
  result = { content: [{ type: "text", text: JSON.stringify(report, null, 2) }] };
513
+ } else if (name === "verify_intent_binding") {
514
+ const verdict = verifyIntentBinding(args.intent, args.tool, args.arguments || {});
515
+ result = {
516
+ content: [{ type: "text", text: JSON.stringify({
517
+ allowed: verdict.allowed,
518
+ intent_id: args.intent.intent_id,
519
+ intent_hash: verdict.intent_hash,
520
+ tool: args.tool,
521
+ ...(verdict.reason ? { reason: verdict.reason } : {}),
522
+ blocked: !verdict.allowed,
523
+ message: verdict.allowed
524
+ ? "Intent binding verified — actual arguments match declared intent"
525
+ : `Intent binding DENIED: ${verdict.reason?.type}`,
526
+ }, null, 2) }],
527
+ isError: !verdict.allowed,
528
+ };
399
529
  } else {
400
530
  result = { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
401
531
  }