@stigmer/mcp-server 3.0.8-dev.20260612122433

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.
Files changed (205) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +157 -0
  3. package/cli/mcp-server-stigmer.d.ts +3 -0
  4. package/cli/mcp-server-stigmer.d.ts.map +1 -0
  5. package/cli/mcp-server-stigmer.js +201 -0
  6. package/cli/mcp-server-stigmer.js.map +1 -0
  7. package/config.d.ts +44 -0
  8. package/config.d.ts.map +1 -0
  9. package/config.js +92 -0
  10. package/config.js.map +1 -0
  11. package/domains/agents/apply.d.ts +4 -0
  12. package/domains/agents/apply.d.ts.map +1 -0
  13. package/domains/agents/apply.js +25 -0
  14. package/domains/agents/apply.js.map +1 -0
  15. package/domains/agents/delete.d.ts +3 -0
  16. package/domains/agents/delete.d.ts.map +1 -0
  17. package/domains/agents/delete.js +35 -0
  18. package/domains/agents/delete.js.map +1 -0
  19. package/domains/agents/fetch.d.ts +6 -0
  20. package/domains/agents/fetch.d.ts.map +1 -0
  21. package/domains/agents/fetch.js +24 -0
  22. package/domains/agents/fetch.js.map +1 -0
  23. package/domains/agents/resources.d.ts +5 -0
  24. package/domains/agents/resources.d.ts.map +1 -0
  25. package/domains/agents/resources.js +16 -0
  26. package/domains/agents/resources.js.map +1 -0
  27. package/domains/agents/tools.d.ts +5 -0
  28. package/domains/agents/tools.d.ts.map +1 -0
  29. package/domains/agents/tools.js +41 -0
  30. package/domains/agents/tools.js.map +1 -0
  31. package/domains/client.d.ts +53 -0
  32. package/domains/client.d.ts.map +1 -0
  33. package/domains/client.js +62 -0
  34. package/domains/client.js.map +1 -0
  35. package/domains/marshal.d.ts +8 -0
  36. package/domains/marshal.d.ts.map +1 -0
  37. package/domains/marshal.js +17 -0
  38. package/domains/marshal.js.map +1 -0
  39. package/domains/mcpservers/apply.d.ts +4 -0
  40. package/domains/mcpservers/apply.d.ts.map +1 -0
  41. package/domains/mcpservers/apply.js +26 -0
  42. package/domains/mcpservers/apply.js.map +1 -0
  43. package/domains/mcpservers/delete.d.ts +6 -0
  44. package/domains/mcpservers/delete.d.ts.map +1 -0
  45. package/domains/mcpservers/delete.js +42 -0
  46. package/domains/mcpservers/delete.js.map +1 -0
  47. package/domains/mcpservers/fetch.d.ts +7 -0
  48. package/domains/mcpservers/fetch.d.ts.map +1 -0
  49. package/domains/mcpservers/fetch.js +26 -0
  50. package/domains/mcpservers/fetch.js.map +1 -0
  51. package/domains/mcpservers/resources.d.ts +5 -0
  52. package/domains/mcpservers/resources.d.ts.map +1 -0
  53. package/domains/mcpservers/resources.js +16 -0
  54. package/domains/mcpservers/resources.js.map +1 -0
  55. package/domains/mcpservers/tools.d.ts +5 -0
  56. package/domains/mcpservers/tools.d.ts.map +1 -0
  57. package/domains/mcpservers/tools.js +39 -0
  58. package/domains/mcpservers/tools.js.map +1 -0
  59. package/domains/resourcehandler.d.ts +27 -0
  60. package/domains/resourcehandler.d.ts.map +1 -0
  61. package/domains/resourcehandler.js +32 -0
  62. package/domains/resourcehandler.js.map +1 -0
  63. package/domains/resourceuri.d.ts +34 -0
  64. package/domains/resourceuri.d.ts.map +1 -0
  65. package/domains/resourceuri.js +100 -0
  66. package/domains/resourceuri.js.map +1 -0
  67. package/domains/rpcerr.d.ts +8 -0
  68. package/domains/rpcerr.d.ts.map +1 -0
  69. package/domains/rpcerr.js +42 -0
  70. package/domains/rpcerr.js.map +1 -0
  71. package/domains/search/tools.d.ts +5 -0
  72. package/domains/search/tools.d.ts.map +1 -0
  73. package/domains/search/tools.js +125 -0
  74. package/domains/search/tools.js.map +1 -0
  75. package/domains/skills/delete.d.ts +6 -0
  76. package/domains/skills/delete.d.ts.map +1 -0
  77. package/domains/skills/delete.js +38 -0
  78. package/domains/skills/delete.js.map +1 -0
  79. package/domains/skills/fetch.d.ts +6 -0
  80. package/domains/skills/fetch.d.ts.map +1 -0
  81. package/domains/skills/fetch.js +28 -0
  82. package/domains/skills/fetch.js.map +1 -0
  83. package/domains/skills/resources.d.ts +5 -0
  84. package/domains/skills/resources.d.ts.map +1 -0
  85. package/domains/skills/resources.js +25 -0
  86. package/domains/skills/resources.js.map +1 -0
  87. package/domains/skills/tools.d.ts +5 -0
  88. package/domains/skills/tools.d.ts.map +1 -0
  89. package/domains/skills/tools.js +39 -0
  90. package/domains/skills/tools.js.map +1 -0
  91. package/domains/toolresult.d.ts +12 -0
  92. package/domains/toolresult.d.ts.map +1 -0
  93. package/domains/toolresult.js +30 -0
  94. package/domains/toolresult.js.map +1 -0
  95. package/domains/workflowexecutions/tools.d.ts +5 -0
  96. package/domains/workflowexecutions/tools.d.ts.map +1 -0
  97. package/domains/workflowexecutions/tools.js +80 -0
  98. package/domains/workflowexecutions/tools.js.map +1 -0
  99. package/domains/workflows/apply.d.ts +4 -0
  100. package/domains/workflows/apply.d.ts.map +1 -0
  101. package/domains/workflows/apply.js +30 -0
  102. package/domains/workflows/apply.js.map +1 -0
  103. package/domains/workflows/delete.d.ts +3 -0
  104. package/domains/workflows/delete.d.ts.map +1 -0
  105. package/domains/workflows/delete.js +35 -0
  106. package/domains/workflows/delete.js.map +1 -0
  107. package/domains/workflows/fetch.d.ts +6 -0
  108. package/domains/workflows/fetch.d.ts.map +1 -0
  109. package/domains/workflows/fetch.js +25 -0
  110. package/domains/workflows/fetch.js.map +1 -0
  111. package/domains/workflows/resources.d.ts +5 -0
  112. package/domains/workflows/resources.d.ts.map +1 -0
  113. package/domains/workflows/resources.js +16 -0
  114. package/domains/workflows/resources.js.map +1 -0
  115. package/domains/workflows/taskkinds.d.ts +5 -0
  116. package/domains/workflows/taskkinds.d.ts.map +1 -0
  117. package/domains/workflows/taskkinds.js +66 -0
  118. package/domains/workflows/taskkinds.js.map +1 -0
  119. package/domains/workflows/tools.d.ts +5 -0
  120. package/domains/workflows/tools.d.ts.map +1 -0
  121. package/domains/workflows/tools.js +35 -0
  122. package/domains/workflows/tools.js.map +1 -0
  123. package/domains/workflows/validate.d.ts +5 -0
  124. package/domains/workflows/validate.d.ts.map +1 -0
  125. package/domains/workflows/validate.js +113 -0
  126. package/domains/workflows/validate.js.map +1 -0
  127. package/gen/agent.d.ts +385 -0
  128. package/gen/agent.d.ts.map +1 -0
  129. package/gen/agent.js +170 -0
  130. package/gen/agent.js.map +1 -0
  131. package/gen/apply-runtime.d.ts +18 -0
  132. package/gen/apply-runtime.d.ts.map +1 -0
  133. package/gen/apply-runtime.js +50 -0
  134. package/gen/apply-runtime.js.map +1 -0
  135. package/gen/mcpserver.d.ts +289 -0
  136. package/gen/mcpserver.d.ts.map +1 -0
  137. package/gen/mcpserver.js +166 -0
  138. package/gen/mcpserver.js.map +1 -0
  139. package/gen/workflow.d.ts +805 -0
  140. package/gen/workflow.d.ts.map +1 -0
  141. package/gen/workflow.js +842 -0
  142. package/gen/workflow.js.map +1 -0
  143. package/index.d.ts +20 -0
  144. package/index.d.ts.map +1 -0
  145. package/index.js +58 -0
  146. package/index.js.map +1 -0
  147. package/logger.d.ts +20 -0
  148. package/logger.d.ts.map +1 -0
  149. package/logger.js +41 -0
  150. package/logger.js.map +1 -0
  151. package/package.json +43 -0
  152. package/server.d.ts +60 -0
  153. package/server.d.ts.map +1 -0
  154. package/server.js +366 -0
  155. package/server.js.map +1 -0
  156. package/src/cli/mcp-server-stigmer.ts +42 -0
  157. package/src/config.test.ts +88 -0
  158. package/src/config.ts +151 -0
  159. package/src/domains/agents/apply.ts +30 -0
  160. package/src/domains/agents/delete.ts +41 -0
  161. package/src/domains/agents/fetch.ts +33 -0
  162. package/src/domains/agents/resources.ts +20 -0
  163. package/src/domains/agents/tools.ts +68 -0
  164. package/src/domains/apply.integration.test.ts +220 -0
  165. package/src/domains/client.ts +95 -0
  166. package/src/domains/deletes.integration.test.ts +124 -0
  167. package/src/domains/marshal.ts +21 -0
  168. package/src/domains/mcpservers/apply.ts +36 -0
  169. package/src/domains/mcpservers/delete.ts +51 -0
  170. package/src/domains/mcpservers/fetch.ts +35 -0
  171. package/src/domains/mcpservers/resources.ts +20 -0
  172. package/src/domains/mcpservers/tools.ts +74 -0
  173. package/src/domains/reads.integration.test.ts +134 -0
  174. package/src/domains/resourcehandler.ts +90 -0
  175. package/src/domains/resources.integration.test.ts +139 -0
  176. package/src/domains/resourceuri.test.ts +97 -0
  177. package/src/domains/resourceuri.ts +124 -0
  178. package/src/domains/rpcerr.test.ts +62 -0
  179. package/src/domains/rpcerr.ts +46 -0
  180. package/src/domains/search/search.integration.test.ts +127 -0
  181. package/src/domains/search/tools.ts +160 -0
  182. package/src/domains/skills/delete.ts +44 -0
  183. package/src/domains/skills/fetch.ts +38 -0
  184. package/src/domains/skills/resources.ts +33 -0
  185. package/src/domains/skills/tools.ts +67 -0
  186. package/src/domains/toolresult.ts +33 -0
  187. package/src/domains/workflowexecutions/tools.ts +133 -0
  188. package/src/domains/workflows/apply.ts +40 -0
  189. package/src/domains/workflows/delete.ts +44 -0
  190. package/src/domains/workflows/fetch.ts +34 -0
  191. package/src/domains/workflows/resources.ts +20 -0
  192. package/src/domains/workflows/taskkinds.ts +103 -0
  193. package/src/domains/workflows/tools.ts +68 -0
  194. package/src/domains/workflows/validate.integration.test.ts +117 -0
  195. package/src/domains/workflows/validate.ts +144 -0
  196. package/src/domains/workflows/workflow-tools.integration.test.ts +148 -0
  197. package/src/gen/agent.ts +173 -0
  198. package/src/gen/apply-runtime.ts +52 -0
  199. package/src/gen/mcpserver.ts +163 -0
  200. package/src/gen/workflow.ts +858 -0
  201. package/src/http.integration.test.ts +140 -0
  202. package/src/index.ts +66 -0
  203. package/src/logger.ts +49 -0
  204. package/src/server.integration.test.ts +82 -0
  205. package/src/server.ts +414 -0
