somamcp 1.0.15 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,6 +14,7 @@ somamcp wraps an underlying MCP framework (currently [FastMCP](https://github.co
14
14
  - **Identity & build info** — `info` MCP tool returns name, version, build commit, runtime, and capability counts; auto-populated from `SOMAMCP_BUILD_*` env vars
15
15
  - **Two-tier health** — public `/health` (minimal, for Docker/k8s probes) + protected `/health/detail` (full operational state)
16
16
  - **Protected artifacts** — `protected: true` on any artifact reuses FastMCP's `authenticate` callback; dashboard and `/info` protected by default
17
+ - **Method-aware protected routes** — `addRoute({ method, path, protected, handler })` for custom `POST`/`PUT`/etc. endpoints behind the same `authenticate` gate
17
18
  - **Agent feedback tool** — `createFeedbackTool` posts agent-reported issues to GitHub or any webhook, with automatic credential/PII redaction
18
19
  - **HTTP dashboard** — auto-generated overview at `/dashboard` (protected)
19
20
  - **Functional style** — powered by [functype](https://github.com/jordanburke/functype) (`Ref`, `Try`, `Either`)
@@ -108,11 +109,16 @@ createServer({
108
109
  Any artifact can opt into auth with `protected: true`. The route runs FastMCP's `authenticate` callback via Hono middleware — same auth model as MCP protocol calls. If `protected: true` is set but no `authenticate` is configured, the route returns 401 unconditionally.
109
110
 
110
111
  ```typescript
112
+ import { createServer, getRequestHeader } from "somamcp"
113
+
111
114
  createServer({
112
115
  name: "my-server",
113
116
  version: "1.0.0",
117
+ // `authenticate` receives either a Hono Request (routes/artifacts) or an
118
+ // http.IncomingMessage (MCP transport). `getRequestHeader` hides the shape difference.
114
119
  authenticate: async (req) => {
115
- const token = req.headers.authorization?.replace("Bearer ", "")
120
+ const header = getRequestHeader(req, "authorization")
121
+ const token = header?.replace(/^Bearer /, "")
116
122
  if (token !== process.env.OPS_TOKEN) throw new Error("denied")
117
123
  return { user: "ops" }
118
124
  },
@@ -127,6 +133,45 @@ createServer({
127
133
  })
128
134
  ```
129
135
 
136
+ ## Protected Routes
137
+
138
+ For write endpoints, or anything the GET-only artifact shape doesn't cover, use `addRoute`. Same `authenticate` gate, method-aware, with optional `onUnauthorized` for custom 401 responses.
139
+
140
+ ```typescript
141
+ server.addRoute({
142
+ method: ["POST", "PUT"],
143
+ path: "/upload",
144
+ protected: true,
145
+ handler: async (c) => {
146
+ const bytes = (await c.req.arrayBuffer()).byteLength
147
+ return c.json({ bytes, status: "accepted" })
148
+ },
149
+ onUnauthorized: (c) =>
150
+ c.json({ error: "unauthorized", hint: "provide Bearer" }, 401, { "WWW-Authenticate": "Bearer" }),
151
+ })
152
+ ```
153
+
154
+ **Route ordering.** Hono dispatches by registration order. `addRoute` registers immediately, alongside artifacts and introspection routes. Concrete non-overlapping paths compose safely in any order; for wildcards or overlapping prefixes, register earlier to take precedence.
155
+
156
+ See [`examples/protected-upload-server`](examples/protected-upload-server) for a runnable end-to-end wiring: httpStream + `authenticate` + `addRoute` + a tool returning a content-array (text + inline image).
157
+
158
+ ## Content-Array Tool Returns
159
+
160
+ A tool's `execute` may return a plain string **or** a full content-array with multimodal parts — telemetry passes the return value through unchanged. Use the exported `imageContent` / `audioContent` helpers to build parts without reaching into the backend.
161
+
162
+ ```typescript
163
+ import { createServer, imageContent } from "somamcp"
164
+
165
+ server.addTool({
166
+ name: "hello_pixel",
167
+ description: "Text + inline image",
168
+ parameters: z.object({ name: z.string() }),
169
+ execute: async ({ name }) => ({
170
+ content: [{ type: "text", text: `hello, ${name}` }, await imageContent({ path: "./banner.png" })],
171
+ }),
172
+ })
173
+ ```
174
+
130
175
  ## Telemetry
131
176
 
132
177
  Telemetry is opt-in via the `telemetry` option. Every tool/resource/prompt call is automatically wrapped with timing, error classification, and optional input/output capture.
@@ -341,6 +386,15 @@ createServer({
341
386
  - `registerArtifacts`, `createDashboardArtifact`, `createHealthArtifact`, `createHealthDetailArtifact`, `createInfoArtifact`
342
387
  - Types: `StaticArtifact`, `DynamicArtifact`, `DirectoryArtifact`, `ArtifactConfig`, `ArtifactAuthenticate`
343
388
 
389
+ **Auth & Routes**
390
+
391
+ - `createAuthMiddleware`, `getRequestHeader`
392
+ - Types: `Authenticate`, `OnUnauthorized`, `AuthMiddlewareConfig`, `RouteConfig`, `RouteMethod`
393
+
394
+ **Transport**
395
+
396
+ - Types: `TransportConfig`, `HttpStreamConfig` (accepts `cors`, `stateless`, `eventStore`, `sslCert` / `sslKey` / `sslCa`, plus `port` / `host` / `endpoint` / `enableJsonResponse`)
397
+
344
398
  **Introspection**
345
399
 
346
400
  - `createInfoTool` (auto-registered as `info`)
@@ -1,2 +1,2 @@
1
- import { a as BackendSession, i as BackendFactory, n as BackendAdapter, r as BackendEvents, t as createFastMCPBackend } from "../index-BoCx9NV1.js";
1
+ import { a as BackendSession, i as BackendFactory, n as BackendAdapter, r as BackendEvents, t as createFastMCPBackend } from "../index-DizthJMe.js";
2
2
  export { type BackendAdapter, type BackendEvents, type BackendFactory, type BackendSession, createFastMCPBackend };
@@ -1,5 +1,4 @@
1
1
  import { Hono } from "hono";
2
-
3
2
  //#region src/types/core.d.ts
4
3
  type SessionAuth = Record<string, unknown> | undefined;
5
4
  /**
@@ -29,7 +28,7 @@ type SchemaParams = {
29
28
  }>;
30
29
  };
31
30
  };
32
- type InferSchemaOutput<P extends SchemaParams> = NonNullable<P["~standard"]["types"]>["output"] extends infer O ? [O] extends [never] ? unknown : O : unknown;
31
+ type InferSchemaOutput<P extends SchemaParams> = NonNullable<P["~standard"]["types"]>["output"] extends (infer O) ? [O] extends [never] ? unknown : O : unknown;
33
32
  type TextContent = {
34
33
  text: string;
35
34
  type: "text";
@@ -167,15 +166,35 @@ type ServerConfig<T extends SessionAuth = SessionAuth> = {
167
166
  name: string;
168
167
  version: `${number}.${number}.${number}`;
169
168
  };
169
+ /**
170
+ * httpStream transport options. All fields are passed through to the underlying
171
+ * MCP backend unchanged; `port` is the only one required.
172
+ *
173
+ * - `cors` — `true` for permissive defaults, or a config object forwarded to the
174
+ * backend's CORS implementation. Only relevant for browser-facing servers.
175
+ * - `stateless` — treat each request independently (no session state). Useful
176
+ * for horizontally-scaled or Lambda-style deployments.
177
+ * - `eventStore` — pluggable store enabling resumable event streams; the
178
+ * backend's `EventStore` interface, opaque to somamcp.
179
+ * - `sslCert` / `sslKey` / `sslCa` — inline PEM strings enabling TLS termination
180
+ * at the server. Omit for plain HTTP behind a proxy.
181
+ */
182
+ type HttpStreamConfig = {
183
+ cors?: boolean | Record<string, unknown>;
184
+ enableJsonResponse?: boolean;
185
+ endpoint?: `/${string}`;
186
+ eventStore?: unknown;
187
+ host?: string;
188
+ port: number;
189
+ sslCa?: string;
190
+ sslCert?: string;
191
+ sslKey?: string;
192
+ stateless?: boolean;
193
+ };
170
194
  type TransportConfig = {
171
195
  transportType: "stdio";
172
196
  } | {
173
- httpStream: {
174
- enableJsonResponse?: boolean;
175
- endpoint?: `/${string}`;
176
- host?: string;
177
- port: number;
178
- };
197
+ httpStream: HttpStreamConfig;
179
198
  transportType: "httpStream";
180
199
  };
181
200
  //#endregion
@@ -217,4 +236,4 @@ type BackendFactory<T extends SessionAuth = SessionAuth> = (config: {
217
236
  //#region src/backend/fastmcp.d.ts
218
237
  declare const createFastMCPBackend: <T extends SessionAuth = SessionAuth>(config: ServerConfig<T>, backendOptions?: Record<string, unknown>) => BackendAdapter<T>;
219
238
  //#endregion
220
- export { UserError as A, ResourceResult as C, TextContent as D, SessionAuth as E, Tool as O, ResourceLink as S, ServerStatus as T, Prompt as _, BackendSession as a, Resource as b, TransportConfig as c, Content as d, ContentResult as f, Progress as g, InferSchemaOutput as h, BackendFactory as i, ToolAnnotations as k, AudioContent as l, ImageContent as m, BackendAdapter as n, Logger as o, Context$1 as p, BackendEvents as r, ServerConfig as s, createFastMCPBackend as t, Completion as u, PromptArgument as v, SchemaParams as w, ResourceContent as x, PromptResult as y };
239
+ 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 };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,9 @@
1
- import { A as UserError, C as ResourceResult, D as TextContent, E as SessionAuth, O as Tool, S as ResourceLink, T as ServerStatus, _ as Prompt, a as BackendSession, b as Resource, c as TransportConfig, d as Content, f as ContentResult, g as Progress, h as InferSchemaOutput, i as BackendFactory, k as ToolAnnotations, l as AudioContent, m as ImageContent, n as BackendAdapter, o as Logger, p as Context, s as ServerConfig, t as createFastMCPBackend, u as Completion, v as PromptArgument, w as SchemaParams, x as ResourceContent, y as PromptResult } from "./index-BoCx9NV1.js";
1
+ import { A as ToolAnnotations, C as ResourceLink, D as SessionAuth, E as ServerStatus, O as TextContent, S as ResourceContent, T as SchemaParams, _ as Progress, a as BackendSession, b as PromptResult, c as ServerConfig, d as Completion, f as Content, g as InferSchemaOutput, h as ImageContent, i as BackendFactory, j as UserError, k as Tool, l as TransportConfig, m as Context, n as BackendAdapter, o as HttpStreamConfig, p as ContentResult, s as Logger, t as createFastMCPBackend, u as AudioContent, v as Prompt, w as ResourceResult, x as Resource, y as PromptArgument } from "./index-DizthJMe.js";
2
2
  import { Option } from "functype";
3
3
  import { DirectLogger, DirectLogger as DirectLogger$1 } from "functype-log";
4
4
  import { z } from "zod";
5
- import { Context as Context$1, Hono } from "hono";
5
+ import { Context as Context$1, Hono, MiddlewareHandler } from "hono";
6
6
  import { Tool as Tool$1 } from "@modelcontextprotocol/sdk/types.js";
7
-
8
7
  //#region src/artifacts/types.d.ts
9
8
  type ArtifactCommon = {
10
9
  protected?: boolean;
@@ -114,6 +113,60 @@ type TelemetryCollector = {
114
113
  recordEvent: (event: TelemetryEvent) => void;
115
114
  };
116
115
  //#endregion
116
+ //#region src/auth/middleware.d.ts
117
+ /**
118
+ * Callback invoked to authenticate an incoming request. Receives either a Hono
119
+ * `Request` (custom routes / protected artifacts) or an `http.IncomingMessage`
120
+ * (MCP transport). Use `getRequestHeader` to hide the shape difference.
121
+ */
122
+ type Authenticate = (request: unknown) => Promise<unknown>;
123
+ /** Response builder invoked when auth is missing or fails. Defaults to `401 { error: "Unauthorized" }`. */
124
+ type OnUnauthorized = (c: Context$1) => Response | Promise<Response>;
125
+ type AuthMiddlewareConfig = {
126
+ authenticate?: Authenticate;
127
+ onUnauthorized?: OnUnauthorized;
128
+ };
129
+ declare const createAuthMiddleware: ({ authenticate, onUnauthorized }: AuthMiddlewareConfig) => MiddlewareHandler;
130
+ //#endregion
131
+ //#region src/auth/request.d.ts
132
+ /**
133
+ * Extract a header from either shape somamcp's `authenticate` callback may receive:
134
+ *
135
+ * - **Hono `Request`** (`c.req.raw`) — used on protected artifact and custom routes.
136
+ * Headers implement the WHATWG `Headers` interface (`.get(name)`).
137
+ * - **`http.IncomingMessage`** — used on the MCP transport path (FastMCP).
138
+ * Headers are a plain lowercased object; values may be `string | string[]`.
139
+ *
140
+ * Case-insensitive on the header name. Returns `undefined` when the header is
141
+ * absent or the request shape is not recognized — matches the `Headers.get`
142
+ * shape callers already write against.
143
+ */
144
+ declare const getRequestHeader: (request: unknown, name: string) => string | undefined;
145
+ //#endregion
146
+ //#region src/types/routes.d.ts
147
+ type RouteMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
148
+ /**
149
+ * Config for a custom HTTP route registered on the server's Hono app via
150
+ * `SomaServerInstance.addRoute`.
151
+ *
152
+ * When `protected: true`, the server's `authenticate` callback runs first — a
153
+ * throw or rejection short-circuits to `onUnauthorized` (default `401`).
154
+ *
155
+ * ### Route ordering
156
+ *
157
+ * Hono dispatches by registration order. `addRoute` registers immediately, so
158
+ * routes added **before** `start()` land alongside artifacts and introspection
159
+ * routes in call order. For concrete non-overlapping paths this doesn't matter;
160
+ * for wildcards or overlapping prefixes, register earlier to take precedence.
161
+ */
162
+ type RouteConfig = {
163
+ method: RouteMethod | ReadonlyArray<RouteMethod>;
164
+ path: `/${string}`;
165
+ protected?: boolean;
166
+ handler: (c: Context$1) => Response | Promise<Response>;
167
+ onUnauthorized?: OnUnauthorized;
168
+ };
169
+ //#endregion
117
170
  //#region src/types.d.ts
118
171
  type SomaServerOptions<T extends SessionAuth = SessionAuth> = ServerConfig<T> & {
119
172
  artifacts?: ArtifactConfig[];
@@ -181,6 +234,7 @@ type SomaServerInstance<T extends SessionAuth = SessionAuth> = {
181
234
  addResource: (resource: Resource<T>) => void;
182
235
  addResources: (resources: Resource<T>[]) => void;
183
236
  addResourceTemplate: (...args: ReadonlyArray<unknown>) => void;
237
+ addRoute: (route: RouteConfig) => void;
184
238
  addTool: <P extends SchemaParams>(tool: Tool<T, P>) => void;
185
239
  addTools: <P extends SchemaParams>(tools: Tool<T, P>[]) => void;
186
240
  getApp: () => Hono;
@@ -242,6 +296,19 @@ declare const createLogLayerTelemetry: (logger: DirectLogger$1) => TelemetryColl
242
296
  declare const NoopTelemetry: TelemetryCollector;
243
297
  //#endregion
244
298
  //#region src/telemetry/telemetryWrapper.d.ts
299
+ /**
300
+ * Wrap a tool's `execute` in telemetry + error enrichment.
301
+ *
302
+ * The success path returns the tool's return value **unchanged**, so any of the
303
+ * supported shapes reach the backend intact: a plain string, a `ContentResult`
304
+ * (`{ content: [...], isError? }`), or a content-array with multimodal parts
305
+ * (`{ type: "image", data, mimeType }`, `audio`, `resource`, etc.). Use the
306
+ * exported `imageContent` / `audioContent` helpers to build parts without
307
+ * reaching into the backend.
308
+ *
309
+ * The failure path converts an error to a `ContentResult` with `isError: true`
310
+ * and the enriched error payload as the text content.
311
+ */
245
312
  declare const wrapTool: <T extends SessionAuth, P extends SchemaParams>(tool: Tool<T, P>, telemetry: TelemetryCollector, captureConfig?: ToolCaptureConfig) => Tool<T, P>;
246
313
  declare const wrapResource: <T extends SessionAuth>(resource: Resource<T>, telemetry: TelemetryCollector) => Resource<T>;
247
314
  declare const wrapPrompt: <T extends SessionAuth, Args extends PromptArgument<T>[] = PromptArgument<T>[]>(prompt: Prompt<T, Args>, telemetry: TelemetryCollector) => Prompt<T, Args>;
@@ -376,4 +443,4 @@ declare const createGatewayManager: (telemetry: TelemetryCollector) => GatewayMa
376
443
  //#region src/gateway/toolProxy.d.ts
377
444
  declare const createProxiedTools: <T extends SessionAuth>(gateway: GatewayInstance, telemetry?: TelemetryCollector) => ReadonlyArray<Tool<T>>;
378
445
  //#endregion
379
- export { type ArtifactAuthenticate, type ArtifactConfig, type AudioContent, 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 ImageContent, type InferSchemaOutput, type JsonFileTelemetryOptions, type Logger, NoopTelemetry, type NormalizedFeedback, type Progress, type Prompt, type PromptArgument, type PromptResult, type RedactionPattern, type RedactionResult, type Resource, type ResourceContent, type ResourceLink, type ResourceResult, 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, createCapabilitiesTool, createCompositeTelemetry, createConnectionsTool, createConsoleTelemetry, createDashboardArtifact, createDefaultLogger, createEnrichedError, createFastMCPBackend, createFeedbackTool, createGateway, createGatewayManager, createGithubFeedback, createHealthArtifact, createHealthDetailArtifact, createHealthTool, createInfoArtifact, createInfoTool, createJsonFileTelemetry, createLogLayerTelemetry, createProxiedTools, createServer, createWebhookFeedback, getRuntimeInfo, imageContent, readBuildInfoFromEnv, redact, registerArtifacts, resolveBuildInfo, wrapPrompt, wrapResource, wrapTool };
446
+ 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, 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-G2JeUHpG.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{createDirectConsoleLogger as l}from"functype-log";import{appendFile as u,mkdir as d,readFile as f}from"node:fs/promises";import{dirname as p,extname as m}from"node:path";import{z as h}from"zod";const g=e=>e.json({error:`Unauthorized`},401),_=e=>async(t,r)=>{if(!e)return g(t);let i=n(t.env).flatMap(e=>n(e.incoming)).orElse(t.req.raw);if((await a.fromPromise(e(i))).isFailure())return g(t);await r()},ee=(e,t,n)=>{t.forEach(t=>{switch(t.protected&&e.use(t.path,_(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));break;case`directory`:break}})},v=(e,t,n)=>({handler:()=>{let r=y(e(),t(),n());return new Response(r,{headers:{"Content-Type":`text/html; charset=utf-8`}})},path:`/dashboard`,protected:!0,type:`dynamic`}),y=(e,t,n)=>`<!DOCTYPE html>
1
+ import{t as e}from"./backend-G2JeUHpG.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{createDirectConsoleLogger as l}from"functype-log";import{appendFile as u,mkdir as d,readFile as f}from"node:fs/promises";import{dirname as p,extname as m}from"node:path";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));break;case`directory`:break}})},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">
@@ -32,7 +32,7 @@ import{t as e}from"./backend-G2JeUHpG.js";import{Map as t,Option as n,Ref as r,S
32
32
  <div class="card">
33
33
  <h2>Health</h2>
34
34
  <div class="metric"><span class="metric-label">Status:</span> <span class="status ${e.status}">${e.status}</span></div>
35
- <div class="metric"><span class="metric-label">Uptime:</span> <span class="metric-value">${b(e.uptime)}</span></div>
35
+ <div class="metric"><span class="metric-label">Uptime:</span> <span class="metric-value">${E(e.uptime)}</span></div>
36
36
  <div class="metric"><span class="metric-label">Sessions:</span> <span class="metric-value">${e.activeSessions}</span></div>
37
37
  <div class="metric"><span class="metric-label">Gateways:</span> <span class="metric-value">${e.gateways.connected}/${e.gateways.total}</span></div>
38
38
  </div>
@@ -54,9 +54,9 @@ import{t as e}from"./backend-G2JeUHpG.js";import{Map as t,Option as n,Ref as r,S
54
54
  </div>`:``}
55
55
  </div>
56
56
  </body>
57
- </html>`,b=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`},x=`/health`,S=`/health/detail`,C=(e,t=x)=>({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`}),w=(e,t=S)=>({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`}),T=`/info`,E=(e,t=T)=>({handler:()=>new Response(JSON.stringify(e()),{headers:{"Content-Type":`application/json`},status:200}),path:t,protected:!0,type:`dynamic`}),D=e=>n(process.env[e]).filter(e=>e.length>0),O=()=>({branch:D(`SOMAMCP_BUILD_BRANCH`).orUndefined(),commit:D(`SOMAMCP_BUILD_COMMIT`).orUndefined(),date:D(`SOMAMCP_BUILD_DATE`).orUndefined(),environment:D(`SOMAMCP_ENVIRONMENT`).orUndefined()}),k=e=>{let t=O();return{branch:e?.branch??t.branch,commit:e?.commit??t.commit,date:e?.date??t.date,environment:e?.environment??t.environment}},A=()=>({arch:process.arch,nodeVersion:process.version,platform:process.platform}),j=(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},te=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=j(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)}}},M=e=>({flush:async()=>{await Promise.all(e.map(e=>e.flush?e.flush():Promise.resolve()))},recordEvent:t=>{e.forEach(e=>e.recordEvent(t))}}),N=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)}}),P=(e=`[soma]`)=>N(l({prefix:e,stream:`stderr`})),F=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`},I=(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`]}},L=(e,t)=>{let n=F(e),r=e instanceof Error?e.message:String(e),i=I(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}},R=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 d(p(e.filePath),{recursive:!0}),a.set(!0));let r=`${n.map(e=>JSON.stringify(e)).join(`
58
- `)}\n`;await u(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()}}},z={flush:()=>Promise.resolve(),recordEvent:()=>{}},B=(e,t)=>!t||t.length===0?e:Object.fromEntries(Object.entries(e).map(([e,n])=>[e,t.includes(e)?`[REDACTED]`:n])),V=(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},H=e=>a.fromPromise(e),U=(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`?B(r,n?.redactInputFields):void 0;return(await H(e.execute(r,i))).fold(n=>{let r=L(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`?V(r,n?.maxOutputSize):void 0,requestId:s,sessionId:c,timestamp:o,type:`tool.execute`}),r))}}),W=(e,t)=>({...e,load:async n=>{let r=Date.now();return(await H(e.load(n))).fold(n=>{let i=L(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))}}),G=(e,t)=>({...e,load:async(n,r)=>{let i=Date.now();return(await H(e.load(n,r))).fold(n=>{let r=L(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))}}),K=e=>a.fromPromise(e),q=(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 K(e.callTool(r.name,i))).fold(n=>{let s=L(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}`}))},J=(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}),Y=t=>{let{artifacts: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:_,...y}=t,b=y.name,x=y.version,S=k(o),T=A(),D=h??``,O=_??(g?N(g):z),j=e(y,a),M=te(O),P=[],F=[],I=[],L=r(0);d?.forEach(e=>M.add(e));let R=()=>{let e=L.get(),t=e>0?Date.now()-e:0;return{activeSessions:j.sessions.length,gateways:{connected:M.connectedCount,total:M.totalCount},name:b,startedAt:e,status:j.serverState,uptime:t}},B=()=>({prompts:I,resources:F,tools:P}),V=()=>({build:S,capabilities:{prompts:I.length,resources:F.length,tools:P.length},name:b,runtime:T,version:x});u!==!1&&j.addTool(J(()=>V(),`${D}info`));let H=[...i??[]];c!==!1&&(H.push(C(()=>R(),p)),H.push(w(()=>R(),f))),l!==!1&&H.push(E(()=>V(),m)),s!==!1&&H.push(v(()=>R(),()=>B(),()=>M.getInfoAll())),H.length>0&&ee(j.getApp(),H,n(y.authenticate).orUndefined()),j.on(`connect`,({session:e})=>{O.recordEvent({data:{sessionId:e.sessionId},name:b,timestamp:Date.now(),type:`session.connect`})}),j.on(`disconnect`,({session:e})=>{O.recordEvent({data:{sessionId:e.sessionId},name:b,timestamp:Date.now(),type:`session.disconnect`})});let K=e=>{let t=U(e,O,`captureConfig`in e?e.captureConfig:void 0);j.addTool(t),P.push({description:e.description,name:e.name})},Y=e=>{let t=W(e,O);j.addResource(t),F.push({description:e.description,name:e.name,uri:e.uri})},X=e=>{let t=G(e,O);j.addPrompt(t),I.push({description:e.description,name:e.name})};return{get name(){return b},get serverState(){return j.serverState},get sessions(){return j.sessions},addPrompt:X,addPrompts:e=>{e.forEach(X)},addResource:Y,addResources:e=>{e.forEach(Y)},addResourceTemplate:(...e)=>{j.addResourceTemplate(...e)},addTool:K,addTools:e=>{e.forEach(e=>K(e))},getApp:()=>j.getApp(),getCapabilities:B,getGatewayManager:()=>M,getHealth:R,getInfo:V,removePrompt:e=>{j.removePrompt(e)},removeResource:e=>{j.removeResource(e)},removeTool:e=>{j.removeTool(e)},async start(e){L.set(Date.now()),O.recordEvent({name:b,timestamp:L.get(),type:`server.start`}),await j.start(e),await M.connectAll(),M.getAll().filter(e=>e.status===`connected`&&e.config.proxyTools!==!1).forEach(e=>{q(e,O).forEach(e=>j.addTool(e))})},async stop(){await M.disconnectAll(),await j.stop(),O.recordEvent({name:b,timestamp:Date.now(),type:`server.stop`}),O.flush&&await O.flush()}}};var X=class extends Error{constructor(e){super(e),this.name=`UserError`}};const ne={".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`},re={".aac":`audio/aac`,".flac":`audio/flac`,".m4a":`audio/mp4`,".mp3":`audio/mpeg`,".ogg":`audio/ogg`,".opus":`audio/opus`,".wav":`audio/wav`,".weba":`audio/webm`},Z=(e,t,n)=>t[m(new URL(e,`file:///`).pathname).toLowerCase()]??n,ie=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 f(e.path),source:e.path}:{data:e.buffer}},ae=async e=>{let{data:t,source:n}=await ie(e,`image`),r=n?Z(n,ne,`image/png`):`image/png`;return{data:t.toString(`base64`),mimeType:r,type:`image`}},oe=async e=>{let{data:t,source:n}=await ie(e,`audio`),r=n?Z(n,re,`audio/mpeg`):`audio/mpeg`;return{data:t.toString(`base64`),mimeType:r,type:`audio`}},se=e=>l({prefix:`[${e}]`,stream:`stderr`}).withContext({cell:e}),ce=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`}),le=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`}),ue=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}},de=[`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
- `),fe=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`)}),pe=(e,r)=>!e.redacted&&!r.redacted?n.none():n([`⚠️ Automatic redactions applied:`,...[...[...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)}`)].join(`
60
- `)),me=e=>e.filter(e=>Object.keys(e).length>0).map(e=>[`---`,`**Context (auto-collected):**`,"```json",JSON.stringify(e,null,2),"```"].join(`
61
- `)),he=(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
- `),ge=e=>{let t=e.redactionPatterns??Q;return{annotations:{destructiveHint:!1,readOnlyHint:!1},description:e.description??de,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=pe(i,a),c=me(o),l=he(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:fe}},_e=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}})}}},ve=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}})}};export{S as DEFAULT_HEALTH_DETAIL_PATH,x as DEFAULT_HEALTH_PATH,T as DEFAULT_INFO_PATH,Q as DEFAULT_REDACTION_PATTERNS,z as NoopTelemetry,X as UserError,oe as audioContent,F as classifyError,ce as createCapabilitiesTool,M as createCompositeTelemetry,le as createConnectionsTool,P as createConsoleTelemetry,v as createDashboardArtifact,se as createDefaultLogger,L as createEnrichedError,e as createFastMCPBackend,ge as createFeedbackTool,j as createGateway,te as createGatewayManager,_e as createGithubFeedback,C as createHealthArtifact,w as createHealthDetailArtifact,ue as createHealthTool,E as createInfoArtifact,J as createInfoTool,R as createJsonFileTelemetry,N as createLogLayerTelemetry,q as createProxiedTools,Y as createServer,ve as createWebhookFeedback,A as getRuntimeInfo,ae as imageContent,O as readBuildInfoFromEnv,$ as redact,ee as registerArtifacts,k as resolveBuildInfo,G as wrapPrompt,W as wrapResource,U as wrapTool};
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`}),k=(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`}),A=`/info`,j=(e,t=A)=>({handler:()=>new Response(JSON.stringify(e()),{headers:{"Content-Type":`application/json`},status:200}),path:t,protected:!0,type:`dynamic`}),M=e=>n(process.env[e]).filter(e=>e.length>0),N=()=>({branch:M(`SOMAMCP_BUILD_BRANCH`).orUndefined(),commit:M(`SOMAMCP_BUILD_COMMIT`).orUndefined(),date:M(`SOMAMCP_BUILD_DATE`).orUndefined(),environment:M(`SOMAMCP_ENVIRONMENT`).orUndefined()}),P=e=>{let t=N();return{branch:e?.branch??t.branch,commit:e?.commit??t.commit,date:e?.date??t.date,environment:e?.environment??t.environment}},F=()=>({arch:process.arch,nodeVersion:process.version,platform:process.platform}),I=(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},L=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=I(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)}}},R=e=>({flush:async()=>{await Promise.all(e.map(e=>e.flush?e.flush():Promise.resolve()))},recordEvent:t=>{e.forEach(e=>e.recordEvent(t))}}),z=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)}}),B=(e=`[soma]`)=>z(l({prefix:e,stream:`stderr`})),V=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`},H=(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`]}},U=(e,t)=>{let n=V(e),r=e instanceof Error?e.message:String(e),i=H(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}},W=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 d(p(e.filePath),{recursive:!0}),a.set(!0));let r=`${n.map(e=>JSON.stringify(e)).join(`
58
+ `)}\n`;await u(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()}}},G={flush:()=>Promise.resolve(),recordEvent:()=>{}},K=(e,t)=>!t||t.length===0?e:Object.fromEntries(Object.entries(e).map(([e,n])=>[e,t.includes(e)?`[REDACTED]`:n])),q=(e,t=1e4)=>{let n=typeof e==`string`?e:JSON.stringify(e);return n&&n.length>t?`${n.slice(0,t)}...[truncated, ${n.length} total chars]`:e},J=e=>a.fromPromise(e),Y=(e,t,n)=>({...e,execute:async(r,i)=>{let a=n?.captureLevel??`full`;if(a===`none`)return e.execute(r,i);let o=Date.now(),{requestId:s,sessionId:c}=i,l=a===`full`?K(r,n?.redactInputFields):void 0;return(await J(e.execute(r,i))).fold(n=>{let r=U(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`?q(r,n?.maxOutputSize):void 0,requestId:s,sessionId:c,timestamp:o,type:`tool.execute`}),r))}}),X=(e,t)=>({...e,load:async n=>{let r=Date.now();return(await J(e.load(n))).fold(n=>{let i=U(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))}}),Z=(e,t)=>({...e,load:async(n,r)=>{let i=Date.now();return(await J(e.load(n,r))).fold(n=>{let r=U(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))}}),te=e=>a.fromPromise(e),ne=(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 te(e.callTool(r.name,i))).fold(n=>{let s=U(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}`}))},re=(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}),ie=t=>{let{artifacts: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}=t,b=y.name,x=y.version,S=P(o),T=F(),E=h??``,D=v??(g?z(g):G),O=e(y,a),A=L(D),M=[],N=[],I=[],R=r(0);d?.forEach(e=>A.add(e));let B=()=>{let e=R.get(),t=e>0?Date.now()-e:0;return{activeSessions:O.sessions.length,gateways:{connected:A.connectedCount,total:A.totalCount},name:b,startedAt:e,status:O.serverState,uptime:t}},V=()=>({prompts:I,resources:N,tools:M}),H=()=>({build:S,capabilities:{prompts:I.length,resources:N.length,tools:M.length},name:b,runtime:T,version:x});u!==!1&&O.addTool(re(()=>H(),`${E}info`));let U=[...i??[]];c!==!1&&(U.push(ee(()=>B(),p)),U.push(k(()=>B(),f))),l!==!1&&U.push(j(()=>H(),m)),s!==!1&&U.push(w(()=>B(),()=>V(),()=>A.getInfoAll())),U.length>0&&C(O.getApp(),U,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 W=e=>{let t=`captureConfig`in e?e.captureConfig:void 0,n=Y(e,D,t);O.addTool(n),M.push({description:e.description,name:e.name})},K=e=>{let t=X(e,D);O.addResource(t),N.push({description:e.description,name:e.name,uri:e.uri})},q=e=>{let t=Z(e,D);O.addPrompt(t),I.push({description:e.description,name:e.name})};return{get name(){return b},get serverState(){return O.serverState},get sessions(){return O.sessions},addPrompt:q,addPrompts:e=>{e.forEach(q)},addResource:K,addResources:e=>{e.forEach(K)},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:W,addTools:e=>{e.forEach(e=>W(e))},getApp:()=>O.getApp(),getCapabilities:V,getGatewayManager:()=>A,getHealth:B,getInfo:H,removePrompt:e=>{O.removePrompt(e)},removeResource:e=>{O.removeResource(e)},removeTool:e=>{O.removeTool(e)},async start(e){R.set(Date.now()),D.recordEvent({name:b,timestamp:R.get(),type:`server.start`}),await O.start(e),await A.connectAll(),A.getAll().filter(e=>e.status===`connected`&&e.config.proxyTools!==!1).forEach(e=>{ne(e,D).forEach(e=>O.addTool(e))})},async stop(){await A.disconnectAll(),await O.stop(),D.recordEvent({name:b,timestamp:Date.now(),type:`server.stop`}),D.flush&&await D.flush()}}};var ae=class extends Error{constructor(e){super(e),this.name=`UserError`}};const oe={".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`},se={".aac":`audio/aac`,".flac":`audio/flac`,".m4a":`audio/mp4`,".mp3":`audio/mpeg`,".ogg":`audio/ogg`,".opus":`audio/opus`,".wav":`audio/wav`,".weba":`audio/webm`},ce=(e,t,n)=>t[m(new URL(e,`file:///`).pathname).toLowerCase()]??n,le=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 f(e.path),source:e.path}:{data:e.buffer}},ue=async e=>{let{data:t,source:n}=await le(e,`image`),r=n?ce(n,oe,`image/png`):`image/png`;return{data:t.toString(`base64`),mimeType:r,type:`image`}},de=async e=>{let{data:t,source:n}=await le(e,`audio`),r=n?ce(n,se,`audio/mpeg`):`audio/mpeg`;return{data:t.toString(`base64`),mimeType:r,type:`audio`}},fe=e=>l({prefix:`[${e}]`,stream:`stderr`}).withContext({cell:e}),pe=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`}),me=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`}),he=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}},ge=[`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
+ `),_e=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`)}),ve=(e,r)=>!e.redacted&&!r.redacted?n.none():n([`⚠️ Automatic redactions applied:`,...[...[...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)}`)].join(`
60
+ `)),ye=e=>e.filter(e=>Object.keys(e).length>0).map(e=>[`---`,`**Context (auto-collected):**`,"```json",JSON.stringify(e,null,2),"```"].join(`
61
+ `)),be=(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
+ `),xe=e=>{let t=e.redactionPatterns??Q;return{annotations:{destructiveHint:!1,readOnlyHint:!1},description:e.description??ge,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=ve(i,a),c=ye(o),l=be(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:_e}},Se=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}})}}},Ce=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}})}};export{O as DEFAULT_HEALTH_DETAIL_PATH,D as DEFAULT_HEALTH_PATH,A as DEFAULT_INFO_PATH,Q as DEFAULT_REDACTION_PATTERNS,G as NoopTelemetry,ae as UserError,de as audioContent,V as classifyError,_ as createAuthMiddleware,pe as createCapabilitiesTool,R as createCompositeTelemetry,me as createConnectionsTool,B as createConsoleTelemetry,w as createDashboardArtifact,fe as createDefaultLogger,U as createEnrichedError,e as createFastMCPBackend,xe as createFeedbackTool,I as createGateway,L as createGatewayManager,Se as createGithubFeedback,ee as createHealthArtifact,k as createHealthDetailArtifact,he as createHealthTool,j as createInfoArtifact,re as createInfoTool,W as createJsonFileTelemetry,z as createLogLayerTelemetry,ne as createProxiedTools,ie as createServer,Ce as createWebhookFeedback,S as getRequestHeader,F as getRuntimeInfo,ue as imageContent,N as readBuildInfoFromEnv,$ as redact,C as registerArtifacts,P as resolveBuildInfo,Z as wrapPrompt,X as wrapResource,Y as wrapTool};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "somamcp",
3
- "version": "1.0.15",
3
+ "version": "1.1.1",
4
4
  "description": "Framework for building MCP servers with telemetry, introspection, and backend abstraction",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -54,18 +54,18 @@
54
54
  "homepage": "https://github.com/sapientsai/SomaMCP#readme",
55
55
  "dependencies": {
56
56
  "@modelcontextprotocol/sdk": "^1.29.0",
57
- "fastmcp": "^4.3.0",
58
- "functype": "^1.4.4",
59
- "functype-log": "^1.4.4",
60
- "hono": "^4.12.26",
57
+ "fastmcp": "^4.8.0",
58
+ "functype": "^1.6.3",
59
+ "functype-log": "^1.6.3",
60
+ "hono": "^4.12.31",
61
61
  "zod": "^4.4.3"
62
62
  },
63
63
  "prettier": "ts-builds/prettier",
64
64
  "devDependencies": {
65
- "@types/node": "^24.13.2",
65
+ "@types/node": "^24.13.3",
66
66
  "get-port-please": "^3.2.0",
67
- "ts-builds": "^3.2.1",
68
- "tsdown": "^0.22.3"
67
+ "ts-builds": "^3.2.3",
68
+ "tsdown": "^0.22.13"
69
69
  },
70
- "packageManager": "pnpm@11.8.0+sha512.c1f5e7c4cb241c8f174b743851d82f42b802324afc8b0f116b96adb15aa06664948dde36960a3ba1079ba5b4b29dd0140135b94b5b5f5263592249d68e555f26"
70
+ "packageManager": "pnpm@11.16.0+sha512.b767e9a98fc87aec0f42daefcd0e84941c2cdb45d73c4e1e68860fb2bdfdbe032ab592105479e5e05c237f740c8a5ffdbbb52385797ddc2296745a1bbb883e8d"
71
71
  }