UNPKG

muppet

Version:

Toolkit for building MCPs on Honojs

2 lines (1 loc) 13.6 kB
var I=Object.defineProperty;var l=(n,e)=>I(n,"name",{value:e,configurable:!0});import{toJsonSchema as f}from"@standard-community/standard-json";const d={ConnectionClosed:-32e3,RequestTimeout:-32001,ParseError:-32700,InvalidRequest:-32600,MethodNotFound:-32601,InvalidParams:-32602,InternalError:-32603};class p extends Error{static{l(this,"McpError")}constructor(e,t,i){super(`MCP error ${e}: ${t}`),this.code=e,this.data=i,this.name="McpError"}}const v=l((n,e,t)=>(i,r)=>{let s=-1;return o(0);async function o(a){if(a<=s)throw new Error("next() called multiple times");s=a;let u,h=!1,c;if(n[a]?c=n[a]:c=a===n.length&&r||void 0,c)try{u=await c(i,()=>o(a+1))}catch(m){if(m instanceof Error&&e)await e(m,i),h=!0;else throw m}else i.finalized===!1&&t&&await t(i);return u&&(i.finalized===!1||h)&&(i.result=u),i}},"compose");class b{static{l(this,"Context")}constructor(e,t){this.message=e,this.finalized=!1,this.set=(i,r)=>{this.#t??=new Map,this.#t.set(i,r)},this.get=i=>this.#t?this.#t.get(i):void 0,t.env&&(this.#t=new Map(Object.entries(t.env.Variables??{}))),this.server=new P(e.id,t.transport)}#t;#e;get result(){return this.#e}set result(e){this.#e=e,this.finalized=!0}get var(){return this.#t?Object.fromEntries(this.#t):{}}}class P{static{l(this,"ContextServer")}#t;#e=new Map;#a=new Map;#r=new Map;constructor(e,t){this.transport=t,this.#t=e}#i(e,t,i,r,s=!1){this.#a.set(e,{timeoutId:setTimeout(r,t),startTime:Date.now(),timeout:t,maxTotalTimeout:i,resetTimeoutOnProgress:s,onTimeout:r})}#s(e){const t=this.#a.get(e);t&&(clearTimeout(t.timeoutId),this.#a.delete(e))}async ping(){return this.request({method:"ping"})}createMessage(e,t){return this.request({method:"sampling/createMessage",params:e},t)}async elicitInput(e,t){const i=await f(e.requestedSchema),r=await this.request({method:"elicitation/create",params:{...e,requestedSchema:i}},t);if(r.action==="accept"&&r.content)try{const s=await e.requestedSchema["~standard"].validate(r.content);if(s.issues)throw new p(d.InvalidParams,`Elicitation response content does not match requested schema: ${s.issues.map(o=>o.message).join(", ")}`)}catch(s){throw s instanceof p?s:new p(d.InternalError,`Error validating elicitation response: ${s}`)}return r}async listRoots(e,t){return this.request({method:"roots/list",params:e},t)}async sendLoggingMessage(e){return this.notification({method:"notifications/message",params:e})}async sendResourceUpdated(e){return this.notification({method:"notifications/resources/updated",params:e})}async sendResourceListChanged(){return this.notification({method:"notifications/resources/list_changed"})}async sendToolListChanged(){return this.notification({method:"notifications/tools/list_changed"})}async sendPromptListChanged(){return this.notification({method:"notifications/prompts/list_changed"})}async request(e,t){return new Promise((i,r)=>{t?.signal?.throwIfAborted();const s=this.#t,o={...e,jsonrpc:"2.0",id:s};t?.onprogress&&(this.#e.set(s,t.onprogress),"params"in e&&"params"in o&&(o.params={...e.params,_meta:{...e.params?._meta||{},progressToken:s}}));const a=l(c=>{this.#r.delete(s),this.#e.delete(s),this.#s(s),this.transport.send({jsonrpc:"2.0",method:"notifications/cancelled",params:{requestId:s,reason:String(c)}}).catch(m=>this.transport.onerror?.(new Error(`Failed to send cancellation: ${m}`))),r(c)},"cancel");this.#r.set(s,c=>{if(!t?.signal?.aborted){if(c instanceof Error)return r(c);try{i(c.result)}catch(m){r(m)}}}),t?.signal?.addEventListener("abort",()=>{a(t?.signal?.reason)});const u=t?.timeout??6e4,h=l(()=>a(new p(d.RequestTimeout,"Request timed out",{timeout:u})),"timeoutHandler");this.#i(s,u,t?.maxTotalTimeout,h,t?.resetTimeoutOnProgress??!1),this.transport.send(o).catch(c=>{this.#s(s),r(c)})})}async notification(e,t){await this.transport.send({...e,jsonrpc:"2.0"})}}function R(n,e){return{type:"text",text:n,_meta:e?._meta}}l(R,"textContent");function _(n,e){return{type:"resource",resource:n,_meta:e?._meta}}l(_,"resourceContent");function C(n,e){return{type:"resource_link",uri:n,mimeType:e?.mimeType,_meta:e?._meta}}l(C,"resourceLink");async function M(n,e){let t;if("url"in n)t=await g(n.url);else if("buffer"in n)t=n.buffer;else if("path"in n)t=await x(n.path);else throw new Error("Invalid content options");const i=await T(t);return{type:"image",data:t.toString("base64"),mimeType:i?.mime??"image/png",_meta:e?._meta}}l(M,"imageContent");async function $(n,e){let t;if("url"in n)t=await g(n.url);else if("buffer"in n)t=n.buffer;else if("path"in n)t=await x(n.path);else throw new Error("Invalid content options");const i=await T(t);return{type:"audio",data:t.toString("base64"),mimeType:i?.mime??"audio/mpeg",_meta:e?._meta}}l($,"audioContent");async function g(n){return fetch(n).then(async e=>{if(!e.ok)throw new Error(`Failed to fetch ${n}: ${e.statusText}`);return Buffer.from(await e.arrayBuffer())}).catch(e=>{throw console.error(`Error loading URL: ${e.message}`),e})}l(g,"loadFromUrl");async function x(n){try{const{readFile:e}=await import("node:fs/promises");return e(n)}catch{throw new Error("muppet: Unable to import 'fs' module.")}}l(x,"loadFromPath");async function T(n){try{const{fileTypeFromBuffer:e}=await import("file-type");return e(n)}catch{throw new Error("muppet: Unable to import 'file-type' module.")}}l(T,"getMimeType");class j{static{l(this,"Muppet")}constructor(e){this.#t="",this.routes=[],this.#a=new Map,this.#r=t=>{t.error={code:d.MethodNotFound,message:"Method not found"}},this.#i=(t,i)=>{i.error={code:"code"in t&&Number.isSafeInteger(t.code)?Number(t.code):d.InternalError,message:t.message??"Internal error",data:"data"in t?t.data:void 0}},this.onError=t=>(this.#i=t,this),this.notFound=t=>(this.#r=t,this),e&&(this.name=e.name,this.version=e.version,e.prefix&&(this.#t=e.prefix))}#t;#e;#a;#r;#i;merge(e){return this.routes.push(...e.routes),this}tool(e,...t){typeof e=="object"&&!Array.isArray(e)?(this.#e=`${this.#t}${e.name}`,this.routes.push({type:"tool",...e,name:this.#e})):this.routes.push({type:"middleware",name:this.#e,handler:e});for(const i of t)this.routes.push({type:"middleware",name:this.#e,handler:i});return this}prompt(e,...t){typeof e=="object"&&!Array.isArray(e)?(this.#e=`${this.#t}${e.name}`,this.routes.push({type:"prompt",...e,name:this.#e})):this.routes.push({type:"middleware",name:this.#e,handler:e});for(const i of t)this.routes.push({type:"middleware",name:this.#e,handler:i});return this}resource(e,...t){if(typeof e=="object"&&!Array.isArray(e)){this.#e=`${this.#t}${e.name}`;const i="uriTemplate"in e?"resource-template":"resource";this.routes.push({...e,type:i,name:this.#e})}else this.routes.push({type:"middleware",name:this.#e,handler:e});for(const i of t)this.routes.push({type:"middleware",name:this.#e,handler:i});return this}enable(e){const t=this.routes.find(i=>i.name===e);return t&&t.type!=="middleware"&&(t.disabled=!1),this}disable(e){const t=this.routes.find(i=>i.name===e);return t&&t.type!=="middleware"&&(t.disabled=!0),this}onNotification(e,t){return this.#a.set(e,t),this}async dispatch(e,t){try{if(e.method==="initialize"){if(!this.name||!this.version)throw new Error("Name and version are required for this instance");let i=!1,r=!1,s=!1;for(const a of this.routes)a.type==="tool"&&(i=!0),a.type==="prompt"&&(r=!0),(a.type==="resource"||a.type==="resource-template")&&(s=!0);const o={};return i&&(o.tools={listChanged:!0}),r&&(o.prompts={listChanged:!0}),s&&(o.resources={listChanged:!0}),{protocolVersion:"2024-11-05",serverInfo:{name:this.name,version:this.version},capabilities:o}}if(e.method==="notifications/cancelled"||e.method==="notifications/progress"||e.method==="notifications/initialized"||e.method==="notifications/roots/list_changed"){const i=this.#a.get(e.method);i&&i(e);return}if(e.method==="ping")return{};if(e.method==="tools/list"){const i=[];for(const r of this.routes)r.type==="tool"&&r.disabled!==!0&&i.push(r);return{tools:await Promise.all(i.map(async r=>{const s=[r.inputSchema?f(r.inputSchema):void 0,r.outputSchema?f(r.outputSchema):void 0],[o,a]=await Promise.all(s);return{name:r.name,description:r.description,inputSchema:o??{type:"object",properties:{}},outputSchema:a,annotations:r.annotations}}))}}if(e.method==="tools/call"){const i=e.params.name;let r;const s=[];for(const a of this.routes){if(a.type==="tool"&&a.name===i&&(r=a,r.disabled)){t.context.error={code:d.InvalidParams,message:"Tool is disabled"};return}a.type==="middleware"&&(a.name===i||a.name==="*")&&s.push(a.handler)}if(r?.inputSchema){const a=await r.inputSchema["~standard"].validate(e.params.arguments);if(a.issues){t.context.error={code:d.InvalidParams,message:"Argument validation failed",data:a.issues};return}t.context.message={...e,params:{...e.params,arguments:a.value}}}const o=await v(s,this.#i,this.#r)(t.context);if(!o.finalized)throw new Error("Context is not finalized. Did you forget to return a Result object or `await next()`?");return o.result}if(e.method==="prompts/list"){const i=[];for(const r of this.routes)r.type==="prompt"&&r.disabled!==!0&&i.push(r);return{prompts:await Promise.all(i.map(async r=>({name:r.name,title:r.title,description:r.description,arguments:await Promise.all(Object.entries(r.arguments??{}).map(async([s,o])=>{const a=await f(o.validation);return{name:s,description:a.description,required:a.required?.includes(s)??!0}}))})))}}if(e.method==="prompts/get"){const i=e.params.name;let r;const s=[];for(const a of this.routes){if(a.type==="prompt"&&a.name===i&&(r=a,r.disabled)){t.context.error={code:d.InvalidParams,message:"Prompt is disabled"};return}a.type==="middleware"&&(a.name===i||a.name==="*")&&s.push(a.handler)}if(r?.arguments){const a={};for(const[u,h]of Object.entries(r.arguments)){const c=await h.validation["~standard"].validate(e.params.arguments[u]);if(c.issues){t.context.error={code:d.InvalidParams,message:`Argument validation failed for ${u}`,data:c.issues};return}a[u]=c.value}}const o=await v(s,this.#i,this.#r)(t.context);if(!o.finalized)throw new Error("Context is not finalized. Did you forget to return a Result object or `await next()`?");return o.result}if(e.method==="resources/list"){const i=[];for(const r of this.routes)r.type==="resource"&&r.disabled!==!0&&i.push(r);return{resources:i.map(r=>({name:r.name,title:r.title,uri:r.uri,description:r.description,mimeType:r.mimeType,_meta:r._meta}))}}if(e.method==="resources/templates/list"){const i=[];for(const r of this.routes)r.type==="resource-template"&&r.disabled!==!0&&i.push(r);return{resourceTemplates:i.map(r=>({name:r.name,title:r.title,uriTemplate:r.uriTemplate,description:r.description,mimeType:r.mimeType,_meta:r._meta}))}}if(e.method==="resources/read"){const i=this.routes.find(a=>a.type==="resource"&&a.uri===e.params.uri),r=[];let s={};if(i){if("disabled"in i&&i.disabled){t.context.error={code:d.InvalidParams,message:"Resource is disabled"};return}for(const a of this.routes)a.type==="middleware"&&(a.name===i.name||a.name==="*")&&r.push(a.handler)}else{let a;for(const u of this.routes){if(u.type!=="resource-template")continue;const h=u.uriTemplate,c=e.params.uri,m=q(h,c);if(m){if(a=u,"disabled"in a&&a.disabled){t.context.error={code:d.InvalidParams,message:"Resource is disabled"};return}if(s={...m},a.arguments)for(const[y,E]of Object.entries(a.arguments)){const w=await E.validation["~standard"].validate(s[y]);if(w.issues){t.context.error={code:d.InvalidParams,message:`Argument validation failed for ${y}`,data:w.issues};return}s[y]=w.value}break}}if(a)for(const u of this.routes)u.type==="middleware"&&(u.name===a.name||u.name==="*")&&r.push(u.handler)}if(r.length===0){t.context.error={code:d.InvalidParams,message:"Resource not found"};return}Object.keys(s).length>0&&(t.context.message={...e,params:{...e.params,arguments:s}});const o=await v(r,this.#i,this.#r)(t.context);if(!o.finalized)throw new Error("Context is not finalized. Did you forget to return a Result object or `await next()`?");return o.result}if(e.method==="completion/complete"){let i;if(e.params.ref.type==="ref/prompt"){const s=e.params.ref.name,o=this.routes.find(a=>a.type==="prompt"&&a.name===s);o?.type==="prompt"&&(i=o.arguments?.[e.params.argument.name]?.completion)}else if(e.params.ref.type==="ref/resource"){const s=e.params.ref.uri,o=this.routes.find(a=>a.type==="resource-template"&&a.uriTemplate===s);o?.type==="resource-template"&&(i=o.arguments?.[e.params.argument.name]?.completion)}if(!i){t.context.error={code:d.InvalidParams,message:`No completion function found for ${e.params.ref.type}`};return}const r=await i(e.params.argument.value,t.context);return Array.isArray(r)?{completion:{values:r,total:r.length,hasMore:!1}}:{completion:{values:r.values,total:r.total,hasMore:r.hasMore}}}this.#r(t.context)}catch(i){this.#i(i,t.context)}}async request(e,t){if(!this.transport)throw new Error("Transport not connected! Call the .connect() method first");const i=new b(e,{...t,transport:this.transport}),r=await this.dispatch(e,{context:i});if(i.error)return{id:e.id,jsonrpc:"2.0",error:i.error};if(r)return{id:e.id,jsonrpc:"2.0",result:r}}async connect(e,t){return this.transport=e,this.transport.onmessage=async i=>{const r=await this.request(i,t);r&&e.send(r)},e.start()}}function S(n){const t="^"+n.replace(/([.+^=!:${}()|[\]/\\])/g,"\\$1").replace(/\\{(\w+)\\}/g,"([^/]+)")+"$";return new RegExp(t)}l(S,"uriTemplateToRegex");function q(n,e){const t=Array.from(n.matchAll(/{(\w+)}/g)).map(o=>o[1]),i=S(n),r=e.match(i);if(!r)return null;const s=r.slice(1);return Object.fromEntries(t.map((o,a)=>[o,s[a]]))}l(q,"extractVariables");export{b as Context,j as Muppet,$ as audioContent,M as imageContent,_ as resourceContent,C as resourceLink,R as textContent};