somamcp 1.3.4 → 1.4.1

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
@@ -476,6 +476,13 @@ createServer({
476
476
  **Transport**
477
477
 
478
478
  - Types: `TransportConfig`, `HttpStreamConfig` (accepts `cors`, `stateless`, `eventStore`, `sslCert` / `sslKey` / `sslCa`, plus `port` / `host` / `endpoint` / `enableJsonResponse`)
479
+ - `streamKeepalive` on `ServerConfig` — `{ enabled, intervalMs }`, written onto an in-flight tool call's own
480
+ response stream so a proxy does not close it as idle during a long, silent call. **Pair it with
481
+ `httpStream.stateless`**: a transport-level ping needs the standing server-to-client stream that stateless
482
+ does not have, so this is the only keepalive left. It sits on `ServerConfig` rather than `HttpStreamConfig`
483
+ because the backend builds its server before `start()` sees the transport. Ignored by the edge backend.
484
+ Leave `httpStream.enableJsonResponse` **off**: in JSON-response mode the MCP SDK skips the SSE write for
485
+ request-related notifications, so the keepalive is accepted, costs an interval timer, and does nothing.
479
486
 
480
487
  **Introspection**
481
488
 
@@ -1,2 +1,2 @@
1
- import { a as BackendSession, i as BackendFactory, n as BackendAdapter, r as BackendEvents, t as createFastMCPBackend } from "../index-pl2m5zH7.js";
1
+ import { a as BackendSession, i as BackendFactory, n as BackendAdapter, r as BackendEvents, t as createFastMCPBackend } from "../index-CSy9en2A.js";
2
2
  export { type BackendAdapter, type BackendEvents, type BackendFactory, type BackendSession, createFastMCPBackend };
@@ -1 +1 @@
1
- import{t as e}from"../backend-CA7j4YCF.js";export{e as createFastMCPBackend};
1
+ import{t as e}from"../backend-Dd0n_9Ai.js";export{e as createFastMCPBackend};
@@ -0,0 +1 @@
1
+ import{FastMCP as e,ServerState as t}from"fastmcp";const n=e=>{switch(e){case t.Running:return`running`;case t.Error:return`error`;default:return`stopped`}},r=(t,r)=>{let i={...r,authenticate:t.authenticate,instructions:t.instructions,logger:t.logger,name:t.name,...t.streamKeepalive?{streamKeepalive:t.streamKeepalive}:{},version:t.version},a=new e(i);return{get serverState(){return n(a.serverState)},get sessions(){return a.sessions.map(e=>({sessionId:e.sessionId}))},addPrompt:e=>{a.addPrompt(e)},addResource:e=>{a.addResource(e)},addResourceTemplate:(...e)=>{a.addResourceTemplate(...e)},addTool:e=>{a.addTool(e)},fetch:async e=>a.getApp().fetch(e),getApp:()=>a.getApp(),on:(e,t)=>{a.on(e,t)},removePrompt:e=>a.removePrompt(e),removeResource:e=>a.removeResource(e),removeTool:e=>a.removeTool(e),start:async e=>{if(!e){await a.start();return}e.transportType===`httpStream`?await a.start({httpStream:e.httpStream,transportType:`httpStream`}):await a.start({transportType:`stdio`})},stop:()=>a.stop()}};export{r as t};
package/dist/edge.d.ts CHANGED
@@ -191,6 +191,37 @@ type ServerConfig<T extends SessionAuth = SessionAuth> = {
191
191
  instructions?: string;
192
192
  logger?: Logger;
193
193
  name: string;
194
+ /**
195
+ * Periodic writes onto an in-flight tool call's own response stream, so a proxy or load
196
+ * balancer does not close the connection as idle while a long-running tool produces no
197
+ * output — a large document read, say.
198
+ *
199
+ * This is the only keepalive that survives `httpStream.stateless`. A transport-level ping
200
+ * needs the standing server-to-client stream, and stateless has none; these travel on the
201
+ * request's own stream instead. If you set `stateless` and have anything with an idle
202
+ * timeout in front of you, you want this too.
203
+ *
204
+ * Set at construction rather than on `httpStream`, because the backend builds its server
205
+ * before `start()` ever sees the transport config.
206
+ *
207
+ * Honoured by the fastmcp backend. The edge backend ignores it — Workers and Deno Deploy
208
+ * terminate the connection themselves, so there is nothing for somamcp to keep alive.
209
+ */
210
+ streamKeepalive?: {
211
+ /** Whether to write keepalives. Opt-in. */
212
+ enabled?: boolean;
213
+ /**
214
+ * Gap between keepalives. Keep it comfortably under the shortest idle timeout on the
215
+ * path — an AWS ALB defaults to 60s. Defaults to 20s in the fastmcp backend.
216
+ */
217
+ intervalMs?: number;
218
+ /**
219
+ * Level reported on the keepalive notification. Defaults to `debug` in the fastmcp
220
+ * backend, which is usually what you want — these fire every interval, for the whole
221
+ * length of every long call.
222
+ */
223
+ logLevel?: "alert" | "critical" | "debug" | "emergency" | "error" | "info" | "notice" | "warning";
224
+ };
194
225
  version: `${number}.${number}.${number}`;
195
226
  };
196
227
  /**
@@ -532,9 +563,6 @@ type EdgeBackendOptions = {
532
563
  * EdgeFastMCP, which serves the MCP endpoint. This ordering matters — EdgeFastMCP
533
564
  * registers its own `/health` at construction, and Hono is first-match-wins, so
534
565
  * mounting it first would shadow somamcp's health artifact.
535
- *
536
- * The prefer-either disable below is for the nested tool-execute throw, which
537
- * EdgeFastMCP requires; this factory itself never throws.
538
566
  */
539
567
  declare const createEdgeBackend: <T extends SessionAuth = SessionAuth>(config: ServerConfig<T>, backendOptions?: Record<string, unknown>) => BackendAdapter<T>;
540
568
  //#endregion
package/dist/edge.js CHANGED
@@ -54,8 +54,8 @@ import{EdgeFastMCP as e}from"fastmcp/edge";import{Map as t,Option as n,Ref as r,
54
54
  </div>`:``}
55
55
  </div>
56
56
  </body>
57
- </html>`,C=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`},w=`/health`,T=`/health/detail`,E=(e,t=w)=>({handler:()=>{let t=e(),n=t.status===`running`?200:503;return new Response(JSON.stringify({name:t.name,status:t.status}),{headers:{"Content-Type":`application/json`},status:n})},path:t,type:`dynamic`}),D=(e,t=T)=>({handler:()=>{let t=e(),n=t.status===`running`?200:503;return new Response(JSON.stringify(t),{headers:{"Content-Type":`application/json`},status:n})},path:t,protected:!0,type:`dynamic`}),O=`/info`,k=(e,t=O)=>({handler:()=>new Response(JSON.stringify(e()),{headers:{"Content-Type":`application/json`},status:200}),path:t,protected:!0,type:`dynamic`}),A=()=>n(globalThis.process),j=e=>n(globalThis[e]),M=()=>j(`navigator`).flatMap(e=>n(e.userAgent)),N=()=>M().contains(`Cloudflare-Workers`)||j(`Deno`).isEmpty===!1||j(`EdgeRuntime`).isEmpty===!1,P=()=>!N()&&A().flatMap(e=>n(e.versions?.node)).isEmpty===!1,F=e=>A().flatMap(t=>n(t.env?.[e])).filter(e=>e.length>0),I=()=>({branch:F(`SOMAMCP_BUILD_BRANCH`).orUndefined(),commit:F(`SOMAMCP_BUILD_COMMIT`).orUndefined(),date:F(`SOMAMCP_BUILD_DATE`).orUndefined(),environment:F(`SOMAMCP_ENVIRONMENT`).orUndefined()}),L=e=>{let t=I();return{branch:e?.branch??t.branch,commit:e?.commit??t.commit,date:e?.date??t.date,environment:e?.environment??t.environment}},R=`unknown`,z=()=>P()?{arch:A().flatMap(e=>n(e.arch)).orElse(R),nodeVersion:A().flatMap(e=>n(e.version)).orElse(R),platform:A().flatMap(e=>n(e.platform)).orElse(R),runtime:`node`}:{arch:R,nodeVersion:R,platform:M().orElse(R),runtime:`edge`},B=(e,t)=>{let i=r(n.none()),o=r(n.none()),s=r([]),u=r(`disconnected`),d=r(n.none()),f=e.name??e.id,p=()=>{let t=e.reconnectIntervalMs??5e3;d.set(n(setTimeout(()=>{m.connect()},t)))},m={get config(){return e},get info(){return{id:e.id,name:f,remoteTools:s.get().map(e=>({description:e.description,name:e.name})),status:u.get(),url:e.url}},get name(){return f},get status(){return u.get()},get tools(){return s.get()},async callTool(t,n={}){let r=i.get();if(r.isEmpty||u.get()!==`connected`)throw Error(`Gateway ${e.id} is not connected`);return r.orThrow(Error(`Gateway ${e.id} is not connected`)).callTool({arguments:n,name:t})},async connect(){if(u.get()===`connected`)return;u.set(`connecting`);let r=Date.now();(await a.fromPromise((async()=>{let t=new l(new URL(e.url));o.set(n(t));let r=new c({name:`soma-gateway-${e.id}`,version:`1.0.0`});i.set(n(r)),await r.connect(t);let a=await r.listTools();s.set(a.tools)})())).fold(n=>{u.set(`error`),t.recordEvent({data:{id:e.id,url:e.url},error:n instanceof Error?n.message:String(n),name:f,timestamp:r,type:`gateway.error`}),e.reconnect!==!1&&p()},()=>{u.set(`connected`),t.recordEvent({data:{id:e.id,remoteToolCount:s.get().length,url:e.url},durationMs:Date.now()-r,name:f,timestamp:r,type:`gateway.connect`})})},async disconnect(){d.get().match({None:()=>void 0,Some:e=>clearTimeout(e)}),d.set(n.none());let r=i.get();r.isSome()&&await a.fromPromise(r.value.close()),i.set(n.none()),o.set(n.none()),s.set([]),u.set(`disconnected`),t.recordEvent({data:{id:e.id},name:f,timestamp:Date.now(),type:`gateway.disconnect`})}};return m},V=e=>{let n=r(t.empty()),i=()=>[...n.get()].map(([,e])=>e);return{get connectedCount(){return i().filter(e=>e.status===`connected`).length},get totalCount(){return n.get().size},add(t){let r=B(t,e);return n.set(n.get().add(o([t.id,r]))),r},async connectAll(){await Promise.allSettled(i().map(e=>e.connect()))},async disconnectAll(){await Promise.allSettled(i().map(e=>e.disconnect()))},get(e){return n.get().get(e)},getAll(){return i()},getInfoAll(){return i().map(e=>e.info)}}},H=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`},U=(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`]}},W=(e,t)=>{let n=H(e),r=e instanceof Error?e.message:String(e),i=U(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}},G=e=>a.fromPromise(e),K=(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 G(e.callTool(r.name,i))).fold(n=>{let s=W(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}`}))},q=(e,t=`info`)=>({annotations:{readOnlyHint:!0},description:`Returns identity and build information for this server (name, version, build commit, runtime, capability counts)`,execute:()=>Promise.resolve(JSON.stringify(e(),null,2)),name:t}),J=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)}}),Y={flush:()=>Promise.resolve(),recordEvent:()=>{}},re=(e,t)=>!t||t.length===0?e:Object.fromEntries(Object.entries(e).map(([e,n])=>[e,t.includes(e)?`[REDACTED]`:n])),ie=(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},X=e=>a.fromPromise(e),Z=(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`?re(r,n?.redactInputFields):void 0;return(await X(e.execute(r,i))).fold(n=>{let r=W(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`?ie(r,n?.maxOutputSize):void 0,requestId:s,sessionId:c,timestamp:o,type:`tool.execute`}),r))}}),ae=(e,t)=>({...e,load:async n=>{let r=Date.now();return(await X(e.load(n))).fold(n=>{let i=W(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))}}),oe=(e,t)=>({...e,load:async(n,r)=>{let i=Date.now();return(await X(e.load(n,r))).fold(n=>{let r=W(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))}}),se=e=>{let{artifacts:t,backend:i,backendOptions:a,build:o,enableDashboard:s,enableHealthEndpoint:c,enableInfoEndpoint:l,enableIntrospection:u,gateways:d,healthDetailPath:f,healthPath:m,infoPath:h,introspectionPrefix:g,logLayer:_,telemetry:v,...y}=e,b=y.name,x=y.version,S=L(o),ne=z(),C=g??``,w=v??(_?J(_):Y),T=i(y,a),O=V(w),A=[],j=[],M=[],N=r(0);d?.forEach(e=>O.add(e));let P=()=>{let e=N.get(),t=e>0?Date.now()-e:0;return{activeSessions:T.sessions.length,gateways:{connected:O.connectedCount,total:O.totalCount},name:b,startedAt:e,status:T.serverState,uptime:t}},F=()=>({prompts:M,resources:j,tools:A}),I=()=>({build:S,capabilities:{prompts:M.length,resources:j.length,tools:A.length},name:b,runtime:ne,version:x}),R=e=>{A.some(t=>t.name===e.name)||A.push({description:e.description,name:e.name})},B=(e,t)=>{if(T.supportsRemoval===!1)return;let n=e.findIndex(e=>e.name===t);n>=0&&e.splice(n,1)};if(u!==!1){let e=q(()=>I(),`${C}info`);T.addTool(e),R(e)}let H=[...t??[]];c!==!1&&(H.push(E(()=>P(),m)),H.push(D(()=>P(),f))),l!==!1&&H.push(k(()=>I(),h)),s!==!1&&H.push(te(()=>P(),()=>F(),()=>O.getInfoAll())),H.length>0&&ee(T.getApp(),H,n(y.authenticate).orUndefined()),T.on(`connect`,({session:e})=>{w.recordEvent({data:{sessionId:e.sessionId},name:b,timestamp:Date.now(),type:`session.connect`})}),T.on(`disconnect`,({session:e})=>{w.recordEvent({data:{sessionId:e.sessionId},name:b,timestamp:Date.now(),type:`session.disconnect`})});let U=e=>{let t=`captureConfig`in e?e.captureConfig:void 0,n=Z(e,w,t);T.addTool(n),R(e)},W=e=>{let t=ae(e,w);T.addResource(t),j.push({description:e.description,name:e.name,uri:e.uri})},G=e=>{let t=oe(e,w);T.addPrompt(t),M.push({description:e.description,name:e.name})};return{get name(){return b},get serverState(){return T.serverState},get sessions(){return T.sessions},addPrompt:G,addPrompts:e=>{e.forEach(G)},addResource:W,addResources:e=>{e.forEach(W)},addResourceTemplate:(...e)=>{T.addResourceTemplate(...e)},addRoute:e=>{let t=T.getApp(),r=Array.isArray(e.method)?[...e.method]:[e.method];if(e.protected){let r=n(y.authenticate).orUndefined();t.use(e.path,p({authenticate:r,onUnauthorized:e.onUnauthorized}))}t.on(r,e.path,t=>e.handler(t))},addTool:U,addTools:e=>{e.forEach(e=>U(e))},fetch:async e=>T.fetch?T.fetch(e):T.getApp().fetch(e),getApp:()=>T.getApp(),getCapabilities:F,getGatewayManager:()=>O,getHealth:P,getInfo:I,removePrompt:e=>{T.removePrompt(e),B(M,e)},removeResource:e=>{T.removeResource(e),B(j,e)},removeTool:e=>{T.removeTool(e),B(A,e)},async start(e){N.set(Date.now()),w.recordEvent({name:b,timestamp:N.get(),type:`server.start`}),await T.start(e),await O.connectAll(),O.getAll().filter(e=>e.status===`connected`&&e.config.proxyTools!==!1).forEach(e=>{K(e,w).forEach(e=>{T.addTool(e),R(e)})})},async stop(){await O.disconnectAll(),await T.stop(),w.recordEvent({name:b,timestamp:Date.now(),type:`server.stop`}),w.flush&&await w.flush()}}};var ce=class extends Error{constructor(e){super(e),this.name=`UserError`}};const le=e=>({flush:async()=>{await Promise.all(e.map(e=>e.flush?e.flush():Promise.resolve()))},recordEvent:t=>{e.forEach(e=>e.recordEvent(t))}}),ue=(e=`[soma]`)=>{let t=u({prefix:e,stream:`stderr`});return J(t)},de=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`}),fe=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`}),pe=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`}),Q=[{description:`GitHub personal access token`,name:`github_pat`,pattern:/\b(?:ghp|gho|ghu|ghs|ghr|github_pat)_[A-Za-z0-9_]{20,}\b/g},{description:`AWS access key ID`,name:`aws_access_key`,pattern:/\bAKIA[0-9A-Z]{16}\b/g},{description:`AWS secret access key (heuristic)`,name:`aws_secret_key`,pattern:/(?<![A-Za-z0-9/+])[A-Za-z0-9/+]{40}(?![A-Za-z0-9/+])/g},{description:`Stripe secret/publishable key`,name:`stripe_key`,pattern:/\b(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9]{20,}\b/g},{description:`Slack token`,name:`slack_token`,pattern:/\bxox[abprs]-[A-Za-z0-9-]{10,}\b/g},{description:`JSON Web Token`,name:`jwt`,pattern:/\beyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]+\b/g},{description:`OpenAI / Anthropic style API key`,name:`ai_api_key`,pattern:/\b(?:sk-ant-|sk-)[A-Za-z0-9_-]{20,}\b/g},{description:`Generic bearer-style secret prefix`,name:`bearer_authorization`,pattern:/\b[Bb]earer\s+[A-Za-z0-9._\-+/]{16,}\b/g},{description:`Email address`,name:`email`,pattern:/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g},{description:`Private IPv4 address (RFC1918)`,name:`private_ipv4`,pattern:/\b(?:10(?:\.\d{1,3}){3}|192\.168(?:\.\d{1,3}){2}|172\.(?:1[6-9]|2\d|3[01])(?:\.\d{1,3}){2})\b/g},{description:`URL pointing at internal/private hostname`,name:`internal_url`,pattern:/\bhttps?:\/\/(?:[a-z0-9-]+\.)*(?:internal|intranet|corp|local|lan)(?:[/:][^\s]*)?/gi}],$=(e,n=Q)=>{let{counts:r,text:i}=n.reduce((e,t)=>{let n=Array.from(e.text.matchAll(t.pattern)).length;return n===0?e:{counts:e.counts.add(o([t.name,n])),text:e.text.replace(t.pattern,t.replacement??`[REDACTED]`)}},{counts:t.empty(),text:e}),a=[...r].map(([e,t])=>({count:t,name:e}));return{matches:a,redacted:a.length>0,text:i}},me=[`Submit a feedback report or issue to this server's maintainers.`,``,`IMPORTANT: Treat all input as potentially public.`,`- Do NOT include API keys, tokens, passwords, or credentials`,`- Do NOT include customer data, PII, or proprietary information`,`- Do NOT include internal URLs, hostnames, or system topology`,`- Describe the behavior and how to reproduce it, not the data involved`,``,`Common credential patterns are automatically redacted before submission,`,`but redaction is best-effort and is not a substitute for caution.`].join(`
57
+ </html>`,C=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`},w=`/health`,T=`/health/detail`,E=(e,t=w)=>({handler:()=>{let t=e(),n=t.status===`running`?200:503;return new Response(JSON.stringify({name:t.name,status:t.status}),{headers:{"Content-Type":`application/json`},status:n})},path:t,type:`dynamic`}),D=(e,t=T)=>({handler:()=>{let t=e(),n=t.status===`running`?200:503;return new Response(JSON.stringify(t),{headers:{"Content-Type":`application/json`},status:n})},path:t,protected:!0,type:`dynamic`}),O=`/info`,k=(e,t=O)=>({handler:()=>new Response(JSON.stringify(e()),{headers:{"Content-Type":`application/json`},status:200}),path:t,protected:!0,type:`dynamic`}),A=()=>n(globalThis.process),j=e=>n(globalThis[e]),M=()=>j(`navigator`).flatMap(e=>n(e.userAgent)),N=()=>M().contains(`Cloudflare-Workers`)||j(`Deno`).isEmpty===!1||j(`EdgeRuntime`).isEmpty===!1,P=()=>!N()&&A().flatMap(e=>n(e.versions?.node)).isEmpty===!1,F=e=>A().flatMap(t=>n(t.env?.[e])).filter(e=>e.length>0),I=()=>({branch:F(`SOMAMCP_BUILD_BRANCH`).orUndefined(),commit:F(`SOMAMCP_BUILD_COMMIT`).orUndefined(),date:F(`SOMAMCP_BUILD_DATE`).orUndefined(),environment:F(`SOMAMCP_ENVIRONMENT`).orUndefined()}),L=e=>{let t=I();return{branch:e?.branch??t.branch,commit:e?.commit??t.commit,date:e?.date??t.date,environment:e?.environment??t.environment}},R=`unknown`,z=()=>P()?{arch:A().flatMap(e=>n(e.arch)).orElse(R),nodeVersion:A().flatMap(e=>n(e.version)).orElse(R),platform:A().flatMap(e=>n(e.platform)).orElse(R),runtime:`node`}:{arch:R,nodeVersion:R,platform:M().orElse(R),runtime:`edge`},B=(e,t)=>{let i=r(n.none()),o=r(n.none()),s=r([]),u=r(`disconnected`),d=r(n.none()),f=e.name??e.id,p=()=>{let t=e.reconnectIntervalMs??5e3;d.set(n(setTimeout(()=>{m.connect()},t)))},m={get config(){return e},get info(){return{id:e.id,name:f,remoteTools:s.get().map(e=>({description:e.description,name:e.name})),status:u.get(),url:e.url}},get name(){return f},get status(){return u.get()},get tools(){return s.get()},async callTool(t,n={}){let r=i.get();if(r.isEmpty||u.get()!==`connected`)throw Error(`Gateway ${e.id} is not connected`);return r.orThrow(/* @__PURE__ */ Error(`Gateway ${e.id} is not connected`)).callTool({arguments:n,name:t})},async connect(){if(u.get()===`connected`)return;u.set(`connecting`);let r=Date.now();(await a.fromPromise((async()=>{let t=new l(new URL(e.url));o.set(n(t));let r=new c({name:`soma-gateway-${e.id}`,version:`1.0.0`});i.set(n(r)),await r.connect(t);let a=await r.listTools();s.set(a.tools)})())).fold(n=>{u.set(`error`),t.recordEvent({data:{id:e.id,url:e.url},error:n instanceof Error?n.message:String(n),name:f,timestamp:r,type:`gateway.error`}),e.reconnect!==!1&&p()},()=>{u.set(`connected`),t.recordEvent({data:{id:e.id,remoteToolCount:s.get().length,url:e.url},durationMs:Date.now()-r,name:f,timestamp:r,type:`gateway.connect`})})},async disconnect(){d.get().match({None:()=>void 0,Some:e=>clearTimeout(e)}),d.set(n.none());let r=i.get();r.isSome()&&await a.fromPromise(r.value.close()),i.set(n.none()),o.set(n.none()),s.set([]),u.set(`disconnected`),t.recordEvent({data:{id:e.id},name:f,timestamp:Date.now(),type:`gateway.disconnect`})}};return m},V=e=>{let n=r(t.empty()),i=()=>[...n.get()].map(([,e])=>e);return{get connectedCount(){return i().filter(e=>e.status===`connected`).length},get totalCount(){return n.get().size},add(t){let r=B(t,e);return n.set(n.get().add(o([t.id,r]))),r},async connectAll(){await Promise.allSettled(i().map(e=>e.connect()))},async disconnectAll(){await Promise.allSettled(i().map(e=>e.disconnect()))},get(e){return n.get().get(e)},getAll(){return i()},getInfoAll(){return i().map(e=>e.info)}}},H=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`},U=(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`]}},W=(e,t)=>{let n=H(e),r=e instanceof Error?e.message:String(e),i=U(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}},G=e=>a.fromPromise(e),K=(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 G(e.callTool(r.name,i))).fold(n=>{let s=W(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}`}))},q=(e,t=`info`)=>({annotations:{readOnlyHint:!0},description:`Returns identity and build information for this server (name, version, build commit, runtime, capability counts)`,execute:()=>Promise.resolve(JSON.stringify(e(),null,2)),name:t}),J=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)}}),Y={flush:()=>Promise.resolve(),recordEvent:()=>{}},re=(e,t)=>!t||t.length===0?e:Object.fromEntries(Object.entries(e).map(([e,n])=>[e,t.includes(e)?`[REDACTED]`:n])),ie=(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},X=e=>a.fromPromise(e),Z=(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`?re(r,n?.redactInputFields):void 0;return(await X(e.execute(r,i))).fold(n=>{let r=W(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`?ie(r,n?.maxOutputSize):void 0,requestId:s,sessionId:c,timestamp:o,type:`tool.execute`}),r))}}),ae=(e,t)=>({...e,load:async n=>{let r=Date.now();return(await X(e.load(n))).fold(n=>{let i=W(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))}}),oe=(e,t)=>({...e,load:async(n,r)=>{let i=Date.now();return(await X(e.load(n,r))).fold(n=>{let r=W(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))}}),se=e=>{let{artifacts:t,backend:i,backendOptions:a,build:o,enableDashboard:s,enableHealthEndpoint:c,enableInfoEndpoint:l,enableIntrospection:u,gateways:d,healthDetailPath:f,healthPath:m,infoPath:h,introspectionPrefix:g,logLayer:_,telemetry:v,...y}=e,b=y.name,x=y.version,S=L(o),ne=z(),C=g??``,w=v??(_?J(_):Y),T=i(y,a),O=V(w),A=[],j=[],M=[],N=r(0);d?.forEach(e=>O.add(e));let P=()=>{let e=N.get(),t=e>0?Date.now()-e:0;return{activeSessions:T.sessions.length,gateways:{connected:O.connectedCount,total:O.totalCount},name:b,startedAt:e,status:T.serverState,uptime:t}},F=()=>({prompts:M,resources:j,tools:A}),I=()=>({build:S,capabilities:{prompts:M.length,resources:j.length,tools:A.length},name:b,runtime:ne,version:x}),R=e=>{A.some(t=>t.name===e.name)||A.push({description:e.description,name:e.name})},B=(e,t)=>{if(T.supportsRemoval===!1)return;let n=e.findIndex(e=>e.name===t);n>=0&&e.splice(n,1)};if(u!==!1){let e=q(()=>I(),`${C}info`);T.addTool(e),R(e)}let H=[...t??[]];c!==!1&&(H.push(E(()=>P(),m)),H.push(D(()=>P(),f))),l!==!1&&H.push(k(()=>I(),h)),s!==!1&&H.push(te(()=>P(),()=>F(),()=>O.getInfoAll())),H.length>0&&ee(T.getApp(),H,n(y.authenticate).orUndefined()),T.on(`connect`,({session:e})=>{w.recordEvent({data:{sessionId:e.sessionId},name:b,timestamp:Date.now(),type:`session.connect`})}),T.on(`disconnect`,({session:e})=>{w.recordEvent({data:{sessionId:e.sessionId},name:b,timestamp:Date.now(),type:`session.disconnect`})});let U=e=>{let t=`captureConfig`in e?e.captureConfig:void 0,n=Z(e,w,t);T.addTool(n),R(e)},W=e=>{let t=ae(e,w);T.addResource(t),j.push({description:e.description,name:e.name,uri:e.uri})},G=e=>{let t=oe(e,w);T.addPrompt(t),M.push({description:e.description,name:e.name})};return{get name(){return b},get serverState(){return T.serverState},get sessions(){return T.sessions},addPrompt:G,addPrompts:e=>{e.forEach(G)},addResource:W,addResources:e=>{e.forEach(W)},addResourceTemplate:(...e)=>{T.addResourceTemplate(...e)},addRoute:e=>{let t=T.getApp(),r=Array.isArray(e.method)?[...e.method]:[e.method];if(e.protected){let r=n(y.authenticate).orUndefined();t.use(e.path,p({authenticate:r,onUnauthorized:e.onUnauthorized}))}t.on(r,e.path,t=>e.handler(t))},addTool:U,addTools:e=>{e.forEach(e=>U(e))},fetch:async e=>T.fetch?T.fetch(e):T.getApp().fetch(e),getApp:()=>T.getApp(),getCapabilities:F,getGatewayManager:()=>O,getHealth:P,getInfo:I,removePrompt:e=>{T.removePrompt(e),B(M,e)},removeResource:e=>{T.removeResource(e),B(j,e)},removeTool:e=>{T.removeTool(e),B(A,e)},async start(e){N.set(Date.now()),w.recordEvent({name:b,timestamp:N.get(),type:`server.start`}),await T.start(e),await O.connectAll(),O.getAll().filter(e=>e.status===`connected`&&e.config.proxyTools!==!1).forEach(e=>{K(e,w).forEach(e=>{T.addTool(e),R(e)})})},async stop(){await O.disconnectAll(),await T.stop(),w.recordEvent({name:b,timestamp:Date.now(),type:`server.stop`}),w.flush&&await w.flush()}}};var ce=class extends Error{constructor(e){super(e),this.name=`UserError`}};const le=e=>({flush:async()=>{await Promise.all(e.map(e=>e.flush?e.flush():Promise.resolve()))},recordEvent:t=>{e.forEach(e=>e.recordEvent(t))}}),ue=(e=`[soma]`)=>{let t=u({prefix:e,stream:`stderr`});return J(t)},de=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`}),fe=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`}),pe=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`}),Q=[{description:`GitHub personal access token`,name:`github_pat`,pattern:/\b(?:ghp|gho|ghu|ghs|ghr|github_pat)_[A-Za-z0-9_]{20,}\b/g},{description:`AWS access key ID`,name:`aws_access_key`,pattern:/\bAKIA[0-9A-Z]{16}\b/g},{description:`AWS secret access key (heuristic)`,name:`aws_secret_key`,pattern:/(?<![A-Za-z0-9/+])[A-Za-z0-9/+]{40}(?![A-Za-z0-9/+])/g},{description:`Stripe secret/publishable key`,name:`stripe_key`,pattern:/\b(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9]{20,}\b/g},{description:`Slack token`,name:`slack_token`,pattern:/\bxox[abprs]-[A-Za-z0-9-]{10,}\b/g},{description:`JSON Web Token`,name:`jwt`,pattern:/\beyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]+\b/g},{description:`OpenAI / Anthropic style API key`,name:`ai_api_key`,pattern:/\b(?:sk-ant-|sk-)[A-Za-z0-9_-]{20,}\b/g},{description:`Generic bearer-style secret prefix`,name:`bearer_authorization`,pattern:/\b[Bb]earer\s+[A-Za-z0-9._\-+/]{16,}\b/g},{description:`Email address`,name:`email`,pattern:/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g},{description:`Private IPv4 address (RFC1918)`,name:`private_ipv4`,pattern:/\b(?:10(?:\.\d{1,3}){3}|192\.168(?:\.\d{1,3}){2}|172\.(?:1[6-9]|2\d|3[01])(?:\.\d{1,3}){2})\b/g},{description:`URL pointing at internal/private hostname`,name:`internal_url`,pattern:/\bhttps?:\/\/(?:[a-z0-9-]+\.)*(?:internal|intranet|corp|local|lan)(?:[/:][^\s]*)?/gi}],$=(e,n=Q)=>{let{counts:r,text:i}=n.reduce((e,t)=>{let n=Array.from(e.text.matchAll(t.pattern)).length;return n===0?e:{counts:e.counts.add(o([t.name,n])),text:e.text.replace(t.pattern,t.replacement??`[REDACTED]`)}},{counts:t.empty(),text:e}),a=[...r].map(([e,t])=>({count:t,name:e}));return{matches:a,redacted:a.length>0,text:i}},me=[`Submit a feedback report or issue to this server's maintainers.`,``,`IMPORTANT: Treat all input as potentially public.`,`- Do NOT include API keys, tokens, passwords, or credentials`,`- Do NOT include customer data, PII, or proprietary information`,`- Do NOT include internal URLs, hostnames, or system topology`,`- Describe the behavior and how to reproduce it, not the data involved`,``,`Common credential patterns are automatically redacted before submission,`,`but redaction is best-effort and is not a substitute for caution.`].join(`
58
58
  `),he=d.object({description:d.string().min(10,`Provide enough context to reproduce (>=10 chars)`).max(8e3).describe(`Detailed description. NO secrets, customer data, or internal URLs.`),severity:d.enum([`low`,`medium`,`high`]).optional().describe(`Severity hint (optional)`),title:d.string().min(5,`Title must be at least 5 characters`).max(200).describe(`Brief, public-safe summary`),type:d.enum([`bug`,`feature`,`feedback`,`question`]).default(`feedback`).describe(`What kind of report this is`)}),ge=(e,r)=>{if(!e.redacted&&!r.redacted)return n.none();let i=[...[...e.matches,...r.matches].reduce((e,t)=>e.add(o([t.name,e.get(t.name).orElse(0)+t.count])),t.empty())].map(([e,t])=>`- ${e}: ${String(t)}`);return n([`⚠️ Automatic redactions applied:`,...i].join(`
59
59
  `))},_e=e=>e.filter(e=>Object.keys(e).length>0).map(e=>[`---`,`**Context (auto-collected):**`,"```json",JSON.stringify(e,null,2),"```"].join(`
60
60
  `)),ve=(e,t,n,r,i)=>[`**Type:** ${t}`,...n.map(e=>`**Severity:** ${e}`).toArray(),``,e,...r.map(e=>[``,e]).toArray().flat(),...i.map(e=>[``,e]).toArray().flat()].join(`
61
- `),ye=e=>{let t=e.redactionPatterns??Q;return{annotations:{destructiveHint:!1,readOnlyHint:!1},description:e.description??me,execute:async r=>{let i=$(r.title,t),a=$(r.description,t),o=e.enrichment?n(await e.enrichment({severity:r.severity,type:r.type})):n.none(),s=ge(i,a),c=_e(o),l=ve(a.text,r.type,n(r.severity),s,c),u=[r.type,...r.severity?[`severity:${r.severity}`]:[],...e.extraLabels??[]],d=await e.provider.submit({body:l,labels:u,title:i.text});return d.success?JSON.stringify({id:d.id,provider:e.provider.name,redacted:s.isSome(),redactionDetails:[...i.matches,...a.matches],success:!0,url:d.url},null,2):JSON.stringify({error:d.error,redacted:s.isSome(),success:!1},null,2)},name:e.name??`report_feedback`,parameters:he}},be=e=>{let t=e.baseUrl??`https://api.github.com`,r=e.fetchImpl??fetch;return{name:`github`,submit:async o=>{let s=e.getToken();if(s.isEmpty)return{error:`No GitHub token configured`,success:!1};let c=s.orThrow(Error(`unreachable`)),l=[...e.defaultLabels??[],...o.labels],u=JSON.stringify({body:o.body,labels:i(l).toArray(),title:o.title});return(await a.fromPromise(r(`${t}/repos/${e.repo}/issues`,{body:u,headers:{Accept:`application/vnd.github+json`,Authorization:`Bearer ${c}`,"Content-Type":`application/json`,"X-GitHub-Api-Version":`2022-11-28`},method:`POST`}))).foldAsync(e=>({error:e instanceof Error?e.message:`Unknown error submitting to GitHub`,success:!1}),async e=>{let t=await e.json().catch(()=>({}));return e.ok?{id:n(t.number).fold(()=>void 0,e=>String(e)),success:!0,url:t.html_url}:{error:`GitHub API ${String(e.status)}: ${t.message??e.statusText}`,success:!1}})}}},xe=e=>{let t=e.fetchImpl??fetch,n=e.transform??(e=>e);return{name:`webhook`,submit:async r=>(await a.fromPromise(t(e.url,{body:JSON.stringify(n(r)),headers:{"Content-Type":`application/json`,...e.headers??{}},method:`POST`}))).foldAsync(e=>({error:e instanceof Error?e.message:`Unknown error posting to webhook`,success:!1}),async e=>{if(!e.ok){let t=await e.text().catch(()=>``);return{error:`Webhook ${String(e.status)}: ${t||e.statusText}`,success:!1}}return{success:!0}})}},Se=e=>se({...e,backend:e.backend??S});export{T as DEFAULT_HEALTH_DETAIL_PATH,w as DEFAULT_HEALTH_PATH,O as DEFAULT_INFO_PATH,Q as DEFAULT_REDACTION_PATTERNS,Y as NoopTelemetry,ce as UserError,H as classifyError,p as createAuthMiddleware,de as createCapabilitiesTool,le as createCompositeTelemetry,fe as createConnectionsTool,ue as createConsoleTelemetry,te as createDashboardArtifact,S as createEdgeBackend,W as createEnrichedError,ye as createFeedbackTool,B as createGateway,V as createGatewayManager,be as createGithubFeedback,E as createHealthArtifact,D as createHealthDetailArtifact,pe as createHealthTool,k as createInfoArtifact,q as createInfoTool,K as createProxiedTools,Se as createServer,se as createServerCore,xe as createWebhookFeedback,v as getRequestHeader,z as getRuntimeInfo,I as readBuildInfoFromEnv,$ as redact,ee as registerArtifacts,L as resolveBuildInfo,oe as wrapPrompt,ae as wrapResource,Z as wrapTool};
61
+ `),ye=e=>{let t=e.redactionPatterns??Q;return{annotations:{destructiveHint:!1,readOnlyHint:!1},description:e.description??me,execute:async r=>{let i=$(r.title,t),a=$(r.description,t),o=e.enrichment?n(await e.enrichment({severity:r.severity,type:r.type})):n.none(),s=ge(i,a),c=_e(o),l=ve(a.text,r.type,n(r.severity),s,c),u=[r.type,...r.severity?[`severity:${r.severity}`]:[],...e.extraLabels??[]],d=await e.provider.submit({body:l,labels:u,title:i.text});return d.success?JSON.stringify({id:d.id,provider:e.provider.name,redacted:s.isSome(),redactionDetails:[...i.matches,...a.matches],success:!0,url:d.url},null,2):JSON.stringify({error:d.error,redacted:s.isSome(),success:!1},null,2)},name:e.name??`report_feedback`,parameters:he}},be=e=>{let t=e.baseUrl??`https://api.github.com`,r=e.fetchImpl??fetch;return{name:`github`,submit:async o=>{let s=e.getToken();if(s.isEmpty)return{error:`No GitHub token configured`,success:!1};let c=s.orThrow(/* @__PURE__ */ Error(`unreachable`)),l=[...e.defaultLabels??[],...o.labels],u=JSON.stringify({body:o.body,labels:i(l).toArray(),title:o.title});return(await a.fromPromise(r(`${t}/repos/${e.repo}/issues`,{body:u,headers:{Accept:`application/vnd.github+json`,Authorization:`Bearer ${c}`,"Content-Type":`application/json`,"X-GitHub-Api-Version":`2022-11-28`},method:`POST`}))).foldAsync(e=>({error:e instanceof Error?e.message:`Unknown error submitting to GitHub`,success:!1}),async e=>{let t=await e.json().catch(()=>({}));return e.ok?{id:n(t.number).fold(()=>void 0,e=>String(e)),success:!0,url:t.html_url}:{error:`GitHub API ${String(e.status)}: ${t.message??e.statusText}`,success:!1}})}}},xe=e=>{let t=e.fetchImpl??fetch,n=e.transform??(e=>e);return{name:`webhook`,submit:async r=>(await a.fromPromise(t(e.url,{body:JSON.stringify(n(r)),headers:{"Content-Type":`application/json`,...e.headers??{}},method:`POST`}))).foldAsync(e=>({error:e instanceof Error?e.message:`Unknown error posting to webhook`,success:!1}),async e=>{if(!e.ok){let t=await e.text().catch(()=>``);return{error:`Webhook ${String(e.status)}: ${t||e.statusText}`,success:!1}}return{success:!0}})}},Se=e=>se({...e,backend:e.backend??S});export{T as DEFAULT_HEALTH_DETAIL_PATH,w as DEFAULT_HEALTH_PATH,O as DEFAULT_INFO_PATH,Q as DEFAULT_REDACTION_PATTERNS,Y as NoopTelemetry,ce as UserError,H as classifyError,p as createAuthMiddleware,de as createCapabilitiesTool,le as createCompositeTelemetry,fe as createConnectionsTool,ue as createConsoleTelemetry,te as createDashboardArtifact,S as createEdgeBackend,W as createEnrichedError,ye as createFeedbackTool,B as createGateway,V as createGatewayManager,be as createGithubFeedback,E as createHealthArtifact,D as createHealthDetailArtifact,pe as createHealthTool,k as createInfoArtifact,q as createInfoTool,K as createProxiedTools,Se as createServer,se as createServerCore,xe as createWebhookFeedback,v as getRequestHeader,z as getRuntimeInfo,I as readBuildInfoFromEnv,$ as redact,ee as registerArtifacts,L as resolveBuildInfo,oe as wrapPrompt,ae as wrapResource,Z as wrapTool};
@@ -164,6 +164,37 @@ type ServerConfig<T extends SessionAuth = SessionAuth> = {
164
164
  instructions?: string;
165
165
  logger?: Logger;
166
166
  name: string;
167
+ /**
168
+ * Periodic writes onto an in-flight tool call's own response stream, so a proxy or load
169
+ * balancer does not close the connection as idle while a long-running tool produces no
170
+ * output — a large document read, say.
171
+ *
172
+ * This is the only keepalive that survives `httpStream.stateless`. A transport-level ping
173
+ * needs the standing server-to-client stream, and stateless has none; these travel on the
174
+ * request's own stream instead. If you set `stateless` and have anything with an idle
175
+ * timeout in front of you, you want this too.
176
+ *
177
+ * Set at construction rather than on `httpStream`, because the backend builds its server
178
+ * before `start()` ever sees the transport config.
179
+ *
180
+ * Honoured by the fastmcp backend. The edge backend ignores it — Workers and Deno Deploy
181
+ * terminate the connection themselves, so there is nothing for somamcp to keep alive.
182
+ */
183
+ streamKeepalive?: {
184
+ /** Whether to write keepalives. Opt-in. */
185
+ enabled?: boolean;
186
+ /**
187
+ * Gap between keepalives. Keep it comfortably under the shortest idle timeout on the
188
+ * path — an AWS ALB defaults to 60s. Defaults to 20s in the fastmcp backend.
189
+ */
190
+ intervalMs?: number;
191
+ /**
192
+ * Level reported on the keepalive notification. Defaults to `debug` in the fastmcp
193
+ * backend, which is usually what you want — these fire every interval, for the whole
194
+ * length of every long call.
195
+ */
196
+ logLevel?: "alert" | "critical" | "debug" | "emergency" | "error" | "info" | "notice" | "warning";
197
+ };
167
198
  version: `${number}.${number}.${number}`;
168
199
  };
