@superblocksteam/sdk-api 0.0.13 → 0.0.15-mcp.1

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 (78) hide show
  1. package/README.md +36 -610
  2. package/dist/errors.d.ts.map +1 -1
  3. package/dist/errors.js +2 -1
  4. package/dist/errors.js.map +1 -1
  5. package/dist/index.d.ts +3 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +2 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/integrations/base/decode-worker-binary-response.d.ts.map +1 -1
  10. package/dist/integrations/base/decode-worker-binary-response.js +22 -1
  11. package/dist/integrations/base/decode-worker-binary-response.js.map +1 -1
  12. package/dist/integrations/base/decode-worker-binary-response.test.js +15 -2
  13. package/dist/integrations/base/decode-worker-binary-response.test.js.map +1 -1
  14. package/dist/integrations/base/index.d.ts +1 -1
  15. package/dist/integrations/base/index.d.ts.map +1 -1
  16. package/dist/integrations/base/request-body.d.ts +1 -0
  17. package/dist/integrations/base/request-body.d.ts.map +1 -1
  18. package/dist/integrations/base/request-body.js +4 -4
  19. package/dist/integrations/base/request-body.js.map +1 -1
  20. package/dist/integrations/base/rest-api-integration-client.d.ts +2 -2
  21. package/dist/integrations/base/rest-api-integration-client.d.ts.map +1 -1
  22. package/dist/integrations/base/rest-api-integration-client.js +3 -1
  23. package/dist/integrations/base/rest-api-integration-client.js.map +1 -1
  24. package/dist/integrations/base/types.d.ts +23 -6
  25. package/dist/integrations/base/types.d.ts.map +1 -1
  26. package/dist/integrations/declarations.d.ts +10 -0
  27. package/dist/integrations/declarations.d.ts.map +1 -1
  28. package/dist/integrations/declarations.js +9 -0
  29. package/dist/integrations/declarations.js.map +1 -1
  30. package/dist/integrations/index.d.ts +3 -1
  31. package/dist/integrations/index.d.ts.map +1 -1
  32. package/dist/integrations/index.js +2 -1
  33. package/dist/integrations/index.js.map +1 -1
  34. package/dist/integrations/mcpintegration/client.d.ts +71 -0
  35. package/dist/integrations/mcpintegration/client.d.ts.map +1 -0
  36. package/dist/integrations/mcpintegration/client.js +349 -0
  37. package/dist/integrations/mcpintegration/client.js.map +1 -0
  38. package/dist/integrations/mcpintegration/client.test.d.ts +17 -0
  39. package/dist/integrations/mcpintegration/client.test.d.ts.map +1 -0
  40. package/dist/integrations/mcpintegration/client.test.js +401 -0
  41. package/dist/integrations/mcpintegration/client.test.js.map +1 -0
  42. package/dist/integrations/mcpintegration/index.d.ts +8 -0
  43. package/dist/integrations/mcpintegration/index.d.ts.map +1 -0
  44. package/dist/integrations/mcpintegration/index.js +7 -0
  45. package/dist/integrations/mcpintegration/index.js.map +1 -0
  46. package/dist/integrations/mcpintegration/types.d.ts +164 -0
  47. package/dist/integrations/mcpintegration/types.d.ts.map +1 -0
  48. package/dist/integrations/mcpintegration/types.js +9 -0
  49. package/dist/integrations/mcpintegration/types.js.map +1 -0
  50. package/dist/integrations/registry.d.ts +1 -0
  51. package/dist/integrations/registry.d.ts.map +1 -1
  52. package/dist/integrations/registry.js +5 -0
  53. package/dist/integrations/registry.js.map +1 -1
  54. package/dist/integrations/restapiintegration/client.test.js +42 -32
  55. package/dist/integrations/restapiintegration/client.test.js.map +1 -1
  56. package/package.json +2 -2
  57. package/src/errors.ts +4 -1
  58. package/src/index.ts +12 -0
  59. package/src/integrations/base/decode-worker-binary-response.test.ts +25 -5
  60. package/src/integrations/base/decode-worker-binary-response.ts +26 -1
  61. package/src/integrations/base/index.ts +1 -0
  62. package/src/integrations/base/request-body.ts +7 -4
  63. package/src/integrations/base/rest-api-integration-client.ts +6 -3
  64. package/src/integrations/base/types.ts +25 -6
  65. package/src/integrations/declarations.ts +16 -0
  66. package/src/integrations/index.ts +14 -0
  67. package/src/integrations/lakebase/README.md +1 -3
  68. package/src/integrations/mcpintegration/README.md +147 -0
  69. package/src/integrations/mcpintegration/client.test.ts +550 -0
  70. package/src/integrations/mcpintegration/client.ts +540 -0
  71. package/src/integrations/mcpintegration/docs.manifest.json +5 -0
  72. package/src/integrations/mcpintegration/index.ts +16 -0
  73. package/src/integrations/mcpintegration/types.ts +185 -0
  74. package/src/integrations/postgres/README.md +1 -3
  75. package/src/integrations/registry.ts +6 -0
  76. package/src/integrations/restapiintegration/client.test.ts +51 -38
  77. package/src/integrations/restapiintegration/overlays/response-types-binary.md +4 -8
  78. package/src/integrations/snowflakepostgres/README.md +1 -3
