somamcp 1.0.1 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # somamcp
1
+ # SomaMCP
2
2
 
3
3
  Framework for building **MCP (Model Context Protocol)** servers with built-in telemetry, introspection, and a backend abstraction layer.
4
4
 
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Option } from "functype";
1
2
  import { DirectLogger, DirectLogger as DirectLogger$1 } from "functype-log";
2
3
  import { Context as Context$1, Hono } from "hono";
3
4
  import { Tool as Tool$1 } from "@modelcontextprotocol/sdk/types.js";
@@ -272,7 +273,7 @@ type GatewayManagerInstance = {
272
273
  add: (config: GatewayConfig) => GatewayInstance;
273
274
  connectAll: () => Promise<void>;
274
275
  disconnectAll: () => Promise<void>;
275
- get: (id: string) => GatewayInstance | undefined;
276
+ get: (id: string) => Option<GatewayInstance>;
276
277
  getAll: () => ReadonlyArray<GatewayInstance>;
277
278
  getInfoAll: () => ReadonlyArray<GatewayInfo>;
278
279
  };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import{Ref as e,Try as t}from"functype";import{FastMCP as n,ServerState as r}from"fastmcp";import{Client as i}from"@modelcontextprotocol/sdk/client/index.js";import{StreamableHTTPClientTransport as a}from"@modelcontextprotocol/sdk/client/streamableHttp.js";import{appendFile as o,mkdir as s,readFile as c}from"node:fs/promises";import{dirname as l,extname as u}from"node:path";import{createDirectConsoleLogger as d}from"functype-log";const f=(e,t)=>{for(let n of t)switch(n.type){case`static`:e.get(n.path,e=>e.body(n.content,{headers:{"Content-Type":n.contentType}}));break;case`dynamic`:e.get(n.path,e=>n.handler(e));break;case`directory`:break}},p=(e,t,n)=>({handler:()=>{let r=m(e(),t(),n());return new Response(r,{headers:{"Content-Type":`text/html; charset=utf-8`}})},path:`/dashboard`,type:`dynamic`}),m=(e,t,n)=>`<!DOCTYPE html>
1
+ import{Option as e,Ref as t,Try as n}from"functype";import{FastMCP as r,ServerState as i}from"fastmcp";import{Client as a}from"@modelcontextprotocol/sdk/client/index.js";import{StreamableHTTPClientTransport as o}from"@modelcontextprotocol/sdk/client/streamableHttp.js";import{createDirectConsoleLogger as s}from"functype-log";import{appendFile as c,mkdir as l,readFile as u}from"node:fs/promises";import{dirname as d,extname as f}from"node:path";const p=(e,t)=>{t.forEach(t=>{switch(t.type){case`static`:e.get(t.path,e=>e.body(t.content,{headers:{"Content-Type":t.contentType}}));break;case`dynamic`:e.get(t.path,e=>t.handler(e));break;case`directory`:break}})},m=(e,t,n)=>({handler:()=>{let r=h(e(),t(),n());return new Response(r,{headers:{"Content-Type":`text/html; charset=utf-8`}})},path:`/dashboard`,type:`dynamic`}),h=(e,t,n)=>`<!DOCTYPE html>
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8">
@@ -32,7 +32,7 @@ import{Ref as e,Try as t}from"functype";import{FastMCP as n,ServerState as r}fro
32
32
  <div class="card">
33
33
  <h2>Health</h2>
34
34
  <div class="metric"><span class="metric-label">Status:</span> <span class="status ${e.status}">${e.status}</span></div>
35
- <div class="metric"><span class="metric-label">Uptime:</span> <span class="metric-value">${h(e.uptime)}</span></div>
35
+ <div class="metric"><span class="metric-label">Uptime:</span> <span class="metric-value">${g(e.uptime)}</span></div>
36
36
  <div class="metric"><span class="metric-label">Sessions:</span> <span class="metric-value">${e.activeSessions}</span></div>
37
37
  <div class="metric"><span class="metric-label">Gateways:</span> <span class="metric-value">${e.gateways.connected}/${e.gateways.total}</span></div>
38
38
  </div>