169
200
  /**
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as ToolAnnotations, C as ResourceLink, D as SessionAuth, E as ServerStatus, O as TextContent, S as ResourceContent, T as SchemaParams, _ as Progress, a as BackendSession, b as PromptResult, c as ServerConfig, d as Completion, f as Content, g as InferSchemaOutput, h as ImageContent, i as BackendFactory, j as UserError, k as Tool, l as TransportConfig, m as Context, n as BackendAdapter, o as HttpStreamConfig, p as ContentResult, s as Logger, t as createFastMCPBackend, u as AudioContent, v as Prompt, w as ResourceResult, x as Resource, y as PromptArgument } from "./index-pl2m5zH7.js";
1
+ import { A as ToolAnnotations, C as ResourceLink, D as SessionAuth, E as ServerStatus, O as TextContent, S as ResourceContent, T as SchemaParams, _ as Progress, a as BackendSession, b as PromptResult, c as ServerConfig, d as Completion, f as Content, g as InferSchemaOutput, h as ImageContent, i as BackendFactory, j as UserError, k as Tool, l as TransportConfig, m as Context, n as BackendAdapter, o as HttpStreamConfig, p as ContentResult, s as Logger, t as createFastMCPBackend, u as AudioContent, v as Prompt, w as ResourceResult, x as Resource, y as PromptArgument } from "./index-CSy9en2A.js";
2
2
  import { Option } from "functype";
3
3
  import { DirectLogger, DirectLogger as DirectLogger$1 } from "functype-log";
4
4
  import { z } from "zod";
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import{t as e}from"./backend-CA7j4YCF.js";import{Map as t,Option as n,Ref as r,Set as i,Try as a,Tuple as o}from"functype";import{Client as s}from"@modelcontextprotocol/sdk/client/index.js";import{StreamableHTTPClientTransport as c}from"@modelcontextprotocol/sdk/client/streamableHttp.js";import{appendFile as l,mkdir as u,readFile as d}from"node:fs/promises";import{dirname as f,extname as p}from"node:path";import{createDirectConsoleLogger as m}from"functype-log";import{z as h}from"zod";const g=e=>e.json({error:`Unauthorized`},401),_=({authenticate:e,onUnauthorized:t})=>{let r=t??g;return async(t,i)=>{if(!e)return r(t);let o=n(t.env).flatMap(e=>n(e.incoming)).orElse(t.req.raw);if((await a.fromPromise(e(o))).isFailure())return r(t);await i()}},v=(e,t)=>typeof e.get==`function`?n(e.get(t)):n.none(),y=(e,t,r)=>{let i=e;return n(i[t]??i[r]).map(e=>Array.isArray(e)?e[0]??``:e)},b=(e,t,r)=>v(e,t).fold(()=>y(e,t,r),e=>n(e)),x=e=>n(e).flatMap(e=>n(e.headers)),S=(e,t)=>{let n=t.toLowerCase();return x(e).flatMap(e=>b(e,n,t)).orUndefined()},C=(e,t,n)=>{t.forEach(t=>{switch(t.protected&&e.use(t.path,_({authenticate:n})),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))}})},w=(e,t,n)=>({handler:()=>{let r=e(),i=t(),a=n(),o=T(r,i,a);return new Response(o,{headers:{"Content-Type":`text/html; charset=utf-8`}})},path:`/dashboard`,protected:!0,type:`dynamic`}),T=(e,t,n)=>`<!DOCTYPE html>
1
+ import{t as e}from"./backend-Dd0n_9Ai.js";import{Map as t,Option as n,Ref as r,Set as i,Try as a,Tuple as o}from"functype";import{Client as s}from"@modelcontextprotocol/sdk/client/index.js";import{StreamableHTTPClientTransport as c}from"@modelcontextprotocol/sdk/client/streamableHttp.js";import{appendFile as l,mkdir as u,readFile as d}from"node:fs/promises";import{dirname as f,extname as p}from"node:path";import{createDirectConsoleLogger as m}from"functype-log";import{z as h}from"zod";const g=e=>e.json({error:`Unauthorized`},401),_=({authenticate:e,onUnauthorized:t})=>{let r=t??g;return async(t,i)=>{if(!e)return r(t);let o=n(t.env).flatMap(e=>n(e.incoming)).orElse(t.req.raw);if((await a.fromPromise(e(o))).isFailure())return r(t);await i()}},v=(e,t)=>typeof e.get==`function`?n(e.get(t)):n.none(),y=(e,t,r)=>{let i=e;return n(i[t]??i[r]).map(e=>Array.isArray(e)?e[0]??``:e)},b=(e,t,r)=>v(e,t).fold(()=>y(e,t,r),e=>n(e)),x=e=>n(e).flatMap(e=>n(e.headers)),S=(e,t)=>{let n=t.toLowerCase();return x(e).flatMap(e=>b(e,n,t)).orUndefined()},C=(e,t,n)=>{t.forEach(t=>{switch(t.protected&&e.use(t.path,_({authenticate:n})),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))}})},w=(e,t,n)=>({handler:()=>{let r=e(),i=t(),a=n(),o=T(r,i,a);return new Response(o,{headers:{"Content-Type":`text/html; charset=utf-8`}})},path:`/dashboard`,protected:!0,type:`dynamic`}),T=(e,t,n)=>`<!DOCTYPE html>
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8">
@@ -54,9 +54,9 @@ import{t as e}from"./backend-CA7j4YCF.js";import{Map as t,Option as n,Ref as r,S
54
54
  </div>`:``}
55
55
  </div>
56
56
  </body>
57
- </html>`,E=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`},D=`/health`,O=`/health/detail`,ee=(e,t=D)=>({handler:()=>{let t=e(),n=t.status===`running`?200:503;return new Response(JSON.stringify({name:t.name,status:t.status}),{headers:{"Content-Type":`application/json`},status:n})},path:t,type:`dynamic`}),te=(e,t=O)=>({handler:()=>{let t=e(),n=t.status===`running`?200:503;return new Response(JSON.stringify(t),{headers:{"Content-Type":`application/json`},status:n})},path:t,protected:!0,type:`dynamic`}),k=`/info`,A=(e,t=k)=>({handler:()=>new Response(JSON.stringify(e()),{headers:{"Content-Type":`application/json`},status:200}),path:t,protected:!0,type:`dynamic`}),j=()=>n(globalThis.process),M=e=>n(globalThis[e]),N=()=>M(`navigator`).flatMap(e=>n(e.userAgent)),P=()=>N().contains(`Cloudflare-Workers`)||M(`Deno`).isEmpty===!1||M(`EdgeRuntime`).isEmpty===!1,F=()=>!P()&&j().flatMap(e=>n(e.versions?.node)).isEmpty===!1,I=e=>j().flatMap(t=>n(t.env?.[e])).filter(e=>e.length>0),L=()=>({branch:I(`SOMAMCP_BUILD_BRANCH`).orUndefined(),commit:I(`SOMAMCP_BUILD_COMMIT`).orUndefined(),date:I(`SOMAMCP_BUILD_DATE`).orUndefined(),environment:I(`SOMAMCP_ENVIRONMENT`).orUndefined()}),ne=e=>{let t=L();return{branch:e?.branch??t.branch,commit:e?.commit??t.commit,date:e?.date??t.date,environment:e?.environment??t.environment}},R=`unknown`,z=()=>F()?{arch:j().flatMap(e=>n(e.arch)).orElse(R),nodeVersion:j().flatMap(e=>n(e.version)).orElse(R),platform:j().flatMap(e=>n(e.platform)).orElse(R),runtime:`node`}:{arch:R,nodeVersion:R,platform:N().orElse(R),runtime:`edge`},B=(e,t)=>{let i=r(n.none()),o=r(n.none()),l=r([]),u=r(`disconnected`),d=r(n.none()),f=e.name??e.id,p=()=>{let t=e.reconnectIntervalMs??5e3;d.set(n(setTimeout(()=>{m.connect()},t)))},m={get config(){return e},get info(){return{id:e.id,name:f,remoteTools:l.get().map(e=>({description:e.description,name:e.name})),status:u.get(),url:e.url}},get name(){return f},get status(){return u.get()},get tools(){return l.get()},async callTool(t,n={}){let r=i.get();if(r.isEmpty||u.get()!==`connected`)throw Error(`Gateway ${e.id} is not connected`);return r.orThrow(Error(`Gateway ${e.id} is not connected`)).callTool({arguments:n,name:t})},async connect(){if(u.get()===`connected`)return;u.set(`connecting`);let r=Date.now();(await a.fromPromise((async()=>{let t=new c(new URL(e.url));o.set(n(t));let r=new s({name:`soma-gateway-${e.id}`,version:`1.0.0`});i.set(n(r)),await r.connect(t);let a=await r.listTools();l.set(a.tools)})())).fold(n=>{u.set(`error`),t.recordEvent({data:{id:e.id,url:e.url},error:n instanceof Error?n.message:String(n),name:f,timestamp:r,type:`gateway.error`}),e.reconnect!==!1&&p()},()=>{u.set(`connected`),t.recordEvent({data:{id:e.id,remoteToolCount:l.get().length,url:e.url},durationMs:Date.now()-r,name:f,timestamp:r,type:`gateway.connect`})})},async disconnect(){d.get().match({None:()=>void 0,Some:e=>clearTimeout(e)}),d.set(n.none());let r=i.get();r.isSome()&&await a.fromPromise(r.value.close()),i.set(n.none()),o.set(n.none()),l.set([]),u.set(`disconnected`),t.recordEvent({data:{id:e.id},name:f,timestamp:Date.now(),type:`gateway.disconnect`})}};return m},V=e=>{let n=r(t.empty()),i=()=>[...n.get()].map(([,e])=>e);return{get connectedCount(){return i().filter(e=>e.status===`connected`).length},get totalCount(){return n.get().size},add(t){let r=B(t,e);return n.set(n.get().add(o([t.id,r]))),r},async connectAll(){await Promise.allSettled(i().map(e=>e.connect()))},async disconnectAll(){await Promise.allSettled(i().map(e=>e.disconnect()))},get(e){return n.get().get(e)},getAll(){return i()},getInfoAll(){return i().map(e=>e.info)}}},H=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`},U=(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`]}},W=(e,t)=>{let n=H(e),r=e instanceof Error?e.message:String(e),i=U(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}},G=e=>a.fromPromise(e),K=(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 G(e.callTool(r.name,i))).fold(n=>{let s=W(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}`}))},q=(e,t=`info`)=>({annotations:{readOnlyHint:!0},description:`Returns identity and build information for this server (name, version, build commit, runtime, capability counts)`,execute:()=>Promise.resolve(JSON.stringify(e(),null,2)),name:t}),J=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)}}),Y={flush:()=>Promise.resolve(),recordEvent:()=>{}},re=(e,t)=>!t||t.length===0?e:Object.fromEntries(Object.entries(e).map(([e,n])=>[e,t.includes(e)?`[REDACTED]`:n])),ie=(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},X=e=>a.fromPromise(e),Z=(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`?re(r,n?.redactInputFields):void 0;return(await X(e.execute(r,i))).fold(n=>{let r=W(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`?ie(r,n?.maxOutputSize):void 0,requestId:s,sessionId:c,timestamp:o,type:`tool.execute`}),r))}}),ae=(e,t)=>({...e,load:async n=>{let r=Date.now();return(await X(e.load(n))).fold(n=>{let i=W(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))}}),oe=(e,t)=>({...e,load:async(n,r)=>{let i=Date.now();return(await X(e.load(n,r))).fold(n=>{let r=W(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))}}),se=e=>{let{artifacts:t,backend:i,backendOptions:a,build:o,enableDashboard:s,enableHealthEndpoint:c,enableInfoEndpoint:l,enableIntrospection:u,gateways:d,healthDetailPath:f,healthPath:p,infoPath:m,introspectionPrefix:h,logLayer:g,telemetry:v,...y}=e,b=y.name,x=y.version,S=ne(o),T=z(),E=h??``,D=v??(g?J(g):Y),O=i(y,a),k=V(D),j=[],M=[],N=[],P=r(0);d?.forEach(e=>k.add(e));let F=()=>{let e=P.get(),t=e>0?Date.now()-e:0;return{activeSessions:O.sessions.length,gateways:{connected:k.connectedCount,total:k.totalCount},name:b,startedAt:e,status:O.serverState,uptime:t}},I=()=>({prompts:N,resources:M,tools:j}),L=()=>({build:S,capabilities:{prompts:N.length,resources:M.length,tools:j.length},name:b,runtime:T,version:x}),R=e=>{j.some(t=>t.name===e.name)||j.push({description:e.description,name:e.name})},B=(e,t)=>{if(O.supportsRemoval===!1)return;let n=e.findIndex(e=>e.name===t);n>=0&&e.splice(n,1)};if(u!==!1){let e=q(()=>L(),`${E}info`);O.addTool(e),R(e)}let H=[...t??[]];c!==!1&&(H.push(ee(()=>F(),p)),H.push(te(()=>F(),f))),l!==!1&&H.push(A(()=>L(),m)),s!==!1&&H.push(w(()=>F(),()=>I(),()=>k.getInfoAll())),H.length>0&&C(O.getApp(),H,n(y.authenticate).orUndefined()),O.on(`connect`,({session:e})=>{D.recordEvent({data:{sessionId:e.sessionId},name:b,timestamp:Date.now(),type:`session.connect`})}),O.on(`disconnect`,({session:e})=>{D.recordEvent({data:{sessionId:e.sessionId},name:b,timestamp:Date.now(),type:`session.disconnect`})});let U=e=>{let t=`captureConfig`in e?e.captureConfig:void 0,n=Z(e,D,t);O.addTool(n),R(e)},W=e=>{let t=ae(e,D);O.addResource(t),M.push({description:e.description,name:e.name,uri:e.uri})},G=e=>{let t=oe(e,D);O.addPrompt(t),N.push({description:e.description,name:e.name})};return{get name(){return b},get serverState(){return O.serverState},get sessions(){return O.sessions},addPrompt:G,addPrompts:e=>{e.forEach(G)},addResource:W,addResources:e=>{e.forEach(W)},addResourceTemplate:(...e)=>{O.addResourceTemplate(...e)},addRoute:e=>{let t=O.getApp(),r=Array.isArray(e.method)?[...e.method]:[e.method];if(e.protected){let r=n(y.authenticate).orUndefined();t.use(e.path,_({authenticate:r,onUnauthorized:e.onUnauthorized}))}t.on(r,e.path,t=>e.handler(t))},addTool:U,addTools:e=>{e.forEach(e=>U(e))},fetch:async e=>O.fetch?O.fetch(e):O.getApp().fetch(e),getApp:()=>O.getApp(),getCapabilities:I,getGatewayManager:()=>k,getHealth:F,getInfo:L,removePrompt:e=>{O.removePrompt(e),B(N,e)},removeResource:e=>{O.removeResource(e),B(M,e)},removeTool:e=>{O.removeTool(e),B(j,e)},async start(e){P.set(Date.now()),D.recordEvent({name:b,timestamp:P.get(),type:`server.start`}),await O.start(e),await k.connectAll(),k.getAll().filter(e=>e.status===`connected`&&e.config.proxyTools!==!1).forEach(e=>{K(e,D).forEach(e=>{O.addTool(e),R(e)})})},async stop(){await k.disconnectAll(),await O.stop(),D.recordEvent({name:b,timestamp:Date.now(),type:`server.stop`}),D.flush&&await D.flush()}}};var ce=class extends Error{constructor(e){super(e),this.name=`UserError`}};const le={".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`},ue={".aac":`audio/aac`,".flac":`audio/flac`,".m4a":`audio/mp4`,".mp3":`audio/mpeg`,".ogg":`audio/ogg`,".opus":`audio/opus`,".wav":`audio/wav`,".weba":`audio/webm`},de=(e,t,n)=>t[p(new URL(e,`file:///`).pathname).toLowerCase()]??n,fe=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 d(e.path),source:e.path}:{data:e.buffer}},pe=async e=>{let{data:t,source:n}=await fe(e,`image`),r=n?de(n,le,`image/png`):`image/png`;return{data:t.toString(`base64`),mimeType:r,type:`image`}},me=async e=>{let{data:t,source:n}=await fe(e,`audio`),r=n?de(n,ue,`audio/mpeg`):`audio/mpeg`;return{data:t.toString(`base64`),mimeType:r,type:`audio`}},he=e=>({flush:async()=>{await Promise.all(e.map(e=>e.flush?e.flush():Promise.resolve()))},recordEvent:t=>{e.forEach(e=>e.recordEvent(t))}}),ge=(e=`[soma]`)=>{let t=m({prefix:e,stream:`stderr`});return J(t)},_e=e=>{let t=r([]),i=r(n.none()),a=r(!1),o=async()=>{let n=t.getAndSet([]);if(n.length===0)return;a.get()||(await u(f(e.filePath),{recursive:!0}),a.set(!0));let r=`${n.map(e=>JSON.stringify(e)).join(`
57
+ </html>`,E=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`},D=`/health`,O=`/health/detail`,ee=(e,t=D)=>({handler:()=>{let t=e(),n=t.status===`running`?200:503;return new Response(JSON.stringify({name:t.name,status:t.status}),{headers:{"Content-Type":`application/json`},status:n})},path:t,type:`dynamic`}),te=(e,t=O)=>({handler:()=>{let t=e(),n=t.status===`running`?200:503;return new Response(JSON.stringify(t),{headers:{"Content-Type":`application/json`},status:n})},path:t,protected:!0,type:`dynamic`}),k=`/info`,A=(e,t=k)=>({handler:()=>new Response(JSON.stringify(e()),{headers:{"Content-Type":`application/json`},status:200}),path:t,protected:!0,type:`dynamic`}),j=()=>n(globalThis.process),M=e=>n(globalThis[e]),N=()=>M(`navigator`).flatMap(e=>n(e.userAgent)),P=()=>N().contains(`Cloudflare-Workers`)||M(`Deno`).isEmpty===!1||M(`EdgeRuntime`).isEmpty===!1,F=()=>!P()&&j().flatMap(e=>n(e.versions?.node)).isEmpty===!1,I=e=>j().flatMap(t=>n(t.env?.[e])).filter(e=>e.length>0),L=()=>({branch:I(`SOMAMCP_BUILD_BRANCH`).orUndefined(),commit:I(`SOMAMCP_BUILD_COMMIT`).orUndefined(),date:I(`SOMAMCP_BUILD_DATE`).orUndefined(),environment:I(`SOMAMCP_ENVIRONMENT`).orUndefined()}),ne=e=>{let t=L();return{branch:e?.branch??t.branch,commit:e?.commit??t.commit,date:e?.date??t.date,environment:e?.environment??t.environment}},R=`unknown`,z=()=>F()?{arch:j().flatMap(e=>n(e.arch)).orElse(R),nodeVersion:j().flatMap(e=>n(e.version)).orElse(R),platform:j().flatMap(e=>n(e.platform)).orElse(R),runtime:`node`}:{arch:R,nodeVersion:R,platform:N().orElse(R),runtime:`edge`},B=(e,t)=>{let i=r(n.none()),o=r(n.none()),l=r([]),u=r(`disconnected`),d=r(n.none()),f=e.name??e.id,p=()=>{let t=e.reconnectIntervalMs??5e3;d.set(n(setTimeout(()=>{m.connect()},t)))},m={get config(){return e},get info(){return{id:e.id,name:f,remoteTools:l.get().map(e=>({description:e.description,name:e.name})),status:u.get(),url:e.url}},get name(){return f},get status(){return u.get()},get tools(){return l.get()},async callTool(t,n={}){let r=i.get();if(r.isEmpty||u.get()!==`connected`)throw Error(`Gateway ${e.id} is not connected`);return r.orThrow(/* @__PURE__ */ Error(`Gateway ${e.id} is not connected`)).callTool({arguments:n,name:t})},async connect(){if(u.get()===`connected`)return;u.set(`connecting`);let r=Date.now();(await a.fromPromise((async()=>{let t=new c(new URL(e.url));o.set(n(t));let r=new s({name:`soma-gateway-${e.id}`,version:`1.0.0`});i.set(n(r)),await r.connect(t);let a=await r.listTools();l.set(a.tools)})())).fold(n=>{u.set(`error`),t.recordEvent({data:{id:e.id,url:e.url},error:n instanceof Error?n.message:String(n),name:f,timestamp:r,type:`gateway.error`}),e.reconnect!==!1&&p()},()=>{u.set(`connected`),t.recordEvent({data:{id:e.id,remoteToolCount:l.get().length,url:e.url},durationMs:Date.now()-r,name:f,timestamp:r,type:`gateway.connect`})})},async disconnect(){d.get().match({None:()=>void 0,Some:e=>clearTimeout(e)}),d.set(n.none());let r=i.get();r.isSome()&&await a.fromPromise(r.value.close()),i.set(n.none()),o.set(n.none()),l.set([]),u.set(`disconnected`),t.recordEvent({data:{id:e.id},name:f,timestamp:Date.now(),type:`gateway.disconnect`})}};return m},V=e=>{let n=r(t.empty()),i=()=>[...n.get()].map(([,e])=>e);return{get connectedCount(){return i().filter(e=>e.status===`connected`).length},get totalCount(){return n.get().size},add(t){let r=B(t,e);return n.set(n.get().add(o([t.id,r]))),r},async connectAll(){await Promise.allSettled(i().map(e=>e.connect()))},async disconnectAll(){await Promise.allSettled(i().map(e=>e.disconnect()))},get(e){return n.get().get(e)},getAll(){return i()},getInfoAll(){return i().map(e=>e.info)}}},H=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`},U=(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`]}},W=(e,t)=>{let n=H(e),r=e instanceof Error?e.message:String(e),i=U(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}},G=e=>a.fromPromise(e),K=(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 G(e.callTool(r.name,i))).fold(n=>{let s=W(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}`}))},q=(e,t=`info`)=>({annotations:{readOnlyHint:!0},description:`Returns identity and build information for this server (name, version, build commit, runtime, capability counts)`,execute:()=>Promise.resolve(JSON.stringify(e(),null,2)),name:t}),J=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)}}),Y={flush:()=>Promise.resolve(),recordEvent:()=>{}},re=(e,t)=>!t||t.length===0?e:Object.fromEntries(Object.entries(e).map(([e,n])=>[e,t.includes(e)?`[REDACTED]`:n])),ie=(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},X=e=>a.fromPromise(e),Z=(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`?re(r,n?.redactInputFields):void 0;return(await X(e.execute(r,i))).fold(n=>{let r=W(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`?ie(r,n?.maxOutputSize):void 0,requestId:s,sessionId:c,timestamp:o,type:`tool.execute`}),r))}}),ae=(e,t)=>({...e,load:async n=>{let r=Date.now();return(await X(e.load(n))).fold(n=>{let i=W(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))}}),oe=(e,t)=>({...e,load:async(n,r)=>{let i=Date.now();return(await X(e.load(n,r))).fold(n=>{let r=W(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))}}),se=e=>{let{artifacts:t,backend:i,backendOptions:a,build:o,enableDashboard:s,enableHealthEndpoint:c,enableInfoEndpoint:l,enableIntrospection:u,gateways:d,healthDetailPath:f,healthPath:p,infoPath:m,introspectionPrefix:h,logLayer:g,telemetry:v,...y}=e,b=y.name,x=y.version,S=ne(o),T=z(),E=h??``,D=v??(g?J(g):Y),O=i(y,a),k=V(D),j=[],M=[],N=[],P=r(0);d?.forEach(e=>k.add(e));let F=()=>{let e=P.get(),t=e>0?Date.now()-e:0;return{activeSessions:O.sessions.length,gateways:{connected:k.connectedCount,total:k.totalCount},name:b,startedAt:e,status:O.serverState,uptime:t}},I=()=>({prompts:N,resources:M,tools:j}),L=()=>({build:S,capabilities:{prompts:N.length,resources:M.length,tools:j.length},name:b,runtime:T,version:x}),R=e=>{j.some(t=>t.name===e.name)||j.push({description:e.description,name:e.name})},B=(e,t)=>{if(O.supportsRemoval===!1)return;let n=e.findIndex(e=>e.name===t);n>=0&&e.splice(n,1)};if(u!==!1){let e=q(()=>L(),`${E}info`);O.addTool(e),R(e)}let H=[...t??[]];c!==!1&&(H.push(ee(()=>F(),p)),H.push(te(()=>F(),f))),l!==!1&&H.push(A(()=>L(),m)),s!==!1&&H.push(w(()=>F(),()=>I(),()=>k.getInfoAll())),H.length>0&&C(O.getApp(),H,n(y.authenticate).orUndefined()),O.on(`connect`,({session:e})=>{D.recordEvent({data:{sessionId:e.sessionId},name:b,timestamp:Date.now(),type:`session.connect`})}),O.on(`disconnect`,({session:e})=>{D.recordEvent({data:{sessionId:e.sessionId},name:b,timestamp:Date.now(),type:`session.disconnect`})});let U=e=>{let t=`captureConfig`in e?e.captureConfig:void 0,n=Z(e,D,t);O.addTool(n),R(e)},W=e=>{let t=ae(e,D);O.addResource(t),M.push({description:e.description,name:e.name,uri:e.uri})},G=e=>{let t=oe(e,D);O.addPrompt(t),N.push({description:e.description,name:e.name})};return{get name(){return b},get serverState(){return O.serverState},get sessions(){return O.sessions},addPrompt:G,addPrompts:e=>{e.forEach(G)},addResource:W,addResources:e=>{e.forEach(W)},addResourceTemplate:(...e)=>{O.addResourceTemplate(...e)},addRoute:e=>{let t=O.getApp(),r=Array.isArray(e.method)?[...e.method]:[e.method];if(e.protected){let r=n(y.authenticate).orUndefined();t.use(e.path,_({authenticate:r,onUnauthorized:e.onUnauthorized}))}t.on(r,e.path,t=>e.handler(t))},addTool:U,addTools:e=>{e.forEach(e=>U(e))},fetch:async e=>O.fetch?O.fetch(e):O.getApp().fetch(e),getApp:()=>O.getApp(),getCapabilities:I,getGatewayManager:()=>k,getHealth:F,getInfo:L,removePrompt:e=>{O.removePrompt(e),B(N,e)},removeResource:e=>{O.removeResource(e),B(M,e)},removeTool:e=>{O.removeTool(e),B(j,e)},async start(e){P.set(Date.now()),D.recordEvent({name:b,timestamp:P.get(),type:`server.start`}),await O.start(e),await k.connectAll(),k.getAll().filter(e=>e.status===`connected`&&e.config.proxyTools!==!1).forEach(e=>{K(e,D).forEach(e=>{O.addTool(e),R(e)})})},async stop(){await k.disconnectAll(),await O.stop(),D.recordEvent({name:b,timestamp:Date.now(),type:`server.stop`}),D.flush&&await D.flush()}}};var ce=class extends Error{constructor(e){super(e),this.name=`UserError`}};const le={".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`},ue={".aac":`audio/aac`,".flac":`audio/flac`,".m4a":`audio/mp4`,".mp3":`audio/mpeg`,".ogg":`audio/ogg`,".opus":`audio/opus`,".wav":`audio/wav`,".weba":`audio/webm`},de=(e,t,n)=>t[p(new URL(e,`file:///`).pathname).toLowerCase()]??n,fe=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 d(e.path),source:e.path}:{data:e.buffer}},pe=async e=>{let{data:t,source:n}=await fe(e,`image`),r=n?de(n,le,`image/png`):`image/png`;return{data:t.toString(`base64`),mimeType:r,type:`image`}},me=async e=>{let{data:t,source:n}=await fe(e,`audio`),r=n?de(n,ue,`audio/mpeg`):`audio/mpeg`;return{data:t.toString(`base64`),mimeType:r,type:`audio`}},he=e=>({flush:async()=>{await Promise.all(e.map(e=>e.flush?e.flush():Promise.resolve()))},recordEvent:t=>{e.forEach(e=>e.recordEvent(t))}}),ge=(e=`[soma]`)=>{let t=m({prefix:e,stream:`stderr`});return J(t)},_e=e=>{let t=r([]),i=r(n.none()),a=r(!1),o=async()=>{let n=t.getAndSet([]);if(n.length===0)return;a.get()||(await u(f(e.filePath),{recursive:!0}),a.set(!0));let r=`${n.map(e=>JSON.stringify(e)).join(`
58
58
  `)}\n`;await l(e.filePath,r)},s=()=>{i.get().isSome()||i.set(n(setTimeout(()=>{i.set(n.none()),o()},e.flushIntervalMs??1e3)))};return{flush:async()=>{i.get().match({None:()=>void 0,Some:e=>clearTimeout(e)}),i.set(n.none()),await o()},recordEvent:e=>{t.update(t=>[...t,e]),s()}}},ve=e=>m({prefix:`[${e}]`,stream:`stderr`}).withContext({cell:e}),ye=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`}),be=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`}),xe=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`}),Q=[{description:`GitHub personal access token`,name:`github_pat`,pattern:/\b(?:ghp|gho|ghu|ghs|ghr|github_pat)_[A-Za-z0-9_]{20,}\b/g},{description:`AWS access key ID`,name:`aws_access_key`,pattern:/\bAKIA[0-9A-Z]{16}\b/g},{description:`AWS secret access key (heuristic)`,name:`aws_secret_key`,pattern:/(?<![A-Za-z0-9/+])[A-Za-z0-9/+]{40}(?![A-Za-z0-9/+])/g},{description:`Stripe secret/publishable key`,name:`stripe_key`,pattern:/\b(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9]{20,}\b/g},{description:`Slack token`,name:`slack_token`,pattern:/\bxox[abprs]-[A-Za-z0-9-]{10,}\b/g},{description:`JSON Web Token`,name:`jwt`,pattern:/\beyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]+\b/g},{description:`OpenAI / Anthropic style API key`,name:`ai_api_key`,pattern:/\b(?:sk-ant-|sk-)[A-Za-z0-9_-]{20,}\b/g},{description:`Generic bearer-style secret prefix`,name:`bearer_authorization`,pattern:/\b[Bb]earer\s+[A-Za-z0-9._\-+/]{16,}\b/g},{description:`Email address`,name:`email`,pattern:/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g},{description:`Private IPv4 address (RFC1918)`,name:`private_ipv4`,pattern:/\b(?:10(?:\.\d{1,3}){3}|192\.168(?:\.\d{1,3}){2}|172\.(?:1[6-9]|2\d|3[01])(?:\.\d{1,3}){2})\b/g},{description:`URL pointing at internal/private hostname`,name:`internal_url`,pattern:/\bhttps?:\/\/(?:[a-z0-9-]+\.)*(?:internal|intranet|corp|local|lan)(?:[/:][^\s]*)?/gi}],$=(e,n=Q)=>{let{counts:r,text:i}=n.reduce((e,t)=>{let n=Array.from(e.text.matchAll(t.pattern)).length;return n===0?e:{counts:e.counts.add(o([t.name,n])),text:e.text.replace(t.pattern,t.replacement??`[REDACTED]`)}},{counts:t.empty(),text:e}),a=[...r].map(([e,t])=>({count:t,name:e}));return{matches:a,redacted:a.length>0,text:i}},Se=[`Submit a feedback report or issue to this server's maintainers.`,``,`IMPORTANT: Treat all input as potentially public.`,`- Do NOT include API keys, tokens, passwords, or credentials`,`- Do NOT include customer data, PII, or proprietary information`,`- Do NOT include internal URLs, hostnames, or system topology`,`- Describe the behavior and how to reproduce it, not the data involved`,``,`Common credential patterns are automatically redacted before submission,`,`but redaction is best-effort and is not a substitute for caution.`].join(`
