@kya-os/mcp-i
Version:
The TypeScript MCP framework with identity features built-in
788 lines (787 loc) • 39.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.addToolsToServer = addToolsToServer;
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
const proof_1 = require("../proof");
const identity_1 = require("../identity");
const tool_protection_registry_1 = require("../tool-protection-registry");
const auth_handshake_1 = require("../auth-handshake");
const session_1 = require("../session");
const request_context_1 = require("../request-context");
const outbound_identity_bridge_1 = require("../outbound-identity-bridge");
// C4 drain: delegation-layer symbol stays on @kya-os/mcp-i-core (src/delegation/** not relocated to mcp-i-runtime - pinned by E3.5 #2904).
const mcp_i_core_1 = require("@kya-os/mcp-i-core");
// C2/C3: @kya-os/mcp-i is CJS; @kya-os/mcp is ESM-only (TS1479 blocks static import).
const proof_batch_queue_1 = require("../proof-batch-queue");
const mcp_i_runtime_1 = require("@kya-os/mcp-i-runtime");
/**
* Format authorization info for error messages.
* Safely handles discriminated union types by checking for property existence.
* Supports: provider (oauth/password), issuer (mdl/vc), credentialType (vc/mdl)
*/
function formatAuthInfo(auth) {
let result = auth.type;
if ("provider" in auth && auth.provider) {
result += `:${auth.provider}`;
}
if ("issuer" in auth && auth.issuer) {
result += `:${auth.issuer}`;
}
if ("credentialType" in auth && auth.credentialType) {
result += `:${auth.credentialType}`;
}
return result;
}
const rawRuntimeConfigPath = typeof RUNTIME_CONFIG_PATH !== "undefined" ? RUNTIME_CONFIG_PATH : undefined;
// Single-parse to match single-stringify from webpack DefinePlugin
const runtimeConfigPath = rawRuntimeConfigPath
? typeof rawRuntimeConfigPath === "string"
? JSON.parse(rawRuntimeConfigPath)
: rawRuntimeConfigPath
: null;
/** Validates if a value is a valid Zod schema object */
function isZodRawShape(value) {
if (typeof value !== "object" || value === null) {
return false;
}
const obj = value;
return Object.entries(obj).every(([key, val]) => {
if (typeof key !== "string")
return false;
if (typeof val !== "object" || val === null)
return false;
if (!("parse" in val) || typeof val.parse !== "function")
return false;
return true;
});
}
function pathToName(path) {
const fileName = path.split("/").pop() || path;
return fileName.replace(/\.[^/.]+$/, "");
}
// Global ProofBatchQueue instance (singleton)
let globalProofQueue = null;
/** Load runtime config and initialize ProofBatchQueue if configured */
async function initializeProofBatchQueue(debug) {
// Return existing instance if already initialized
if (globalProofQueue) {
return globalProofQueue;
}
// Check if runtime config path is available
if (!runtimeConfigPath) {
if (debug) {
console.error("[MCPI] No runtime config found - proof submission disabled");
}
return null;
}
try {
if (debug) {
console.error("[MCPI] Loading runtime config from:", runtimeConfigPath);
}
// Dynamically import the runtime config
const configModule = await import(runtimeConfigPath);
const runtimeConfig = typeof configModule.getRuntimeConfig === "function"
? configModule.getRuntimeConfig()
: configModule.default;
// Check if proofing is enabled
if (!runtimeConfig?.proofing?.enabled) {
if (debug) {
console.error("[MCPI] Proofing is disabled in runtime config");
}
return null;
}
const proofingConfig = runtimeConfig.proofing;
if (!proofingConfig.batchQueue?.destinations ||
proofingConfig.batchQueue.destinations.length === 0) {
if (debug) {
console.error("[MCPI] No proof destinations configured");
}
return null;
}
// Create proof destinations
const destinations = [];
for (const destConfig of proofingConfig.batchQueue.destinations) {
if (destConfig.type === "agentshield") {
destinations.push(new proof_batch_queue_1.AgentShieldProofDestination(destConfig.apiUrl, destConfig.apiKey));
if (debug) {
console.error(`[MCPI] Added AgentShield destination: ${destConfig.apiUrl}`);
}
}
else if (destConfig.type === "kta") {
destinations.push(new proof_batch_queue_1.KTAProofDestination(destConfig.apiUrl, destConfig.apiKey));
if (debug) {
console.error(`[MCPI] Added KTA destination: ${destConfig.apiUrl}`);
}
}
else {
console.warn(`[MCPI] Unsupported proof destination type "${destConfig.type}" - skipping`);
}
}
if (destinations.length === 0) {
if (debug) {
console.error("[MCPI] No valid proof destinations configured");
}
return null;
}
// Initialize ProofBatchQueue
globalProofQueue = new proof_batch_queue_1.ProofBatchQueue({
destinations,
maxBatchSize: proofingConfig.batchQueue.maxBatchSize || 10,
flushIntervalMs: proofingConfig.batchQueue.flushIntervalMs || 5000,
maxRetries: proofingConfig.batchQueue.maxRetries || 3,
debug: proofingConfig.batchQueue.debug || debug,
});
if (debug) {
console.error(`[MCPI] ProofBatchQueue initialized (batch: ${proofingConfig.batchQueue.maxBatchSize}, ` +
`flush: ${proofingConfig.batchQueue.flushIntervalMs}ms, ` +
`destinations: ${destinations.length})`);
}
return globalProofQueue;
}
catch (error) {
if (debug) {
console.error("[MCPI] Failed to initialize ProofBatchQueue:", error);
}
return null;
}
}
if (typeof global !== "undefined") {
global.__MCPI_HANDLERS_REGISTERED__ =
global.__MCPI_HANDLERS_REGISTERED__ || false;
}
/** Loads tools and injects them into the server */
async function addToolsToServer(server, toolModules, identityConfig, clientMessagesConfig) {
// Guard against double registration using global scope
// Set this IMMEDIATELY to prevent race conditions
if (typeof global !== "undefined") {
if (global.__MCPI_HANDLERS_REGISTERED__) {
if (identityConfig?.debug) {
console.error("[MCPI] Handlers already registered, skipping duplicate registration");
}
return server;
}
// Mark as registered RIGHT NOW before doing anything else
global.__MCPI_HANDLERS_REGISTERED__ = true;
}
if (identityConfig?.debug) {
console.error("[MCPI] Registering handlers for the first time");
}
// Initialize session manager for handshake support
const sessionManager = new session_1.SessionManager();
// Initialize identity manager if identity is enabled
let identityManager = null;
if (identityConfig?.enabled) {
try {
identityManager = new identity_1.IdentityManager({
environment: identityConfig.environment ||
"development",
devIdentityPath: identityConfig.devIdentityPath,
debug: identityConfig.debug,
});
// Ensure identity exists (loads or generates it)
const identity = await identityManager.ensureIdentity();
if (identityConfig.debug) {
console.error(`[MCPI] Identity enabled - DID: ${identity.did}`);
console.error(`[MCPI] Identity path: ${identityConfig.devIdentityPath || ".mcpi/identity.json"}`);
}
}
catch (error) {
if (identityConfig?.debug) {
console.error("[MCPI] Failed to initialize identity:", error);
}
// Continue without identity if initialization fails
// Set identityManager to null to prevent later unhandled calls to ensureIdentity()
identityManager = null;
}
}
// Initialize ProofBatchQueue for proof submission (if runtime config exists)
await initializeProofBatchQueue(identityConfig?.debug);
// Collect all tools and their handlers
const tools = [];
const toolHandlers = new Map();
toolModules.forEach((toolModule, path) => {
const defaultName = pathToName(path);
const toolConfig = {
name: defaultName,
description: "No description provided",
};
let toolSchema = {};
const { default: handler, metadata, schema } = toolModule;
if (typeof metadata === "object" && metadata !== null) {
Object.assign(toolConfig, metadata);
}
// Validate and ensure schema is properly typed
if (isZodRawShape(schema)) {
Object.assign(toolSchema, schema);
}
else if (schema !== undefined && schema !== null) {
console.warn(`Invalid schema for tool "${toolConfig.name}" at ${path}. Expected Record<string, z.ZodType>`);
}
// Make sure tools has annotations with a title
if (toolConfig.annotations === undefined) {
toolConfig.annotations = {};
}
if (toolConfig.annotations.title === undefined) {
toolConfig.annotations.title = toolConfig.name;
}
// Create tool definition for MCP
const tool = {
name: toolConfig.name,
description: toolConfig.description,
inputSchema: {
type: "object",
properties: Object.fromEntries(Object.entries(toolSchema).map(([key, zodType]) => [
key,
{ type: "string" }, // Simplified - in real implementation, convert Zod to JSON Schema
])),
},
};
tools.push(tool);
toolHandlers.set(toolConfig.name, handler);
});
// Debug: log server state before registering handler
if (identityConfig?.debug) {
console.error("[MCPI] About to register handlers (tools/list, tools/call, handshake)");
}
// Set server DID on session manager after identity is loaded
if (identityManager) {
try {
const identity = await identityManager.ensureIdentity();
sessionManager.setServerDid(identity.did);
}
catch (error) {
// If identity initialization failed earlier, ensureIdentity() may throw again
// Log but don't crash - server can operate without identity
if (identityConfig?.debug) {
console.error("[MCPI] Failed to set server DID on session manager:", error);
}
// Set to null to prevent future attempts
identityManager = null;
}
}
// Add internal handshake tool for MCP-I session establishment
// This allows MCP clients to perform a handshake and receive a sessionId
const handshakeTool = {
name: "_kyaos_handshake",
description: "Internal MCP-I handshake tool for session establishment. Clients should call this before protected tools.",
inputSchema: {
type: "object",
properties: {
nonce: {
type: "string",
description: "Unique nonce for replay prevention",
},
audience: {
type: "string",
description: "Target audience (server DID or URL)",
},
timestamp: { type: "number", description: "Unix timestamp in seconds" },
agentDid: {
type: "string",
description: "Agent DID for delegation verification (optional)",
},
clientInfo: {
type: "object",
description: "MCP client metadata (optional)",
properties: {
name: { type: "string" },
version: { type: "string" },
platform: { type: "string" },
vendor: { type: "string" },
},
},
clientProtocolVersion: {
type: "string",
description: "MCP protocol version (optional)",
},
clientCapabilities: {
type: "object",
description: "MCP client capabilities (optional)",
additionalProperties: true,
},
},
required: ["nonce", "audience", "timestamp"],
},
};
tools.push(handshakeTool);
// Handshake tool handler. Registered under the canonical `_kyaos_` name; the
// dispatch lookup normalizes a legacy `_mcpi_handshake` call to this key
// (C4 #2918 dual-accept).
toolHandlers.set("_kyaos_handshake", async (args) => {
// Validate required fields explicitly before conversion
const missingFields = [];
if (!args.nonce ||
(typeof args.nonce === "string" && args.nonce.trim().length === 0)) {
missingFields.push("nonce");
}
if (!args.audience ||
(typeof args.audience === "string" && args.audience.trim().length === 0)) {
missingFields.push("audience");
}
// Validate timestamp is present and valid
if (args.timestamp === undefined ||
args.timestamp === null ||
typeof args.timestamp !== "number" ||
args.timestamp <= 0 ||
!Number.isInteger(args.timestamp)) {
if (args.timestamp === undefined || args.timestamp === null) {
missingFields.push("timestamp");
}
else {
// Invalid format (not a positive integer)
return {
success: false,
error: {
code: "XMCP_I_EHANDSHAKE",
message: "Invalid timestamp: must be a positive integer (Unix timestamp in seconds)",
details: {
received: typeof args.timestamp,
value: args.timestamp,
},
},
};
}
}
if (missingFields.length > 0) {
return {
success: false,
error: {
code: "XMCP_I_EHANDSHAKE",
message: `Missing required field(s): ${missingFields.join(", ")}`,
details: {
missingFields,
requiredFields: ["nonce", "audience", "timestamp"],
},
},
};
}
// At this point, timestamp is guaranteed to be a valid positive integer
const timestamp = args.timestamp;
// Build handshake request from validated tool arguments
const handshakeRequest = {
nonce: String(args.nonce).trim(),
audience: String(args.audience).trim(),
timestamp,
agentDid: typeof args.agentDid === "string" && args.agentDid.trim().length > 0
? args.agentDid.trim()
: undefined,
clientInfo: typeof args.clientInfo === "object" && args.clientInfo !== null
? args.clientInfo
: undefined,
clientProtocolVersion: typeof args.clientProtocolVersion === "string" &&
args.clientProtocolVersion.trim().length > 0
? args.clientProtocolVersion.trim()
: undefined,
clientCapabilities: typeof args.clientCapabilities === "object" &&
args.clientCapabilities !== null
? args.clientCapabilities
: undefined,
};
// Double-check format (should always pass at this point, but defensive)
if (!(0, session_1.validateHandshakeFormat)(handshakeRequest)) {
return {
success: false,
error: {
code: "XMCP_I_EHANDSHAKE",
message: "Invalid handshake request format. Required: nonce, audience, timestamp",
},
};
}
// Validate handshake and create session
const result = await sessionManager.validateHandshake(handshakeRequest);
// Log clientInfo in development mode
if (identityConfig?.debug &&
result.success &&
result.session?.clientInfo) {
console.error("[MCPI] Client connected:", {
name: result.session.clientInfo.name,
version: result.session.clientInfo.version,
platform: result.session.clientInfo.platform,
vendor: result.session.clientInfo.vendor,
clientId: result.session.clientInfo.clientId,
});
}
if (result.success && result.session) {
return {
success: true,
sessionId: result.session.sessionId,
serverDid: result.session.serverDid,
ttlMinutes: result.session.ttlMinutes,
...(result.session.clientInfo && {
clientInfo: result.session.clientInfo,
}),
};
}
return {
success: false,
error: result.error,
};
});
// Register tools/list handler
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async (request) => {
return {
tools: tools,
};
});
// Register tools/call handler
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
// Extract sessionId from request metadata (XMCP-I extension)
const sessionId = request._meta?.sessionId;
let session = null;
if (sessionId) {
session = await sessionManager.getSession(sessionId);
if (!session && identityConfig?.debug) {
console.error(`[MCPI] Session not found or expired: ${sessionId}`);
}
}
// Wrap tool execution with request context for AsyncLocalStorage
return (0, request_context_1.runWithContext)({
session: session || undefined,
requestId: request._meta?.requestId,
startTime: Date.now(),
}, async () => {
const { name: rawName, arguments: args } = request.params;
// C4 (#2918) dual-accept: normalize a legacy `_mcpi_*` internal tool
// name to its canonical `_kyaos_*` form before dispatch, and count the
// wire form for the sunset gate. Ordinary tool names pass through.
const name = (0, mcp_i_runtime_1.normalizeInternalToolName)(rawName);
if ((0, mcp_i_runtime_1.isInternalToolName)(rawName)) {
(0, mcp_i_runtime_1.recordWireForm)("tool_name", name === rawName ? "new" : "old", {
tool: name,
});
}
const handler = toolHandlers.get(name);
if (!handler) {
return {
content: [
{
type: "text",
text: `Tool "${name}" not found`,
},
],
isError: true,
};
}
// PHASE 1.5: Check if tool is protected BEFORE executing
const toolProtection = tool_protection_registry_1.toolProtectionRegistry.get(name);
if (toolProtection?.requiresDelegation) {
const authConfig = tool_protection_registry_1.toolProtectionRegistry.getAuthConfig();
const delegationVerifier = tool_protection_registry_1.toolProtectionRegistry.getDelegationVerifier();
if (!delegationVerifier || !authConfig) {
if (identityConfig?.debug) {
console.error(`[MCPI] Tool "${name}" requires delegation but verifier not configured`);
}
return {
content: [
{
type: "text",
text: `❌ Tool "${name}" requires delegation verification, but the delegation system is not configured.\n\n` +
`To fix this:\n` +
`1. Configure a delegation verifier:\n` +
` - MemoryDelegationVerifier (development/testing)\n` +
` - KVDelegationVerifier (production with KV store)\n` +
` - AgentShieldVerifier (production with AgentShield service)\n\n` +
`2. Enable delegation in your runtime config:\n` +
` const runtime = new MCPIRuntime({\n` +
` delegation: { enabled: true },\n` +
` // ... other config\n` +
` });\n\n` +
`3. Ensure the verifier is initialized before tool execution.\n\n` +
`📚 Documentation: https://docs.mcp-i.dev/delegation/setup`,
},
],
isError: true,
};
}
// Extract agent DID from current request context (AsyncLocalStorage)
const agentDid = (0, request_context_1.getCurrentAgentDid)();
if (!agentDid) {
if (identityConfig?.debug) {
console.error(`[MCPI] Tool "${name}" requires delegation but no agent DID in session`);
}
return {
content: [
{
type: "text",
text: `❌ Tool "${name}" requires delegation, but no agent identity was found in the current session.\n\n` +
`This usually means one of the following:\n\n` +
`1. **No handshake was performed**: The MCP client must send a handshake request before calling protected tools.\n` +
` Example handshake:\n` +
` {\n` +
` "agentDid": "did:key:z6Mk...",\n` +
` "nonce": "unique-value",\n` +
` "audience": "server-did",\n` +
` "timestamp": ${Math.floor(Date.now() / 1000)}\n` +
` }\n\n` +
`2. **Handshake missing agentDid**: Ensure the handshake request includes the 'agentDid' field.\n\n` +
`3. **Session expired**: The session may have expired. Try performing a new handshake.\n\n` +
`4. **Request missing sessionId**: Ensure tool call requests include _meta.sessionId from the handshake response.\n\n` +
`📚 Learn more: https://docs.mcp-i.dev/delegation/handshake`,
},
],
isError: true,
};
}
if (identityConfig?.debug) {
console.error(`[MCPI] Tool "${name}" requires delegation - verifying scopes: ${toolProtection.requiredScopes?.join(", ")}`);
}
// Verify delegation
const verifyResult = await (0, auth_handshake_1.verifyOrHints)(agentDid, toolProtection.requiredScopes || [], authConfig);
// If not authorized, return needs_authorization error
if (!verifyResult.authorized) {
if (identityConfig?.debug) {
console.error(`[MCPI] Tool "${name}" blocked - authorization required`);
}
// Format human-readable message using DelegationErrorFormatter
// Supports client-specific messages via clientMessagesConfig
// The formatter may modify the URL (e.g., adding extraParams from config)
const formatter = new mcp_i_runtime_1.DelegationErrorFormatter(clientMessagesConfig);
const { message: displayMessage, consentUrl } = formatter.format({
toolName: name,
consentUrl: verifyResult.authError?.authorizationUrl,
scopes: verifyResult.authError?.scopes,
// TODO: Client-specific message formatting not yet available for Node.js runtime.
// The `findKnownClient()` function that maps client names to IDs (claude, chatgpt, etc.)
// is currently in @kya-os/mcp-i-cloudflare. To enable client-specific messages here,
// consider moving findKnownClient to @kya-os/mcp-i-core for cross-platform use.
// See: packages/mcp-i-cloudflare/src/utils/known-clients.ts
clientId: undefined,
});
// Return MCP error format with authorization hints
// Includes both human-readable displayMessage and structured data
// Use formatter's consentUrl (may include extraParams) for consistency with displayMessage
return {
content: [
{
type: "text",
text: JSON.stringify({
error: "needs_authorization",
message: `Tool "${name}" requires user authorization`,
displayMessage, // Human-readable message for clients to display
authorizationUrl: consentUrl, // Use formatter's URL for consistency
resumeToken: verifyResult.authError?.resumeToken,
scopes: verifyResult.authError?.scopes,
display: verifyResult.authError?.display,
}),
},
],
isError: true,
};
}
// Check if authorization method matches current tool requirements
// This prevents stale delegations from working after tool auth method changes
// NOTE: Authorization validation only applies when credential is present (AgentShield API verifier).
// KV/Memory verifiers don't return credential data, so we skip auth method validation for them.
if (verifyResult.authorized &&
toolProtection.authorization &&
verifyResult.credential) {
const toolAuth = toolProtection.authorization;
const delegationAuth = verifyResult.credential.authorization;
// If tool requires authorization but credential doesn't have auth data, reject
// This prevents bypassing auth checks with incomplete credential data
if (!delegationAuth) {
if (identityConfig?.debug) {
console.error(`[MCPI] Tool "${name}" blocked - credential missing authorization data`);
}
// Build authorization URL for re-authorization
const authUrl = new URL(authConfig.bouncer.authorizationUrl);
authUrl.searchParams.set("agent_did", agentDid);
authUrl.searchParams.set("scopes", (toolProtection.requiredScopes || []).join(","));
authUrl.searchParams.set("tool", name);
return {
content: [
{
type: "text",
text: JSON.stringify({
error: "authorization_data_missing",
message: `Tool "${name}" requires authorization ${formatAuthInfo(toolAuth)} ` +
`but delegation credential does not contain authorization data. Please re-authorize.`,
authorizationUrl: authUrl.toString(),
scopes: toolProtection.requiredScopes || [],
requiredAuth: toolAuth,
}),
},
],
isError: true,
};
}
// Compare authorization types using the helper function
if (!(0, mcp_i_runtime_1.authorizationMatches)(delegationAuth, toolAuth)) {
if (identityConfig?.debug) {
console.error(`[MCPI] Tool "${name}" blocked - authorization method mismatch`);
}
// Build authorization URL for re-authorization
const authUrl = new URL(authConfig.bouncer.authorizationUrl);
authUrl.searchParams.set("agent_did", agentDid);
authUrl.searchParams.set("scopes", (toolProtection.requiredScopes || []).join(","));
authUrl.searchParams.set("tool", name);
return {
content: [
{
type: "text",
text: JSON.stringify({
error: "authorization_method_mismatch",
message: `Tool "${name}" requires re-authorization. ` +
`Delegation was created with ${formatAuthInfo(delegationAuth)} ` +
`but tool now requires ${formatAuthInfo(toolAuth)}.`,
authorizationUrl: authUrl.toString(),
scopes: toolProtection.requiredScopes || [],
currentAuth: delegationAuth,
requiredAuth: toolAuth,
}),
},
],
isError: true,
};
}
}
if (identityConfig?.debug) {
console.error(`[MCPI] Tool "${name}" authorized - executing handler`);
}
// Populate delegation context in AsyncLocalStorage so outbound
// fetch calls made by the tool handler can inject delegation headers.
if (verifyResult.delegation) {
const identity = identityManager
? await identityManager.ensureIdentity()
: null;
(0, request_context_1.setDelegationContextFromIdentity)({
delegationId: verifyResult.delegation.id,
delegationChain: (0, mcp_i_core_1.buildChainString)(verifyResult.delegation),
delegationScopes: toolProtection.requiredScopes || [],
// JWS-compact DelegationCredential VC from the verified
// result — emitted on KYA-OS-Delegation-Credential when the
// verifier returned a presentable credential.
delegationCredential: verifyResult.credential?.credential_jwt,
identity: identity ?? null,
});
}
// Bridge this AsyncLocalStorage context into the compute
// outbound-identity interceptor (OpenClaw sidecar path) when it is
// present in-process. Idempotent + best-effort: a no-op on
// deployments without the compute interceptor.
(0, outbound_identity_bridge_1.ensureOutboundIdentityBridge)();
}
try {
// Execute the tool handler
const result = await handler(args || {});
// Build base response
const baseResponse = {
content: [
{
type: "text",
text: typeof result === "string"
? result
: JSON.stringify(result),
},
],
};
// If identity is enabled, generate cryptographic proof
if (identityManager) {
try {
// Ensure identity exists
const identity = await identityManager.ensureIdentity();
// Create a session context for this request
const toolRequest = {
method: name,
params: args || {},
};
const toolResponse = {
data: result,
};
// Reuse the active session if available, otherwise synthesize one
const timestamp = Math.floor(Date.now() / 1000);
const proofSession = session ?? {
sessionId: `tool-${Date.now()}`,
nonce: `${Date.now()}`,
audience: "client",
createdAt: timestamp,
timestamp,
lastActivity: timestamp,
ttlMinutes: 30,
identityState: "anonymous", // Phase 5: Synthesized sessions are anonymous
};
// Determine scopeId from tool protection configuration
// This enables AgentShield tool auto-discovery
let scopeId;
const toolProtection = tool_protection_registry_1.toolProtectionRegistry.get(name);
if (toolProtection?.requiredScopes &&
toolProtection.requiredScopes.length > 0) {
// Use the first required scope as the scopeId (e.g., "files:read")
scopeId = toolProtection.requiredScopes[0];
}
else {
// Fallback: Use tool name with "execute" action for unprotected tools
scopeId = `${name}:execute`;
}
if (identityConfig?.debug) {
console.error(`[MCPI] Proof scopeId for tool "${name}": ${scopeId}`);
}
// Generate proof using the proof generator with scopeId
const proofGen = new proof_1.ProofGenerator(identity);
const proof = await proofGen.generateProof(toolRequest, toolResponse, proofSession, {
scopeId, // Pass scopeId for tool auto-discovery
clientDid: session?.clientDid,
toolName: name,
submissionOutcome: "success",
});
if (identityConfig?.debug) {
console.error(`[MCPI] Generated proof for tool "${name}" - DID: ${proof.meta.did}`);
}
// Submit proof to ProofBatchQueue (if initialized)
if (globalProofQueue) {
try {
globalProofQueue.enqueue({
proof,
toolName: name,
// Derive from the signed proof metadata so the
// submitted value can never diverge from what the
// server's verifier checks against the JWS payload.
outcome: proof.meta.submissionOutcome ?? "success",
});
if (identityConfig?.debug) {
console.error(`[MCPI] Proof enqueued for submission to AgentShield`);
}
}
catch (queueError) {
if (identityConfig?.debug) {
console.error(`[MCPI] Failed to enqueue proof:`, queueError);
}
// Continue even if queueing fails - proof is still in response
}
}
// Return response with proof metadata
return {
...baseResponse,
_meta: {
proof,
},
};
}
catch (proofError) {
if (identityConfig?.debug) {
console.error(`[MCPI] Failed to generate proof for tool "${name}":`, proofError);
}
// Return base response without proof if generation fails
return baseResponse;
}
}
return baseResponse;
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error executing tool "${name}": ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
});
});
if (identityConfig?.debug) {
console.error("[MCPI] Handlers registered successfully");
}
return server;
}