dxcomplete 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/.env.example +0 -7
  2. package/README.md +68 -103
  3. package/dist/cli.js +2 -24
  4. package/dist/validate.js +10 -26
  5. package/docs/cost-model.md +2 -2
  6. package/docs/decision-basis.md +5 -11
  7. package/docs/diagrams.md +3 -3
  8. package/docs/index.md +25 -39
  9. package/docs/model.md +15 -23
  10. package/docs/open-questions.md +1 -1
  11. package/docs/taxonomy.md +7 -8
  12. package/docs/workflows.md +3 -3
  13. package/package.json +24 -24
  14. package/templates/process/README.md +11 -11
  15. package/templates/process/controls.yml +19 -19
  16. package/templates/process/cost-model.yml +3 -3
  17. package/templates/process/decision-basis.yml +4 -4
  18. package/templates/process/diagrams/00-decision-basis.mmd +1 -1
  19. package/templates/process/diagrams/00-overview.mmd +1 -1
  20. package/templates/process/diagrams/01-intake-triage.mmd +4 -4
  21. package/templates/process/diagrams/02-product-definition.mmd +3 -3
  22. package/templates/process/diagrams/03-engineering-execution.mmd +1 -1
  23. package/templates/process/diagrams/04-qa-verification.mmd +1 -1
  24. package/templates/process/diagrams/05-product-validation.mmd +1 -1
  25. package/templates/process/diagrams/06-change-release-control.mmd +1 -1
  26. package/templates/process/diagrams/07-deployment-operations.mmd +1 -1
  27. package/templates/process/diagrams/08-support-incident-management.mmd +1 -1
  28. package/templates/process/diagrams/09-problem-improvement.mmd +1 -1
  29. package/templates/process/diagrams/10-risk-control-management.mmd +1 -1
  30. package/templates/process/diagrams/11-audit-evidence-capture.mmd +1 -1
  31. package/templates/process/roles.yml +6 -6
  32. package/templates/process/taxonomy.yml +46 -46
  33. package/templates/process/workflows.yml +29 -29
  34. package/website/account.html +57 -0
  35. package/website/app.js +177 -0
  36. package/website/flow.html +4 -0
  37. package/website/glossary.html +4 -0
  38. package/website/index.html +4 -0
  39. package/website/objects.html +4 -0
  40. package/website/operating-guide.html +4 -0
  41. package/website/outcomes.html +4 -0
  42. package/website/phase-build.html +4 -0
  43. package/website/phase-elicit.html +4 -0
  44. package/website/phase-go-live.html +4 -0
  45. package/website/phase-measure.html +4 -0
  46. package/website/phase-operate.html +4 -0
  47. package/website/phase-orient.html +4 -0
  48. package/website/phase-weigh.html +4 -0
  49. package/website/roles.html +4 -0
  50. package/website/styles.css +217 -1
  51. package/dist/http/service.d.ts +0 -7
  52. package/dist/http/service.js +0 -725
  53. package/dist/mcp/docs.d.ts +0 -114
  54. package/dist/mcp/docs.js +0 -626
  55. package/dist/mcp/server.d.ts +0 -20
  56. package/dist/mcp/server.js +0 -3059
  57. package/dist/runtime/auth.d.ts +0 -162
  58. package/dist/runtime/auth.js +0 -394
  59. package/dist/runtime/check.d.ts +0 -7
  60. package/dist/runtime/check.js +0 -16
  61. package/dist/runtime/config.d.ts +0 -17
  62. package/dist/runtime/config.js +0 -93
  63. package/dist/runtime/mongo.d.ts +0 -9
  64. package/dist/runtime/mongo.js +0 -56
  65. package/dist/runtime/records.d.ts +0 -427
  66. package/dist/runtime/records.js +0 -2092
  67. package/scripts/check-env-surface.mjs +0 -136
  68. package/scripts/check-public-copy.mjs +0 -263
  69. package/scripts/check-service-boundary.mjs +0 -63
  70. package/scripts/runtime-work-order.mjs +0 -506
  71. package/scripts/smoke-mcp-http.mjs +0 -4026
  72. package/src/cli.ts +0 -268
  73. package/src/http/server.ts +0 -314
  74. package/src/http/service.ts +0 -934
  75. package/src/init.ts +0 -262
  76. package/src/install-manifest.ts +0 -144
  77. package/src/mcp/docs.ts +0 -777
  78. package/src/mcp/server.ts +0 -4580
  79. package/src/package-root.ts +0 -31
  80. package/src/runtime/actor.ts +0 -61
  81. package/src/runtime/auth.ts +0 -673
  82. package/src/runtime/check.ts +0 -18
  83. package/src/runtime/config.ts +0 -128
  84. package/src/runtime/mongo.ts +0 -89
  85. package/src/runtime/records.ts +0 -3205
  86. package/src/runtime/workspace.ts +0 -155
  87. package/src/upgrade.ts +0 -356
  88. package/src/validate.ts +0 -141
  89. package/src/version.ts +0 -16
