@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
JavaScript
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