UNPKG

muppet

Version:

Toolkit for building MCPs on Honojs

2 lines (1 loc) 7.73 kB
"use strict";var P=Object.defineProperty;var f=(i,n)=>P(i,"name",{value:n,configurable:!0});var y=require("@hono/standard-validator"),d=require("@modelcontextprotocol/sdk/types.js"),g=require("hono");const w=Symbol("muppet"),h={RESOURCES:"resources",TOOLS:"tools",PROMPTS:"prompts"};async function $(i,n){const l=await M(i,n.symbols);return R({config:n,specs:l,app:i})}f($,"muppet");function R(i){const{config:n,specs:l,app:c}=i,a=new g.Hono().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",y.sValidator("json",d.CallToolRequestSchema),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 r=await(await e.get("app").request(...b({path:s.path,method:s.method,schema:s.schema,args:t.arguments}),T(e))).json();return s.resourceType==="text"?e.json({result:{content:[{type:"text",text:typeof r=="string"?r:JSON.stringify(r)}]}}):Array.isArray(r)?e.json({result:{content:r}}):e.json({result:r})}),o=new g.Hono().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",y.sValidator("json",d.GetPromptRequestSchema),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 r=await(await e.get("app").request(...b({path:s.path,method:s.method,schema:s.schema,args:t.arguments}),T(e))).json();return Array.isArray(r)?e.json({result:{description:s.description,messages:r}}):e.json({result:r})}),u=new g.Hono().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 q(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 q(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",y.sValidator("json",d.ReadResourceRequestSchema),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 r=await p(t.uri);return Array.isArray(r)?e.json({result:{contents:r}}):e.json({result:r})});return new g.Hono().use(async(e,t)=>{n.logger?.info({method:e.req.method,path:e.req.path},"Incoming request"),e.set("muppet",n),e.set("specs",l),e.set("app",c),await t(),n.logger?.info({status:e.res.status},"Outgoing response")}).post("/initialize",y.sValidator("json",d.InitializeRequestSchema),async e=>{const{params:t}=e.req.valid("json"),{name:s,version:p}=e.get("muppet"),r=e.get("specs"),m=h.TOOLS in r,S=h.PROMPTS in r,E=h.RESOURCES in r;return e.json({result:{protocolVersion:d.SUPPORTED_PROTOCOL_VERSIONS.includes(t?.protocolVersion)?t.protocolVersion:d.LATEST_PROTOCOL_VERSION,serverInfo:{name:s,version:p},capabilities:{tools:m?{}:void 0,prompts:S?{}: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",o).route("/resources",u).post("/completion/complete",y.sValidator("json",d.CompleteRequestSchema),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 q(e,r=>{if(r.type==="template"&&r.uri===t.ref.uri)return r.completion}).then(r=>r[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:d.ErrorCode.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:d.ErrorCode.InternalError,message:e.message??"Internal error"}})))}f(R,"createMuppetServer");async function M(i,n=[]){const l={},c=[...n,w];for(const o of i.routes){const u=c.find(S=>S in o.handler);if(!u)continue;const{validationTarget:e,toJson:t,type:s}=o.handler[u];let p;typeof t=="function"?p=await t():p=t??{};const r=l[o.path]?.[o.method];if(r?.type&&s&&r.type!==s)throw new Error(`Conflicting types for ${o.path}: ${r.type} and ${s}`);let m={...r??{},type:s??r?.type};e&&"schema"in p?m.schema={...m.schema??{},[e]:p.schema}:m={...m,...p},l[o.path]={...l[o.path]??{},[o.method]:m}}const a={};for(const[o,u]of Object.entries(l))if(u){for(const[e,t]of Object.entries(u))if(t){if(!t.type)throw new Error(`Type not found for ${o}`);if(t.type===h.TOOLS){a.tools||(a.tools={});const s=t.name??j(e,o);a.tools[s]={name:s,description:t.description,resourceType:t.resourceType,inputSchema:O(t.schema)??{},path:o,method:e,schema:t.schema}}else if(t.type===h.PROMPTS){a.prompts||(a.prompts={});const s=t.name??j(e,o),p=[],r=O(t.schema)??{};for(const m of Object.keys(r.properties??{}))p.push({name:m,description:r.properties?.[m]?.description,required:r.required?.includes(m)??!1});a.prompts[s]={name:s,description:t.description,completion:t.completion,arguments:p,path:o,method:e,schema:t.schema}}else t.type===h.RESOURCES&&(a.resources||(a.resources={}),a.resources[j(e,o)]={path:o,method:e})}}return a}f(M,"generateSpecs");function O(i){let n;for(const l of Object.values(i??{})){if(!n){n=l;continue}n={...n,properties:{...n.properties,...l.properties},required:[...n.required??[],...l.required??[]]}}return n}f(O,"mergeSchemas");async function q(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 o=n(a);return o&&c.push(o),c},[])}f(q,"findAllTheResources");function j(i,n){return`${i}:${n}`}f(j,"generateKey");function b(i){const{path:n,method:l,schema:c,args:a}=i,o={},u={method:l};for(const[s,{properties:p}]of Object.entries(c??{}))p&&(o[s]=Object.keys(p).reduce((r,m)=>(a?.[m]!==void 0&&(r[m]=a?.[m]),r),{}));Object.values(o.header??{}).length>0&&(u.headers=o.header),Object.values(o.json??{}).length>0&&(u.body=JSON.stringify(o.json),u.headers={...u.headers,"content-type":"application/json"});const e=v(o.query);return[`${A(n,o.param)}${e.length>0?`?${e}`:""}`,u]}f(b,"getRequestInit");function A(i,n){return i.split("/").map(l=>{let c=l;if(c.startsWith(":")){const a=c.match(/^:([^{?]+)(?:{(.+)})?(\?)?$/);if(a){const o=a[1],u=n?.[o];u&&(c=String(u))}else c=c.slice(1,c.length),c.endsWith("?")&&(c=c.slice(0,-1))}return c}).join("/")}f(A,"placeParamValues");function v(i,n){const{prefix:l,separator:c="__"}=n??{};return Object.entries(i??{}).reduce((a,[o,u])=>{const e=`${l?`${l}${c}`:""}${o}`;return u&&(Array.isArray(u)?a.push(...u.filter(t=>t!==void 0).map(t=>`${e}=${t}`)):typeof u=="object"?a.push(v(u,{prefix:e,separator:c})):a.push(`${e}=${u}`)),a},[]).join("&")}f(v,"querySerializer");function T(i){return{...i.env,muppet:{req:i.req}}}f(T,"createMuppetEnv"),exports.McpPrimitives=h,exports.createMuppetServer=R,exports.generateKey=j,exports.mergeSchemas=O,exports.muppet=$,exports.uniqueSymbol=w;