integrate-sdk 0.3.6 → 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 +0 -102
- package/dist/src/adapters/nextjs-callback.d.ts +6 -2
- package/dist/src/adapters/nextjs-callback.d.ts.map +1 -1
- package/dist/src/index.d.ts +0 -1
- package/dist/src/index.d.ts.map +1 -1
- package/package.json +6 -1
- 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
package/dist/index.js
CHANGED
|
@@ -1356,106 +1356,6 @@ async function clearClientCache() {
|
|
|
1356
1356
|
// src/index.ts
|
|
1357
1357
|
init_nextjs();
|
|
1358
1358
|
|
|
1359
|
-
// src/adapters/nextjs-callback.tsx
|
|
1360
|
-
import { useEffect } from "react";
|
|
1361
|
-
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
1362
|
-
"use client";
|
|
1363
|
-
function OAuthCallbackPage(config) {
|
|
1364
|
-
const redirectUrl = config?.redirectUrl || "/";
|
|
1365
|
-
const errorRedirectUrl = config?.errorRedirectUrl || "/auth-error";
|
|
1366
|
-
useEffect(() => {
|
|
1367
|
-
const params = new URLSearchParams(window.location.search);
|
|
1368
|
-
const code = params.get("code");
|
|
1369
|
-
const state = params.get("state");
|
|
1370
|
-
const error = params.get("error");
|
|
1371
|
-
const errorDescription = params.get("error_description");
|
|
1372
|
-
if (error) {
|
|
1373
|
-
const errorMsg = errorDescription || error;
|
|
1374
|
-
console.error("[OAuth Callback] Error:", errorMsg);
|
|
1375
|
-
window.location.href = `${errorRedirectUrl}?error=${encodeURIComponent(errorMsg)}`;
|
|
1376
|
-
return;
|
|
1377
|
-
}
|
|
1378
|
-
if (!code || !state) {
|
|
1379
|
-
console.error("[OAuth Callback] Missing code or state parameter");
|
|
1380
|
-
window.location.href = `${errorRedirectUrl}?error=${encodeURIComponent("Invalid OAuth callback")}`;
|
|
1381
|
-
return;
|
|
1382
|
-
}
|
|
1383
|
-
if (window.opener) {
|
|
1384
|
-
window.opener.postMessage({
|
|
1385
|
-
type: "oauth_callback",
|
|
1386
|
-
code,
|
|
1387
|
-
state
|
|
1388
|
-
}, "*");
|
|
1389
|
-
setTimeout(() => {
|
|
1390
|
-
window.close();
|
|
1391
|
-
}, 100);
|
|
1392
|
-
} else {
|
|
1393
|
-
try {
|
|
1394
|
-
sessionStorage.setItem("oauth_callback_params", JSON.stringify({ code, state }));
|
|
1395
|
-
} catch (e) {
|
|
1396
|
-
console.error("Failed to store OAuth callback params:", e);
|
|
1397
|
-
}
|
|
1398
|
-
setTimeout(() => {
|
|
1399
|
-
window.location.href = redirectUrl;
|
|
1400
|
-
}, 500);
|
|
1401
|
-
}
|
|
1402
|
-
}, [redirectUrl, errorRedirectUrl]);
|
|
1403
|
-
return /* @__PURE__ */ jsxDEV("div", {
|
|
1404
|
-
style: {
|
|
1405
|
-
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
1406
|
-
display: "flex",
|
|
1407
|
-
alignItems: "center",
|
|
1408
|
-
justifyContent: "center",
|
|
1409
|
-
minHeight: "100vh",
|
|
1410
|
-
margin: 0,
|
|
1411
|
-
background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
|
|
1412
|
-
color: "white"
|
|
1413
|
-
},
|
|
1414
|
-
children: /* @__PURE__ */ jsxDEV("div", {
|
|
1415
|
-
style: { textAlign: "center", padding: "2rem" },
|
|
1416
|
-
children: [
|
|
1417
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
1418
|
-
style: {
|
|
1419
|
-
border: "3px solid rgba(255, 255, 255, 0.3)",
|
|
1420
|
-
borderRadius: "50%",
|
|
1421
|
-
borderTop: "3px solid white",
|
|
1422
|
-
width: "40px",
|
|
1423
|
-
height: "40px",
|
|
1424
|
-
animation: "spin 1s linear infinite",
|
|
1425
|
-
margin: "0 auto 1rem"
|
|
1426
|
-
}
|
|
1427
|
-
}, undefined, false, undefined, this),
|
|
1428
|
-
/* @__PURE__ */ jsxDEV("h1", {
|
|
1429
|
-
style: {
|
|
1430
|
-
margin: "0 0 0.5rem",
|
|
1431
|
-
fontSize: "1.5rem",
|
|
1432
|
-
fontWeight: 600
|
|
1433
|
-
},
|
|
1434
|
-
children: "Authorization Complete"
|
|
1435
|
-
}, undefined, false, undefined, this),
|
|
1436
|
-
/* @__PURE__ */ jsxDEV("p", {
|
|
1437
|
-
style: { margin: 0, opacity: 0.9, fontSize: "0.875rem" },
|
|
1438
|
-
children: "This window will close automatically..."
|
|
1439
|
-
}, undefined, false, undefined, this),
|
|
1440
|
-
/* @__PURE__ */ jsxDEV("style", {
|
|
1441
|
-
children: `
|
|
1442
|
-
@keyframes spin {
|
|
1443
|
-
0% { transform: rotate(0deg); }
|
|
1444
|
-
100% { transform: rotate(360deg); }
|
|
1445
|
-
}
|
|
1446
|
-
`
|
|
1447
|
-
}, undefined, false, undefined, this)
|
|
1448
|
-
]
|
|
1449
|
-
}, undefined, true, undefined, this)
|
|
1450
|
-
}, undefined, false, undefined, this);
|
|
1451
|
-
}
|
|
1452
|
-
function createOAuthCallbackPage(config) {
|
|
1453
|
-
return function OAuthCallback() {
|
|
1454
|
-
return /* @__PURE__ */ jsxDEV(OAuthCallbackPage, {
|
|
1455
|
-
...config
|
|
1456
|
-
}, undefined, false, undefined, this);
|
|
1457
|
-
};
|
|
1458
|
-
}
|
|
1459
1359
|
// src/adapters/tanstack-start.ts
|
|
1460
1360
|
function createTanStackOAuthHandler(config) {
|
|
1461
1361
|
const handler = new OAuthHandler(config);
|
|
@@ -1690,7 +1590,6 @@ export {
|
|
|
1690
1590
|
generateCodeChallenge,
|
|
1691
1591
|
createTanStackOAuthHandler,
|
|
1692
1592
|
createSimplePlugin,
|
|
1693
|
-
createOAuthCallbackPage,
|
|
1694
1593
|
createNextOAuthHandler,
|
|
1695
1594
|
createMCPClient,
|
|
1696
1595
|
convertMCPToolsToVercelAI,
|
|
@@ -1701,7 +1600,6 @@ export {
|
|
|
1701
1600
|
OAuthWindowManager,
|
|
1702
1601
|
OAuthManager,
|
|
1703
1602
|
OAuthHandler,
|
|
1704
|
-
OAuthCallbackPage,
|
|
1705
1603
|
MCPMethod,
|
|
1706
1604
|
MCPClient,
|
|
1707
1605
|
IntegrateSDKError,
|
|
@@ -19,7 +19,7 @@ import type { OAuthCallbackHandlerConfig } from '../oauth/types.js';
|
|
|
19
19
|
* @example
|
|
20
20
|
* ```tsx
|
|
21
21
|
* // app/oauth/callback/page.tsx
|
|
22
|
-
* import { OAuthCallbackPage } from 'integrate-sdk';
|
|
22
|
+
* import { OAuthCallbackPage } from 'integrate-sdk/oauth-callback';
|
|
23
23
|
*
|
|
24
24
|
* export default function CallbackPage() {
|
|
25
25
|
* return <OAuthCallbackPage redirectUrl="/dashboard" />;
|
|
@@ -33,7 +33,11 @@ export declare function OAuthCallbackPage(config?: OAuthCallbackHandlerConfig):
|
|
|
33
33
|
* @example
|
|
34
34
|
* ```tsx
|
|
35
35
|
* // app/oauth/callback/page.tsx
|
|
36
|
-
*
|
|
36
|
+
* import { createOAuthCallbackPage } from 'integrate-sdk/oauth-callback';
|
|
37
|
+
*
|
|
38
|
+
* export default createOAuthCallbackPage({
|
|
39
|
+
* redirectUrl: '/dashboard',
|
|
40
|
+
* });
|
|
37
41
|
* ```
|
|
38
42
|
*/
|
|
39
43
|
export declare function createOAuthCallbackPage(config?: OAuthCallbackHandlerConfig): () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nextjs-callback.d.ts","sourceRoot":"","sources":["../../../src/adapters/nextjs-callback.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAEpE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,CAAC,EAAE,0BAA0B,2CA2GpE;AAED
|
|
1
|
+
{"version":3,"file":"nextjs-callback.d.ts","sourceRoot":"","sources":["../../../src/adapters/nextjs-callback.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAEpE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,CAAC,EAAE,0BAA0B,2CA2GpE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,CAAC,EAAE,0BAA0B,iDAI1E"}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -11,7 +11,6 @@ export type { OAuthFlowConfig, PopupOptions, AuthStatus, PendingAuth, Authorizat
|
|
|
11
11
|
export { OAuthHandler } from "./adapters/base-handler.js";
|
|
12
12
|
export type { OAuthHandlerConfig, AuthorizeRequest, AuthorizeResponse, CallbackRequest, CallbackResponse, StatusResponse, } from "./adapters/base-handler.js";
|
|
13
13
|
export { createNextOAuthHandler } from "./adapters/nextjs.js";
|
|
14
|
-
export { OAuthCallbackPage, createOAuthCallbackPage } from "./adapters/nextjs-callback.js";
|
|
15
14
|
export { createTanStackOAuthHandler } from "./adapters/tanstack-start.js";
|
|
16
15
|
export type { MCPClientConfig, ReauthContext, ReauthHandler } from "./config/types.js";
|
|
17
16
|
export { IntegrateSDKError, AuthenticationError, AuthorizationError, TokenExpiredError, ConnectionError, ToolCallError, isAuthError, isTokenExpiredError, isAuthorizationError, parseServerError, } from "./errors.js";
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC3E,YAAY,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAGzD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACrF,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC7F,YAAY,EACV,eAAe,EACf,YAAY,EACZ,UAAU,EACV,WAAW,EACX,wBAAwB,EACxB,qBAAqB,EACrB,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,cAAc,GACf,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC3E,YAAY,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAGzD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACrF,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC7F,YAAY,EACV,eAAe,EACf,YAAY,EACZ,UAAU,EACV,WAAW,EACX,wBAAwB,EACxB,qBAAqB,EACrB,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,cAAc,GACf,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAG1E,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGvF,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,WAAW,EACX,mBAAmB,EACnB,oBAAoB,EACpB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAGrB,YAAY,EACV,SAAS,EACT,WAAW,EACX,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,YAAY,EAAE,kBAAkB,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE/F,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,YAAY,EAAE,iBAAiB,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAG3F,YAAY,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAErE,OAAO,EACL,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAGrE,OAAO,EACL,wBAAwB,EACxB,yBAAyB,EACzB,gBAAgB,GACjB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAGhE,YAAY,EACV,cAAc,EACd,eAAe,EACf,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,OAAO,EACP,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAGnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,YAAY,EACV,cAAc,EACd,2BAA2B,GAC5B,MAAM,6BAA6B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "integrate-sdk",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "Type-safe TypeScript SDK for MCP Client with plugin-based OAuth provider configuration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -26,10 +26,15 @@
|
|
|
26
26
|
"./oauth": {
|
|
27
27
|
"import": "./dist/oauth.js",
|
|
28
28
|
"types": "./dist/oauth.d.ts"
|
|
29
|
+
},
|
|
30
|
+
"./oauth-callback": {
|
|
31
|
+
"import": "./src/adapters/nextjs-callback.tsx",
|
|
32
|
+
"types": "./dist/adapters/nextjs-callback.d.ts"
|
|
29
33
|
}
|
|
30
34
|
},
|
|
31
35
|
"files": [
|
|
32
36
|
"dist",
|
|
37
|
+
"src",
|
|
33
38
|
"index.ts",
|
|
34
39
|
"server.ts",
|
|
35
40
|
"oauth.ts"
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-generated OAuth Routes
|
|
3
|
+
* Automatically creates the correct route handlers based on framework detection
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { OAuthHandler, type OAuthHandlerConfig } from './base-handler.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Global OAuth configuration
|
|
10
|
+
* Set by createMCPClient when oauthConfig is provided
|
|
11
|
+
*/
|
|
12
|
+
let globalOAuthConfig: OAuthHandlerConfig | null = null;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Set the global OAuth configuration
|
|
16
|
+
* Called internally by createMCPClient
|
|
17
|
+
*/
|
|
18
|
+
export function setGlobalOAuthConfig(config: OAuthHandlerConfig): void {
|
|
19
|
+
globalOAuthConfig = config;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get the global OAuth configuration
|
|
24
|
+
*/
|
|
25
|
+
export function getGlobalOAuthConfig(): OAuthHandlerConfig | null {
|
|
26
|
+
return globalOAuthConfig;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Universal OAuth route handler
|
|
31
|
+
* Automatically detects framework and handles all OAuth actions
|
|
32
|
+
*
|
|
33
|
+
* This is the magic function that makes everything "just work"
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* // app/api/integrate/oauth/[action]/route.ts (Next.js)
|
|
38
|
+
* export * from 'integrate-sdk/oauth';
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* // app/routes/api/integrate/oauth/[action].ts (TanStack Start)
|
|
44
|
+
* export * from 'integrate-sdk/oauth';
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
// Framework detection helpers (unused but kept for future enhancements)
|
|
49
|
+
// function isNextJS(request: any): boolean {
|
|
50
|
+
// return (
|
|
51
|
+
// request?.constructor?.name === 'NextRequest' ||
|
|
52
|
+
// typeof request?.nextUrl !== 'undefined' ||
|
|
53
|
+
// typeof (globalThis as any).NextResponse !== 'undefined'
|
|
54
|
+
// );
|
|
55
|
+
// }
|
|
56
|
+
|
|
57
|
+
// function isTanStackStart(request: any): boolean {
|
|
58
|
+
// return (
|
|
59
|
+
// request instanceof Request &&
|
|
60
|
+
// !isNextJS(request)
|
|
61
|
+
// );
|
|
62
|
+
// }
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Universal POST handler
|
|
66
|
+
* Handles authorize and callback actions
|
|
67
|
+
*/
|
|
68
|
+
export async function POST(
|
|
69
|
+
req: any,
|
|
70
|
+
context?: { params: { action: string } }
|
|
71
|
+
): Promise<any> {
|
|
72
|
+
if (!globalOAuthConfig) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
'OAuth configuration not found. Did you configure oauthProviders in createMCPClient?'
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const handler = new OAuthHandler(globalOAuthConfig);
|
|
79
|
+
const action = context?.params?.action;
|
|
80
|
+
|
|
81
|
+
if (!action) {
|
|
82
|
+
return createErrorResponse('Missing action parameter', 400);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
if (action === 'authorize') {
|
|
87
|
+
const body = await parseRequestBody(req);
|
|
88
|
+
const result = await handler.handleAuthorize(body);
|
|
89
|
+
return createSuccessResponse(result);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (action === 'callback') {
|
|
93
|
+
const body = await parseRequestBody(req);
|
|
94
|
+
const result = await handler.handleCallback(body);
|
|
95
|
+
return createSuccessResponse(result);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return createErrorResponse(`Unknown action: ${action}`, 404);
|
|
99
|
+
} catch (error: any) {
|
|
100
|
+
console.error(`[OAuth ${action}] Error:`, error);
|
|
101
|
+
return createErrorResponse(error.message, 500);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Universal GET handler
|
|
107
|
+
* Handles status action
|
|
108
|
+
*/
|
|
109
|
+
export async function GET(
|
|
110
|
+
req: any,
|
|
111
|
+
context?: { params: { action: string } }
|
|
112
|
+
): Promise<any> {
|
|
113
|
+
if (!globalOAuthConfig) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
'OAuth configuration not found. Did you configure oauthProviders in createMCPClient?'
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const handler = new OAuthHandler(globalOAuthConfig);
|
|
120
|
+
const action = context?.params?.action;
|
|
121
|
+
|
|
122
|
+
if (!action) {
|
|
123
|
+
return createErrorResponse('Missing action parameter', 400);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
if (action === 'status') {
|
|
128
|
+
const { provider, sessionToken } = parseQueryParams(req);
|
|
129
|
+
|
|
130
|
+
if (!provider || !sessionToken) {
|
|
131
|
+
return createErrorResponse(
|
|
132
|
+
'Missing provider or session token',
|
|
133
|
+
400
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const result = await handler.handleStatus(provider, sessionToken);
|
|
138
|
+
return createSuccessResponse(result);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return createErrorResponse(`Unknown action: ${action}`, 404);
|
|
142
|
+
} catch (error: any) {
|
|
143
|
+
console.error(`[OAuth ${action}] Error:`, error);
|
|
144
|
+
return createErrorResponse(error.message, 500);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Parse request body (works for both Next.js and standard Request)
|
|
150
|
+
*/
|
|
151
|
+
async function parseRequestBody(req: any): Promise<any> {
|
|
152
|
+
if (typeof req.json === 'function') {
|
|
153
|
+
return await req.json();
|
|
154
|
+
}
|
|
155
|
+
throw new Error('Unable to parse request body');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Parse query parameters (works for both Next.js and standard Request)
|
|
160
|
+
*/
|
|
161
|
+
function parseQueryParams(req: any): { provider?: string; sessionToken?: string } {
|
|
162
|
+
let url: URL;
|
|
163
|
+
|
|
164
|
+
// Next.js
|
|
165
|
+
if (req.nextUrl) {
|
|
166
|
+
url = new URL(req.nextUrl);
|
|
167
|
+
}
|
|
168
|
+
// Standard Request
|
|
169
|
+
else if (req.url) {
|
|
170
|
+
url = new URL(req.url);
|
|
171
|
+
} else {
|
|
172
|
+
return {};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const provider = url.searchParams.get('provider') || undefined;
|
|
176
|
+
const sessionToken = req.headers?.get?.('x-session-token') || undefined;
|
|
177
|
+
|
|
178
|
+
return { provider, sessionToken };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Create success response (works for both frameworks)
|
|
183
|
+
*/
|
|
184
|
+
function createSuccessResponse(data: any): any {
|
|
185
|
+
// Try Next.js first
|
|
186
|
+
if (typeof (globalThis as any).NextResponse !== 'undefined') {
|
|
187
|
+
const NextResponse = (globalThis as any).NextResponse;
|
|
188
|
+
return NextResponse.json(data);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Fallback to standard Response
|
|
192
|
+
return new Response(JSON.stringify(data), {
|
|
193
|
+
status: 200,
|
|
194
|
+
headers: { 'Content-Type': 'application/json' },
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Create error response (works for both frameworks)
|
|
200
|
+
*/
|
|
201
|
+
function createErrorResponse(message: string, status: number): any {
|
|
202
|
+
// Try Next.js first
|
|
203
|
+
if (typeof (globalThis as any).NextResponse !== 'undefined') {
|
|
204
|
+
const NextResponse = (globalThis as any).NextResponse;
|
|
205
|
+
return NextResponse.json({ error: message }, { status });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Fallback to standard Response
|
|
209
|
+
return new Response(
|
|
210
|
+
JSON.stringify({ error: message }),
|
|
211
|
+
{
|
|
212
|
+
status,
|
|
213
|
+
headers: { 'Content-Type': 'application/json' },
|
|
214
|
+
}
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base OAuth Handler
|
|
3
|
+
* Framework-agnostic OAuth route logic for secure server-side token management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* MCP Server URL - managed by Integrate
|
|
8
|
+
*/
|
|
9
|
+
const MCP_SERVER_URL = 'https://mcp.integrate.dev/api/v1/mcp';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* OAuth handler configuration
|
|
13
|
+
* OAuth credentials for each provider
|
|
14
|
+
*/
|
|
15
|
+
export interface OAuthHandlerConfig {
|
|
16
|
+
/** OAuth configurations by provider */
|
|
17
|
+
providers: Record<string, {
|
|
18
|
+
/** OAuth client ID from environment variables */
|
|
19
|
+
clientId: string;
|
|
20
|
+
/** OAuth client secret from environment variables */
|
|
21
|
+
clientSecret: string;
|
|
22
|
+
/** Optional redirect URI override */
|
|
23
|
+
redirectUri?: string;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Request body for authorize endpoint
|
|
29
|
+
*/
|
|
30
|
+
export interface AuthorizeRequest {
|
|
31
|
+
provider: string;
|
|
32
|
+
scopes: string[];
|
|
33
|
+
state: string;
|
|
34
|
+
codeChallenge: string;
|
|
35
|
+
codeChallengeMethod: string;
|
|
36
|
+
redirectUri?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Response from authorize endpoint
|
|
41
|
+
*/
|
|
42
|
+
export interface AuthorizeResponse {
|
|
43
|
+
authorizationUrl: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Request body for callback endpoint
|
|
48
|
+
*/
|
|
49
|
+
export interface CallbackRequest {
|
|
50
|
+
provider: string;
|
|
51
|
+
code: string;
|
|
52
|
+
codeVerifier: string;
|
|
53
|
+
state: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Response from callback endpoint
|
|
58
|
+
*/
|
|
59
|
+
export interface CallbackResponse {
|
|
60
|
+
sessionToken: string;
|
|
61
|
+
provider: string;
|
|
62
|
+
scopes: string[];
|
|
63
|
+
expiresAt?: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Response from status endpoint
|
|
68
|
+
*/
|
|
69
|
+
export interface StatusResponse {
|
|
70
|
+
authorized: boolean;
|
|
71
|
+
provider: string;
|
|
72
|
+
scopes?: string[];
|
|
73
|
+
expiresAt?: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* OAuth Handler
|
|
78
|
+
* Handles OAuth authorization flows by proxying requests to MCP server
|
|
79
|
+
* with server-side OAuth credentials from environment variables
|
|
80
|
+
*/
|
|
81
|
+
export class OAuthHandler {
|
|
82
|
+
private readonly serverUrl = MCP_SERVER_URL;
|
|
83
|
+
|
|
84
|
+
constructor(private config: OAuthHandlerConfig) {}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Handle authorization URL request
|
|
88
|
+
* Gets authorization URL from MCP server with full OAuth credentials
|
|
89
|
+
*
|
|
90
|
+
* @param request - Authorization request from client
|
|
91
|
+
* @returns Authorization URL to redirect/open for user
|
|
92
|
+
*
|
|
93
|
+
* @throws Error if provider is not configured
|
|
94
|
+
* @throws Error if MCP server request fails
|
|
95
|
+
*/
|
|
96
|
+
async handleAuthorize(request: AuthorizeRequest): Promise<AuthorizeResponse> {
|
|
97
|
+
// Get OAuth config from environment (server-side)
|
|
98
|
+
const providerConfig = this.config.providers[request.provider];
|
|
99
|
+
if (!providerConfig) {
|
|
100
|
+
throw new Error(`Provider ${request.provider} not configured. Add OAuth credentials to your API route configuration.`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Validate required fields
|
|
104
|
+
if (!providerConfig.clientId || !providerConfig.clientSecret) {
|
|
105
|
+
throw new Error(`Missing OAuth credentials for ${request.provider}. Check your environment variables.`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Build URL to MCP server
|
|
109
|
+
const url = new URL('/oauth/authorize', this.serverUrl);
|
|
110
|
+
url.searchParams.set('provider', request.provider);
|
|
111
|
+
url.searchParams.set('client_id', providerConfig.clientId);
|
|
112
|
+
url.searchParams.set('client_secret', providerConfig.clientSecret);
|
|
113
|
+
url.searchParams.set('scope', request.scopes.join(','));
|
|
114
|
+
url.searchParams.set('state', request.state);
|
|
115
|
+
url.searchParams.set('code_challenge', request.codeChallenge);
|
|
116
|
+
url.searchParams.set('code_challenge_method', request.codeChallengeMethod);
|
|
117
|
+
|
|
118
|
+
// Use request redirect URI or fallback to provider config
|
|
119
|
+
const redirectUri = request.redirectUri || providerConfig.redirectUri;
|
|
120
|
+
if (redirectUri) {
|
|
121
|
+
url.searchParams.set('redirect_uri', redirectUri);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Forward to MCP server
|
|
125
|
+
const response = await fetch(url.toString(), {
|
|
126
|
+
method: 'GET',
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (!response.ok) {
|
|
130
|
+
const error = await response.text();
|
|
131
|
+
throw new Error(`MCP server failed to generate authorization URL: ${error}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const data = await response.json();
|
|
135
|
+
return data as AuthorizeResponse;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Handle OAuth callback
|
|
140
|
+
* Exchanges authorization code for session token
|
|
141
|
+
*
|
|
142
|
+
* @param request - Callback request with authorization code
|
|
143
|
+
* @returns Session token and authorization details
|
|
144
|
+
*
|
|
145
|
+
* @throws Error if MCP server request fails
|
|
146
|
+
*/
|
|
147
|
+
async handleCallback(request: CallbackRequest): Promise<CallbackResponse> {
|
|
148
|
+
// Forward to MCP server for token exchange
|
|
149
|
+
const url = new URL('/oauth/callback', this.serverUrl);
|
|
150
|
+
|
|
151
|
+
const response = await fetch(url.toString(), {
|
|
152
|
+
method: 'POST',
|
|
153
|
+
headers: {
|
|
154
|
+
'Content-Type': 'application/json',
|
|
155
|
+
},
|
|
156
|
+
body: JSON.stringify({
|
|
157
|
+
provider: request.provider,
|
|
158
|
+
code: request.code,
|
|
159
|
+
code_verifier: request.codeVerifier,
|
|
160
|
+
state: request.state,
|
|
161
|
+
}),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
const error = await response.text();
|
|
166
|
+
throw new Error(`MCP server failed to exchange authorization code: ${error}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const data = await response.json();
|
|
170
|
+
return data as CallbackResponse;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Handle authorization status check
|
|
175
|
+
* Checks if a provider is currently authorized
|
|
176
|
+
*
|
|
177
|
+
* @param provider - Provider to check
|
|
178
|
+
* @param sessionToken - Session token from client
|
|
179
|
+
* @returns Authorization status
|
|
180
|
+
*
|
|
181
|
+
* @throws Error if MCP server request fails
|
|
182
|
+
*/
|
|
183
|
+
async handleStatus(provider: string, sessionToken: string): Promise<StatusResponse> {
|
|
184
|
+
// Forward to MCP server
|
|
185
|
+
const url = new URL('/oauth/status', this.serverUrl);
|
|
186
|
+
url.searchParams.set('provider', provider);
|
|
187
|
+
|
|
188
|
+
const response = await fetch(url.toString(), {
|
|
189
|
+
method: 'GET',
|
|
190
|
+
headers: {
|
|
191
|
+
'X-Session-Token': sessionToken,
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
if (!response.ok) {
|
|
196
|
+
// If unauthorized, return not authorized status
|
|
197
|
+
if (response.status === 401) {
|
|
198
|
+
return {
|
|
199
|
+
authorized: false,
|
|
200
|
+
provider,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const error = await response.text();
|
|
205
|
+
throw new Error(`MCP server failed to check authorization status: ${error}`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const data = await response.json();
|
|
209
|
+
return data as StatusResponse;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|