gemi 0.120.0 → 0.120.1

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.
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- import{Pb as eZ,Sb as t,Tb as Z3,ac as YZ}from"./chunk-mr94nfp1.js";import{lc as LZ,nc as ZZ,qc as B3,rc as I3,sc as N3,uc as O3}from"./chunk-9rr5h818.js";import{Bc as D3,Dc as K3,Fc as H3,Mc as D,Nc as U3,Oc as Q3,Qc as V3,wc as Y3}from"./chunk-dwedx1jb.js";import{Tc as J}from"./chunk-9xyxbxgk.js";import{He as l,Le as m,Me as zZ,Ne as AZ,Vd as p,de as P3,ee as qZ,id as T3,ie as r,md as v,pd as rZ,se as MZ,td as S3,ue as GZ,ye as kZ}from"./chunk-hhymaj0m.js";import{Ag as i,Rf as b,Tf as CZ,gg as jZ,ug as gZ,yg as $Z,zg as a}from"./chunk-4acpw92g.js";import{Mg as aZ}from"./chunk-m83zm86m.js";import{Sg as DZ}from"./chunk-1xdrs2hx.js";import{Vg as x3,Wg as OZ,Zg as e,_g as SZ,bh as _3}from"./chunk-19dpzp4y.js";import{Ih as X3,Kh as k}from"./chunk-meg8rtr6.js";import{Lh as _}from"./chunk-h43jp8ws.js";import{Rh as L3}from"./chunk-s07v65x6.js";import{Fi as TZ}from"./chunk-1wnnkde0.js";import{Mi as F3,Pi as g}from"./chunk-nz8z8jr1.js";var R3="mcp",E3=/^[\x21\x23-\x5B\x5D-\x7E]+$/;class L extends YZ{status;constructor(Z,$){super(Z);this.status=$;this.name="McpToolError"}}class d extends Error{reason;constructor(Z,$){super(Z);this.reason=$;this.name="McpCallRefusedError"}}var w3=/^[A-Za-z0-9_-]{1,64}$/,b3=/:([A-Za-z_][A-Za-z0-9_]*)([?*+]?)/g,RZ=4000,y=1e5,y3=/^(text\/|application\/([\w.+-]*\+)?(json|xml))/i;class f{router;dispatcher;static token="router.mcp";plans=new Map;scopeTools=new Map;files;constructor(Z,$,G={}){this.router=Z;this.dispatcher=$;this.files=v3(G.files);for(let[W,z]of Object.entries(Z.routes??{}))this.plans.set(W,this.plan(W,z));this.resolveScopes()}list(Z,$){if(EZ(Z),Z.kind==="local")return this.descriptors($);let G=[];for(let[W,z]of this.plans){if(!z.remote||!this.grants(Z.principal,W))continue;if(wZ(z.remote,$))G.push(z.remote)}return G}descriptors(Z){let $=[];for(let{descriptor:G}of this.plans.values())if(wZ(G,Z))$.push(G);return $}get maxFileBytes(){return this.files.maxBytes}scopes(){return[...this.scopeTools].map(([Z,{description:$}])=>({name:Z,description:$}))}scopesFor(Z){return[...this.scopeTools].filter(([,{tools:$}])=>$.has(Z)).map(([$])=>$)}async execute(Z,$,G,W,z={}){EZ(Z);let X=this.plans.get($);if(Z.kind==="remote"){if(!X?.remote||!this.grants(Z.principal,$))throw new d(`There is no tool named "${$}".`,"unknown-tool");if(X.descriptor.requiresApproval&&z.approved!==!0)throw new d(`"${$}" needs the user's approval, and this call does not have it.`,"approval-required");W=void 0}if(!X)throw Error(`McpRegistry: there is no tool named "${$}".`);let Y=X,K=Z.kind==="remote"?Y.remote:Y.descriptor,U=K.inputSchema.safeParse(G);if(U.ok===!1)throw new L(`Invalid arguments for "${$}": ${U.errors.join(", ")}`);let H=U.value,Q={caller:Z,req:Z.req,tool:K,input:H,ctx:W},V=await this.fillPath(Y,Q),{path:I}=this.dispatcher.getRouteHandlerAndParams(new Request(`http://gemi.internal/api${V}`));if(I!==K.url)throw new L(`"${$}" has nothing at ${V}.`,404);let C={};for(let q of Y.jsonKeys)if(H[q]!==void 0)C[q]=H[q];let P,x="";if(Y.fileFields.length>0)P=await this.formData(Y,C,Q);else if(K.method==="GET")x=t3(C);else if(Y.jsonKeys.length>0)P=C;let j=this.router.credentials?await this.bind(Y,"the credentials",()=>this.router.credentials(Q)):void 0,w={...j?{credentials:j}:{},...z.signal?{signal:z.signal}:{},...z.progress?{progress:z.progress}:{},...Z.kind==="remote"?{identity:{user:Z.principal.user,grant:{via:Z.principal.via,id:Z.principal.id,scopes:Z.principal.scopes,...Z.principal.clientId?{clientId:Z.principal.clientId}:{}}}}:{}},u;try{let q=x?`${V}?${x}`:V;u=Object.keys(w).length>0?await this.dispatcher.dispatchAs(Z.req,K.method,q,P,w):await this.dispatcher.dispatchAs(Z.req,K.method,q,P)}catch(q){throw console.error(`[gemi/mcp] Dispatching "${$}" failed:`,q),new L(`"${$}" failed on the server.`,500)}if(!Y.result&&!K.outputSchema)return await vZ($,u);return await this.project(Y,Q,u)}plan(Z,$){if(!w3.test(Z))throw Error(`McpRouter: "${Z}" is not a valid tool name. A routes key is the tool's name as a model and an MCP client see it, used verbatim \u2014 letters, digits, "_" and "-", at most 64.`);let G=`McpRouter: "${Z}" (${$?.method} ${$?.url})`;if(!($ instanceof Object)||$.__internal_brand!=="McpRoute")throw Error(`McpRouter: "${Z}" is not a route. Declare it with this.fromApiRoute(...).`);let{method:W,url:z,meta:X}=$;if(z.startsWith("/__gemi__"))throw Error(`${G} is a framework route, not an app route.`);let Y=this.dispatcher.flatRoutes[z]?.[W];if(!Y)throw Error(`${G} names no route of this app. Expose a route by the path and verb it is mounted at.`);let K=[...z.matchAll(b3)].map(([,B,A])=>({name:B,modifier:A})),U=X.params??{};for(let B of K)if(!(B.name in U))throw Error(`${G}: the path param "${B.name}" must be bound or declared "input".`);for(let[B,A]of Object.entries(U)){if(!K.some((M)=>M.name===B))throw Error(`${G}: "${B}" in params is not a param of the url.`);if(yZ(A))d3(G,`params.${B}`,A);else bZ(G,`params.${B}`,A)}let H=[],Q;if(X.input){if(Q=X.input.toJSONSchema(),Q.type!=="object"||!Q.properties)throw Error(`${G}: input must be an s.object(...), the fields of the request body.`);H=Object.keys(Q.properties)}let V=Object.entries(X.files??{}).map(([B,A])=>({name:B,binder:A}));for(let B of V)bZ(G,`files.${B.name}`,B.binder);if(X.result!==void 0&&typeof X.result!=="function")throw Error(`${G}: result must be a function.`);if(X.output!==void 0&&typeof X.output?.safeParse!=="function")throw Error(`${G}: output must be a schema, built with s.`);if(X.title!==void 0&&(typeof X.title!=="string"||X.title===""))throw Error(`${G}: title must be a non-empty string.`);let I=i3(G,W,X.annotations);if(V.length>0&&W==="GET")throw Error(`${G}: a GET has no body to carry a file.`);let C={},P=(B,A)=>{if(B in C)throw Error(`${G}: "${B}" is the input key of two params or files, and the model can only send one.`);C[B]=A},x=new Map,j=new Map;for(let B of K){let A=U[B.name];if(A==="input"){let M=b.string().describe(`The ":${B.name}" segment of the url.`);P(B.name,B.modifier==="?"||B.modifier==="*"?M.optional():M)}else if(yZ(A)){let M=A.as??B.name;P(M,A.input),j.set(B.name,{key:M,param:A})}else x.set(B.name,A)}let w={};for(let B of V)if(B.binder==="input")P(B.name,b.string().describe("The id of an attachment the user uploaded or a tool produced (gemi_att_\u2026), whose file is sent as this field.")),w[B.name]=this.remoteFileSchema();let u=[...Object.keys(C),...V.map((B)=>B.name)];for(let B of new Set(u))if(H.includes(B))throw Error(`${G}: "${B}" is both an input field and a param or file, and the model can only send one.`);let q=Object.freeze({name:Z,description:X.description,method:W,url:z,inputSchema:fZ(X.input,Q,b.object(C)),...X.output?{outputSchema:X.output}:{},...X.title?{title:X.title}:{},annotations:I,tags:Object.freeze([...X.tags??[]]),requiresApproval:X.requiresApproval===!0,...Y.source?{source:Y.source}:{}}),k3=!V.every((B)=>B.binder==="input")?null:V.length===0?q:Object.freeze({...q,inputSchema:fZ(X.input,Q,b.object(l3(C,Object.keys(w))),w)});return{descriptor:q,remote:k3,params:K,paramBinders:x,modelParams:j,fileFields:V,jsonKeys:H,...X.result?{result:X.result}:{}}}remoteFileSchema(){let{maxBytes:Z,fetch:$}=this.files,G={name:{type:"string",description:"The file's name, like report.pdf."},mimeType:{type:"string",description:"The file's media type, like application/pdf."},data:{type:"string",contentEncoding:"base64",description:`The file's bytes, base64-encoded (RFC 4648, standard alphabet). At most ${Z} bytes decoded.`}};if($)G.url={type:"string",format:"uri",description:"An https URL the server downloads the file from, instead of data."};let W={type:"object",description:$?"A file, sent as base64 in data or fetched from url: exactly one of the two.":"A file, sent as base64 in data.",properties:G,required:["name","mimeType"],additionalProperties:!1},z=Object.keys(G),X=(Y)=>{if(typeof Y!=="object"||Y===null||Array.isArray(Y))return{ok:!1,errors:["a file must be an object"]};let K=[],U={};for(let H of Object.keys(Y))if(!z.includes(H))K.push(`a file has no field "${H}"`);for(let H of z){let Q=Y[H];if(Q===void 0||Q===null){if(H==="name"||H==="mimeType")K.push(`a file needs "${H}"`);continue}if(typeof Q!=="string")K.push(`a file's "${H}" must be a string`);else U[H]=Q}return K.length>0?{ok:!1,errors:K}:{ok:!0,value:U}};return{toJSONSchema:()=>W,safeParse:X,parse(Y){let K=X(Y);if(K.ok===!1)throw Error(K.errors.join(", "));return K.value}}}async fillPath(Z,$){let{input:G,req:W}=$,z=Z.descriptor.url;for(let X of Z.params){let Y=Z.paramBinders.get(X.name),K=Z.modelParams.get(X.name),U;if(Y||K){if(U=K?await this.bind(Z,`the param "${X.name}"`,()=>K.param.bind(G[K.key],W,$),{refusals:!0}):await this.bind(Z,`the param "${X.name}"`,()=>Y(W,$)),U===void 0||U===null||U==="")throw console.error(`[gemi/mcp] The ${K?"resolver":"binder"} for "${X.name}" of "${Z.descriptor.name}" returned ${JSON.stringify(U)}.`),new L(`"${Z.descriptor.name}" failed on the server.`,500)}else U=G[X.name];let H=`:${X.name}${X.modifier}`;if(U===void 0||U===null||U===""){if(X.modifier!=="?"&&X.modifier!=="*")throw new L(`"${X.name}" of "${Z.descriptor.name}" must not be empty.`);z=z.replace(`/${H}`,"");continue}z=z.replace(H,n3(Z.descriptor.name,X,String(U)))}return z}async formData(Z,$,G){let{input:W,ctx:z}=G,X=Z.descriptor.name;if(G.caller.kind==="remote"){let K=new FormData;for(let[U,H]of Object.entries($))XZ(K,U,H);for(let U of Z.fileFields)K.append(U.name,await this.remoteFile(X,U.name,W[U.name]));return K}if(!z)throw Error(`McpRegistry: "${X}" sends a file, which is resolved through the tool context's attachments, and none was passed to execute().`);let Y=new FormData;for(let[K,U]of Object.entries($))XZ(Y,K,U);for(let K of Z.fileFields){let U=K.binder==="input"?W[K.name]:await this.bind(Z,`the file "${K.name}"`,()=>K.binder(z,G));if(typeof U!=="string"||U==="")throw new L(`"${X}" needs a file for "${K.name}", and this turn has none. Ask the user to attach one.`);Y.append(K.name,await z.attachments.file(U))}return Y}async remoteFile(Z,$,G){let W=(Q)=>new L(`"${$}" of "${Z}" ${Q}`,400),z=G,{maxBytes:X,fetch:Y}=this.files;if(!g3(z.name))throw W("needs a name of 1 to 255 characters, without control characters or slashes.");if(!h3.test(z.mimeType))throw W(`has a mimeType that is not a media type: "${z.mimeType.slice(0,100)}".`);let K=typeof z.data==="string",U=typeof z.url==="string";if(K===U)throw W(Y?"needs exactly one of data or url.":"needs its bytes, base64, in data.");if(K){let Q=z.data.replace(/\s+/g,"");if(Math.floor(Q.length*3/4)-u3(Q)>X)throw W(`is larger than the ${X} bytes a file may be.`);if(Q.length%4!==0||!m3.test(Q))throw W("has data that is not base64 (RFC 4648, standard alphabet, padded).");return new File([Buffer.from(Q,"base64")],z.name,{type:z.mimeType})}if(!Y)throw W("cannot be fetched from a URL here. Send its bytes, base64, in data.");let H;try{H=new URL(z.url)}catch{throw W("has a url that is not a URL.")}if(H.protocol!=="https:")throw W("can only be fetched from an https URL.");try{let Q=await gZ(H.href,{...Y,maxSize:Math.min(Y.maxSize??X,X),redirect:"follow"});if(!Q.ok)throw W(`could not be fetched: ${H.host} answered ${Q.status}.`);let V=await Q.arrayBuffer();return new File([V],z.name,{type:z.mimeType})}catch(Q){if(Q instanceof L)throw Q;throw W(`could not be fetched from ${H.host}: ${Q instanceof Error?Q.message:"failed"}.`)}}grants(Z,$){for(let G of Z.scopes??[])if(this.scopeTools.get(G)?.tools.has($))return!0;return!1}resolveScopes(){let Z=this.router.scopes;if(Z===void 0){this.scopeTools.set(R3,{description:"Use this app's tools as you",tools:new Set(this.plans.keys())});return}if(typeof Z!=="object"||Z===null)throw Error("McpRouter: scopes must be an object of scope names.");let $=new Set([...this.plans.values()].flatMap(({descriptor:G})=>G.tags));for(let[G,W]of Object.entries(Z)){let z=`McpRouter: scope "${G}"`;if(!E3.test(G))throw Error(`${z} is not a valid OAuth scope: printable ASCII, no spaces or quotes.`);if(typeof W?.description!=="string"||W.description==="")throw Error(`${z} needs a description: it is what a consent screen shows.`);let X=new Set;for(let Y of W.tags??[]){if(!$.has(Y))throw Error(`${z} names the tag "${Y}", which no tool has.`);for(let[K,{descriptor:U}]of this.plans)if(U.tags.includes(Y))X.add(K)}for(let Y of W.names??[]){if(!this.plans.has(Y))throw Error(`${z} names "${Y}", which is no tool.`);X.add(Y)}if(X.size===0)throw Error(`${z} reaches no tool. Give it tags or names.`);this.scopeTools.set(G,{description:W.description,tools:X})}}async project(Z,$,G){let{name:W,outputSchema:z}=Z.descriptor;if(G.status<200||G.status>=300)return await vZ(W,G);let X=(V,I)=>{if(I===void 0)console.error(`[gemi/mcp] "${W}" ${V}`);else console.error(`[gemi/mcp] "${W}" ${V}`,I);return new L(`"${W}" failed on the server.`,500)},Y=G.headers.get("Content-Type"),K=await G.text(),U=null;if(K!=="")try{U=JSON.parse(K)}catch{throw X(`answered ${Y??"a body with no content-type"} that is not JSON, and its result or output needs JSON.`)}let H=U;if(Z.result)try{H=await Z.result(U,$)}catch(V){throw X("failed in its result projection:",V)}if(z){let V=z.safeParse(H);if(V.ok===!1)throw X(`answered what its output schema refuses: ${V.errors.join(", ")}`);H=V.value}if(H===void 0)return null;let Q=JSON.stringify(H);if(Q!==void 0&&Q.length>y)return`${Q.slice(0,y)}\u2026 [cut at ${y} of ${Q.length} characters]`;return H}async bind(Z,$,G,W={}){try{return await G()}catch(z){if(W.refusals&&z instanceof YZ)throw z;throw console.error(`[gemi/mcp] Binding ${$} of "${Z.descriptor.name}" failed:`,z),new L(`"${Z.descriptor.name}" failed on the server.`,500)}}}function EZ(Z){if(Z?.kind==="local"&&Z.req)return;if(Z?.kind==="remote"&&Z.req&&Z.principal?.user&&typeof Z.principal.id==="string"&&Array.isArray(Z.principal.scopes))return;throw Error('McpRegistry: a caller is { kind: "local", req } or { kind: "remote", req, principal }, with the principal a caller resolver verified.')}function wZ(Z,$){if($?.names&&!$.names.includes(Z.name))return!1;if($?.tags&&!Z.tags.some((G)=>$.tags.includes(G)))return!1;return!0}var f3=10485760;function v3(Z){let $=Z?.maxBytes??f3;if(!Number.isSafeInteger($)||$<=0)throw Error("McpRegistry: files.maxBytes must be a positive whole number of bytes.");let G=Z?.fetchUrls===!0?{}:typeof Z?.fetchUrls==="object"&&Z.fetchUrls!==null?Z.fetchUrls:null;return{maxBytes:$,fetch:G}}function g3(Z){if(Z.length===0||Z.length>255)return!1;for(let $ of Z){let G=$.codePointAt(0);if(G<32||G===127||$==="/"||$==="\\")return!1}return!0}var h3=/^[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}\/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}(\s*;.{0,200})?$/,m3=/^[A-Za-z0-9+/]*={0,2}$/;function u3(Z){return Z.endsWith("==")?2:Z.endsWith("=")?1:0}function bZ(Z,$,G){if(G!=="input"&&typeof G!=="function")throw Error(`${Z}: ${$} must be a function or "input".`)}function yZ(Z){return Z instanceof Object&&Z.__internal_brand==="McpModelParam"}function d3(Z,$,G){if(G.as!==void 0&&(typeof G.as!=="string"||G.as===""))throw Error(`${Z}: ${$}.as must be a non-empty string.`);let W=G.input;if(typeof W?.safeParse!=="function"||typeof W.toJSONSchema!=="function")throw Error(`${Z}: ${$}.input must be a schema, built with s.`);if(typeof G.bind!=="function")throw Error(`${Z}: ${$}.bind must be a function.`)}var c3=new Set(["readOnlyHint","destructiveHint","idempotentHint","openWorldHint"]);function p3(Z){switch(Z){case"GET":return{readOnlyHint:!0};case"DELETE":return{destructiveHint:!0};case"PUT":return{idempotentHint:!0};default:return{}}}function i3(Z,$,G){if(G!==void 0&&(typeof G!=="object"||G===null))throw Error(`${Z}: annotations must be an object of hints.`);let W=p3($);for(let[z,X]of Object.entries(G??{})){if(!c3.has(z))throw Error(`${Z}: "${z}" is not a tool annotation. Use readOnlyHint, destructiveHint, idempotentHint or openWorldHint.`);if(X===void 0)continue;if(typeof X!=="boolean")throw Error(`${Z}: annotations.${z} must be true or false.`);W[z]=X}if(W.readOnlyHint===!0&&(W.destructiveHint||W.idempotentHint))throw Error(`${Z} is read-only${G?.readOnlyHint?"":` (a ${$})`} and also ${W.destructiveHint?"destructive":"idempotent"}, which only a tool that is not read-only can be. Set readOnlyHint: false if it changes something.`);return Object.freeze(W)}function fZ(Z,$,G,W={}){let z=G.toJSONSchema(),X=Object.fromEntries(Object.entries(W).map(([U,H])=>[U,H.toJSONSchema()])),Y={...$??{},type:"object",properties:{...$?.properties??{},...z.properties,...X},required:[...$?.required??[],...z.required??[],...Object.keys(W)],additionalProperties:!1},K=(U)=>{if(typeof U!=="object"||U===null||Array.isArray(U))return{ok:!1,errors:["expected an object"]};let H=[],Q={};if(Z){let I=Z.safeParse(U);if(I.ok===!1)H.push(...I.errors);else Q={...I.value}}let V=G.safeParse(U);if(V.ok===!1)H.push(...V.errors);else Q={...Q,...V.value};for(let[I,C]of Object.entries(W)){let P=U[I];if(P===void 0){H.push(`"${I}" is required`);continue}let x=C.safeParse(P);if(x.ok===!1)H.push(...x.errors.map((j)=>`${I}: ${j}`));else Q[I]=x.value}return H.length>0?{ok:!1,errors:H}:{ok:!0,value:Q}};return{toJSONSchema:()=>Y,safeParse:K,parse(U){let H=K(U);if(H.ok===!1)throw Error(H.errors.join(", "));return H.value}}}function l3(Z,$){return Object.fromEntries(Object.entries(Z).filter(([G])=>!$.includes(G)))}function n3(Z,$,G){let W=$.modifier==="*"||$.modifier==="+"?G.split("/"):[G];if(W.some((z)=>z==="."||z===".."))throw new L(`"${G}" is not a valid value for "${$.name}" of "${Z}".`);return W.map(encodeURIComponent).join("/")}function XZ(Z,$,G){if(G===void 0||G===null)return;if(Array.isArray(G)){for(let W of G)XZ(Z,$,W);return}Z.append($,typeof G==="object"?JSON.stringify(G):String(G))}function t3(Z){let $=new URLSearchParams,G=(W,z)=>{if(z===void 0||z===null)return;if(Array.isArray(z)){for(let X of z)G(W,X);return}$.append(W,typeof z==="object"?JSON.stringify(z):String(z))};for(let[W,z]of Object.entries(Z))G(W,z);return $.toString()}async function vZ(Z,$){if($.status>=200&&$.status<300){let W=$.headers.get("Content-Type");if(W&&!y3.test(W)){let X=(await $.arrayBuffer()).byteLength;return`"${Z}" answered with ${X} bytes of ${W}, which is not shown.`}let z=await $.text();if(z==="")return null;if(z.length>y)return`${z.slice(0,y)}\u2026 [cut at ${y} of ${z.length} characters]`;try{return JSON.parse(z)}catch{return z}}let G=await $.text();if($.status>=400&&$.status<500){let W=G.length>RZ?`${G.slice(0,RZ)}\u2026`:G;throw new L(`"${Z}" was refused with ${$.status}${W?`: ${W}`:"."}`,$.status)}if($.status<500)console.error(`[gemi/mcp] "${Z}" answered ${$.status}${$.headers.get("Location")?` to ${$.headers.get("Location")}`:""}, which a tool call cannot follow.`);throw new L(`"${Z}" failed on the server.`,$.status)}import{createHash as s3,randomBytes as o3,timingSafeEqual as a3}from"crypto";function KZ(Z){let $=Z.headers.get("authorization");if(!$)return null;let G=/^Bearer[ ]+([\x21-\x7e]+)$/i.exec($.trim());return G?G[1]:null}class r3{options;name="api-key";constructor(Z){this.options=Z;if(typeof Z?.prefix!=="string"||!/^[A-Za-z0-9_-]{3,32}$/.test(Z.prefix))throw Error('McpApiKeyResolver: prefix is required: 3 to 32 letters, digits, _ or -, like "myapp_mcp_".');if(typeof Z.verify!=="function")throw Error("McpApiKeyResolver: verify must be a function that looks a key up.")}static generate(Z){return`${Z}${o3(32).toString("base64url")}`}static hash(Z){return s3("sha256").update(Z).digest("hex")}static hashesMatch(Z,$){let G=Buffer.from(Z),W=Buffer.from($);return G.length===W.length&&a3(G,W)}async resolve(Z){let $=KZ(Z);if(!$?.startsWith(this.options.prefix))return null;let G=await this.options.verify($,Z);if(!G)return{ok:!1,error:"invalid_token",description:"The API key is not valid."};if(!G.user||typeof G.id!=="string"||G.id===""||!Array.isArray(G.scopes))throw Error("McpApiKeyResolver: verify must answer { user, id, scopes } for a key it knows, or null.");return{ok:!0,principal:{user:G.user,via:this.name,id:G.id,scopes:[...G.scopes]}}}}import{createHash as $3,randomBytes as dZ}from"crypto";async function HZ(Z,$){let G=e3();if(!G)return null;let W=await G.consume(Z,$);return W.allowed?null:Math.max(1,Math.ceil(W.retryAfter/1000))}function e3(){try{let Z=g();return Z.bound(v)?Z.make(v):null}catch{return null}}var UZ=["2026-07-28"],n=["2025-11-25","2025-06-18","2025-03-26"],QZ=[...UZ,...n],VZ="io.modelcontextprotocol/protocolVersion",BZ="io.modelcontextprotocol/clientCapabilities";var IZ="io.modelcontextprotocol/serverInfo",N={ParseError:-32700,InvalidRequest:-32600,MethodNotFound:-32601,InvalidParams:-32602,InternalError:-32603,HeaderMismatch:-32020,MissingRequiredClientCapability:-32021,UnsupportedProtocolVersion:-32022};class F extends Error{code;data;status;constructor(Z,$,G,W=200){super($);this.code=Z;this.data=G;this.status=W;this.name="JsonRpcError"}}function T(Z,$){return{jsonrpc:"2.0",id:Z,error:{code:$.code,message:$.message,...$.data!==void 0?{data:$.data}:{}}}}function hZ(Z){if(Array.isArray(Z))throw new F(N.InvalidRequest,"Batched requests are not supported.",void 0,400);if(typeof Z!=="object"||Z===null||Z.jsonrpc!=="2.0")throw new F(N.InvalidRequest,"Not a JSON-RPC 2.0 message.",void 0,400);let $=Z,G=$.id,W=typeof G==="string"||typeof G==="number"&&Number.isFinite(G);if(typeof $.method==="string"){if($.params!==void 0&&(typeof $.params!=="object"||$.params===null||Array.isArray($.params)))throw new F(N.InvalidRequest,"params must be an object.",void 0,400);if(G===void 0)return{kind:"notification",message:$};if(!W)throw new F(N.InvalidRequest,"id must be a string or a number.",void 0,400);return{kind:"request",message:$}}if(W&&(("result"in $)||("error"in $)))return{kind:"response",message:$};throw new F(N.InvalidRequest,"Not a JSON-RPC request, notification or response.",void 0,400)}function JZ(Z,$){let G=Z.outputSchema?.toJSONSchema(),W={...Z.annotations,...Z.title&&!$?{title:Z.title}:{}};return{name:Z.name,...Z.title?{title:Z.title}:{},description:Z.description,inputSchema:Z.inputSchema.toJSONSchema(),...G&&($||G.type==="object")?{outputSchema:G}:{},...Object.keys(W).length>0?{annotations:W}:{}}}function O(Z){return typeof Z==="object"&&Z!==null&&!Array.isArray(Z)}function mZ(Z){let $=/^=\?base64\?([A-Za-z0-9+/]*={0,2})\?=$/.exec(Z);if(!$)return Z;try{let G=Buffer.from($[1],"base64");return new TextDecoder("utf-8",{fatal:!0}).decode(G)}catch{return null}}import{createHmac as Z9,timingSafeEqual as $9}from"crypto";function FZ(Z,$,G){let W=Buffer.from(JSON.stringify(G)).toString("base64url");return`${$}.${W}.${uZ(Z,$,W).toString("base64url")}`}function NZ(Z,$,G,W=Date.now()){if(typeof G!=="string"||G.length>8192)return null;let z=G.split(".");if(z.length!==3||z[0]!==$)return null;let[,X,Y]=z,K=Buffer.from(Y,"base64url"),U=uZ(Z,$,X);if(K.length!==U.length||!$9(K,U))return null;let H;try{H=JSON.parse(Buffer.from(X,"base64url").toString("utf8"))}catch{return null}if(typeof H!=="object"||H===null)return null;if(typeof H.exp!=="number"||H.exp<=W)return null;return H}function uZ(Z,$,G){return Z9("sha256",t(Z)).update(`${$}.${G}`).digest()}var xZ="gemi.mcp.request-state.v1",cZ="gemi.mcp.session.v1",G9=86400000,pZ=600000,iZ="gemi_approval",PZ=2000,W9={limit:600,window:60},z9={limit:30,window:60};class c{registry;static token="router.mcp.http";endpoint;resolvers;context;allowedOrigins;rateLimit;maxBodyBytes;serverInfo;instructions;nonces;approvalTimeoutMs;pending=new Map;inflight=new Map;constructor(Z,$){this.registry=Z;if(typeof $.url!=="string")throw Error(`route.mcp.remote.url is required: the endpoint's canonical URL, like "https://example.com/mcp".`);this.endpoint=K9($.url);let G=$.resolvers??[];if(!Array.isArray(G)||G.length===0)throw Error("route.mcp.remote is enabled without a caller resolver. Configure route.mcp.remote.resolvers \u2014 an McpOAuthServer, an McpApiKeyResolver, or your own \u2014 or leave the endpoint off. Without one every request would be refused, or answered as nobody.");for(let W of G)if(typeof W?.resolve!=="function"||typeof W.name!=="string"||W.name==="")throw Error("route.mcp.remote.resolvers: each resolver needs a name and a resolve().");this.resolvers=G,t(xZ),this.context={resource:this.resource,registry:Z},this.allowedOrigins=new Set(($.allowedOrigins??[]).map((W)=>{if(new URL(W).origin!==W)throw Error(`route.mcp.remote.allowedOrigins: "${W}" is not an origin (scheme://host[:port]).`);return W})),this.allowedOrigins.add(this.endpoint.origin),this.rateLimit=$.rateLimit===!1?!1:$.rateLimit??W9,this.maxBodyBytes=$.maxBodyBytes??Math.ceil(Z.maxFileBytes*1.4)+1048576,this.serverInfo={name:$.server?.name??"gemi",version:$.server?.version??"1.0.0"},this.instructions=$.server?.instructions,this.nonces=$.nonces??new eZ,this.approvalTimeoutMs=$.approvalTimeoutMs??pZ;for(let W of G)W.boot?.(this.context)}get resource(){return this.endpoint.href.replace(/\/$/,"")}async handle(Z){let $=new URL(Z.url);if($.host!==this.endpoint.host)return null;for(let z of this.resolvers){let X=await z.handle?.(Z,this.context);if(X)return X}if($.pathname!==this.endpoint.pathname)return null;let G=Z.headers.get("origin");if(G!==null&&!this.allowedOrigins.has(G))return S(T(null,new F(N.InvalidRequest,"Origin not allowed.")),403);let W=G?Y9(G):{};if(Z.method==="OPTIONS")return new Response(null,{status:204,headers:{...W,"Access-Control-Allow-Methods":"POST","Access-Control-Allow-Headers":"Authorization, Content-Type, Accept, MCP-Protocol-Version, Mcp-Session-Id, Mcp-Method, Mcp-Name, Last-Event-ID","Access-Control-Max-Age":"600"}});if(Z.method!=="POST")return lZ(new Response(null,{status:405,headers:{Allow:"POST, OPTIONS"}}),W);return lZ(await this.post(Z),W)}async post(Z){let $=null;for(let Y of this.resolvers){let K=await Y.resolve(Z,this.context);if(K===null)continue;if(K.ok===!1)return await this.failedAttempt(Z)??this.unauthorized(K.description);$={...K.principal,via:Y.name};break}if(!$)return(KZ(Z)!==null?await this.failedAttempt(Z):null)??this.unauthorized();let G=await this.spend($);if(G)return G;let W=Z.headers.get("content-type")??"";if(!/^application\/json\s*(;|$)/i.test(W))return new Response("Content-Type must be application/json.",{status:415});let z;try{z=JSON.parse(await X9(Z,this.maxBodyBytes))}catch(Y){if(Y instanceof o)return S(T(null,new F(N.InvalidRequest,`The request is larger than ${this.maxBodyBytes} bytes.`)),413);return S(T(null,new F(N.ParseError,"Parse error.")),400)}let X=new CZ(Z,{},"api",this.endpoint.pathname);return await aZ.run(X,async()=>{try{return await this.dispatch(Z,X,$,z)}catch(Y){if(Y instanceof F){let K=O(z)&&(typeof z.id==="string"||typeof z.id==="number")?z.id:null;return S(T(K,Y),Y.status)}return console.error("[gemi/mcp] The MCP endpoint failed:",Y),S(T(null,new F(N.InternalError,"Internal error.")),500)}},{user:$.user})}async dispatch(Z,$,G,W){let z=hZ(W),X={kind:"remote",req:$,principal:G};if(z.kind==="response"){let P=this.session(Z,G),x=this.pending.get(String(z.message.id));if(x&&x.session===P.id)this.pending.delete(String(z.message.id)),x.resolve(z.message);return new Response(null,{status:202})}let{message:Y}=z,K=Y.params??{},U=O(K._meta)?K._meta:{};if(typeof U[VZ]==="string"){if(this.checkModernHeaders(Z,Y,U[VZ]),z.kind==="notification")return new Response(null,{status:202});return await this.respond(Y,async(P,x)=>this.modern({principal:G,caller:X,message:Y,params:K,era:{modern:!0},stream:P,signal:x}),tZ(K),Z.signal)}if(Y.method==="initialize"){if(z.kind!=="request")throw new F(N.InvalidRequest,"initialize is a request.",void 0,400);return this.initialize(z.message,K,G)}let H=this.session(Z,G),Q=Z.headers.get("mcp-protocol-version");if(Q!==null&&Q!==H.state.v)throw new F(N.InvalidRequest,`MCP-Protocol-Version ${Q} is not the session's ${H.state.v}.`,void 0,400);if(z.kind==="notification"){if(Y.method==="notifications/cancelled"){let P=K.requestId;this.inflight.get(`${H.id}:${String(P)}`)?.abort()}return new Response(null,{status:202})}let V=`${H.id}:${String(Y.id)}`,I=new AbortController;this.inflight.set(V,I);let C=AbortSignal.any([I.signal,Z.signal]);try{return await this.respond(Y,async(P,x)=>this.legacy({principal:G,caller:X,message:Y,params:K,era:{modern:!1,session:H.state,sessionId:H.id},stream:P,signal:x}),tZ(K)||U9(this.registry,Y,H.state),C,()=>this.inflight.delete(V))}catch(P){throw this.inflight.delete(V),P}}async modern(Z){switch(Z.message.method){case"server/discover":return this.complete({supportedVersions:QZ,capabilities:{tools:{}},...this.instructions?{instructions:this.instructions}:{},ttlMs:60000,cacheScope:"private"});case"tools/list":return this.complete({tools:this.registry.list(Z.caller).map(($)=>JZ($,!0)),ttlMs:60000,cacheScope:"private"});case"tools/call":return await this.callTool(Z);default:throw new F(N.MethodNotFound,`Method not found: ${Z.message.method}`,void 0,404)}}async legacy(Z){switch(Z.message.method){case"ping":return{};case"tools/list":return{tools:this.registry.list(Z.caller).map(($)=>JZ($,!1))};case"tools/call":return await this.callTool(Z);default:throw new F(N.MethodNotFound,`Method not found: ${Z.message.method}`)}}complete(Z){return{resultType:"complete",...Z,_meta:{[IZ]:this.serverInfo}}}initialize(Z,$,G){let W=$.protocolVersion,z=n.includes(W)?W:n[0],X=O($.capabilities)?$.capabilities:{},Y={v:z,e:nZ(X.elicitation),p:s(G),exp:Date.now()+G9},K=FZ(cZ,"gms1",Y);return S({jsonrpc:"2.0",id:Z.id,result:{protocolVersion:z,capabilities:{tools:{listChanged:!1}},serverInfo:this.serverInfo,...this.instructions?{instructions:this.instructions}:{}}},200,{"Mcp-Session-Id":K})}session(Z,$){let G=Z.headers.get("mcp-session-id");if(!G)throw new F(N.InvalidRequest,"Missing Mcp-Session-Id. Send initialize first, or a request carrying io.modelcontextprotocol/protocolVersion in _meta.",void 0,400);let W=NZ(cZ,"gms1",G);if(!W||W.p!==s($))throw new F(N.InvalidRequest,"Session not found.",void 0,404);return{id:G,state:W}}checkModernHeaders(Z,$,G){if(!UZ.includes(G))throw new F(N.UnsupportedProtocolVersion,"Unsupported protocol version",{supported:QZ,requested:G},400);let W=(Y)=>new F(N.HeaderMismatch,`Header mismatch: ${Y}`,void 0,400);if(Z.headers.get("mcp-protocol-version")!==G)throw W("MCP-Protocol-Version does not match the protocol version in _meta.");if(Z.headers.get("mcp-method")!==$.method)throw W("Mcp-Method does not match the method.");let z=$.params??{},X=$.method==="tools/call"||$.method==="prompts/get"?z.name:$.method==="resources/read"?z.uri:void 0;if(X!==void 0){let Y=Z.headers.get("mcp-name");if(Y===null||mZ(Y)!==X)throw W("Mcp-Name does not match the request's name.")}}async callTool(Z){let{params:$,caller:G,principal:W}=Z,z=$.name;if(typeof z!=="string")throw new F(N.InvalidParams,"tools/call needs a tool name.");let X=$.arguments??{},Y=this.registry.list(G,{names:[z]})[0];if(!Y)throw new F(N.InvalidParams,`Unknown tool: ${z}`);let K=!1;if(Y.requiresApproval){let I=Y.inputSchema.safeParse(X);if(I.ok===!1)return this.toolResult(Z,{content:[{type:"text",text:`Invalid arguments for "${z}": ${I.errors.join(", ")}`}],isError:!0});let C=await this.approval(Z,Y,X);if(C.kind==="answer")return C.result;K=!0}let U=Q9(Z),H;try{H=await this.registry.execute(G,z,X,void 0,{approved:K,signal:Z.signal,...U?{progress:U}:{}})}catch(I){if(I instanceof L)return this.toolResult(Z,{content:[{type:"text",text:I.message}],isError:!0});if(I instanceof d)throw new F(N.InvalidParams,I.reason==="unknown-tool"?`Unknown tool: ${z}`:I.message);return console.error(`[gemi/mcp] "${z}" failed for ${W.via}:${W.id}:`,I),this.toolResult(Z,{content:[{type:"text",text:`"${z}" failed on the server.`}],isError:!0})}let Q=typeof H==="string"?H:JSON.stringify(H??null),V=Y.outputSchema&&(Z.era.modern||O(H))?{structuredContent:H}:{};return this.toolResult(Z,{content:[{type:"text",text:Q}],...V,isError:!1})}toolResult(Z,$){return Z.era.modern?this.complete($):$}async approval(Z,$,G){let W=V9($,G),z=(H)=>({kind:"answer",result:this.toolResult(Z,{content:[{type:"text",text:H}],isError:!0})});if(Z.era.modern){let H=O(Z.params._meta)?Z.params._meta:{},Q=O(H[BZ])?H[BZ]:{},V=O(Z.params.inputResponses)?Z.params.inputResponses:null,I=Z.params.requestState,C=H9(G);if(V&&I!==void 0){let x=NZ(xZ,"grs1",I);if(!x||x.p!==s(Z.principal)||x.t!==$.name||x.a!==C)throw new F(N.InvalidParams,"requestState is not valid for this call.");if(!await this.spendNonce(x.n,x.exp))throw new F(N.InvalidParams,"requestState has already been used.");return sZ(V[iZ])?{kind:"approved"}:z(`The user did not approve "${$.name}", and it did not run.`)}if(!nZ(Q.elicitation))throw new F(N.MissingRequiredClientCapability,`"${$.name}" needs the user's approval, and this client cannot ask for it (elicitation).`,{requiredCapabilities:{elicitation:{form:{}}}},400);let P={p:s(Z.principal),t:$.name,a:C,n:dZ(16).toString("base64url"),exp:Date.now()+pZ};return{kind:"answer",result:{resultType:"input_required",inputRequests:{[iZ]:{method:"elicitation/create",params:{mode:"form",...W}}},requestState:FZ(xZ,"grs1",P),_meta:{[IZ]:this.serverInfo}}}}let X=Z.era;if(!X.session.e||!Z.stream)return z(`"${$.name}" needs the user's approval, and this client cannot ask for it (elicitation). It did not run.`);let Y=`gemi-approval-${dZ(12).toString("base64url")}`,K=X.sessionId,U=await new Promise((H)=>{let Q=setTimeout(()=>{this.pending.delete(Y),H(null)},this.approvalTimeoutMs),V=(I)=>{clearTimeout(Q),H(I)};this.pending.set(Y,{session:K,resolve:V}),Z.signal.addEventListener("abort",()=>{this.pending.delete(Y),V(null)}),Z.stream.send({jsonrpc:"2.0",id:Y,method:"elicitation/create",params:X.session.v==="2025-11-25"?{mode:"form",...W}:W})});return U&&!U.error&&sZ(U.result)?{kind:"approved"}:z(`The user did not approve "${$.name}", and it did not run.`)}async spendNonce(Z,$){try{return await this.nonces.consume(Z,$)}catch(G){return console.error("[gemi/mcp] Spending an approval failed:",G),!1}}async respond(Z,$,G,W,z){if(!G)try{let U=await $(null,W);return S({jsonrpc:"2.0",id:Z.id,result:U},200)}finally{z?.()}let X=new AbortController,Y=AbortSignal.any([X.signal,W]),K=new G3(()=>X.abort());return(async()=>{try{let U=await $(K,Y);K.send({jsonrpc:"2.0",id:Z.id,result:U})}catch(U){if(U instanceof F)K.send(T(Z.id,U));else console.error("[gemi/mcp] The MCP endpoint failed:",U),K.send(T(Z.id,new F(N.InternalError,"Internal error.")))}finally{z?.(),K.close()}})(),new Response(K.readable,{status:200,headers:{"Content-Type":"text/event-stream","Cache-Control":"no-store","X-Accel-Buffering":"no"}})}unauthorized(Z){let $=this.resolvers.map((W)=>W.challenge?.(this.context)).find(Boolean)??'Bearer realm="mcp"',G=Z?`${$}, error="invalid_token", error_description="${Z.replace(/["\\]/g,"")}"`:$;return S(T(null,new F(N.InvalidRequest,"Unauthorized.")),401,{"WWW-Authenticate":G})}async failedAttempt(Z){let $=rZ(new CZ(Z,{},"api",this.endpoint.pathname)),G=await HZ(`mcp-auth-failed:${$}`,z9);return G===null?null:oZ(G)}async spend(Z){if(!this.rateLimit)return null;let $=await HZ(`mcp-endpoint:${Z.via}:${Z.id}`,this.rateLimit);return $===null?null:oZ($)}}class G3{readable;controller;closed=!1;encoder=new TextEncoder;constructor(Z){this.readable=new ReadableStream({start:($)=>{this.controller=$},cancel:()=>{this.closed=!0,Z()}})}send(Z){if(this.closed)return;this.controller.enqueue(this.encoder.encode(`event: message
2
+ import{Pb as eZ,Sb as t,Tb as Z3,ac as YZ}from"./chunk-mr94nfp1.js";import{lc as LZ,nc as ZZ,qc as B3,rc as I3,sc as N3,uc as O3}from"./chunk-9rr5h818.js";import{Bc as D3,Dc as K3,Fc as H3,Mc as D,Nc as U3,Oc as Q3,Qc as V3,wc as Y3}from"./chunk-dwedx1jb.js";import{Tc as J}from"./chunk-9xyxbxgk.js";import{He as l,Le as m,Me as zZ,Ne as AZ,Vd as p,de as P3,ee as qZ,id as T3,ie as r,md as v,pd as rZ,se as MZ,td as S3,ue as GZ,ye as kZ}from"./chunk-b5a5vvkk.js";import{Ag as i,Rf as b,Tf as CZ,gg as jZ,ug as gZ,yg as $Z,zg as a}from"./chunk-4acpw92g.js";import{Mg as aZ}from"./chunk-m83zm86m.js";import{Sg as DZ}from"./chunk-1xdrs2hx.js";import{Vg as x3,Wg as OZ,Zg as e,_g as SZ,bh as _3}from"./chunk-19dpzp4y.js";import{Ih as X3,Kh as k}from"./chunk-meg8rtr6.js";import{Lh as _}from"./chunk-h43jp8ws.js";import{Rh as L3}from"./chunk-s07v65x6.js";import{Fi as TZ}from"./chunk-1wnnkde0.js";import{Mi as F3,Pi as g}from"./chunk-nz8z8jr1.js";var R3="mcp",E3=/^[\x21\x23-\x5B\x5D-\x7E]+$/;class L extends YZ{status;constructor(Z,$){super(Z);this.status=$;this.name="McpToolError"}}class d extends Error{reason;constructor(Z,$){super(Z);this.reason=$;this.name="McpCallRefusedError"}}var w3=/^[A-Za-z0-9_-]{1,64}$/,b3=/:([A-Za-z_][A-Za-z0-9_]*)([?*+]?)/g,RZ=4000,y=1e5,y3=/^(text\/|application\/([\w.+-]*\+)?(json|xml))/i;class f{router;dispatcher;static token="router.mcp";plans=new Map;scopeTools=new Map;files;constructor(Z,$,G={}){this.router=Z;this.dispatcher=$;this.files=v3(G.files);for(let[W,z]of Object.entries(Z.routes??{}))this.plans.set(W,this.plan(W,z));this.resolveScopes()}list(Z,$){if(EZ(Z),Z.kind==="local")return this.descriptors($);let G=[];for(let[W,z]of this.plans){if(!z.remote||!this.grants(Z.principal,W))continue;if(wZ(z.remote,$))G.push(z.remote)}return G}descriptors(Z){let $=[];for(let{descriptor:G}of this.plans.values())if(wZ(G,Z))$.push(G);return $}get maxFileBytes(){return this.files.maxBytes}scopes(){return[...this.scopeTools].map(([Z,{description:$}])=>({name:Z,description:$}))}scopesFor(Z){return[...this.scopeTools].filter(([,{tools:$}])=>$.has(Z)).map(([$])=>$)}async execute(Z,$,G,W,z={}){EZ(Z);let X=this.plans.get($);if(Z.kind==="remote"){if(!X?.remote||!this.grants(Z.principal,$))throw new d(`There is no tool named "${$}".`,"unknown-tool");if(X.descriptor.requiresApproval&&z.approved!==!0)throw new d(`"${$}" needs the user's approval, and this call does not have it.`,"approval-required");W=void 0}if(!X)throw Error(`McpRegistry: there is no tool named "${$}".`);let Y=X,K=Z.kind==="remote"?Y.remote:Y.descriptor,U=K.inputSchema.safeParse(G);if(U.ok===!1)throw new L(`Invalid arguments for "${$}": ${U.errors.join(", ")}`);let H=U.value,Q={caller:Z,req:Z.req,tool:K,input:H,ctx:W},V=await this.fillPath(Y,Q),{path:I}=this.dispatcher.getRouteHandlerAndParams(new Request(`http://gemi.internal/api${V}`));if(I!==K.url)throw new L(`"${$}" has nothing at ${V}.`,404);let C={};for(let q of Y.jsonKeys)if(H[q]!==void 0)C[q]=H[q];let P,x="";if(Y.fileFields.length>0)P=await this.formData(Y,C,Q);else if(K.method==="GET")x=t3(C);else if(Y.jsonKeys.length>0)P=C;let j=this.router.credentials?await this.bind(Y,"the credentials",()=>this.router.credentials(Q)):void 0,w={...j?{credentials:j}:{},...z.signal?{signal:z.signal}:{},...z.progress?{progress:z.progress}:{},...Z.kind==="remote"?{identity:{user:Z.principal.user,grant:{via:Z.principal.via,id:Z.principal.id,scopes:Z.principal.scopes,...Z.principal.clientId?{clientId:Z.principal.clientId}:{}}}}:{}},u;try{let q=x?`${V}?${x}`:V;u=Object.keys(w).length>0?await this.dispatcher.dispatchAs(Z.req,K.method,q,P,w):await this.dispatcher.dispatchAs(Z.req,K.method,q,P)}catch(q){throw console.error(`[gemi/mcp] Dispatching "${$}" failed:`,q),new L(`"${$}" failed on the server.`,500)}if(!Y.result&&!K.outputSchema)return await vZ($,u);return await this.project(Y,Q,u)}plan(Z,$){if(!w3.test(Z))throw Error(`McpRouter: "${Z}" is not a valid tool name. A routes key is the tool's name as a model and an MCP client see it, used verbatim \u2014 letters, digits, "_" and "-", at most 64.`);let G=`McpRouter: "${Z}" (${$?.method} ${$?.url})`;if(!($ instanceof Object)||$.__internal_brand!=="McpRoute")throw Error(`McpRouter: "${Z}" is not a route. Declare it with this.fromApiRoute(...).`);let{method:W,url:z,meta:X}=$;if(z.startsWith("/__gemi__"))throw Error(`${G} is a framework route, not an app route.`);let Y=this.dispatcher.flatRoutes[z]?.[W];if(!Y)throw Error(`${G} names no route of this app. Expose a route by the path and verb it is mounted at.`);let K=[...z.matchAll(b3)].map(([,B,A])=>({name:B,modifier:A})),U=X.params??{};for(let B of K)if(!(B.name in U))throw Error(`${G}: the path param "${B.name}" must be bound or declared "input".`);for(let[B,A]of Object.entries(U)){if(!K.some((M)=>M.name===B))throw Error(`${G}: "${B}" in params is not a param of the url.`);if(yZ(A))d3(G,`params.${B}`,A);else bZ(G,`params.${B}`,A)}let H=[],Q;if(X.input){if(Q=X.input.toJSONSchema(),Q.type!=="object"||!Q.properties)throw Error(`${G}: input must be an s.object(...), the fields of the request body.`);H=Object.keys(Q.properties)}let V=Object.entries(X.files??{}).map(([B,A])=>({name:B,binder:A}));for(let B of V)bZ(G,`files.${B.name}`,B.binder);if(X.result!==void 0&&typeof X.result!=="function")throw Error(`${G}: result must be a function.`);if(X.output!==void 0&&typeof X.output?.safeParse!=="function")throw Error(`${G}: output must be a schema, built with s.`);if(X.title!==void 0&&(typeof X.title!=="string"||X.title===""))throw Error(`${G}: title must be a non-empty string.`);let I=i3(G,W,X.annotations);if(V.length>0&&W==="GET")throw Error(`${G}: a GET has no body to carry a file.`);let C={},P=(B,A)=>{if(B in C)throw Error(`${G}: "${B}" is the input key of two params or files, and the model can only send one.`);C[B]=A},x=new Map,j=new Map;for(let B of K){let A=U[B.name];if(A==="input"){let M=b.string().describe(`The ":${B.name}" segment of the url.`);P(B.name,B.modifier==="?"||B.modifier==="*"?M.optional():M)}else if(yZ(A)){let M=A.as??B.name;P(M,A.input),j.set(B.name,{key:M,param:A})}else x.set(B.name,A)}let w={};for(let B of V)if(B.binder==="input")P(B.name,b.string().describe("The id of an attachment the user uploaded or a tool produced (gemi_att_\u2026), whose file is sent as this field.")),w[B.name]=this.remoteFileSchema();let u=[...Object.keys(C),...V.map((B)=>B.name)];for(let B of new Set(u))if(H.includes(B))throw Error(`${G}: "${B}" is both an input field and a param or file, and the model can only send one.`);let q=Object.freeze({name:Z,description:X.description,method:W,url:z,inputSchema:fZ(X.input,Q,b.object(C)),...X.output?{outputSchema:X.output}:{},...X.title?{title:X.title}:{},annotations:I,tags:Object.freeze([...X.tags??[]]),requiresApproval:X.requiresApproval===!0,...Y.source?{source:Y.source}:{}}),k3=!V.every((B)=>B.binder==="input")?null:V.length===0?q:Object.freeze({...q,inputSchema:fZ(X.input,Q,b.object(l3(C,Object.keys(w))),w)});return{descriptor:q,remote:k3,params:K,paramBinders:x,modelParams:j,fileFields:V,jsonKeys:H,...X.result?{result:X.result}:{}}}remoteFileSchema(){let{maxBytes:Z,fetch:$}=this.files,G={name:{type:"string",description:"The file's name, like report.pdf."},mimeType:{type:"string",description:"The file's media type, like application/pdf."},data:{type:"string",contentEncoding:"base64",description:`The file's bytes, base64-encoded (RFC 4648, standard alphabet). At most ${Z} bytes decoded.`}};if($)G.url={type:"string",format:"uri",description:"An https URL the server downloads the file from, instead of data."};let W={type:"object",description:$?"A file, sent as base64 in data or fetched from url: exactly one of the two.":"A file, sent as base64 in data.",properties:G,required:["name","mimeType"],additionalProperties:!1},z=Object.keys(G),X=(Y)=>{if(typeof Y!=="object"||Y===null||Array.isArray(Y))return{ok:!1,errors:["a file must be an object"]};let K=[],U={};for(let H of Object.keys(Y))if(!z.includes(H))K.push(`a file has no field "${H}"`);for(let H of z){let Q=Y[H];if(Q===void 0||Q===null){if(H==="name"||H==="mimeType")K.push(`a file needs "${H}"`);continue}if(typeof Q!=="string")K.push(`a file's "${H}" must be a string`);else U[H]=Q}return K.length>0?{ok:!1,errors:K}:{ok:!0,value:U}};return{toJSONSchema:()=>W,safeParse:X,parse(Y){let K=X(Y);if(K.ok===!1)throw Error(K.errors.join(", "));return K.value}}}async fillPath(Z,$){let{input:G,req:W}=$,z=Z.descriptor.url;for(let X of Z.params){let Y=Z.paramBinders.get(X.name),K=Z.modelParams.get(X.name),U;if(Y||K){if(U=K?await this.bind(Z,`the param "${X.name}"`,()=>K.param.bind(G[K.key],W,$),{refusals:!0}):await this.bind(Z,`the param "${X.name}"`,()=>Y(W,$)),U===void 0||U===null||U==="")throw console.error(`[gemi/mcp] The ${K?"resolver":"binder"} for "${X.name}" of "${Z.descriptor.name}" returned ${JSON.stringify(U)}.`),new L(`"${Z.descriptor.name}" failed on the server.`,500)}else U=G[X.name];let H=`:${X.name}${X.modifier}`;if(U===void 0||U===null||U===""){if(X.modifier!=="?"&&X.modifier!=="*")throw new L(`"${X.name}" of "${Z.descriptor.name}" must not be empty.`);z=z.replace(`/${H}`,"");continue}z=z.replace(H,n3(Z.descriptor.name,X,String(U)))}return z}async formData(Z,$,G){let{input:W,ctx:z}=G,X=Z.descriptor.name;if(G.caller.kind==="remote"){let K=new FormData;for(let[U,H]of Object.entries($))XZ(K,U,H);for(let U of Z.fileFields)K.append(U.name,await this.remoteFile(X,U.name,W[U.name]));return K}if(!z)throw Error(`McpRegistry: "${X}" sends a file, which is resolved through the tool context's attachments, and none was passed to execute().`);let Y=new FormData;for(let[K,U]of Object.entries($))XZ(Y,K,U);for(let K of Z.fileFields){let U=K.binder==="input"?W[K.name]:await this.bind(Z,`the file "${K.name}"`,()=>K.binder(z,G));if(typeof U!=="string"||U==="")throw new L(`"${X}" needs a file for "${K.name}", and this turn has none. Ask the user to attach one.`);Y.append(K.name,await z.attachments.file(U))}return Y}async remoteFile(Z,$,G){let W=(Q)=>new L(`"${$}" of "${Z}" ${Q}`,400),z=G,{maxBytes:X,fetch:Y}=this.files;if(!g3(z.name))throw W("needs a name of 1 to 255 characters, without control characters or slashes.");if(!h3.test(z.mimeType))throw W(`has a mimeType that is not a media type: "${z.mimeType.slice(0,100)}".`);let K=typeof z.data==="string",U=typeof z.url==="string";if(K===U)throw W(Y?"needs exactly one of data or url.":"needs its bytes, base64, in data.");if(K){let Q=z.data.replace(/\s+/g,"");if(Math.floor(Q.length*3/4)-u3(Q)>X)throw W(`is larger than the ${X} bytes a file may be.`);if(Q.length%4!==0||!m3.test(Q))throw W("has data that is not base64 (RFC 4648, standard alphabet, padded).");return new File([Buffer.from(Q,"base64")],z.name,{type:z.mimeType})}if(!Y)throw W("cannot be fetched from a URL here. Send its bytes, base64, in data.");let H;try{H=new URL(z.url)}catch{throw W("has a url that is not a URL.")}if(H.protocol!=="https:")throw W("can only be fetched from an https URL.");try{let Q=await gZ(H.href,{...Y,maxSize:Math.min(Y.maxSize??X,X),redirect:"follow"});if(!Q.ok)throw W(`could not be fetched: ${H.host} answered ${Q.status}.`);let V=await Q.arrayBuffer();return new File([V],z.name,{type:z.mimeType})}catch(Q){if(Q instanceof L)throw Q;throw W(`could not be fetched from ${H.host}: ${Q instanceof Error?Q.message:"failed"}.`)}}grants(Z,$){for(let G of Z.scopes??[])if(this.scopeTools.get(G)?.tools.has($))return!0;return!1}resolveScopes(){let Z=this.router.scopes;if(Z===void 0){this.scopeTools.set(R3,{description:"Use this app's tools as you",tools:new Set(this.plans.keys())});return}if(typeof Z!=="object"||Z===null)throw Error("McpRouter: scopes must be an object of scope names.");let $=new Set([...this.plans.values()].flatMap(({descriptor:G})=>G.tags));for(let[G,W]of Object.entries(Z)){let z=`McpRouter: scope "${G}"`;if(!E3.test(G))throw Error(`${z} is not a valid OAuth scope: printable ASCII, no spaces or quotes.`);if(typeof W?.description!=="string"||W.description==="")throw Error(`${z} needs a description: it is what a consent screen shows.`);let X=new Set;for(let Y of W.tags??[]){if(!$.has(Y))throw Error(`${z} names the tag "${Y}", which no tool has.`);for(let[K,{descriptor:U}]of this.plans)if(U.tags.includes(Y))X.add(K)}for(let Y of W.names??[]){if(!this.plans.has(Y))throw Error(`${z} names "${Y}", which is no tool.`);X.add(Y)}if(X.size===0)throw Error(`${z} reaches no tool. Give it tags or names.`);this.scopeTools.set(G,{description:W.description,tools:X})}}async project(Z,$,G){let{name:W,outputSchema:z}=Z.descriptor;if(G.status<200||G.status>=300)return await vZ(W,G);let X=(V,I)=>{if(I===void 0)console.error(`[gemi/mcp] "${W}" ${V}`);else console.error(`[gemi/mcp] "${W}" ${V}`,I);return new L(`"${W}" failed on the server.`,500)},Y=G.headers.get("Content-Type"),K=await G.text(),U=null;if(K!=="")try{U=JSON.parse(K)}catch{throw X(`answered ${Y??"a body with no content-type"} that is not JSON, and its result or output needs JSON.`)}let H=U;if(Z.result)try{H=await Z.result(U,$)}catch(V){throw X("failed in its result projection:",V)}if(z){let V=z.safeParse(H);if(V.ok===!1)throw X(`answered what its output schema refuses: ${V.errors.join(", ")}`);H=V.value}if(H===void 0)return null;let Q=JSON.stringify(H);if(Q!==void 0&&Q.length>y)return`${Q.slice(0,y)}\u2026 [cut at ${y} of ${Q.length} characters]`;return H}async bind(Z,$,G,W={}){try{return await G()}catch(z){if(W.refusals&&z instanceof YZ)throw z;throw console.error(`[gemi/mcp] Binding ${$} of "${Z.descriptor.name}" failed:`,z),new L(`"${Z.descriptor.name}" failed on the server.`,500)}}}function EZ(Z){if(Z?.kind==="local"&&Z.req)return;if(Z?.kind==="remote"&&Z.req&&Z.principal?.user&&typeof Z.principal.id==="string"&&Array.isArray(Z.principal.scopes))return;throw Error('McpRegistry: a caller is { kind: "local", req } or { kind: "remote", req, principal }, with the principal a caller resolver verified.')}function wZ(Z,$){if($?.names&&!$.names.includes(Z.name))return!1;if($?.tags&&!Z.tags.some((G)=>$.tags.includes(G)))return!1;return!0}var f3=10485760;function v3(Z){let $=Z?.maxBytes??f3;if(!Number.isSafeInteger($)||$<=0)throw Error("McpRegistry: files.maxBytes must be a positive whole number of bytes.");let G=Z?.fetchUrls===!0?{}:typeof Z?.fetchUrls==="object"&&Z.fetchUrls!==null?Z.fetchUrls:null;return{maxBytes:$,fetch:G}}function g3(Z){if(Z.length===0||Z.length>255)return!1;for(let $ of Z){let G=$.codePointAt(0);if(G<32||G===127||$==="/"||$==="\\")return!1}return!0}var h3=/^[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}\/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}(\s*;.{0,200})?$/,m3=/^[A-Za-z0-9+/]*={0,2}$/;function u3(Z){return Z.endsWith("==")?2:Z.endsWith("=")?1:0}function bZ(Z,$,G){if(G!=="input"&&typeof G!=="function")throw Error(`${Z}: ${$} must be a function or "input".`)}function yZ(Z){return Z instanceof Object&&Z.__internal_brand==="McpModelParam"}function d3(Z,$,G){if(G.as!==void 0&&(typeof G.as!=="string"||G.as===""))throw Error(`${Z}: ${$}.as must be a non-empty string.`);let W=G.input;if(typeof W?.safeParse!=="function"||typeof W.toJSONSchema!=="function")throw Error(`${Z}: ${$}.input must be a schema, built with s.`);if(typeof G.bind!=="function")throw Error(`${Z}: ${$}.bind must be a function.`)}var c3=new Set(["readOnlyHint","destructiveHint","idempotentHint","openWorldHint"]);function p3(Z){switch(Z){case"GET":return{readOnlyHint:!0};case"DELETE":return{destructiveHint:!0};case"PUT":return{idempotentHint:!0};default:return{}}}function i3(Z,$,G){if(G!==void 0&&(typeof G!=="object"||G===null))throw Error(`${Z}: annotations must be an object of hints.`);let W=p3($);for(let[z,X]of Object.entries(G??{})){if(!c3.has(z))throw Error(`${Z}: "${z}" is not a tool annotation. Use readOnlyHint, destructiveHint, idempotentHint or openWorldHint.`);if(X===void 0)continue;if(typeof X!=="boolean")throw Error(`${Z}: annotations.${z} must be true or false.`);W[z]=X}if(W.readOnlyHint===!0&&(W.destructiveHint||W.idempotentHint))throw Error(`${Z} is read-only${G?.readOnlyHint?"":` (a ${$})`} and also ${W.destructiveHint?"destructive":"idempotent"}, which only a tool that is not read-only can be. Set readOnlyHint: false if it changes something.`);return Object.freeze(W)}function fZ(Z,$,G,W={}){let z=G.toJSONSchema(),X=Object.fromEntries(Object.entries(W).map(([U,H])=>[U,H.toJSONSchema()])),Y={...$??{},type:"object",properties:{...$?.properties??{},...z.properties,...X},required:[...$?.required??[],...z.required??[],...Object.keys(W)],additionalProperties:!1},K=(U)=>{if(typeof U!=="object"||U===null||Array.isArray(U))return{ok:!1,errors:["expected an object"]};let H=[],Q={};if(Z){let I=Z.safeParse(U);if(I.ok===!1)H.push(...I.errors);else Q={...I.value}}let V=G.safeParse(U);if(V.ok===!1)H.push(...V.errors);else Q={...Q,...V.value};for(let[I,C]of Object.entries(W)){let P=U[I];if(P===void 0){H.push(`"${I}" is required`);continue}let x=C.safeParse(P);if(x.ok===!1)H.push(...x.errors.map((j)=>`${I}: ${j}`));else Q[I]=x.value}return H.length>0?{ok:!1,errors:H}:{ok:!0,value:Q}};return{toJSONSchema:()=>Y,safeParse:K,parse(U){let H=K(U);if(H.ok===!1)throw Error(H.errors.join(", "));return H.value}}}function l3(Z,$){return Object.fromEntries(Object.entries(Z).filter(([G])=>!$.includes(G)))}function n3(Z,$,G){let W=$.modifier==="*"||$.modifier==="+"?G.split("/"):[G];if(W.some((z)=>z==="."||z===".."))throw new L(`"${G}" is not a valid value for "${$.name}" of "${Z}".`);return W.map(encodeURIComponent).join("/")}function XZ(Z,$,G){if(G===void 0||G===null)return;if(Array.isArray(G)){for(let W of G)XZ(Z,$,W);return}Z.append($,typeof G==="object"?JSON.stringify(G):String(G))}function t3(Z){let $=new URLSearchParams,G=(W,z)=>{if(z===void 0||z===null)return;if(Array.isArray(z)){for(let X of z)G(W,X);return}$.append(W,typeof z==="object"?JSON.stringify(z):String(z))};for(let[W,z]of Object.entries(Z))G(W,z);return $.toString()}async function vZ(Z,$){if($.status>=200&&$.status<300){let W=$.headers.get("Content-Type");if(W&&!y3.test(W)){let X=(await $.arrayBuffer()).byteLength;return`"${Z}" answered with ${X} bytes of ${W}, which is not shown.`}let z=await $.text();if(z==="")return null;if(z.length>y)return`${z.slice(0,y)}\u2026 [cut at ${y} of ${z.length} characters]`;try{return JSON.parse(z)}catch{return z}}let G=await $.text();if($.status>=400&&$.status<500){let W=G.length>RZ?`${G.slice(0,RZ)}\u2026`:G;throw new L(`"${Z}" was refused with ${$.status}${W?`: ${W}`:"."}`,$.status)}if($.status<500)console.error(`[gemi/mcp] "${Z}" answered ${$.status}${$.headers.get("Location")?` to ${$.headers.get("Location")}`:""}, which a tool call cannot follow.`);throw new L(`"${Z}" failed on the server.`,$.status)}import{createHash as s3,randomBytes as o3,timingSafeEqual as a3}from"crypto";function KZ(Z){let $=Z.headers.get("authorization");if(!$)return null;let G=/^Bearer[ ]+([\x21-\x7e]+)$/i.exec($.trim());return G?G[1]:null}class r3{options;name="api-key";constructor(Z){this.options=Z;if(typeof Z?.prefix!=="string"||!/^[A-Za-z0-9_-]{3,32}$/.test(Z.prefix))throw Error('McpApiKeyResolver: prefix is required: 3 to 32 letters, digits, _ or -, like "myapp_mcp_".');if(typeof Z.verify!=="function")throw Error("McpApiKeyResolver: verify must be a function that looks a key up.")}static generate(Z){return`${Z}${o3(32).toString("base64url")}`}static hash(Z){return s3("sha256").update(Z).digest("hex")}static hashesMatch(Z,$){let G=Buffer.from(Z),W=Buffer.from($);return G.length===W.length&&a3(G,W)}async resolve(Z){let $=KZ(Z);if(!$?.startsWith(this.options.prefix))return null;let G=await this.options.verify($,Z);if(!G)return{ok:!1,error:"invalid_token",description:"The API key is not valid."};if(!G.user||typeof G.id!=="string"||G.id===""||!Array.isArray(G.scopes))throw Error("McpApiKeyResolver: verify must answer { user, id, scopes } for a key it knows, or null.");return{ok:!0,principal:{user:G.user,via:this.name,id:G.id,scopes:[...G.scopes]}}}}import{createHash as $3,randomBytes as dZ}from"crypto";async function HZ(Z,$){let G=e3();if(!G)return null;let W=await G.consume(Z,$);return W.allowed?null:Math.max(1,Math.ceil(W.retryAfter/1000))}function e3(){try{let Z=g();return Z.bound(v)?Z.make(v):null}catch{return null}}var UZ=["2026-07-28"],n=["2025-11-25","2025-06-18","2025-03-26"],QZ=[...UZ,...n],VZ="io.modelcontextprotocol/protocolVersion",BZ="io.modelcontextprotocol/clientCapabilities";var IZ="io.modelcontextprotocol/serverInfo",N={ParseError:-32700,InvalidRequest:-32600,MethodNotFound:-32601,InvalidParams:-32602,InternalError:-32603,HeaderMismatch:-32020,MissingRequiredClientCapability:-32021,UnsupportedProtocolVersion:-32022};class F extends Error{code;data;status;constructor(Z,$,G,W=200){super($);this.code=Z;this.data=G;this.status=W;this.name="JsonRpcError"}}function T(Z,$){return{jsonrpc:"2.0",id:Z,error:{code:$.code,message:$.message,...$.data!==void 0?{data:$.data}:{}}}}function hZ(Z){if(Array.isArray(Z))throw new F(N.InvalidRequest,"Batched requests are not supported.",void 0,400);if(typeof Z!=="object"||Z===null||Z.jsonrpc!=="2.0")throw new F(N.InvalidRequest,"Not a JSON-RPC 2.0 message.",void 0,400);let $=Z,G=$.id,W=typeof G==="string"||typeof G==="number"&&Number.isFinite(G);if(typeof $.method==="string"){if($.params!==void 0&&(typeof $.params!=="object"||$.params===null||Array.isArray($.params)))throw new F(N.InvalidRequest,"params must be an object.",void 0,400);if(G===void 0)return{kind:"notification",message:$};if(!W)throw new F(N.InvalidRequest,"id must be a string or a number.",void 0,400);return{kind:"request",message:$}}if(W&&(("result"in $)||("error"in $)))return{kind:"response",message:$};throw new F(N.InvalidRequest,"Not a JSON-RPC request, notification or response.",void 0,400)}function JZ(Z,$){let G=Z.outputSchema?.toJSONSchema(),W={...Z.annotations,...Z.title&&!$?{title:Z.title}:{}};return{name:Z.name,...Z.title?{title:Z.title}:{},description:Z.description,inputSchema:Z.inputSchema.toJSONSchema(),...G&&($||G.type==="object")?{outputSchema:G}:{},...Object.keys(W).length>0?{annotations:W}:{}}}function O(Z){return typeof Z==="object"&&Z!==null&&!Array.isArray(Z)}function mZ(Z){let $=/^=\?base64\?([A-Za-z0-9+/]*={0,2})\?=$/.exec(Z);if(!$)return Z;try{let G=Buffer.from($[1],"base64");return new TextDecoder("utf-8",{fatal:!0}).decode(G)}catch{return null}}import{createHmac as Z9,timingSafeEqual as $9}from"crypto";function FZ(Z,$,G){let W=Buffer.from(JSON.stringify(G)).toString("base64url");return`${$}.${W}.${uZ(Z,$,W).toString("base64url")}`}function NZ(Z,$,G,W=Date.now()){if(typeof G!=="string"||G.length>8192)return null;let z=G.split(".");if(z.length!==3||z[0]!==$)return null;let[,X,Y]=z,K=Buffer.from(Y,"base64url"),U=uZ(Z,$,X);if(K.length!==U.length||!$9(K,U))return null;let H;try{H=JSON.parse(Buffer.from(X,"base64url").toString("utf8"))}catch{return null}if(typeof H!=="object"||H===null)return null;if(typeof H.exp!=="number"||H.exp<=W)return null;return H}function uZ(Z,$,G){return Z9("sha256",t(Z)).update(`${$}.${G}`).digest()}var xZ="gemi.mcp.request-state.v1",cZ="gemi.mcp.session.v1",G9=86400000,pZ=600000,iZ="gemi_approval",PZ=2000,W9={limit:600,window:60},z9={limit:30,window:60};class c{registry;static token="router.mcp.http";endpoint;resolvers;context;allowedOrigins;rateLimit;maxBodyBytes;serverInfo;instructions;nonces;approvalTimeoutMs;pending=new Map;inflight=new Map;constructor(Z,$){this.registry=Z;if(typeof $.url!=="string")throw Error(`route.mcp.remote.url is required: the endpoint's canonical URL, like "https://example.com/mcp".`);this.endpoint=K9($.url);let G=$.resolvers??[];if(!Array.isArray(G)||G.length===0)throw Error("route.mcp.remote is enabled without a caller resolver. Configure route.mcp.remote.resolvers \u2014 an McpOAuthServer, an McpApiKeyResolver, or your own \u2014 or leave the endpoint off. Without one every request would be refused, or answered as nobody.");for(let W of G)if(typeof W?.resolve!=="function"||typeof W.name!=="string"||W.name==="")throw Error("route.mcp.remote.resolvers: each resolver needs a name and a resolve().");this.resolvers=G,t(xZ),this.context={resource:this.resource,registry:Z},this.allowedOrigins=new Set(($.allowedOrigins??[]).map((W)=>{if(new URL(W).origin!==W)throw Error(`route.mcp.remote.allowedOrigins: "${W}" is not an origin (scheme://host[:port]).`);return W})),this.allowedOrigins.add(this.endpoint.origin),this.rateLimit=$.rateLimit===!1?!1:$.rateLimit??W9,this.maxBodyBytes=$.maxBodyBytes??Math.ceil(Z.maxFileBytes*1.4)+1048576,this.serverInfo={name:$.server?.name??"gemi",version:$.server?.version??"1.0.0"},this.instructions=$.server?.instructions,this.nonces=$.nonces??new eZ,this.approvalTimeoutMs=$.approvalTimeoutMs??pZ;for(let W of G)W.boot?.(this.context)}get resource(){return this.endpoint.href.replace(/\/$/,"")}async handle(Z){let $=new URL(Z.url);if($.host!==this.endpoint.host)return null;for(let z of this.resolvers){let X=await z.handle?.(Z,this.context);if(X)return X}if($.pathname!==this.endpoint.pathname)return null;let G=Z.headers.get("origin");if(G!==null&&!this.allowedOrigins.has(G))return S(T(null,new F(N.InvalidRequest,"Origin not allowed.")),403);let W=G?Y9(G):{};if(Z.method==="OPTIONS")return new Response(null,{status:204,headers:{...W,"Access-Control-Allow-Methods":"POST","Access-Control-Allow-Headers":"Authorization, Content-Type, Accept, MCP-Protocol-Version, Mcp-Session-Id, Mcp-Method, Mcp-Name, Last-Event-ID","Access-Control-Max-Age":"600"}});if(Z.method!=="POST")return lZ(new Response(null,{status:405,headers:{Allow:"POST, OPTIONS"}}),W);return lZ(await this.post(Z),W)}async post(Z){let $=null;for(let Y of this.resolvers){let K=await Y.resolve(Z,this.context);if(K===null)continue;if(K.ok===!1)return await this.failedAttempt(Z)??this.unauthorized(K.description);$={...K.principal,via:Y.name};break}if(!$)return(KZ(Z)!==null?await this.failedAttempt(Z):null)??this.unauthorized();let G=await this.spend($);if(G)return G;let W=Z.headers.get("content-type")??"";if(!/^application\/json\s*(;|$)/i.test(W))return new Response("Content-Type must be application/json.",{status:415});let z;try{z=JSON.parse(await X9(Z,this.maxBodyBytes))}catch(Y){if(Y instanceof o)return S(T(null,new F(N.InvalidRequest,`The request is larger than ${this.maxBodyBytes} bytes.`)),413);return S(T(null,new F(N.ParseError,"Parse error.")),400)}let X=new CZ(Z,{},"api",this.endpoint.pathname);return await aZ.run(X,async()=>{try{return await this.dispatch(Z,X,$,z)}catch(Y){if(Y instanceof F){let K=O(z)&&(typeof z.id==="string"||typeof z.id==="number")?z.id:null;return S(T(K,Y),Y.status)}return console.error("[gemi/mcp] The MCP endpoint failed:",Y),S(T(null,new F(N.InternalError,"Internal error.")),500)}},{user:$.user})}async dispatch(Z,$,G,W){let z=hZ(W),X={kind:"remote",req:$,principal:G};if(z.kind==="response"){let P=this.session(Z,G),x=this.pending.get(String(z.message.id));if(x&&x.session===P.id)this.pending.delete(String(z.message.id)),x.resolve(z.message);return new Response(null,{status:202})}let{message:Y}=z,K=Y.params??{},U=O(K._meta)?K._meta:{};if(typeof U[VZ]==="string"){if(this.checkModernHeaders(Z,Y,U[VZ]),z.kind==="notification")return new Response(null,{status:202});return await this.respond(Y,async(P,x)=>this.modern({principal:G,caller:X,message:Y,params:K,era:{modern:!0},stream:P,signal:x}),tZ(K),Z.signal)}if(Y.method==="initialize"){if(z.kind!=="request")throw new F(N.InvalidRequest,"initialize is a request.",void 0,400);return this.initialize(z.message,K,G)}let H=this.session(Z,G),Q=Z.headers.get("mcp-protocol-version");if(Q!==null&&Q!==H.state.v)throw new F(N.InvalidRequest,`MCP-Protocol-Version ${Q} is not the session's ${H.state.v}.`,void 0,400);if(z.kind==="notification"){if(Y.method==="notifications/cancelled"){let P=K.requestId;this.inflight.get(`${H.id}:${String(P)}`)?.abort()}return new Response(null,{status:202})}let V=`${H.id}:${String(Y.id)}`,I=new AbortController;this.inflight.set(V,I);let C=AbortSignal.any([I.signal,Z.signal]);try{return await this.respond(Y,async(P,x)=>this.legacy({principal:G,caller:X,message:Y,params:K,era:{modern:!1,session:H.state,sessionId:H.id},stream:P,signal:x}),tZ(K)||U9(this.registry,Y,H.state),C,()=>this.inflight.delete(V))}catch(P){throw this.inflight.delete(V),P}}async modern(Z){switch(Z.message.method){case"server/discover":return this.complete({supportedVersions:QZ,capabilities:{tools:{}},...this.instructions?{instructions:this.instructions}:{},ttlMs:60000,cacheScope:"private"});case"tools/list":return this.complete({tools:this.registry.list(Z.caller).map(($)=>JZ($,!0)),ttlMs:60000,cacheScope:"private"});case"tools/call":return await this.callTool(Z);default:throw new F(N.MethodNotFound,`Method not found: ${Z.message.method}`,void 0,404)}}async legacy(Z){switch(Z.message.method){case"ping":return{};case"tools/list":return{tools:this.registry.list(Z.caller).map(($)=>JZ($,!1))};case"tools/call":return await this.callTool(Z);default:throw new F(N.MethodNotFound,`Method not found: ${Z.message.method}`)}}complete(Z){return{resultType:"complete",...Z,_meta:{[IZ]:this.serverInfo}}}initialize(Z,$,G){let W=$.protocolVersion,z=n.includes(W)?W:n[0],X=O($.capabilities)?$.capabilities:{},Y={v:z,e:nZ(X.elicitation),p:s(G),exp:Date.now()+G9},K=FZ(cZ,"gms1",Y);return S({jsonrpc:"2.0",id:Z.id,result:{protocolVersion:z,capabilities:{tools:{listChanged:!1}},serverInfo:this.serverInfo,...this.instructions?{instructions:this.instructions}:{}}},200,{"Mcp-Session-Id":K})}session(Z,$){let G=Z.headers.get("mcp-session-id");if(!G)throw new F(N.InvalidRequest,"Missing Mcp-Session-Id. Send initialize first, or a request carrying io.modelcontextprotocol/protocolVersion in _meta.",void 0,400);let W=NZ(cZ,"gms1",G);if(!W||W.p!==s($))throw new F(N.InvalidRequest,"Session not found.",void 0,404);return{id:G,state:W}}checkModernHeaders(Z,$,G){if(!UZ.includes(G))throw new F(N.UnsupportedProtocolVersion,"Unsupported protocol version",{supported:QZ,requested:G},400);let W=(Y)=>new F(N.HeaderMismatch,`Header mismatch: ${Y}`,void 0,400);if(Z.headers.get("mcp-protocol-version")!==G)throw W("MCP-Protocol-Version does not match the protocol version in _meta.");if(Z.headers.get("mcp-method")!==$.method)throw W("Mcp-Method does not match the method.");let z=$.params??{},X=$.method==="tools/call"||$.method==="prompts/get"?z.name:$.method==="resources/read"?z.uri:void 0;if(X!==void 0){let Y=Z.headers.get("mcp-name");if(Y===null||mZ(Y)!==X)throw W("Mcp-Name does not match the request's name.")}}async callTool(Z){let{params:$,caller:G,principal:W}=Z,z=$.name;if(typeof z!=="string")throw new F(N.InvalidParams,"tools/call needs a tool name.");let X=$.arguments??{},Y=this.registry.list(G,{names:[z]})[0];if(!Y)throw new F(N.InvalidParams,`Unknown tool: ${z}`);let K=!1;if(Y.requiresApproval){let I=Y.inputSchema.safeParse(X);if(I.ok===!1)return this.toolResult(Z,{content:[{type:"text",text:`Invalid arguments for "${z}": ${I.errors.join(", ")}`}],isError:!0});let C=await this.approval(Z,Y,X);if(C.kind==="answer")return C.result;K=!0}let U=Q9(Z),H;try{H=await this.registry.execute(G,z,X,void 0,{approved:K,signal:Z.signal,...U?{progress:U}:{}})}catch(I){if(I instanceof L)return this.toolResult(Z,{content:[{type:"text",text:I.message}],isError:!0});if(I instanceof d)throw new F(N.InvalidParams,I.reason==="unknown-tool"?`Unknown tool: ${z}`:I.message);return console.error(`[gemi/mcp] "${z}" failed for ${W.via}:${W.id}:`,I),this.toolResult(Z,{content:[{type:"text",text:`"${z}" failed on the server.`}],isError:!0})}let Q=typeof H==="string"?H:JSON.stringify(H??null),V=Y.outputSchema&&(Z.era.modern||O(H))?{structuredContent:H}:{};return this.toolResult(Z,{content:[{type:"text",text:Q}],...V,isError:!1})}toolResult(Z,$){return Z.era.modern?this.complete($):$}async approval(Z,$,G){let W=V9($,G),z=(H)=>({kind:"answer",result:this.toolResult(Z,{content:[{type:"text",text:H}],isError:!0})});if(Z.era.modern){let H=O(Z.params._meta)?Z.params._meta:{},Q=O(H[BZ])?H[BZ]:{},V=O(Z.params.inputResponses)?Z.params.inputResponses:null,I=Z.params.requestState,C=H9(G);if(V&&I!==void 0){let x=NZ(xZ,"grs1",I);if(!x||x.p!==s(Z.principal)||x.t!==$.name||x.a!==C)throw new F(N.InvalidParams,"requestState is not valid for this call.");if(!await this.spendNonce(x.n,x.exp))throw new F(N.InvalidParams,"requestState has already been used.");return sZ(V[iZ])?{kind:"approved"}:z(`The user did not approve "${$.name}", and it did not run.`)}if(!nZ(Q.elicitation))throw new F(N.MissingRequiredClientCapability,`"${$.name}" needs the user's approval, and this client cannot ask for it (elicitation).`,{requiredCapabilities:{elicitation:{form:{}}}},400);let P={p:s(Z.principal),t:$.name,a:C,n:dZ(16).toString("base64url"),exp:Date.now()+pZ};return{kind:"answer",result:{resultType:"input_required",inputRequests:{[iZ]:{method:"elicitation/create",params:{mode:"form",...W}}},requestState:FZ(xZ,"grs1",P),_meta:{[IZ]:this.serverInfo}}}}let X=Z.era;if(!X.session.e||!Z.stream)return z(`"${$.name}" needs the user's approval, and this client cannot ask for it (elicitation). It did not run.`);let Y=`gemi-approval-${dZ(12).toString("base64url")}`,K=X.sessionId,U=await new Promise((H)=>{let Q=setTimeout(()=>{this.pending.delete(Y),H(null)},this.approvalTimeoutMs),V=(I)=>{clearTimeout(Q),H(I)};this.pending.set(Y,{session:K,resolve:V}),Z.signal.addEventListener("abort",()=>{this.pending.delete(Y),V(null)}),Z.stream.send({jsonrpc:"2.0",id:Y,method:"elicitation/create",params:X.session.v==="2025-11-25"?{mode:"form",...W}:W})});return U&&!U.error&&sZ(U.result)?{kind:"approved"}:z(`The user did not approve "${$.name}", and it did not run.`)}async spendNonce(Z,$){try{return await this.nonces.consume(Z,$)}catch(G){return console.error("[gemi/mcp] Spending an approval failed:",G),!1}}async respond(Z,$,G,W,z){if(!G)try{let U=await $(null,W);return S({jsonrpc:"2.0",id:Z.id,result:U},200)}finally{z?.()}let X=new AbortController,Y=AbortSignal.any([X.signal,W]),K=new G3(()=>X.abort());return(async()=>{try{let U=await $(K,Y);K.send({jsonrpc:"2.0",id:Z.id,result:U})}catch(U){if(U instanceof F)K.send(T(Z.id,U));else console.error("[gemi/mcp] The MCP endpoint failed:",U),K.send(T(Z.id,new F(N.InternalError,"Internal error.")))}finally{z?.(),K.close()}})(),new Response(K.readable,{status:200,headers:{"Content-Type":"text/event-stream","Cache-Control":"no-store","X-Accel-Buffering":"no"}})}unauthorized(Z){let $=this.resolvers.map((W)=>W.challenge?.(this.context)).find(Boolean)??'Bearer realm="mcp"',G=Z?`${$}, error="invalid_token", error_description="${Z.replace(/["\\]/g,"")}"`:$;return S(T(null,new F(N.InvalidRequest,"Unauthorized.")),401,{"WWW-Authenticate":G})}async failedAttempt(Z){let $=rZ(new CZ(Z,{},"api",this.endpoint.pathname)),G=await HZ(`mcp-auth-failed:${$}`,z9);return G===null?null:oZ(G)}async spend(Z){if(!this.rateLimit)return null;let $=await HZ(`mcp-endpoint:${Z.via}:${Z.id}`,this.rateLimit);return $===null?null:oZ($)}}class G3{readable;controller;closed=!1;encoder=new TextEncoder;constructor(Z){this.readable=new ReadableStream({start:($)=>{this.controller=$},cancel:()=>{this.closed=!0,Z()}})}send(Z){if(this.closed)return;this.controller.enqueue(this.encoder.encode(`event: message
3
3
  data: ${JSON.stringify(Z)}
4
4
 
5
5
  `))}close(){if(this.closed)return;this.closed=!0,this.controller.close()}}class o extends Error{}async function X9(Z,$){let G=Number(Z.headers.get("content-length"));if(Number.isFinite(G)&&G>$)throw new o;if(!Z.body)return"";let W=Z.body.getReader(),z=[],X=0;for(;;){let{done:Y,value:K}=await W.read();if(Y)break;if(X+=K.byteLength,X>$)throw await W.cancel(),new o;z.push(K)}return Buffer.concat(z).toString("utf8")}function S(Z,$,G={}){return new Response(JSON.stringify(Z),{status:$,headers:{"Content-Type":"application/json","Cache-Control":"no-store",...G}})}function lZ(Z,$){for(let[G,W]of Object.entries($))Z.headers.set(G,W);return Z}function Y9(Z){return{"Access-Control-Allow-Origin":Z,"Access-Control-Expose-Headers":"Mcp-Session-Id, WWW-Authenticate",Vary:"Origin"}}function K9(Z){let $;try{$=new URL(Z)}catch{throw Error(`route.mcp.remote.url: "${Z}" is not a URL.`)}let G=$.hostname==="localhost"||$.hostname==="127.0.0.1"||$.hostname==="[::1]";if($.protocol!=="https:"&&!($.protocol==="http:"&&G))throw Error("route.mcp.remote.url must be https (http only on localhost): tokens travel to it.");if($.search||$.hash||$.username||$.password)throw Error("route.mcp.remote.url must have no query, fragment or credentials.");if($.pathname==="/"||$.pathname.endsWith("/"))throw Error('route.mcp.remote.url needs a path of its own, like "/mcp", without a trailing slash.');return $}function s(Z){return $3("sha256").update(`${Z.via}\x00${Z.id}\x00${Z.user?.id}`).digest("base64url").slice(0,32)}function H9(Z){return $3("sha256").update(Z3(Z??{})).digest("base64url")}function nZ(Z){if(!O(Z))return!1;if(Object.keys(Z).length===0)return!0;return"form"in Z}function tZ(Z){let $=O(Z._meta)?Z._meta:{};return typeof $.progressToken==="string"||typeof $.progressToken==="number"}function U9(Z,$,G){if($.method!=="tools/call"||!G.e)return!1;let W=$.params?.name;return typeof W==="string"&&Z.descriptors({names:[W]})[0]?.requiresApproval===!0}function Q9(Z){let G=(O(Z.params._meta)?Z.params._meta:{}).progressToken;if(!Z.stream||typeof G!=="string"&&typeof G!=="number")return;let W=-1/0;return(z)=>{if(typeof z?.progress!=="number"||!Number.isFinite(z.progress)||z.progress<=W)return;W=z.progress,Z.stream.send({jsonrpc:"2.0",method:"notifications/progress",params:{progressToken:G,progress:z.progress,...typeof z.total==="number"?{total:z.total}:{},...typeof z.message==="string"?{message:z.message.slice(0,1000)}:{}}})}}function V9(Z,$){let G=JSON.stringify($??{},null,1),W=G.length>PZ?`${G.slice(0,PZ)}\u2026 (${G.length-PZ} more characters not shown)`:G;return{message:`Allow "${Z.title??Z.name}" to run? ${Z.description}