muppet
Version:
Toolkit for building MCPs on Honojs
2 lines (1 loc) • 14.1 kB
JavaScript
"use strict";var P=Object.create;var v=Object.defineProperty;var R=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var _=Object.getPrototypeOf,M=Object.prototype.hasOwnProperty;var l=(s,e)=>v(s,"name",{value:e,configurable:!0});var $=(s,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of S(e))!M.call(s,r)&&r!==t&&v(s,r,{get:()=>e[r],enumerable:!(i=R(e,r))||i.enumerable});return s};var g=(s,e,t)=>(t=s!=null?P(_(s)):{},$(e||!s||!s.__esModule?v(t,"default",{value:s,enumerable:!0}):t,s));var p=require("@standard-community/standard-json");const d={ConnectionClosed:-32e3,RequestTimeout:-32001,ParseError:-32700,InvalidRequest:-32600,MethodNotFound:-32601,InvalidParams:-32602,InternalError:-32603};class f 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 b=l((s,e,t)=>(i,r)=>{let n=-1;return o(0);async function o(a){if(a<=n)throw new Error("next() called multiple times");n=a;let u,h=!1,c;if(s[a]?c=s[a]:c=a===s.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 x{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 j(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 j{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,n=!1){this.#a.set(e,{timeoutId:setTimeout(r,t),startTime:Date.now(),timeout:t,maxTotalTimeout:i,resetTimeoutOnProgress:n,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 p.toJsonSchema(e.requestedSchema),r=await this.request({method:"elicitation/create",params:{...e,requestedSchema:i}},t);if(r.action==="accept"&&r.content)try{const n=await e.requestedSchema["~standard"].validate(r.content);if(n.issues)throw new f(d.InvalidParams,`Elicitation response content does not match requested schema: ${n.issues.map(o=>o.message).join(", ")}`)}catch(n){throw n instanceof f?n:new f(d.InternalError,`Error validating elicitation response: ${n}`)}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 n=this.#t,o={...e,jsonrpc:"2.0",id:n};t?.onprogress&&(this.#e.set(n,t.onprogress),"params"in e&&"params"in o&&(o.params={...e.params,_meta:{...e.params?._meta||{},progressToken:n}}));const a=l(c=>{this.#r.delete(n),this.#e.delete(n),this.#s(n),this.transport.send({jsonrpc:"2.0",method:"notifications/cancelled",params:{requestId:n,reason:String(c)}}).catch(m=>this.transport.onerror?.(new Error(`Failed to send cancellation: ${m}`))),r(c)},"cancel");this.#r.set(n,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 f(d.RequestTimeout,"Request timed out",{timeout:u})),"timeoutHandler");this.#i(n,u,t?.maxTotalTimeout,h,t?.resetTimeoutOnProgress??!1),this.transport.send(o).catch(c=>{this.#s(n),r(c)})})}async notification(e,t){await this.transport.send({...e,jsonrpc:"2.0"})}}function q(s,e){return{type:"text",text:s,_meta:e?._meta}}l(q,"textContent");function A(s,e){return{type:"resource",resource:s,_meta:e?._meta}}l(A,"resourceContent");function z(s,e){return{type:"resource_link",uri:s,mimeType:e?.mimeType,_meta:e?._meta}}l(z,"resourceLink");async function D(s,e){let t;if("url"in s)t=await T(s.url);else if("buffer"in s)t=s.buffer;else if("path"in s)t=await E(s.path);else throw new Error("Invalid content options");const i=await I(t);return{type:"image",data:t.toString("base64"),mimeType:i?.mime??"image/png",_meta:e?._meta}}l(D,"imageContent");async function F(s,e){let t;if("url"in s)t=await T(s.url);else if("buffer"in s)t=s.buffer;else if("path"in s)t=await E(s.path);else throw new Error("Invalid content options");const i=await I(t);return{type:"audio",data:t.toString("base64"),mimeType:i?.mime??"audio/mpeg",_meta:e?._meta}}l(F,"audioContent");async function T(s){return fetch(s).then(async e=>{if(!e.ok)throw new Error(`Failed to fetch ${s}: ${e.statusText}`);return Buffer.from(await e.arrayBuffer())}).catch(e=>{throw console.error(`Error loading URL: ${e.message}`),e})}l(T,"loadFromUrl");async function E(s){try{const{readFile:e}=await import("node:fs/promises");return e(s)}catch{throw new Error("muppet: Unable to import 'fs' module.")}}l(E,"loadFromPath");async function I(s){try{const{fileTypeFromBuffer:e}=await import("file-type");return e(s)}catch{throw new Error("muppet: Unable to import 'file-type' module.")}}l(I,"getMimeType");class O{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,n=!1;for(const a of this.routes)a.type==="tool"&&(i=!0),a.type==="prompt"&&(r=!0),(a.type==="resource"||a.type==="resource-template")&&(n=!0);const o={};return i&&(o.tools={listChanged:!0}),r&&(o.prompts={listChanged:!0}),n&&(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 n=[r.inputSchema?p.toJsonSchema(r.inputSchema):void 0,r.outputSchema?p.toJsonSchema(r.outputSchema):void 0],[o,a]=await Promise.all(n);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 n=[];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==="*")&&n.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 b(n,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([n,o])=>{const a=await p.toJsonSchema(o.validation);return{name:n,description:a.description,required:a.required?.includes(n)??!0}}))})))}}if(e.method==="prompts/get"){const i=e.params.name;let r;const n=[];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==="*")&&n.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 b(n,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 n={};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=L(h,c);if(m){if(a=u,"disabled"in a&&a.disabled){t.context.error={code:d.InvalidParams,message:"Resource is disabled"};return}if(n={...m},a.arguments)for(const[y,C]of Object.entries(a.arguments)){const w=await C.validation["~standard"].validate(n[y]);if(w.issues){t.context.error={code:d.InvalidParams,message:`Argument validation failed for ${y}`,data:w.issues};return}n[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(n).length>0&&(t.context.message={...e,params:{...e.params,arguments:n}});const o=await b(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 n=e.params.ref.name,o=this.routes.find(a=>a.type==="prompt"&&a.name===n);o?.type==="prompt"&&(i=o.arguments?.[e.params.argument.name]?.completion)}else if(e.params.ref.type==="ref/resource"){const n=e.params.ref.uri,o=this.routes.find(a=>a.type==="resource-template"&&a.uriTemplate===n);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 x(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 k(s){const t="^"+s.replace(/([.+^=!:${}()|[\]/\\])/g,"\\$1").replace(/\\{(\w+)\\}/g,"([^/]+)")+"$";return new RegExp(t)}l(k,"uriTemplateToRegex");function L(s,e){const t=Array.from(s.matchAll(/{(\w+)}/g)).map(o=>o[1]),i=k(s),r=e.match(i);if(!r)return null;const n=r.slice(1);return Object.fromEntries(t.map((o,a)=>[o,n[a]]))}l(L,"extractVariables"),exports.Context=x,exports.Muppet=O,exports.audioContent=F,exports.imageContent=D,exports.resourceContent=A,exports.resourceLink=z,exports.textContent=q;