integrate-sdk 0.3.3 → 0.3.7

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 (40) hide show
  1. package/dist/index.js +42 -15
  2. package/dist/server.js +42 -15
  3. package/dist/src/adapters/nextjs-callback.d.ts +44 -0
  4. package/dist/src/adapters/nextjs-callback.d.ts.map +1 -0
  5. package/dist/src/adapters/nextjs.d.ts +6 -2
  6. package/dist/src/adapters/nextjs.d.ts.map +1 -1
  7. package/dist/src/index.d.ts +1 -1
  8. package/dist/src/index.d.ts.map +1 -1
  9. package/dist/src/oauth/types.d.ts +9 -0
  10. package/dist/src/oauth/types.d.ts.map +1 -1
  11. package/dist/src/oauth/window-manager.d.ts +2 -1
  12. package/dist/src/oauth/window-manager.d.ts.map +1 -1
  13. package/package.json +15 -3
  14. package/src/adapters/auto-routes.ts +217 -0
  15. package/src/adapters/base-handler.ts +212 -0
  16. package/src/adapters/nextjs-callback.tsx +160 -0
  17. package/src/adapters/nextjs.ts +318 -0
  18. package/src/adapters/tanstack-start.ts +264 -0
  19. package/src/client.ts +952 -0
  20. package/src/config/types.ts +180 -0
  21. package/src/errors.ts +207 -0
  22. package/src/index.ts +110 -0
  23. package/src/integrations/vercel-ai.ts +104 -0
  24. package/src/oauth/manager.ts +307 -0
  25. package/src/oauth/pkce.ts +127 -0
  26. package/src/oauth/types.ts +101 -0
  27. package/src/oauth/window-manager.ts +322 -0
  28. package/src/plugins/generic.ts +119 -0
  29. package/src/plugins/github-client.ts +345 -0
  30. package/src/plugins/github.ts +122 -0
  31. package/src/plugins/gmail-client.ts +114 -0
  32. package/src/plugins/gmail.ts +108 -0
  33. package/src/plugins/server-client.ts +20 -0
  34. package/src/plugins/types.ts +89 -0
  35. package/src/protocol/jsonrpc.ts +88 -0
  36. package/src/protocol/messages.ts +145 -0
  37. package/src/server.ts +117 -0
  38. package/src/transport/http-session.ts +322 -0
  39. package/src/transport/http-stream.ts +331 -0
  40. package/src/utils/naming.ts +52 -0