@@ -0,0 +1,185 @@
1
+ /**
2
+ * MCP Server client types.
3
+ *
4
+ * An MCP Server integration points at a remote Model Context Protocol server.
5
+ * The client exposes the tools surface of that server: list the tools it
6
+ * advertises and call one of them. Resources and prompts are not exposed yet.
7
+ */
8
+
9
+ import type { z } from "zod";
10
+
11
+ import type { BaseIntegrationClient } from "../../types.js";
12
+
13
+ /**
14
+ * One block of a tool result's `content` array.
15
+ *
16
+ * MCP defines text, image, audio, resource link and embedded resource blocks.
17
+ * Only `type` is guaranteed; the remaining fields depend on the block type.
18
+ */
19
+ export interface McpContentBlock {
20
+ type: string;
21
+ /** Present on `text` blocks. */
22
+ text?: string;
23
+ [key: string]: unknown;
24
+ }
25
+
26
+ /**
27
+ * The result of a `tools/call` request, as returned by the MCP server.
28
+ */
29
+ export interface CallToolResult {
30
+ /** Unstructured result blocks. */
31
+ content: McpContentBlock[];
32
+ /** Structured result, present when the tool declares an output schema. */
33
+ structuredContent?: Record<string, unknown>;
34
+ /** True when the tool itself failed; `content` then carries the error text. */
35
+ isError?: boolean;
36
+ _meta?: Record<string, unknown>;
37
+ }
38
+
39
+ /**
40
+ * Behavioral hints a server attaches to a tool. All hints are advisory.
41
+ */
42
+ export interface ToolAnnotations {
43
+ title?: string;
44
+ readOnlyHint?: boolean;
45
+ destructiveHint?: boolean;
46
+ idempotentHint?: boolean;
47
+ openWorldHint?: boolean;
48
+ [key: string]: unknown;
49
+ }
50
+
51
+ /**
52
+ * One tool advertised by the MCP server through `tools/list`.
53
+ */
54
+ export interface Tool {
55
+ name: string;
56
+ title?: string;
57
+ description?: string;
58
+ /** JSON Schema describing the tool's `arguments` object. */
59
+ inputSchema: {
60
+ type: "object";
61
+ properties?: Record<string, unknown>;
62
+ required?: string[];
63
+ [key: string]: unknown;
64
+ };
65
+ /** JSON Schema describing `structuredContent`, when the tool declares one. */
66
+ outputSchema?: Record<string, unknown>;
67
+ annotations?: ToolAnnotations;
68
+ _meta?: Record<string, unknown>;
69
+ }
70
+
71
+ /**
72
+ * Options shared by every MCP request.
73
+ *
74
+ * **Security:** `label` is trace metadata and may be stored in logs; do not put
75
+ * secrets in it. Headers are sent to the MCP server as-is.
76
+ */
77
+ export interface McpRequestOptions {
78
+ /** Short human-readable label shown in the Trace View for this call. */
79
+ label?: string;
80
+ /**
81
+ * Extra HTTP headers for this request, on top of the headers configured on
82
+ * the integration. Use this for per-call values such as `Mcp-Param-*`.
83
+ */
84
+ headers?: Record<string, string>;
85
+ }
86
+
87
+ /**
88
+ * Options for {@link McpClient.callTool}. Always the third argument: a schema
89
+ * placed inside `args` is rejected with a `TypeError` instead of being sent to
90
+ * the server as tool arguments.
91
+ */
92
+ export interface McpCallToolOptions<T = unknown> extends McpRequestOptions {
93
+ /**
94
+ * Zod schema for the tool result. When given, the call returns the validated
95
+ * value instead of the `CallToolResult`. The value validated is
96
+ * `structuredContent` when the server returns an object; otherwise the first
97
+ * `text` block. Text that is JSON is validated as the parsed value first and
98
+ * as the raw string second, so a string schema still accepts text such as
99
+ * "12345". Text that is not JSON is validated as a string.
100
+ */
101
+ schema?: z.ZodSchema<T>;
102
+ }
103
+
104
+ /**
105
+ * MCP Server client for listing and calling tools.
106
+ *
107
+ * When the server needs an MCP session (handshake-era servers), the plugin
108
+ * opens one on the first call and the client sends it back on every later
109
+ * call of the same API run, so the handshake runs once per client instance.
110
+ *
111
+ * @example
112
+ * ```typescript
113
+ * const mcp = ctx.integrations.github;
114
+ *
115
+ * // Discover the server's tools
116
+ * const tools = await mcp.listTools();
117
+ *
118
+ * // Call a tool and get the raw CallToolResult
119
+ * const raw = await mcp.callTool("search_issues", { query: "is:open" });
120
+ *
121
+ * // Call a tool and validate the result
122
+ * const issues = await mcp.callTool(
123
+ * "search_issues",
124
+ * { query: "is:open" },
125
+ * { schema: z.object({ items: z.array(IssueSchema) }) },
126
+ * );
127
+ * ```
128
+ */
129
+ export interface McpClient extends BaseIntegrationClient {
130
+ /**
131
+ * Call a tool and validate its result against `schema`.
132
+ *
133
+ * @param name - Tool name as advertised by `tools/list`
134
+ * @param args - Tool arguments; sent as `{}` when omitted
135
+ * @param options - Request options with a required `schema`
136
+ * @returns The validated result
137
+ * @throws IntegrationError when the server reports `isError: true`
138
+ * @throws RestApiValidationError when the result does not match `schema`
139
+ */
140
+ callTool<T>(
141
+ name: string,
142
+ args: Record<string, unknown> | undefined,
143
+ options: McpCallToolOptions<T> & { schema: z.ZodSchema<T> },
144
+ ): Promise<T>;
145
+
146
+ /**
147
+ * Call a tool and return the server's `CallToolResult`, with the plugin's
148
+ * session bookkeeping fields removed and a `null` `structuredContent`
149
+ * normalized to absent.
150
+ *
151
+ * @param name - Tool name as advertised by `tools/list`
152
+ * @param args - Tool arguments; sent as `{}` when omitted
153
+ * @param options - Request options
154
+ * @returns The `CallToolResult`
155
+ * @throws IntegrationError when the server reports `isError: true`; the
156
+ * error's `details.cause` carries the tool name, the text and the result
157
+ */
158
+ callTool(
159
+ name: string,
160
+ args?: Record<string, unknown>,
161
+ options?: McpRequestOptions,
162
+ ): Promise<CallToolResult>;
163
+
164
+ /**
165
+ * Call a tool. Returns the validated value when `options.schema` is set,
166
+ * otherwise the `CallToolResult`.
167
+ */
168
+ callTool<T>(
169
+ name: string,
170
+ args?: Record<string, unknown>,
171
+ options?: McpCallToolOptions<T>,
172
+ ): Promise<CallToolResult | T>;
173
+
174
+ /**
175
+ * List every tool the server advertises.
176
+ *
177
+ * Follows `nextCursor` until the server stops paginating, so the returned
178
+ * array is the complete catalog. Gives up with an `IntegrationError` after
179
+ * 50 pages or when a cursor repeats.
180
+ *
181
+ * @param options - Request options
182
+ * @returns All tools, in server order
183
+ */
184
+ listTools(options?: McpRequestOptions): Promise<Tool[]>;
185
+ }
@@ -292,9 +292,7 @@ The `query()` method requires a Zod schema for runtime validation. This is by de
292
292
  // WRONG - Missing schema parameter