package/src/cli.ts DELETED
@@ -1,268 +0,0 @@
1
- #!/usr/bin/env node
2
- import { initProject } from "./init.js";
3
- import { checkRuntime } from "./runtime/check.js";
4
- import { upgradeProject } from "./upgrade.js";
5
- import { validateScaffold } from "./validate.js";
6
- import { DXCOMPLETE_PACKAGE_VERSION } from "./version.js";
7
-
8
- type ParsedArgs = {
9
- command?: string;
10
- targetDir: string;
11
- envFile?: string;
12
- force: boolean;
13
- dryRun: boolean;
14
- apply: boolean;
15
- includeGithubWorkflow: boolean;
16
- packageLayout: boolean;
17
- help: boolean;
18
- version: boolean;
19
- };
20
-
21
- main(process.argv.slice(2)).catch((error: unknown) => {
22
- const message = error instanceof Error ? error.message : String(error);
23
- console.error(`dxcomplete: ${message}`);
24
- process.exitCode = 1;
25
- });
26
-
27
- async function main(argv: string[]): Promise<void> {
28
- const args = parseArgs(argv);
29
-
30
- if (args.version) {
31
- console.log(DXCOMPLETE_PACKAGE_VERSION);
32
- return;
33
- }
34
-
35
- if (args.help || !args.command) {
36
- printHelp();
37
- return;
38
- }
39
-
40
- if (args.command === "init") {
41
- const result = await initProject({
42
- targetDir: args.targetDir,
43
- force: args.force,
44
- dryRun: args.dryRun,
45
- includeGithubWorkflow: args.includeGithubWorkflow
46
- });
47
-
48
- if (args.dryRun) {
49
- console.log(`Would initialize DX Complete scaffold in ${result.targetDir}`);
50
- printList("Would write", result.planned);
51
- printList("Would skip existing", result.skipped);
52
- return;
53
- }
54
-
55
- console.log(`Initialized DX Complete scaffold in ${result.targetDir}`);
56
- printList("Written", result.written);
57
- printList("Skipped existing", result.skipped);
58
- console.log("Review dxcomplete/docs/open-questions.md before treating the draft model as policy.");
59
- return;
60
- }
61
-
62
- if (args.command === "upgrade") {
63
- const result = await upgradeProject({
64
- targetDir: args.targetDir,
65
- apply: args.apply,
66
- force: args.force
67
- });
68
-
69
- if (args.apply) {
70
- console.log(
71
- result.manualReview.length > 0
72
- ? `DX Complete upgrade requires manual review in ${result.targetDir}`
73
- : `Upgraded DX Complete scaffold in ${result.targetDir}`
74
- );
75
- printList("Written", result.written);
76
- printList("Already current", result.unchanged);
77
- printList("Manual review required", result.manualReview);
78
- printList("User-owned scaffold drift", result.userOwnedDrift);
79
- if (result.manualReview.length > 0) {
80
- process.exitCode = 1;
81
- }
82
- return;
83
- }
84
-
85
- console.log(`Would upgrade DX Complete scaffold in ${result.targetDir}`);
86
- console.log(
87
- `Package version ${result.packageVersion}; workspace compatibility ${result.workspaceCompatibility}.`
88
- );
89
- printList("Would write", result.planned);
90
- printList("Already current", result.unchanged);
91
- printList("Manual review required", result.manualReview);
92
- printList("User-owned scaffold drift", result.userOwnedDrift);
93
- console.log("Run dxcomplete upgrade --apply to write compatibility-critical scaffold updates.");
94
- return;
95
- }
96
-
97
- if (args.command === "validate") {
98
- const result = await validateScaffold({
99
- targetDir: args.targetDir,
100
- packageLayout: args.packageLayout
101
- });
102
-
103
- if (result.ok) {
104
- console.log(`DX Complete scaffold shape is valid in ${result.targetDir}`);
105
- return;
106
- }
107
-
108
- console.error(`DX Complete scaffold is missing ${result.missing.length} required file(s):`);
109
- for (const file of result.missing) {
110
- console.error(`- ${file}`);
111
- }
112
- process.exitCode = 1;
113
- return;
114
- }
115
-
116
- if (args.command === "check-runtime") {
117
- const result = await checkRuntime({ envFile: args.envFile });
118
- console.log(JSON.stringify(result, null, 2));
119
- return;
120
- }
121
-
122
- throw new Error(`Unknown command "${args.command}".`);
123
- }
124
-
125
- function parseArgs(argv: string[]): ParsedArgs {
126
- const parsed: ParsedArgs = {
127
- targetDir: process.cwd(),
128
- force: false,
129
- dryRun: false,
130
- apply: false,
131
- includeGithubWorkflow: true,
132
- packageLayout: false,
133
- help: false,
134
- version: false
135
- };
136
-
137
- const positionals: string[] = [];
138
-
139
- for (let index = 0; index < argv.length; index += 1) {
140
- const arg = argv[index];
141
-
142
- if (arg === "--help" || arg === "-h") {
143
- parsed.help = true;
144
- continue;
145
- }
146
-
147
- if (arg === "--apply") {
148
- parsed.apply = true;
149
- continue;
150
- }
151
-
152
- if (arg === "--version" || arg === "-v") {
153
- parsed.version = true;
154
- continue;
155
- }
156
-
157
- if (arg === "--target") {
158
- const value = argv[index + 1];
159
- if (!value) {
160
- throw new Error("--target requires a directory.");
161
- }
162
- parsed.targetDir = value;
163
- index += 1;
164
- continue;
165
- }
166
-
167
- if (arg.startsWith("--target=")) {
168
- parsed.targetDir = arg.slice("--target=".length);
169
- continue;
170
- }
171
-
172
- if (arg === "--env") {
173
- const value = argv[index + 1];
174
- if (!value) {
175
- throw new Error("--env requires a file path.");
176
- }
177
- parsed.envFile = value;
178
- index += 1;
179
- continue;
180
- }
181
-
182
- if (arg.startsWith("--env=")) {
183
- parsed.envFile = arg.slice("--env=".length);
184
- continue;
185
- }
186
-
187
- if (arg === "--force") {
188
- parsed.force = true;
189
- continue;
190
- }
191
-
192
- if (arg === "--dry-run") {
193
- parsed.dryRun = true;
194
- continue;
195
- }
196
-
197
- if (arg === "--no-github-workflow") {
198
- parsed.includeGithubWorkflow = false;
199
- continue;
200
- }
201
-
202
- if (arg === "--package-layout") {
203
- parsed.packageLayout = true;
204
- continue;
205
- }
206
-
207
- if (arg.startsWith("-")) {
208
- throw new Error(`Unknown option "${arg}".`);
209
- }
210
-
211
- positionals.push(arg);
212
- }
213
-
214
- parsed.command = positionals[0];
215
-
216
- if (positionals.length > 1) {
217
- throw new Error(`Unexpected argument "${positionals[1]}".`);
218
- }
219
-
220
- if (parsed.apply && parsed.command !== "upgrade") {
221
- throw new Error("--apply is only supported by upgrade.");
222
- }
223
-
224
- if (parsed.command === "upgrade" && parsed.apply && parsed.dryRun) {
225
- throw new Error("upgrade cannot combine --apply and --dry-run.");
226
- }
227
-
228
- return parsed;
229
- }
230
-
231
- function printHelp(): void {
232
- console.log(`dxcomplete
233
-
234
- Usage:
235
- dxcomplete init [--target <dir>] [--force] [--dry-run] [--no-github-workflow]
236
- dxcomplete upgrade [--target <dir>] [--apply] [--force]
237
- dxcomplete validate [--target <dir>]
238
- dxcomplete check-runtime [--env <file>]
239
-
240
- Commands:
241
- init Install the editable draft scaffold into a project.
242
- upgrade Preview or apply compatibility-critical scaffold updates.
243
- validate Validate the expected scaffold file shape.
244
- check-runtime Verify MongoDB connectivity and required collections.
245
-
246
- Options:
247
- --target <dir> Target project directory. Defaults to the current directory.
248
- --env <file> Runtime env file. Defaults to .env.local when present.
249
- --apply Write upgrade changes. Upgrade previews by default.
250
- --force Overwrite existing scaffold files.
251
- --dry-run Show what would be written without changing files.
252
- --no-github-workflow Skip installing .github/workflows/dxcomplete.yml.
253
- --package-layout Validate this package repository layout instead of an installed scaffold.
254
- --help Show help.
255
- --version Show version.
256
- `);
257
- }
258
-
259
- function printList(label: string, values: string[]): void {
260
- if (values.length === 0) {
261
- return;
262
- }
263
-
264
- console.log(`${label}:`);
265
- for (const value of values) {
266
- console.log(`- ${value}`);
267
- }
268
- }
@@ -1,314 +0,0 @@
1
- import type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from "node:http";
2
- import { loadWorkspaceConfig, parseWorkspaceConfig, type WorkspaceConfig } from "../runtime/workspace.js";
3
-
4
- const MCP_PATH = "/api/mcp";
5
- const GOOGLE_CALLBACK_PATH = "/api/auth/callback/google";
6
- const MCP_SCOPE = "mcp:tools";
7
-
8
- type WorkspaceServiceConfig = {
9
- serviceUrl: string;
10
- serviceClientId: string;
11
- serviceClientSecret: string;
12
- };
13
-
14
- let workspaceConfigPromise: Promise<WorkspaceConfig> | undefined;
15
-
16
- export function configureDxcompleteWorkspace(config: unknown): void {
17
- workspaceConfigPromise = Promise.resolve(parseWorkspaceConfig(config, "DX Complete workspace config"));
18
- }
19
-
20
- export async function closeDxcompleteHttpRuntime(): Promise<void> {
21
- workspaceConfigPromise = undefined;
22
- }
23
-
24
- export default async function handleDxcompleteHttpRequest(
25
- req: IncomingMessage & { body?: unknown; query?: Record<string, unknown> },
26
- res: ServerResponse
27
- ): Promise<void> {
28
- try {
29
- setCorsHeaders(res);
30
-
31
- if (req.method === "OPTIONS") {
32
- res.writeHead(204).end();
33
- return;
34
- }
35
-
36
- const baseUrl = getBaseUrl(req);
37
- const requestUrl = new URL(req.url ?? "/", baseUrl);
38
- const path = normalizePath(requestUrl.pathname);
39
-
40
- if (isProtectedResourceMetadataPath(path)) {
41
- writeJson(res, 200, protectedResourceMetadata(baseUrl));
42
- return;
43
- }
44
-
45
- if (isAuthorizationServerMetadataPath(path)) {
46
- writeJson(res, 200, authorizationServerMetadata(baseUrl));
47
- return;
48
- }
49
-
50
- if (path === MCP_PATH && !readBearerToken(req)) {
51
- await drainRequestBody(req);
52
- writeOAuthChallenge(res, baseUrl);
53
- return;
54
- }
55
-
56
- const servicePath = servicePathForPublicPath(path);
57
- if (!servicePath) {
58
- writeJson(res, 404, { error: "not_found" });
59
- return;
60
- }
61
-
62
- const workspaceConfig = await getWorkspaceConfig();
63
- const serviceConfig = getWorkspaceServiceConfig();
64
- await proxyToCentralService(req, res, {
65
- serviceConfig,
66
- workspaceConfig,
67
- baseUrl,
68
- servicePath,
69
- search: requestUrl.search
70
- });
71
- } catch (error) {
72
- const message = error instanceof Error ? error.message : String(error);
73
- if (!res.headersSent) {
74
- writeJson(res, 500, { error: "server_error", error_description: message });
75
- } else {
76
- res.end();
77
- }
78
- }
79
- }
80
-
81
- async function proxyToCentralService(
82
- req: IncomingMessage,
83
- res: ServerResponse,
84
- input: {
85
- serviceConfig: WorkspaceServiceConfig;
86
- workspaceConfig: WorkspaceConfig;
87
- baseUrl: string;
88
- servicePath: string;
89
- search: string;
90
- }
91
- ): Promise<void> {
92
- const targetUrl = new URL(input.servicePath + input.search, input.serviceConfig.serviceUrl);
93
- const body = req.method === "GET" || req.method === "HEAD" ? undefined : await readRequestBody(req);
94
- const response = await fetch(targetUrl, {
95
- method: req.method,
96
- headers: serviceHeaders(req.headers, input),
97
- body,
98
- redirect: "manual"
99
- });
100
-
101
- const responseBody = Buffer.from(await response.arrayBuffer());
102
- const headers: Record<string, string> = {};
103
- response.headers.forEach((value, key) => {
104
- if (!shouldForwardResponseHeader(key)) {
105
- return;
106
- }
107
- headers[key] = value;
108
- });
109
- headers["content-length"] = String(responseBody.byteLength);
110
-
111
- res.writeHead(response.status, headers);
112
- res.end(responseBody);
113
- }
114
-
115
- function serviceHeaders(
116
- headers: IncomingHttpHeaders,
117
- input: {
118
- serviceConfig: WorkspaceServiceConfig;
119
- workspaceConfig: WorkspaceConfig;
120
- baseUrl: string;
121
- }
122
- ): Headers {
123
- const forwarded = new Headers();
124
-
125
- for (const key of [
126
- "accept",
127
- "authorization",
128
- "content-type",
129
- "mcp-protocol-version",
130
- "mcp-session-id",
131
- "last-event-id",
132
- "user-agent"
133
- ]) {
134
- const value = firstHeader(headers[key]);
135
- if (value) {
136
- forwarded.set(key, value);
137
- }
138
- }
139
-
140
- forwarded.set("x-dxc-service-client-id", input.serviceConfig.serviceClientId);
141
- forwarded.set("x-dxc-service-client-secret", input.serviceConfig.serviceClientSecret);
142
- forwarded.set("x-dxc-workspace-id", input.workspaceConfig.workspaceId);
143
- forwarded.set("x-dxc-workspace-name", input.workspaceConfig.name);
144
- forwarded.set("x-dxc-forwarded-base-url", input.baseUrl);
145
-
146
- return forwarded;
147
- }
148
-
149
- function shouldForwardResponseHeader(key: string): boolean {
150
- const lowerKey = key.toLowerCase();
151
- return !["connection", "content-encoding", "content-length", "keep-alive", "transfer-encoding"].includes(lowerKey);
152
- }
153
-
154
- function servicePathForPublicPath(pathname: string): string | undefined {
155
- switch (pathname) {
156
- case MCP_PATH:
157
- return "/api/dxcomplete/service/mcp";
158
- case "/api/dxcomplete/auth/register":
159
- return "/api/dxcomplete/service/auth/register";
160
- case "/api/dxcomplete/auth/authorize":
161
- return "/api/dxcomplete/service/auth/authorize";
162
- case GOOGLE_CALLBACK_PATH:
163
- return "/api/dxcomplete/service/auth/google/callback";
164
- case "/api/dxcomplete/auth/token":
165
- return "/api/dxcomplete/service/auth/token";
166
- default:
167
- return undefined;
168
- }
169
- }
170
-
171
- function getWorkspaceServiceConfig(env: NodeJS.ProcessEnv = process.env): WorkspaceServiceConfig {
172
- return {
173
- serviceUrl: readRequiredEnv(env, "DXC_SERVICE_URL"),
174
- serviceClientId: readRequiredEnv(env, "DXC_SERVICE_CLIENT_ID"),
175
- serviceClientSecret: readRequiredEnv(env, "DXC_SERVICE_CLIENT_SECRET")
176
- };
177
- }
178
-
179
- async function getWorkspaceConfig(): Promise<WorkspaceConfig> {
180
- workspaceConfigPromise ??= loadWorkspaceConfig();
181
- return workspaceConfigPromise;
182
- }
183
-
184
- function protectedResourceMetadata(baseUrl: string): Record<string, unknown> {
185
- return {
186
- resource: mcpResourceUrl(baseUrl),
187
- authorization_servers: [baseUrl],
188
- scopes_supported: [MCP_SCOPE],
189
- resource_name: "DX Complete"
190
- };
191
- }
192
-
193
- function authorizationServerMetadata(baseUrl: string): Record<string, unknown> {
194
- return {
195
- issuer: baseUrl,
196
- authorization_endpoint: `${baseUrl}/api/dxcomplete/auth/authorize`,
197
- token_endpoint: `${baseUrl}/api/dxcomplete/auth/token`,
198
- registration_endpoint: `${baseUrl}/api/dxcomplete/auth/register`,
199
- response_types_supported: ["code"],
200
- code_challenge_methods_supported: ["S256"],
201
- token_endpoint_auth_methods_supported: ["none"],
202
- grant_types_supported: ["authorization_code", "refresh_token"],
203
- scopes_supported: [MCP_SCOPE]
204
- };
205
- }
206
-
207
- function writeOAuthChallenge(res: ServerResponse, baseUrl: string): void {
208
- res.setHeader(
209
- "www-authenticate",
210
- `Bearer resource_metadata="${protectedResourceMetadataUrl(baseUrl)}", scope="${MCP_SCOPE}"`
211
- );
212
- writeJson(res, 401, { error: "unauthorized" });
213
- }
214
-
215
- function setCorsHeaders(res: ServerResponse): void {
216
- res.setHeader("access-control-allow-origin", "*");
217
- res.setHeader(
218
- "access-control-allow-headers",
219
- "authorization,content-type,mcp-protocol-version,mcp-session-id,last-event-id"
220
- );
221
- res.setHeader("access-control-allow-methods", "GET,POST,DELETE,OPTIONS");
222
- res.setHeader("access-control-expose-headers", "mcp-session-id,www-authenticate");
223
- }
224
-
225
- function writeJson(res: ServerResponse, status: number, value: unknown): void {
226
- const body = JSON.stringify(value);
227
- if (!res.headersSent) {
228
- res.writeHead(status, {
229
- "content-type": "application/json",
230
- "content-length": String(Buffer.byteLength(body))
231
- });
232
- }
233
- res.end(body);
234
- }
235
-
236
- function getBaseUrl(req: IncomingMessage): string {
237
- const forwardedProto = firstHeader(req.headers["x-forwarded-proto"]);
238
- const forwardedHost = firstHeader(req.headers["x-forwarded-host"]);
239
- const host = forwardedHost || firstHeader(req.headers.host);
240
- const protocol = forwardedProto || "http";
241
-
242
- if (!host) {
243
- throw new Error("Host header is required.");
244
- }
245
-
246
- return `${protocol}://${host}`;
247
- }
248
-
249
- function firstHeader(value: string | string[] | undefined): string | undefined {
250
- return Array.isArray(value) ? value[0] : value;
251
- }
252
-
253
- function normalizePath(pathname: string): string {
254
- if (pathname === "/api/mcp" || pathname === "/api/dxcomplete" || pathname === "/api/dxcomplete/mcp") {
255
- return MCP_PATH;
256
- }
257
-
258
- return pathname.endsWith("/") && pathname.length > 1 ? pathname.slice(0, -1) : pathname;
259
- }
260
-
261
- function isProtectedResourceMetadataPath(pathname: string): boolean {
262
- return (
263
- pathname === protectedResourceMetadataPath() ||
264
- pathname === "/.well-known/oauth-protected-resource/api/dxcomplete/mcp" ||
265
- pathname === `/api/dxcomplete${protectedResourceMetadataPath()}`
266
- );
267
- }
268
-
269
- function isAuthorizationServerMetadataPath(pathname: string): boolean {
270
- return (
271
- pathname === "/.well-known/oauth-authorization-server" ||
272
- pathname === "/api/dxcomplete/.well-known/oauth-authorization-server"
273
- );
274
- }
275
-
276
- function protectedResourceMetadataPath(): string {
277
- return `/.well-known/oauth-protected-resource${MCP_PATH}`;
278
- }
279
-
280
- function protectedResourceMetadataUrl(baseUrl: string): string {
281
- return `${baseUrl}${protectedResourceMetadataPath()}`;
282
- }
283
-
284
- function mcpResourceUrl(baseUrl: string): string {
285
- return `${baseUrl}${MCP_PATH}`;
286
- }
287
-
288
- async function readRequestBody(req: IncomingMessage): Promise<Buffer> {
289
- const chunks: Buffer[] = [];
290
- for await (const chunk of req) {
291
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
292
- }
293
- return Buffer.concat(chunks);
294
- }
295
-
296
- async function drainRequestBody(req: IncomingMessage): Promise<void> {
297
- for await (const _ of req) {
298
- // Drain request body so clients can reuse the connection.
299
- }
300
- }
301
-
302
- function readBearerToken(req: IncomingMessage): string | undefined {
303
- const header = firstHeader(req.headers.authorization);
304
- const match = header?.match(/^Bearer\s+(.+)$/i);
305
- return match?.[1];
306
- }
307
-
308
- function readRequiredEnv(env: NodeJS.ProcessEnv, key: string): string {
309
- const value = env[key]?.trim();
310
- if (!value) {
311
- throw new Error(`${key} is required for the workspace MCP proxy.`);
312
- }
313
- return value;
314
- }