package/server.js ADDED
@@ -0,0 +1,366 @@
1
+ // Server construction, tool registration, and transport entry points.
2
+ //
3
+ // Mirrors Go internal/server (server.go + http.go) plus the lifecycle helpers
4
+ // from pkg/mcpserver/run.go. The server is stateless: every per-request value
5
+ // (the credential, and thus the gRPC client) is derived from the transport's
6
+ // auth context, so the registration is identical regardless of transport.
7
+ //
8
+ // One structural difference from Go is called out in DD-008: the TS McpServer
9
+ // "assumes ownership" of a single transport, so `both` mode uses one McpServer
10
+ // per transport rather than sharing a single instance across stdio + HTTP.
11
+ import { createServer as createHttpServer } from "node:http";
12
+ import { randomBytes, randomUUID } from "node:crypto";
13
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
16
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
17
+ import { registerAgentResources } from "./domains/agents/resources.js";
18
+ import { registerAgentTools } from "./domains/agents/tools.js";
19
+ import { registerMcpServerResources } from "./domains/mcpservers/resources.js";
20
+ import { registerMcpServerTools } from "./domains/mcpservers/tools.js";
21
+ import { registerSearchTools } from "./domains/search/tools.js";
22
+ import { registerSkillResources } from "./domains/skills/resources.js";
23
+ import { registerSkillTools } from "./domains/skills/tools.js";
24
+ import { registerWorkflowExecutionTools } from "./domains/workflowexecutions/tools.js";
25
+ import { registerTaskKindTools } from "./domains/workflows/taskkinds.js";
26
+ import { registerWorkflowResources } from "./domains/workflows/resources.js";
27
+ import { registerWorkflowTools } from "./domains/workflows/tools.js";
28
+ import { registerValidateWorkflowYamlTool } from "./domains/workflows/validate.js";
29
+ import { log } from "./logger.js";
30
+ /**
31
+ * Server version. Overridable at publish/build time; "dev" otherwise, matching
32
+ * the Go server's ldflags fallback.
33
+ */
34
+ export const SERVER_VERSION = process.env.STIGMER_MCP_VERSION || "dev";
35
+ /** Grace period for draining in-flight HTTP requests on shutdown. */
36
+ const HTTP_SHUTDOWN_GRACE_MS = 5_000;
37
+ /**
38
+ * Well-known location (RFC 9728 §3.1) of the OAuth 2.0 Protected Resource
39
+ * Metadata document. Served only when OAuth discovery is enabled.
40
+ */
41
+ const PROTECTED_RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource";
42
+ /**
43
+ * Build a configured MCP server with every Stigmer tool registered. The backend
44
+ * target (address + startup credential) is captured in each handler's closure.
45
+ */
46
+ export function createServer(target) {
47
+ const server = new McpServer({ name: "mcp-server-stigmer", version: SERVER_VERSION });
48
+ registerTools(server, target);
49
+ registerResources(server, target);
50
+ return server;
51
+ }
52
+ /**
53
+ * Wire up every domain's tools. Each domain returns the names it registered so
54
+ * the startup log's count and roster cannot drift from what is actually wired,
55
+ * matching the Go server's startup log shape.
56
+ */
57
+ function registerTools(server, target) {
58
+ const tools = [
59
+ ...registerSearchTools(server, target),
60
+ ...registerAgentTools(server, target),
61
+ ...registerMcpServerTools(server, target),
62
+ ...registerSkillTools(server, target),
63
+ ...registerWorkflowTools(server, target),
64
+ ...registerValidateWorkflowYamlTool(server, target),
65
+ ...registerTaskKindTools(server, target),
66
+ ...registerWorkflowExecutionTools(server, target),
67
+ ];
68
+ log.info("tools registered", { count: tools.length, tools });
69
+ }
70
+ /**
71
+ * Wire up every domain's resource templates (the discovery-to-read surface).
72
+ * Like {@link registerTools}, each domain returns the names it registered so the
73
+ * startup log stays accurate.
74
+ */
75
+ function registerResources(server, target) {
76
+ const resources = [
77
+ ...registerAgentResources(server, target),
78
+ ...registerMcpServerResources(server, target),
79
+ ...registerSkillResources(server, target),
80
+ ...registerWorkflowResources(server, target),
81
+ ];
82
+ log.info("resources registered", { count: resources.length, resources });
83
+ }
84
+ /**
85
+ * Serve over stdin/stdout until the client disconnects or `signal` aborts.
86
+ *
87
+ * Resolves on a clean disconnect (the MCP discovery probe connects, lists
88
+ * tools/resources, then closes stdin → EOF). Protocol-level errors are logged
89
+ * but do not terminate the process, mirroring the Go server treating EOF /
90
+ * broken pipe as a normal shutdown rather than a failure.
91
+ */
92
+ export async function serveStdio(server, signal) {
93
+ const transport = new StdioServerTransport();
94
+ await server.connect(transport);
95
+ return new Promise((resolve) => {
96
+ const onAbort = () => void server.close();
97
+ signal.addEventListener("abort", onAbort, { once: true });
98
+ // The low-level Server's onclose/onerror are user hooks (not overwritten by
99
+ // connect), so they are the safe place to observe lifecycle transitions.
100
+ server.server.onclose = () => {
101
+ signal.removeEventListener("abort", onAbort);
102
+ resolve();
103
+ };
104
+ server.server.onerror = (err) => log.error("mcp protocol error", { error: err.message });
105
+ });
106
+ }
107
+ /**
108
+ * Serve over Streamable HTTP until `signal` aborts.
109
+ *
110
+ * Each request carries its own credential via the Authorization header; the
111
+ * (non-validating) auth layer extracts it onto `req.auth`, which the transport
112
+ * surfaces to tool handlers as `extra.authInfo`. The application keeps no
113
+ * per-user state.
114
+ *
115
+ * Spike A established the concurrency model: the TS SDK binds one McpServer to
116
+ * one transport to one MCP session (a second `initialize` on a shared server
117
+ * fails with "Server already initialized"). Unlike Go — whose SDK multiplexes
118
+ * sessions over a single shared `*mcp.Server` — the TS server keeps a registry
119
+ * of `sessionId → transport`, each built from `makeServer` on `initialize`. The
120
+ * per-request Bearer passthrough is orthogonal and applies on every request.
121
+ *
122
+ * Each request is wrapped in access logging (16-hex request id, method, path,
123
+ * status, duration) and, when OAuth discovery is enabled, RFC 9728 metadata is
124
+ * served and a WWW-Authenticate challenge is attached to token-less requests.
125
+ * DNS-rebinding allow-lists are intentionally out of parity scope (the Go server
126
+ * has none).
127
+ */
128
+ export async function serveHttp(makeServer, cfg, signal) {
129
+ const sessions = new Map();
130
+ const httpServer = createHttpServer((req, res) => {
131
+ logAccess(req, res);
132
+ void routeRequest(req, res, sessions, makeServer, cfg);
133
+ });
134
+ const addr = `:${cfg.httpPort}`;
135
+ return new Promise((resolve, reject) => {
136
+ httpServer.on("error", reject);
137
+ httpServer.listen(Number(cfg.httpPort), () => {
138
+ log.info("HTTP transport listening", { addr, auth_enabled: cfg.httpAuthEnabled });
139
+ });
140
+ signal.addEventListener("abort", () => {
141
+ log.info("HTTP server shutting down", { grace_period_ms: HTTP_SHUTDOWN_GRACE_MS });
142
+ const force = setTimeout(() => httpServer.closeAllConnections?.(), HTTP_SHUTDOWN_GRACE_MS);
143
+ httpServer.close((err) => {
144
+ clearTimeout(force);
145
+ for (const transport of sessions.values())
146
+ void transport.close();
147
+ sessions.clear();
148
+ if (err)
149
+ reject(err);
150
+ else
151
+ resolve();
152
+ });
153
+ }, { once: true });
154
+ });
155
+ }
156
+ /**
157
+ * Serve stdio and HTTP concurrently. stdio binds a single server; HTTP builds
158
+ * one server per session via the factory. The first transport to settle aborts
159
+ * the other, mirroring Go's serveBoth.
160
+ */
161
+ export async function serveBoth(target, cfg, signal) {
162
+ const linked = new AbortController();
163
+ const onParentAbort = () => linked.abort();
164
+ signal.addEventListener("abort", onParentAbort, { once: true });
165
+ const tasks = [
166
+ serveStdio(createServer(target), linked.signal),
167
+ serveHttp(() => createServer(target), cfg, linked.signal),
168
+ ];
169
+ try {
170
+ await Promise.race(tasks);
171
+ }
172
+ finally {
173
+ linked.abort();
174
+ await Promise.allSettled(tasks);
175
+ signal.removeEventListener("abort", onParentAbort);
176
+ }
177
+ }
178
+ /**
179
+ * Route an inbound HTTP request: liveness probe, the non-validating Bearer
180
+ * extraction, then delegation to the session's MCP transport (reusing an
181
+ * existing session or creating one for an `initialize` request).
182
+ *
183
+ * The token is never validated here — presence is the only check, and it is
184
+ * forwarded unchanged to stigmer-server which performs validation. This mirrors
185
+ * the Go authMiddleware exactly (inventory §4.2).
186
+ */
187
+ async function routeRequest(req, res, sessions, makeServer, cfg) {
188
+ if (req.method === "GET" && req.url === "/health") {
189
+ res.writeHead(200, { "Content-Type": "application/json" });
190
+ res.end(`{"status":"ok"}\n`);
191
+ return;
192
+ }
193
+ // RFC 9728 Protected Resource Metadata — public, unauthenticated, and served
194
+ // only when OAuth discovery is enabled. CORS-open so browser-based clients
195
+ // (e.g. Claude Desktop's connector GUI) can discover the authorization server.
196
+ if (cfg.oauth.enabled && requestPath(req) === PROTECTED_RESOURCE_METADATA_PATH) {
197
+ serveProtectedResourceMetadata(req, res, cfg);
198
+ return;
199
+ }
200
+ // Non-validating Bearer passthrough, applied to EVERY request so each call's
201
+ // gRPC client uses that request's own credential.
202
+ if (cfg.httpAuthEnabled) {
203
+ const token = extractBearerToken(req);
204
+ if (token === "") {
205
+ const headers = { "Content-Type": "text/plain" };
206
+ // RFC 9728 §5.1: point OAuth-capable clients at the metadata document.
207
+ if (cfg.oauth.enabled)
208
+ headers["WWW-Authenticate"] = bearerChallenge(cfg);
209
+ res.writeHead(401, headers);
210
+ res.end("missing or malformed Authorization: Bearer header");
211
+ return;
212
+ }
213
+ req.auth = { token, clientId: "stigmer-mcp-passthrough", scopes: [] };
214
+ }
215
+ const sessionId = headerValue(req, "mcp-session-id");
216
+ // Established session → dispatch to its transport.
217
+ if (sessionId !== undefined) {
218
+ const transport = sessions.get(sessionId);
219
+ if (transport === undefined) {
220
+ res.writeHead(404, { "Content-Type": "text/plain" });
221
+ res.end("unknown or expired MCP session");
222
+ return;
223
+ }
224
+ await transport.handleRequest(req, res);
225
+ return;
226
+ }
227
+ // No session → only an initialize POST may open one.
228
+ if (req.method !== "POST") {
229
+ res.writeHead(400, { "Content-Type": "text/plain" });
230
+ res.end("missing Mcp-Session-Id header");
231
+ return;
232
+ }
233
+ const body = await readJsonBody(req);
234
+ if (!isInitializeRequest(body)) {
235
+ res.writeHead(400, { "Content-Type": "text/plain" });
236
+ res.end("Bad Request: an initialize request is required to open a session");
237
+ return;
238
+ }
239
+ const transport = new StreamableHTTPServerTransport({
240
+ sessionIdGenerator: () => randomUUID(),
241
+ onsessioninitialized: (id) => {
242
+ sessions.set(id, transport);
243
+ },
244
+ onsessionclosed: (id) => {
245
+ sessions.delete(id);
246
+ },
247
+ });
248
+ transport.onclose = () => {
249
+ if (transport.sessionId !== undefined)
250
+ sessions.delete(transport.sessionId);
251
+ };
252
+ await makeServer().connect(transport);
253
+ await transport.handleRequest(req, res, body);
254
+ }
255
+ /** Return a single header value, collapsing the array form Node may produce. */
256
+ function headerValue(req, name) {
257
+ const v = req.headers[name];
258
+ return Array.isArray(v) ? v[0] : v;
259
+ }
260
+ /** Read and JSON-parse a request body (used to classify the initialize POST). */
261
+ function readJsonBody(req) {
262
+ return new Promise((resolve, reject) => {
263
+ const chunks = [];
264
+ req.on("data", (chunk) => chunks.push(chunk));
265
+ req.on("end", () => {
266
+ const raw = Buffer.concat(chunks).toString("utf8");
267
+ try {
268
+ resolve(raw === "" ? null : JSON.parse(raw));
269
+ }
270
+ catch (err) {
271
+ reject(err);
272
+ }
273
+ });
274
+ req.on("error", reject);
275
+ });
276
+ }
277
+ /** Request path without the query string (mirrors Go's r.URL.Path). */
278
+ function requestPath(req) {
279
+ const url = req.url ?? "/";
280
+ const q = url.indexOf("?");
281
+ return q === -1 ? url : url.slice(0, q);
282
+ }
283
+ /**
284
+ * Attach access logging to a request: on completion, log a 16-hex request id,
285
+ * method, path, status, and duration. Mirrors Go's requestLogger middleware.
286
+ */
287
+ function logAccess(req, res) {
288
+ const start = Date.now();
289
+ const requestId = randomBytes(8).toString("hex");
290
+ res.on("finish", () => {
291
+ log.info("http request", {
292
+ request_id: requestId,
293
+ method: req.method,
294
+ path: requestPath(req),
295
+ status: res.statusCode,
296
+ duration_ms: Date.now() - start,
297
+ });
298
+ });
299
+ }
300
+ /**
301
+ * Serve the OAuth 2.0 Protected Resource Metadata document (RFC 9728), answering
302
+ * the CORS preflight (OPTIONS) and the GET. Mirrors the Go SDK's
303
+ * ProtectedResourceMetadataHandler output shape.
304
+ */
305
+ function serveProtectedResourceMetadata(req, res, cfg) {
306
+ res.setHeader("Access-Control-Allow-Origin", "*");
307
+ if (req.method === "OPTIONS") {
308
+ res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
309
+ res.setHeader("Access-Control-Allow-Headers", "*");
310
+ res.writeHead(204);
311
+ res.end();
312
+ return;
313
+ }
314
+ const metadata = {
315
+ resource: cfg.oauth.resource,
316
+ authorization_servers: cfg.oauth.authorizationServers,
317
+ bearer_methods_supported: ["header"],
318
+ resource_name: "Stigmer MCP Server",
319
+ };
320
+ if (cfg.oauth.scopesSupported.length > 0) {
321
+ metadata.scopes_supported = cfg.oauth.scopesSupported;
322
+ }
323
+ res.writeHead(200, { "Content-Type": "application/json" });
324
+ res.end(JSON.stringify(metadata));
325
+ }
326
+ /**
327
+ * Build the WWW-Authenticate challenge pointing OAuth clients at this server's
328
+ * protected-resource-metadata document (RFC 9728 §5.1). Mirrors Go bearerChallenge.
329
+ */
330
+ function bearerChallenge(cfg) {
331
+ const metadataURL = cfg.oauth.resource.replace(/\/+$/, "") + PROTECTED_RESOURCE_METADATA_PATH;
332
+ const params = [`realm="stigmer"`, `resource_metadata="${metadataURL}"`];
333
+ if (cfg.oauth.scopesSupported.length > 0) {
334
+ params.push(`scope="${cfg.oauth.scopesSupported.join(" ")}"`);
335
+ }
336
+ return "Bearer " + params.join(", ");
337
+ }
338
+ /** Parse the "Authorization: Bearer <token>" header; "" when absent/malformed. */
339
+ function extractBearerToken(req) {
340
+ const header = req.headers.authorization;
341
+ if (!header)
342
+ return "";
343
+ const prefix = "Bearer ";
344
+ if (!header.startsWith(prefix))
345
+ return "";
346
+ return header.slice(prefix.length).trim();
347
+ }
348
+ /**
349
+ * Reports whether an error represents a clean client disconnect (EOF / broken
350
+ * pipe / abort) rather than a genuine failure, so discovery probes that connect
351
+ * and immediately disconnect do not cause a non-zero exit. Mirrors Go's
352
+ * isNormalShutdown.
353
+ */
354
+ export function isNormalShutdown(err) {
355
+ if (err == null)
356
+ return true;
357
+ const name = err.name;
358
+ if (name === "AbortError")
359
+ return true;
360
+ const code = err.code;
361
+ if (code === "EPIPE" || code === "ABORT_ERR" || code === "ECONNRESET")
362
+ return true;
363
+ const message = err instanceof Error ? err.message : String(err);
364
+ return message.includes("EOF") || message.includes("broken pipe");
365
+ }
366
+ //# sourceMappingURL=server.js.map
package/server.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ // mcp-server-stigmer — the Model Context Protocol server for the Stigmer
3
+ // platform. Mirrors Go cmd/mcp-server-stigmer/main.go.
4
+ //
5
+ // Usage:
6
+ // mcp-server-stigmer Transport from STIGMER_MCP_TRANSPORT (default stdio)
7
+ // mcp-server-stigmer stdio stdin/stdout JSON-RPC
8
+ // mcp-server-stigmer http Streamable HTTP
9
+ // mcp-server-stigmer both Both transports simultaneously
10
+ //
11
+ // All other settings are read from STIGMER_-prefixed environment variables;
12
+ // see ./config.ts for the full list.
13
+
14
+ import { defaultConfig, run, type Transport } from "../index.js";
15
+
16
+ const VALID_TRANSPORTS: readonly string[] = ["stdio", "http", "both"];
17
+
18
+ async function main(): Promise<void> {
19
+ let cfg = defaultConfig();
20
+
21
+ // A positional subcommand overrides the env-var-based transport.
22
+ const sub = process.argv[2];
23
+ if (sub !== undefined) {
24
+ if (!VALID_TRANSPORTS.includes(sub)) {
25
+ process.stderr.write(`unknown subcommand "${sub}" (expected stdio, http, or both)\n`);
26
+ process.exit(1);
27
+ }
28
+ cfg = { ...cfg, transport: sub as Transport };
29
+ }
30
+
31
+ const controller = new AbortController();
32
+ const shutdown = () => controller.abort();
33
+ process.once("SIGINT", shutdown);
34
+ process.once("SIGTERM", shutdown);
35
+
36
+ await run(cfg, controller.signal);
37
+ }
38
+
39
+ main().catch((err: unknown) => {
40
+ process.stderr.write(`fatal: ${err instanceof Error ? err.message : String(err)}\n`);
41
+ process.exit(1);
42
+ });
@@ -0,0 +1,88 @@
1
+ import { beforeAll, describe, expect, it } from "vitest";
2
+
3
+ import { loadConfigFromEnv, validateConfig, type Config } from "./config";
4
+ import { configureLogger } from "./logger";
5
+
6
+ // Quiet the soft-validation warnings during these tests.
7
+ beforeAll(() => configureLogger({ level: "error", format: "text" }));
8
+
9
+ /** Build a config from an isolated env (defaults applied; process.env ignored). */
10
+ function fromEnv(env: Record<string, string> = {}): Config {
11
+ return loadConfigFromEnv(env);
12
+ }
13
+
14
+ describe("loadConfigFromEnv", () => {
15
+ it("applies development defaults", () => {
16
+ const c = fromEnv();
17
+ expect(c.stigmerServerAddress).toBe("localhost:7234");
18
+ expect(c.apiKey).toBe("");
19
+ expect(c.transport).toBe("stdio");
20
+ expect(c.httpPort).toBe("8080");
21
+ expect(c.httpAuthEnabled).toBe(true);
22
+ expect(c.logFormat).toBe("text");
23
+ expect(c.logLevel).toBe("info");
24
+ expect(c.oauth.enabled).toBe(false);
25
+ });
26
+
27
+ it("reads overrides and lowercases the transport", () => {
28
+ const c = fromEnv({
29
+ STIGMER_MCP_TRANSPORT: "HTTP",
30
+ STIGMER_API_KEY: "sk_live",
31
+ STIGMER_MCP_HTTP_AUTH_ENABLED: "false",
32
+ });
33
+ expect(c.transport).toBe("http");
34
+ expect(c.apiKey).toBe("sk_live");
35
+ expect(c.httpAuthEnabled).toBe(false);
36
+ });
37
+
38
+ it("only the exact string 'true' enables boolean flags (Go parity)", () => {
39
+ expect(fromEnv({ STIGMER_MCP_HTTP_AUTH_ENABLED: "TRUE" }).httpAuthEnabled).toBe(false);
40
+ expect(fromEnv({ STIGMER_MCP_OAUTH_ENABLED: "1" }).oauth.enabled).toBe(false);
41
+ });
42
+
43
+ it("parses comma-separated lists, dropping blanks", () => {
44
+ const c = fromEnv({
45
+ STIGMER_MCP_OAUTH_ENABLED: "true",
46
+ STIGMER_MCP_OAUTH_RESOURCE: "https://mcp.stigmer.ai",
47
+ STIGMER_MCP_OAUTH_AUTHORIZATION_SERVERS: "https://a , , https://b",
48
+ });
49
+ expect(c.oauth.authorizationServers).toEqual(["https://a", "https://b"]);
50
+ });
51
+ });
52
+
53
+ describe("validateConfig", () => {
54
+ it("accepts the default config", () => {
55
+ expect(() => validateConfig(fromEnv())).not.toThrow();
56
+ });
57
+
58
+ it("rejects an invalid transport", () => {
59
+ expect(() => validateConfig(fromEnv({ STIGMER_MCP_TRANSPORT: "carrier-pigeon" }))).toThrow(
60
+ /STIGMER_MCP_TRANSPORT/,
61
+ );
62
+ });
63
+
64
+ it("rejects an empty server address", () => {
65
+ expect(() => validateConfig({ ...fromEnv(), stigmerServerAddress: "" })).toThrow(
66
+ /STIGMER_SERVER_ADDRESS/,
67
+ );
68
+ });
69
+
70
+ it("rejects an invalid log format and level", () => {
71
+ expect(() => validateConfig(fromEnv({ STIGMER_MCP_LOG_FORMAT: "xml" }))).toThrow(/LOG_FORMAT/);
72
+ expect(() => validateConfig(fromEnv({ STIGMER_MCP_LOG_LEVEL: "trace" }))).toThrow(/LOG_LEVEL/);
73
+ });
74
+
75
+ it("requires oauth resource and servers when oauth is enabled", () => {
76
+ expect(() => validateConfig(fromEnv({ STIGMER_MCP_OAUTH_ENABLED: "true" }))).toThrow(
77
+ /OAUTH_RESOURCE/,
78
+ );
79
+ expect(() =>
80
+ validateConfig(
81
+ fromEnv({
82
+ STIGMER_MCP_OAUTH_ENABLED: "true",
83
+ STIGMER_MCP_OAUTH_RESOURCE: "https://mcp.stigmer.ai",
84
+ }),
85
+ ),
86
+ ).toThrow(/AUTHORIZATION_SERVERS/);
87
+ });
88
+ });
package/src/config.ts ADDED
@@ -0,0 +1,151 @@
1
+ // Environment-variable configuration for mcp-server-stigmer.
2
+ //
3
+ // Every value is read from a STIGMER_-prefixed environment variable with a
4
+ // development-friendly default. This is the embeddable configuration surface
5
+ // (plain types, no internal coupling) and the parity mirror of the Go server's
6
+ // internal/config + pkg/mcpserver Config (inventory §4.4) — the validation
7
+ // reproduces Go's hard-errors AND warnings, not just the defaults.
8
+
9
+ import { log, type LogFormat, type LogLevel } from "./logger.js";
10
+
11
+ /** Communication mode between MCP clients and the server. */
12
+ export type Transport = "stdio" | "http" | "both";
13
+
14
+ /**
15
+ * OAuth 2.0 Protected Resource Metadata (RFC 9728) discovery settings.
16
+ *
17
+ * Purely additive: the server stays a stateless Bearer passthrough that never
18
+ * validates tokens. Issuer values come from deployment config, never code, so
19
+ * the OSS server is issuer-agnostic. (HTTP discovery wiring itself lands in T02.)
20
+ */
21
+ export interface OAuthConfig {
22
+ readonly enabled: boolean;
23
+ readonly resource: string;
24
+ readonly authorizationServers: string[];
25
+ readonly scopesSupported: string[];
26
+ }
27
+
28
+ /** Runtime configuration for the MCP server. */
29
+ export interface Config {
30
+ /** gRPC dial target for stigmer-server (e.g. "localhost:7234"). */
31
+ readonly stigmerServerAddress: string;
32
+ /**
33
+ * API key for stigmer-server. Used for stdio/both; in http mode every
34
+ * request carries its own Bearer token. Empty when targeting an
35
+ * unauthenticated local backend.
36
+ */
37
+ readonly apiKey: string;
38
+ readonly transport: Transport;
39
+ readonly httpPort: string;
40
+ /** Whether HTTP requests require an Authorization: Bearer header. */
41
+ readonly httpAuthEnabled: boolean;
42
+ readonly oauth: OAuthConfig;
43
+ readonly logFormat: LogFormat;
44
+ readonly logLevel: LogLevel;
45
+ }
46
+
47
+ const VALID_TRANSPORTS: readonly string[] = ["stdio", "http", "both"];
48
+ const VALID_LOG_FORMATS: readonly string[] = ["text", "json"];
49
+ const VALID_LOG_LEVELS: readonly string[] = ["debug", "info", "warn", "error"];
50
+
51
+ /** Reads configuration from the process environment, applying defaults. */
52
+ export function loadConfigFromEnv(env: NodeJS.ProcessEnv = process.env): Config {
53
+ return {
54
+ stigmerServerAddress: envOr(env, "STIGMER_SERVER_ADDRESS", "localhost:7234"),
55
+ apiKey: env.STIGMER_API_KEY ?? "",
56
+ transport: envOr(env, "STIGMER_MCP_TRANSPORT", "stdio").toLowerCase() as Transport,
57
+ httpPort: envOr(env, "STIGMER_MCP_HTTP_PORT", "8080"),
58
+ // Go semantics: only the exact string "true" enables auth.
59
+ httpAuthEnabled: envOr(env, "STIGMER_MCP_HTTP_AUTH_ENABLED", "true") === "true",
60
+ oauth: {
61
+ enabled: env.STIGMER_MCP_OAUTH_ENABLED === "true",
62
+ resource: (env.STIGMER_MCP_OAUTH_RESOURCE ?? "").trim(),
63
+ authorizationServers: splitList(env.STIGMER_MCP_OAUTH_AUTHORIZATION_SERVERS),
64
+ scopesSupported: splitList(env.STIGMER_MCP_OAUTH_SCOPES_SUPPORTED),
65
+ },
66
+ logFormat: envOr(env, "STIGMER_MCP_LOG_FORMAT", "text").toLowerCase() as LogFormat,
67
+ logLevel: envOr(env, "STIGMER_MCP_LOG_LEVEL", "info").toLowerCase() as LogLevel,
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Validates invariants that must hold before the server starts. Throws on hard
73
+ * errors; emits warnings (via the configured logger) for the soft cases the Go
74
+ * server warns about. Configure the logger before calling this so warnings are
75
+ * formatted consistently.
76
+ */
77
+ export function validateConfig(cfg: Config): void {
78
+ if (!VALID_TRANSPORTS.includes(cfg.transport)) {
79
+ throw new Error(
80
+ `invalid STIGMER_MCP_TRANSPORT "${cfg.transport}": must be stdio, http, or both`,
81
+ );
82
+ }
83
+
84
+ if (cfg.stigmerServerAddress === "") {
85
+ throw new Error("STIGMER_SERVER_ADDRESS must not be empty");
86
+ }
87
+
88
+ if (cfg.stigmerServerAddress.includes("://")) {
89
+ log.warn(
90
+ "STIGMER_SERVER_ADDRESS contains a URL scheme; gRPC targets are host:port — " +
91
+ "the scheme is informational and TLS is derived from the port",
92
+ { value: cfg.stigmerServerAddress },
93
+ );
94
+ } else if (!hasExplicitPort(cfg.stigmerServerAddress)) {
95
+ log.warn(
96
+ "STIGMER_SERVER_ADDRESS has no explicit port; :443 with TLS is assumed for non-loopback addresses",
97
+ { value: cfg.stigmerServerAddress },
98
+ );
99
+ }
100
+
101
+ if (!VALID_LOG_FORMATS.includes(cfg.logFormat)) {
102
+ throw new Error(`invalid STIGMER_MCP_LOG_FORMAT "${cfg.logFormat}": must be text or json`);
103
+ }
104
+
105
+ if (!VALID_LOG_LEVELS.includes(cfg.logLevel)) {
106
+ throw new Error(
107
+ `invalid STIGMER_MCP_LOG_LEVEL "${cfg.logLevel}": must be debug, info, warn, or error`,
108
+ );
109
+ }
110
+
111
+ if (cfg.oauth.enabled) {
112
+ if (cfg.oauth.resource === "") {
113
+ throw new Error(
114
+ "STIGMER_MCP_OAUTH_RESOURCE must be set when STIGMER_MCP_OAUTH_ENABLED is true",
115
+ );
116
+ }
117
+ if (cfg.oauth.authorizationServers.length === 0) {
118
+ throw new Error(
119
+ "STIGMER_MCP_OAUTH_AUTHORIZATION_SERVERS must list at least one issuer when STIGMER_MCP_OAUTH_ENABLED is true",
120
+ );
121
+ }
122
+ }
123
+ }
124
+
125
+ function envOr(env: NodeJS.ProcessEnv, key: string, fallback: string): string {
126
+ const v = env[key];
127
+ return v !== undefined && v !== "" ? v : fallback;
128
+ }
129
+
130
+ /** Parses a comma-separated value into trimmed, non-empty entries. */
131
+ function splitList(raw: string | undefined): string[] {
132
+ if (!raw || raw.trim() === "") return [];
133
+ return raw
134
+ .split(",")
135
+ .map((p) => p.trim())
136
+ .filter((p) => p !== "");
137
+ }
138
+
139
+ /**
140
+ * Reports whether an authority carries an explicit port. Tolerates bracketed
141
+ * IPv6 (`[::1]:443`) and treats bare IPv6 (`::1`) as port-less, matching the
142
+ * intent of Go's net.SplitHostPort in the config warning path.
143
+ */
144
+ function hasExplicitPort(authority: string): boolean {
145
+ if (authority.startsWith("[")) {
146
+ const close = authority.indexOf("]");
147
+ return close !== -1 && authority.slice(close + 1).startsWith(":");
148
+ }
149
+ const lastColon = authority.lastIndexOf(":");
150
+ return lastColon !== -1 && authority.indexOf(":") === lastColon;
151
+ }