runbios-mcp 0.2.1-dev.62

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 (90) hide show
  1. package/README.md +363 -0
  2. package/dist/api-client.d.ts +147 -0
  3. package/dist/api-client.d.ts.map +1 -0
  4. package/dist/api-client.js +806 -0
  5. package/dist/api-client.js.map +1 -0
  6. package/dist/auth.d.ts +3 -0
  7. package/dist/auth.d.ts.map +1 -0
  8. package/dist/auth.js +11 -0
  9. package/dist/auth.js.map +1 -0
  10. package/dist/config.d.ts +30 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +16 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/deployment-contract.d.ts +78 -0
  15. package/dist/deployment-contract.d.ts.map +1 -0
  16. package/dist/deployment-contract.js +155 -0
  17. package/dist/deployment-contract.js.map +1 -0
  18. package/dist/gpu-priorities.d.ts +25 -0
  19. package/dist/gpu-priorities.d.ts.map +1 -0
  20. package/dist/gpu-priorities.js +61 -0
  21. package/dist/gpu-priorities.js.map +1 -0
  22. package/dist/http/app.d.ts +14 -0
  23. package/dist/http/app.d.ts.map +1 -0
  24. package/dist/http/app.js +140 -0
  25. package/dist/http/app.js.map +1 -0
  26. package/dist/http/audit.d.ts +23 -0
  27. package/dist/http/audit.d.ts.map +1 -0
  28. package/dist/http/audit.js +41 -0
  29. package/dist/http/audit.js.map +1 -0
  30. package/dist/http/config.d.ts +23 -0
  31. package/dist/http/config.d.ts.map +1 -0
  32. package/dist/http/config.js +70 -0
  33. package/dist/http/config.js.map +1 -0
  34. package/dist/http/consent.d.ts +18 -0
  35. package/dist/http/consent.d.ts.map +1 -0
  36. package/dist/http/consent.js +148 -0
  37. package/dist/http/consent.js.map +1 -0
  38. package/dist/http/internal-auth.d.ts +22 -0
  39. package/dist/http/internal-auth.d.ts.map +1 -0
  40. package/dist/http/internal-auth.js +73 -0
  41. package/dist/http/internal-auth.js.map +1 -0
  42. package/dist/http/main.d.ts +3 -0
  43. package/dist/http/main.d.ts.map +1 -0
  44. package/dist/http/main.js +65 -0
  45. package/dist/http/main.js.map +1 -0
  46. package/dist/http/mcp-handler.d.ts +12 -0
  47. package/dist/http/mcp-handler.d.ts.map +1 -0
  48. package/dist/http/mcp-handler.js +103 -0
  49. package/dist/http/mcp-handler.js.map +1 -0
  50. package/dist/http/metadata.d.ts +6 -0
  51. package/dist/http/metadata.d.ts.map +1 -0
  52. package/dist/http/metadata.js +32 -0
  53. package/dist/http/metadata.js.map +1 -0
  54. package/dist/http/oauth.d.ts +56 -0
  55. package/dist/http/oauth.d.ts.map +1 -0
  56. package/dist/http/oauth.js +486 -0
  57. package/dist/http/oauth.js.map +1 -0
  58. package/dist/http/serviceHostGuard.d.ts +31 -0
  59. package/dist/http/serviceHostGuard.d.ts.map +1 -0
  60. package/dist/http/serviceHostGuard.js +68 -0
  61. package/dist/http/serviceHostGuard.js.map +1 -0
  62. package/dist/http/store.d.ts +150 -0
  63. package/dist/http/store.d.ts.map +1 -0
  64. package/dist/http/store.js +366 -0
  65. package/dist/http/store.js.map +1 -0
  66. package/dist/index.d.ts +3 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +79 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/inference-contract.d.ts +29 -0
  71. package/dist/inference-contract.d.ts.map +1 -0
  72. package/dist/inference-contract.js +112 -0
  73. package/dist/inference-contract.js.map +1 -0
  74. package/dist/redaction.d.ts +74 -0
  75. package/dist/redaction.d.ts.map +1 -0
  76. package/dist/redaction.js +316 -0
  77. package/dist/redaction.js.map +1 -0
  78. package/dist/server.d.ts +63 -0
  79. package/dist/server.d.ts.map +1 -0
  80. package/dist/server.js +2282 -0
  81. package/dist/server.js.map +1 -0
  82. package/dist/training-contract.d.ts +107 -0
  83. package/dist/training-contract.d.ts.map +1 -0
  84. package/dist/training-contract.js +141 -0
  85. package/dist/training-contract.js.map +1 -0
  86. package/dist/version.d.ts +2 -0
  87. package/dist/version.d.ts.map +1 -0
  88. package/dist/version.js +2 -0
  89. package/dist/version.js.map +1 -0
  90. package/package.json +47 -0
