@rudderhq/shared 0.5.2-canary.2 → 0.6.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 (79) hide show
  1. package/dist/chat-transcript-provenance.d.ts +7 -0
  2. package/dist/chat-transcript-provenance.d.ts.map +1 -0
  3. package/dist/chat-transcript-provenance.js +41 -0
  4. package/dist/chat-transcript-provenance.js.map +1 -0
  5. package/dist/chat-transcript-provenance.test.d.ts +2 -0
  6. package/dist/chat-transcript-provenance.test.d.ts.map +1 -0
  7. package/dist/chat-transcript-provenance.test.js +30 -0
  8. package/dist/chat-transcript-provenance.test.js.map +1 -0
  9. package/dist/chat-transcript-visibility.d.ts +14 -0
  10. package/dist/chat-transcript-visibility.d.ts.map +1 -0
  11. package/dist/chat-transcript-visibility.js +21 -0
  12. package/dist/chat-transcript-visibility.js.map +1 -0
  13. package/dist/config-schema.d.ts +42 -42
  14. package/dist/constants.d.ts +60 -2
  15. package/dist/constants.d.ts.map +1 -1
  16. package/dist/constants.js +70 -2
  17. package/dist/constants.js.map +1 -1
  18. package/dist/index.d.ts +8 -3
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +6 -2
  21. package/dist/index.js.map +1 -1
  22. package/dist/markdown-source-boundary.d.ts +12 -0
  23. package/dist/markdown-source-boundary.d.ts.map +1 -0
  24. package/dist/markdown-source-boundary.js +201 -0
  25. package/dist/markdown-source-boundary.js.map +1 -0
  26. package/dist/types/chat.d.ts +53 -8
  27. package/dist/types/chat.d.ts.map +1 -1
  28. package/dist/types/heartbeat.d.ts +1 -0
  29. package/dist/types/heartbeat.d.ts.map +1 -1
  30. package/dist/types/index.d.ts +2 -1
  31. package/dist/types/index.d.ts.map +1 -1
  32. package/dist/types/mcp.d.ts +146 -0
  33. package/dist/types/mcp.d.ts.map +1 -0
  34. package/dist/types/mcp.js +2 -0
  35. package/dist/types/mcp.js.map +1 -0
  36. package/dist/types/secrets.d.ts +2 -0
  37. package/dist/types/secrets.d.ts.map +1 -1
  38. package/dist/validators/adapter-skills.d.ts +8 -8
  39. package/dist/validators/agent-integration.d.ts +6 -6
  40. package/dist/validators/automation.d.ts +6 -6
  41. package/dist/validators/chat-inline-annotations.test.d.ts +2 -0
  42. package/dist/validators/chat-inline-annotations.test.d.ts.map +1 -0
  43. package/dist/validators/chat-inline-annotations.test.js +251 -0
  44. package/dist/validators/chat-inline-annotations.test.js.map +1 -0
  45. package/dist/validators/chat.d.ts +2437 -39
  46. package/dist/validators/chat.d.ts.map +1 -1
  47. package/dist/validators/chat.js +246 -3
  48. package/dist/validators/chat.js.map +1 -1
  49. package/dist/validators/cost.d.ts +4 -4
  50. package/dist/validators/custom-integration.d.ts +1 -1
  51. package/dist/validators/custom-integration.d.ts.map +1 -1
  52. package/dist/validators/finance.d.ts +4 -4
  53. package/dist/validators/index.d.ts +2 -1
  54. package/dist/validators/index.d.ts.map +1 -1
  55. package/dist/validators/index.js +2 -1
  56. package/dist/validators/index.js.map +1 -1
  57. package/dist/validators/mcp.d.ts +2780 -0
  58. package/dist/validators/mcp.d.ts.map +1 -0
  59. package/dist/validators/mcp.js +500 -0
  60. package/dist/validators/mcp.js.map +1 -0
  61. package/dist/validators/mcp.test.d.ts +2 -0
  62. package/dist/validators/mcp.test.d.ts.map +1 -0
  63. package/dist/validators/mcp.test.js +643 -0
  64. package/dist/validators/mcp.test.js.map +1 -0
  65. package/dist/validators/messenger.d.ts +2 -2
  66. package/dist/validators/messenger.test.js +13 -0
  67. package/dist/validators/messenger.test.js.map +1 -1
  68. package/dist/validators/organization-portability.d.ts +20 -20
  69. package/dist/validators/project.d.ts +6 -6
  70. package/dist/validators/secret.d.ts +3 -3
  71. package/dist/validators/secret.d.ts.map +1 -1
  72. package/dist/validators/secret.js +3 -3
  73. package/dist/validators/secret.js.map +1 -1
  74. package/dist/validators/secret.test.d.ts +2 -0
  75. package/dist/validators/secret.test.d.ts.map +1 -0
  76. package/dist/validators/secret.test.js +20 -0
  77. package/dist/validators/secret.test.js.map +1 -0
  78. package/dist/validators/work-product.d.ts +6 -6
  79. package/package.json +1 -1
