integrate-sdk 0.3.7 → 0.3.9

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 (46) hide show
  1. package/dist/index.js +207 -11
  2. package/dist/server.js +182 -11
  3. package/dist/src/adapters/nextjs-oauth-redirect.d.ts +34 -0
  4. package/dist/src/adapters/nextjs-oauth-redirect.d.ts.map +1 -0
  5. package/dist/src/client.d.ts +43 -5
  6. package/dist/src/client.d.ts.map +1 -1
  7. package/dist/src/config/types.d.ts +15 -0
  8. package/dist/src/config/types.d.ts.map +1 -1
  9. package/dist/src/index.d.ts +3 -1
  10. package/dist/src/index.d.ts.map +1 -1
  11. package/dist/src/oauth/manager.d.ts +4 -0
  12. package/dist/src/oauth/manager.d.ts.map +1 -1
  13. package/dist/src/oauth/types.d.ts +30 -6
  14. package/dist/src/oauth/types.d.ts.map +1 -1
  15. package/dist/src/oauth/window-manager.d.ts +1 -1
  16. package/dist/src/oauth/window-manager.d.ts.map +1 -1
  17. package/package.json +3 -15
  18. package/dist/src/adapters/nextjs-callback.d.ts +0 -44
  19. package/dist/src/adapters/nextjs-callback.d.ts.map +0 -1
  20. package/src/adapters/auto-routes.ts +0 -217
  21. package/src/adapters/base-handler.ts +0 -212
  22. package/src/adapters/nextjs-callback.tsx +0 -160
  23. package/src/adapters/nextjs.ts +0 -318
  24. package/src/adapters/tanstack-start.ts +0 -264
  25. package/src/client.ts +0 -952
  26. package/src/config/types.ts +0 -180
  27. package/src/errors.ts +0 -207
  28. package/src/index.ts +0 -110
  29. package/src/integrations/vercel-ai.ts +0 -104
  30. package/src/oauth/manager.ts +0 -307
  31. package/src/oauth/pkce.ts +0 -127
  32. package/src/oauth/types.ts +0 -101
  33. package/src/oauth/window-manager.ts +0 -322
  34. package/src/plugins/generic.ts +0 -119
  35. package/src/plugins/github-client.ts +0 -345
  36. package/src/plugins/github.ts +0 -122
  37. package/src/plugins/gmail-client.ts +0 -114
  38. package/src/plugins/gmail.ts +0 -108
  39. package/src/plugins/server-client.ts +0 -20
  40. package/src/plugins/types.ts +0 -89
  41. package/src/protocol/jsonrpc.ts +0 -88
  42. package/src/protocol/messages.ts +0 -145
  43. package/src/server.ts +0 -117
  44. package/src/transport/http-session.ts +0 -322
  45. package/src/transport/http-stream.ts +0 -331
  46. package/src/utils/naming.ts +0 -52