@@ -0,0 +1,140 @@
1
+ import express from "express";
2
+ import { nullAudit } from "./audit.js";
3
+ import { createOAuthRouter, validateAccessToken, sha256Hex } from "./oauth.js";
4
+ import { createConsentRouter } from "./consent.js";
5
+ import { createMcpPostHandler, methodNotAllowed } from "./mcp-handler.js";
6
+ import { authorizationServerMetadata, protectedResourceMetadata } from "./metadata.js";
7
+ import { verifyInternalToken } from "./internal-auth.js";
8
+ /** True when a query value looks like a credential (api key, MCP token/code). */
9
+ function looksLikeCredential(value) {
10
+ return value.startsWith("bios-") || value.startsWith("usf-") || value.startsWith("mcp_") || /^bearer\s/i.test(value);
11
+ }
12
+ function queryHasCredential(value) {
13
+ if (typeof value === "string")
14
+ return looksLikeCredential(value);
15
+ if (Array.isArray(value))
16
+ return value.some(queryHasCredential);
17
+ if (value && typeof value === "object") {
18
+ return Object.values(value).some(queryHasCredential);
19
+ }
20
+ return false;
21
+ }
22
+ export function createApp(deps) {
23
+ const { store, cfg } = deps;
24
+ const audit = deps.audit ?? nullAudit;
25
+ const app = express();
26
+ app.disable("x-powered-by");
27
+ // Behind the ALB + api-gateway; trust X-Forwarded-For for rate-limit keys.
28
+ app.set("trust proxy", true);
29
+ // Tokens must only ever travel in headers or bodies, never in URLs.
30
+ app.use((req, res, next) => {
31
+ if (queryHasCredential(req.query)) {
32
+ res.status(400).json({
33
+ error: "invalid_request",
34
+ error_description: "Credentials must not be sent in the URL.",
35
+ });
36
+ return;
37
+ }
38
+ next();
39
+ });
40
+ // CORS for browser-based MCP clients on the public endpoints
41
+ // (/mcp, /mcp/oauth/*, /.well-known/*). No credentials, wildcard origin.
42
+ app.use((req, res, next) => {
43
+ const isPublic = req.path === cfg.publicMcpPath ||
44
+ req.path.startsWith(`${cfg.publicMcpPath}/`) ||
45
+ req.path.startsWith("/.well-known/");
46
+ if (!isPublic) {
47
+ next();
48
+ return;
49
+ }
50
+ res.set("Access-Control-Allow-Origin", "*");
51
+ res.set("Access-Control-Allow-Methods", "GET,POST,DELETE,OPTIONS");
52
+ res.set("Access-Control-Allow-Headers", "Content-Type, Authorization, Mcp-Protocol-Version, Mcp-Session-Id, Last-Event-ID");
53
+ res.set("Access-Control-Expose-Headers", "Mcp-Session-Id, WWW-Authenticate");
54
+ res.set("Access-Control-Max-Age", "86400");
55
+ if (req.method === "OPTIONS") {
56
+ res.status(204).end();
57
+ return;
58
+ }
59
+ next();
60
+ });
61
+ app.use(express.json({ limit: "4mb" }));
62
+ app.use(express.urlencoded({ extended: false }));
63
+ /* ─── Health (ECS health check fetches "/" and requires res.ok) ─── */
64
+ app.get("/", (_req, res) => {
65
+ res.json({ status: "ok", service: "bios-mcp" });
66
+ });
67
+ /* ─── Discovery metadata ─── */
68
+ const serveResourceMeta = (_req, res) => {
69
+ res.json(protectedResourceMetadata(cfg));
70
+ };
71
+ const serveAsMeta = (_req, res) => {
72
+ res.json(authorizationServerMetadata(cfg));
73
+ };
74
+ app.get("/.well-known/oauth-protected-resource", serveResourceMeta);
75
+ app.get("/.well-known/oauth-protected-resource/mcp", serveResourceMeta);
76
+ app.get("/.well-known/oauth-authorization-server", serveAsMeta);
77
+ app.get("/.well-known/oauth-authorization-server/mcp", serveAsMeta);
78
+ app.get("/.well-known/openid-configuration", serveAsMeta);
79
+ app.get("/.well-known/openid-configuration/mcp", serveAsMeta);
80
+ /* ─── OAuth authorization server + consent API ─── */
81
+ app.use(createOAuthRouter({
82
+ store,
83
+ cfg,
84
+ audit,
85
+ registerLimiter: deps.registerLimiter,
86
+ tokenLimiter: deps.tokenLimiter,
87
+ }));
88
+ app.use(createConsentRouter({ store, cfg, audit }));
89
+ /* ─── Gateway-facing token introspection ─── */
90
+ app.post("/internal/oauth/introspect", async (req, res) => {
91
+ const internalToken = req.header("x-internal-token") ?? "";
92
+ if (!verifyInternalToken(internalToken, cfg.internalServiceKey)) {
93
+ res.status(401).json({ error: "unauthorized" });
94
+ return;
95
+ }
96
+ const body = (req.body ?? {});
97
+ const raw = typeof body.token === "string" ? body.token : "";
98
+ const access = raw ? await validateAccessToken(store, raw) : null;
99
+ if (!access) {
100
+ res.json({ active: false });
101
+ return;
102
+ }
103
+ const { grant } = access;
104
+ res.json({
105
+ active: true,
106
+ token_id: sha256Hex(raw).slice(0, 12),
107
+ grant_id: grant.id,
108
+ user_id: grant.user_id,
109
+ email: grant.user_email,
110
+ org_id: grant.org_id,
111
+ workspace_id: grant.workspace_id,
112
+ scopes: grant.scopes,
113
+ });
114
+ });
115
+ /* ─── MCP Streamable HTTP endpoint (stateless) ─── */
116
+ app.post(cfg.publicMcpPath, createMcpPostHandler({ store, cfg, audit }));
117
+ app.get(cfg.publicMcpPath, methodNotAllowed);
118
+ app.delete(cfg.publicMcpPath, methodNotAllowed);
119
+ /* ─── Fallbacks ─── */
120
+ app.use((_req, res) => {
121
+ res.status(404).json({ error: "not_found" });
122
+ });
123
+ // Central error handler: malformed bodies get a clean 400; anything else a
124
+ // generic 500. Error details never include request payloads or tokens.
125
+ app.use((err, _req, res, _next) => {
126
+ const isParseError = err instanceof SyntaxError ||
127
+ (typeof err === "object" && err !== null && err.type === "entity.parse.failed");
128
+ if (res.headersSent) {
129
+ res.end();
130
+ return;
131
+ }
132
+ if (isParseError) {
133
+ res.status(400).json({ error: "invalid_request", error_description: "Malformed request body" });
134
+ return;
135
+ }
136
+ res.status(500).json({ error: "server_error" });
137
+ });
138
+ return app;
139
+ }
140
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/http/app.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Structured audit logging: single-line JSON on stdout, plus fire-and-forget
3
+ * mirroring to audit-service (POST /internal/audit/events, authenticated with
4
+ * the internal service JWT) when AUDIT_SERVICE_HOST is configured.
5
+ *
6
+ * Never pass raw tokens, codes, verifiers, api keys, or tool arguments into
7
+ * emit() — only identifiers and outcomes.
8
+ */
9
+ export interface AuditEmitter {
10
+ emit(evt: string, fields: Record<string, unknown>): void;
11
+ }
12
+ export interface AuditOptions {
13
+ /** e.g. "http://bios-audit:7113"; empty disables mirroring. */
14
+ auditBaseUrl?: string;
15
+ internalServiceKey?: string;
16
+ /** Overridable for tests. */
17
+ fetchImpl?: typeof fetch;
18
+ out?: (line: string) => void;
19
+ }
20
+ export declare function createAudit(opts?: AuditOptions): AuditEmitter;
21
+ /** No-op emitter for tests that do not assert on audit output. */
22
+ export declare const nullAudit: AuditEmitter;
23
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/http/audit.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC1D;AAED,MAAM,WAAW,YAAY;IAC3B,+DAA+D;IAC/D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,6BAA6B;IAC7B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED,wBAAgB,WAAW,CAAC,IAAI,GAAE,YAAiB,GAAG,YAAY,CAqCjE;AAED,kEAAkE;AAClE,eAAO,MAAM,SAAS,EAAE,YAAiC,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { generateInternalToken } from "./internal-auth.js";
2
+ export function createAudit(opts = {}) {
3
+ const write = opts.out ?? ((line) => process.stdout.write(`${line}\n`));
4
+ const fetchImpl = opts.fetchImpl ?? fetch;
5
+ return {
6
+ emit(evt, fields) {
7
+ write(JSON.stringify({ ts: new Date().toISOString(), level: "info", evt, ...fields }));
8
+ if (!opts.auditBaseUrl || !opts.internalServiceKey)
9
+ return;
10
+ const payload = {
11
+ event_type: `mcp.${evt}`,
12
+ actor_type: fields.user_id ? "user" : "service",
13
+ actor_id: String(fields.user_id ?? fields.client_id ?? "bios-mcp"),
14
+ resource_type: "mcp",
15
+ resource_id: fields.grant_id != null
16
+ ? String(fields.grant_id)
17
+ : fields.client_id != null
18
+ ? String(fields.client_id)
19
+ : null,
20
+ workspace_id: fields.workspace_id != null ? String(fields.workspace_id) : null,
21
+ details: fields,
22
+ source_service: "bios-mcp",
23
+ };
24
+ try {
25
+ const token = generateInternalToken(opts.internalServiceKey, "bios-mcp");
26
+ void fetchImpl(`${opts.auditBaseUrl}/internal/audit/events`, {
27
+ method: "POST",
28
+ headers: { "Content-Type": "application/json", "X-Internal-Token": token },
29
+ body: JSON.stringify(payload),
30
+ signal: AbortSignal.timeout(3_000),
31
+ }).catch(() => { });
32
+ }
33
+ catch {
34
+ // Audit mirroring is strictly best-effort.
35
+ }
36
+ },
37
+ };
38
+ }
39
+ /** No-op emitter for tests that do not assert on audit output. */
40
+ export const nullAudit = { emit: () => { } };
41
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/http/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAwB3D,MAAM,UAAU,WAAW,CAAC,OAAqB,EAAE;IACjD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC;IAChF,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC;IAE1C,OAAO;QACL,IAAI,CAAC,GAAW,EAAE,MAA+B;YAC/C,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC,CAAC;YAEvF,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,kBAAkB;gBAAE,OAAO;YAC3D,MAAM,OAAO,GAAG;gBACd,UAAU,EAAE,OAAO,GAAG,EAAE;gBACxB,UAAU,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;gBAC/C,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,SAAS,IAAI,UAAU,CAAC;gBAClE,aAAa,EAAE,KAAK;gBACpB,WAAW,EACT,MAAM,CAAC,QAAQ,IAAI,IAAI;oBACrB,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;oBACzB,CAAC,CAAC,MAAM,CAAC,SAAS,IAAI,IAAI;wBACxB,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC;wBAC1B,CAAC,CAAC,IAAI;gBACZ,YAAY,EAAE,MAAM,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI;gBAC9E,OAAO,EAAE,MAAM;gBACf,cAAc,EAAE,UAAU;aAC3B,CAAC;YACF,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,qBAAqB,CAAC,IAAI,CAAC,kBAAkB,EAAE,UAAU,CAAC,CAAC;gBACzE,KAAK,SAAS,CAAC,GAAG,IAAI,CAAC,YAAY,wBAAwB,EAAE;oBAC3D,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,KAAK,EAAE;oBAC1E,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;oBAC7B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC;iBACnC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YACrB,CAAC;YAAC,MAAM,CAAC;gBACP,2CAA2C;YAC7C,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,kEAAkE;AAClE,MAAM,CAAC,MAAM,SAAS,GAAiB,EAAE,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAC"}
@@ -0,0 +1,23 @@
1
+ /** Configuration for the hosted MCP HTTP service (ECS service "bios-mcp"). */
2
+ export interface HostedConfig {
3
+ /** Port the HTTP server binds; default 7152. */
4
+ port: number;
5
+ /** Public origin the gateway/ALB serves, e.g. https://api-dev.runbios.ai. */
6
+ publicBaseUrl: string;
7
+ /** Public path of the MCP endpoint; default "/mcp". */
8
+ publicMcpPath: string;
9
+ /** Console origin used for the consent redirect, e.g. https://platform-dev.runbios.ai. */
10
+ consoleBaseUrl: string;
11
+ /** Internal api-gateway host/port used as the BiosClient base URL. */
12
+ apiGatewayHost: string;
13
+ apiGatewayPort: number;
14
+ /** Postgres connection string for the mcp schema. */
15
+ databaseUrl: string;
16
+ /** Shared internal service key (gateway stamp + internal JWTs). */
17
+ internalServiceKey: string;
18
+ /** Optional audit-service host; when empty, audit events go to stdout only. */
19
+ auditServiceHost: string;
20
+ auditServicePort: number;
21
+ }
22
+ export declare function loadHostedConfig(env?: NodeJS.ProcessEnv): HostedConfig;
23
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/http/config.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAE9E,MAAM,WAAW,YAAY;IAC3B,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,aAAa,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,aAAa,EAAE,MAAM,CAAC;IACtB,0FAA0F;IAC1F,cAAc,EAAE,MAAM,CAAC;IACvB,sEAAsE;IACtE,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,qDAAqD;IACrD,WAAW,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,kBAAkB,EAAE,MAAM,CAAC;IAC3B,+EAA+E;IAC/E,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AA6BD,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,YAAY,CA4CnF"}
@@ -0,0 +1,70 @@
1
+ /** Configuration for the hosted MCP HTTP service (ECS service "bios-mcp"). */
2
+ function stripTrailingSlash(value) {
3
+ return value.replace(/\/+$/, "");
4
+ }
5
+ /**
6
+ * The connection can be given either as a single MCP_DATABASE_URL (how the
7
+ * deployed secret is stored) or as discrete MCP_DB_* parts (how local dev
8
+ * configures it — repo files must never contain credential-bearing URLs).
9
+ */
10
+ function resolveDatabaseUrl(env) {
11
+ const url = (env.MCP_DATABASE_URL ?? "").trim();
12
+ if (url)
13
+ return url;
14
+ const host = (env.MCP_DB_HOST ?? "").trim();
15
+ const password = env.MCP_DB_PASSWORD ?? "";
16
+ if (!host || !password)
17
+ return "";
18
+ const port = (env.MCP_DB_PORT ?? "").trim() || "5432";
19
+ const user = (env.MCP_DB_USER ?? "").trim() || "mcp_svc";
20
+ const name = (env.MCP_DB_NAME ?? "").trim() || "bios_saas";
21
+ const sslmode = (env.MCP_DB_SSLMODE ?? "").trim() || "disable";
22
+ const dsn = new URL(`postgres://${host}:${port}`);
23
+ dsn.username = user;
24
+ dsn.password = password;
25
+ dsn.pathname = `/${name}`;
26
+ dsn.search = sslmode === "disable" ? "sslmode=disable" : `uselibpqcompat=true&sslmode=${sslmode}`;
27
+ return dsn.toString();
28
+ }
29
+ export function loadHostedConfig(env = process.env) {
30
+ const missing = [];
31
+ const required = (name) => {
32
+ const value = (env[name] ?? "").trim();
33
+ if (!value)
34
+ missing.push(name);
35
+ return value;
36
+ };
37
+ const publicBaseUrl = stripTrailingSlash(required("PUBLIC_BASE_URL"));
38
+ const consoleBaseUrl = stripTrailingSlash(required("CONSOLE_BASE_URL"));
39
+ const databaseUrl = resolveDatabaseUrl(env);
40
+ if (!databaseUrl)
41
+ missing.push("MCP_DATABASE_URL (or MCP_DB_HOST + MCP_DB_PASSWORD)");
42
+ const internalServiceKey = required("INTERNAL_SERVICE_KEY");
43
+ if (missing.length > 0) {
44
+ throw new Error(`bios-mcp cannot start: missing required environment variable(s): ${missing.join(", ")}. ` +
45
+ `Set PUBLIC_BASE_URL (public origin), CONSOLE_BASE_URL (console origin), ` +
46
+ `MCP_DATABASE_URL or the MCP_DB_* parts (Postgres), and INTERNAL_SERVICE_KEY (shared internal key).`);
47
+ }
48
+ let publicMcpPath = (env.PUBLIC_MCP_PATH ?? "").trim() || "/mcp";
49
+ if (!publicMcpPath.startsWith("/"))
50
+ publicMcpPath = `/${publicMcpPath}`;
51
+ publicMcpPath = stripTrailingSlash(publicMcpPath) || "/mcp";
52
+ const port = Number.parseInt(env.MCP_PORT ?? "", 10) || 7152;
53
+ const apiGatewayHost = (env.API_GATEWAY_HOST ?? "").trim() || "localhost";
54
+ const apiGatewayPort = Number.parseInt(env.API_GATEWAY_PORT ?? "", 10) || 7100;
55
+ const auditServiceHost = (env.AUDIT_SERVICE_HOST ?? "").trim();
56
+ const auditServicePort = Number.parseInt(env.AUDIT_SERVICE_PORT ?? "", 10) || 7113;
57
+ return {
58
+ port,
59
+ publicBaseUrl,
60
+ publicMcpPath,
61
+ consoleBaseUrl,
62
+ apiGatewayHost,
63
+ apiGatewayPort,
64
+ databaseUrl,
65
+ internalServiceKey,
66
+ auditServiceHost,
67
+ auditServicePort,
68
+ };
69
+ }
70
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/http/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { Router } from "express";
2
+ import type { AuditEmitter } from "./audit.js";
3
+ import type { HostedConfig } from "./config.js";
4
+ import type { McpStore } from "./store.js";
5
+ /**
6
+ * Consent endpoints under /api/mcp/oauth/*. These are reached through the Go
7
+ * api-gateway, which authenticates the console user's JWT, validates
8
+ * workspace membership on X-Workspace-ID, stamps identity headers, and marks
9
+ * the request with X-Gateway-Auth = INTERNAL_SERVICE_KEY (the same contract
10
+ * every Go service trusts in its JWTAuth middleware).
11
+ */
12
+ export interface ConsentRouterDeps {
13
+ store: McpStore;
14
+ cfg: HostedConfig;
15
+ audit: AuditEmitter;
16
+ }
17
+ export declare function createConsentRouter(deps: ConsentRouterDeps): Router;
18
+ //# sourceMappingURL=consent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consent.d.ts","sourceRoot":"","sources":["../../src/http/consent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAkD,MAAM,SAAS,CAAC;AACjF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAmB,MAAM,YAAY,CAAC;AAW5D;;;;;;GAMG;AAEH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,QAAQ,CAAC;IAChB,GAAG,EAAE,YAAY,CAAC;IAClB,KAAK,EAAE,YAAY,CAAC;CACrB;AAaD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAwJnE"}
@@ -0,0 +1,148 @@
1
+ import { Router } from "express";
2
+ import { AUTH_CODE_TTL_MS, newId, newRawToken, normalizeScopes, parseScopeParam, sha256Hex, timingSafeEqualStr, } from "./oauth.js";
3
+ function str(value) {
4
+ return typeof value === "string" ? value : "";
5
+ }
6
+ export function createConsentRouter(deps) {
7
+ const { store, cfg, audit } = deps;
8
+ const router = Router();
9
+ const gatewayAuth = (req, res, next) => {
10
+ const stamp = req.header("x-gateway-auth") ?? "";
11
+ if (!stamp || !cfg.internalServiceKey || !timingSafeEqualStr(stamp, cfg.internalServiceKey)) {
12
+ res.status(401).json({ error: "unauthorized" });
13
+ return;
14
+ }
15
+ // Consent requires an interactive console session, never an API key or
16
+ // an OAuth token (an MCP client must not be able to approve itself).
17
+ if (req.header("x-auth-type") !== "jwt") {
18
+ res.status(403).json({ error: "forbidden", message: "Sign in to the console to review this connection." });
19
+ return;
20
+ }
21
+ if (!(req.header("x-user-id") ?? "").trim()) {
22
+ res.status(401).json({ error: "unauthorized" });
23
+ return;
24
+ }
25
+ next();
26
+ };
27
+ const identity = (req) => ({
28
+ userId: (req.header("x-user-id") ?? "").trim(),
29
+ userEmail: (req.header("x-user-email") ?? "").trim(),
30
+ orgId: (req.header("x-org-id") ?? "").trim(),
31
+ workspaceId: (req.header("x-workspace-id") ?? "").trim(),
32
+ });
33
+ const expireIfNeeded = async (row) => {
34
+ if (row.status === "pending" && row.expires_at.getTime() <= Date.now()) {
35
+ await store.setRequestStatus(row.id, "expired");
36
+ return { ...row, status: "expired" };
37
+ }
38
+ return row;
39
+ };
40
+ router.get("/api/mcp/oauth/request/:id", gatewayAuth, async (req, res) => {
41
+ const raw = await store.getRequest(req.params.id);
42
+ if (!raw) {
43
+ res.status(404).json({ error: "not_found" });
44
+ return;
45
+ }
46
+ const row = await expireIfNeeded(raw);
47
+ const client = await store.getClient(row.client_id);
48
+ let clientHost = "";
49
+ try {
50
+ clientHost = new URL(client?.redirect_uris[0] ?? row.redirect_uri).hostname;
51
+ }
52
+ catch {
53
+ clientHost = "";
54
+ }
55
+ res.json({
56
+ request_id: row.id,
57
+ client_name: client?.client_name ?? "MCP client",
58
+ client_host: clientHost,
59
+ requested_scopes: row.scope ? parseScopeParam(row.scope) : [],
60
+ status: row.status,
61
+ expires_at: row.expires_at.toISOString(),
62
+ });
63
+ });
64
+ router.post("/api/mcp/oauth/approve", gatewayAuth, async (req, res) => {
65
+ const body = (req.body ?? {});
66
+ const requestId = str(body.request_id);
67
+ const workspaceId = str(body.workspace_id).trim();
68
+ const raw = requestId ? await store.getRequest(requestId) : null;
69
+ if (!raw) {
70
+ res.status(404).json({ error: "not_found" });
71
+ return;
72
+ }
73
+ const row = await expireIfNeeded(raw);
74
+ if (row.status !== "pending") {
75
+ res.status(400).json({ error: "invalid_request", message: `This connection request is ${row.status}. Start again from your MCP client.` });
76
+ return;
77
+ }
78
+ const scopes = normalizeScopes(body.scopes);
79
+ if (!scopes) {
80
+ res.status(400).json({ error: "invalid_request", message: "Choose at least one valid permission for this connection." });
81
+ return;
82
+ }
83
+ const who = identity(req);
84
+ // The gateway already verified membership for the X-Workspace-ID header.
85
+ // Requiring the body value to equal that header makes the body value
86
+ // trustworthy without a second membership check here.
87
+ if (!workspaceId || workspaceId !== who.workspaceId) {
88
+ res.status(400).json({
89
+ error: "invalid_request",
90
+ message: "workspace_id must match the workspace this request was made from. Reload the page and try again.",
91
+ });
92
+ return;
93
+ }
94
+ const grantId = newId("grant");
95
+ await store.insertGrant({
96
+ id: grantId,
97
+ client_id: row.client_id,
98
+ user_id: who.userId,
99
+ user_email: who.userEmail,
100
+ org_id: who.orgId,
101
+ workspace_id: workspaceId,
102
+ scopes,
103
+ });
104
+ const code = newRawToken("mcp_ac_");
105
+ await store.insertCode({
106
+ code_hash: sha256Hex(code),
107
+ grant_id: grantId,
108
+ client_id: row.client_id,
109
+ redirect_uri: row.redirect_uri,
110
+ code_challenge: row.code_challenge,
111
+ code_challenge_method: row.code_challenge_method,
112
+ expires_at: new Date(Date.now() + AUTH_CODE_TTL_MS),
113
+ });
114
+ await store.setRequestStatus(row.id, "approved");
115
+ audit.emit("oauth_grant_created", {
116
+ grant_id: grantId,
117
+ user_id: who.userId,
118
+ workspace_id: workspaceId,
119
+ scopes,
120
+ client_id: row.client_id,
121
+ });
122
+ const redirect = new URL(row.redirect_uri);
123
+ redirect.searchParams.set("code", code);
124
+ if (row.state)
125
+ redirect.searchParams.set("state", row.state);
126
+ res.json({ redirect_url: redirect.toString() });
127
+ });
128
+ router.post("/api/mcp/oauth/deny", gatewayAuth, async (req, res) => {
129
+ const body = (req.body ?? {});
130
+ const requestId = str(body.request_id);
131
+ const raw = requestId ? await store.getRequest(requestId) : null;
132
+ if (!raw) {
133
+ res.status(404).json({ error: "not_found" });
134
+ return;
135
+ }
136
+ const row = await expireIfNeeded(raw);
137
+ if (row.status === "pending") {
138
+ await store.setRequestStatus(row.id, "denied");
139
+ }
140
+ const redirect = new URL(row.redirect_uri);
141
+ redirect.searchParams.set("error", "access_denied");
142
+ if (row.state)
143
+ redirect.searchParams.set("state", row.state);
144
+ res.json({ redirect_url: redirect.toString() });
145
+ });
146
+ return router;
147
+ }
148
+ //# sourceMappingURL=consent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consent.js","sourceRoot":"","sources":["../../src/http/consent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Internal service-to-service JWT, mirroring the Go side
3
+ * (services/iam-service/internal/platform/auth.go):
4
+ *
5
+ * - GenerateInternalToken(key, source): HS256, claims { jti, sub: source,
6
+ * iat, exp: iat+30s, iss: "bios-saas-internal" }.
7
+ * - ValidateInternalToken(token, key): signature must verify, issuer must be
8
+ * "bios-saas-internal", expiry must not have passed.
9
+ *
10
+ * The verifier here is deliberately strict where Go is lax: only HS256 is
11
+ * accepted and `exp` is required. The only legitimate issuer (the gateway)
12
+ * always signs HS256 with a 30 second expiry, so the stricter check cannot
13
+ * reject valid traffic and fails closed on anything else.
14
+ */
15
+ export declare const INTERNAL_ISSUER = "bios-saas-internal";
16
+ export declare function generateInternalToken(serviceKey: string, sourceService: string, ttlSeconds?: number, now?: () => number): string;
17
+ export interface InternalTokenClaims {
18
+ sub: string;
19
+ }
20
+ /** Returns the claims when the token is valid, null otherwise. Never throws. */
21
+ export declare function verifyInternalToken(token: string, serviceKey: string, now?: () => number): InternalTokenClaims | null;
22
+ //# sourceMappingURL=internal-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal-auth.d.ts","sourceRoot":"","sources":["../../src/http/internal-auth.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH,eAAO,MAAM,eAAe,uBAAuB,CAAC;AAgBpD,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,UAAU,SAAK,EACf,GAAG,GAAE,MAAM,MAAiB,GAC3B,MAAM,CAcR;AAED,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAC;CACb;AAED,gFAAgF;AAChF,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,GAAG,GAAE,MAAM,MAAiB,GAC3B,mBAAmB,GAAG,IAAI,CA6B5B"}
@@ -0,0 +1,73 @@
1
+ import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
2
+ /**
3
+ * Internal service-to-service JWT, mirroring the Go side
4
+ * (services/iam-service/internal/platform/auth.go):
5
+ *
6
+ * - GenerateInternalToken(key, source): HS256, claims { jti, sub: source,
7
+ * iat, exp: iat+30s, iss: "bios-saas-internal" }.
8
+ * - ValidateInternalToken(token, key): signature must verify, issuer must be
9
+ * "bios-saas-internal", expiry must not have passed.
10
+ *
11
+ * The verifier here is deliberately strict where Go is lax: only HS256 is
12
+ * accepted and `exp` is required. The only legitimate issuer (the gateway)
13
+ * always signs HS256 with a 30 second expiry, so the stricter check cannot
14
+ * reject valid traffic and fails closed on anything else.
15
+ */
16
+ export const INTERNAL_ISSUER = "bios-saas-internal";
17
+ const b64url = (buf) => buf.toString("base64url");
18
+ function encodeSegment(value) {
19
+ return b64url(Buffer.from(JSON.stringify(value), "utf8"));
20
+ }
21
+ function decodeSegment(segment) {
22
+ try {
23
+ return JSON.parse(Buffer.from(segment, "base64url").toString("utf8"));
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ }
29
+ export function generateInternalToken(serviceKey, sourceService, ttlSeconds = 30, now = Date.now) {
30
+ const iat = Math.floor(now() / 1000);
31
+ const header = encodeSegment({ alg: "HS256", typ: "JWT" });
32
+ const payload = encodeSegment({
33
+ jti: randomUUID(),
34
+ sub: sourceService,
35
+ iat,
36
+ exp: iat + ttlSeconds,
37
+ iss: INTERNAL_ISSUER,
38
+ });
39
+ const signature = b64url(createHmac("sha256", serviceKey).update(`${header}.${payload}`).digest());
40
+ return `${header}.${payload}.${signature}`;
41
+ }
42
+ /** Returns the claims when the token is valid, null otherwise. Never throws. */
43
+ export function verifyInternalToken(token, serviceKey, now = Date.now) {
44
+ if (!token || !serviceKey)
45
+ return null;
46
+ const parts = token.split(".");
47
+ if (parts.length !== 3)
48
+ return null;
49
+ const [headerB64, payloadB64, signatureB64] = parts;
50
+ const header = decodeSegment(headerB64);
51
+ if (!header || header.alg !== "HS256")
52
+ return null;
53
+ const expected = createHmac("sha256", serviceKey)
54
+ .update(`${headerB64}.${payloadB64}`)
55
+ .digest();
56
+ let provided;
57
+ try {
58
+ provided = Buffer.from(signatureB64, "base64url");
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ if (provided.length !== expected.length || !timingSafeEqual(provided, expected)) {
64
+ return null;
65
+ }
66
+ const payload = decodeSegment(payloadB64);
67
+ if (!payload || payload.iss !== INTERNAL_ISSUER)
68
+ return null;
69
+ if (typeof payload.exp !== "number" || payload.exp * 1000 <= now())
70
+ return null;
71
+ return { sub: typeof payload.sub === "string" ? payload.sub : "" };
72
+ }
73
+ //# sourceMappingURL=internal-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal-auth.js","sourceRoot":"","sources":["../../src/http/internal-auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/http/main.ts"],"names":[],"mappings":""}