293
293
  const users = await ctx.integrations.db.query(
294
294
  "SELECT * FROM users",
295
- [
296
- /* params */
297
- ], // This is wrong - params are 3rd argument
295
+ [/* params */], // This is wrong - params are 3rd argument
298
296
  );
299
297
 
300
298
  // CORRECT - Schema is the second parameter
@@ -28,6 +28,7 @@ import { HubSpotClientImpl } from "./hubspot/index.js";
28
28
  import { IntercomClientImpl } from "./intercom/index.js";
29
29
  import { JiraClientImpl } from "./jira/index.js";
30
30
  import { LaunchDarklyClientImpl } from "./launchdarkly/index.js";
31
+ import { McpClientImpl } from "./mcpintegration/index.js";
31
32
  import { MistralClientImpl } from "./mistral/index.js";
32
33
  import { NotionClientImpl } from "./notion/index.js";
33
34
  import { OpenAIClientImpl } from "./openai_v2/index.js";
@@ -137,6 +138,8 @@ export const SUPPORTED_PLUGINS = {
137
138
  snowflakepostgres: "snowflakepostgres",
138
139
  // Email
139
140
  smtp: "smtp",
141
+ // MCP servers
142
+ mcpintegration: "mcpintegration",
140
143
  // REST API plugins
141
144
  restapiintegration: "restapiintegration",
142
145
  } as const;