@@ -54,5 +54,5 @@ import{Ref as e,Try as t}from"functype";import{FastMCP as n,ServerState as r}fro
54
54
  </div>`:``}
55
55
  </div>
56
56
  </body>
57
- </html>`,h=e=>{let t=Math.floor(e/1e3),n=Math.floor(t/60),r=Math.floor(n/60),i=Math.floor(r/24);return i>0?`${String(i)}d ${String(r%24)}h`:r>0?`${String(r)}h ${String(n%60)}m`:n>0?`${String(n)}m ${String(t%60)}s`:`${String(t)}s`},g=e=>{switch(e){case r.Running:return`running`;case r.Error:return`error`;default:return`stopped`}},_=(e,t)=>{let r=new n({...t,authenticate:e.authenticate,instructions:e.instructions,logger:e.logger,name:e.name,version:e.version});return{get serverState(){return g(r.serverState)},get sessions(){return r.sessions.map(e=>({sessionId:e.sessionId}))},addPrompt:e=>{r.addPrompt(e)},addResource:e=>{r.addResource(e)},addResourceTemplate:(...e)=>{r.addResourceTemplate(...e)},addTool:e=>{r.addTool(e)},getApp:()=>r.getApp(),on:(e,t)=>{r.on(e,t)},removePrompt:e=>r.removePrompt(e),removeResource:e=>r.removeResource(e),removeTool:e=>r.removeTool(e),start:async e=>{if(!e){await r.start();return}e.transportType===`httpStream`?await r.start({httpStream:e.httpStream,transportType:`httpStream`}):await r.start({transportType:`stdio`})},stop:()=>r.stop()}},v=(t,n)=>{let r=e(void 0),o=e(void 0),s=e([]),c=e(`disconnected`),l=e(void 0),u=t.name??t.id,d=()=>{let e=t.reconnectIntervalMs??5e3;l.set(setTimeout(()=>{f.connect()},e))},f={get config(){return t},get info(){return{id:t.id,name:u,remoteTools:s.get().map(e=>({description:e.description,name:e.name})),status:c.get(),url:t.url}},get name(){return u},get status(){return c.get()},get tools(){return s.get()},async callTool(e,n={}){let i=r.get();if(!i||c.get()!==`connected`)throw Error(`Gateway ${t.id} is not connected`);return await i.callTool({arguments:n,name:e})},async connect(){if(c.get()===`connected`)return;c.set(`connecting`);let e=Date.now();try{let l=new a(new URL(t.url));o.set(l);let d=new i({name:`soma-gateway-${t.id}`,version:`1.0.0`});r.set(d),await d.connect(l);let f=await d.listTools();s.set(f.tools),c.set(`connected`),n.recordEvent({data:{id:t.id,remoteToolCount:s.get().length,url:t.url},durationMs:Date.now()-e,name:u,timestamp:e,type:`gateway.connect`})}catch(r){c.set(`error`),n.recordEvent({data:{id:t.id,url:t.url},error:r instanceof Error?r.message:String(r),name:u,timestamp:e,type:`gateway.error`}),t.reconnect!==!1&&d()}},async disconnect(){let e=l.get();e&&(clearTimeout(e),l.set(void 0));let i=r.get();if(i)try{await i.close()}catch{}r.set(void 0),o.set(void 0),s.set([]),c.set(`disconnected`),n.recordEvent({data:{id:t.id},name:u,timestamp:Date.now(),type:`gateway.disconnect`})}};return f},y=e=>{let t=new Map;return{get connectedCount(){return[...t.values()].filter(e=>e.status===`connected`).length},get totalCount(){return t.size},add(n){let r=v(n,e);return t.set(n.id,r),r},async connectAll(){await Promise.allSettled([...t.values()].map(e=>e.connect()))},async disconnectAll(){await Promise.allSettled([...t.values()].map(e=>e.disconnect()))},get(e){return t.get(e)},getAll(){return[...t.values()]},getInfoAll(){return[...t.values()].map(e=>e.info)}}},b=e=>{if(e instanceof Error){let t=e.message.toLowerCase();if(t.includes(`validation`)||t.includes(`invalid`)||t.includes(`schema`))return`validation`;if(t.includes(`timeout`)||t.includes(`timed out`)||t.includes(`etimedout`))return`timeout`;if(t.includes(`gateway`)||t.includes(`not connected`)||t.includes(`econnrefused`)||t.includes(`connection refused`))return`gateway`;if(t.includes(`auth`)||t.includes(`unauthorized`)||t.includes(`forbidden`))return`auth`;if(t.includes(`not found`)||t.includes(`404`))return`not_found`}return typeof e==`object`&&e&&`name`in e&&e.name===`UserError`?`validation`:`internal`},x=(e,t)=>{switch(e){case`validation`:return[`Check the tool's parameter schema and provide valid input`];case`timeout`:return[`The operation timed out. Try again or reduce the scope of the request`];case`gateway`:return[`Gateway is ${t?.gatewayStatus??`unavailable`}. The remote service may be down`,`Try again later or check the remote service status`];case`auth`:return[`Authentication failed. Check credentials or permissions`];case`not_found`:return[`The requested resource was not found. Verify the identifier`];case`internal`:return[`An internal error occurred. The tool maintainer should investigate`]}},S=(e,t)=>{let n=b(e),r=e instanceof Error?e.message:String(e),i=x(n,t),a={...t?.gatewayStatus&&{gatewayStatus:t.gatewayStatus},...t?.toolName&&{toolName:t.toolName}};return{errorCategory:n,message:r,...Object.keys(a).length>0&&{relatedState:a},suggestions:i}},C=e=>t.fromPromise(e),w=(e,t)=>{let n=e.config.toolPrefix??`${e.config.id}_`;return e.tools.map(r=>({annotations:{openWorldHint:!0,readOnlyHint:!0},description:`[${e.name}] ${r.description??`Remote tool`}`,execute:async i=>{let a=Date.now(),o=`${n}${r.name}`;return(await C(e.callTool(r.name,i))).fold(n=>{let s=S(n,{gatewayStatus:e.status,toolName:r.name});return t?.recordEvent({data:{gateway:e.config.id,remoteTool:r.name},durationMs:Date.now()-a,error:s.message,errorCategory:s.errorCategory,errorStack:n.stack,input:i,name:o,timestamp:a,type:`tool.error`}),{content:[{text:JSON.stringify(s,null,2),type:`text`}],isError:!0}},n=>(t?.recordEvent({data:{gateway:e.config.id,remoteTool:r.name},durationMs:Date.now()-a,input:i,name:o,output:n,timestamp:a,type:`tool.execute`}),JSON.stringify(n,null,2)))},name:`${n}${r.name}`}))},T=e=>({annotations:{readOnlyHint:!0},description:`Lists all tools, resources, and prompts registered on this server`,execute:()=>Promise.resolve(JSON.stringify(e(),null,2)),name:`soma_capabilities`}),E=e=>({annotations:{readOnlyHint:!0},description:`Lists all gateway connections to remote servers with their status and available tools`,execute:()=>Promise.resolve(JSON.stringify(e(),null,2)),name:`soma_connections`}),D=e=>({annotations:{readOnlyHint:!0},description:`Returns health and status information for this server including uptime, sessions, and gateway status`,execute:()=>Promise.resolve(JSON.stringify(e(),null,2)),name:`soma_health`}),O=e=>({flush:()=>Promise.resolve(),recordEvent:t=>{let n={durationMs:t.durationMs,eventName:t.name,timestamp:new Date(t.timestamp).toISOString(),type:t.type,...t.sessionId&&{sessionId:t.sessionId},...t.requestId&&{requestId:t.requestId},...t.errorCategory&&{errorCategory:t.errorCategory},...t.data};t.error?e.withError(Error(t.error)).error(t.type,n):e.info(t.type,n)}}),k={flush:()=>Promise.resolve(),recordEvent:()=>{}},A=(e,t)=>!t||t.length===0?e:Object.fromEntries(Object.entries(e).map(([e,n])=>[e,t.includes(e)?`[REDACTED]`:n])),j=(e,t=1e4)=>{let n=typeof e==`string`?e:JSON.stringify(e);return n&&n.length>t?`${n.slice(0,t)}...[truncated, ${n.length} total chars]`:e},M=e=>t.fromPromise(e),N=(e,t,n)=>({...e,execute:async(r,i)=>{let a=n?.captureLevel??`full`;if(a===`none`)return e.execute(r,i);let o=Date.now(),{requestId:s,sessionId:c}=i,l=a===`full`?A(r,n?.redactInputFields):void 0;return(await M(e.execute(r,i))).fold(n=>{let r=S(n,{toolName:e.name});return t.recordEvent({data:{name:e.name},durationMs:Date.now()-o,error:r.message,errorCategory:r.errorCategory,errorStack:n.stack,input:l,name:e.name,requestId:s,sessionId:c,timestamp:o,type:`tool.error`}),{content:[{text:JSON.stringify(r,null,2),type:`text`}],isError:!0}},r=>(t.recordEvent({data:{name:e.name},durationMs:Date.now()-o,input:l,name:e.name,output:a===`full`?j(r,n?.maxOutputSize):void 0,requestId:s,sessionId:c,timestamp:o,type:`tool.execute`}),r))}}),P=(e,t)=>({...e,load:async n=>{let r=Date.now();return(await M(e.load(n))).fold(n=>{let i=S(n);throw t.recordEvent({data:{name:e.name,uri:e.uri},durationMs:Date.now()-r,error:i.message,errorCategory:i.errorCategory,errorStack:n.stack,name:e.name,timestamp:r,type:`resource.error`}),n},n=>(t.recordEvent({data:{name:e.name,uri:e.uri},durationMs:Date.now()-r,name:e.name,timestamp:r,type:`resource.load`}),n))}}),F=(e,t)=>({...e,load:async(n,r)=>{let i=Date.now();return(await M(e.load(n,r))).fold(n=>{let r=S(n);throw t.recordEvent({data:{name:e.name},durationMs:Date.now()-i,error:r.message,errorCategory:r.errorCategory,errorStack:n.stack,name:e.name,timestamp:i,type:`prompt.error`}),n},n=>(t.recordEvent({data:{name:e.name},durationMs:Date.now()-i,name:e.name,timestamp:i,type:`prompt.load`}),n))}}),I=t=>{let{artifacts:n,backendOptions:r,enableDashboard:i,enableIntrospection:a,gateways:o,logLayer:s,telemetry:c,...l}=t,u=l.name,d=c??(s?O(s):k),m=_(l,r),h=y(d),g=[],v=[],b=[],x=e(0);if(o)for(let e of o)h.add(e);let S=()=>{let e=x.get(),t=e>0?Date.now()-e:0;return{activeSessions:m.sessions.length,gateways:{connected:h.connectedCount,total:h.totalCount},name:u,startedAt:e,status:m.serverState,uptime:t}},C=()=>({prompts:b,resources:v,tools:g});a!==!1&&(m.addTool(D(()=>S())),m.addTool(T(()=>C())),m.addTool(E(()=>h.getInfoAll())));let A=[...n??[]];i!==!1&&A.push(p(()=>S(),()=>C(),()=>h.getInfoAll())),A.length>0&&f(m.getApp(),A),m.on(`connect`,({session:e})=>{d.recordEvent({data:{sessionId:e.sessionId},name:u,timestamp:Date.now(),type:`session.connect`})}),m.on(`disconnect`,({session:e})=>{d.recordEvent({data:{sessionId:e.sessionId},name:u,timestamp:Date.now(),type:`session.disconnect`})});let j=e=>{let t=N(e,d,`captureConfig`in e?e.captureConfig:void 0);m.addTool(t),g.push({description:e.description,name:e.name})},M=e=>{let t=P(e,d);m.addResource(t),v.push({description:e.description,name:e.name,uri:e.uri})},I=e=>{let t=F(e,d);m.addPrompt(t),b.push({description:e.description,name:e.name})};return{get name(){return u},get serverState(){return m.serverState},get sessions(){return m.sessions},addPrompt:I,addPrompts:e=>{for(let t of e)I(t)},addResource:M,addResources:e=>{for(let t of e)M(t)},addResourceTemplate:(...e)=>{m.addResourceTemplate(...e)},addTool:j,addTools:e=>{for(let t of e)j(t)},getApp:()=>m.getApp(),getCapabilities:C,getGatewayManager:()=>h,getHealth:S,removePrompt:e=>{m.removePrompt(e)},removeResource:e=>{m.removeResource(e)},removeTool:e=>{m.removeTool(e)},async start(e){x.set(Date.now()),d.recordEvent({name:u,timestamp:x.get(),type:`server.start`}),await m.start(e),await h.connectAll();for(let e of h.getAll())if(e.status===`connected`&&e.config.proxyTools!==!1){let t=w(e,d);for(let e of t)m.addTool(e)}},async stop(){await h.disconnectAll(),await m.stop(),d.recordEvent({name:u,timestamp:Date.now(),type:`server.stop`}),d.flush&&await d.flush()}}};var L=class extends Error{constructor(e){super(e),this.name=`UserError`}};const R={".avif":`image/avif`,".bmp":`image/bmp`,".gif":`image/gif`,".ico":`image/x-icon`,".jpeg":`image/jpeg`,".jpg":`image/jpeg`,".png":`image/png`,".svg":`image/svg+xml`,".tiff":`image/tiff`,".webp":`image/webp`},z={".aac":`audio/aac`,".flac":`audio/flac`,".m4a":`audio/mp4`,".mp3":`audio/mpeg`,".ogg":`audio/ogg`,".opus":`audio/opus`,".wav":`audio/wav`,".weba":`audio/webm`},B=(e,t,n)=>t[u(new URL(e,`file:///`).pathname).toLowerCase()]??n,V=async(e,t)=>{if(`url`in e){let n=await fetch(e.url);if(!n.ok)throw Error(`Failed to fetch ${t} from URL (${e.url}): ${n.status} ${n.statusText}`);return{data:Buffer.from(await n.arrayBuffer()),source:e.url}}return`path`in e?{data:await c(e.path),source:e.path}:{data:e.buffer}},H=async e=>{let{data:t,source:n}=await V(e,`image`),r=n?B(n,R,`image/png`):`image/png`;return{data:t.toString(`base64`),mimeType:r,type:`image`}},U=async e=>{let{data:t,source:n}=await V(e,`audio`),r=n?B(n,z,`audio/mpeg`):`audio/mpeg`;return{data:t.toString(`base64`),mimeType:r,type:`audio`}},W=e=>({flush:async()=>{await Promise.all(e.map(e=>e.flush?e.flush():Promise.resolve()))},recordEvent:t=>{e.forEach(e=>e.recordEvent(t))}}),G=(e=`[soma]`)=>O(d({prefix:e})),K=t=>{let n=e([]),r=e(void 0),i=e(!1),a=async()=>{let e=n.getAndSet([]);if(e.length===0)return;i.get()||(await s(l(t.filePath),{recursive:!0}),i.set(!0));let r=`${e.map(e=>JSON.stringify(e)).join(`
58
- `)}\n`;await o(t.filePath,r)},c=()=>{r.get()||r.set(setTimeout(()=>{r.set(void 0),a()},t.flushIntervalMs??1e3))};return{flush:async()=>{let e=r.get();e&&(clearTimeout(e),r.set(void 0)),await a()},recordEvent:e=>{n.update(t=>[...t,e]),c()}}},q=e=>d({prefix:`[${e}]`}).withContext({cell:e});export{k as NoopTelemetry,L as UserError,U as audioContent,b as classifyError,T as createCapabilitiesTool,W as createCompositeTelemetry,E as createConnectionsTool,G as createConsoleTelemetry,p as createDashboardArtifact,q as createDefaultLogger,S as createEnrichedError,_ as createFastMCPBackend,v as createGateway,y as createGatewayManager,D as createHealthTool,K as createJsonFileTelemetry,O as createLogLayerTelemetry,w as createProxiedTools,I as createServer,H as imageContent,f as registerArtifacts,F as wrapPrompt,P as wrapResource,N as wrapTool};
57
+ </html>`,g=e=>{let t=Math.floor(e/1e3),n=Math.floor(t/60),r=Math.floor(n/60),i=Math.floor(r/24);return i>0?`${String(i)}d ${String(r%24)}h`:r>0?`${String(r)}h ${String(n%60)}m`:n>0?`${String(n)}m ${String(t%60)}s`:`${String(t)}s`},_=e=>{switch(e){case i.Running:return`running`;case i.Error:return`error`;default:return`stopped`}},v=(e,t)=>{let n=new r({...t,authenticate:e.authenticate,instructions:e.instructions,logger:e.logger,name:e.name,version:e.version});return{get serverState(){return _(n.serverState)},get sessions(){return n.sessions.map(e=>({sessionId:e.sessionId}))},addPrompt:e=>{n.addPrompt(e)},addResource:e=>{n.addResource(e)},addResourceTemplate:(...e)=>{n.addResourceTemplate(...e)},addTool:e=>{n.addTool(e)},getApp:()=>n.getApp(),on:(e,t)=>{n.on(e,t)},removePrompt:e=>n.removePrompt(e),removeResource:e=>n.removeResource(e),removeTool:e=>n.removeTool(e),start:async e=>{if(!e){await n.start();return}e.transportType===`httpStream`?await n.start({httpStream:e.httpStream,transportType:`httpStream`}):await n.start({transportType:`stdio`})},stop:()=>n.stop()}},y=(r,i)=>{let s=t(e.none()),c=t(e.none()),l=t([]),u=t(`disconnected`),d=t(e.none()),f=r.name??r.id,p=()=>{let t=r.reconnectIntervalMs??5e3;d.set(e(setTimeout(()=>{m.connect()},t)))},m={get config(){return r},get info(){return{id:r.id,name:f,remoteTools:l.get().map(e=>({description:e.description,name:e.name})),status:u.get(),url:r.url}},get name(){return f},get status(){return u.get()},get tools(){return l.get()},async callTool(e,t={}){let n=s.get();if(n.isEmpty||u.get()!==`connected`)throw Error(`Gateway ${r.id} is not connected`);return n.orThrow(Error(`Gateway ${r.id} is not connected`)).callTool({arguments:t,name:e})},async connect(){if(u.get()===`connected`)return;u.set(`connecting`);let t=Date.now();(await n.fromPromise((async()=>{let t=new o(new URL(r.url));c.set(e(t));let n=new a({name:`soma-gateway-${r.id}`,version:`1.0.0`});s.set(e(n)),await n.connect(t);let i=await n.listTools();l.set(i.tools)})())).fold(e=>{u.set(`error`),i.recordEvent({data:{id:r.id,url:r.url},error:e instanceof Error?e.message:String(e),name:f,timestamp:t,type:`gateway.error`}),r.reconnect!==!1&&p()},()=>{u.set(`connected`),i.recordEvent({data:{id:r.id,remoteToolCount:l.get().length,url:r.url},durationMs:Date.now()-t,name:f,timestamp:t,type:`gateway.connect`})})},async disconnect(){d.get().match({None:()=>void 0,Some:e=>clearTimeout(e)}),d.set(e.none());let t=s.get();t.isSome()&&await n.fromPromise(t.value.close()),s.set(e.none()),c.set(e.none()),l.set([]),u.set(`disconnected`),i.recordEvent({data:{id:r.id},name:f,timestamp:Date.now(),type:`gateway.disconnect`})}};return m},b=t=>{let n=new Map;return{get connectedCount(){return[...n.values()].filter(e=>e.status===`connected`).length},get totalCount(){return n.size},add(e){let r=y(e,t);return n.set(e.id,r),r},async connectAll(){await Promise.allSettled([...n.values()].map(e=>e.connect()))},async disconnectAll(){await Promise.allSettled([...n.values()].map(e=>e.disconnect()))},get(t){return e(n.get(t))},getAll(){return[...n.values()]},getInfoAll(){return[...n.values()].map(e=>e.info)}}},x=e=>({flush:async()=>{await Promise.all(e.map(e=>e.flush?e.flush():Promise.resolve()))},recordEvent:t=>{e.forEach(e=>e.recordEvent(t))}}),S=e=>({flush:()=>Promise.resolve(),recordEvent:t=>{let n={durationMs:t.durationMs,eventName:t.name,timestamp:new Date(t.timestamp).toISOString(),type:t.type,...t.sessionId&&{sessionId:t.sessionId},...t.requestId&&{requestId:t.requestId},...t.errorCategory&&{errorCategory:t.errorCategory},...t.data};t.error?e.withError(Error(t.error)).error(t.type,n):e.info(t.type,n)}}),C=(e=`[soma]`)=>S(s({prefix:e})),w=e=>{if(e instanceof Error){let t=e.message.toLowerCase();if(t.includes(`validation`)||t.includes(`invalid`)||t.includes(`schema`))return`validation`;if(t.includes(`timeout`)||t.includes(`timed out`)||t.includes(`etimedout`))return`timeout`;if(t.includes(`gateway`)||t.includes(`not connected`)||t.includes(`econnrefused`)||t.includes(`connection refused`))return`gateway`;if(t.includes(`auth`)||t.includes(`unauthorized`)||t.includes(`forbidden`))return`auth`;if(t.includes(`not found`)||t.includes(`404`))return`not_found`}return typeof e==`object`&&e&&`name`in e&&e.name===`UserError`?`validation`:`internal`},T=(e,t)=>{switch(e){case`validation`:return[`Check the tool's parameter schema and provide valid input`];case`timeout`:return[`The operation timed out. Try again or reduce the scope of the request`];case`gateway`:return[`Gateway is ${t?.gatewayStatus??`unavailable`}. The remote service may be down`,`Try again later or check the remote service status`];case`auth`:return[`Authentication failed. Check credentials or permissions`];case`not_found`:return[`The requested resource was not found. Verify the identifier`];case`internal`:return[`An internal error occurred. The tool maintainer should investigate`]}},E=(e,t)=>{let n=w(e),r=e instanceof Error?e.message:String(e),i=T(n,t),a={...t?.gatewayStatus&&{gatewayStatus:t.gatewayStatus},...t?.toolName&&{toolName:t.toolName}};return{errorCategory:n,message:r,...Object.keys(a).length>0&&{relatedState:a},suggestions:i}},D=n=>{let r=t([]),i=t(e.none()),a=t(!1),o=async()=>{let e=r.getAndSet([]);if(e.length===0)return;a.get()||(await l(d(n.filePath),{recursive:!0}),a.set(!0));let t=`${e.map(e=>JSON.stringify(e)).join(`
58
+ `)}\n`;await c(n.filePath,t)},s=()=>{i.get().isSome()||i.set(e(setTimeout(()=>{i.set(e.none()),o()},n.flushIntervalMs??1e3)))};return{flush:async()=>{i.get().match({None:()=>void 0,Some:e=>clearTimeout(e)}),i.set(e.none()),await o()},recordEvent:e=>{r.update(t=>[...t,e]),s()}}},O={flush:()=>Promise.resolve(),recordEvent:()=>{}},k=(e,t)=>!t||t.length===0?e:Object.fromEntries(Object.entries(e).map(([e,n])=>[e,t.includes(e)?`[REDACTED]`:n])),A=(e,t=1e4)=>{let n=typeof e==`string`?e:JSON.stringify(e);return n&&n.length>t?`${n.slice(0,t)}...[truncated, ${n.length} total chars]`:e},j=e=>n.fromPromise(e),M=(e,t,n)=>({...e,execute:async(r,i)=>{let a=n?.captureLevel??`full`;if(a===`none`)return e.execute(r,i);let o=Date.now(),{requestId:s,sessionId:c}=i,l=a===`full`?k(r,n?.redactInputFields):void 0;return(await j(e.execute(r,i))).fold(n=>{let r=E(n,{toolName:e.name});return t.recordEvent({data:{name:e.name},durationMs:Date.now()-o,error:r.message,errorCategory:r.errorCategory,errorStack:n.stack,input:l,name:e.name,requestId:s,sessionId:c,timestamp:o,type:`tool.error`}),{content:[{text:JSON.stringify(r,null,2),type:`text`}],isError:!0}},r=>(t.recordEvent({data:{name:e.name},durationMs:Date.now()-o,input:l,name:e.name,output:a===`full`?A(r,n?.maxOutputSize):void 0,requestId:s,sessionId:c,timestamp:o,type:`tool.execute`}),r))}}),N=(e,t)=>({...e,load:async n=>{let r=Date.now();return(await j(e.load(n))).fold(n=>{let i=E(n);throw t.recordEvent({data:{name:e.name,uri:e.uri},durationMs:Date.now()-r,error:i.message,errorCategory:i.errorCategory,errorStack:n.stack,name:e.name,timestamp:r,type:`resource.error`}),n},n=>(t.recordEvent({data:{name:e.name,uri:e.uri},durationMs:Date.now()-r,name:e.name,timestamp:r,type:`resource.load`}),n))}}),P=(e,t)=>({...e,load:async(n,r)=>{let i=Date.now();return(await j(e.load(n,r))).fold(n=>{let r=E(n);throw t.recordEvent({data:{name:e.name},durationMs:Date.now()-i,error:r.message,errorCategory:r.errorCategory,errorStack:n.stack,name:e.name,timestamp:i,type:`prompt.error`}),n},n=>(t.recordEvent({data:{name:e.name},durationMs:Date.now()-i,name:e.name,timestamp:i,type:`prompt.load`}),n))}}),F=e=>n.fromPromise(e),I=(e,t)=>{let n=e.config.toolPrefix??`${e.config.id}_`;return e.tools.map(r=>({annotations:{openWorldHint:!0,readOnlyHint:!0},description:`[${e.name}] ${r.description??`Remote tool`}`,execute:async i=>{let a=Date.now(),o=`${n}${r.name}`;return(await F(e.callTool(r.name,i))).fold(n=>{let s=E(n,{gatewayStatus:e.status,toolName:r.name});return t?.recordEvent({data:{gateway:e.config.id,remoteTool:r.name},durationMs:Date.now()-a,error:s.message,errorCategory:s.errorCategory,errorStack:n.stack,input:i,name:o,timestamp:a,type:`tool.error`}),{content:[{text:JSON.stringify(s,null,2),type:`text`}],isError:!0}},n=>(t?.recordEvent({data:{gateway:e.config.id,remoteTool:r.name},durationMs:Date.now()-a,input:i,name:o,output:n,timestamp:a,type:`tool.execute`}),JSON.stringify(n,null,2)))},name:`${n}${r.name}`}))},L=e=>({annotations:{readOnlyHint:!0},description:`Lists all tools, resources, and prompts registered on this server`,execute:()=>Promise.resolve(JSON.stringify(e(),null,2)),name:`soma_capabilities`}),R=e=>({annotations:{readOnlyHint:!0},description:`Lists all gateway connections to remote servers with their status and available tools`,execute:()=>Promise.resolve(JSON.stringify(e(),null,2)),name:`soma_connections`}),z=e=>({annotations:{readOnlyHint:!0},description:`Returns health and status information for this server including uptime, sessions, and gateway status`,execute:()=>Promise.resolve(JSON.stringify(e(),null,2)),name:`soma_health`}),B=e=>{let{artifacts:n,backendOptions:r,enableDashboard:i,enableIntrospection:a,gateways:o,logLayer:s,telemetry:c,...l}=e,u=l.name,d=c??(s?S(s):O),f=v(l,r),h=b(d),g=[],_=[],y=[],x=t(0);o?.forEach(e=>h.add(e));let C=()=>{let e=x.get(),t=e>0?Date.now()-e:0;return{activeSessions:f.sessions.length,gateways:{connected:h.connectedCount,total:h.totalCount},name:u,startedAt:e,status:f.serverState,uptime:t}},w=()=>({prompts:y,resources:_,tools:g});a!==!1&&(f.addTool(z(()=>C())),f.addTool(L(()=>w())),f.addTool(R(()=>h.getInfoAll())));let T=[...n??[]];i!==!1&&T.push(m(()=>C(),()=>w(),()=>h.getInfoAll())),T.length>0&&p(f.getApp(),T),f.on(`connect`,({session:e})=>{d.recordEvent({data:{sessionId:e.sessionId},name:u,timestamp:Date.now(),type:`session.connect`})}),f.on(`disconnect`,({session:e})=>{d.recordEvent({data:{sessionId:e.sessionId},name:u,timestamp:Date.now(),type:`session.disconnect`})});let E=e=>{let t=M(e,d,`captureConfig`in e?e.captureConfig:void 0);f.addTool(t),g.push({description:e.description,name:e.name})},D=e=>{let t=N(e,d);f.addResource(t),_.push({description:e.description,name:e.name,uri:e.uri})},k=e=>{let t=P(e,d);f.addPrompt(t),y.push({description:e.description,name:e.name})};return{get name(){return u},get serverState(){return f.serverState},get sessions(){return f.sessions},addPrompt:k,addPrompts:e=>{e.forEach(k)},addResource:D,addResources:e=>{e.forEach(D)},addResourceTemplate:(...e)=>{f.addResourceTemplate(...e)},addTool:E,addTools:e=>{e.forEach(e=>E(e))},getApp:()=>f.getApp(),getCapabilities:w,getGatewayManager:()=>h,getHealth:C,removePrompt:e=>{f.removePrompt(e)},removeResource:e=>{f.removeResource(e)},removeTool:e=>{f.removeTool(e)},async start(e){x.set(Date.now()),d.recordEvent({name:u,timestamp:x.get(),type:`server.start`}),await f.start(e),await h.connectAll(),h.getAll().filter(e=>e.status===`connected`&&e.config.proxyTools!==!1).forEach(e=>{I(e,d).forEach(e=>f.addTool(e))})},async stop(){await h.disconnectAll(),await f.stop(),d.recordEvent({name:u,timestamp:Date.now(),type:`server.stop`}),d.flush&&await d.flush()}}};var V=class extends Error{constructor(e){super(e),this.name=`UserError`}};const H={".avif":`image/avif`,".bmp":`image/bmp`,".gif":`image/gif`,".ico":`image/x-icon`,".jpeg":`image/jpeg`,".jpg":`image/jpeg`,".png":`image/png`,".svg":`image/svg+xml`,".tiff":`image/tiff`,".webp":`image/webp`},U={".aac":`audio/aac`,".flac":`audio/flac`,".m4a":`audio/mp4`,".mp3":`audio/mpeg`,".ogg":`audio/ogg`,".opus":`audio/opus`,".wav":`audio/wav`,".weba":`audio/webm`},W=(e,t,n)=>t[f(new URL(e,`file:///`).pathname).toLowerCase()]??n,G=async(e,t)=>{if(`url`in e){let n=await fetch(e.url);if(!n.ok)throw Error(`Failed to fetch ${t} from URL (${e.url}): ${n.status} ${n.statusText}`);return{data:Buffer.from(await n.arrayBuffer()),source:e.url}}return`path`in e?{data:await u(e.path),source:e.path}:{data:e.buffer}},K=async e=>{let{data:t,source:n}=await G(e,`image`),r=n?W(n,H,`image/png`):`image/png`;return{data:t.toString(`base64`),mimeType:r,type:`image`}},q=async e=>{let{data:t,source:n}=await G(e,`audio`),r=n?W(n,U,`audio/mpeg`):`audio/mpeg`;return{data:t.toString(`base64`),mimeType:r,type:`audio`}},J=e=>s({prefix:`[${e}]`}).withContext({cell:e});export{O as NoopTelemetry,V as UserError,q as audioContent,w as classifyError,L as createCapabilitiesTool,x as createCompositeTelemetry,R as createConnectionsTool,C as createConsoleTelemetry,m as createDashboardArtifact,J as createDefaultLogger,E as createEnrichedError,v as createFastMCPBackend,y as createGateway,b as createGatewayManager,z as createHealthTool,D as createJsonFileTelemetry,S as createLogLayerTelemetry,I as createProxiedTools,B as createServer,K as imageContent,p as registerArtifacts,P as wrapPrompt,N as wrapResource,M as wrapTool};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "somamcp",
3
- "version": "1.0.1",
3
+ "version": "1.0.6",
4
4
  "description": "Framework for building MCP servers with telemetry, introspection, and backend abstraction",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -20,6 +20,21 @@