59
59
  `),Ce=h.object({description:h.string().min(10,`Provide enough context to reproduce (>=10 chars)`).max(8e3).describe(`Detailed description. NO secrets, customer data, or internal URLs.`),severity:h.enum([`low`,`medium`,`high`]).optional().describe(`Severity hint (optional)`),title:h.string().min(5,`Title must be at least 5 characters`).max(200).describe(`Brief, public-safe summary`),type:h.enum([`bug`,`feature`,`feedback`,`question`]).default(`feedback`).describe(`What kind of report this is`)}),we=(e,r)=>{if(!e.redacted&&!r.redacted)return n.none();let i=[...[...e.matches,...r.matches].reduce((e,t)=>e.add(o([t.name,e.get(t.name).orElse(0)+t.count])),t.empty())].map(([e,t])=>`- ${e}: ${String(t)}`);return n([`⚠️ Automatic redactions applied:`,...i].join(`
60
60
  `))},Te=e=>e.filter(e=>Object.keys(e).length>0).map(e=>[`---`,`**Context (auto-collected):**`,"```json",JSON.stringify(e,null,2),"```"].join(`
61
61
  `)),Ee=(e,t,n,r,i)=>[`**Type:** ${t}`,...n.map(e=>`**Severity:** ${e}`).toArray(),``,e,...r.map(e=>[``,e]).toArray().flat(),...i.map(e=>[``,e]).toArray().flat()].join(`