@@ -406,6 +409,9 @@ export function createClient(
406
409
  case SUPPORTED_PLUGINS.smtp:
407
410
  return new SmtpClientImpl(config, executeQuery);
408
411
 
412
+ case SUPPORTED_PLUGINS.mcpintegration:
413
+ return new McpClientImpl(config, executeQuery);
414
+
409
415
  case SUPPORTED_PLUGINS.restapiintegration:
410
416
  return new RestApiIntegrationPluginClientImpl(config, executeQuery);
411
417
 
@@ -227,50 +227,55 @@ describe("RestApiIntegrationPluginClientImpl", () => {
227
227
  expect(executeQuery).toHaveBeenCalled();
228
228
  });
229
229
 
230
- it("accepts response schemas that transform bytes to bytes", async () => {
230
+ it("returns decoded bytes and ignores a response schema passed at runtime", async () => {
231
+ // Models sandboxed API code: its Uint8Array is a different constructor,
232
+ // so a caller's z.instanceof(Uint8Array) rejects every SDK-decoded body.
233
+ const crossRealmInstanceof = z.custom<Uint8Array>(() => false);
231
234
  const { client } = createClient(workerBufferPayload(PDF_BYTES));
232
235
 
233
- const result = await client.apiRequest(
236
+ const result = await callApiRequest(
237
+ client,
234
238
  { method: "GET", path: "/file.pdf", responseType: "binary" },
235
- {
236
- response: z
237
- .instanceof(Uint8Array)
238
- .refine((bytes) => bytes[0] === 0x25)
239
- .transform((bytes) => bytes.slice(1)),
240
- },
239
+ { response: crossRealmInstanceof },
241
240
  );
242
241
 
243
- expect(Array.from(result)).toEqual(PDF_BYTES.slice(1));
242
+ expect(result).toBeInstanceOf(Uint8Array);
243
+ expect(result instanceof Uint8Array && Array.from(result)).toEqual(
244
+ PDF_BYTES,
245
+ );
244
246
  });
245
247
 
246
- it("throws RestApiValidationError when the schema rejects normalized bytes", async () => {
247
- const { client } = createClient(workerBufferPayload(PDF_BYTES));
248
+ it("still validates the request body for binary responses", async () => {
249
+ const { client, executeQuery } = createClient(
250
+ workerBufferPayload(PDF_BYTES),
251
+ );
252
+
253
+ const dynamicBody: unknown = JSON.parse('{"pages":"many"}');
248
254
 
249
255
  await expect(
250
256
  client.apiRequest(
251
- { method: "GET", path: "/file.pdf", responseType: "binary" },
252
257
  {
253
- response: z
254
- .instanceof(Uint8Array)
255
- .refine((bytes) => bytes.byteLength > 64),
258
+ method: "POST",
259
+ path: "/render.pdf",
260
+ body: dynamicBody,
261
+ responseType: "binary",
256
262
  },
263
+ { body: z.object({ pages: z.number() }) },
257
264
  ),
258
265
  ).rejects.toThrow(RestApiValidationError);
266
+ expect(executeQuery).not.toHaveBeenCalled();
259
267
  });
260
268
 
261
- it("redacts binary data from response validation errors", async () => {
262
- const bytes = Array.from({ length: 4096 }, () => 0);
269
+ it("redacts binary data from malformed-body errors", async () => {
270
+ const bytes = [...Array.from({ length: 4096 }, () => 0), 256];
263
271
  const { client } = createClient(workerBufferPayload(bytes));
264
272
 
265
273
  try {
266
- await client.apiRequest(
267
- { method: "GET", path: "/file.pdf", responseType: "binary" },
268
- {
269
- response: z
270
- .instanceof(Uint8Array)
271
- .refine((value) => value.byteLength < 1024),
272
- },
273
- );
274
+ await client.apiRequest({
275
+ method: "GET",
276
+ path: "/file.pdf",
277
+ responseType: "binary",
278
+ });
274
279
  } catch (error) {
275
280
  expect(error).toBeInstanceOf(RestApiValidationError);
276
281
  if (!(error instanceof RestApiValidationError)) {
@@ -282,20 +287,20 @@ describe("RestApiIntegrationPluginClientImpl", () => {
282
287
  });
283
288
  return;
284
289
  }
285
- throw new Error("Expected binary response validation to fail");
290
+ throw new Error("Expected malformed binary decoding to fail");
286
291
  });
287
292
 
288
293
  it("keeps binary bytes out of the thrown message, not just details.zodError", async () => {
289
- const bytes = [137, 80, 78, 71];
294
+ const bytes = [137, 80, 78, 71, 256];
290
295
  const { client } = createClient(workerBufferPayload(bytes));
291
296
 
292
297
  let caught: unknown;
293
298
  try {
294
- await callApiRequest(
295
- client,
296
- { method: "GET", path: "/file.png", responseType: "binary" },
297
- { response: z.literal("unexpected") },
298
- );
299
+ await client.apiRequest({
300
+ method: "GET",
301
+ path: "/file.png",
302
+ responseType: "binary",
303
+ });
299
304
  } catch (error) {
300
305
  caught = error;
301
306
  }
@@ -461,24 +466,32 @@ describe("RestApiIntegrationPluginClientImpl", () => {
461
466
  await wrongBinary;
462
467
  });
463
468
 
464
- it("rejects binary response schemas that transform bytes to another type", () => {
469
+ it("rejects response schemas on binary requests but accepts body schemas", async () => {
465
470
  const { client: binaryClient } = createClient(
466
471
  workerBufferPayload(PDF_BYTES),
467
472
  );
468
473
 
469
- // @ts-expect-error binary response schemas must output Uint8Array
470
- binaryClient.apiRequest(
474
+ // @ts-expect-error binary responses do not take a response schema
475
+ const withResponse = binaryClient.apiRequest(
471
476
  {
472
477
  method: "GET",
473
478
  path: "/file.pdf",
474
479
  responseType: "binary",
475
480
  },
481
+ { response: z.instanceof(Uint8Array) },
482
+ );
483
+ await withResponse;
484
+
485
+ const withBody: Promise<Uint8Array> = binaryClient.apiRequest(
476
486
  {
477
- response: z
478
- .instanceof(Uint8Array)
479
- .transform((bytes) => bytes.byteLength),
487
+ method: "POST",
488
+ path: "/render.pdf",
489
+ body: { pages: 1 },
490
+ responseType: "binary",
480
491
  },
492
+ { body: z.object({ pages: z.number() }) },
481
493
  );
494
+ expect(Array.from(await withBody)).toEqual(PDF_BYTES);
482
495
  });
483
496
 
484
497
  it("rejects response types outside the public SDK contract", async () => {
@@ -5,7 +5,7 @@
5
5
  - `"binary"` for PDFs and other byte payloads. The result is a `Uint8Array`
6
6
  - `"text"` for XML, CSV, HTML, and other decoded strings
7
7
 
8
- With `responseType: "text"` or `responseType: "binary"` the response schema may be omitted. Text returns the raw string typed `unknown`. Binary returns a `Uint8Array`:
8
+ With `responseType: "text"` the response schema may be omitted, and the raw string is returned typed `unknown`. With `responseType: "binary"` never pass a response schema. The SDK decodes and validates the bytes and returns a `Uint8Array`:
9
9
 
10
10
  ```typescript
11
11
  const xml = await ctx.integrations.legacyApi.apiRequest({
@@ -23,7 +23,7 @@ const pdf = await ctx.integrations.legacyApi.apiRequest({
23
23
  // pdf is a Uint8Array
24
24
  ```
25
25
 
26
- To validate after decoding, pass a schema that matches the decoded value:
26
+ To validate a text response after decoding, pass a schema that matches the decoded string:
27
27
 
28
28
  ```typescript
29
29
  const xml = await ctx.integrations.legacyApi.apiRequest(
@@ -31,14 +31,10 @@ const xml = await ctx.integrations.legacyApi.apiRequest(
31
31
  { response: z.string() },
32
32
  );
33
33
  // xml is typed string
34
-
35
- const pdf = await ctx.integrations.legacyApi.apiRequest(
36
- { method: "GET", path: "/file.pdf", responseType: "binary" },
37
- { response: z.instanceof(Uint8Array) },
38
- );
39
- // pdf is typed Uint8Array
40
34
  ```
41
35
 
36
+ Do not use `z.instanceof(Uint8Array)` anywhere in an API, including the response or output schema. API code runs in a sandbox where the bytes the SDK returns are not instances of the API code's `Uint8Array`, so that check always fails. Check binary content directly in `run()` instead, for example `if (pdf.byteLength === 0) throw new Error("Empty PDF")`.
37
+
42
38
  Convert binary data to a JSON-safe representation before returning it from an SDK API:
43
39
 
44
40
  ```typescript
@@ -137,9 +137,7 @@ The `query()` method requires a Zod schema for runtime validation:
137
137
  // WRONG - Missing schema parameter
138
138
  const users = await ctx.integrations.db.query(
139
139
  "SELECT * FROM users",
140
- [
141
- /* params */
142
- ], // This is wrong - params are 3rd argument
140
+ [/* params */], // This is wrong - params are 3rd argument
143
141
  );
144
142
 
145
143
  // CORRECT - Schema is the second parameter