@@ -1,322 +0,0 @@
1
- /**
2
- * HTTP Session-Based Transport for MCP
3
- * Uses POST for request/response and optional SSE for notifications
4
- * Compatible with MCP StreamableHTTPServer with sessions
5
- */
6
-
7
- import type {
8
- JSONRPCRequest,
9
- JSONRPCResponse,
10
- JSONRPCNotification,
11
- } from "../protocol/messages.js";
12
- import { parseMessage } from "../protocol/jsonrpc.js";
13
-
14
- export type MessageHandler = (
15
- message: JSONRPCResponse | JSONRPCNotification
16
- ) => void;
17
-
18
- export interface HttpSessionTransportOptions {
19
- url: string;
20
- headers?: Record<string, string>;
21
- /** Timeout for requests in milliseconds */
22
- timeout?: number;
23
- }
24
-
25
- /**
26
- * HTTP Session Transport
27
- * Maintains a session with the MCP server
28
- * - Sends requests via POST
29
- * - Receives responses in POST response body
30
- * - Optionally listens for notifications via SSE
31
- */
32
- export class HttpSessionTransport {
33
- private url: string;
34
- private headers: Record<string, string>;
35
- private timeout: number;
36
- private messageHandlers: Set<MessageHandler> = new Set();
37
- private sessionId?: string;
38
- private sseController?: AbortController;
39
- private connected = false;
40
-
41
- constructor(options: HttpSessionTransportOptions) {
42
- this.url = options.url;
43
- this.headers = options.headers || {};
44
- this.timeout = options.timeout || 30000;
45
- }
46
-
47
- /**
48
- * Initialize session (no persistent connection needed)
49
- */
50
- async connect(): Promise<void> {
51
- if (this.connected) {
52
- return;
53
- }
54
-
55
- // Session will be established on first request
56
- this.connected = true;
57
- }
58
-
59
- /**
60
- * Send a request to the server and get immediate response
61
- */
62
- async sendRequest<T = unknown>(
63
- method: string,
64
- params?: unknown
65
- ): Promise<T> {
66
- if (!this.connected) {
67
- throw new Error("Not connected to server");
68
- }
69
-
70
- const request: JSONRPCRequest = {
71
- jsonrpc: "2.0",
72
- id: Date.now() + Math.random(),
73
- method,
74
- params: params as Record<string, unknown> | unknown[] | undefined,
75
- };
76
-
77
- const headers: Record<string, string> = {
78
- ...this.headers,
79
- "Content-Type": "application/json",
80
- };
81
-
82
- // Include session ID if we have one
83
- if (this.sessionId) {
84
- headers["mcp-session-id"] = this.sessionId;
85
- }
86
-
87
- try {
88
- const controller = new AbortController();
89
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
90
-
91
- const response = await fetch(this.url, {
92
- method: "POST",
93
- headers,
94
- body: JSON.stringify(request),
95
- signal: controller.signal,
96
- });
97
-
98
- clearTimeout(timeoutId);
99
-
100
- if (!response.ok) {
101
- // Preserve HTTP status code for auth error detection
102
- const error = new Error(`Request failed: ${response.statusText}`) as Error & { statusCode?: number };
103
- error.statusCode = response.status;
104
- throw error;
105
- }
106
-
107
- // Capture session ID from first response
108
- if (!this.sessionId) {
109
- const sid = response.headers.get("mcp-session-id");
110
- if (sid) {
111
- this.sessionId = sid;
112
- console.log("Session established:", sid);
113
-
114
- // Start SSE listener for notifications
115
- this.startSSEListener();
116
- }
117
- }
118
-
119
- const jsonResponse = (await response.json()) as JSONRPCResponse<T>;
120
-
121
- if ("error" in jsonResponse) {
122
- // Preserve the full error object for the client to parse
123
- const error = new Error(
124
- `JSON-RPC Error ${jsonResponse.error.code}: ${jsonResponse.error.message}`
125
- ) as Error & { code?: number; data?: unknown };
126
- error.code = jsonResponse.error.code;
127
- if (jsonResponse.error.data) {
128
- error.data = jsonResponse.error.data;
129
- }
130
- // Attach the original JSON-RPC error for detailed parsing
131
- (error as any).jsonrpcError = jsonResponse.error;
132
- throw error;
133
- }
134
-
135
- return jsonResponse.result as T;
136
- } catch (error) {
137
- // If it's a fetch error (network, timeout, etc.), wrap it appropriately
138
- if (error instanceof Error) {
139
- if (error.name === "AbortError") {
140
- const timeoutError = new Error("Request timeout") as Error & { code?: number };
141
- timeoutError.code = -32000; // Custom timeout code
142
- throw timeoutError;
143
- }
144
- // Re-throw errors with preserved information
145
- throw error;
146
- }
147
- throw new Error(String(error));
148
- }
149
- }
150
-
151
- /**
152
- * Start SSE listener for server-initiated notifications
153
- */
154
- private async startSSEListener(): Promise<void> {
155
- if (!this.sessionId || this.sseController) {
156
- return;
157
- }
158
-
159
- this.sseController = new AbortController();
160
-
161
- try {
162
- const response = await fetch(this.url, {
163
- method: "GET",
164
- headers: {
165
- ...this.headers,
166
- "Accept": "text/event-stream",
167
- "mcp-session-id": this.sessionId,
168
- },
169
- signal: this.sseController.signal,
170
- });
171
-
172
- if (!response.ok || !response.body) {
173
- return;
174
- }
175
-
176
- // Process SSE stream for notifications
177
- this.processSSEStream(response.body);
178
- } catch (error) {
179
- if (error instanceof Error && error.name === "AbortError") {
180
- // Connection was intentionally closed
181
- return;
182
- }
183
- console.error("SSE connection error:", error);
184
- }
185
- }
186
-
187
- /**
188
- * Process SSE stream for server notifications
189
- */
190
- private async processSSEStream(body: ReadableStream<Uint8Array>): Promise<void> {
191
- const reader = body.getReader();
192
- const decoder = new TextDecoder();
193
- let buffer = "";
194
-
195
- try {
196
- while (true) {
197
- const { done, value } = await reader.read();
198
- if (done) break;
199
-
200
- buffer += decoder.decode(value, { stream: true });
201
- const lines = buffer.split("\n");
202
- buffer = lines.pop() || "";
203
-
204
- for (const line of lines) {
205
- if (line.startsWith("data: ")) {
206
- const data = line.slice(6).trim();
207
- if (data) {
208
- this.handleNotification(data);
209
- }
210
- }
211
- }
212
- }
213
- } catch (error) {
214
- if (error instanceof Error && error.name === "AbortError") {
215
- return;
216
- }
217
- console.error("SSE stream error:", error);
218
- } finally {
219
- reader.releaseLock();
220
- }
221
- }
222
-
223
- /**
224
- * Handle incoming notification from SSE
225
- */
226
- private handleNotification(data: string): void {
227
- try {
228
- const message = parseMessage(data);
229
-
230
- // Notify all message handlers
231
- this.messageHandlers.forEach((handler) => {
232
- try {
233
- handler(message);
234
- } catch (error) {
235
- console.error("Error in message handler:", error);
236
- }
237
- });
238
- } catch (error) {
239
- console.error("Failed to parse notification:", error);
240
- }
241
- }
242
-
243
- /**
244
- * Register a message handler for notifications
245
- */
246
- onMessage(handler: MessageHandler): () => void {
247
- this.messageHandlers.add(handler);
248
-
249
- // Return unsubscribe function
250
- return () => {
251
- this.messageHandlers.delete(handler);
252
- };
253
- }
254
-
255
- /**
256
- * Disconnect from the server
257
- */
258
- async disconnect(): Promise<void> {
259
- if (!this.connected) {
260
- return;
261
- }
262
-
263
- // Close SSE connection if open
264
- if (this.sseController) {
265
- this.sseController.abort();
266
- this.sseController = undefined;
267
- }
268
-
269
- // Clear handlers
270
- this.messageHandlers.clear();
271
-
272
- this.sessionId = undefined;
273
- this.connected = false;
274
- }
275
-
276
- /**
277
- * Check if transport is connected
278
- */
279
- isConnected(): boolean {
280
- return this.connected;
281
- }
282
-
283
- /**
284
- * Get current session ID
285
- */
286
- getSessionId(): string | undefined {
287
- return this.sessionId;
288
- }
289
-
290
- /**
291
- * Set a custom header for all requests
292
- * Used for session tokens and other auth headers
293
- *
294
- * @param key - Header name
295
- * @param value - Header value
296
- *
297
- * @example
298
- * ```typescript
299
- * transport.setHeader('X-Session-Token', 'abc123');
300
- * ```
301
- */
302
- setHeader(key: string, value: string): void {
303
- this.headers[key] = value;
304
- }
305
-
306
- /**
307
- * Remove a custom header
308
- *
309
- * @param key - Header name to remove
310
- */
311
- removeHeader(key: string): void {
312
- delete this.headers[key];
313
- }
314
-
315
- /**
316
- * Get all current headers
317
- */
318
- getHeaders(): Record<string, string> {
319
- return { ...this.headers };
320
- }
321
- }
322
-
@@ -1,331 +0,0 @@
1
- /**
2
- * HTTP Streaming Transport for MCP
3
- * Implements HTTP streaming with newline-delimited JSON (NDJSON)
4
- * Compatible with MCP's StreamableHTTPServer
5
- */
6
-
7
- import type {
8
- JSONRPCRequest,
9
- JSONRPCResponse,
10
- JSONRPCNotification,
11
- } from "../protocol/messages.js";
12
- import { parseMessage } from "../protocol/jsonrpc.js";
13
-
14
- export type MessageHandler = (
15
- message: JSONRPCResponse | JSONRPCNotification
16
- ) => void;
17
-
18
- export interface HttpStreamTransportOptions {
19
- url: string;
20
- headers?: Record<string, string>;
21
- /** Timeout for requests in milliseconds */
22
- timeout?: number;
23
- /** Heartbeat interval in milliseconds (default: 30000) */
24
- heartbeatInterval?: number;
25
- }
26
-
27
- /**
28
- * HTTP Streaming Transport
29
- * Handles bidirectional communication with MCP server via HTTP streaming
30
- * Uses newline-delimited JSON (NDJSON) over a single persistent connection
31
- */
32
- export class HttpStreamTransport {
33
- private url: string;
34
- private headers: Record<string, string>;
35
- private timeout: number;
36
- private heartbeatInterval: number;
37
- private messageHandlers: Set<MessageHandler> = new Set();
38
- private pendingRequests: Map<
39
- string | number,
40
- {
41
- resolve: (value: JSONRPCResponse) => void;
42
- reject: (error: Error) => void;
43
- timeoutId?: ReturnType<typeof setTimeout>;
44
- }
45
- > = new Map();
46
- private streamController?: AbortController;
47
- private connected = false;
48
- private heartbeatTimer?: ReturnType<typeof setInterval>;
49
-
50
- constructor(options: HttpStreamTransportOptions) {
51
- this.url = options.url;
52
- this.headers = options.headers || {};
53
- this.timeout = options.timeout || 30000;
54
- this.heartbeatInterval = options.heartbeatInterval || 30000;
55
- }
56
-
57
- /**
58
- * Connect to the MCP server and establish SSE connection
59
- */
60
- async connect(): Promise<void> {
61
- if (this.connected) {
62
- return;
63
- }
64
-
65
- this.streamController = new AbortController();
66
-
67
- try {
68
- // Establish SSE connection for receiving messages
69
- const response = await fetch(this.url, {
70
- method: "GET",
71
- headers: {
72
- ...this.headers,
73
- "Accept": "text/event-stream",
74
- "Cache-Control": "no-cache",
75
- },
76
- signal: this.streamController.signal,
77
- });
78
-
79
- if (!response.ok) {
80
- throw new Error(`Failed to connect: ${response.statusText}`);
81
- }
82
-
83
- if (!response.body) {
84
- throw new Error("Response body is null");
85
- }
86
-
87
- this.connected = true;
88
-
89
- // Process incoming SSE stream
90
- this.processStream(response.body);
91
-
92
- // Start heartbeat
93
- this.startHeartbeat();
94
- } catch (error) {
95
- if (error instanceof Error && error.name === "AbortError") {
96
- // Connection was intentionally closed
97
- return;
98
- }
99
- throw error;
100
- }
101
- }
102
-
103
- /**
104
- * Process incoming SSE stream
105
- */
106
- private async processStream(body: ReadableStream<Uint8Array>): Promise<void> {
107
- const reader = body.getReader();
108
- const decoder = new TextDecoder();
109
- let buffer = "";
110
-
111
- try {
112
- while (true) {
113
- const { done, value } = await reader.read();
114
-
115
- if (done) {
116
- break;
117
- }
118
-
119
- // Decode and add to buffer
120
- buffer += decoder.decode(value, { stream: true });
121
-
122
- // Split by newlines
123
- const lines = buffer.split("\n");
124
-
125
- // Keep the last incomplete line in the buffer
126
- buffer = lines.pop() || "";
127
-
128
- // Process SSE format: "data: {json}\n\n"
129
- for (const line of lines) {
130
- if (line.startsWith("data: ")) {
131
- const data = line.slice(6).trim();
132
- if (data) {
133
- this.handleMessage(data);
134
- }
135
- } else if (line.trim() === "") {
136
- // Empty line separates events
137
- continue;
138
- } else if (line.startsWith(":")) {
139
- // SSE comment, ignore
140
- continue;
141
- }
142
- }
143
- }
144
- } catch (error) {
145
- if (error instanceof Error && error.name === "AbortError") {
146
- // Stream was intentionally closed
147
- return;
148
- }
149
- console.error("Stream error:", error);
150
- } finally {
151
- reader.releaseLock();
152
- }
153
- }
154
-
155
- /**
156
- * Start sending periodic heartbeats to keep connection alive
157
- */
158
- private startHeartbeat(): void {
159
- if (this.heartbeatTimer) {
160
- clearInterval(this.heartbeatTimer);
161
- }
162
-
163
- this.heartbeatTimer = setInterval(() => {
164
- if (this.connected) {
165
- // Send a ping/heartbeat (you can customize this based on your server's expectations)
166
- this.sendRawMessage({ jsonrpc: "2.0", method: "ping" }).catch((error) => {
167
- console.error("Heartbeat failed:", error);
168
- });
169
- }
170
- }, this.heartbeatInterval);
171
- }
172
-
173
- /**
174
- * Handle incoming message from server
175
- */
176
- private handleMessage(data: string): void {
177
- try {
178
- const message = parseMessage(data);
179
-
180
- // Check if this is a response to a pending request
181
- if ("id" in message && message.id !== undefined) {
182
- const pending = this.pendingRequests.get(message.id);
183
- if (pending) {
184
- if (pending.timeoutId) {
185
- clearTimeout(pending.timeoutId);
186
- }
187
- this.pendingRequests.delete(message.id);
188
- pending.resolve(message as JSONRPCResponse);
189
- return;
190
- }
191
- }
192
-
193
- // Otherwise, notify all message handlers (notifications, etc.)
194
- this.messageHandlers.forEach((handler) => {
195
- try {
196
- handler(message);
197
- } catch (error) {
198
- console.error("Error in message handler:", error);
199
- }
200
- });
201
- } catch (error) {
202
- console.error("Failed to parse message:", error);
203
- }
204
- }
205
-
206
- /**
207
- * Send a raw message to the server via POST (internal use)
208
- */
209
- private async sendRawMessage(message: unknown): Promise<void> {
210
- // Send each message as a separate POST request
211
- const response = await fetch(this.url, {
212
- method: "POST",
213
- headers: {
214
- ...this.headers,
215
- "Content-Type": "application/json",
216
- },
217
- body: JSON.stringify(message),
218
- });
219
-
220
- if (!response.ok) {
221
- throw new Error(`Failed to send message: ${response.statusText}`);
222
- }
223
- }
224
-
225
- /**
226
- * Send a request to the server
227
- */
228
- async sendRequest<T = unknown>(
229
- method: string,
230
- params?: unknown
231
- ): Promise<T> {
232
- if (!this.connected) {
233
- throw new Error("Not connected to server");
234
- }
235
-
236
- const request: JSONRPCRequest = {
237
- jsonrpc: "2.0",
238
- id: Date.now() + Math.random(),
239
- method,
240
- params: params as Record<string, unknown> | unknown[] | undefined,
241
- };
242
-
243
- // Create promise for response
244
- const responsePromise = new Promise<JSONRPCResponse>((resolve, reject) => {
245
- const timeoutId = setTimeout(() => {
246
- this.pendingRequests.delete(request.id);
247
- reject(new Error(`Request timeout: ${method}`));
248
- }, this.timeout);
249
-
250
- this.pendingRequests.set(request.id, {
251
- resolve,
252
- reject,
253
- timeoutId,
254
- });
255
- });
256
-
257
- // Send request over the stream
258
- try {
259
- await this.sendRawMessage(request);
260
- } catch (error) {
261
- this.pendingRequests.delete(request.id);
262
- throw error;
263
- }
264
-
265
- // Wait for response over the same stream
266
- const response = await responsePromise;
267
-
268
- if ("error" in response) {
269
- throw new Error(
270
- `JSON-RPC Error ${response.error.code}: ${response.error.message}`
271
- );
272
- }
273
-
274
- return response.result as T;
275
- }
276
-
277
- /**
278
- * Register a message handler for notifications and other messages
279
- */
280
- onMessage(handler: MessageHandler): () => void {
281
- this.messageHandlers.add(handler);
282
-
283
- // Return unsubscribe function
284
- return () => {
285
- this.messageHandlers.delete(handler);
286
- };
287
- }
288
-
289
- /**
290
- * Disconnect from the server
291
- */
292
- async disconnect(): Promise<void> {
293
- if (!this.connected) {
294
- return;
295
- }
296
-
297
- // Stop heartbeat
298
- if (this.heartbeatTimer) {
299
- clearInterval(this.heartbeatTimer);
300
- this.heartbeatTimer = undefined;
301
- }
302
-
303
- // Cancel SSE stream connection
304
- if (this.streamController) {
305
- this.streamController.abort();
306
- this.streamController = undefined;
307
- }
308
-
309
- // Reject all pending requests
310
- this.pendingRequests.forEach(({ reject, timeoutId }) => {
311
- if (timeoutId) {
312
- clearTimeout(timeoutId);
313
- }
314
- reject(new Error("Connection closed"));
315
- });
316
- this.pendingRequests.clear();
317
-
318
- // Clear handlers
319
- this.messageHandlers.clear();
320
-
321
- this.connected = false;
322
- }
323
-
324
- /**
325
- * Check if transport is connected
326
- */
327
- isConnected(): boolean {
328
- return this.connected;
329
- }
330
- }
331
-
@@ -1,52 +0,0 @@
1
- /**
2
- * Naming conversion utilities
3
- * Helpers for converting between camelCase and snake_case
4
- */
5
-
6
- /**
7
- * Convert camelCase to snake_case
8
- *
9
- * @example
10
- * camelToSnake('getRepo') // 'get_repo'
11
- * camelToSnake('listOwnRepos') // 'list_own_repos'
12
- */
13
- export function camelToSnake(str: string): string {
14
- return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
15
- }
16
-
17
- /**
18
- * Convert snake_case to camelCase
19
- *
20
- * @example
21
- * snakeToCamel('get_repo') // 'getRepo'
22
- * snakeToCamel('list_own_repos') // 'listOwnRepos'
23
- */
24
- export function snakeToCamel(str: string): string {
25
- return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
26
- }
27
-
28
- /**
29
- * Convert a method name to a full tool name with plugin prefix
30
- *
31
- * @example
32
- * methodToToolName('getRepo', 'github') // 'github_get_repo'
33
- * methodToToolName('sendEmail', 'gmail') // 'gmail_send_email'
34
- */
35
- export function methodToToolName(methodName: string, pluginId: string): string {
36
- const snakeCaseMethod = camelToSnake(methodName);
37
- return `${pluginId}_${snakeCaseMethod}`;
38
- }
39
-
40
- /**
41
- * Convert a tool name to a method name (removes plugin prefix and converts to camelCase)
42
- *
43
- * @example
44
- * toolNameToMethod('github_get_repo') // 'getRepo'
45
- * toolNameToMethod('gmail_send_email') // 'sendEmail'
46
- */
47
- export function toolNameToMethod(toolName: string): string {
48
- // Remove the plugin prefix (everything before the first underscore)
49
- const withoutPrefix = toolName.replace(/^[^_]+_/, '');
50
- return snakeToCamel(withoutPrefix);
51
- }
52
-