@databrainhq/mcp-server 0.3.0-rc.13 → 0.3.0-rc.15
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/.mcp.json +6 -1
- package/dist/apps/server.d.ts +102 -389
- package/dist/apps/server.js +5 -5
- package/dist/apps/stdio.d.ts +1 -2
- package/dist/apps/stdio.js +6 -6
- package/dist/{bundle-TDQXWIPM.js → bundle-QJNCUMNY.js} +2 -2
- package/dist/{chunk-Q5EYDKSA.js → chunk-2F7V6RZR.js} +511 -107
- package/dist/chunk-2F7V6RZR.js.map +1 -0
- package/dist/chunk-3AH56XX7.js +28 -0
- package/dist/chunk-3AH56XX7.js.map +1 -0
- package/dist/{chunk-AEQTEE5C.js → chunk-4E57ZRZO.js} +302 -63
- package/dist/chunk-4E57ZRZO.js.map +1 -0
- package/dist/{chunk-PSJVYD4W.js → chunk-6MD7KOAU.js} +11723 -10560
- package/dist/chunk-6MD7KOAU.js.map +1 -0
- package/dist/{chunk-HCYZN7I3.js → chunk-FJLRZJ4A.js} +10 -7
- package/dist/chunk-FJLRZJ4A.js.map +1 -0
- package/dist/{chunk-X44C5GW2.js → chunk-MUZ3KEJX.js} +63 -40
- package/dist/chunk-MUZ3KEJX.js.map +1 -0
- package/dist/{chunk-DTFYHLYN.js → chunk-NXE2A4WV.js} +26 -11
- package/dist/chunk-NXE2A4WV.js.map +1 -0
- package/dist/{chunk-233VGFHY.js → chunk-QQ6KNULX.js} +317 -147
- package/dist/chunk-QQ6KNULX.js.map +1 -0
- package/dist/chunk-V5JINLOA.js +506 -0
- package/dist/chunk-V5JINLOA.js.map +1 -0
- package/dist/cli.d.ts +16 -15
- package/dist/cli.js +134 -36
- package/dist/cli.js.map +1 -1
- package/dist/custom-chart/custom-runtime.js +6 -6
- package/dist/custom-chart/template/chart.ts +116 -28
- package/dist/custom-chart/template/icon.svg +1 -1
- package/dist/custom-chart/template/manifest.json +66 -12
- package/dist/custom-chart/template/sample-data.json +21 -3
- package/dist/index.d.ts +1 -2
- package/dist/index.js +67 -13
- package/dist/index.js.map +1 -1
- package/dist/{main-VEKNKWL5.js → main-2FKQQLWX.js} +489 -174
- package/dist/main-2FKQQLWX.js.map +1 -0
- package/dist/{test-only-LJGOY2QA.js → test-only-VH3FF5ML.js} +15 -9
- package/dist/test-only-VH3FF5ML.js.map +1 -0
- package/package.json +20 -9
- package/dist/chunk-233VGFHY.js.map +0 -1
- package/dist/chunk-AEQTEE5C.js.map +0 -1
- package/dist/chunk-DTFYHLYN.js.map +0 -1
- package/dist/chunk-HCYZN7I3.js.map +0 -1
- package/dist/chunk-PSJVYD4W.js.map +0 -1
- package/dist/chunk-Q5EYDKSA.js.map +0 -1
- package/dist/chunk-X44C5GW2.js.map +0 -1
- package/dist/main-VEKNKWL5.js.map +0 -1
- package/dist/test-only-LJGOY2QA.js.map +0 -1
- /package/dist/{bundle-TDQXWIPM.js.map → bundle-QJNCUMNY.js.map} +0 -0
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
registerChartApps
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-QQ6KNULX.js";
|
|
5
5
|
import {
|
|
6
6
|
enableChartApps,
|
|
7
7
|
registerAllPrompts,
|
|
8
8
|
registerAllResources,
|
|
9
9
|
registerAllTools
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-6MD7KOAU.js";
|
|
11
11
|
import {
|
|
12
12
|
DatabrainClient,
|
|
13
13
|
MCP_SERVER_VERSION
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-4E57ZRZO.js";
|
|
15
15
|
|
|
16
16
|
// src/apps/server.ts
|
|
17
17
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
18
18
|
function createChartAppServer(config, backend) {
|
|
19
19
|
const client = new DatabrainClient(config);
|
|
20
|
-
const server = new McpServer(
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const server = new McpServer(
|
|
21
|
+
{ name: "databrain-chart-apps", version: MCP_SERVER_VERSION },
|
|
22
|
+
{
|
|
23
|
+
instructions: "Use read-only chart previews to inspect analytics. Chart interactions inherit guest scope. Never request or expose credentials. Mutation tools retain their existing dry-run and confirmation requirements."
|
|
24
|
+
}
|
|
25
|
+
);
|
|
23
26
|
enableChartApps(server);
|
|
24
27
|
registerAllTools(server, client);
|
|
25
28
|
registerAllResources(server);
|
|
@@ -31,4 +34,4 @@ function createChartAppServer(config, backend) {
|
|
|
31
34
|
export {
|
|
32
35
|
createChartAppServer
|
|
33
36
|
};
|
|
34
|
-
//# sourceMappingURL=chunk-
|
|
37
|
+
//# sourceMappingURL=chunk-FJLRZJ4A.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/apps/server.ts"],"sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { DatabrainClient } from '../client/databrain-client.js';\nimport { registerAllPrompts } from '../prompts/registry.js';\nimport { registerAllResources } from '../resources/registry.js';\nimport { registerAllTools } from '../tools/registry.js';\nimport type { Config } from '../utils/config.js';\nimport { MCP_SERVER_VERSION } from '../version.js';\nimport type { ChartBackend } from './backend.js';\nimport { enableChartApps } from './feature.js';\nimport { registerChartApps } from './registry.js';\n\n/** Reviewable composition seam; hosted owners must retain their principal-bound client/policy. */\nexport function createChartAppServer(\n config: Config,\n backend?: ChartBackend\n): McpServer {\n const client = new DatabrainClient(config);\n const server = new McpServer(\n { name: 'databrain-chart-apps', version: MCP_SERVER_VERSION },\n {\n instructions:\n 'Use read-only chart previews to inspect analytics. Chart interactions inherit guest scope. ' +\n 'Never request or expose credentials. Mutation tools retain their existing dry-run and confirmation requirements.',\n }\n );\n enableChartApps(server);\n registerAllTools(server, client);\n registerAllResources(server);\n registerAllPrompts(server);\n registerChartApps(server, client, backend);\n return server;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA,SAAS,iBAAiB;AAYnB,SAAS,qBACd,QACA,SACW;AACX,QAAM,SAAS,IAAI,gBAAgB,MAAM;AACzC,QAAM,SAAS,IAAI;AAAA,IACjB,EAAE,MAAM,wBAAwB,SAAS,mBAAmB;AAAA,IAC5D;AAAA,MACE,cACE;AAAA,IAEJ;AAAA,EACF;AACA,kBAAgB,MAAM;AACtB,mBAAiB,QAAQ,MAAM;AAC/B,uBAAqB,MAAM;AAC3B,qBAAmB,MAAM;AACzB,oBAAkB,QAAQ,QAAQ,OAAO;AACzC,SAAO;AACT;","names":[]}
|
|
@@ -1,29 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
"mcp:dashboards:write",
|
|
7
|
-
"mcp:datamarts:read",
|
|
8
|
-
"mcp:datamarts:write",
|
|
9
|
-
"mcp:embeds:write",
|
|
10
|
-
"mcp:query",
|
|
11
|
-
"mcp:custom-charts:read",
|
|
12
|
-
"mcp:custom-charts:write"
|
|
13
|
-
];
|
|
14
|
-
var HostedAuthError = class extends Error {
|
|
15
|
-
constructor(status, code, detail) {
|
|
16
|
-
super(code);
|
|
17
|
-
this.status = status;
|
|
18
|
-
this.code = code;
|
|
19
|
-
this.detail = detail;
|
|
20
|
-
}
|
|
21
|
-
};
|
|
2
|
+
import {
|
|
3
|
+
HOSTED_SCOPES,
|
|
4
|
+
HostedAuthError
|
|
5
|
+
} from "./chunk-3AH56XX7.js";
|
|
22
6
|
|
|
23
7
|
// src/hosted/auth.ts
|
|
24
8
|
import { createHash, timingSafeEqual } from "crypto";
|
|
25
|
-
import { open } from "fs/promises";
|
|
26
9
|
import { constants } from "fs";
|
|
10
|
+
import { open } from "fs/promises";
|
|
27
11
|
import { z } from "zod";
|
|
28
12
|
var identity = z.string().min(1).max(512).regex(/^[^\x00-\x1f\x7f]+$/);
|
|
29
13
|
var workspaceSchema = z.object({ id: identity, name: identity.optional() }).strip();
|
|
@@ -36,7 +20,10 @@ var principalSchema = z.object({
|
|
|
36
20
|
workspaceName: identity.optional(),
|
|
37
21
|
workspaces: z.union([
|
|
38
22
|
z.object({ mode: z.literal("all") }).strip(),
|
|
39
|
-
z.object({
|
|
23
|
+
z.object({
|
|
24
|
+
mode: z.literal("list"),
|
|
25
|
+
ids: z.array(identity).min(1).max(100)
|
|
26
|
+
}).strip()
|
|
40
27
|
]).optional(),
|
|
41
28
|
grantedWorkspaces: z.array(workspaceSchema).max(1e3).optional(),
|
|
42
29
|
// The issuer's own pick; recomputed below from grantedWorkspaces, never trusted on its own.
|
|
@@ -48,8 +35,16 @@ var principalSchema = z.object({
|
|
|
48
35
|
tenantIds: z.array(identity).optional()
|
|
49
36
|
}).strict();
|
|
50
37
|
function normalizeWorkspaces(value) {
|
|
51
|
-
const {
|
|
52
|
-
|
|
38
|
+
const {
|
|
39
|
+
defaultWorkspace: _default,
|
|
40
|
+
workspaceId,
|
|
41
|
+
workspaceName,
|
|
42
|
+
grantedWorkspaces,
|
|
43
|
+
...rest
|
|
44
|
+
} = value;
|
|
45
|
+
const granted = grantedWorkspaces ?? (workspaceId ? [
|
|
46
|
+
workspaceName === void 0 ? { id: workspaceId } : { id: workspaceId, name: workspaceName }
|
|
47
|
+
] : []);
|
|
53
48
|
if (!granted.length || new Set(granted.map((item) => item.id)).size !== granted.length || workspaceId !== void 0 && !granted.some((item) => item.id === workspaceId)) {
|
|
54
49
|
throw new HostedAuthError(401, "invalid_token");
|
|
55
50
|
}
|
|
@@ -74,12 +69,21 @@ var FilePatStore = class {
|
|
|
74
69
|
this.path = path;
|
|
75
70
|
}
|
|
76
71
|
async findByHash(hash) {
|
|
77
|
-
const handle = await open(
|
|
72
|
+
const handle = await open(
|
|
73
|
+
this.path,
|
|
74
|
+
constants.O_RDONLY | constants.O_NOFOLLOW
|
|
75
|
+
);
|
|
78
76
|
try {
|
|
79
77
|
const stat = await handle.stat();
|
|
80
|
-
if (!stat.isFile() || (stat.mode & 63) !== 0 || stat.size > 1048576 || process.getuid && stat.uid !== process.getuid())
|
|
78
|
+
if (!stat.isFile() || (stat.mode & 63) !== 0 || stat.size > 1048576 || process.getuid && stat.uid !== process.getuid())
|
|
79
|
+
throw new Error("Invalid credential store");
|
|
81
80
|
const records = z.array(recordSchema).max(1e3).parse(JSON.parse(await handle.readFile("utf8")));
|
|
82
|
-
const matches = records.filter(
|
|
81
|
+
const matches = records.filter(
|
|
82
|
+
(record) => timingSafeEqual(
|
|
83
|
+
Buffer.from(record.hash, "hex"),
|
|
84
|
+
Buffer.from(hash, "hex")
|
|
85
|
+
)
|
|
86
|
+
);
|
|
83
87
|
if (matches.length > 1) throw new Error("Ambiguous credential store");
|
|
84
88
|
return matches[0];
|
|
85
89
|
} finally {
|
|
@@ -89,28 +93,49 @@ var FilePatStore = class {
|
|
|
89
93
|
};
|
|
90
94
|
function validatePrincipal(value, resource, kind) {
|
|
91
95
|
const parsed = principalSchema.safeParse(value);
|
|
92
|
-
if (!parsed.success || parsed.data.resource !== resource || parsed.data.credentialType !== kind || parsed.data.expiresAt !== void 0 && parsed.data.expiresAt <= Date.now() / 1e3 || kind === "pat" && parsed.success && parsed.data.expiresAt === void 0)
|
|
96
|
+
if (!parsed.success || parsed.data.resource !== resource || parsed.data.credentialType !== kind || parsed.data.expiresAt !== void 0 && parsed.data.expiresAt <= Date.now() / 1e3 || kind === "pat" && parsed.success && parsed.data.expiresAt === void 0)
|
|
97
|
+
throw new HostedAuthError(401, "invalid_token");
|
|
93
98
|
return normalizeWorkspaces(parsed.data);
|
|
94
99
|
}
|
|
95
100
|
function createBearerVerifier(adapter, resource, pats) {
|
|
96
101
|
return async (token) => {
|
|
97
102
|
try {
|
|
98
|
-
if (!/^[A-Za-z0-9._
|
|
103
|
+
if (!/^[A-Za-z0-9._~+/-]+=*$/.test(token) || token.length > 8192)
|
|
104
|
+
throw new HostedAuthError(401, "invalid_token");
|
|
99
105
|
if (!token.startsWith("dbp_")) {
|
|
100
|
-
return validatePrincipal(
|
|
106
|
+
return validatePrincipal(
|
|
107
|
+
await adapter.verifyOAuth(token, resource),
|
|
108
|
+
resource,
|
|
109
|
+
"oauth"
|
|
110
|
+
);
|
|
101
111
|
}
|
|
102
|
-
if (!/^dbp_[A-Za-z0-9_-]{32,}$/.test(token))
|
|
112
|
+
if (!/^dbp_[A-Za-z0-9_-]{32,}$/.test(token))
|
|
113
|
+
throw new HostedAuthError(401, "invalid_token");
|
|
103
114
|
const record = await pats?.findByHash(hashCredential(token));
|
|
104
|
-
if (!record || record.revokedAt !== null || record.hash !== hashCredential(token))
|
|
115
|
+
if (!record || record.revokedAt !== null || record.hash !== hashCredential(token))
|
|
116
|
+
throw new HostedAuthError(401, "invalid_token");
|
|
105
117
|
const stored = validatePrincipal(record.principal, resource, "pat");
|
|
106
|
-
const resolved = validatePrincipal(
|
|
107
|
-
|
|
108
|
-
|
|
118
|
+
const resolved = validatePrincipal(
|
|
119
|
+
await adapter.resolvePatGrant(structuredClone(stored)),
|
|
120
|
+
resource,
|
|
121
|
+
"pat"
|
|
122
|
+
);
|
|
123
|
+
for (const key of [
|
|
124
|
+
"userId",
|
|
125
|
+
"companyId",
|
|
126
|
+
"clientId",
|
|
127
|
+
"resource"
|
|
128
|
+
]) {
|
|
129
|
+
if (resolved[key] !== stored[key])
|
|
130
|
+
throw new HostedAuthError(401, "invalid_token");
|
|
109
131
|
}
|
|
110
|
-
if (resolved.grantedWorkspaces.some(
|
|
132
|
+
if (resolved.grantedWorkspaces.some(
|
|
133
|
+
(item) => !stored.grantedWorkspaces.some((old) => old.id === item.id)
|
|
134
|
+
)) {
|
|
111
135
|
throw new HostedAuthError(401, "invalid_token");
|
|
112
136
|
}
|
|
113
|
-
if (resolved.expiresAt > stored.expiresAt || resolved.scopes.some((scope) => !stored.scopes.includes(scope)) || resolved.tenantIds?.some((id) => !stored.tenantIds?.includes(id)))
|
|
137
|
+
if (resolved.expiresAt > stored.expiresAt || resolved.scopes.some((scope) => !stored.scopes.includes(scope)) || resolved.tenantIds?.some((id) => !stored.tenantIds?.includes(id)))
|
|
138
|
+
throw new HostedAuthError(401, "invalid_token");
|
|
114
139
|
return resolved;
|
|
115
140
|
} catch (error) {
|
|
116
141
|
if (error instanceof HostedAuthError) throw error;
|
|
@@ -120,11 +145,9 @@ function createBearerVerifier(adapter, resource, pats) {
|
|
|
120
145
|
}
|
|
121
146
|
|
|
122
147
|
export {
|
|
123
|
-
HOSTED_SCOPES,
|
|
124
|
-
HostedAuthError,
|
|
125
148
|
principalSchema,
|
|
126
149
|
hashCredential,
|
|
127
150
|
FilePatStore,
|
|
128
151
|
createBearerVerifier
|
|
129
152
|
};
|
|
130
|
-
//# sourceMappingURL=chunk-
|
|
153
|
+
//# sourceMappingURL=chunk-MUZ3KEJX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hosted/auth.ts"],"sourcesContent":["import { createHash, timingSafeEqual } from 'node:crypto';\nimport { constants } from 'node:fs';\nimport { open } from 'node:fs/promises';\nimport { z } from 'zod';\nimport type {\n HostedAdapter,\n HostedPrincipal,\n PatRecord,\n PatStore,\n} from './types.js';\nimport { HOSTED_SCOPES, HostedAuthError } from './types.js';\n\nconst identity = z\n .string()\n .min(1)\n .max(512)\n // biome-ignore lint/suspicious/noControlCharactersInRegex: Identity validation intentionally rejects control characters.\n .regex(/^[^\\x00-\\x1f\\x7f]+$/);\nconst workspaceSchema = z\n .object({ id: identity, name: identity.optional() })\n .strip();\nexport const principalSchema = z\n .object({\n actorKind: z.literal('user'),\n credentialType: z.enum(['oauth', 'pat']),\n userId: identity,\n companyId: identity,\n workspaceId: identity.optional(),\n workspaceName: identity.optional(),\n workspaces: z\n .union([\n z.object({ mode: z.literal('all') }).strip(),\n z\n .object({\n mode: z.literal('list'),\n ids: z.array(identity).min(1).max(100),\n })\n .strip(),\n ])\n .optional(),\n grantedWorkspaces: z.array(workspaceSchema).max(1000).optional(),\n // The issuer's own pick; recomputed below from grantedWorkspaces, never trusted on its own.\n defaultWorkspace: workspaceSchema.optional(),\n clientId: identity,\n resource: z.string().url(),\n scopes: z.array(z.enum(HOSTED_SCOPES)),\n expiresAt: z.number().finite().positive().optional(),\n tenantIds: z.array(identity).optional(),\n })\n .strict();\n\n/**\n * One shape for every issuer: the live granted list, and `workspaceId`/`workspaceName`\n * only when exactly one workspace is granted (the default for each call). An issuer\n * that grants one workspace sends only `workspaceId`; read that as a one-item list.\n * No workspace left, duplicate ids, or a `workspaceId` outside the list is refused.\n */\nfunction normalizeWorkspaces(\n value: z.infer<typeof principalSchema>\n): HostedPrincipal {\n const {\n defaultWorkspace: _default,\n workspaceId,\n workspaceName,\n grantedWorkspaces,\n ...rest\n } = value;\n const granted =\n grantedWorkspaces ??\n (workspaceId\n ? [\n workspaceName === undefined\n ? { id: workspaceId }\n : { id: workspaceId, name: workspaceName },\n ]\n : []);\n if (\n !granted.length ||\n new Set(granted.map((item) => item.id)).size !== granted.length ||\n (workspaceId !== undefined &&\n !granted.some((item) => item.id === workspaceId))\n ) {\n throw new HostedAuthError(401, 'invalid_token');\n }\n const only = granted.length === 1 ? granted[0] : undefined;\n return {\n ...rest,\n grantedWorkspaces: granted,\n ...(only ? { workspaceId: only.id } : {}),\n ...(only?.name !== undefined ? { workspaceName: only.name } : {}),\n };\n}\nconst recordSchema = z\n .object({\n hash: z.string().regex(/^[a-f0-9]{64}$/),\n revokedAt: z.number().finite().nullable(),\n principal: principalSchema,\n })\n .strict();\n\nexport function hashCredential(token: string): string {\n return createHash('sha256').update(token).digest('hex');\n}\n\n/** Reread on every request: an atomic file replacement revokes immediately, without restart. */\nexport class FilePatStore implements PatStore {\n constructor(private readonly path: string) {}\n async findByHash(hash: string): Promise<PatRecord | undefined> {\n const handle = await open(\n this.path,\n constants.O_RDONLY | constants.O_NOFOLLOW\n );\n try {\n const stat = await handle.stat();\n if (\n !stat.isFile() ||\n (stat.mode & 0o077) !== 0 ||\n stat.size > 1_048_576 ||\n (process.getuid && stat.uid !== process.getuid())\n )\n throw new Error('Invalid credential store');\n const records = z\n .array(recordSchema)\n .max(1000)\n .parse(JSON.parse(await handle.readFile('utf8')));\n const matches = records.filter((record) =>\n timingSafeEqual(\n Buffer.from(record.hash, 'hex'),\n Buffer.from(hash, 'hex')\n )\n );\n // Duplicate records are ambiguous, including an active/revoked pair.\n if (matches.length > 1) throw new Error('Ambiguous credential store');\n return matches[0];\n } finally {\n await handle.close();\n }\n }\n}\n\nfunction validatePrincipal(\n value: unknown,\n resource: string,\n kind: 'oauth' | 'pat'\n): HostedPrincipal {\n const parsed = principalSchema.safeParse(value);\n if (\n !parsed.success ||\n parsed.data.resource !== resource ||\n parsed.data.credentialType !== kind ||\n (parsed.data.expiresAt !== undefined &&\n parsed.data.expiresAt <= Date.now() / 1000) ||\n (kind === 'pat' && parsed.success && parsed.data.expiresAt === undefined)\n )\n throw new HostedAuthError(401, 'invalid_token');\n return normalizeWorkspaces(parsed.data);\n}\n\nexport function createBearerVerifier(\n adapter: HostedAdapter,\n resource: string,\n pats?: PatStore\n) {\n return async (token: string): Promise<HostedPrincipal> => {\n try {\n if (!/^[A-Za-z0-9._~+/-]+=*$/.test(token) || token.length > 8192)\n throw new HostedAuthError(401, 'invalid_token');\n if (!token.startsWith('dbp_')) {\n return validatePrincipal(\n await adapter.verifyOAuth(token, resource),\n resource,\n 'oauth'\n );\n }\n if (!/^dbp_[A-Za-z0-9_-]{32,}$/.test(token))\n throw new HostedAuthError(401, 'invalid_token');\n const record = await pats?.findByHash(hashCredential(token));\n if (\n !record ||\n record.revokedAt !== null ||\n record.hash !== hashCredential(token)\n )\n throw new HostedAuthError(401, 'invalid_token');\n const stored = validatePrincipal(record.principal, resource, 'pat');\n const resolved = validatePrincipal(\n await adapter.resolvePatGrant(structuredClone(stored)),\n resource,\n 'pat'\n );\n // Membership resolution may narrow a stored grant, never move or expand it.\n for (const key of [\n 'userId',\n 'companyId',\n 'clientId',\n 'resource',\n ] as const) {\n if (resolved[key] !== stored[key])\n throw new HostedAuthError(401, 'invalid_token');\n }\n if (\n resolved.grantedWorkspaces!.some(\n (item) => !stored.grantedWorkspaces!.some((old) => old.id === item.id)\n )\n ) {\n throw new HostedAuthError(401, 'invalid_token');\n }\n if (\n resolved.expiresAt! > stored.expiresAt! ||\n resolved.scopes.some((scope) => !stored.scopes.includes(scope)) ||\n resolved.tenantIds?.some((id) => !stored.tenantIds?.includes(id))\n )\n throw new HostedAuthError(401, 'invalid_token');\n return resolved;\n } catch (error) {\n if (error instanceof HostedAuthError) throw error;\n // No raw verifier, filesystem or remote errors can disclose credential values.\n throw new HostedAuthError(401, 'invalid_token');\n }\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
oauthConfigSchema,
|
|
4
4
|
oauthHttpsUrl
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-4E57ZRZO.js";
|
|
6
6
|
|
|
7
7
|
// src/utils/config.ts
|
|
8
|
-
import {
|
|
8
|
+
import { ZodError, z } from "zod";
|
|
9
9
|
var DEFAULT_POSTHOG_API_KEY = "phc_YgR4BO3sbtQAUvTfRUEN5xBzzPrJfrEG9STJEy3mNO2";
|
|
10
10
|
var DEFAULT_POSTHOG_HOST = "https://us.i.posthog.com";
|
|
11
11
|
var configSchema = z.object({
|
|
@@ -22,17 +22,27 @@ var configSchema = z.object({
|
|
|
22
22
|
message: "DATABRAIN_API_URL must use HTTPS without userinfo or fragments when OAuth is configured"
|
|
23
23
|
});
|
|
24
24
|
function formatConfigError(err) {
|
|
25
|
-
const issues = err.issues.map(
|
|
25
|
+
const issues = err.issues.map(
|
|
26
|
+
(i) => i.path[0] === "oauth" ? `Invalid or missing OAuth setting: ${i.path.join(".")}` : i.message
|
|
27
|
+
);
|
|
26
28
|
const lines = ["[databrain] Configuration error:"];
|
|
27
29
|
for (const issue of issues) {
|
|
28
30
|
lines.push(` - ${issue}`);
|
|
29
31
|
}
|
|
30
32
|
lines.push("");
|
|
31
|
-
lines.push(
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
lines.push(
|
|
34
|
+
" Set DATABRAIN_SERVICE_TOKEN (Settings > Service Tokens) \u2014 org-level token for setup and management."
|
|
35
|
+
);
|
|
36
|
+
lines.push(
|
|
37
|
+
' Then add to your MCP config: { "env": { "DATABRAIN_SERVICE_TOKEN": "..." } }'
|
|
38
|
+
);
|
|
39
|
+
lines.push(
|
|
40
|
+
" Or configure DATABRAIN_OAUTH_ISSUER, DATABRAIN_OAUTH_CLIENT_ID and DATABRAIN_OAUTH_CLIENT_SECRET for service credentials. See specs/oauth-client-credentials.md."
|
|
41
|
+
);
|
|
34
42
|
lines.push("");
|
|
35
|
-
lines.push(
|
|
43
|
+
lines.push(
|
|
44
|
+
" Self-hosted? Also set DATABRAIN_API_URL to your instance's API origin (e.g. https://api.your-databrain.example.com). Tokens minted on cloud do not work against a self-hosted instance and vice versa."
|
|
45
|
+
);
|
|
36
46
|
return lines.join("\n");
|
|
37
47
|
}
|
|
38
48
|
function loadConfig() {
|
|
@@ -45,7 +55,9 @@ function loadConfig() {
|
|
|
45
55
|
scope: process.env.DATABRAIN_OAUTH_SCOPE || void 0,
|
|
46
56
|
authMethod: process.env.DATABRAIN_OAUTH_AUTH_METHOD || void 0
|
|
47
57
|
};
|
|
48
|
-
const oauthConfigured = Object.values(oauthEnv).some(
|
|
58
|
+
const oauthConfigured = Object.values(oauthEnv).some(
|
|
59
|
+
(value) => value !== void 0
|
|
60
|
+
);
|
|
49
61
|
return configSchema.parse({
|
|
50
62
|
serviceToken: process.env.DATABRAIN_SERVICE_TOKEN || void 0,
|
|
51
63
|
apiToken: process.env.DATABRAIN_API_TOKEN || void 0,
|
|
@@ -60,8 +72,11 @@ function loadConfig() {
|
|
|
60
72
|
});
|
|
61
73
|
} catch (err) {
|
|
62
74
|
if (err instanceof ZodError) {
|
|
63
|
-
process.stderr.write(formatConfigError(err)
|
|
64
|
-
|
|
75
|
+
process.stderr.write(`${formatConfigError(err)}
|
|
76
|
+
`);
|
|
77
|
+
throw new Error(
|
|
78
|
+
"Invalid Databrain configuration. Check the named environment settings."
|
|
79
|
+
);
|
|
65
80
|
}
|
|
66
81
|
throw err;
|
|
67
82
|
}
|
|
@@ -72,4 +87,4 @@ export {
|
|
|
72
87
|
DEFAULT_POSTHOG_HOST,
|
|
73
88
|
loadConfig
|
|
74
89
|
};
|
|
75
|
-
//# sourceMappingURL=chunk-
|
|
90
|
+
//# sourceMappingURL=chunk-NXE2A4WV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/config.ts"],"sourcesContent":["import { ZodError, z } from 'zod';\nimport {\n oauthConfigSchema,\n oauthHttpsUrl,\n} from '../client/oauth-client-credentials.js';\n\n// PostHog project token for Databrain's own MCP usage analytics. This is a\n// public write-only key (like a web analytics site ID), safe to ship in the\n// package — it lets the CLI send telemetry out of the box, matching how\n// DATABRAIN_API_URL already ships with a working default.\nexport const DEFAULT_POSTHOG_API_KEY =\n 'phc_YgR4BO3sbtQAUvTfRUEN5xBzzPrJfrEG9STJEy3mNO2';\nexport const DEFAULT_POSTHOG_HOST = 'https://us.i.posthog.com';\n\nconst configSchema = z\n .object({\n serviceToken: z.string().min(1).optional(),\n apiToken: z.string().min(1).optional(),\n oauth: oauthConfigSchema.optional(),\n apiUrl: z.string().url().default('https://api.usedatabrain.com'),\n demoDomain: z.string().url().default('https://demo.usedatabrain.com'),\n posthogApiKey: z.string().min(1).optional(),\n posthogHost: z.string().url().optional(),\n })\n .refine((c) => c.serviceToken || c.apiToken || c.oauth, {\n message:\n 'Set DATABRAIN_SERVICE_TOKEN, DATABRAIN_API_TOKEN, or complete DATABRAIN_OAUTH_* client credentials',\n })\n .refine((c) => !c.oauth || oauthHttpsUrl.safeParse(c.apiUrl).success, {\n message:\n 'DATABRAIN_API_URL must use HTTPS without userinfo or fragments when OAuth is configured',\n });\n\nexport type Config = z.infer<typeof configSchema>;\n\nfunction formatConfigError(err: ZodError): string {\n const issues = err.issues.map((i) =>\n i.path[0] === 'oauth'\n ? `Invalid or missing OAuth setting: ${i.path.join('.')}`\n : i.message\n );\n const lines = ['[databrain] Configuration error:'];\n for (const issue of issues) {\n lines.push(` - ${issue}`);\n }\n lines.push('');\n lines.push(\n ' Set DATABRAIN_SERVICE_TOKEN (Settings > Service Tokens) — org-level token for setup and management.'\n );\n lines.push(\n ' Then add to your MCP config: { \"env\": { \"DATABRAIN_SERVICE_TOKEN\": \"...\" } }'\n );\n lines.push(\n ' Or configure DATABRAIN_OAUTH_ISSUER, DATABRAIN_OAUTH_CLIENT_ID and DATABRAIN_OAUTH_CLIENT_SECRET for service credentials. See specs/oauth-client-credentials.md.'\n );\n lines.push('');\n lines.push(\n \" Self-hosted? Also set DATABRAIN_API_URL to your instance's API origin (e.g. https://api.your-databrain.example.com). Tokens minted on cloud do not work against a self-hosted instance and vice versa.\"\n );\n return lines.join('\\n');\n}\n\nexport function loadConfig(): Config {\n try {\n const oauthEnv = {\n issuer: process.env.DATABRAIN_OAUTH_ISSUER || undefined,\n clientId: process.env.DATABRAIN_OAUTH_CLIENT_ID || undefined,\n clientSecret: process.env.DATABRAIN_OAUTH_CLIENT_SECRET || undefined,\n resource: process.env.DATABRAIN_OAUTH_RESOURCE || undefined,\n scope: process.env.DATABRAIN_OAUTH_SCOPE || undefined,\n authMethod: process.env.DATABRAIN_OAUTH_AUTH_METHOD || undefined,\n };\n const oauthConfigured = Object.values(oauthEnv).some(\n (value) => value !== undefined\n );\n return configSchema.parse({\n serviceToken: process.env.DATABRAIN_SERVICE_TOKEN || undefined,\n apiToken: process.env.DATABRAIN_API_TOKEN || undefined,\n oauth: oauthConfigured\n ? {\n ...oauthEnv,\n resource:\n oauthEnv.resource ||\n process.env.DATABRAIN_API_URL ||\n 'https://api.usedatabrain.com',\n }\n : undefined,\n apiUrl: process.env.DATABRAIN_API_URL || 'https://api.usedatabrain.com',\n demoDomain:\n process.env.DATABRAIN_DEMO_DOMAIN || 'https://demo.usedatabrain.com',\n posthogApiKey:\n process.env.POSTHOG_PROJECT_API_KEY || DEFAULT_POSTHOG_API_KEY,\n posthogHost: process.env.POSTHOG_HOST || DEFAULT_POSTHOG_HOST,\n });\n } catch (err) {\n if (err instanceof ZodError) {\n process.stderr.write(`${formatConfigError(err)}\\n`);\n // Zod enum failures can contain the supplied value; index.ts serializes\n // startup errors. Do not expose any OAuth config values to that logger.\n throw new Error(\n 'Invalid Databrain configuration. Check the named environment settings.'\n );\n }\n throw err;\n }\n}\n"],"mappings":";;;;;;;AAAA,SAAS,UAAU,SAAS;AAUrB,IAAM,0BACX;AACK,IAAM,uBAAuB;AAEpC,IAAM,eAAe,EAClB,OAAO;AAAA,EACN,cAAc,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,EACzC,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,EACrC,OAAO,kBAAkB,SAAS;AAAA,EAClC,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,8BAA8B;AAAA,EAC/D,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,+BAA+B;AAAA,EACpE,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,EAC1C,aAAa,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AACzC,CAAC,EACA,OAAO,CAAC,MAAM,EAAE,gBAAgB,EAAE,YAAY,EAAE,OAAO;AAAA,EACtD,SACE;AACJ,CAAC,EACA,OAAO,CAAC,MAAM,CAAC,EAAE,SAAS,cAAc,UAAU,EAAE,MAAM,EAAE,SAAS;AAAA,EACpE,SACE;AACJ,CAAC;AAIH,SAAS,kBAAkB,KAAuB;AAChD,QAAM,SAAS,IAAI,OAAO;AAAA,IAAI,CAAC,MAC7B,EAAE,KAAK,CAAC,MAAM,UACV,qCAAqC,EAAE,KAAK,KAAK,GAAG,CAAC,KACrD,EAAE;AAAA,EACR;AACA,QAAM,QAAQ,CAAC,kCAAkC;AACjD,aAAW,SAAS,QAAQ;AAC1B,UAAM,KAAK,OAAO,KAAK,EAAE;AAAA,EAC3B;AACA,QAAM,KAAK,EAAE;AACb,QAAM;AAAA,IACJ;AAAA,EACF;AACA,QAAM;AAAA,IACJ;AAAA,EACF;AACA,QAAM;AAAA,IACJ;AAAA,EACF;AACA,QAAM,KAAK,EAAE;AACb,QAAM;AAAA,IACJ;AAAA,EACF;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEO,SAAS,aAAqB;AACnC,MAAI;AACF,UAAM,WAAW;AAAA,MACf,QAAQ,QAAQ,IAAI,0BAA0B;AAAA,MAC9C,UAAU,QAAQ,IAAI,6BAA6B;AAAA,MACnD,cAAc,QAAQ,IAAI,iCAAiC;AAAA,MAC3D,UAAU,QAAQ,IAAI,4BAA4B;AAAA,MAClD,OAAO,QAAQ,IAAI,yBAAyB;AAAA,MAC5C,YAAY,QAAQ,IAAI,+BAA+B;AAAA,IACzD;AACA,UAAM,kBAAkB,OAAO,OAAO,QAAQ,EAAE;AAAA,MAC9C,CAAC,UAAU,UAAU;AAAA,IACvB;AACA,WAAO,aAAa,MAAM;AAAA,MACxB,cAAc,QAAQ,IAAI,2BAA2B;AAAA,MACrD,UAAU,QAAQ,IAAI,uBAAuB;AAAA,MAC7C,OAAO,kBACH;AAAA,QACE,GAAG;AAAA,QACH,UACE,SAAS,YACT,QAAQ,IAAI,qBACZ;AAAA,MACJ,IACA;AAAA,MACJ,QAAQ,QAAQ,IAAI,qBAAqB;AAAA,MACzC,YACE,QAAQ,IAAI,yBAAyB;AAAA,MACvC,eACE,QAAQ,IAAI,2BAA2B;AAAA,MACzC,aAAa,QAAQ,IAAI,gBAAgB;AAAA,IAC3C,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,QAAI,eAAe,UAAU;AAC3B,cAAQ,OAAO,MAAM,GAAG,kBAAkB,GAAG,CAAC;AAAA,CAAI;AAGlD,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,UAAM;AAAA,EACR;AACF;","names":[]}
|