dxcomplete 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +11 -0
- package/README.md +215 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +212 -0
- package/dist/http/server.d.ts +7 -0
- package/dist/http/server.js +236 -0
- package/dist/http/service.d.ts +7 -0
- package/dist/http/service.js +725 -0
- package/dist/init.d.ts +13 -0
- package/dist/init.js +128 -0
- package/dist/install-manifest.d.ts +25 -0
- package/dist/install-manifest.js +96 -0
- package/dist/mcp/docs.d.ts +98 -0
- package/dist/mcp/docs.js +438 -0
- package/dist/mcp/server.d.ts +20 -0
- package/dist/mcp/server.js +2345 -0
- package/dist/package-root.d.ts +2 -0
- package/dist/package-root.js +28 -0
- package/dist/runtime/actor.d.ts +14 -0
- package/dist/runtime/actor.js +42 -0
- package/dist/runtime/auth.d.ts +162 -0
- package/dist/runtime/auth.js +394 -0
- package/dist/runtime/check.d.ts +7 -0
- package/dist/runtime/check.js +16 -0
- package/dist/runtime/config.d.ts +17 -0
- package/dist/runtime/config.js +93 -0
- package/dist/runtime/mongo.d.ts +9 -0
- package/dist/runtime/mongo.js +56 -0
- package/dist/runtime/records.d.ts +336 -0
- package/dist/runtime/records.js +1463 -0
- package/dist/runtime/workspace.d.ts +19 -0
- package/dist/runtime/workspace.js +102 -0
- package/dist/upgrade.d.ts +20 -0
- package/dist/upgrade.js +246 -0
- package/dist/validate.d.ts +10 -0
- package/dist/validate.js +119 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.js +12 -0
- package/docs/codex-integration.md +29 -0
- package/docs/cost-model.md +61 -0
- package/docs/decision-basis.md +57 -0
- package/docs/diagrams.md +31 -0
- package/docs/glossary.md +147 -0
- package/docs/index.md +60 -0
- package/docs/model.md +110 -0
- package/docs/open-questions.md +61 -0
- package/docs/roles.md +42 -0
- package/docs/taxonomy.md +96 -0
- package/docs/workflows.md +60 -0
- package/package.json +62 -0
- package/scripts/check-env-surface.mjs +136 -0
- package/scripts/check-public-copy.mjs +263 -0
- package/scripts/check-service-boundary.mjs +63 -0
- package/scripts/dogfood-work-order.mjs +506 -0
- package/scripts/smoke-mcp-http.mjs +3572 -0
- package/src/cli.ts +268 -0
- package/src/http/server.ts +314 -0
- package/src/http/service.ts +934 -0
- package/src/init.ts +227 -0
- package/src/install-manifest.ts +144 -0
- package/src/mcp/docs.ts +557 -0
- package/src/mcp/server.ts +3525 -0
- package/src/package-root.ts +31 -0
- package/src/runtime/actor.ts +61 -0
- package/src/runtime/auth.ts +673 -0
- package/src/runtime/check.ts +18 -0
- package/src/runtime/config.ts +128 -0
- package/src/runtime/mongo.ts +89 -0
- package/src/runtime/records.ts +2303 -0
- package/src/runtime/workspace.ts +155 -0
- package/src/upgrade.ts +356 -0
- package/src/validate.ts +139 -0
- package/src/version.ts +16 -0
- package/templates/github/workflows/dxcomplete.yml +16 -0
- package/templates/next/pages/api/auth/callback/google.js +12 -0
- package/templates/next/pages/api/dxcomplete/[...path].js +12 -0
- package/templates/next/pages/api/dxcomplete.js +12 -0
- package/templates/next/pages/api/mcp.js +12 -0
- package/templates/next/vercel.json +18 -0
- package/templates/process/README.md +38 -0
- package/templates/process/controls.yml +113 -0
- package/templates/process/cost-model.yml +71 -0
- package/templates/process/decision-basis.yml +53 -0
- package/templates/process/decisions/.gitkeep +1 -0
- package/templates/process/diagrams/00-decision-basis.mmd +24 -0
- package/templates/process/diagrams/00-overview.mmd +20 -0
- package/templates/process/diagrams/01-intake-triage.mmd +20 -0
- package/templates/process/diagrams/02-product-definition.mmd +14 -0
- package/templates/process/diagrams/03-engineering-execution.mmd +15 -0
- package/templates/process/diagrams/04-qa-verification.mmd +12 -0
- package/templates/process/diagrams/05-product-validation.mmd +12 -0
- package/templates/process/diagrams/06-change-release-control.mmd +16 -0
- package/templates/process/diagrams/07-deployment-operations.mmd +16 -0
- package/templates/process/diagrams/08-support-incident-management.mmd +16 -0
- package/templates/process/diagrams/09-problem-improvement.mmd +14 -0
- package/templates/process/diagrams/10-risk-control-management.mmd +14 -0
- package/templates/process/diagrams/11-audit-evidence-capture.mmd +13 -0
- package/templates/process/evidence/.gitkeep +1 -0
- package/templates/process/risks/.gitkeep +1 -0
- package/templates/process/roles.yml +96 -0
- package/templates/process/taxonomy.yml +514 -0
- package/templates/process/workflows.yml +210 -0
- package/website/.well-known/oauth-authorization-server +22 -0
- package/website/.well-known/oauth-protected-resource/api/dxcomplete/mcp +10 -0
- package/website/.well-known/oauth-protected-resource/api/mcp +10 -0
- package/website/README.md +12 -0
- package/website/app.js +36 -0
- package/website/flow.html +85 -0
- package/website/glossary.html +280 -0
- package/website/index.html +90 -0
- package/website/objects.html +287 -0
- package/website/outcomes.html +117 -0
- package/website/phase-build.html +101 -0
- package/website/phase-elicit.html +102 -0
- package/website/phase-go-live.html +103 -0
- package/website/phase-measure.html +93 -0
- package/website/phase-operate.html +102 -0
- package/website/phase-orient.html +92 -0
- package/website/phase-weigh.html +98 -0
- package/website/roles.html +52 -0
- package/website/styles.css +1169 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { loadWorkspaceConfig, parseWorkspaceConfig } from "../runtime/workspace.js";
|
|
2
|
+
const MCP_PATH = "/api/mcp";
|
|
3
|
+
const GOOGLE_CALLBACK_PATH = "/api/auth/callback/google";
|
|
4
|
+
const MCP_SCOPE = "mcp:tools";
|
|
5
|
+
let workspaceConfigPromise;
|
|
6
|
+
export function configureDxcompleteWorkspace(config) {
|
|
7
|
+
workspaceConfigPromise = Promise.resolve(parseWorkspaceConfig(config, "DX Complete workspace config"));
|
|
8
|
+
}
|
|
9
|
+
export async function closeDxcompleteHttpRuntime() {
|
|
10
|
+
workspaceConfigPromise = undefined;
|
|
11
|
+
}
|
|
12
|
+
export default async function handleDxcompleteHttpRequest(req, res) {
|
|
13
|
+
try {
|
|
14
|
+
setCorsHeaders(res);
|
|
15
|
+
if (req.method === "OPTIONS") {
|
|
16
|
+
res.writeHead(204).end();
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const baseUrl = getBaseUrl(req);
|
|
20
|
+
const requestUrl = new URL(req.url ?? "/", baseUrl);
|
|
21
|
+
const path = normalizePath(requestUrl.pathname);
|
|
22
|
+
if (isProtectedResourceMetadataPath(path)) {
|
|
23
|
+
writeJson(res, 200, protectedResourceMetadata(baseUrl));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (isAuthorizationServerMetadataPath(path)) {
|
|
27
|
+
writeJson(res, 200, authorizationServerMetadata(baseUrl));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (path === MCP_PATH && !readBearerToken(req)) {
|
|
31
|
+
await drainRequestBody(req);
|
|
32
|
+
writeOAuthChallenge(res, baseUrl);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const servicePath = servicePathForPublicPath(path);
|
|
36
|
+
if (!servicePath) {
|
|
37
|
+
writeJson(res, 404, { error: "not_found" });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const workspaceConfig = await getWorkspaceConfig();
|
|
41
|
+
const serviceConfig = getWorkspaceServiceConfig();
|
|
42
|
+
await proxyToCentralService(req, res, {
|
|
43
|
+
serviceConfig,
|
|
44
|
+
workspaceConfig,
|
|
45
|
+
baseUrl,
|
|
46
|
+
servicePath,
|
|
47
|
+
search: requestUrl.search
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
52
|
+
if (!res.headersSent) {
|
|
53
|
+
writeJson(res, 500, { error: "server_error", error_description: message });
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
res.end();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function proxyToCentralService(req, res, input) {
|
|
61
|
+
const targetUrl = new URL(input.servicePath + input.search, input.serviceConfig.serviceUrl);
|
|
62
|
+
const body = req.method === "GET" || req.method === "HEAD" ? undefined : await readRequestBody(req);
|
|
63
|
+
const response = await fetch(targetUrl, {
|
|
64
|
+
method: req.method,
|
|
65
|
+
headers: serviceHeaders(req.headers, input),
|
|
66
|
+
body,
|
|
67
|
+
redirect: "manual"
|
|
68
|
+
});
|
|
69
|
+
const responseBody = Buffer.from(await response.arrayBuffer());
|
|
70
|
+
const headers = {};
|
|
71
|
+
response.headers.forEach((value, key) => {
|
|
72
|
+
if (!shouldForwardResponseHeader(key)) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
headers[key] = value;
|
|
76
|
+
});
|
|
77
|
+
headers["content-length"] = String(responseBody.byteLength);
|
|
78
|
+
res.writeHead(response.status, headers);
|
|
79
|
+
res.end(responseBody);
|
|
80
|
+
}
|
|
81
|
+
function serviceHeaders(headers, input) {
|
|
82
|
+
const forwarded = new Headers();
|
|
83
|
+
for (const key of [
|
|
84
|
+
"accept",
|
|
85
|
+
"authorization",
|
|
86
|
+
"content-type",
|
|
87
|
+
"mcp-protocol-version",
|
|
88
|
+
"mcp-session-id",
|
|
89
|
+
"last-event-id",
|
|
90
|
+
"user-agent"
|
|
91
|
+
]) {
|
|
92
|
+
const value = firstHeader(headers[key]);
|
|
93
|
+
if (value) {
|
|
94
|
+
forwarded.set(key, value);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
forwarded.set("x-dxc-service-client-id", input.serviceConfig.serviceClientId);
|
|
98
|
+
forwarded.set("x-dxc-service-client-secret", input.serviceConfig.serviceClientSecret);
|
|
99
|
+
forwarded.set("x-dxc-workspace-id", input.workspaceConfig.workspaceId);
|
|
100
|
+
forwarded.set("x-dxc-workspace-name", input.workspaceConfig.name);
|
|
101
|
+
forwarded.set("x-dxc-forwarded-base-url", input.baseUrl);
|
|
102
|
+
return forwarded;
|
|
103
|
+
}
|
|
104
|
+
function shouldForwardResponseHeader(key) {
|
|
105
|
+
const lowerKey = key.toLowerCase();
|
|
106
|
+
return !["connection", "content-encoding", "content-length", "keep-alive", "transfer-encoding"].includes(lowerKey);
|
|
107
|
+
}
|
|
108
|
+
function servicePathForPublicPath(pathname) {
|
|
109
|
+
switch (pathname) {
|
|
110
|
+
case MCP_PATH:
|
|
111
|
+
return "/api/dxcomplete/service/mcp";
|
|
112
|
+
case "/api/dxcomplete/auth/register":
|
|
113
|
+
return "/api/dxcomplete/service/auth/register";
|
|
114
|
+
case "/api/dxcomplete/auth/authorize":
|
|
115
|
+
return "/api/dxcomplete/service/auth/authorize";
|
|
116
|
+
case GOOGLE_CALLBACK_PATH:
|
|
117
|
+
return "/api/dxcomplete/service/auth/google/callback";
|
|
118
|
+
case "/api/dxcomplete/auth/token":
|
|
119
|
+
return "/api/dxcomplete/service/auth/token";
|
|
120
|
+
default:
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function getWorkspaceServiceConfig(env = process.env) {
|
|
125
|
+
return {
|
|
126
|
+
serviceUrl: readRequiredEnv(env, "DXC_SERVICE_URL"),
|
|
127
|
+
serviceClientId: readRequiredEnv(env, "DXC_SERVICE_CLIENT_ID"),
|
|
128
|
+
serviceClientSecret: readRequiredEnv(env, "DXC_SERVICE_CLIENT_SECRET")
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
async function getWorkspaceConfig() {
|
|
132
|
+
workspaceConfigPromise ??= loadWorkspaceConfig();
|
|
133
|
+
return workspaceConfigPromise;
|
|
134
|
+
}
|
|
135
|
+
function protectedResourceMetadata(baseUrl) {
|
|
136
|
+
return {
|
|
137
|
+
resource: mcpResourceUrl(baseUrl),
|
|
138
|
+
authorization_servers: [baseUrl],
|
|
139
|
+
scopes_supported: [MCP_SCOPE],
|
|
140
|
+
resource_name: "DX Complete"
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function authorizationServerMetadata(baseUrl) {
|
|
144
|
+
return {
|
|
145
|
+
issuer: baseUrl,
|
|
146
|
+
authorization_endpoint: `${baseUrl}/api/dxcomplete/auth/authorize`,
|
|
147
|
+
token_endpoint: `${baseUrl}/api/dxcomplete/auth/token`,
|
|
148
|
+
registration_endpoint: `${baseUrl}/api/dxcomplete/auth/register`,
|
|
149
|
+
response_types_supported: ["code"],
|
|
150
|
+
code_challenge_methods_supported: ["S256"],
|
|
151
|
+
token_endpoint_auth_methods_supported: ["none"],
|
|
152
|
+
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
153
|
+
scopes_supported: [MCP_SCOPE]
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function writeOAuthChallenge(res, baseUrl) {
|
|
157
|
+
res.setHeader("www-authenticate", `Bearer resource_metadata="${protectedResourceMetadataUrl(baseUrl)}", scope="${MCP_SCOPE}"`);
|
|
158
|
+
writeJson(res, 401, { error: "unauthorized" });
|
|
159
|
+
}
|
|
160
|
+
function setCorsHeaders(res) {
|
|
161
|
+
res.setHeader("access-control-allow-origin", "*");
|
|
162
|
+
res.setHeader("access-control-allow-headers", "authorization,content-type,mcp-protocol-version,mcp-session-id,last-event-id");
|
|
163
|
+
res.setHeader("access-control-allow-methods", "GET,POST,DELETE,OPTIONS");
|
|
164
|
+
res.setHeader("access-control-expose-headers", "mcp-session-id,www-authenticate");
|
|
165
|
+
}
|
|
166
|
+
function writeJson(res, status, value) {
|
|
167
|
+
const body = JSON.stringify(value);
|
|
168
|
+
if (!res.headersSent) {
|
|
169
|
+
res.writeHead(status, {
|
|
170
|
+
"content-type": "application/json",
|
|
171
|
+
"content-length": String(Buffer.byteLength(body))
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
res.end(body);
|
|
175
|
+
}
|
|
176
|
+
function getBaseUrl(req) {
|
|
177
|
+
const forwardedProto = firstHeader(req.headers["x-forwarded-proto"]);
|
|
178
|
+
const forwardedHost = firstHeader(req.headers["x-forwarded-host"]);
|
|
179
|
+
const host = forwardedHost || firstHeader(req.headers.host);
|
|
180
|
+
const protocol = forwardedProto || "http";
|
|
181
|
+
if (!host) {
|
|
182
|
+
throw new Error("Host header is required.");
|
|
183
|
+
}
|
|
184
|
+
return `${protocol}://${host}`;
|
|
185
|
+
}
|
|
186
|
+
function firstHeader(value) {
|
|
187
|
+
return Array.isArray(value) ? value[0] : value;
|
|
188
|
+
}
|
|
189
|
+
function normalizePath(pathname) {
|
|
190
|
+
if (pathname === "/api/mcp" || pathname === "/api/dxcomplete" || pathname === "/api/dxcomplete/mcp") {
|
|
191
|
+
return MCP_PATH;
|
|
192
|
+
}
|
|
193
|
+
return pathname.endsWith("/") && pathname.length > 1 ? pathname.slice(0, -1) : pathname;
|
|
194
|
+
}
|
|
195
|
+
function isProtectedResourceMetadataPath(pathname) {
|
|
196
|
+
return (pathname === protectedResourceMetadataPath() ||
|
|
197
|
+
pathname === "/.well-known/oauth-protected-resource/api/dxcomplete/mcp" ||
|
|
198
|
+
pathname === `/api/dxcomplete${protectedResourceMetadataPath()}`);
|
|
199
|
+
}
|
|
200
|
+
function isAuthorizationServerMetadataPath(pathname) {
|
|
201
|
+
return (pathname === "/.well-known/oauth-authorization-server" ||
|
|
202
|
+
pathname === "/api/dxcomplete/.well-known/oauth-authorization-server");
|
|
203
|
+
}
|
|
204
|
+
function protectedResourceMetadataPath() {
|
|
205
|
+
return `/.well-known/oauth-protected-resource${MCP_PATH}`;
|
|
206
|
+
}
|
|
207
|
+
function protectedResourceMetadataUrl(baseUrl) {
|
|
208
|
+
return `${baseUrl}${protectedResourceMetadataPath()}`;
|
|
209
|
+
}
|
|
210
|
+
function mcpResourceUrl(baseUrl) {
|
|
211
|
+
return `${baseUrl}${MCP_PATH}`;
|
|
212
|
+
}
|
|
213
|
+
async function readRequestBody(req) {
|
|
214
|
+
const chunks = [];
|
|
215
|
+
for await (const chunk of req) {
|
|
216
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
217
|
+
}
|
|
218
|
+
return Buffer.concat(chunks);
|
|
219
|
+
}
|
|
220
|
+
async function drainRequestBody(req) {
|
|
221
|
+
for await (const _ of req) {
|
|
222
|
+
// Drain request body so clients can reuse the connection.
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function readBearerToken(req) {
|
|
226
|
+
const header = firstHeader(req.headers.authorization);
|
|
227
|
+
const match = header?.match(/^Bearer\s+(.+)$/i);
|
|
228
|
+
return match?.[1];
|
|
229
|
+
}
|
|
230
|
+
function readRequiredEnv(env, key) {
|
|
231
|
+
const value = env[key]?.trim();
|
|
232
|
+
if (!value) {
|
|
233
|
+
throw new Error(`${key} is required for the workspace MCP proxy.`);
|
|
234
|
+
}
|
|
235
|
+
return value;
|
|
236
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
export declare function closeDxcompleteServiceRuntime(): Promise<void>;
|
|
3
|
+
export declare function closeDxcompleteHttpRuntime(): Promise<void>;
|
|
4
|
+
export default function handleDxcompleteServiceRequest(req: IncomingMessage & {
|
|
5
|
+
body?: unknown;
|
|
6
|
+
query?: Record<string, unknown>;
|
|
7
|
+
}, res: ServerResponse): Promise<void>;
|