UNPKG

@tanstack/ai-mcp

Version:

Host-side Model Context Protocol client for TanStack AI: discover and run MCP server tools, resources, and prompts in any adapter's chat() loop, with generated end-to-end types.

67 lines (66 loc) • 2.21 kB
import { decodeMCPEventSecret } from "./secret.js"; //#region src/events/signature.ts function concat(prefix, body) { const out = new Uint8Array(prefix.byteLength + body.byteLength); out.set(prefix); out.set(body, prefix.byteLength); return out; } function signedBytes(messageId, timestamp, body) { return concat(new TextEncoder().encode(`${messageId}.${timestamp}.`), body); } function bytesToBase64(bytes) { let binary = ""; for (const byte of bytes) binary += String.fromCharCode(byte); return btoa(binary); } function base64ToBytes(value) { try { const padded = value + "=".repeat((4 - value.length % 4) % 4); const binary = atob(padded); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); return bytes; } catch { return; } } async function hmacKey(secret, usages) { return crypto.subtle.importKey("raw", decodeMCPEventSecret(secret), { name: "HMAC", hash: "SHA-256" }, false, usages); } /** * Sign a webhook body as Standard Webhooks v1 (`v1,<base64>`). * * @experimental */ async function signMCPEventPayload(input) { const key = await hmacKey(input.secret, ["sign"]); const signature = await crypto.subtle.sign("HMAC", key, signedBytes(input.messageId, input.timestamp, input.body)); return `v1,${bytesToBase64(new Uint8Array(signature))}`; } /** * Verify a Standard Webhooks `webhook-signature` header. * Accepts any valid `v1,` entry. Ignores `v1a,` and unknown versions. * * @experimental */ async function verifyMCPEventSignature(input) { const key = await hmacKey(input.secret, ["verify"]); const payload = signedBytes(input.messageId, input.timestamp, input.body); const entries = input.header.trim().split(/\s+/); for (const entry of entries) { const comma = entry.indexOf(","); if (comma <= 0) continue; if (entry.slice(0, comma) !== "v1") continue; const signature = base64ToBytes(entry.slice(comma + 1)); if (signature === void 0 || signature.byteLength === 0) continue; if (await crypto.subtle.verify("HMAC", key, signature, payload)) return true; } return false; } //#endregion export { signMCPEventPayload, verifyMCPEventSignature }; //# sourceMappingURL=signature.js.map