@tenova/swt3-mcp 0.1.0 → 0.5.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/README.md +162 -33
- package/dist/bin/swt3-mcp.js +3 -2
- package/dist/bin/swt3-mcp.js.map +1 -1
- package/dist/chain-verifier.d.ts +46 -0
- package/dist/chain-verifier.d.ts.map +1 -0
- package/dist/chain-verifier.js +210 -0
- package/dist/chain-verifier.js.map +1 -0
- package/dist/client.d.ts +4 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +56 -6
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +151 -14
- package/dist/config.js.map +1 -1
- package/dist/density-policy.d.ts +55 -0
- package/dist/density-policy.d.ts.map +1 -0
- package/dist/density-policy.js +128 -0
- package/dist/density-policy.js.map +1 -0
- package/dist/redis-reader.d.ts +57 -0
- package/dist/redis-reader.d.ts.map +1 -0
- package/dist/redis-reader.js +254 -0
- package/dist/redis-reader.js.map +1 -0
- package/dist/server.d.ts +2 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +555 -1
- package/dist/server.js.map +1 -1
- package/dist/state.d.ts +29 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +20 -0
- package/dist/state.js.map +1 -0
- package/dist/tools/audit.d.ts +20 -0
- package/dist/tools/audit.d.ts.map +1 -0
- package/dist/tools/audit.js +102 -0
- package/dist/tools/audit.js.map +1 -0
- package/dist/tools/authorize.d.ts +19 -0
- package/dist/tools/authorize.d.ts.map +1 -0
- package/dist/tools/authorize.js +96 -0
- package/dist/tools/authorize.js.map +1 -0
- package/dist/tools/chain.d.ts +24 -0
- package/dist/tools/chain.d.ts.map +1 -0
- package/dist/tools/chain.js +114 -0
- package/dist/tools/chain.js.map +1 -0
- package/dist/tools/model.d.ts +33 -0
- package/dist/tools/model.d.ts.map +1 -0
- package/dist/tools/model.js +162 -0
- package/dist/tools/model.js.map +1 -0
- package/dist/tools/signup.d.ts +2 -2
- package/dist/tools/signup.d.ts.map +1 -1
- package/dist/tools/signup.js +10 -3
- package/dist/tools/signup.js.map +1 -1
- package/dist/tools/skill.d.ts +33 -0
- package/dist/tools/skill.d.ts.map +1 -0
- package/dist/tools/skill.js +168 -0
- package/dist/tools/skill.js.map +1 -0
- package/dist/tools/suggest.d.ts +15 -0
- package/dist/tools/suggest.d.ts.map +1 -0
- package/dist/tools/suggest.js +174 -0
- package/dist/tools/suggest.js.map +1 -0
- package/dist/tools/trust.d.ts +33 -0
- package/dist/tools/trust.d.ts.map +1 -0
- package/dist/tools/trust.js +213 -0
- package/dist/tools/trust.js.map +1 -0
- package/dist/tools/violation.d.ts +20 -0
- package/dist/tools/violation.d.ts.map +1 -0
- package/dist/tools/violation.js +97 -0
- package/dist/tools/violation.js.map +1 -0
- package/dist/tools/witness.d.ts +6 -1
- package/dist/tools/witness.d.ts.map +1 -1
- package/dist/tools/witness.js +19 -5
- package/dist/tools/witness.js.map +1 -1
- package/package.json +33 -3
package/dist/server.js
CHANGED
|
@@ -8,17 +8,203 @@
|
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
10
|
import { AxiomClient } from "./client.js";
|
|
11
|
+
import { createSessionState } from "./state.js";
|
|
11
12
|
import { handleWitness } from "./tools/witness.js";
|
|
12
13
|
import { handleVerify } from "./tools/verify.js";
|
|
13
14
|
import { handleProcedures } from "./tools/procedures.js";
|
|
14
15
|
import { handlePosture } from "./tools/posture.js";
|
|
15
16
|
import { handleSignup } from "./tools/signup.js";
|
|
17
|
+
import { handleAuthorize } from "./tools/authorize.js";
|
|
18
|
+
import { handleStartAudit, handleEndAudit, trackProcedure } from "./tools/audit.js";
|
|
19
|
+
import { handleSuggest } from "./tools/suggest.js";
|
|
20
|
+
import { handleStartChain, handleChainHandoff } from "./tools/chain.js";
|
|
21
|
+
import { handleReportViolation } from "./tools/violation.js";
|
|
22
|
+
import { handleWitnessModelIntegrity, handleWitnessAdapterStack } from "./tools/model.js";
|
|
23
|
+
import { handleAttestSkillManifest, handleAttestMemoryContext } from "./tools/skill.js";
|
|
24
|
+
import { handleVerifyAgentTrust, handlePresentCredential } from "./tools/trust.js";
|
|
16
25
|
import { readRegistry } from "./resources/registry.js";
|
|
17
26
|
import { readHealth } from "./resources/health.js";
|
|
18
27
|
import { REGISTRY_RESOURCE } from "./resources/registry.js";
|
|
19
28
|
import { HEALTH_RESOURCE } from "./resources/health.js";
|
|
20
|
-
|
|
29
|
+
import { verifyAnchorChain } from "./chain-verifier.js";
|
|
30
|
+
import { loadDensityPolicy } from "./density-policy.js";
|
|
31
|
+
import { startRedisReader, stopRedisReader, getReaderState } from "./redis-reader.js";
|
|
32
|
+
import { mintFingerprint, timestampMs, signPayload } from "./fingerprint.js";
|
|
33
|
+
/**
|
|
34
|
+
* Match a tool name against a glob pattern list.
|
|
35
|
+
* Supports * (any chars) and ? (single char).
|
|
36
|
+
*/
|
|
37
|
+
function matchesToolPattern(toolName, patterns) {
|
|
38
|
+
for (const pattern of patterns) {
|
|
39
|
+
const regex = new RegExp("^" + pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
40
|
+
.replace(/\*/g, ".*")
|
|
41
|
+
.replace(/\?/g, ".") + "$");
|
|
42
|
+
if (regex.test(toolName))
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
function initChainDensity(policy) {
|
|
48
|
+
if (!policy.maxVelocity && policy.maxChainDepth === undefined &&
|
|
49
|
+
!policy.toolAllowlist?.length && !policy.toolBlocklist?.length) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
let velocityLimit = 0, velocityWindowMs = 0;
|
|
53
|
+
if (policy.maxVelocity) {
|
|
54
|
+
const parts = policy.maxVelocity.split("/");
|
|
55
|
+
velocityLimit = parseInt(parts[0], 10);
|
|
56
|
+
velocityWindowMs = parseInt(parts[1].replace("s", ""), 10) * 1000;
|
|
57
|
+
}
|
|
58
|
+
const toRegex = (p) => new RegExp("^" + p.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".") + "$");
|
|
59
|
+
return {
|
|
60
|
+
velocityWindow: [],
|
|
61
|
+
velocityLimit,
|
|
62
|
+
velocityWindowMs,
|
|
63
|
+
chainDepth: 0,
|
|
64
|
+
maxChainDepth: policy.maxChainDepth ?? Infinity,
|
|
65
|
+
lastToolName: null,
|
|
66
|
+
blockPatterns: (policy.toolBlocklist ?? []).map(toRegex),
|
|
67
|
+
allowPatterns: policy.toolAllowlist?.length ? policy.toolAllowlist.map(toRegex) : null,
|
|
68
|
+
failSecure: policy.failSecure ?? true,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function checkChainDensity(state, toolName) {
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
// Blocklist
|
|
74
|
+
for (const p of state.blockPatterns) {
|
|
75
|
+
if (p.test(toolName))
|
|
76
|
+
return `Tool "${toolName}" is on the blocklist`;
|
|
77
|
+
}
|
|
78
|
+
// Allowlist
|
|
79
|
+
if (state.allowPatterns && !state.allowPatterns.some((p) => p.test(toolName))) {
|
|
80
|
+
return `Tool "${toolName}" is not on the allowlist`;
|
|
81
|
+
}
|
|
82
|
+
// Velocity
|
|
83
|
+
if (state.velocityLimit > 0) {
|
|
84
|
+
const cutoff = now - state.velocityWindowMs;
|
|
85
|
+
while (state.velocityWindow.length > 0 && state.velocityWindow[0] <= cutoff) {
|
|
86
|
+
state.velocityWindow.shift();
|
|
87
|
+
}
|
|
88
|
+
if (state.velocityWindow.length >= state.velocityLimit) {
|
|
89
|
+
if (state.failSecure)
|
|
90
|
+
return `Rate limit exceeded: ${state.velocityLimit} calls per ${state.velocityWindowMs / 1000}s`;
|
|
91
|
+
}
|
|
92
|
+
state.velocityWindow.push(now);
|
|
93
|
+
}
|
|
94
|
+
// Depth
|
|
95
|
+
if (state.maxChainDepth < Infinity) {
|
|
96
|
+
if (toolName !== state.lastToolName && state.lastToolName !== null)
|
|
97
|
+
state.chainDepth = 0;
|
|
98
|
+
state.chainDepth++;
|
|
99
|
+
state.lastToolName = toolName;
|
|
100
|
+
if (state.chainDepth > state.maxChainDepth) {
|
|
101
|
+
if (state.failSecure)
|
|
102
|
+
return `Chain depth ${state.chainDepth} exceeds max ${state.maxChainDepth}`;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
export function createServer(config, bundle) {
|
|
21
108
|
const client = new AxiomClient(config);
|
|
109
|
+
const sessionState = createSessionState(bundle?.trustMesh?.trustedTenants, bundle?.trustMesh?.deniedAgents);
|
|
110
|
+
const densityPolicy = bundle?.densityPolicy ?? loadDensityPolicy();
|
|
111
|
+
const mcpPolicy = bundle?.mcpPolicy ?? null;
|
|
112
|
+
const chainDensity = mcpPolicy ? initChainDensity(mcpPolicy) : null;
|
|
113
|
+
// Start Redis reader if chain verification is enabled
|
|
114
|
+
if (config.chainVerify) {
|
|
115
|
+
startRedisReader({ redisUrl: config.redisUrl, streamName: config.redisStream })
|
|
116
|
+
.then((ok) => {
|
|
117
|
+
sessionState.redisReader = getReaderState();
|
|
118
|
+
if (!ok) {
|
|
119
|
+
// Redis unavailable -- will fall back to ledger queries
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
.catch(() => { });
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Chain verification gate. Returns null if chain is valid (or gate disabled).
|
|
126
|
+
* Returns denial message string if chain verification fails.
|
|
127
|
+
*/
|
|
128
|
+
async function chainGate(args) {
|
|
129
|
+
if (!config.chainVerify || config.demo)
|
|
130
|
+
return null;
|
|
131
|
+
const agentId = args.agent_id || config.agentId;
|
|
132
|
+
const cycleId = args.cycle_id || sessionState.activeChain?.cycleId;
|
|
133
|
+
// No identity context -- cannot verify, deny
|
|
134
|
+
if (!agentId && !cycleId) {
|
|
135
|
+
return "Chain verification failed: no agent_id or cycle_id provided. " +
|
|
136
|
+
"Set SWT3_AGENT_ID or pass agent_id/cycle_id to enable chain verification.";
|
|
137
|
+
}
|
|
138
|
+
const result = await verifyAnchorChain(agentId, cycleId, config, client, densityPolicy, args.input_tokens);
|
|
139
|
+
if (result.valid)
|
|
140
|
+
return null;
|
|
141
|
+
// Mint AI-TRUST.1 FAIL anchor for the denial
|
|
142
|
+
const [ts, epoch] = timestampMs();
|
|
143
|
+
const fp = mintFingerprint(config.tenantId, "AI-TRUST.1", 1, 0, 0, ts);
|
|
144
|
+
if (!config.demo) {
|
|
145
|
+
client.postWitness({
|
|
146
|
+
procedure_id: "AI-TRUST.1",
|
|
147
|
+
factor_a: 1, factor_b: 0, factor_c: 0,
|
|
148
|
+
clearing_level: config.clearingLevel,
|
|
149
|
+
anchor_fingerprint: fp,
|
|
150
|
+
anchor_epoch: epoch,
|
|
151
|
+
fingerprint_timestamp_ms: ts,
|
|
152
|
+
ai_model_id: "chain-gate",
|
|
153
|
+
witness_source: "mcp",
|
|
154
|
+
...(agentId ? { agent_id: agentId } : {}),
|
|
155
|
+
...(cycleId ? { cycle_id: cycleId } : {}),
|
|
156
|
+
...(config.signingKey ? { payload_signature: signPayload(config.signingKey, fp, agentId) } : {}),
|
|
157
|
+
}).catch(() => { });
|
|
158
|
+
}
|
|
159
|
+
const violations = result.policyViolations.map((v) => ` - ${v.message}`).join("\n");
|
|
160
|
+
return [
|
|
161
|
+
`Chain Verification DENIED`,
|
|
162
|
+
`Reason: ${result.reason || "unknown"}`,
|
|
163
|
+
`Anchors found: ${result.anchorCount} (source: ${result.source})`,
|
|
164
|
+
...(result.gaps.length > 0 ? [`Gaps: ${result.gaps.length} (max gap: ${Math.max(...result.gaps.map((g) => g.gapSeconds))}s)`] : []),
|
|
165
|
+
...(result.revoked.length > 0 ? [`Revoked anchors: ${result.revoked.join(", ")}`] : []),
|
|
166
|
+
...(violations ? [`Policy violations:\n${violations}`] : []),
|
|
167
|
+
``,
|
|
168
|
+
`To pass chain verification, ensure your agent has recent SWT3 anchors`,
|
|
169
|
+
`linked by agent_id or cycle_id with no gaps exceeding ${config.maxChainGapSeconds}s.`,
|
|
170
|
+
].join("\n");
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* MCP tool policy gate. Checks if a tool name requires witnessing per the
|
|
174
|
+
* mcp_policy section of the YAML config. Returns:
|
|
175
|
+
* - "witness": tool must be witnessed (auto_witness or matched pattern)
|
|
176
|
+
* - "exempt": tool is explicitly exempt
|
|
177
|
+
* - "block": tool blocked due to trust level or failure policy
|
|
178
|
+
* - null: no mcp_policy configured, pass through
|
|
179
|
+
*/
|
|
180
|
+
function toolPolicyGate(toolName) {
|
|
181
|
+
if (!mcpPolicy)
|
|
182
|
+
return null;
|
|
183
|
+
// Exempt tools always pass through unwatched
|
|
184
|
+
if (mcpPolicy.exemptTools.length > 0 && matchesToolPattern(toolName, mcpPolicy.exemptTools)) {
|
|
185
|
+
return "exempt";
|
|
186
|
+
}
|
|
187
|
+
// Check if tool matches witnessed patterns
|
|
188
|
+
const isWitnessed = mcpPolicy.witnessedTools.length === 0
|
|
189
|
+
|| matchesToolPattern(toolName, mcpPolicy.witnessedTools);
|
|
190
|
+
if (!isWitnessed)
|
|
191
|
+
return "exempt";
|
|
192
|
+
// Trust level gate: use real verified trust level, fall back to heuristic
|
|
193
|
+
if (mcpPolicy.requireTrustLevel > 0) {
|
|
194
|
+
const sessionTrust = sessionState.verifiedTrustLevel
|
|
195
|
+
?? (sessionState.activeAuditSession ? 2 : (config.signingKey ? 1 : 0));
|
|
196
|
+
if (sessionTrust < mcpPolicy.requireTrustLevel) {
|
|
197
|
+
return "block";
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Chain density enforcement
|
|
201
|
+
if (chainDensity) {
|
|
202
|
+
const violation = checkChainDensity(chainDensity, toolName);
|
|
203
|
+
if (violation)
|
|
204
|
+
return "block";
|
|
205
|
+
}
|
|
206
|
+
return mcpPolicy.autoWitness ? "witness" : "exempt";
|
|
207
|
+
}
|
|
22
208
|
const server = new McpServer({
|
|
23
209
|
name: "swt3-mcp",
|
|
24
210
|
version: "0.1.0",
|
|
@@ -42,11 +228,17 @@ export function createServer(config) {
|
|
|
42
228
|
.describe("Data clearing level (0=analytics, 1=standard, 2=sensitive, 3=classified)"),
|
|
43
229
|
procedure: z.string().optional().describe("UCT procedure ID (default: AI-INF.1)"),
|
|
44
230
|
provider: z.string().optional().describe("AI provider name (openai, anthropic, bedrock, etc.)"),
|
|
231
|
+
agent_id: z.string().optional().describe("Agent identity for this inference (AI-ID.1)"),
|
|
232
|
+
cycle_id: z.string().optional().describe("Multi-agent chain link identifier"),
|
|
233
|
+
jurisdiction: z.string().optional().describe("ISO 3166-1 jurisdiction code (e.g., 'DE', 'US-VA')"),
|
|
234
|
+
legal_basis: z.string().optional().describe("GDPR legal basis (e.g., 'consent', 'legitimate_interest', 'contract')"),
|
|
235
|
+
purpose_class: z.string().optional().describe("Processing purpose classification (e.g., 'clinical_decision_support')"),
|
|
45
236
|
},
|
|
46
237
|
annotations: { readOnlyHint: false },
|
|
47
238
|
}, async (args) => {
|
|
48
239
|
try {
|
|
49
240
|
const text = await handleWitness(args, config, client);
|
|
241
|
+
trackProcedure(sessionState, args.procedure || "AI-INF.1");
|
|
50
242
|
return { content: [{ type: "text", text }] };
|
|
51
243
|
}
|
|
52
244
|
catch (err) {
|
|
@@ -139,6 +331,365 @@ export function createServer(config) {
|
|
|
139
331
|
};
|
|
140
332
|
}
|
|
141
333
|
});
|
|
334
|
+
server.registerTool("witness_authorization", {
|
|
335
|
+
description: "Witness an authorization decision as an AI-ACC.1 anchor. " +
|
|
336
|
+
"Records whether a resource access was granted or denied. " +
|
|
337
|
+
"FAIL anchors trigger alerts but never block execution." +
|
|
338
|
+
(config.demo ? " Currently in DEMO mode — anchors are minted locally. Use the signup tool to persist them." : ""),
|
|
339
|
+
inputSchema: {
|
|
340
|
+
resource: z.string().describe("Resource being accessed (e.g., 'prod-database', 'user-pii-store')"),
|
|
341
|
+
scope: z.string().optional().describe("Authorization scope (e.g., 'read-only', 'write', 'admin')"),
|
|
342
|
+
granted: z.boolean().describe("Whether access was granted (true) or denied (false)"),
|
|
343
|
+
agent_id: z.string().optional().describe("Agent identity requesting access (AI-ID.1)"),
|
|
344
|
+
cycle_id: z.string().optional().describe("Multi-agent chain link identifier"),
|
|
345
|
+
clearing_level: z.union([z.literal(0), z.literal(1), z.literal(2), z.literal(3)]).optional()
|
|
346
|
+
.describe("Data clearing level (0=analytics, 1=standard, 2=sensitive, 3=classified)"),
|
|
347
|
+
},
|
|
348
|
+
annotations: { readOnlyHint: false },
|
|
349
|
+
}, async (args) => {
|
|
350
|
+
try {
|
|
351
|
+
const denial = await chainGate(args);
|
|
352
|
+
if (denial)
|
|
353
|
+
return { content: [{ type: "text", text: denial }], isError: true };
|
|
354
|
+
const text = await handleAuthorize(args, config, client);
|
|
355
|
+
trackProcedure(sessionState, "AI-ACC.1");
|
|
356
|
+
return { content: [{ type: "text", text }] };
|
|
357
|
+
}
|
|
358
|
+
catch (err) {
|
|
359
|
+
return {
|
|
360
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
361
|
+
isError: true,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
// --- Compliance Discovery Tools ---
|
|
366
|
+
server.registerTool("start_audit_session", {
|
|
367
|
+
description: "Begin passive compliance tracking for this conversation. " +
|
|
368
|
+
"Records which procedures are witnessed. Call end_audit_session for a gap report. " +
|
|
369
|
+
"Purely observational -- never blocks execution.",
|
|
370
|
+
annotations: { readOnlyHint: true },
|
|
371
|
+
}, async () => {
|
|
372
|
+
try {
|
|
373
|
+
const text = handleStartAudit(sessionState);
|
|
374
|
+
return { content: [{ type: "text", text }] };
|
|
375
|
+
}
|
|
376
|
+
catch (err) {
|
|
377
|
+
return {
|
|
378
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
379
|
+
isError: true,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
server.registerTool("end_audit_session", {
|
|
384
|
+
description: "End the audit session and produce a compliance gap report. " +
|
|
385
|
+
"Shows which AI procedures were witnessed and which were missed.",
|
|
386
|
+
inputSchema: {
|
|
387
|
+
session_id: z.string().optional().describe("Session ID (uses active session if omitted)"),
|
|
388
|
+
},
|
|
389
|
+
annotations: { readOnlyHint: true },
|
|
390
|
+
}, async (args) => {
|
|
391
|
+
try {
|
|
392
|
+
const text = await handleEndAudit(args, sessionState, client);
|
|
393
|
+
return { content: [{ type: "text", text }] };
|
|
394
|
+
}
|
|
395
|
+
catch (err) {
|
|
396
|
+
return {
|
|
397
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
398
|
+
isError: true,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
server.registerTool("suggest_procedures", {
|
|
403
|
+
description: "Get advisory suggestions for which SWT3 procedures to witness based on context. " +
|
|
404
|
+
"Returns a ranked list of applicable procedures. Advisory only -- never enforced. " +
|
|
405
|
+
"No network call required.",
|
|
406
|
+
inputSchema: {
|
|
407
|
+
context: z.string().describe("What the agent is doing (e.g., 'calling GPT-4o to summarize a contract')"),
|
|
408
|
+
model_id: z.string().optional().describe("AI model being used"),
|
|
409
|
+
data_classification: z.string().optional().describe("Data sensitivity (public, internal, sensitive, classified)"),
|
|
410
|
+
tools_used: z.array(z.string()).optional().describe("Tools or functions being called"),
|
|
411
|
+
},
|
|
412
|
+
annotations: { readOnlyHint: true },
|
|
413
|
+
}, async (args) => {
|
|
414
|
+
try {
|
|
415
|
+
const text = handleSuggest(args);
|
|
416
|
+
return { content: [{ type: "text", text }] };
|
|
417
|
+
}
|
|
418
|
+
catch (err) {
|
|
419
|
+
return {
|
|
420
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
421
|
+
isError: true,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
// --- Multi-Agent Chain Tools ---
|
|
426
|
+
server.registerTool("start_chain", {
|
|
427
|
+
description: "Generate a cycle_id for a multi-agent chain. " +
|
|
428
|
+
"Pass the returned cycle_id to subsequent witness calls to link all anchors in the chain. " +
|
|
429
|
+
"Metadata only -- never blocks execution.",
|
|
430
|
+
inputSchema: {
|
|
431
|
+
description: z.string().optional().describe("Chain description (e.g., 'contract review pipeline')"),
|
|
432
|
+
},
|
|
433
|
+
annotations: { readOnlyHint: true },
|
|
434
|
+
}, async (args) => {
|
|
435
|
+
try {
|
|
436
|
+
const text = handleStartChain(args, sessionState);
|
|
437
|
+
return { content: [{ type: "text", text }] };
|
|
438
|
+
}
|
|
439
|
+
catch (err) {
|
|
440
|
+
return {
|
|
441
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
442
|
+
isError: true,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
server.registerTool("chain_handoff", {
|
|
447
|
+
description: "Witness a handoff between agents in a multi-agent chain. " +
|
|
448
|
+
"Mints an AI-CHAIN.1 anchor recording custody transfer. " +
|
|
449
|
+
"Evidence only -- never blocks execution." +
|
|
450
|
+
(config.demo ? " Currently in DEMO mode -- anchors are minted locally." : ""),
|
|
451
|
+
inputSchema: {
|
|
452
|
+
cycle_id: z.string().describe("Chain cycle_id from start_chain"),
|
|
453
|
+
from_agent: z.string().describe("Agent handing off (e.g., 'summarizer-agent')"),
|
|
454
|
+
to_agent: z.string().describe("Agent receiving handoff (e.g., 'reviewer-agent')"),
|
|
455
|
+
context: z.string().optional().describe("What is being handed off"),
|
|
456
|
+
clearing_level: z.union([z.literal(0), z.literal(1), z.literal(2), z.literal(3)]).optional()
|
|
457
|
+
.describe("Data clearing level (0=analytics, 1=standard, 2=sensitive, 3=classified)"),
|
|
458
|
+
},
|
|
459
|
+
annotations: { readOnlyHint: false },
|
|
460
|
+
}, async (args) => {
|
|
461
|
+
try {
|
|
462
|
+
const text = await handleChainHandoff(args, config, client);
|
|
463
|
+
trackProcedure(sessionState, "AI-CHAIN.1");
|
|
464
|
+
return { content: [{ type: "text", text }] };
|
|
465
|
+
}
|
|
466
|
+
catch (err) {
|
|
467
|
+
return {
|
|
468
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
469
|
+
isError: true,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
// --- Self-Attestation Tools ---
|
|
474
|
+
server.registerTool("report_violation", {
|
|
475
|
+
description: "Voluntarily self-report a policy violation. " +
|
|
476
|
+
"Mints a FAIL anchor as evidence. Never blocks execution. " +
|
|
477
|
+
"FAIL anchors trigger downstream alerts via the existing pipeline." +
|
|
478
|
+
(config.demo ? " Currently in DEMO mode -- anchors are minted locally." : ""),
|
|
479
|
+
inputSchema: {
|
|
480
|
+
violation_type: z.string().describe("Type of violation (e.g., 'unauthorized_model', 'data_leak', 'jurisdiction_mismatch')"),
|
|
481
|
+
description: z.string().describe("Description of what happened"),
|
|
482
|
+
severity: z.string().optional().describe("Severity level (low, medium, high, critical). Default: medium"),
|
|
483
|
+
agent_id: z.string().optional().describe("Agent identity reporting the violation"),
|
|
484
|
+
cycle_id: z.string().optional().describe("Multi-agent chain link identifier"),
|
|
485
|
+
clearing_level: z.union([z.literal(0), z.literal(1), z.literal(2), z.literal(3)]).optional()
|
|
486
|
+
.describe("Data clearing level (0=analytics, 1=standard, 2=sensitive, 3=classified)"),
|
|
487
|
+
},
|
|
488
|
+
annotations: { readOnlyHint: false },
|
|
489
|
+
}, async (args) => {
|
|
490
|
+
try {
|
|
491
|
+
const text = await handleReportViolation(args, config, client);
|
|
492
|
+
trackProcedure(sessionState, "AI-VIO.1");
|
|
493
|
+
return { content: [{ type: "text", text }] };
|
|
494
|
+
}
|
|
495
|
+
catch (err) {
|
|
496
|
+
return {
|
|
497
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
498
|
+
isError: true,
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
// --- Model Weight & Adapter Tools ---
|
|
503
|
+
server.registerTool("witness_model_integrity", {
|
|
504
|
+
description: "Witness model weight file integrity (AI-MDL.5). " +
|
|
505
|
+
"Verifies the SHA-256 hash of model weights against an expected value. " +
|
|
506
|
+
"Evidence only -- never blocks execution." +
|
|
507
|
+
(config.demo ? " Currently in DEMO mode -- anchors are minted locally." : ""),
|
|
508
|
+
inputSchema: {
|
|
509
|
+
model_id: z.string().describe("Model identifier (e.g., 'llama-3.1-70b-instruct')"),
|
|
510
|
+
weight_hash: z.string().describe("SHA-256 hash of the model weight file"),
|
|
511
|
+
expected_hash: z.string().optional().describe("Expected hash for verification. Omit to attest without verification."),
|
|
512
|
+
format: z.string().optional().describe("Weight file format (safetensors, gguf, bin, pt)"),
|
|
513
|
+
file_size_bytes: z.number().optional().describe("Weight file size in bytes"),
|
|
514
|
+
agent_id: z.string().optional().describe("Agent identity"),
|
|
515
|
+
cycle_id: z.string().optional().describe("Multi-agent chain link identifier"),
|
|
516
|
+
clearing_level: z.union([z.literal(0), z.literal(1), z.literal(2), z.literal(3)]).optional()
|
|
517
|
+
.describe("Data clearing level (0=analytics, 1=standard, 2=sensitive, 3=classified)"),
|
|
518
|
+
},
|
|
519
|
+
annotations: { readOnlyHint: false },
|
|
520
|
+
}, async (args) => {
|
|
521
|
+
try {
|
|
522
|
+
const denial = await chainGate(args);
|
|
523
|
+
if (denial)
|
|
524
|
+
return { content: [{ type: "text", text: denial }], isError: true };
|
|
525
|
+
const text = await handleWitnessModelIntegrity(args, config, client);
|
|
526
|
+
trackProcedure(sessionState, "AI-MDL.5");
|
|
527
|
+
return { content: [{ type: "text", text }] };
|
|
528
|
+
}
|
|
529
|
+
catch (err) {
|
|
530
|
+
return {
|
|
531
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
532
|
+
isError: true,
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
server.registerTool("witness_adapter_stack", {
|
|
537
|
+
description: "Witness active LoRA/QLoRA/PEFT adapter stack (AI-MDL.6). " +
|
|
538
|
+
"Records which adapters are loaded on top of a base model. " +
|
|
539
|
+
"Evidence only -- never blocks execution." +
|
|
540
|
+
(config.demo ? " Currently in DEMO mode -- anchors are minted locally." : ""),
|
|
541
|
+
inputSchema: {
|
|
542
|
+
base_model: z.string().describe("Base model identifier (e.g., 'llama-3.1-70b')"),
|
|
543
|
+
adapters: z.array(z.object({
|
|
544
|
+
name: z.string().describe("Adapter name"),
|
|
545
|
+
hash: z.string().describe("SHA-256 hash of adapter weights"),
|
|
546
|
+
base_model: z.string().optional().describe("Base model this adapter was trained on"),
|
|
547
|
+
})).describe("List of active adapters"),
|
|
548
|
+
agent_id: z.string().optional().describe("Agent identity"),
|
|
549
|
+
cycle_id: z.string().optional().describe("Multi-agent chain link identifier"),
|
|
550
|
+
clearing_level: z.union([z.literal(0), z.literal(1), z.literal(2), z.literal(3)]).optional()
|
|
551
|
+
.describe("Data clearing level"),
|
|
552
|
+
},
|
|
553
|
+
annotations: { readOnlyHint: false },
|
|
554
|
+
}, async (args) => {
|
|
555
|
+
try {
|
|
556
|
+
const denial = await chainGate(args);
|
|
557
|
+
if (denial)
|
|
558
|
+
return { content: [{ type: "text", text: denial }], isError: true };
|
|
559
|
+
const text = await handleWitnessAdapterStack(args, config, client);
|
|
560
|
+
trackProcedure(sessionState, "AI-MDL.6");
|
|
561
|
+
return { content: [{ type: "text", text }] };
|
|
562
|
+
}
|
|
563
|
+
catch (err) {
|
|
564
|
+
return {
|
|
565
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
566
|
+
isError: true,
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
// --- Skill & Memory Attestation Tools ---
|
|
571
|
+
server.registerTool("attest_skill_manifest", {
|
|
572
|
+
description: "Attest the active skill/tool/plugin manifest (AI-SKILL.1). " +
|
|
573
|
+
"Records which capabilities are loaded. " +
|
|
574
|
+
"Evidence only -- never blocks execution." +
|
|
575
|
+
(config.demo ? " Currently in DEMO mode -- anchors are minted locally." : ""),
|
|
576
|
+
inputSchema: {
|
|
577
|
+
skills: z.array(z.object({
|
|
578
|
+
name: z.string().describe("Skill name"),
|
|
579
|
+
version: z.string().optional().describe("Skill version"),
|
|
580
|
+
hash: z.string().optional().describe("SHA-256 hash of skill definition"),
|
|
581
|
+
})).describe("List of active skills/tools/plugins"),
|
|
582
|
+
expected_manifest_hash: z.string().optional().describe("Expected manifest hash for verification"),
|
|
583
|
+
agent_id: z.string().optional().describe("Agent identity"),
|
|
584
|
+
cycle_id: z.string().optional().describe("Multi-agent chain link identifier"),
|
|
585
|
+
clearing_level: z.union([z.literal(0), z.literal(1), z.literal(2), z.literal(3)]).optional()
|
|
586
|
+
.describe("Data clearing level"),
|
|
587
|
+
},
|
|
588
|
+
annotations: { readOnlyHint: false },
|
|
589
|
+
}, async (args) => {
|
|
590
|
+
try {
|
|
591
|
+
const denial = await chainGate(args);
|
|
592
|
+
if (denial)
|
|
593
|
+
return { content: [{ type: "text", text: denial }], isError: true };
|
|
594
|
+
const text = await handleAttestSkillManifest(args, config, client);
|
|
595
|
+
trackProcedure(sessionState, "AI-SKILL.1");
|
|
596
|
+
return { content: [{ type: "text", text }] };
|
|
597
|
+
}
|
|
598
|
+
catch (err) {
|
|
599
|
+
return {
|
|
600
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
601
|
+
isError: true,
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
server.registerTool("attest_memory_context", {
|
|
606
|
+
description: "Attest persistent memory sources influencing decisions (AI-SKILL.2). " +
|
|
607
|
+
"Records which memory stores (vector DBs, conversation history, etc.) are active. " +
|
|
608
|
+
"Evidence only -- never blocks execution." +
|
|
609
|
+
(config.demo ? " Currently in DEMO mode -- anchors are minted locally." : ""),
|
|
610
|
+
inputSchema: {
|
|
611
|
+
memory_sources: z.array(z.object({
|
|
612
|
+
type: z.string().describe("Memory source type (vector_store, conversation, scratchpad, knowledge_base)"),
|
|
613
|
+
id: z.string().optional().describe("Source identifier"),
|
|
614
|
+
hash: z.string().optional().describe("SHA-256 hash of memory contents"),
|
|
615
|
+
})).describe("List of active memory sources"),
|
|
616
|
+
agent_id: z.string().optional().describe("Agent identity"),
|
|
617
|
+
cycle_id: z.string().optional().describe("Multi-agent chain link identifier"),
|
|
618
|
+
clearing_level: z.union([z.literal(0), z.literal(1), z.literal(2), z.literal(3)]).optional()
|
|
619
|
+
.describe("Data clearing level"),
|
|
620
|
+
},
|
|
621
|
+
annotations: { readOnlyHint: false },
|
|
622
|
+
}, async (args) => {
|
|
623
|
+
try {
|
|
624
|
+
const denial = await chainGate(args);
|
|
625
|
+
if (denial)
|
|
626
|
+
return { content: [{ type: "text", text: denial }], isError: true };
|
|
627
|
+
const text = await handleAttestMemoryContext(args, config, client);
|
|
628
|
+
trackProcedure(sessionState, "AI-SKILL.2");
|
|
629
|
+
return { content: [{ type: "text", text }] };
|
|
630
|
+
}
|
|
631
|
+
catch (err) {
|
|
632
|
+
return {
|
|
633
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
634
|
+
isError: true,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
server.registerTool("verify_agent_trust", {
|
|
639
|
+
description: "Verify a counterpart agent's compliance posture before exchanging data or calling their tools (AI-TRUST.1). " +
|
|
640
|
+
"Checks: deny list, tenant trust, anchor freshness, signing status. " +
|
|
641
|
+
"Returns trust level (denied/basic/verified/attested/sovereign). " +
|
|
642
|
+
"Both PASS and FAIL produce cryptographic evidence anchors." +
|
|
643
|
+
(config.demo ? " Currently in DEMO mode -- anchors are minted locally." : ""),
|
|
644
|
+
inputSchema: {
|
|
645
|
+
counterpart_agent_id: z.string().describe("Agent ID of the counterpart to verify"),
|
|
646
|
+
counterpart_tenant_id: z.string().describe("Tenant ID of the counterpart agent"),
|
|
647
|
+
anchor_fingerprint: z.string().describe("Counterpart's latest SWT3 anchor fingerprint (12 hex chars)"),
|
|
648
|
+
anchor_timestamp_ms: z.number().optional().describe("When the counterpart's anchor was minted (ms since epoch)"),
|
|
649
|
+
is_signed: z.boolean().optional().describe("Whether the counterpart's anchor carries a payload signature"),
|
|
650
|
+
procedures: z.array(z.string()).optional().describe("UCT procedures the counterpart has witnessed"),
|
|
651
|
+
clearing_level: z.union([z.literal(0), z.literal(1), z.literal(2), z.literal(3)]).optional()
|
|
652
|
+
.describe("Counterpart's clearing level"),
|
|
653
|
+
has_hardware_attestation: z.boolean().optional().describe("Counterpart has AI-HW.1 hardware attestation"),
|
|
654
|
+
has_guardrails: z.boolean().optional().describe("Counterpart has active guardrails"),
|
|
655
|
+
agent_id: z.string().optional().describe("This agent's identity"),
|
|
656
|
+
cycle_id: z.string().optional().describe("Multi-agent chain link identifier"),
|
|
657
|
+
},
|
|
658
|
+
annotations: { readOnlyHint: false },
|
|
659
|
+
}, async (args) => {
|
|
660
|
+
try {
|
|
661
|
+
const text = await handleVerifyAgentTrust(args, config, client, sessionState);
|
|
662
|
+
trackProcedure(sessionState, "AI-TRUST.1");
|
|
663
|
+
return { content: [{ type: "text", text }] };
|
|
664
|
+
}
|
|
665
|
+
catch (err) {
|
|
666
|
+
return {
|
|
667
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
668
|
+
isError: true,
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
server.registerTool("present_trust_credential", {
|
|
673
|
+
description: "Get this agent's trust credential for presentation to another agent. " +
|
|
674
|
+
"Returns agent_id, tenant_id, anchor fingerprint, and trust metadata. " +
|
|
675
|
+
"Pass these fields to another agent's verify_agent_trust tool " +
|
|
676
|
+
"to establish mutual compliance trust before exchanging data.",
|
|
677
|
+
inputSchema: {
|
|
678
|
+
agent_id: z.string().optional().describe("Override agent identity for this credential"),
|
|
679
|
+
},
|
|
680
|
+
annotations: { readOnlyHint: true },
|
|
681
|
+
}, async (args) => {
|
|
682
|
+
try {
|
|
683
|
+
const text = handlePresentCredential(args, config);
|
|
684
|
+
return { content: [{ type: "text", text }] };
|
|
685
|
+
}
|
|
686
|
+
catch (err) {
|
|
687
|
+
return {
|
|
688
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
689
|
+
isError: true,
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
});
|
|
142
693
|
// --- Resources ---
|
|
143
694
|
server.registerResource(REGISTRY_RESOURCE.name, REGISTRY_RESOURCE.uri, { mimeType: REGISTRY_RESOURCE.mimeType, description: REGISTRY_RESOURCE.description }, async () => ({
|
|
144
695
|
contents: [
|
|
@@ -158,6 +709,9 @@ export function createServer(config) {
|
|
|
158
709
|
},
|
|
159
710
|
],
|
|
160
711
|
}));
|
|
712
|
+
// Graceful shutdown hook for Redis reader
|
|
713
|
+
process.on("SIGTERM", () => { stopRedisReader(); });
|
|
714
|
+
process.on("SIGINT", () => { stopRedisReader(); });
|
|
161
715
|
return server;
|
|
162
716
|
}
|
|
163
717
|
//# sourceMappingURL=server.js.map
|