@@ -0,0 +1,643 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import * as shared from "../index.js";
3
+ function exportedSchema(name) {
4
+ const value = shared[name];
5
+ expect(value, `${name} must be exported from @rudderhq/shared`).toBeDefined();
6
+ return value ? value : null;
7
+ }
8
+ describe("managed MCP shared contracts", () => {
9
+ it("exports the complete provider, transport, access, and lifecycle catalogs", () => {
10
+ expect(shared.MCP_CONNECTION_PROVIDERS)
11
+ .toEqual(["supabase", "linear", "notion", "custom"]);
12
+ expect(shared.MCP_CONNECTION_TRANSPORTS)
13
+ .toEqual(["stdio", "streamable_http", "legacy_manual"]);
14
+ expect(shared.MCP_CONNECTION_ACCESS_MODES)
15
+ .toEqual(["provider_default", "read_only", "read_write"]);
16
+ expect(shared.MCP_CONNECTION_STATUSES)
17
+ .toEqual([
18
+ "draft",
19
+ "authorizing",
20
+ "selecting_scope",
21
+ "active",
22
+ "needs_reauth",
23
+ "disabled",
24
+ "revoked",
25
+ "error",
26
+ ]);
27
+ expect(shared.MCP_PROVIDER_CATALOG).toEqual(expect.arrayContaining([
28
+ expect.objectContaining({
29
+ id: "supabase",
30
+ accessModes: ["read_only", "read_write"],
31
+ defaultAccessMode: "read_only",
32
+ }),
33
+ expect.objectContaining({
34
+ id: "linear",
35
+ accessModes: ["read_only", "read_write"],
36
+ defaultAccessMode: "read_write",
37
+ }),
38
+ expect.objectContaining({
39
+ id: "notion",
40
+ accessModes: ["provider_default"],
41
+ defaultAccessMode: "provider_default",
42
+ }),
43
+ expect.objectContaining({
44
+ id: "custom",
45
+ transports: ["stdio", "streamable_http"],
46
+ }),
47
+ ]));
48
+ });
49
+ it("accepts curated HTTP and custom STDIO connection inputs with safe config only", () => {
50
+ const schema = exportedSchema("createMcpConnectionSchema");
51
+ if (!schema)
52
+ return;
53
+ expect(schema.safeParse({
54
+ name: "team-supabase",
55
+ displayName: "Team Supabase",
56
+ provider: "supabase",
57
+ transport: "streamable_http",
58
+ accessMode: "read_only",
59
+ safeConfig: {},
60
+ startupTimeoutMs: 10_000,
61
+ toolTimeoutMs: 60_000,
62
+ enabled: true,
63
+ required: false,
64
+ }).success).toBe(true);
65
+ expect(schema.safeParse({
66
+ name: "local-tools",
67
+ displayName: "Local tools",
68
+ provider: "custom",
69
+ transport: "stdio",
70
+ accessMode: "provider_default",
71
+ safeConfig: {
72
+ command: "npx",
73
+ args: ["-y", "@example/mcp-server"],
74
+ cwd: "/workspace",
75
+ staticEnv: { LOG_LEVEL: "info" },
76
+ forwardedEnv: ["HOME"],
77
+ secretEnvNames: ["API_TOKEN"],
78
+ },
79
+ secrets: {
80
+ env: { API_TOKEN: "mutation-only-secret" },
81
+ },
82
+ enabled: true,
83
+ required: true,
84
+ }).success).toBe(true);
85
+ });
86
+ it("rejects internal runtime server names when creating external MCP connections", () => {
87
+ const schema = exportedSchema("createMcpConnectionSchema");
88
+ if (!schema)
89
+ return;
90
+ for (const name of ["rudder-tools", "rudder-browser"]) {
91
+ expect(schema.safeParse({
92
+ name,
93
+ displayName: "Reserved runtime name",
94
+ provider: "custom",
95
+ transport: "streamable_http",
96
+ accessMode: "provider_default",
97
+ safeConfig: { url: "https://mcp.example.com" },
98
+ }).success).toBe(false);
99
+ }
100
+ });
101
+ it("keeps secret mutation fields out of safe connection config", () => {
102
+ const schema = exportedSchema("createMcpConnectionSchema");
103
+ if (!schema)
104
+ return;
105
+ for (const unsafeConfig of [
106
+ { accessToken: "raw-token" },
107
+ { clientSecret: "raw-secret" },
108
+ { credentialSecretId: "11111111-1111-4111-8111-111111111111" },
109
+ { secrets: { bearerToken: "raw-token" } },
110
+ { secretValues: { API_TOKEN: "raw-token" } },
111
+ ]) {
112
+ expect(schema.safeParse({
113
+ name: "unsafe",
114
+ displayName: "Unsafe",
115
+ provider: "custom",
116
+ transport: "streamable_http",
117
+ accessMode: "provider_default",
118
+ safeConfig: {
119
+ url: "https://mcp.example.com",
120
+ ...unsafeConfig,
121
+ },
122
+ }).success).toBe(false);
123
+ }
124
+ });
125
+ it("separates static HTTP config, environment mappings, and mutation-only secrets", () => {
126
+ const schema = exportedSchema("createMcpConnectionSchema");
127
+ if (!schema)
128
+ return;
129
+ const parsed = schema.safeParse({
130
+ name: "custom-http",
131
+ displayName: "Custom HTTP",
132
+ provider: "custom",
133
+ transport: "streamable_http",
134
+ accessMode: "provider_default",
135
+ safeConfig: {
136
+ url: "https://mcp.example.com",
137
+ staticHeaders: { Accept: "application/json" },
138
+ headersFromEnv: { "X-Api-Key": "MCP_API_KEY" },
139
+ secretHeaderNames: ["X-Tenant-Token"],
140
+ },
141
+ secrets: {
142
+ headers: { "X-Tenant-Token": "encrypted-header-secret" },
143
+ },
144
+ });
145
+ expect(parsed.success).toBe(true);
146
+ });
147
+ it("accepts bounded exact-name tool allowlists and denylists for custom MCPs", () => {
148
+ const schema = exportedSchema("createMcpConnectionSchema");
149
+ if (!schema)
150
+ return;
151
+ const base = {
152
+ name: "filtered-tools",
153
+ displayName: "Filtered tools",
154
+ provider: "custom",
155
+ transport: "streamable_http",
156
+ accessMode: "provider_default",
157
+ };
158
+ expect(schema.safeParse({
159
+ ...base,
160
+ safeConfig: {
161
+ url: "https://mcp.example.com",
162
+ toolAllowlist: ["read_rows", "search.docs"],
163
+ toolDenylist: ["delete_rows"],
164
+ },
165
+ }).success).toBe(true);
166
+ expect(schema.safeParse({
167
+ ...base,
168
+ safeConfig: {
169
+ url: "https://mcp.example.com",
170
+ toolAllowlist: ["read rows"],
171
+ },
172
+ }).success).toBe(false);
173
+ });
174
+ it("accepts exactly one manual Authorization or Bearer source and rejects every pair", () => {
175
+ const schema = exportedSchema("createMcpConnectionSchema");
176
+ if (!schema)
177
+ return;
178
+ const base = {
179
+ name: "conflicting-auth",
180
+ displayName: "Conflicting auth",
181
+ provider: "custom",
182
+ transport: "streamable_http",
183
+ accessMode: "provider_default",
184
+ };
185
+ const sources = [
186
+ {
187
+ safeConfig: { headersFromEnv: { Authorization: "MCP_AUTHORIZATION" } },
188
+ },
189
+ {
190
+ safeConfig: { secretHeaderNames: ["Authorization"] },
191
+ secrets: { headers: { Authorization: "Bearer encrypted-header-token" } },
192
+ },
193
+ {
194
+ safeConfig: { bearerTokenEnvVar: "MCP_BEARER_TOKEN" },
195
+ },
196
+ {
197
+ safeConfig: { hasBearerToken: true },
198
+ secrets: { bearerToken: "direct-bearer-token" },
199
+ },
200
+ ];
201
+ for (const source of sources) {
202
+ expect(schema.safeParse({
203
+ ...base,
204
+ safeConfig: { url: "https://mcp.example.com", ...source.safeConfig },
205
+ ...("secrets" in source ? { secrets: source.secrets } : {}),
206
+ }).success).toBe(true);
207
+ }
208
+ for (let first = 0; first < sources.length; first += 1) {
209
+ for (let second = first + 1; second < sources.length; second += 1) {
210
+ const firstSource = sources[first];
211
+ const secondSource = sources[second];
212
+ const headers = {
213
+ ...firstSource.secrets?.headers,
214
+ ...secondSource.secrets?.headers,
215
+ };
216
+ const bearerToken = (firstSource.secrets?.bearerToken
217
+ ?? secondSource.secrets?.bearerToken);
218
+ const secrets = Object.keys(headers).length > 0 || bearerToken
219
+ ? { headers, bearerToken }
220
+ : undefined;
221
+ expect(schema.safeParse({
222
+ ...base,
223
+ safeConfig: {
224
+ url: "https://mcp.example.com",
225
+ ...firstSource.safeConfig,
226
+ ...secondSource.safeConfig,
227
+ },
228
+ ...(secrets ? { secrets } : {}),
229
+ }).success).toBe(false);
230
+ }
231
+ }
232
+ });
233
+ it("requires mutation secrets to exactly match their safe declarations", () => {
234
+ const schema = exportedSchema("createMcpConnectionSchema");
235
+ if (!schema)
236
+ return;
237
+ const base = {
238
+ name: "secret-shape",
239
+ displayName: "Secret shape",
240
+ provider: "custom",
241
+ transport: "streamable_http",
242
+ accessMode: "provider_default",
243
+ };
244
+ for (const input of [
245
+ {
246
+ safeConfig: { url: "https://mcp.example.com" },
247
+ secrets: { env: { MCP_API_KEY: "HTTP-env-secrets-are-not-accepted" } },
248
+ },
249
+ {
250
+ safeConfig: {
251
+ url: "https://mcp.example.com",
252
+ secretHeaderNames: ["X-Api-Key"],
253
+ },
254
+ },
255
+ {
256
+ safeConfig: { url: "https://mcp.example.com" },
257
+ secrets: { headers: { "X-Api-Key": "undeclared-secret" } },
258
+ },
259
+ {
260
+ safeConfig: {
261
+ url: "https://mcp.example.com",
262
+ hasBearerToken: true,
263
+ },
264
+ },
265
+ {
266
+ safeConfig: { url: "https://mcp.example.com" },
267
+ secrets: { bearerToken: "undeclared-direct-bearer" },
268
+ },
269
+ ]) {
270
+ expect(schema.safeParse({ ...base, ...input }).success).toBe(false);
271
+ }
272
+ });
273
+ it("validates persisted secret declarations without requiring plaintext again", () => {
274
+ const mergedConfigSchema = exportedSchema("mcpConnectionMergedConfigSchema");
275
+ const mutationConfigSchema = exportedSchema("mcpConnectionMutationConfigSchema");
276
+ if (!mergedConfigSchema || !mutationConfigSchema)
277
+ return;
278
+ const persistedStdio = {
279
+ provider: "custom",
280
+ transport: "stdio",
281
+ accessMode: "provider_default",
282
+ safeConfig: {
283
+ command: "node",
284
+ secretEnvNames: ["API_TOKEN"],
285
+ },
286
+ enabled: true,
287
+ };
288
+ const persistedHttp = {
289
+ provider: "custom",
290
+ transport: "streamable_http",
291
+ accessMode: "provider_default",
292
+ safeConfig: {
293
+ url: "https://mcp.example.com",
294
+ secretHeaderNames: ["X-Api-Key"],
295
+ hasBearerToken: true,
296
+ },
297
+ enabled: true,
298
+ };
299
+ expect(mergedConfigSchema.safeParse(persistedStdio).success).toBe(true);
300
+ expect(mergedConfigSchema.safeParse(persistedHttp).success).toBe(true);
301
+ expect(mutationConfigSchema.safeParse(persistedStdio).success).toBe(false);
302
+ expect(mutationConfigSchema.safeParse({
303
+ ...persistedStdio,
304
+ secrets: { env: { API_TOKEN: "rotated-secret" } },
305
+ }).success).toBe(true);
306
+ expect(mutationConfigSchema.safeParse(persistedHttp).success).toBe(false);
307
+ expect(mutationConfigSchema.safeParse({
308
+ ...persistedHttp,
309
+ secrets: {
310
+ headers: { "X-Api-Key": "rotated-header-secret" },
311
+ bearerToken: "rotated-bearer-secret",
312
+ },
313
+ }).success).toBe(true);
314
+ });
315
+ it("rejects sensitive names when their values remain in static safe config", () => {
316
+ const schema = exportedSchema("createMcpConnectionSchema");
317
+ if (!schema)
318
+ return;
319
+ expect(schema.safeParse({
320
+ name: "unsafe-stdio",
321
+ displayName: "Unsafe STDIO",
322
+ provider: "custom",
323
+ transport: "stdio",
324
+ accessMode: "provider_default",
325
+ safeConfig: {
326
+ command: "node",
327
+ staticEnv: { API_TOKEN: "must-not-remain" },
328
+ secretEnvNames: ["API_TOKEN"],
329
+ },
330
+ }).success).toBe(false);
331
+ expect(schema.safeParse({
332
+ name: "unsafe-http",
333
+ displayName: "Unsafe HTTP",
334
+ provider: "custom",
335
+ transport: "streamable_http",
336
+ accessMode: "provider_default",
337
+ safeConfig: {
338
+ url: "https://mcp.example.com",
339
+ staticHeaders: { Authorization: "Bearer raw-secret" },
340
+ },
341
+ }).success).toBe(false);
342
+ });
343
+ it("keeps public summaries free of credential references and values", () => {
344
+ const schema = exportedSchema("mcpConnectionSummarySchema");
345
+ if (!schema)
346
+ return;
347
+ const safeSummary = {
348
+ id: "11111111-1111-4111-8111-111111111111",
349
+ orgId: "22222222-2222-4222-8222-222222222222",
350
+ name: "notion-team",
351
+ displayName: "Notion Team",
352
+ provider: "notion",
353
+ transport: "streamable_http",
354
+ externalScope: "workspace-1",
355
+ accessMode: "provider_default",
356
+ status: "active",
357
+ safeConfig: {},
358
+ startupTimeoutMs: 10_000,
359
+ toolTimeoutMs: 60_000,
360
+ enabled: true,
361
+ required: false,
362
+ hasCredentials: true,
363
+ lastDiscoveredAt: null,
364
+ activatedAt: "2026-07-23T00:00:00.000Z",
365
+ disabledAt: null,
366
+ revokedAt: null,
367
+ createdAt: "2026-07-23T00:00:00.000Z",
368
+ updatedAt: "2026-07-23T00:00:00.000Z",
369
+ };
370
+ expect(schema.safeParse(safeSummary).success).toBe(true);
371
+ expect(schema.safeParse({
372
+ ...safeSummary,
373
+ credentialSecretId: "33333333-3333-4333-8333-333333333333",
374
+ }).success).toBe(false);
375
+ expect(schema.safeParse({
376
+ ...safeSummary,
377
+ accessToken: "raw-token",
378
+ }).success).toBe(false);
379
+ expect(schema.safeParse({
380
+ ...safeSummary,
381
+ secrets: { bearerToken: "raw-token" },
382
+ }).success).toBe(false);
383
+ });
384
+ it("enforces the curated provider access and configuration matrix", () => {
385
+ const createSchema = exportedSchema("createMcpConnectionSchema");
386
+ const updateSchema = exportedSchema("updateMcpConnectionSchema");
387
+ const mergedConfigSchema = exportedSchema("mcpConnectionMergedConfigSchema");
388
+ if (!createSchema || !updateSchema || !mergedConfigSchema)
389
+ return;
390
+ const supabaseBase = {
391
+ name: "supabase-team",
392
+ displayName: "Supabase Team",
393
+ provider: "supabase",
394
+ transport: "streamable_http",
395
+ safeConfig: {},
396
+ };
397
+ const supabaseDefault = createSchema.safeParse(supabaseBase);
398
+ expect(supabaseDefault.success).toBe(true);
399
+ if (supabaseDefault.success) {
400
+ expect(supabaseDefault.data.accessMode).toBe("read_only");
401
+ }
402
+ expect(createSchema.safeParse({ ...supabaseBase, accessMode: "read_write" }).success).toBe(true);
403
+ expect(createSchema.safeParse({ ...supabaseBase, accessMode: "provider_default" }).success).toBe(false);
404
+ const linearBase = {
405
+ name: "linear-team",
406
+ displayName: "Linear Team",
407
+ provider: "linear",
408
+ transport: "streamable_http",
409
+ safeConfig: {},
410
+ };
411
+ expect(createSchema.safeParse({ ...linearBase, accessMode: "read_only" }).success).toBe(true);
412
+ expect(createSchema.safeParse({ ...linearBase, accessMode: "read_write" }).success).toBe(true);
413
+ expect(createSchema.safeParse({ ...linearBase, accessMode: "provider_default" }).success).toBe(false);
414
+ const linearDefault = createSchema.safeParse(linearBase);
415
+ expect(linearDefault.success).toBe(true);
416
+ if (linearDefault.success) {
417
+ expect(linearDefault.data.accessMode).toBe("read_write");
418
+ }
419
+ const notionBase = {
420
+ name: "notion-team",
421
+ displayName: "Notion Team",
422
+ provider: "notion",
423
+ transport: "streamable_http",
424
+ safeConfig: {},
425
+ };
426
+ expect(createSchema.safeParse({ ...notionBase, accessMode: "provider_default" }).success).toBe(true);
427
+ expect(createSchema.safeParse({ ...notionBase, accessMode: "read_only" }).success).toBe(false);
428
+ expect(createSchema.safeParse({ ...notionBase, accessMode: "read_write" }).success).toBe(false);
429
+ for (const override of [
430
+ { safeConfig: { url: "https://override.example.com" } },
431
+ { safeConfig: { staticHeaders: { Accept: "application/json" } } },
432
+ { transport: "stdio", safeConfig: { command: "node" } },
433
+ { transport: "legacy_manual", enabled: false, safeConfig: { legacyConfigRetained: true } },
434
+ { secrets: { bearerToken: "curated-secret-override" } },
435
+ ]) {
436
+ expect(createSchema.safeParse({
437
+ ...supabaseBase,
438
+ accessMode: "read_only",
439
+ ...override,
440
+ }).success).toBe(false);
441
+ }
442
+ expect(updateSchema.safeParse({
443
+ accessMode: "read_write",
444
+ }).success).toBe(true);
445
+ expect(updateSchema.safeParse({
446
+ provider: "supabase",
447
+ transport: "streamable_http",
448
+ accessMode: "read_write",
449
+ }).success).toBe(false);
450
+ expect(mergedConfigSchema.safeParse({
451
+ provider: "supabase",
452
+ transport: "streamable_http",
453
+ accessMode: "read_write",
454
+ safeConfig: {},
455
+ enabled: true,
456
+ }).success).toBe(true);
457
+ expect(mergedConfigSchema.safeParse({
458
+ provider: "supabase",
459
+ transport: "streamable_http",
460
+ accessMode: "read_write",
461
+ safeConfig: {
462
+ featureGroups: {
463
+ mode: "provider_default",
464
+ excluded: ["storage"],
465
+ },
466
+ },
467
+ enabled: true,
468
+ }).success).toBe(true);
469
+ expect(mergedConfigSchema.safeParse({
470
+ provider: "supabase",
471
+ transport: "streamable_http",
472
+ accessMode: "provider_default",
473
+ safeConfig: {},
474
+ enabled: true,
475
+ }).success).toBe(false);
476
+ expect(mergedConfigSchema.safeParse({
477
+ provider: "notion",
478
+ transport: "streamable_http",
479
+ accessMode: "read_only",
480
+ safeConfig: {},
481
+ enabled: true,
482
+ }).success).toBe(false);
483
+ expect(mergedConfigSchema.safeParse({
484
+ provider: "linear",
485
+ transport: "streamable_http",
486
+ accessMode: "read_only",
487
+ safeConfig: { url: "https://override.example.com" },
488
+ enabled: true,
489
+ }).success).toBe(false);
490
+ });
491
+ it("validates OAuth start, callback, and external scope selection without credential fields", () => {
492
+ const startSchema = exportedSchema("mcpOAuthStartSchema");
493
+ const callbackSchema = exportedSchema("mcpOAuthCallbackSchema");
494
+ const selectionSchema = exportedSchema("mcpScopeSelectionSchema");
495
+ if (!startSchema || !callbackSchema || !selectionSchema)
496
+ return;
497
+ expect(startSchema.safeParse({}).success).toBe(true);
498
+ expect(startSchema.safeParse({
499
+ connectionId: "11111111-1111-4111-8111-111111111111",
500
+ }).success).toBe(false);
501
+ expect(startSchema.safeParse({
502
+ redirectUri: "https://attacker.example/oauth/callback",
503
+ }).success).toBe(false);
504
+ expect(callbackSchema.safeParse({
505
+ state: "opaque-one-time-state",
506
+ code: "provider-code",
507
+ iss: "https://oauth.example.com",
508
+ }).success).toBe(true);
509
+ expect(selectionSchema.safeParse({
510
+ connectionId: "11111111-1111-4111-8111-111111111111",
511
+ externalScope: "project-ref",
512
+ accessMode: "read_only",
513
+ }).success).toBe(true);
514
+ expect(callbackSchema.safeParse({
515
+ state: "opaque-one-time-state",
516
+ code: "provider-code",
517
+ refreshToken: "must-not-cross-the-callback-contract",
518
+ }).success).toBe(false);
519
+ expect(callbackSchema.safeParse({
520
+ state: "opaque-one-time-state",
521
+ error: "access_denied",
522
+ errorDescription: "must-not-be-reflected",
523
+ iss: "https://oauth.example.com",
524
+ }).success).toBe(true);
525
+ expect(callbackSchema.safeParse({
526
+ state: "opaque-one-time-state",
527
+ code: "provider-code",
528
+ error: "access_denied",
529
+ }).success).toBe(false);
530
+ expect(callbackSchema.safeParse({
531
+ state: "opaque-one-time-state",
532
+ }).success).toBe(false);
533
+ });
534
+ it("accepts persisted curated feature-group config without allowing it in create input", () => {
535
+ const safeConfigSchema = exportedSchema("mcpConnectionSafeConfigSchema");
536
+ const createSchema = exportedSchema("createMcpConnectionSchema");
537
+ if (!safeConfigSchema || !createSchema)
538
+ return;
539
+ const safeConfig = {
540
+ featureGroups: {
541
+ mode: "provider_default",
542
+ excluded: ["storage"],
543
+ },
544
+ };
545
+ expect(safeConfigSchema.safeParse(safeConfig).success).toBe(true);
546
+ expect(createSchema.safeParse({
547
+ name: "supabase-main",
548
+ displayName: "Supabase",
549
+ provider: "supabase",
550
+ transport: "streamable_http",
551
+ safeConfig,
552
+ }).success).toBe(false);
553
+ });
554
+ it("validates discovered tools, agent bindings, and a provider-neutral runtime binding", () => {
555
+ const toolSchema = exportedSchema("mcpDiscoveredToolSchema");
556
+ const bindingSchema = exportedSchema("mcpAgentBindingSchema");
557
+ const runtimeBindingSchema = exportedSchema("managedExternalMcpBindingSchema");
558
+ if (!toolSchema || !bindingSchema || !runtimeBindingSchema)
559
+ return;
560
+ expect(toolSchema.safeParse({
561
+ id: "11111111-1111-4111-8111-111111111111",
562
+ connectionId: "22222222-2222-4222-8222-222222222222",
563
+ externalToolName: "search",
564
+ rudderToolName: "external.team.search",
565
+ description: "Search connected content",
566
+ inputSchema: { type: "object" },
567
+ outputSchema: null,
568
+ enabled: true,
569
+ removedAt: null,
570
+ }).success).toBe(true);
571
+ expect(bindingSchema.safeParse({
572
+ id: "11111111-1111-4111-8111-111111111111",
573
+ connectionId: "22222222-2222-4222-8222-222222222222",
574
+ agentId: "33333333-3333-4333-8333-333333333333",
575
+ status: "active",
576
+ enabledToolIds: ["44444444-4444-4444-8444-444444444444"],
577
+ }).success).toBe(true);
578
+ const runtimeBinding = {
579
+ bindingId: "22222222-2222-4222-8222-222222222222",
580
+ serverName: "team-tools",
581
+ toolPolicy: {
582
+ mode: "allowlist",
583
+ allowedToolNames: ["search"],
584
+ },
585
+ required: false,
586
+ startupTimeoutMs: 10_000,
587
+ toolTimeoutMs: 60_000,
588
+ };
589
+ expect(runtimeBindingSchema.safeParse(runtimeBinding).success).toBe(true);
590
+ expect(runtimeBindingSchema.safeParse({
591
+ ...runtimeBinding,
592
+ provider: "supabase",
593
+ projectRef: "provider-specific-field",
594
+ connectionId: "22222222-2222-4222-8222-222222222222",
595
+ proxyUrl: "http://127.0.0.1:3100/api/runtime/mcp/connection",
596
+ authorizationEnvVar: "RUDDER_EXTERNAL_MCP_TOKEN_1",
597
+ credentialSecretId: "55555555-5555-4555-8555-555555555555",
598
+ accessToken: "raw-token",
599
+ }).success).toBe(false);
600
+ });
601
+ it("accepts an empty first-bind request and validates agent connection summaries", () => {
602
+ const upsertSchema = exportedSchema("upsertMcpAgentBindingSchema");
603
+ const summarySchema = exportedSchema("mcpAgentConnectionSummarySchema");
604
+ if (!upsertSchema || !summarySchema)
605
+ return;
606
+ expect(upsertSchema.safeParse({}).success).toBe(true);
607
+ expect(upsertSchema.safeParse({
608
+ status: "disabled",
609
+ enabledToolIds: ["44444444-4444-4444-8444-444444444444"],
610
+ }).success).toBe(true);
611
+ expect(upsertSchema.safeParse({
612
+ enabledToolIds: ["not-a-uuid"],
613
+ }).success).toBe(false);
614
+ expect(summarySchema.safeParse({
615
+ connection: {
616
+ id: "22222222-2222-4222-8222-222222222222",
617
+ orgId: "11111111-1111-4111-8111-111111111111",
618
+ name: "team-tools",
619
+ displayName: "Team tools",
620
+ provider: "custom",
621
+ transport: "streamable_http",
622
+ externalScope: null,
623
+ accessMode: "provider_default",
624
+ status: "active",
625
+ safeConfig: { url: "https://mcp.example.com/mcp" },
626
+ startupTimeoutMs: 10_000,
627
+ toolTimeoutMs: 60_000,
628
+ enabled: true,
629
+ required: false,
630
+ hasCredentials: false,
631
+ lastDiscoveredAt: null,
632
+ activatedAt: null,
633
+ disabledAt: null,
634
+ revokedAt: null,
635
+ createdAt: "2026-07-24T00:00:00.000Z",
636
+ updatedAt: "2026-07-24T00:00:00.000Z",
637
+ },
638
+ binding: null,
639
+ tools: [],
640
+ }).success).toBe(true);
641
+ });
642
+ });
643
+ //# sourceMappingURL=mcp.test.js.map