20
20
  "files": [
21
21
  "dist"
22
22
  ],
23
+ "scripts": {
24
+ "validate": "ts-builds validate",
25
+ "format": "ts-builds format",
26
+ "format:check": "ts-builds format:check",
27
+ "lint": "ts-builds lint",
28
+ "lint:check": "ts-builds lint:check",
29
+ "typecheck": "ts-builds typecheck",
30
+ "test": "ts-builds test",
31
+ "test:watch": "ts-builds test:watch",
32
+ "test:coverage": "ts-builds test:coverage",
33
+ "build": "ts-builds build",
34
+ "dev": "ts-builds dev",
35
+ "clean": "rimraf dist",
36
+ "prepublishOnly": "pnpm validate"
37
+ },
23
38
  "keywords": [
24
39
  "MCP",
25
40
  "server",
@@ -29,33 +44,28 @@
29
44
  ],
30
45
  "author": "Jordan Burke",
31
46
  "license": "MIT",
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/sapientsai/SomaMCP.git"
50
+ },
51
+ "bugs": {
52
+ "url": "https://github.com/sapientsai/SomaMCP/issues"
53
+ },
54
+ "homepage": "https://github.com/sapientsai/SomaMCP#readme",
32
55
  "dependencies": {
33
56
  "@modelcontextprotocol/sdk": "^1.29.0",
34
- "fastmcp": "^3.35.0",
35
- "functype": "^0.56.0",
36
- "functype-log": "^0.2.0",
37
- "hono": "^4.12.12",
38
- "zod": "^4.3.6"
57
+ "fastmcp": "^4.0.1",
58
+ "functype": "^0.60.3",
59
+ "functype-log": "^0.60.0",
60
+ "hono": "^4.12.16",
61
+ "zod": "^4.4.3"
39
62
  },
40
63
  "prettier": "ts-builds/prettier",
41
64
  "devDependencies": {
42
65
  "@types/node": "^24.12.2",
43
66
  "get-port-please": "^3.2.0",
44
- "ts-builds": "^2.6.4",
45
- "tsdown": "^0.21.7"
67
+ "ts-builds": "^2.7.2",
68
+ "tsdown": "^0.21.10"
46
69
  },
47
- "scripts": {
48
- "validate": "ts-builds validate",
49
- "format": "ts-builds format",
50
- "format:check": "ts-builds format:check",
51
- "lint": "ts-builds lint",
52
- "lint:check": "ts-builds lint:check",
53
- "typecheck": "ts-builds typecheck",
54
- "test": "ts-builds test",
55
- "test:watch": "ts-builds test:watch",
56
- "test:coverage": "ts-builds test:coverage",
57
- "build": "ts-builds build",
58
- "dev": "ts-builds dev",
59
- "clean": "rimraf dist"
60
- }
61
- }
70
+ "packageManager": "pnpm@10.33.3+sha512.a19744364a7e248b92657a4ca5973f9354d21caf982579674b1c539f32c7420c47138ad8b1254df07aba9bc782d9b3029e3db34d5dbff974326eb74dac8ff489"
71
+ }