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.
- package/dist/index.js +42 -15
- package/dist/server.js +42 -15
- package/dist/src/adapters/nextjs-callback.d.ts +44 -0
- package/dist/src/adapters/nextjs-callback.d.ts.map +1 -0
- package/dist/src/adapters/nextjs.d.ts +6 -2
- package/dist/src/adapters/nextjs.d.ts.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/oauth/types.d.ts +9 -0
- package/dist/src/oauth/types.d.ts.map +1 -1
- package/dist/src/oauth/window-manager.d.ts +2 -1
- package/dist/src/oauth/window-manager.d.ts.map +1 -1
- package/package.json +15 -3
- package/src/adapters/auto-routes.ts +217 -0
- package/src/adapters/base-handler.ts +212 -0
- package/src/adapters/nextjs-callback.tsx +160 -0
- package/src/adapters/nextjs.ts +318 -0
- package/src/adapters/tanstack-start.ts +264 -0
- package/src/client.ts +952 -0
- package/src/config/types.ts +180 -0
- package/src/errors.ts +207 -0
- package/src/index.ts +110 -0
- package/src/integrations/vercel-ai.ts +104 -0
- package/src/oauth/manager.ts +307 -0
- package/src/oauth/pkce.ts +127 -0
- package/src/oauth/types.ts +101 -0
- package/src/oauth/window-manager.ts +322 -0
- package/src/plugins/generic.ts +119 -0
- package/src/plugins/github-client.ts +345 -0
- package/src/plugins/github.ts +122 -0
- package/src/plugins/gmail-client.ts +114 -0
- package/src/plugins/gmail.ts +108 -0
- package/src/plugins/server-client.ts +20 -0
- package/src/plugins/types.ts +89 -0
- package/src/protocol/jsonrpc.ts +88 -0
- package/src/protocol/messages.ts +145 -0
- package/src/server.ts +117 -0
- package/src/transport/http-session.ts +322 -0
- package/src/transport/http-stream.ts +331 -0
- 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
|
+
|