muppet
Version:
Toolkit for building MCPs on Honojs
2 lines (1 loc) • 7.6 kB
JavaScript
var P=Object.defineProperty;var f=(i,n)=>P(i,"name",{value:n,configurable:!0});import{sValidator as d}from"@hono/standard-validator";import{CallToolRequestSchema as $,GetPromptRequestSchema as M,ReadResourceRequestSchema as A,InitializeRequestSchema as I,LATEST_PROTOCOL_VERSION as C,SUPPORTED_PROTOCOL_VERSIONS as N,CompleteRequestSchema as U,ErrorCode as w}from"@modelcontextprotocol/sdk/types.js";import{Hono as y}from"hono";const q=Symbol("muppet"),h={RESOURCES:"resources",TOOLS:"tools",PROMPTS:"prompts"};async function _(i,n){const m=await L(i,n.symbols);return R({config:n,specs:m,app:i})}f(_,"muppet");function R(i){const{config:n,specs:m,app:c}=i,a=new y().use(async(e,t)=>{if(!(h.TOOLS in e.get("specs")))throw new Error("No tools available");await t()}).post("/list",e=>e.json({result:{tools:Object.values(e.get("specs").tools??{}).map(({name:t,description:s,inputSchema:p})=>({name:t,description:s,inputSchema:p}))}})).post("/call",d("json",$),async e=>{const{params:t}=e.req.valid("json"),s=e.get("specs").tools?.[t.name];if(!s)throw new Error("Unable to find the path for the tool!");const o=await(await e.get("app").request(...b({path:s.path,method:s.method,schema:s.schema,args:t.arguments}),v(e))).json();return s.resourceType==="text"?e.json({result:{content:[{type:"text",text:typeof o=="string"?o:JSON.stringify(o)}]}}):Array.isArray(o)?e.json({result:{content:o}}):e.json({result:o})}),r=new y().use(async(e,t)=>{if(!(h.PROMPTS in e.get("specs")))throw new Error("No prompts available");await t()}).post("/list",e=>e.json({result:{prompts:Object.values(e.get("specs").prompts??{}).map(({path:t,...s})=>s)}})).post("/get",d("json",M),async e=>{const{params:t}=e.req.valid("json"),s=e.get("specs").prompts?.[t.name];if(!s)throw new Error("Unable to find the path for the prompt!");const o=await(await e.get("app").request(...b({path:s.path,method:s.method,schema:s.schema,args:t.arguments}),v(e))).json();return Array.isArray(o)?e.json({result:{description:s.description,messages:o}}):e.json({result:o})}),u=new y().use(async(e,t)=>{if(!(h.RESOURCES in e.get("specs")))throw new Error("No resources available");await t()}).post("/list",async e=>{const t=await S(e,s=>{if(s.type!=="template")return{name:s.name,description:s.description,mimeType:s.mimeType,uri:s.uri}});return e.json({result:{resources:t}})}).post("/templates/list",async e=>{const t=await S(e,s=>{if(s.type==="template")return{name:s.name,description:s.description,mimeType:s.mimeType,uriTemplate:s.uri}});return e.json({result:{resourceTemplates:t}})}).post("/read",d("json",A),async e=>{const{params:t}=e.req.valid("json"),s=t.uri.split(":")[0],p=e.get("muppet").resources?.[s];if(!p)throw new Error(`Unable to find the handler for ${s} protocol!`);const o=await p(t.uri);return Array.isArray(o)?e.json({result:{contents:o}}):e.json({result:o})});return new y().use(async(e,t)=>{n.logger?.info({method:e.req.method,path:e.req.path},"Incoming request"),e.set("muppet",n),e.set("specs",m),e.set("app",c),await t(),n.logger?.info({status:e.res.status},"Outgoing response")}).post("/initialize",d("json",I),async e=>{const{params:t}=e.req.valid("json"),{name:s,version:p}=e.get("muppet"),o=e.get("specs"),l=h.TOOLS in o,j=h.PROMPTS in o,E=h.RESOURCES in o;return e.json({result:{protocolVersion:N.includes(t?.protocolVersion)?t.protocolVersion:C,serverInfo:{name:s,version:p},capabilities:{tools:l?{}:void 0,prompts:j?{}:void 0,resources:E?{}:void 0}}})}).post("/notifications/:event",e=>(e.get("muppet").events?.emit(e,`notifications/${e.req.param("event")}`,void 0),e.body(null,204))).post("/ping",e=>e.json({result:{}})).route("/tools",a).route("/prompts",r).route("/resources",u).post("/completion/complete",d("json",U),async e=>{const{params:t}=e.req.valid("json");let s;if(t.ref.type==="ref/prompt"?s=e.get("specs").prompts?.[t.ref.name].completion:t.ref.type==="ref/resource"&&(s=await S(e,o=>{if(o.type==="template"&&o.uri===t.ref.uri)return o.completion}).then(o=>o[0])),!s)return e.json({result:{completion:{values:[],total:0,hasMore:!1}}});const p=await s(t.argument);return Array.isArray(p)?e.json({result:{completion:{values:p,total:p.length,hasMore:!1}}}):e.json({result:{completion:p}})}).notFound(e=>(e.get("muppet").logger?.info("Method not found"),e.json({error:{code:w.MethodNotFound,message:"Method not found"}}))).onError((e,t)=>(t.get("muppet").logger?.error({err:e},"Internal error"),t.json({error:{code:Number.isSafeInteger(e.code)?e.code:w.InternalError,message:e.message??"Internal error"}})))}f(R,"createMuppetServer");async function L(i,n=[]){const m={},c=[...n,q];for(const r of i.routes){const u=c.find(j=>j in r.handler);if(!u)continue;const{validationTarget:e,toJson:t,type:s}=r.handler[u];let p;typeof t=="function"?p=await t():p=t??{};const o=m[r.path]?.[r.method];if(o?.type&&s&&o.type!==s)throw new Error(`Conflicting types for ${r.path}: ${o.type} and ${s}`);let l={...o??{},type:s??o?.type};e&&"schema"in p?l.schema={...l.schema??{},[e]:p.schema}:l={...l,...p},m[r.path]={...m[r.path]??{},[r.method]:l}}const a={};for(const[r,u]of Object.entries(m))if(u){for(const[e,t]of Object.entries(u))if(t){if(!t.type)throw new Error(`Type not found for ${r}`);if(t.type===h.TOOLS){a.tools||(a.tools={});const s=t.name??g(e,r);a.tools[s]={name:s,description:t.description,resourceType:t.resourceType,inputSchema:O(t.schema)??{},path:r,method:e,schema:t.schema}}else if(t.type===h.PROMPTS){a.prompts||(a.prompts={});const s=t.name??g(e,r),p=[],o=O(t.schema)??{};for(const l of Object.keys(o.properties??{}))p.push({name:l,description:o.properties?.[l]?.description,required:o.required?.includes(l)??!1});a.prompts[s]={name:s,description:t.description,completion:t.completion,arguments:p,path:r,method:e,schema:t.schema}}else t.type===h.RESOURCES&&(a.resources||(a.resources={}),a.resources[g(e,r)]={path:r,method:e})}}return a}f(L,"generateSpecs");function O(i){let n;for(const m of Object.values(i??{})){if(!n){n=m;continue}n={...n,properties:{...n.properties,...m.properties},required:[...n.required??[],...m.required??[]]}}return n}f(O,"mergeSchemas");async function S(i,n){return(await Promise.all(Object.values(i.get("specs").resources??{}).map(async({path:c,method:a})=>i.get("app").request(c,{method:a,headers:i.req.header()})))).flat(2).reduce((c,a)=>{const r=n(a);return r&&c.push(r),c},[])}f(S,"findAllTheResources");function g(i,n){return`${i}:${n}`}f(g,"generateKey");function b(i){const{path:n,method:m,schema:c,args:a}=i,r={},u={method:m};for(const[s,{properties:p}]of Object.entries(c??{}))p&&(r[s]=Object.keys(p).reduce((o,l)=>(a?.[l]!==void 0&&(o[l]=a?.[l]),o),{}));Object.values(r.header??{}).length>0&&(u.headers=r.header),Object.values(r.json??{}).length>0&&(u.body=JSON.stringify(r.json),u.headers={...u.headers,"content-type":"application/json"});const e=T(r.query);return[`${V(n,r.param)}${e.length>0?`?${e}`:""}`,u]}f(b,"getRequestInit");function V(i,n){return i.split("/").map(m=>{let c=m;if(c.startsWith(":")){const a=c.match(/^:([^{?]+)(?:{(.+)})?(\?)?$/);if(a){const r=a[1],u=n?.[r];u&&(c=String(u))}else c=c.slice(1,c.length),c.endsWith("?")&&(c=c.slice(0,-1))}return c}).join("/")}f(V,"placeParamValues");function T(i,n){const{prefix:m,separator:c="__"}=n??{};return Object.entries(i??{}).reduce((a,[r,u])=>{const e=`${m?`${m}${c}`:""}${r}`;return u&&(Array.isArray(u)?a.push(...u.filter(t=>t!==void 0).map(t=>`${e}=${t}`)):typeof u=="object"?a.push(T(u,{prefix:e,separator:c})):a.push(`${e}=${u}`)),a},[]).join("&")}f(T,"querySerializer");function v(i){return{...i.env,muppet:{req:i.req}}}f(v,"createMuppetEnv");export{h as M,O as a,R as c,g,_ as m,q as u};