somamcp 1.4.0 → 1.5.0

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
@@ -400,6 +400,39 @@ The ⚠️ rows are limits of `EdgeFastMCP`, which exposes no removal API, has n
400
400
 
401
401
  Bun deliberately reports `"node"`: it is a Node-family server runtime with full Node APIs, so that is the accurate answer even though `somamcp/edge` runs there.
402
402
 
403
+ ## MCP SDK 2.x Backend (opt-in)
404
+
405
+ `createSdkBackend` builds on the official MCP TypeScript SDK 2.x (`@modelcontextprotocol/server`). One endpoint serves both protocol revisions:
406
+
407
+ - **`2026-07-28`**, which current Claude clients request first. The default FastMCP 4.x backend answers that request with HTTP 400, and a client that doesn't retry with the older handshake ends up with no tools.
408
+ - **2025-era clients**, which open with `initialize`.
409
+
410
+ Pass it as `backend`. Nothing else changes:
411
+
412
+ ```typescript
413
+ import { createServer } from "somamcp"
414
+ import { createSdkBackend } from "somamcp/backend"
415
+
416
+ const server = createServer({ name: "my-server", version: "1.0.0", backend: createSdkBackend })
417
+
418
+ await server.start({ transportType: "httpStream", httpStream: { port: 3000 } })
419
+ // or: await server.start({ transportType: "stdio" })
420
+ ```
421
+
422
+ How it differs from the FastMCP backend:
423
+
424
+ - **Stateless only.** Every request gets a fresh SDK server built from somamcp's registry. There are no sessions, `connect`/`disconnect` events never fire, and a 2025 client's `GET` or `DELETE` is answered `405`. `httpStream.stateless: false` and `eventStore` are ignored, with a warning through `logger`.
425
+ - **`authenticate` receives a web `Request`**, not Node's `http.IncomingMessage`. Read headers with `getRequestHeader` and the same function works on both backends. Rejecting with a `Response` serves that response as-is. Any other rejection, a synchronous throw, or a nullish result (as on FastMCP) is a `401`. `canAccess` is only ever called with a real session: with none, gated tools are hidden when `authenticate` is configured and shown otherwise, as on FastMCP.
426
+ - **Errors follow SDK 2.x and the current spec.** Calling an unknown tool, or one hidden by `canAccess`, is a JSON-RPC `InvalidParams` error, as on FastMCP. Invalid arguments are an `isError` tool result, so the model can read the message and retry. FastMCP reports them as an `InvalidParams` protocol error.
427
+ - **Schemas must describe themselves as JSON Schema** (`~standard.jsonSchema`): zod 4, ArkType, or Valibot via `@valibot/to-json-schema`. Other schemas still validate, but clients see an empty argument list; `addTool` warns through `logger`.
428
+ - **`context.client.version`** is filled in for `2026-07-28` clients, which send their client info on every request. It is `undefined` for 2025 clients, which send it only during `initialize`.
429
+ - **`httpStream.host` defaults to `localhost`**, as on FastMCP. Pass `host: "0.0.0.0"` to listen on every interface.
430
+ - **`streamKeepalive`** maps to the SDK's SSE comment frames (`enabled` and `intervalMs`; the default is 20 s, as on FastMCP). FastMCP sends log notifications instead. With it on, every tool call is answered as an SSE stream, so a long call that produces no output still gets keepalives. `enableJsonResponse` turns them off; it applies to `2026-07-28` exchanges only, so a 2025 client's responses are unchanged by it.
431
+ - **`context.log` follows the client's log level.** A `2026-07-28` client receives tool logs only when its request asks for a log level (`io.modelcontextprotocol/logLevel`); otherwise the SDK drops them. 2025 clients receive them as before. Progress (`reportProgress`) is unaffected.
432
+ - **Health reports `running` from construction**, because the backend can answer `fetch()` before `start()`. FastMCP reports `stopped` until `start()`. Both report `stopped` after `stop()`.
433
+ - **Removal is real:** `removeTool`, `removeResource` and `removePrompt` apply from the next request.
434
+ - **`backendOptions`** takes `mcpPath` (the endpoint for `fetch()` use; `httpStream.endpoint` overrides it) and `stdioTransport` (a transport to use in place of the process's stdin/stdout). FastMCP's `ping`, `health`, `roots` and `oauth` options don't apply.
435
+
403
436
  ## Backend Abstraction
404
437
 
405
438
  The underlying MCP framework is accessed exclusively through the `BackendAdapter` interface. `createServer` accepts a `backend` factory — this is how `somamcp/edge` swaps in the edge adapter:
@@ -501,8 +534,8 @@ createServer({
501
534
 
502
535
  **Backend** (via `somamcp/backend`)
503
536
 
504
- - `createFastMCPBackend`
505
- - Types: `BackendAdapter`, `BackendFactory`, `BackendSession`
537
+ - `createFastMCPBackend`, `createSdkBackend`
538
+ - Types: `BackendAdapter`, `BackendFactory`, `BackendSession`, `SdkBackendOptions`
506
539
 
507
540
  ## Scripts
508
541
 
@@ -1,2 +1,2 @@
1
- import { a as BackendSession, i as BackendFactory, n as BackendAdapter, r as BackendEvents, t as createFastMCPBackend } from "../index-CSy9en2A.js";
2
- export { type BackendAdapter, type BackendEvents, type BackendFactory, type BackendSession, createFastMCPBackend };
1
+ import { a as BackendEvents, i as BackendAdapter, n as createSdkBackend, o as BackendFactory, r as createFastMCPBackend, s as BackendSession, t as SdkBackendOptions } from "../index-C2wa9nmp.js";
2
+ export { type BackendAdapter, type BackendEvents, type BackendFactory, type BackendSession, type SdkBackendOptions, createFastMCPBackend, createSdkBackend };
@@ -1 +1 @@
1
- import{t as e}from"../backend-Dd0n_9Ai.js";export{e as createFastMCPBackend};
1
+ export{n as createFastMCPBackend,t as createSdkBackend}from"../backend-D0qSyfXw.js";
@@ -0,0 +1 @@
1
+ import{FastMCP as e,ServerState as t}from"fastmcp";import{ProtocolError as n,ProtocolErrorCode as r,ResourceNotFoundError as i,Server as a,UriTemplate as o,createMcpHandler as s}from"@modelcontextprotocol/server";import{List as c,Map as l,Option as u,Ref as d,Try as f,Tuple as p}from"functype";import{Hono as m}from"hono";import{cors as h}from"hono/cors";const g=e=>{switch(e){case t.Running:return`running`;case t.Error:return`error`;default:return`stopped`}},_=(t,n)=>{let r={...n,authenticate:t.authenticate,instructions:t.instructions,logger:t.logger,name:t.name,...t.streamKeepalive?{streamKeepalive:t.streamKeepalive}:{},version:t.version},i=new e(r);return{get serverState(){return g(i.serverState)},get sessions(){return i.sessions.map(e=>({sessionId:e.sessionId}))},addPrompt:e=>{i.addPrompt(e)},addResource:e=>{i.addResource(e)},addResourceTemplate:(...e)=>{i.addResourceTemplate(...e)},addTool:e=>{i.addTool(e)},fetch:async e=>i.getApp().fetch(e),getApp:()=>i.getApp(),on:(e,t)=>{i.on(e,t)},removePrompt:e=>i.removePrompt(e),removeResource:e=>i.removeResource(e),removeTool:e=>i.removeTool(e),start:async e=>{e?e.transportType===`httpStream`?await i.start({httpStream:e.httpStream,transportType:`httpStream`}):await i.start({transportType:`stdio`}):await i.start()},stop:()=>i.stop()}},v={properties:{},type:`object`},y=e=>u(e[`~standard`].jsonSchema).isSome(),b=e=>e.flatMap(e=>u(e[`~standard`].jsonSchema)).map(e=>e.input({target:`draft-2020-12`})).orElse(v),x=e=>typeof e==`string`?{content:[{text:e,type:`text`}]}:e===void 0?{content:[]}:`content`in e?e:{content:[e]},S=e=>({content:[{text:e,type:`text`}],isError:!0}),C=(e,t)=>({contents:(Array.isArray(e)?e:[e]).map(e=>({mimeType:e.mimeType??t.mimeType,uri:e.uri??t.uri,...`blob`in e?{blob:e.blob}:{text:e.text}}))}),w=e=>typeof e==`string`?{messages:[{content:{text:e,type:`text`},role:`user`}]}:e,T={values:[]},E=e=>({completion:e.orElse(T)}),D=(e,t)=>{let n=e.filter(e=>e.startsWith(t));return{total:n.length,values:n}},O=(e,t,n)=>{let r=t=>e.mcpReq.notify(t).catch(()=>{}),i=t=>(n,r)=>{e.mcpReq.log(t,r===void 0?n:{data:r,message:n}).catch(()=>{})},a=u(e.mcpReq._meta?.progressToken);return{client:{version:u(e.mcpReq.envelope?.[`io.modelcontextprotocol/clientInfo`]).map(e=>e.version).orUndefined()},log:{debug:i(`debug`),error:i(`error`),info:i(`info`),warn:i(`warning`)},reportProgress:async({progress:e,total:t})=>{await a.fold(async()=>{},n=>r({method:`notifications/progress`,params:{progress:e,progressToken:n,total:t}}))},requestId:String(e.mcpReq.id),session:t.orUndefined(),sessionId:e.sessionId,streamContent:async e=>{await r({method:`notifications/tool/streamContent`,params:{content:Array.isArray(e)?e:[e],toolName:n}})}}},k=e=>c([...e].map(([,e])=>e)),A=(...e)=>e.reduce((e,t)=>e.fold(()=>t(),()=>e),u.none()),j=e=>e instanceof Error?e.message:String(e),M=e=>`Tool '${e.name}' timed out after ${e.timeoutMs}ms. Consider increasing timeoutMs or optimizing the tool implementation.`,N=(e,t)=>u(e.timeoutMs).fold(()=>t,n=>{let r=d(u.none()),i=new Promise((t,i)=>{r.set(u(setTimeout(()=>i(Error(M(e))),n)))});return Promise.race([t,i]).finally(()=>r.get().forEach(clearTimeout))}),P=(e,t)=>u(e.parameters).fold(()=>Promise.resolve({value:t}),e=>Promise.resolve(e[`~standard`].validate(t))),F=e=>Object.fromEntries(Object.entries(e).map(([e,t])=>[e,Array.isArray(t)?t.join(`,`):t])),I=(e,t,s)=>{let l=s.orUndefined(),d=e=>s.fold(()=>!(t.authRequired&&e.canAccess),t=>!e.canAccess||e.canAccess(t)),p=t=>e.tools().get(t).filter(d),m=new a({name:t.name,version:t.version},{capabilities:{completions:{},logging:{},prompts:{},resources:{},tools:{}},instructions:t.instructions});m.setRequestHandler(`tools/list`,()=>({tools:k(e.tools()).filter(d).map(e=>({...e._meta?{_meta:e._meta}:{},...e.annotations?{annotations:e.annotations}:{},description:e.description,inputSchema:b(u(e.parameters)),name:e.name,...e.outputSchema?{outputSchema:b(u(e.outputSchema))}:{},...e.annotations?.title?{title:e.annotations.title}:{}})).toArray()}));let h=async(e,t,n)=>{let r=await P(e,t);return`issues`in r?S(`Invalid arguments for tool '${e.name}': ${r.issues.map(e=>e.message).join(`; `)}`):(await f.fromPromise(N(e,e.execute(r.value,n)))).fold(e=>S(j(e)),e=>x(e))},g=e=>p(e).orThrow(()=>new n(r.InvalidParams,`Unknown tool: ${e}`));m.setRequestHandler(`tools/call`,(e,t)=>{let{arguments:n,name:r}=e.params;return h(g(r),n??{},O(t,s,r))}),m.setRequestHandler(`resources/list`,()=>({resources:k(e.resources()).map(e=>({description:e.description,mimeType:e.mimeType,name:e.name,uri:e.uri})).toArray()})),m.setRequestHandler(`resources/templates/list`,()=>({resourceTemplates:k(e.templates()).map(e=>({description:e.description,mimeType:e.mimeType,name:e.name,uriTemplate:e.uriTemplate})).toArray()}));let _=t=>A(()=>k(e.resources()).find(e=>e.uri===t).map(e=>({load:()=>e.load(l),mimeType:e.mimeType})),()=>k(e.templates()).map(e=>({template:e,variables:u(new o(e.uriTemplate).match(t))})).find(e=>e.variables.isSome()).map(({template:e,variables:t})=>({load:()=>e.load(F(t.orElse({})),l),mimeType:e.mimeType}))),v=async e=>{let t=_(e).orThrow(()=>new i(e));return C(await t.load(),{mimeType:t.mimeType,uri:e})};m.setRequestHandler(`resources/read`,e=>v(e.params.uri)),m.setRequestHandler(`prompts/list`,()=>({prompts:k(e.prompts()).map(e=>({arguments:e.arguments?.map(e=>({description:e.description,name:e.name,required:e.required})),description:e.description,name:e.name})).toArray()}));let y=async(t,i)=>{let a=e.prompts().get(t).orThrow(()=>new n(r.InvalidParams,`Unknown prompt: ${t}`)),o=c(a.arguments??[]).filter(e=>e.required===!0&&!(e.name in i));if(!o.isEmpty){let e=o.map(e=>e.name).toArray().join(`, `);throw new n(r.InvalidParams,`Prompt '${t}' is missing required arguments: ${e}`)}return w(await a.load(i,l))};m.setRequestHandler(`prompts/get`,e=>y(e.params.name,e.params.arguments??{}));let T=(t,n,r)=>{let i=e.prompts().get(t),a=i.flatMap(e=>c(e.arguments??[]).find(e=>e.name===n));return A(()=>a.flatMap(e=>u(e.complete)).map(e=>()=>e(r,l)),()=>a.flatMap(e=>u(e.enum)).map(e=>()=>Promise.resolve(D(e,r))),()=>i.flatMap(e=>u(e.complete)).map(e=>()=>e(n,r,l)))},M=(t,n,r)=>{let i=k(e.templates()).find(e=>e.uriTemplate===t),a=i.flatMap(e=>c(e.arguments).find(e=>e.name===n));return A(()=>a.flatMap(e=>u(e.complete)).map(e=>()=>e(r,l)),()=>i.flatMap(e=>u(e.complete)).map(e=>()=>e(n,r,l)))};return m.setRequestHandler(`completion/complete`,async e=>{let{argument:t,ref:n}=e.params,r=n.type===`ref/prompt`?T(n.name,t.name,t.value):M(n.uri,t.name,t.value);return E(await r.foldAsync(()=>Promise.resolve(u.none()),async e=>u(await e())))}),m},L=`somamcp/session`,R=(e,t)=>{let n=u(e.logger),r=e=>n.forEach(t=>t.warn(`[somamcp/sdk] ${e}`)),i=d(l.empty()),a=d(l.empty()),o=d(l.empty()),c=d(l.empty()),g={prompts:()=>c.get(),resources:()=>a.get(),templates:()=>o.get(),tools:()=>i.get()},_=(e,t,n)=>{e.set(e.get().add(p([t,n])))},v=(e,t)=>{e.set(e.get().remove(t))},b={authRequired:e.authenticate!==void 0,instructions:e.instructions,name:e.name,version:e.version},x=u(e.streamKeepalive).filter(e=>e.enabled===!0).map(e=>e.intervalMs??2e4),S=x.fold(()=>`auto`,()=>`sse`),C=e=>s(({authInfo:e})=>I(g,b,u(e).flatMap(e=>u(e.extra?.[L]))),{keepAliveMs:x.orUndefined(),onerror:e=>n.forEach(t=>t.error(`[somamcp/sdk]`,e)),responseMode:e}),w=d(S),T=d(C(w.get())),E=d(u(t).flatMap(e=>u(e.mcpPath)).orElse(`/mcp`)),D=d(u.none()),O=d(u.none()),k=d(`running`),A=(e,t)=>t instanceof Response?t:e.json({error:`Unauthorized`},401),j=(e,t)=>Promise.resolve().then(()=>e(t)).then(e=>u(e).fold(()=>Promise.reject(/* @__PURE__ */ Error(`Authentication required`)),e=>Promise.resolve(e))),M=async t=>{let n=t.req.raw;return e.authenticate?j(e.authenticate,n).then(e=>T.get().fetch(n,{authInfo:{clientId:``,extra:{[L]:e},scopes:[],token:``}}),e=>A(t,e)):T.get().fetch(n)},N=new m;N.use(`*`,async(e,t)=>e.req.path===E.get()?D.get().fold(()=>M(e),async t=>await t(e,async()=>{e.res=await M(e)})??e.res):t());let P=e=>{u(e.endpoint).forEach(e=>E.set(e)),u(e.cors).filter(e=>e!==!1).forEach(e=>D.set(u(h(e===!0?{}:e)))),e.enableJsonResponse===!0&&(x.isSome()&&r(`enableJsonResponse disables streamKeepalive: JSON responses cannot carry keepalive frames.`),w.set(`json`),T.set(C(`json`))),e.stateless===!1&&r(`stateful sessions are not supported by the SDK backend; serving statelessly.`),e.eventStore!==void 0&&r(`eventStore is not supported by the SDK backend; ignoring.`)},F=async()=>{let{listenStdio:r}=await import(`./listen-ZkXBwyaw.js`),i=e.authenticate?(await f.fromPromise(j(e.authenticate,void 0))).fold(e=>(n.forEach(t=>t.error(`[somamcp/sdk] authentication failed for stdio transport:`,e.message)),u.none()),e=>u(e)):u.none();return r(()=>I(g,b,i),u(t).flatMap(e=>u(e.stdioTransport)).orUndefined())},R=async e=>{let{listenHttp:t}=await import(`./listen-ZkXBwyaw.js`);return P(e),t(N,e)};return{get serverState(){return k.get()},get sessions(){return[]},addPrompt:e=>{_(c,e.name,e)},addResource:e=>{_(a,e.name,e)},addResourceTemplate:(...e)=>{let t=e[0];_(o,t.name,t)},addTool:e=>{e.parameters&&!y(e.parameters)&&r(`tool "${e.name}": its schema has no ~standard.jsonSchema, so clients see an empty argument list. Use zod 4, ArkType, or Valibot with @valibot/to-json-schema.`),_(i,e.name,e)},fetch:async e=>N.fetch(e),getApp:()=>N,on:()=>{},removePrompt:e=>{v(c,e)},removeResource:e=>{v(a,e)},removeTool:e=>{v(i,e)},start:async e=>{if(O.get().isSome()){r(`start() called while already listening; ignoring. Call stop() first to restart.`);return}let t=await f.fromPromise(e?.transportType===`httpStream`?R(e.httpStream):F());t.isFailure()&&k.set(`error`),O.set(u(t.orThrow())),k.set(`running`)},stop:async()=>{await T.get().close(),await O.get().fold(async()=>{},e=>e.close()),O.set(u.none()),T.set(C(w.get())),k.set(`stopped`)}}};export{_ as n,R as t};
package/dist/edge.d.ts CHANGED
@@ -156,6 +156,25 @@ type Resource<T extends SessionAuth = SessionAuth> = {
156
156
  name: string;
157
157
  uri: string;
158
158
  };
159
+ type ResourceTemplateArgument<T extends SessionAuth = SessionAuth> = Readonly<{
160
+ complete?: (value: string, auth?: T) => Promise<Completion>;
161
+ description?: string;
162
+ name: string;
163
+ required?: boolean;
164
+ }>;
165
+ /**
166
+ * A family of resources addressed by an RFC 6570 URI template, e.g. `docs://{path}`.
167
+ * `load` receives the variables matched from the requested URI.
168
+ */
169
+ type ResourceTemplate<T extends SessionAuth = SessionAuth> = {
170
+ arguments: ResourceTemplateArgument<T>[];
171
+ complete?: (name: string, value: string, auth?: T) => Promise<Completion>;
172
+ description?: string;
173
+ load: (args: Record<string, string>, auth?: T) => Promise<ResourceResult | ResourceResult[]>;
174
+ mimeType?: string;
175
+ name: string;
176
+ uriTemplate: string;
177
+ };
159
178
  type PromptArgument<T extends SessionAuth = SessionAuth> = Readonly<{
160
179
  complete?: (value: string, auth?: T) => Promise<Completion>;
161
180
  description?: string;
@@ -563,9 +582,6 @@ type EdgeBackendOptions = {
563
582
  * EdgeFastMCP, which serves the MCP endpoint. This ordering matters — EdgeFastMCP
564
583
  * registers its own `/health` at construction, and Hono is first-match-wins, so
565
584
  * mounting it first would shadow somamcp's health artifact.
566
- *
567
- * The prefer-either disable below is for the nested tool-execute throw, which
568
- * EdgeFastMCP requires; this factory itself never throws.
569
585
  */
570
586
  declare const createEdgeBackend: <T extends SessionAuth = SessionAuth>(config: ServerConfig<T>, backendOptions?: Record<string, unknown>) => BackendAdapter<T>;
571
587
  //#endregion
@@ -748,4 +764,4 @@ declare const createProxiedTools: <T extends SessionAuth>(gateway: GatewayInstan
748
764
  */
749
765
  declare const createServer: <T extends SessionAuth = SessionAuth>(options: SomaServerOptions<T>) => SomaServerInstance<T>;
750
766
  //#endregion
751
- export { type ArtifactAuthenticate, type ArtifactConfig, type AudioContent, type AuthMiddlewareConfig, type Authenticate, type BackendAdapter, type BackendFactory, type BackendSession, type BuildInfo, type CapabilitiesSummary, type CaptureLevel, type Completion, type Content, type ContentResult, type Context, DEFAULT_HEALTH_DETAIL_PATH, DEFAULT_HEALTH_PATH, DEFAULT_INFO_PATH, DEFAULT_REDACTION_PATTERNS, type DirectoryArtifact, type DynamicArtifact, type EdgeBackendOptions, type EnrichedErrorResponse, type ErrorCategory, type FeedbackEnrichmentContext, type FeedbackProvider, type FeedbackSeverity, type FeedbackSubmitFailure, type FeedbackSubmitResult, type FeedbackSubmitSuccess, type FeedbackToolOptions, type FeedbackType, type GatewayConfig, type GatewayInfo, type GatewayInstance, type GatewayManagerInstance, type GatewayStatus, type GithubFeedbackOptions, type HttpStreamConfig, type ImageContent, type InferSchemaOutput, type Logger, NoopTelemetry, type NormalizedFeedback, type OnUnauthorized, type Progress, type Prompt, type PromptArgument, type PromptResult, type RedactionPattern, type RedactionResult, type Resource, type ResourceContent, type ResourceLink, type ResourceResult, type RouteConfig, type RouteMethod, type RuntimeInfo, type SchemaParams, type ServerCapabilities, type ServerConfig, type ServerHealth, type ServerInfo, type ServerStatus, type SessionAuth, type SomaServerInstance, type SomaServerOptions, type StaticArtifact, type TelemetryCollector, type TelemetryEvent, type TelemetryEventType, type TextContent, type Tool, type ToolAnnotations, type ToolCaptureConfig, type ToolOptions, type TransportConfig, UserError, type WebhookFeedbackOptions, classifyError, createAuthMiddleware, createCapabilitiesTool, createCompositeTelemetry, createConnectionsTool, createConsoleTelemetry, createDashboardArtifact, createEdgeBackend, createEnrichedError, createFeedbackTool, createGateway, createGatewayManager, createGithubFeedback, createHealthArtifact, createHealthDetailArtifact, createHealthTool, createInfoArtifact, createInfoTool, createProxiedTools, createServer, createServerCore, createWebhookFeedback, getRequestHeader, getRuntimeInfo, readBuildInfoFromEnv, redact, registerArtifacts, resolveBuildInfo, wrapPrompt, wrapResource, wrapTool };
767
+ export { type ArtifactAuthenticate, type ArtifactConfig, type AudioContent, type AuthMiddlewareConfig, type Authenticate, type BackendAdapter, type BackendFactory, type BackendSession, type BuildInfo, type CapabilitiesSummary, type CaptureLevel, type Completion, type Content, type ContentResult, type Context, DEFAULT_HEALTH_DETAIL_PATH, DEFAULT_HEALTH_PATH, DEFAULT_INFO_PATH, DEFAULT_REDACTION_PATTERNS, type DirectoryArtifact, type DynamicArtifact, type EdgeBackendOptions, type EnrichedErrorResponse, type ErrorCategory, type FeedbackEnrichmentContext, type FeedbackProvider, type FeedbackSeverity, type FeedbackSubmitFailure, type FeedbackSubmitResult, type FeedbackSubmitSuccess, type FeedbackToolOptions, type FeedbackType, type GatewayConfig, type GatewayInfo, type GatewayInstance, type GatewayManagerInstance, type GatewayStatus, type GithubFeedbackOptions, type HttpStreamConfig, type ImageContent, type InferSchemaOutput, type Logger, NoopTelemetry, type NormalizedFeedback, type OnUnauthorized, type Progress, type Prompt, type PromptArgument, type PromptResult, type RedactionPattern, type RedactionResult, type Resource, type ResourceContent, type ResourceLink, type ResourceResult, type ResourceTemplate, type ResourceTemplateArgument, type RouteConfig, type RouteMethod, type RuntimeInfo, type SchemaParams, type ServerCapabilities, type ServerConfig, type ServerHealth, type ServerInfo, type ServerStatus, type SessionAuth, type SomaServerInstance, type SomaServerOptions, type StaticArtifact, type TelemetryCollector, type TelemetryEvent, type TelemetryEventType, type TextContent, type Tool, type ToolAnnotations, type ToolCaptureConfig, type ToolOptions, type TransportConfig, UserError, type WebhookFeedbackOptions, classifyError, createAuthMiddleware, createCapabilitiesTool, createCompositeTelemetry, createConnectionsTool, createConsoleTelemetry, createDashboardArtifact, createEdgeBackend, createEnrichedError, createFeedbackTool, createGateway, createGatewayManager, createGithubFeedback, createHealthArtifact, createHealthDetailArtifact, createHealthTool, createInfoArtifact, createInfoTool, createProxiedTools, createServer, createServerCore, createWebhookFeedback, getRequestHeader, getRuntimeInfo, readBuildInfoFromEnv, redact, registerArtifacts, resolveBuildInfo, wrapPrompt, wrapResource, wrapTool };
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};
@@ -1,3 +1,4 @@
1
+ import { Transport } from "@modelcontextprotocol/server";
1
2
  import { Hono } from "hono";
2
3
  //#region src/types/core.d.ts
3
4
  type SessionAuth = Record<string, unknown> | undefined;
@@ -129,6 +130,25 @@ type Resource<T extends SessionAuth = SessionAuth> = {
129
130
  name: string;
130
131
  uri: string;
131
132
  };
133
+ type ResourceTemplateArgument<T extends SessionAuth = SessionAuth> = Readonly<{
134
+ complete?: (value: string, auth?: T) => Promise<Completion>;
135
+ description?: string;
136
+ name: string;
137
+ required?: boolean;
138
+ }>;
139
+ /**
140
+ * A family of resources addressed by an RFC 6570 URI template, e.g. `docs://{path}`.
141
+ * `load` receives the variables matched from the requested URI.
142
+ */
143
+ type ResourceTemplate<T extends SessionAuth = SessionAuth> = {
144
+ arguments: ResourceTemplateArgument<T>[];
145
+ complete?: (name: string, value: string, auth?: T) => Promise<Completion>;
146
+ description?: string;
147
+ load: (args: Record<string, string>, auth?: T) => Promise<ResourceResult | ResourceResult[]>;
148
+ mimeType?: string;
149
+ name: string;
150
+ uriTemplate: string;
151
+ };
132
152
  type PromptArgument<T extends SessionAuth = SessionAuth> = Readonly<{
133
153
  complete?: (value: string, auth?: T) => Promise<Completion>;
134
154
  description?: string;
@@ -277,4 +297,28 @@ type BackendFactory<T extends SessionAuth = SessionAuth> = (config: ServerConfig
277
297
  //#region src/backend/fastmcp.d.ts
278
298
  declare const createFastMCPBackend: <T extends SessionAuth = SessionAuth>(config: ServerConfig<T>, backendOptions?: Record<string, unknown>) => BackendAdapter<T>;
279
299
  //#endregion
280
- export { ToolAnnotations as A, ResourceLink as C, SessionAuth as D, ServerStatus as E, TextContent as O, ResourceContent as S, SchemaParams as T, Progress as _, BackendSession as a, PromptResult as b, ServerConfig as c, Completion as d, Content as f, InferSchemaOutput as g, ImageContent as h, BackendFactory as i, UserError as j, Tool as k, TransportConfig as l, Context$1 as m, BackendAdapter as n, HttpStreamConfig as o, ContentResult as p, BackendEvents as r, Logger as s, createFastMCPBackend as t, AudioContent as u, Prompt as v, ResourceResult as w, Resource as x, PromptArgument as y };
300
+ //#region src/backend/sdk/index.d.ts
301
+ type SdkBackendOptions = {
302
+ /** MCP endpoint path when serving through `fetch`. `start({ httpStream: { endpoint } })` overrides it. Defaults to "/mcp". */
303
+ mcpPath?: string;
304
+ /**
305
+ * Transport for `start({ transportType: "stdio" })` in place of the process's
306
+ * stdin/stdout, e.g. a `StdioServerTransport` over a Unix socket.
307
+ */
308
+ stdioTransport?: Transport;
309
+ };
310
+ /**
311
+ * Backend built on the official MCP TypeScript SDK 2.x (`@modelcontextprotocol/server`).
312
+ *
313
+ * One endpoint serves both protocol eras: the `2026-07-28` revision that current
314
+ * Claude clients request first, and 2025-era clients (`initialize`). Serving is
315
+ * stateless: every request gets a fresh SDK server built from somamcp's registry,
316
+ * so there are no sessions, `connect`/`disconnect` never fire, and a 2025 client's
317
+ * GET or DELETE is answered `405`.
318
+ *
319
+ * `start()` serves a port (`httpStream`) or stdio on Node; `fetch()` serves any
320
+ * runtime with web-standard `Request`/`Response`.
321
+ */
322
+ declare const createSdkBackend: <T extends SessionAuth = SessionAuth>(config: ServerConfig<T>, backendOptions?: Record<string, unknown>) => BackendAdapter<T>;
323
+ //#endregion
324
+ export { ServerStatus as A, Resource as C, ResourceTemplate as D, ResourceResult as E, UserError as F, TextContent as M, Tool as N, ResourceTemplateArgument as O, ToolAnnotations as P, PromptResult as S, ResourceLink as T, ImageContent as _, BackendEvents as a, Prompt as b, HttpStreamConfig as c, TransportConfig as d, AudioContent as f, Context$1 as g, ContentResult as h, BackendAdapter as i, SessionAuth as j, SchemaParams as k, Logger as l, Content as m, createSdkBackend as n, BackendFactory as o, Completion as p, createFastMCPBackend as r, BackendSession as s, SdkBackendOptions as t, ServerConfig as u, InferSchemaOutput as v, ResourceContent as w, PromptArgument as x, Progress as y };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
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";
1
+ import { A as ServerStatus, C as Resource, D as ResourceTemplate, E as ResourceResult, F as UserError, M as TextContent, N as Tool, O as ResourceTemplateArgument, P as ToolAnnotations, S as PromptResult, T as ResourceLink, _ as ImageContent, b as Prompt, c as HttpStreamConfig, d as TransportConfig, f as AudioContent, g as Context, h as ContentResult, i as BackendAdapter, j as SessionAuth, k as SchemaParams, l as Logger, m as Content, o as BackendFactory, p as Completion, r as createFastMCPBackend, s as BackendSession, u as ServerConfig, v as InferSchemaOutput, w as ResourceContent, x as PromptArgument, y as Progress } from "./index-C2wa9nmp.js";
2
2
  import { Option } from "functype";
3
+ import { Context as Context$1, Hono, MiddlewareHandler } from "hono";
3
4
  import { DirectLogger, DirectLogger as DirectLogger$1 } from "functype-log";
4
5
  import { z } from "zod";
5
- import { Context as Context$1, Hono, MiddlewareHandler } from "hono";
6
6
  import { Tool as Tool$1 } from "@modelcontextprotocol/sdk/types.js";
7
7
  //#region src/artifacts/types.d.ts
8
8
  type ArtifactCommon = {
@@ -471,4 +471,4 @@ declare const createProxiedTools: <T extends SessionAuth>(gateway: GatewayInstan
471
471
  /** Create a server backed by FastMCP (Node). For edge runtimes, use `somamcp/edge`. */
472
472
  declare const createServer: <T extends SessionAuth = SessionAuth>(options: SomaServerOptions<T>) => SomaServerInstance<T>;
473
473
  //#endregion
474
- export { type ArtifactAuthenticate, type ArtifactConfig, type AudioContent, type AuthMiddlewareConfig, type Authenticate, type BackendAdapter, type BackendFactory, type BackendSession, type BuildInfo, type CapabilitiesSummary, type CaptureLevel, type Completion, type Content, type ContentResult, type Context, DEFAULT_HEALTH_DETAIL_PATH, DEFAULT_HEALTH_PATH, DEFAULT_INFO_PATH, DEFAULT_REDACTION_PATTERNS, type DirectLogger, type DirectoryArtifact, type DynamicArtifact, type EnrichedErrorResponse, type ErrorCategory, type FeedbackEnrichmentContext, type FeedbackProvider, type FeedbackSeverity, type FeedbackSubmitFailure, type FeedbackSubmitResult, type FeedbackSubmitSuccess, type FeedbackToolOptions, type FeedbackType, type GatewayConfig, type GatewayInfo, type GatewayInstance, type GatewayManagerInstance, type GatewayStatus, type GithubFeedbackOptions, type HttpStreamConfig, type ImageContent, type InferSchemaOutput, type JsonFileTelemetryOptions, type Logger, NoopTelemetry, type NormalizedFeedback, type OnUnauthorized, type Progress, type Prompt, type PromptArgument, type PromptResult, type RedactionPattern, type RedactionResult, type Resource, type ResourceContent, type ResourceLink, type ResourceResult, type RouteConfig, type RouteMethod, type RuntimeInfo, type SchemaParams, type ServerCapabilities, type ServerConfig, type ServerHealth, type ServerInfo, type ServerStatus, type SessionAuth, type SomaServerInstance, type SomaServerOptions, type StaticArtifact, type TelemetryCollector, type TelemetryEvent, type TelemetryEventType, type TextContent, type Tool, type ToolAnnotations, type ToolCaptureConfig, type ToolOptions, type TransportConfig, UserError, type WebhookFeedbackOptions, audioContent, classifyError, createAuthMiddleware, createCapabilitiesTool, createCompositeTelemetry, createConnectionsTool, createConsoleTelemetry, createDashboardArtifact, createDefaultLogger, createEnrichedError, createFastMCPBackend, createFeedbackTool, createGateway, createGatewayManager, createGithubFeedback, createHealthArtifact, createHealthDetailArtifact, createHealthTool, createInfoArtifact, createInfoTool, createJsonFileTelemetry, createLogLayerTelemetry, createProxiedTools, createServer, createServerCore, createWebhookFeedback, getRequestHeader, getRuntimeInfo, imageContent, readBuildInfoFromEnv, redact, registerArtifacts, resolveBuildInfo, wrapPrompt, wrapResource, wrapTool };
474
+ export { type ArtifactAuthenticate, type ArtifactConfig, type AudioContent, type AuthMiddlewareConfig, type Authenticate, type BackendAdapter, type BackendFactory, type BackendSession, type BuildInfo, type CapabilitiesSummary, type CaptureLevel, type Completion, type Content, type ContentResult, type Context, DEFAULT_HEALTH_DETAIL_PATH, DEFAULT_HEALTH_PATH, DEFAULT_INFO_PATH, DEFAULT_REDACTION_PATTERNS, type DirectLogger, type DirectoryArtifact, type DynamicArtifact, type EnrichedErrorResponse, type ErrorCategory, type FeedbackEnrichmentContext, type FeedbackProvider, type FeedbackSeverity, type FeedbackSubmitFailure, type FeedbackSubmitResult, type FeedbackSubmitSuccess, type FeedbackToolOptions, type FeedbackType, type GatewayConfig, type GatewayInfo, type GatewayInstance, type GatewayManagerInstance, type GatewayStatus, type GithubFeedbackOptions, type HttpStreamConfig, type ImageContent, type InferSchemaOutput, type JsonFileTelemetryOptions, type Logger, NoopTelemetry, type NormalizedFeedback, type OnUnauthorized, type Progress, type Prompt, type PromptArgument, type PromptResult, type RedactionPattern, type RedactionResult, type Resource, type ResourceContent, type ResourceLink, type ResourceResult, type ResourceTemplate, type ResourceTemplateArgument, type RouteConfig, type RouteMethod, type RuntimeInfo, type SchemaParams, type ServerCapabilities, type ServerConfig, type ServerHealth, type ServerInfo, type ServerStatus, type SessionAuth, type SomaServerInstance, type SomaServerOptions, type StaticArtifact, type TelemetryCollector, type TelemetryEvent, type TelemetryEventType, type TextContent, type Tool, type ToolAnnotations, type ToolCaptureConfig, type ToolOptions, type TransportConfig, UserError, type WebhookFeedbackOptions, audioContent, classifyError, createAuthMiddleware, createCapabilitiesTool, createCompositeTelemetry, createConnectionsTool, createConsoleTelemetry, createDashboardArtifact, createDefaultLogger, createEnrichedError, createFastMCPBackend, createFeedbackTool, createGateway, createGatewayManager, createGithubFeedback, createHealthArtifact, createHealthDetailArtifact, createHealthTool, createInfoArtifact, createInfoTool, createJsonFileTelemetry, createLogLayerTelemetry, createProxiedTools, createServer, createServerCore, createWebhookFeedback, getRequestHeader, getRuntimeInfo, imageContent, readBuildInfoFromEnv, redact, registerArtifacts, resolveBuildInfo, wrapPrompt, wrapResource, wrapTool };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
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>
1
+ import{n as e}from"./backend-D0qSyfXw.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-Dd0n_9Ai.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};
@@ -0,0 +1 @@
1
+ const e=async(e,t)=>{let{serve:n}=await import(`@hono/node-server`),r=t.sslCert&&t.sslKey?(await import(`node:https`)).createServer:void 0;return new Promise((i,a)=>{let o=n({fetch:e.fetch,hostname:t.host??`localhost`,port:t.port,...r?{createServer:r,serverOptions:{ca:t.sslCa,cert:t.sslCert,key:t.sslKey}}:{}},()=>i({close:()=>new Promise((e,t)=>{o.close(n=>n?t(n):e()),`closeAllConnections`in o&&o.closeAllConnections()})}));o.once(`error`,a)})},t=async(e,t)=>{let{serveStdio:n}=await import(`@modelcontextprotocol/server/stdio`),r=n(e,t?{transport:t}:{});return{close:()=>r.close()}};export{e as listenHttp,t as listenStdio};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "somamcp",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
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,22 @@
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
+ "@hono/node-server": "^2.1.5",
63
+ "@modelcontextprotocol/sdk": "^1.32.1",
64
+ "@modelcontextprotocol/server": "~2.3.1",
65
+ "fastmcp": "^4.22.10",
66
+ "functype": "^1.17.0",
67
+ "functype-log": "^1.17.0",
68
+ "hono": "^4.13.13",
69
+ "zod": "^4.6.7"
68
70
  },
69
71
  "prettier": "ts-builds/prettier",
70
72
  "devDependencies": {
71
- "@types/node": "^24.13.3",
73
+ "@modelcontextprotocol/client": "~2.3.1",
74
+ "@types/node": "^24.19.2",
72
75
  "get-port-please": "^3.2.0",
73
- "ts-builds": "^3.4.1",
76
+ "ts-builds": "^3.4.3",
74
77
  "tsdown": "^0.22.14"
75
78
  },
76
- "packageManager": "pnpm@11.25.0+sha512.5cde925b4f075f725eb71fbae18a42ffe784524789f19b61c731cb8721ec28aaee160e01a8d5af4fedb2a42cdbf300efe23db356b0d4a17b4d63e11f8ab7c956"
79
+ "packageManager": "pnpm@11.27.0+sha512.ad12821463bb143295f1fa3ecde77a6b9b97a7990386da800ff57bffc3964dc64b7b1c7559d5dbe5c337aea659ed9c8937d4370bd198ca2009bc6fa0249808e7"
77
80
  }
@@ -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,...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};