62
- `),De=e=>{let t=e.redactionPatterns??Q;return{annotations:{destructiveHint:!1,readOnlyHint:!1},description:e.description??Se,execute:async r=>{let i=$(r.title,t),a=$(r.description,t),o=e.enrichment?n(await e.enrichment({severity:r.severity,type:r.type})):n.none(),s=we(i,a),c=Te(o),l=Ee(a.text,r.type,n(r.severity),s,c),u=[r.type,...r.severity?[`severity:${r.severity}`]:[],...e.extraLabels??[]],d=await e.provider.submit({body:l,labels:u,title:i.text});return d.success?JSON.stringify({id:d.id,provider:e.provider.name,redacted:s.isSome(),redactionDetails:[...i.matches,...a.matches],success:!0,url:d.url},null,2):JSON.stringify({error:d.error,redacted:s.isSome(),success:!1},null,2)},name:e.name??`report_feedback`,parameters:Ce}},Oe=e=>{let t=e.baseUrl??`https://api.github.com`,r=e.fetchImpl??fetch;return{name:`github`,submit:async o=>{let s=e.getToken();if(s.isEmpty)return{error:`No GitHub token configured`,success:!1};let c=s.orThrow(Error(`unreachable`)),l=[...e.defaultLabels??[],...o.labels],u=JSON.stringify({body:o.body,labels:i(l).toArray(),title:o.title});return(await a.fromPromise(r(`${t}/repos/${e.repo}/issues`,{body:u,headers:{Accept:`application/vnd.github+json`,Authorization:`Bearer ${c}`,"Content-Type":`application/json`,"X-GitHub-Api-Version":`2022-11-28`},method:`POST`}))).foldAsync(e=>({error:e instanceof Error?e.message:`Unknown error submitting to GitHub`,success:!1}),async e=>{let t=await e.json().catch(()=>({}));return e.ok?{id:n(t.number).fold(()=>void 0,e=>String(e)),success:!0,url:t.html_url}:{error:`GitHub API ${String(e.status)}: ${t.message??e.statusText}`,success:!1}})}}},ke=e=>{let t=e.fetchImpl??fetch,n=e.transform??(e=>e);return{name:`webhook`,submit:async r=>(await a.fromPromise(t(e.url,{body:JSON.stringify(n(r)),headers:{"Content-Type":`application/json`,...e.headers??{}},method:`POST`}))).foldAsync(e=>({error:e instanceof Error?e.message:`Unknown error posting to webhook`,success:!1}),async e=>{if(!e.ok){let t=await e.text().catch(()=>``);return{error:`Webhook ${String(e.status)}: ${t||e.statusText}`,success:!1}}return{success:!0}})}},Ae=t=>se({...t,backend:t.backend??e});export{O as DEFAULT_HEALTH_DETAIL_PATH,D as DEFAULT_HEALTH_PATH,k as DEFAULT_INFO_PATH,Q as DEFAULT_REDACTION_PATTERNS,Y as NoopTelemetry,ce as UserError,me as audioContent,H as classifyError,_ as createAuthMiddleware,ye as createCapabilitiesTool,he as createCompositeTelemetry,be as createConnectionsTool,ge as createConsoleTelemetry,w as createDashboardArtifact,ve as createDefaultLogger,W as createEnrichedError,e as createFastMCPBackend,De as createFeedbackTool,B as createGateway,V as createGatewayManager,Oe as createGithubFeedback,ee as createHealthArtifact,te as createHealthDetailArtifact,xe as createHealthTool,A as createInfoArtifact,q as createInfoTool,_e as createJsonFileTelemetry,J as createLogLayerTelemetry,K as createProxiedTools,Ae as createServer,se as createServerCore,ke as createWebhookFeedback,S as getRequestHeader,z as getRuntimeInfo,pe as imageContent,L as readBuildInfoFromEnv,$ as redact,C as registerArtifacts,ne as resolveBuildInfo,oe as wrapPrompt,ae as wrapResource,Z as wrapTool};
62
+ `),De=e=>{let t=e.redactionPatterns??Q;return{annotations:{destructiveHint:!1,readOnlyHint:!1},description:e.description??Se,execute:async r=>{let i=$(r.title,t),a=$(r.description,t),o=e.enrichment?n(await e.enrichment({severity:r.severity,type:r.type})):n.none(),s=we(i,a),c=Te(o),l=Ee(a.text,r.type,n(r.severity),s,c),u=[r.type,...r.severity?[`severity:${r.severity}`]:[],...e.extraLabels??[]],d=await e.provider.submit({body:l,labels:u,title:i.text});return d.success?JSON.stringify({id:d.id,provider:e.provider.name,redacted:s.isSome(),redactionDetails:[...i.matches,...a.matches],success:!0,url:d.url},null,2):JSON.stringify({error:d.error,redacted:s.isSome(),success:!1},null,2)},name:e.name??`report_feedback`,parameters:Ce}},Oe=e=>{let t=e.baseUrl??`https://api.github.com`,r=e.fetchImpl??fetch;return{name:`github`,submit:async o=>{let s=e.getToken();if(s.isEmpty)return{error:`No GitHub token configured`,success:!1};let c=s.orThrow(/* @__PURE__ */ Error(`unreachable`)),l=[...e.defaultLabels??[],...o.labels],u=JSON.stringify({body:o.body,labels:i(l).toArray(),title:o.title});return(await a.fromPromise(r(`${t}/repos/${e.repo}/issues`,{body:u,headers:{Accept:`application/vnd.github+json`,Authorization:`Bearer ${c}`,"Content-Type":`application/json`,"X-GitHub-Api-Version":`2022-11-28`},method:`POST`}))).foldAsync(e=>({error:e instanceof Error?e.message:`Unknown error submitting to GitHub`,success:!1}),async e=>{let t=await e.json().catch(()=>({}));return e.ok?{id:n(t.number).fold(()=>void 0,e=>String(e)),success:!0,url:t.html_url}:{error:`GitHub API ${String(e.status)}: ${t.message??e.statusText}`,success:!1}})}}},ke=e=>{let t=e.fetchImpl??fetch,n=e.transform??(e=>e);return{name:`webhook`,submit:async r=>(await a.fromPromise(t(e.url,{body:JSON.stringify(n(r)),headers:{"Content-Type":`application/json`,...e.headers??{}},method:`POST`}))).foldAsync(e=>({error:e instanceof Error?e.message:`Unknown error posting to webhook`,success:!1}),async e=>{if(!e.ok){let t=await e.text().catch(()=>``);return{error:`Webhook ${String(e.status)}: ${t||e.statusText}`,success:!1}}return{success:!0}})}},Ae=t=>se({...t,backend:t.backend??e});export{O as DEFAULT_HEALTH_DETAIL_PATH,D as DEFAULT_HEALTH_PATH,k as DEFAULT_INFO_PATH,Q as DEFAULT_REDACTION_PATTERNS,Y as NoopTelemetry,ce as UserError,me as audioContent,H as classifyError,_ as createAuthMiddleware,ye as createCapabilitiesTool,he as createCompositeTelemetry,be as createConnectionsTool,ge as createConsoleTelemetry,w as createDashboardArtifact,ve as createDefaultLogger,W as createEnrichedError,e as createFastMCPBackend,De as createFeedbackTool,B as createGateway,V as createGatewayManager,Oe as createGithubFeedback,ee as createHealthArtifact,te as createHealthDetailArtifact,xe as createHealthTool,A as createInfoArtifact,q as createInfoTool,_e as createJsonFileTelemetry,J as createLogLayerTelemetry,K as createProxiedTools,Ae as createServer,se as createServerCore,ke as createWebhookFeedback,S as getRequestHeader,z as getRuntimeInfo,pe as imageContent,L as readBuildInfoFromEnv,$ as redact,C as registerArtifacts,ne as resolveBuildInfo,oe as wrapPrompt,ae as wrapResource,Z as wrapTool};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "somamcp",
3
- "version": "1.3.4",
3
+ "version": "1.4.1",
4
4
  "description": "Framework for building MCP servers with telemetry, introspection, and backend abstraction",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -59,19 +59,19 @@
