UNPKG

jsonv-ts

Version:

JSON Schema builder and validator for TypeScript with static type inference, Hono middleware for OpenAPI generation and validation, and MCP server/client implementation. Lightweight, dependency-free, and built on Web Standards.

8 lines (7 loc) 17.4 kB
import*as y from"jsonv-ts";import*as d from"jsonv-ts";var b={ConnectionClosed:{code:-32e3,message:"Connection closed"},RequestTimeout:{code:-32001,message:"Request timeout"},ParseError:{code:-32700,message:"Parse error"},InvalidRequest:{code:-32600,message:"Invalid request"},MethodNotFound:{code:-32601,message:"Method not found"},InvalidParams:{code:-32602,message:"Invalid params"},InternalError:{code:-32603,message:"Internal error",statusCode:500}},c=class extends Error{constructor(r,s,t){super(t??b[r].message);this.code=r;this.data=s}jsonrpc="2.0";id;static get codes(){return Object.fromEntries(Object.entries(b).map(([r,s])=>[r,r]))}setId(r){return this.id=r,this}get statusCode(){return b[this.code]?.statusCode??400}toJSON(){return{jsonrpc:this.jsonrpc,id:this.id,error:{code:b[this.code].code,message:this.message,data:this.data}}}toString(){return`MCP Error (${b[this.code].code} ${this.code}): ${this.message}`}};var te=d.object({}),_=d.object({jsonrpc:d.string({const:"2.0"}),id:d.oneOf([d.string(),d.number()]).optional()}),Re=d.object({..._.properties,method:d.string(),params:d.any().optional()}),ye=d.object({..._.properties,result:te.optional(),error:d.object({}).optional()}),u=class{constructor(e){this.server=e}static isValidMessage(e){try{return _.validate(structuredClone(e)).valid}catch{return!1}}is(e){if(e.jsonrpc!=="2.0")throw new c("InvalidRequest",{expected:"2.0",actual:e.jsonrpc},"Invalid JSON-RPC version");if(e.method!==this.method)return!1;let r=this.params.validate(e.params);if(!r.valid)throw new c("InvalidParams",{method:this.method,errors:r.errors});return!0}formatRespond(e,r){return{jsonrpc:"2.0",id:e.id,result:r}}},w=class extends u{params=d.any();constructor(e){super(e)}async respond(e){return await this.handle(e),null}};var j=class extends u{method="initialize";params=y.object({protocolVersion:y.string(),capabilities:y.object({}),clientInfo:y.object({}).optional(),serverInfo:y.object({}).optional(),instructions:y.string().optional()});async respond(e){return this.formatRespond(e,{protocolVersion:this.server.version,capabilities:{tools:this.server.tools.length>0?{}:void 0,resources:this.server.resources.length>0?{}:void 0,logging:{},completions:{}},serverInfo:this.server.serverInfo})}},O=class extends w{method="notifications/initialized";async handle(e){}};import*as q from"jsonv-ts";var I=class extends u{method="ping";params=q.record(q.any());async respond(e){return this.formatRespond(e,{})}};import*as h from"jsonv-ts";var C=class extends u{method="tools/list";params=h.object({});async respond(e){return this.formatRespond(e,{tools:this.server.tools.map(r=>r.toJSON())})}},P=class extends u{method="tools/call";params=h.object({name:h.string(),arguments:h.record(h.any()).optional()});async respond(e,r){let s=this.server.tools.find(t=>t.name===e.params.name);if(!s)throw new c("InvalidParams",{tool:e.params.name},"Tool not found");try{let t=await s.call(e.params.arguments,this.server.context,r);return this.formatRespond(e,{content:Array.isArray(t)?t:[t]})}catch(t){return t instanceof c?this.server.console.error(t.toJSON()):this.server.console.error(t),this.formatRespond(e,{content:[{type:"text",text:String(t)}],isError:!0})}}};import*as T from"jsonv-ts";var L=class extends u{method="resources/list";params=T.object({});async respond(e){let r=this.server.resources.filter(s=>!s.isDynamic()).map(s=>s.toJSON());return this.formatRespond(e,{resources:[...r]})}},N=class extends u{method="resources/templates/list";params=T.object({});async respond(e){let r=this.server.resources.filter(s=>s.isDynamic()).map(s=>s.toJSON());return this.formatRespond(e,{resourceTemplates:[...r]})}},E=class extends u{method="resources/read";params=T.object({uri:T.string()});async respond(e,r){let s=e.params.uri,t=this.server.resources.find(n=>n.matches(s));if(!t)throw new c("MethodNotFound",`Resource not found: ${s}`);return this.formatRespond(e,{contents:[await t.toJSONContent(this.server.context,e.params.uri,r)]})}};import*as m from"jsonv-ts";var H=class extends u{method="completion/complete";params=m.object({argument:m.strictObject({name:m.string(),value:m.string()}),ref:m.oneOf([m.strictObject({type:m.literal("ref/resource"),uri:m.string()}),m.strictObject({type:m.literal("ref/tool"),name:m.string()})])});async respond(e){let r=e.params.ref;if(r.type==="ref/resource"){let s=this.server.resources.find(t=>t.uri===r.uri);if(!s)throw new c("InvalidParams",{uri:r.uri});return this.formatRespond(e,{completion:await s.suggest(e.params.argument.name,e.params.argument.value,{})})}return this.formatRespond(e,{completion:{values:[],total:0,hasMore:!1}})}};import{s as Q}from"jsonv-ts";var k=class extends u{method="logging/setLevel";params=Q.object({level:Q.string({enum:J})});async respond(e){return this.server.setLogLevel(e.params.level),this.formatRespond(e,{})}};var X={InitializeMessage:j,InitializedNotificationMessage:O,PingMessage:I,ToolsListMessage:C,ToolsCallMessage:P,ResourcesListMessage:L,ResourcesTemplatesListMessage:N,ResourcesReadMessage:E,CompletionMessage:H,LoggingMessage:k};import*as M from"jsonv-ts";import*as g from"jsonv-ts";var ze=g.object({title:g.string(),readOnlyHint:g.boolean(),destructiveHint:g.boolean({default:!0}),idempotentHint:g.boolean(),openWorldHint:g.boolean({default:!0})}).partial().strict(),x=class{constructor(e,r,s){this.name=e;this.config=r;this.handler=s}async call(e,r,s){if(this.config?.inputSchema){let t=this.config.inputSchema.validate(e);if(!t.valid)throw new c("InvalidParams",{method:this.name,errors:t.errors,given:e,schema:this.config.inputSchema.toJSON()},"Invalid tool parameters")}return await this.handler(e,{context:r,raw:s,text:t=>({type:"text",text:t}),json:t=>({type:"text",text:JSON.stringify(t)})})}toJSON(){return{name:this.name,title:this.config?.title,description:this.config?.description,inputSchema:this.config?.inputSchema?.toJSON()??{type:"object"},outputSchema:this.config?.outputSchema?.toJSON(),annotations:Object.keys(this.config?.annotations??{}).length>0?this.config?.annotations:void 0,_meta:this.config?._meta}}};function se(o,e){let r=new RegExp("^"+o.replace(/[{](.*?)[}]/g,(t,n)=>`(?<${n}>[^/]+)`)+"$");return e.match(r)?.groups??{}}function re(o,e){return new RegExp("^"+o.replace(/[{](.*?)[}]/g,"[^/]+")+"$").test(e)}var S=class{constructor(e,r,s,t={}){this.name=e;this.uri=r;this.handler=s;this.options=t}isDynamic(){return this.uri.includes("{")}matches(e){return re(this.uri,e)}async suggest(e,r,s){let t=[];if(this.options.complete){let i=this.options.complete[e];if(i){let a=await i(r,s);if(!Array.isArray(a))return a;t=a}}if(this.options.list){let i=await this.options.list;if(!Array.isArray(i))return i;t=i}let n=t.filter(i=>i.startsWith(r));return{values:n,total:n.length,hasMore:!1}}async call(e,r,s){let t=se(this.uri,e);return await this.handler({context:r,uri:e,raw:s,text:(n,i)=>({mimeType:"text/plain",text:String(n),...i}),json:(n,i)=>({mimeType:"application/json",text:JSON.stringify(n),...i}),binary:(n,i)=>({mimeType:n instanceof File?n.type:"application/octet-stream",blob:n instanceof File?n:new Blob([n]),...i})},t)}async toJSONContent(e,r=this.uri,s=new Request(r)){let{uriTemplate:t,name:n,title:i,description:a,...R}=this.toJSON(),f=await this.call(r,e,s);return{...R,uri:r,name:f.title?.trim()??n,...f}}toJSON(){return{[this.isDynamic()?"uriTemplate":"uri"]:this.uri,name:this.name,title:this.options.title,description:this.options.description,mimeType:this.options.mimeType,size:this.options.size,_meta:this.options._meta}}};var Ze=M.strictObject({name:M.string(),version:M.string()}),F={emergency:"error",alert:"error",critical:"error",error:"error",warning:"warn",notice:"log",info:"info",debug:"debug"},J=Object.keys(F),A="2025-06-18",v=class o{constructor(e={name:"mcp-server",version:"0.0.0"},r={},s=[],t=[]){this.serverInfo=e;this.context=r;this.tools=s;this.resources=t;this.messages=Object.values(X).map(n=>new n(this))}messages=[];version=A;currentId;_id=crypto.randomUUID();logLevel="warning";history=new Map;onNotificationListener;onNotification(e){return this.onNotificationListener=e,this}clone(){let e=new o(this.serverInfo,this.context,this.tools,this.resources);return e.setLogLevel(this.logLevel),e.onNotificationListener=this.onNotificationListener,e}setLogLevel(e){this.logLevel=e,this.console.info("set log level",e)}addTool(e){return this.tools.push(e),this}tool(e,r,s){return this.tools.push(new x(e,r,s)),this}addResource(e){return this.resources.push(e),this}resource(e,r,s,t){return this.resources.push(new S(e,r,s,t)),this}get console(){let e=this.logLevel,r=this.onNotificationListener;return new Proxy({},{get(s,t){if(t in F)return(...n)=>{let i=J.indexOf(e);J.indexOf(String(t))>i||r&&r({jsonrpc:"2.0",method:"notification/message",params:{data:n,level:F[t]}})}}})}async handle(e,r){try{if(this.console.debug("payload",e),!u.isValidMessage(e))throw new c("ParseError",{payload:e});if(this.currentId=e.id,this.currentId){if(this.history.has(this.currentId))throw this.console.warning("duplicate request",this.currentId),new c("InvalidRequest",{error:"Duplicate request"});this.history.set(this.currentId,{request:e})}let s=this.messages.find(t=>t.is(e));if(s){let t=await s.respond(e,r);return this.console.info("result",t),this.currentId&&this.history.set(this.currentId,{request:e,response:t}),t}throw new c("MethodNotFound",{method:e.method,params:e.params})}catch(s){throw s instanceof c?(this.console.error(s.toJSON()),s):(this.console.error(s),new c("InternalError",{error:String(s)}))}}toJSON(){return{serverInfo:this.serverInfo,tools:this.tools.map(e=>e.toJSON()),resources:this.resources.map(e=>e.toJSON())}}};function D(o){let e=new v(o.serverInfo,o.context,o.tools,o.resources);return o.logLevel&&e.setLogLevel(o.logLevel),e}function V(o,e){let r=(s,t)=>{let n,i;switch(s instanceof Request?(n=s,i=new Headers(n.headers)):(n=new Request(s,t),i=new Headers(t?.headers)),e?.authentication?.type){case"bearer":i.get("Authorization")||i.set("Authorization",`Bearer ${e.authentication.token}`);break}return new Request(n.url,{method:n.method,body:n.body,headers:i,cache:n.cache,credentials:n.credentials,keepalive:n.keepalive,mode:n.mode,signal:n.signal,...e?._init})};return async(s,t)=>{let n=r(s,t);if(n.method==="POST")try{let i=await n.json(),a=await o.handle(i,n);return a===null?new Response(null,{status:202}):Response.json(a,{status:200})}catch(i){return i instanceof c?Response.json(i.toJSON(),{status:i.statusCode}):Response.json(new c("InternalError").toJSON(),{status:500})}return new Response("Method not allowed",{status:405})}}var z=o=>{let e=o?.endpoint?.path??"/sse",r=new Map;return async(s,t)=>{let n=s.req.path,i=s.req.header("Mcp-Session-Id");if(e!==n){if(i&&o?.debug?.historyEndpoint&&n===`${e}/__history`){let a=r.get(i);if(a)return s.json(Array.from(a.history.values()),200)}await t()}else{let a;if(o?.sessionsEnabled&&(i?a=r.get(i):i=crypto.randomUUID()),!a){let l=o&&"setup"in o&&o?.setup?await o?.setup(s):o;"server"in l?a=l.server.clone():a=D({serverInfo:l?.serverInfo,context:l?.context,tools:l?.tools,resources:l?.resources}),o?.debug?.logLevel&&a.setLogLevel(o.debug.logLevel),o?.sessionsEnabled&&r.set(i,a)}if(o?.debug?.explainEndpoint&&s.req.query("explain"))return s.json(a.toJSON());let f=await V(a,{_init:o?.endpoint?._init})(s.req.raw),p=new Headers(f.headers);return o?.sessionsEnabled&&p.set("Mcp-Session-Id",i),new Response(f.body,{status:f.status,headers:p})}}};var B=class{constructor(e){this.config=e}id=1;sessionId;async request(e,r){let t={jsonrpc:"2.0",id:this.id++,method:e,params:r},n=new Headers({"Content-Type":"application/json",Accept:"application/json"});this.sessionId&&n.set("Mcp-Session-Id",this.sessionId);let i=await(this.config.fetch??fetch)(this.config.url,{method:"POST",headers:n,body:JSON.stringify(t)});this.sessionId||(this.sessionId=i.headers.get("Mcp-Session-Id")??void 0);try{let a=await i.json();if(a.jsonrpc!=="2.0")throw new Error("Invalid JSON-RPC version");return a.result}catch(a){throw console.error(a),a}}async connect(){return this.request("initialize",{protocolVersion:A,capabilities:{},clientInfo:{name:this.config.name,version:this.config.version}})}async ping(){return this.request("ping",{})}async setLoggingLevel(e){return this.request("logging/setLevel",{level:e})}async listResources(){return this.request("resources/list",{})}async listResourceTemplates(){return this.request("resources/templates/list",{})}async readResource(e){return this.request("resources/read",e)}async callTool(e){return this.request("tools/call",e)}async listTools(){return this.request("tools/list",{})}};import{info as oe}from"jsonv-ts/hono";import{invariant as W,isSchema as ne,s as Y}from"jsonv-ts";var U=Symbol("mcp-feature"),ie=(o,e={})=>Object.assign(async(s,t)=>{await t()},{[U]:{type:"tool",tool:{name:o,config:e}}}),ae=(o,e,r={})=>Object.assign(async(t,n)=>{await n()},{[U]:{type:"resource",resource:{name:o,uri:e,config:r}}}),ce=(o,e)=>{for(let[r,s]of Object.entries(e)){let t=new RegExp("/:"+r+"(?:{[^/]+})?\\??");o=o.replace(t,s?`/${s}`:"")}return o};function Z(o={},e){if(Object.values(o||{}).length===0)return;for(let[t,n]of Object.entries(o))n&&(!n.type||n.type==="object"&&Object.keys(n.properties||{}).length===0)&&(n.$synthetic=!0,o[t]=Y.object({[t]:n}));let s=Object.entries(o||{}).reduce((t,[n,i])=>{if(i.type==="object")for(let[,a]of Object.entries(i.properties||{}))a.$target=n;return t.push(i),t},[]);return Y.allOf(s)}function pe(o,e){W(ne(e),"schema must be a schema",e),W(e.type==="object","schema must be an object schema",e);let r=e.properties,s={};for(let[t,n]of Object.entries(r)){let i=n.$target;W(i,"target must be a string",n),s[i]=s[i]??{},n.$synthetic?s[i]=Object.assign({},s[i],o[t]):s[i][t]=o[t]}return s}function ue(o,e,r){let s=r?.inputSchema??Z(o.info.validation),t=s?pe(e,s):{},n=new URL("https://localhost"),i=o.info.method??"GET",a=r?.headers??{},R={},f;if(n.pathname=t.param?ce(o.path,t.param):o.path,t.query)for(let[p,l]of Object.entries(t.query))l!=null&&(R[p]=l);if(["POST","PUT","PATCH","DELETE"].includes(i)){if(t.json)a["content-type"]="application/json",f=JSON.stringify(t.json);else if(t.form){f=new FormData;for(let[p,l]of Object.entries(t.form))f.append(p,l)}}else if(i==="GET"&&t.form){a["content-type"]="application/x-www-form-urlencoded";for(let[p,l]of Object.entries(t.form))R[p]=l}if(t.header)for(let[p,l]of Object.entries(t.header))a[p]=l;return n.search=new URLSearchParams(R).toString(),new Request(n.toString(),{method:i,headers:Object.fromEntries(Object.entries(a).filter(([,p])=>p!==void 0)),body:f})}function le(o){let r=oe(o,{useSchemas:!0}),s=[];for(let t of o.routes)if(U in t.handler){let n=t.handler[U],i=t.method.toUpperCase(),a=r[t.path]?.[i];if(!a||!a.handler)throw new Error(`Route ${t.path} has no handler`);s.push({...n,path:t.path,info:a})}return s}function de(o){return o.properties=Object.fromEntries(Object.entries(o.properties).sort(([,e],[,r])=>{let s=t=>{let n=t.$target==="param",i=!t.isOptional();return n&&i?1:n?2:i?3:4};return s(e)-s(r)})),o}function ee(o){let e=le(o),r=new v;for(let s of e)if(s.type==="tool"){let t=Z(s.tool.config.inputSchema??s.info.validation);t&&(t=de(t));let n=new x(s.tool.name,{description:s.info.openAPI?.summary,...s.tool.config,inputSchema:t},async(i,a)=>{let R={authorization:a.raw instanceof Request?a.raw.headers.get("authorization")??void 0:void 0},f=ue(s,i,{inputSchema:t,headers:R}),p=await o.request(f);if(!p.ok&&!s.tool.config.noErrorCodes?.includes(p.status)){let l=`HTTP ${p.status} ${p.statusText}`;try{let $=await p.json();$&&(l=JSON.stringify($))}catch{}throw new Error(l)}return p.headers.get("content-type")?.includes("application/json")?a.json(await p.json()):a.text(await p.text())});r.addTool(n)}return r}function me(o,e={}){let r=ee(o);return o.use(z({server:r,...e}))}import{invariant as K}from"jsonv-ts";function fe(o,e){let r=e.stdin,s=e.stdout,t=e.stderr;return K(r&&r.readable&&typeof r.on=="function","stdin is required"),K(s&&s.writable&&typeof s.write=="function","stdout is required"),K(t&&t.writable&&typeof t.write=="function","stderr is required"),o.onNotification(n=>{n.method==="notification/message"&&s.write(JSON.stringify(n)+` `)}),new G(r,{onMessage:async n=>{try{let i=await o.handle(n,e.raw);typeof i=="object"&&i!==null&&s.write(JSON.stringify(i)+` `)}catch(i){i instanceof c?t.write(JSON.stringify(i.toJSON())+` `):t.write(JSON.stringify({error:String(i)})+` `)}},onError:async n=>{t.write(JSON.stringify({error:String(n)})+` `)}})}var G=class{constructor(e,r){this.stdin=e;this.opts=r;this.stdin.on("data",this.onData.bind(this)),this.stdin.on("error",this.onError.bind(this))}_buffer;onData(e){this.append(e)}onError(e){this.opts?.onError?.(e)}append(e){for(this._buffer=this._buffer?Buffer.concat([this._buffer,e]):e;;)try{let r=this.readMessage();if(r===null)break;this.opts?.onMessage?.(r)}catch(r){this.opts?.onError?.(r)}}readMessage(){if(!this._buffer)return null;let e=this._buffer.indexOf(` `);if(e===-1)return null;let r=this._buffer.toString("utf8",0,e).replace(/\r$/,"");return this._buffer=this._buffer.subarray(e+1),JSON.parse(r)}[Symbol.dispose](){this.stdin.off("data",this.onData),this.stdin.off("error",this.onError),this._buffer=void 0}};export{B as McpClient,v as McpServer,S as Resource,x as Tool,ee as getMcpServer,z as mcp,ae as mcpResource,D as mcpServer,ie as mcpTool,fe as stdioTransport,V as streamableHttpTransport,me as withMcp};