@@ -0,0 +1,322 @@
1
+ /**
2
+ * OAuth Window Manager
3
+ * Handles OAuth authorization UI (popup windows and redirects)
4
+ */
5
+
6
+ import type { PopupOptions, OAuthCallbackParams } from "./types.js";
7
+
8
+ /**
9
+ * Check if we're in a browser environment
10
+ */
11
+ function isBrowser(): boolean {
12
+ return typeof window !== 'undefined' && typeof window.document !== 'undefined';
13
+ }
14
+
15
+ /**
16
+ * OAuth Window Manager
17
+ * Manages popup windows and redirect flows for OAuth authorization
18
+ *
19
+ * Note: This class should only be used in browser environments.
20
+ * Server-side usage will throw errors.
21
+ */
22
+ export class OAuthWindowManager {
23
+ private popupWindow: Window | null = null;
24
+ private popupCheckInterval: ReturnType<typeof setInterval> | null = null;
25
+ private popupCheckTimeout: ReturnType<typeof setTimeout> | null = null;
26
+
27
+ /**
28
+ * Open OAuth authorization in a popup window
29
+ *
30
+ * @param url - The authorization URL to open
31
+ * @param options - Popup window dimensions
32
+ * @returns The opened popup window or null if blocked
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * const manager = new OAuthWindowManager();
37
+ * const popup = manager.openPopup(authUrl, { width: 600, height: 700 });
38
+ * ```
39
+ */
40
+ openPopup(url: string, options?: PopupOptions): Window | null {
41
+ if (!isBrowser()) {
42
+ throw new Error('OAuthWindowManager.openPopup() can only be used in browser environments');
43
+ }
44
+
45
+ const width = options?.width || 600;
46
+ const height = options?.height || 700;
47
+
48
+ // Calculate center position
49
+ const left = window.screenX + (window.outerWidth - width) / 2;
50
+ const top = window.screenY + (window.outerHeight - height) / 2;
51
+
52
+ const features = [
53
+ `width=${width}`,
54
+ `height=${height}`,
55
+ `left=${left}`,
56
+ `top=${top}`,
57
+ 'toolbar=no',
58
+ 'location=no',
59
+ 'directories=no',
60
+ 'status=no',
61
+ 'menubar=no',
62
+ 'scrollbars=yes',
63
+ 'resizable=yes',
64
+ 'copyhistory=no',
65
+ ].join(',');
66
+
67
+ this.popupWindow = window.open(url, 'oauth_popup', features);
68
+
69
+ if (!this.popupWindow) {
70
+ console.warn('Popup was blocked by the browser. Please allow popups for this site.');
71
+ return null;
72
+ }
73
+
74
+ // Focus the popup
75
+ this.popupWindow.focus();
76
+
77
+ return this.popupWindow;
78
+ }
79
+
80
+ /**
81
+ * Redirect current window to OAuth authorization URL
82
+ *
83
+ * @param url - The authorization URL to redirect to
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * const manager = new OAuthWindowManager();
88
+ * manager.openRedirect(authUrl);
89
+ * ```
90
+ */
91
+ openRedirect(url: string): void {
92
+ if (!isBrowser()) {
93
+ throw new Error('OAuthWindowManager.openRedirect() can only be used in browser environments');
94
+ }
95
+
96
+ window.location.href = url;
97
+ }
98
+
99
+ /**
100
+ * Listen for OAuth callback
101
+ * For popup: listens for postMessage from callback page
102
+ * For redirect: parses URL parameters after redirect back
103
+ *
104
+ * @param mode - The OAuth flow mode ('popup' or 'redirect')
105
+ * @param timeoutMs - Timeout in milliseconds (default: 5 minutes)
106
+ * @returns Promise resolving to callback parameters (code and state)
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * // For popup flow
111
+ * const params = await manager.listenForCallback('popup');
112
+ *
113
+ * // For redirect flow (after redirect back)
114
+ * const params = await manager.listenForCallback('redirect');
115
+ * ```
116
+ */
117
+ listenForCallback(
118
+ mode: 'popup' | 'redirect',
119
+ timeoutMs: number = 5 * 60 * 1000
120
+ ): Promise<OAuthCallbackParams> {
121
+ if (mode === 'popup') {
122
+ return this.listenForPopupCallback(timeoutMs);
123
+ } else {
124
+ return this.listenForRedirectCallback();
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Listen for callback from popup window via postMessage
130
+ */
131
+ private listenForPopupCallback(timeoutMs: number): Promise<OAuthCallbackParams> {
132
+ if (!isBrowser()) {
133
+ return Promise.reject(new Error('OAuth popup callback can only be used in browser environments'));
134
+ }
135
+
136
+ return new Promise((resolve, reject) => {
137
+ // Set timeout
138
+ const timeout = setTimeout(() => {
139
+ this.cleanup();
140
+ reject(new Error('OAuth authorization timed out'));
141
+ }, timeoutMs);
142
+
143
+ // Listen for postMessage from popup
144
+ const messageHandler = (event: MessageEvent) => {
145
+ // Validate origin (you may want to make this configurable)
146
+ // For now, we accept any origin since the callback page is on the user's domain
147
+
148
+ if (event.data && event.data.type === 'oauth_callback') {
149
+ clearTimeout(timeout);
150
+
151
+ // Clear the grace period timeout if message arrives early
152
+ if (this.popupCheckTimeout) {
153
+ clearTimeout(this.popupCheckTimeout);
154
+ this.popupCheckTimeout = null;
155
+ }
156
+
157
+ window.removeEventListener('message', messageHandler);
158
+
159
+ const { code, state, error } = event.data;
160
+
161
+ if (error) {
162
+ this.cleanup();
163
+ reject(new Error(`OAuth error: ${error}`));
164
+ return;
165
+ }
166
+
167
+ if (!code || !state) {
168
+ this.cleanup();
169
+ reject(new Error('Invalid OAuth callback: missing code or state'));
170
+ return;
171
+ }
172
+
173
+ this.cleanup();
174
+ resolve({ code, state });
175
+ }
176
+ };
177
+
178
+ window.addEventListener('message', messageHandler);
179
+
180
+ // Check if popup was closed
181
+ // Add a grace period (2s) before checking to avoid false positives during OAuth redirect/navigation
182
+ this.popupCheckTimeout = setTimeout(() => {
183
+ this.popupCheckTimeout = null; // Clear the timeout reference after it fires
184
+ this.popupCheckInterval = setInterval(() => {
185
+ if (this.popupWindow?.closed) {
186
+ clearTimeout(timeout);
187
+ clearInterval(this.popupCheckInterval!);
188
+ window.removeEventListener('message', messageHandler);
189
+ this.cleanup();
190
+ reject(new Error('OAuth popup was closed by user'));
191
+ }
192
+ }, 500);
193
+ }, 2000);
194
+ });
195
+ }
196
+
197
+ /**
198
+ * Parse callback parameters from current URL or sessionStorage (for redirect flow)
199
+ */
200
+ private listenForRedirectCallback(): Promise<OAuthCallbackParams> {
201
+ if (!isBrowser()) {
202
+ return Promise.reject(new Error('OAuth redirect callback can only be used in browser environments'));
203
+ }
204
+
205
+ return new Promise((resolve, reject) => {
206
+ // First, try to get params from URL (legacy/direct callback)
207
+ const params = new URLSearchParams(window.location.search);
208
+
209
+ let code = params.get('code');
210
+ let state = params.get('state');
211
+ let error = params.get('error');
212
+ let errorDescription = params.get('error_description');
213
+
214
+ // If not in URL, check sessionStorage (from callback handler)
215
+ if (!code && !error) {
216
+ try {
217
+ const stored = sessionStorage.getItem('oauth_callback_params');
218
+ if (stored) {
219
+ const parsed = JSON.parse(stored);
220
+ code = parsed.code;
221
+ state = parsed.state;
222
+ // Clear after reading
223
+ sessionStorage.removeItem('oauth_callback_params');
224
+ }
225
+ } catch (e) {
226
+ console.error('Failed to parse OAuth callback params from sessionStorage:', e);
227
+ }
228
+ }
229
+
230
+ if (error) {
231
+ const errorMsg = errorDescription || error;
232
+ reject(new Error(`OAuth error: ${errorMsg}`));
233
+ return;
234
+ }
235
+
236
+ if (!code || !state) {
237
+ reject(new Error('Invalid OAuth callback: missing code or state in URL'));
238
+ return;
239
+ }
240
+
241
+ resolve({ code, state });
242
+ });
243
+ }
244
+
245
+ /**
246
+ * Clean up popup window and intervals
247
+ */
248
+ private cleanup(): void {
249
+ if (this.popupWindow && !this.popupWindow.closed) {
250
+ this.popupWindow.close();
251
+ }
252
+ this.popupWindow = null;
253
+
254
+ if (this.popupCheckInterval) {
255
+ clearInterval(this.popupCheckInterval);
256
+ this.popupCheckInterval = null;
257
+ }
258
+
259
+ if (this.popupCheckTimeout) {
260
+ clearTimeout(this.popupCheckTimeout);
261
+ this.popupCheckTimeout = null;
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Close any open popup windows
267
+ * Call this when aborting the OAuth flow
268
+ */
269
+ close(): void {
270
+ this.cleanup();
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Helper function to send callback data from callback page to opener window
276
+ * Call this in your OAuth callback page
277
+ *
278
+ * @param params - The callback parameters from URL
279
+ *
280
+ * @example
281
+ * ```typescript
282
+ * // In your callback page (e.g., /oauth/callback.html)
283
+ * import { sendCallbackToOpener } from '@integrate/sdk';
284
+ *
285
+ * const params = new URLSearchParams(window.location.search);
286
+ * sendCallbackToOpener({
287
+ * code: params.get('code'),
288
+ * state: params.get('state'),
289
+ * error: params.get('error')
290
+ * });
291
+ * ```
292
+ */
293
+ export function sendCallbackToOpener(params: {
294
+ code: string | null;
295
+ state: string | null;
296
+ error?: string | null;
297
+ }): void {
298
+ if (!isBrowser()) {
299
+ console.error('sendCallbackToOpener() can only be used in browser environments');
300
+ return;
301
+ }
302
+
303
+ if (!window.opener) {
304
+ console.error('No opener window found. This function should only be called from a popup window.');
305
+ return;
306
+ }
307
+
308
+ // Send message to opener
309
+ window.opener.postMessage(
310
+ {
311
+ type: 'oauth_callback',
312
+ code: params.code,
313
+ state: params.state,
314
+ error: params.error,
315
+ },
316
+ '*' // In production, you should specify the exact origin
317
+ );
318
+
319
+ // Close the popup
320
+ window.close();
321
+ }
322
+
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Generic OAuth Plugin
3
+ * Configure OAuth and enable tools for any integration supported by the server
4
+ */
5
+
6
+ import type { MCPPlugin, OAuthConfig } from "./types.js";
7
+
8
+ /**
9
+ * Generic OAuth plugin configuration
10
+ */
11
+ export interface GenericOAuthPluginConfig {
12
+ /** Plugin unique identifier (must match the integration ID on the server) */
13
+ id: string;
14
+ /** OAuth provider name */
15
+ provider: string;
16
+ /** OAuth client ID */
17
+ clientId: string;
18
+ /** OAuth client secret */
19
+ clientSecret: string;
20
+ /** OAuth scopes */
21
+ scopes: string[];
22
+ /** Tool names to enable from the server (must exist on the server) */
23
+ tools: string[];
24
+ /** OAuth redirect URI */
25
+ redirectUri?: string;
26
+ /** Additional provider-specific configuration */
27
+ config?: Record<string, unknown>;
28
+ /** Optional initialization callback */
29
+ onInit?: (client: any) => Promise<void> | void;
30
+ /** Optional after connect callback */
31
+ onAfterConnect?: (client: any) => Promise<void> | void;
32
+ /** Optional disconnect callback */
33
+ onDisconnect?: (client: any) => Promise<void> | void;
34
+ }
35
+
36
+ /**
37
+ * Generic OAuth Plugin
38
+ *
39
+ * Configure OAuth and enable tools for any integration supported by the Integrate MCP server.
40
+ * Note: This does NOT create new tools - it only configures access to existing server-side tools.
41
+ * All tools must be implemented on the server and exposed via the MCP protocol.
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * // Configure Slack integration (assuming server supports Slack tools)
46
+ * const slackPlugin = genericOAuthPlugin({
47
+ * id: 'slack',
48
+ * provider: 'slack',
49
+ * clientId: process.env.SLACK_CLIENT_ID!,
50
+ * clientSecret: process.env.SLACK_CLIENT_SECRET!,
51
+ * scopes: ['chat:write', 'channels:read'],
52
+ * tools: [
53
+ * 'slack_send_message', // Must exist on server
54
+ * 'slack_list_channels', // Must exist on server
55
+ * ],
56
+ * });
57
+ *
58
+ * const client = createMCPClient({
59
+ * plugins: [slackPlugin],
60
+ * });
61
+ *
62
+ * await client.connect();
63
+ * // Call server tools using _callToolByName
64
+ * await client._callToolByName('slack_send_message', { channel: '#general', text: 'Hello' });
65
+ * ```
66
+ */
67
+ export function genericOAuthPlugin(
68
+ config: GenericOAuthPluginConfig
69
+ ): MCPPlugin {
70
+ const oauth: OAuthConfig = {
71
+ provider: config.provider,
72
+ clientId: config.clientId,
73
+ clientSecret: config.clientSecret,
74
+ scopes: config.scopes,
75
+ redirectUri: config.redirectUri,
76
+ config,
77
+ };
78
+
79
+ return {
80
+ id: config.id,
81
+ tools: config.tools,
82
+ oauth,
83
+
84
+ onInit: config.onInit,
85
+ onAfterConnect: config.onAfterConnect,
86
+ onDisconnect: config.onDisconnect,
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Create a simple plugin without OAuth
92
+ * Enable server-provided tools that don't require authentication
93
+ * Note: Tools must exist on the server - this does not create new tools
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * // Enable server-provided math tools (if they exist on the server)
98
+ * const mathPlugin = createSimplePlugin({
99
+ * id: 'math',
100
+ * tools: ['math_add', 'math_subtract', 'math_multiply', 'math_divide'],
101
+ * });
102
+ * ```
103
+ */
104
+ export function createSimplePlugin(config: {
105
+ id: string;
106
+ tools: string[];
107
+ onInit?: (client: any) => Promise<void> | void;
108
+ onAfterConnect?: (client: any) => Promise<void> | void;
109
+ onDisconnect?: (client: any) => Promise<void> | void;
110
+ }): MCPPlugin {
111
+ return {
112
+ id: config.id,
113
+ tools: config.tools,
114
+ onInit: config.onInit,
115
+ onAfterConnect: config.onAfterConnect,
116
+ onDisconnect: config.onDisconnect,
117
+ };
118
+ }
119
+