@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.
- package/LICENSE +190 -0
- package/README.md +157 -0
- package/cli/mcp-server-stigmer.d.ts +3 -0
- package/cli/mcp-server-stigmer.d.ts.map +1 -0
- package/cli/mcp-server-stigmer.js +201 -0
- package/cli/mcp-server-stigmer.js.map +1 -0
- package/config.d.ts +44 -0
- package/config.d.ts.map +1 -0
- package/config.js +92 -0
- package/config.js.map +1 -0
- package/domains/agents/apply.d.ts +4 -0
- package/domains/agents/apply.d.ts.map +1 -0
- package/domains/agents/apply.js +25 -0
- package/domains/agents/apply.js.map +1 -0
- package/domains/agents/delete.d.ts +3 -0
- package/domains/agents/delete.d.ts.map +1 -0
- package/domains/agents/delete.js +35 -0
- package/domains/agents/delete.js.map +1 -0
- package/domains/agents/fetch.d.ts +6 -0
- package/domains/agents/fetch.d.ts.map +1 -0
- package/domains/agents/fetch.js +24 -0
- package/domains/agents/fetch.js.map +1 -0
- package/domains/agents/resources.d.ts +5 -0
- package/domains/agents/resources.d.ts.map +1 -0
- package/domains/agents/resources.js +16 -0
- package/domains/agents/resources.js.map +1 -0
- package/domains/agents/tools.d.ts +5 -0
- package/domains/agents/tools.d.ts.map +1 -0
- package/domains/agents/tools.js +41 -0
- package/domains/agents/tools.js.map +1 -0
- package/domains/client.d.ts +53 -0
- package/domains/client.d.ts.map +1 -0
- package/domains/client.js +62 -0
- package/domains/client.js.map +1 -0
- package/domains/marshal.d.ts +8 -0
- package/domains/marshal.d.ts.map +1 -0
- package/domains/marshal.js +17 -0
- package/domains/marshal.js.map +1 -0
- package/domains/mcpservers/apply.d.ts +4 -0
- package/domains/mcpservers/apply.d.ts.map +1 -0
- package/domains/mcpservers/apply.js +26 -0
- package/domains/mcpservers/apply.js.map +1 -0
- package/domains/mcpservers/delete.d.ts +6 -0
- package/domains/mcpservers/delete.d.ts.map +1 -0
- package/domains/mcpservers/delete.js +42 -0
- package/domains/mcpservers/delete.js.map +1 -0
- package/domains/mcpservers/fetch.d.ts +7 -0
- package/domains/mcpservers/fetch.d.ts.map +1 -0
- package/domains/mcpservers/fetch.js +26 -0
- package/domains/mcpservers/fetch.js.map +1 -0
- package/domains/mcpservers/resources.d.ts +5 -0
- package/domains/mcpservers/resources.d.ts.map +1 -0
- package/domains/mcpservers/resources.js +16 -0
- package/domains/mcpservers/resources.js.map +1 -0
- package/domains/mcpservers/tools.d.ts +5 -0
- package/domains/mcpservers/tools.d.ts.map +1 -0
- package/domains/mcpservers/tools.js +39 -0
- package/domains/mcpservers/tools.js.map +1 -0
- package/domains/resourcehandler.d.ts +27 -0
- package/domains/resourcehandler.d.ts.map +1 -0
- package/domains/resourcehandler.js +32 -0
- package/domains/resourcehandler.js.map +1 -0
- package/domains/resourceuri.d.ts +34 -0
- package/domains/resourceuri.d.ts.map +1 -0
- package/domains/resourceuri.js +100 -0
- package/domains/resourceuri.js.map +1 -0
- package/domains/rpcerr.d.ts +8 -0
- package/domains/rpcerr.d.ts.map +1 -0
- package/domains/rpcerr.js +42 -0
- package/domains/rpcerr.js.map +1 -0
- package/domains/search/tools.d.ts +5 -0
- package/domains/search/tools.d.ts.map +1 -0
- package/domains/search/tools.js +125 -0
- package/domains/search/tools.js.map +1 -0
- package/domains/skills/delete.d.ts +6 -0
- package/domains/skills/delete.d.ts.map +1 -0
- package/domains/skills/delete.js +38 -0
- package/domains/skills/delete.js.map +1 -0
- package/domains/skills/fetch.d.ts +6 -0
- package/domains/skills/fetch.d.ts.map +1 -0
- package/domains/skills/fetch.js +28 -0
- package/domains/skills/fetch.js.map +1 -0
- package/domains/skills/resources.d.ts +5 -0
- package/domains/skills/resources.d.ts.map +1 -0
- package/domains/skills/resources.js +25 -0
- package/domains/skills/resources.js.map +1 -0
- package/domains/skills/tools.d.ts +5 -0
- package/domains/skills/tools.d.ts.map +1 -0
- package/domains/skills/tools.js +39 -0
- package/domains/skills/tools.js.map +1 -0
- package/domains/toolresult.d.ts +12 -0
- package/domains/toolresult.d.ts.map +1 -0
- package/domains/toolresult.js +30 -0
- package/domains/toolresult.js.map +1 -0
- package/domains/workflowexecutions/tools.d.ts +5 -0
- package/domains/workflowexecutions/tools.d.ts.map +1 -0
- package/domains/workflowexecutions/tools.js +80 -0
- package/domains/workflowexecutions/tools.js.map +1 -0
- package/domains/workflows/apply.d.ts +4 -0
- package/domains/workflows/apply.d.ts.map +1 -0
- package/domains/workflows/apply.js +30 -0
- package/domains/workflows/apply.js.map +1 -0
- package/domains/workflows/delete.d.ts +3 -0
- package/domains/workflows/delete.d.ts.map +1 -0
- package/domains/workflows/delete.js +35 -0
- package/domains/workflows/delete.js.map +1 -0
- package/domains/workflows/fetch.d.ts +6 -0
- package/domains/workflows/fetch.d.ts.map +1 -0
- package/domains/workflows/fetch.js +25 -0
- package/domains/workflows/fetch.js.map +1 -0
- package/domains/workflows/resources.d.ts +5 -0
- package/domains/workflows/resources.d.ts.map +1 -0
- package/domains/workflows/resources.js +16 -0
- package/domains/workflows/resources.js.map +1 -0
- package/domains/workflows/taskkinds.d.ts +5 -0
- package/domains/workflows/taskkinds.d.ts.map +1 -0
- package/domains/workflows/taskkinds.js +66 -0
- package/domains/workflows/taskkinds.js.map +1 -0
- package/domains/workflows/tools.d.ts +5 -0
- package/domains/workflows/tools.d.ts.map +1 -0
- package/domains/workflows/tools.js +35 -0
- package/domains/workflows/tools.js.map +1 -0
- package/domains/workflows/validate.d.ts +5 -0
- package/domains/workflows/validate.d.ts.map +1 -0
- package/domains/workflows/validate.js +113 -0
- package/domains/workflows/validate.js.map +1 -0
- package/gen/agent.d.ts +385 -0
- package/gen/agent.d.ts.map +1 -0
- package/gen/agent.js +170 -0
- package/gen/agent.js.map +1 -0
- package/gen/apply-runtime.d.ts +18 -0
- package/gen/apply-runtime.d.ts.map +1 -0
- package/gen/apply-runtime.js +50 -0
- package/gen/apply-runtime.js.map +1 -0
- package/gen/mcpserver.d.ts +289 -0
- package/gen/mcpserver.d.ts.map +1 -0
- package/gen/mcpserver.js +166 -0
- package/gen/mcpserver.js.map +1 -0
- package/gen/workflow.d.ts +805 -0
- package/gen/workflow.d.ts.map +1 -0
- package/gen/workflow.js +842 -0
- package/gen/workflow.js.map +1 -0
- package/index.d.ts +20 -0
- package/index.d.ts.map +1 -0
- package/index.js +58 -0
- package/index.js.map +1 -0
- package/logger.d.ts +20 -0
- package/logger.d.ts.map +1 -0
- package/logger.js +41 -0
- package/logger.js.map +1 -0
- package/package.json +43 -0
- package/server.d.ts +60 -0
- package/server.d.ts.map +1 -0
- package/server.js +366 -0
- package/server.js.map +1 -0
- package/src/cli/mcp-server-stigmer.ts +42 -0
- package/src/config.test.ts +88 -0
- package/src/config.ts +151 -0
- package/src/domains/agents/apply.ts +30 -0
- package/src/domains/agents/delete.ts +41 -0
- package/src/domains/agents/fetch.ts +33 -0
- package/src/domains/agents/resources.ts +20 -0
- package/src/domains/agents/tools.ts +68 -0
- package/src/domains/apply.integration.test.ts +220 -0
- package/src/domains/client.ts +95 -0
- package/src/domains/deletes.integration.test.ts +124 -0
- package/src/domains/marshal.ts +21 -0
- package/src/domains/mcpservers/apply.ts +36 -0
- package/src/domains/mcpservers/delete.ts +51 -0
- package/src/domains/mcpservers/fetch.ts +35 -0
- package/src/domains/mcpservers/resources.ts +20 -0
- package/src/domains/mcpservers/tools.ts +74 -0
- package/src/domains/reads.integration.test.ts +134 -0
- package/src/domains/resourcehandler.ts +90 -0
- package/src/domains/resources.integration.test.ts +139 -0
- package/src/domains/resourceuri.test.ts +97 -0
- package/src/domains/resourceuri.ts +124 -0
- package/src/domains/rpcerr.test.ts +62 -0
- package/src/domains/rpcerr.ts +46 -0
- package/src/domains/search/search.integration.test.ts +127 -0
- package/src/domains/search/tools.ts +160 -0
- package/src/domains/skills/delete.ts +44 -0
- package/src/domains/skills/fetch.ts +38 -0
- package/src/domains/skills/resources.ts +33 -0
- package/src/domains/skills/tools.ts +67 -0
- package/src/domains/toolresult.ts +33 -0
- package/src/domains/workflowexecutions/tools.ts +133 -0
- package/src/domains/workflows/apply.ts +40 -0
- package/src/domains/workflows/delete.ts +44 -0
- package/src/domains/workflows/fetch.ts +34 -0
- package/src/domains/workflows/resources.ts +20 -0
- package/src/domains/workflows/taskkinds.ts +103 -0
- package/src/domains/workflows/tools.ts +68 -0
- package/src/domains/workflows/validate.integration.test.ts +117 -0
- package/src/domains/workflows/validate.ts +144 -0
- package/src/domains/workflows/workflow-tools.integration.test.ts +148 -0
- package/src/gen/agent.ts +173 -0
- package/src/gen/apply-runtime.ts +52 -0
- package/src/gen/mcpserver.ts +163 -0
- package/src/gen/workflow.ts +858 -0
- package/src/http.integration.test.ts +140 -0
- package/src/index.ts +66 -0
- package/src/logger.ts +49 -0
- package/src/server.integration.test.ts +82 -0
- 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
|
+
}
|