59
59
  },
60
60
  "homepage": "https://github.com/sapientsai/SomaMCP#readme",
61
61
  "dependencies": {
62
- "@modelcontextprotocol/sdk": "^1.30.0",
63
- "fastmcp": "^4.20.0",
64
- "functype": "^1.9.0",
65
- "functype-log": "^1.9.0",
66
- "hono": "^4.13.5",
67
- "zod": "^4.5.4"
62
+ "@modelcontextprotocol/sdk": "^1.31.0",
63
+ "fastmcp": "^4.22.1",
64
+ "functype": "^1.12.0",
65
+ "functype-log": "^1.12.0",
66
+ "hono": "^4.13.12",
67
+ "zod": "^4.6.5"
68
68
  },
69
69
  "prettier": "ts-builds/prettier",
70
70
  "devDependencies": {
71
- "@types/node": "^24.13.3",
71
+ "@types/node": "^24.19.1",
72
72
  "get-port-please": "^3.2.0",
73
- "ts-builds": "^3.4.1",
73
+ "ts-builds": "^3.4.3",
74
74
  "tsdown": "^0.22.14"
75
75
  },
76
- "packageManager": "pnpm@11.25.0+sha512.5cde925b4f075f725eb71fbae18a42ffe784524789f19b61c731cb8721ec28aaee160e01a8d5af4fedb2a42cdbf300efe23db356b0d4a17b4d63e11f8ab7c956"
76
+ "packageManager": "pnpm@11.27.0+sha512.ad12821463bb143295f1fa3ecde77a6b9b97a7990386da800ff57bffc3964dc64b7b1c7559d5dbe5c337aea659ed9c8937d4370bd198ca2009bc6fa0249808e7"
77
77
  }
@@ -1 +0,0 @@
1
- import{FastMCP as e,ServerState as t}from"fastmcp";const n=e=>{switch(e){case t.Running:return`running`;case t.Error:return`error`;default:return`stopped`}},r=(t,r)=>{let i={...r,authenticate:t.authenticate,instructions:t.instructions,logger:t.logger,name:t.name,version:t.version},a=new e(i);return{get serverState(){return n(a.serverState)},get sessions(){return a.sessions.map(e=>({sessionId:e.sessionId}))},addPrompt:e=>{a.addPrompt(e)},addResource:e=>{a.addResource(e)},addResourceTemplate:(...e)=>{a.addResourceTemplate(...e)},addTool:e=>{a.addTool(e)},fetch:async e=>a.getApp().fetch(e),getApp:()=>a.getApp(),on:(e,t)=>{a.on(e,t)},removePrompt:e=>a.removePrompt(e),removeResource:e=>a.removeResource(e),removeTool:e=>a.removeTool(e),start:async e=>{if(!e){await a.start();return}e.transportType===`httpStream`?await a.start({httpStream:e.httpStream,transportType:`httpStream`}):await a.start({transportType:`stdio`})},stop:()=>a.stop()}};export{r as t};