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.

65 lines (59 loc) • 1.75 kB
import { MCPEventConfigError } from '../errors' const PREFIX = 'whsec_' const MIN_BYTES = 24 const MAX_BYTES = 64 const GENERATED_BYTES = 32 function bytesToBase64(bytes: Uint8Array): string { let binary = '' for (const byte of bytes) binary += String.fromCharCode(byte) return btoa(binary) } function base64ToBytes(value: string): Uint8Array { const padded = value + '='.repeat((4 - (value.length % 4)) % 4) try { 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 { throw new MCPEventConfigError('MCP event secret is not valid base64') } } /** * Generate a Standard Webhooks secret: `whsec_` plus 32 random bytes. * * @experimental * * @example * ```ts * import { createMCPEventSecret } from '@tanstack/ai-mcp/events' * * const secret = createMCPEventSecret() * ``` */ export function createMCPEventSecret(): string { const bytes = new Uint8Array(GENERATED_BYTES) crypto.getRandomValues(bytes) return `${PREFIX}${bytesToBase64(bytes)}` } /** * Decode a `whsec_` secret to the raw HMAC key. * * @experimental */ export function decodeMCPEventSecret(secret: string): Uint8Array { if (!secret.startsWith(PREFIX)) { throw new MCPEventConfigError('MCP event secret must start with whsec_') } const encoded = secret.slice(PREFIX.length) if (encoded.length === 0) { throw new MCPEventConfigError('MCP event secret is not valid base64') } const bytes = base64ToBytes(encoded) if (bytes.byteLength < MIN_BYTES || bytes.byteLength > MAX_BYTES) { throw new MCPEventConfigError( 'MCP event secret must decode to 24 to 64 bytes', ) } return bytes }