@inkeep/agents-manage-api 0.9.0 → 0.10.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.
- package/dist/index.cjs +372 -341
- package/dist/index.js +373 -342
- package/package.json +5 -3
package/dist/index.cjs
CHANGED
|
@@ -3480,245 +3480,7 @@ app14.openapi(
|
|
|
3480
3480
|
}
|
|
3481
3481
|
);
|
|
3482
3482
|
var projects_default = app14;
|
|
3483
|
-
var logger3 = agentsCore.getLogger("
|
|
3484
|
-
var pkceStore = /* @__PURE__ */ new Map();
|
|
3485
|
-
function storePKCEVerifier(state, codeVerifier, toolId, tenantId, projectId, clientId) {
|
|
3486
|
-
pkceStore.set(state, { codeVerifier, toolId, tenantId, projectId, clientId });
|
|
3487
|
-
setTimeout(
|
|
3488
|
-
() => {
|
|
3489
|
-
pkceStore.delete(state);
|
|
3490
|
-
},
|
|
3491
|
-
10 * 60 * 1e3
|
|
3492
|
-
);
|
|
3493
|
-
}
|
|
3494
|
-
function retrievePKCEVerifier(state) {
|
|
3495
|
-
const data = pkceStore.get(state);
|
|
3496
|
-
if (data) {
|
|
3497
|
-
pkceStore.delete(state);
|
|
3498
|
-
return data;
|
|
3499
|
-
}
|
|
3500
|
-
return null;
|
|
3501
|
-
}
|
|
3502
|
-
var OAuthService = class {
|
|
3503
|
-
constructor(config = {}) {
|
|
3504
|
-
__publicField(this, "defaultConfig");
|
|
3505
|
-
this.defaultConfig = {
|
|
3506
|
-
defaultClientId: config.defaultClientId || process.env.DEFAULT_OAUTH_CLIENT_ID || "mcp-client",
|
|
3507
|
-
clientName: config.clientName || process.env.OAUTH_CLIENT_NAME || "Inkeep Agent Framework",
|
|
3508
|
-
clientUri: config.clientUri || process.env.OAUTH_CLIENT_URI || "https://inkeep.com",
|
|
3509
|
-
logoUri: config.logoUri || process.env.OAUTH_CLIENT_LOGO_URI || "https://inkeep.com/images/logos/inkeep-logo-blue.svg",
|
|
3510
|
-
redirectBaseUrl: config.redirectBaseUrl || process.env.OAUTH_REDIRECT_BASE_URL || "http://localhost:3002"
|
|
3511
|
-
};
|
|
3512
|
-
}
|
|
3513
|
-
/**
|
|
3514
|
-
* Initiate OAuth flow for an MCP tool
|
|
3515
|
-
*/
|
|
3516
|
-
async initiateOAuthFlow(params) {
|
|
3517
|
-
const { tool, tenantId, projectId, toolId } = params;
|
|
3518
|
-
const oAuthConfig = await agentsCore.discoverOAuthEndpoints(tool.config.mcp.server.url, logger3);
|
|
3519
|
-
if (!oAuthConfig) {
|
|
3520
|
-
throw new Error("OAuth not supported by this server");
|
|
3521
|
-
}
|
|
3522
|
-
const { codeVerifier, codeChallenge } = await this.generatePKCEInternal();
|
|
3523
|
-
const redirectUri = `${this.defaultConfig.redirectBaseUrl}/oauth/callback`;
|
|
3524
|
-
let clientId = this.defaultConfig.defaultClientId;
|
|
3525
|
-
if (oAuthConfig.supportsDynamicRegistration && oAuthConfig.registrationUrl) {
|
|
3526
|
-
clientId = await this.performDynamicClientRegistration(
|
|
3527
|
-
oAuthConfig.registrationUrl,
|
|
3528
|
-
redirectUri
|
|
3529
|
-
);
|
|
3530
|
-
}
|
|
3531
|
-
const state = `tool_${toolId}`;
|
|
3532
|
-
const authUrl = this.buildAuthorizationUrl({
|
|
3533
|
-
oAuthConfig,
|
|
3534
|
-
clientId,
|
|
3535
|
-
redirectUri,
|
|
3536
|
-
state,
|
|
3537
|
-
codeChallenge,
|
|
3538
|
-
resource: tool.config.mcp.server.url
|
|
3539
|
-
});
|
|
3540
|
-
storePKCEVerifier(state, codeVerifier, toolId, tenantId, projectId, clientId);
|
|
3541
|
-
logger3.info({ toolId, oAuthConfig, tenantId, projectId }, "OAuth flow initiated successfully");
|
|
3542
|
-
return {
|
|
3543
|
-
redirectUrl: authUrl,
|
|
3544
|
-
state
|
|
3545
|
-
};
|
|
3546
|
-
}
|
|
3547
|
-
/**
|
|
3548
|
-
* Exchange authorization code for access tokens
|
|
3549
|
-
*/
|
|
3550
|
-
async exchangeCodeForTokens(params) {
|
|
3551
|
-
const { code, codeVerifier, clientId, tool } = params;
|
|
3552
|
-
const oAuthConfig = await agentsCore.discoverOAuthEndpoints(tool.config.mcp.server.url, logger3);
|
|
3553
|
-
if (!oAuthConfig?.tokenUrl) {
|
|
3554
|
-
throw new Error("Could not discover OAuth token endpoint");
|
|
3555
|
-
}
|
|
3556
|
-
const redirectUri = `${this.defaultConfig.redirectBaseUrl}/oauth/callback`;
|
|
3557
|
-
let tokens;
|
|
3558
|
-
try {
|
|
3559
|
-
tokens = await this.exchangeWithOpenIdClient({
|
|
3560
|
-
oAuthConfig,
|
|
3561
|
-
clientId,
|
|
3562
|
-
code,
|
|
3563
|
-
codeVerifier,
|
|
3564
|
-
redirectUri
|
|
3565
|
-
});
|
|
3566
|
-
logger3.info({ tokenType: tokens.token_type }, "Token exchange successful with openid-client");
|
|
3567
|
-
} catch (error) {
|
|
3568
|
-
logger3.warn(
|
|
3569
|
-
{ error: error instanceof Error ? error.message : error },
|
|
3570
|
-
"openid-client failed, falling back to manual token exchange"
|
|
3571
|
-
);
|
|
3572
|
-
tokens = await this.exchangeManually({
|
|
3573
|
-
oAuthConfig,
|
|
3574
|
-
clientId,
|
|
3575
|
-
code,
|
|
3576
|
-
codeVerifier,
|
|
3577
|
-
redirectUri
|
|
3578
|
-
});
|
|
3579
|
-
logger3.info({ tokenType: tokens.token_type }, "Manual token exchange successful");
|
|
3580
|
-
}
|
|
3581
|
-
return { tokens, oAuthConfig };
|
|
3582
|
-
}
|
|
3583
|
-
/**
|
|
3584
|
-
* Perform dynamic client registration
|
|
3585
|
-
*/
|
|
3586
|
-
async performDynamicClientRegistration(registrationUrl, redirectUri) {
|
|
3587
|
-
logger3.info({ registrationUrl }, "Attempting dynamic client registration");
|
|
3588
|
-
try {
|
|
3589
|
-
const registrationResponse = await fetch(registrationUrl, {
|
|
3590
|
-
method: "POST",
|
|
3591
|
-
headers: {
|
|
3592
|
-
"Content-Type": "application/json",
|
|
3593
|
-
Accept: "application/json"
|
|
3594
|
-
},
|
|
3595
|
-
body: JSON.stringify({
|
|
3596
|
-
client_name: this.defaultConfig.clientName,
|
|
3597
|
-
client_uri: this.defaultConfig.clientUri,
|
|
3598
|
-
logo_uri: this.defaultConfig.logoUri,
|
|
3599
|
-
redirect_uris: [redirectUri],
|
|
3600
|
-
grant_types: ["authorization_code"],
|
|
3601
|
-
response_types: ["code"],
|
|
3602
|
-
token_endpoint_auth_method: "none",
|
|
3603
|
-
// PKCE only, no client secret
|
|
3604
|
-
application_type: "native"
|
|
3605
|
-
// For PKCE flows
|
|
3606
|
-
})
|
|
3607
|
-
});
|
|
3608
|
-
if (registrationResponse.ok) {
|
|
3609
|
-
const registration = await registrationResponse.json();
|
|
3610
|
-
logger3.info({ clientId: registration.client_id }, "Dynamic client registration successful");
|
|
3611
|
-
return registration.client_id;
|
|
3612
|
-
} else {
|
|
3613
|
-
const errorText = await registrationResponse.text();
|
|
3614
|
-
logger3.warn(
|
|
3615
|
-
{
|
|
3616
|
-
status: registrationResponse.status,
|
|
3617
|
-
errorText
|
|
3618
|
-
},
|
|
3619
|
-
"Dynamic client registration failed, using default client_id"
|
|
3620
|
-
);
|
|
3621
|
-
}
|
|
3622
|
-
} catch (regError) {
|
|
3623
|
-
logger3.warn(
|
|
3624
|
-
{ error: regError },
|
|
3625
|
-
"Dynamic client registration error, using default client_id"
|
|
3626
|
-
);
|
|
3627
|
-
}
|
|
3628
|
-
return this.defaultConfig.defaultClientId;
|
|
3629
|
-
}
|
|
3630
|
-
/**
|
|
3631
|
-
* Build authorization URL
|
|
3632
|
-
*/
|
|
3633
|
-
buildAuthorizationUrl(params) {
|
|
3634
|
-
const { oAuthConfig, clientId, redirectUri, state, codeChallenge, resource } = params;
|
|
3635
|
-
const authUrl = new URL(oAuthConfig.authorizationUrl);
|
|
3636
|
-
authUrl.searchParams.set("response_type", "code");
|
|
3637
|
-
authUrl.searchParams.set("client_id", clientId);
|
|
3638
|
-
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
3639
|
-
authUrl.searchParams.set("state", state);
|
|
3640
|
-
authUrl.searchParams.set("code_challenge", codeChallenge);
|
|
3641
|
-
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
3642
|
-
authUrl.searchParams.set("resource", resource);
|
|
3643
|
-
return authUrl.toString();
|
|
3644
|
-
}
|
|
3645
|
-
/**
|
|
3646
|
-
* Exchange code using openid-client library
|
|
3647
|
-
*/
|
|
3648
|
-
async exchangeWithOpenIdClient(params) {
|
|
3649
|
-
const { oAuthConfig, clientId, code, codeVerifier, redirectUri } = params;
|
|
3650
|
-
const oauth = await import('openid-client');
|
|
3651
|
-
const tokenUrl = new URL(oAuthConfig.tokenUrl);
|
|
3652
|
-
const oauthServerUrl = `${tokenUrl.protocol}//${tokenUrl.host}`;
|
|
3653
|
-
logger3.info({ oauthServerUrl, clientId }, "Attempting openid-client discovery");
|
|
3654
|
-
const config = await oauth.discovery(
|
|
3655
|
-
new URL(oauthServerUrl),
|
|
3656
|
-
clientId,
|
|
3657
|
-
void 0
|
|
3658
|
-
// No client secret for PKCE
|
|
3659
|
-
);
|
|
3660
|
-
const callbackUrl = new URL(
|
|
3661
|
-
`${redirectUri}?${new URLSearchParams({ code, state: "unused" }).toString()}`
|
|
3662
|
-
);
|
|
3663
|
-
return await oauth.authorizationCodeGrant(config, callbackUrl, {
|
|
3664
|
-
pkceCodeVerifier: codeVerifier
|
|
3665
|
-
});
|
|
3666
|
-
}
|
|
3667
|
-
/**
|
|
3668
|
-
* Internal PKCE generation
|
|
3669
|
-
*/
|
|
3670
|
-
async generatePKCEInternal() {
|
|
3671
|
-
const codeVerifier = Buffer.from(
|
|
3672
|
-
Array.from(crypto.getRandomValues(new Uint8Array(32)))
|
|
3673
|
-
).toString("base64url");
|
|
3674
|
-
const encoder = new TextEncoder();
|
|
3675
|
-
const data = encoder.encode(codeVerifier);
|
|
3676
|
-
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
3677
|
-
const codeChallenge = Buffer.from(hash).toString("base64url");
|
|
3678
|
-
return { codeVerifier, codeChallenge };
|
|
3679
|
-
}
|
|
3680
|
-
/**
|
|
3681
|
-
* Manual token exchange fallback
|
|
3682
|
-
*/
|
|
3683
|
-
async exchangeManually(params) {
|
|
3684
|
-
const { oAuthConfig, clientId, code, codeVerifier, redirectUri } = params;
|
|
3685
|
-
logger3.info({ tokenUrl: oAuthConfig.tokenUrl }, "Attempting manual token exchange");
|
|
3686
|
-
const tokenResponse = await fetch(oAuthConfig.tokenUrl, {
|
|
3687
|
-
method: "POST",
|
|
3688
|
-
headers: {
|
|
3689
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
3690
|
-
Accept: "application/json"
|
|
3691
|
-
},
|
|
3692
|
-
body: new URLSearchParams({
|
|
3693
|
-
grant_type: "authorization_code",
|
|
3694
|
-
code,
|
|
3695
|
-
redirect_uri: redirectUri,
|
|
3696
|
-
client_id: clientId,
|
|
3697
|
-
code_verifier: codeVerifier
|
|
3698
|
-
// PKCE verification
|
|
3699
|
-
})
|
|
3700
|
-
});
|
|
3701
|
-
if (!tokenResponse.ok) {
|
|
3702
|
-
const errorText = await tokenResponse.text();
|
|
3703
|
-
logger3.error(
|
|
3704
|
-
{
|
|
3705
|
-
status: tokenResponse.status,
|
|
3706
|
-
statusText: tokenResponse.statusText,
|
|
3707
|
-
...process.env.NODE_ENV === "development" && { errorText },
|
|
3708
|
-
clientId,
|
|
3709
|
-
tokenUrl: oAuthConfig.tokenUrl
|
|
3710
|
-
},
|
|
3711
|
-
"Token exchange failed"
|
|
3712
|
-
);
|
|
3713
|
-
throw new Error("Authentication failed. Please try again or contact support.");
|
|
3714
|
-
}
|
|
3715
|
-
return await tokenResponse.json();
|
|
3716
|
-
}
|
|
3717
|
-
};
|
|
3718
|
-
var oauthService = new OAuthService();
|
|
3719
|
-
|
|
3720
|
-
// src/routes/tools.ts
|
|
3721
|
-
var logger4 = agentsCore.getLogger("tools");
|
|
3483
|
+
var logger3 = agentsCore.getLogger("tools");
|
|
3722
3484
|
var app15 = new zodOpenapi.OpenAPIHono();
|
|
3723
3485
|
app15.openapi(
|
|
3724
3486
|
zodOpenapi.createRoute({
|
|
@@ -3850,7 +3612,7 @@ app15.openapi(
|
|
|
3850
3612
|
const { tenantId, projectId } = c.req.valid("param");
|
|
3851
3613
|
const body = c.req.valid("json");
|
|
3852
3614
|
const credentialStores = c.get("credentialStores");
|
|
3853
|
-
|
|
3615
|
+
logger3.info({ body }, "body");
|
|
3854
3616
|
const id = body.id || nanoid.nanoid();
|
|
3855
3617
|
const tool = await agentsCore.createTool(dbClient_default)({
|
|
3856
3618
|
tenantId,
|
|
@@ -3967,109 +3729,375 @@ app15.openapi(
|
|
|
3967
3729
|
return c.body(null, 204);
|
|
3968
3730
|
}
|
|
3969
3731
|
);
|
|
3970
|
-
app15
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3732
|
+
var tools_default = app15;
|
|
3733
|
+
|
|
3734
|
+
// src/routes/index.ts
|
|
3735
|
+
var app16 = new zodOpenapi.OpenAPIHono();
|
|
3736
|
+
app16.route("/projects", projects_default);
|
|
3737
|
+
app16.route("/projects/:projectId/graphs/:graphId/agents", agents_default);
|
|
3738
|
+
app16.route("/projects/:projectId/graphs/:graphId/agent-relations", agentRelations_default);
|
|
3739
|
+
app16.route("/projects/:projectId/agent-graphs", agentGraph_default);
|
|
3740
|
+
app16.route("/projects/:projectId/graphs/:graphId/agent-tool-relations", agentToolRelations_default);
|
|
3741
|
+
app16.route("/projects/:projectId/graphs/:graphId/agent-artifact-components", agentArtifactComponents_default);
|
|
3742
|
+
app16.route("/projects/:projectId/graphs/:graphId/agent-data-components", agentDataComponents_default);
|
|
3743
|
+
app16.route("/projects/:projectId/artifact-components", artifactComponents_default);
|
|
3744
|
+
app16.route("/projects/:projectId/context-configs", contextConfigs_default);
|
|
3745
|
+
app16.route("/projects/:projectId/credentials", credentials_default);
|
|
3746
|
+
app16.route("/projects/:projectId/data-components", dataComponents_default);
|
|
3747
|
+
app16.route("/projects/:projectId/graphs/:graphId/external-agents", externalAgents_default);
|
|
3748
|
+
app16.route("/projects/:projectId/tools", tools_default);
|
|
3749
|
+
app16.route("/projects/:projectId/api-keys", apiKeys_default);
|
|
3750
|
+
app16.route("/projects/:projectId/graph", graphFull_default);
|
|
3751
|
+
var routes_default = app16;
|
|
3752
|
+
var logger4 = agentsCore.getLogger("oauth-service");
|
|
3753
|
+
var pkceStore = /* @__PURE__ */ new Map();
|
|
3754
|
+
function storePKCEVerifier(state, codeVerifier, toolId, tenantId, projectId, clientId) {
|
|
3755
|
+
pkceStore.set(state, { codeVerifier, toolId, tenantId, projectId, clientId });
|
|
3756
|
+
setTimeout(
|
|
3757
|
+
() => {
|
|
3758
|
+
pkceStore.delete(state);
|
|
3980
3759
|
},
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3760
|
+
10 * 60 * 1e3
|
|
3761
|
+
);
|
|
3762
|
+
}
|
|
3763
|
+
function retrievePKCEVerifier(state) {
|
|
3764
|
+
const data = pkceStore.get(state);
|
|
3765
|
+
if (data) {
|
|
3766
|
+
pkceStore.delete(state);
|
|
3767
|
+
return data;
|
|
3768
|
+
}
|
|
3769
|
+
return null;
|
|
3770
|
+
}
|
|
3771
|
+
var OAuthService = class {
|
|
3772
|
+
constructor(config = {}) {
|
|
3773
|
+
__publicField(this, "defaultConfig");
|
|
3774
|
+
this.defaultConfig = {
|
|
3775
|
+
defaultClientId: config.defaultClientId || process.env.DEFAULT_OAUTH_CLIENT_ID || "mcp-client",
|
|
3776
|
+
clientName: config.clientName || process.env.OAUTH_CLIENT_NAME || "Inkeep Agent Framework",
|
|
3777
|
+
clientUri: config.clientUri || process.env.OAUTH_CLIENT_URI || "https://inkeep.com",
|
|
3778
|
+
logoUri: config.logoUri || process.env.OAUTH_CLIENT_LOGO_URI || "https://inkeep.com/images/logos/inkeep-logo-blue.svg",
|
|
3779
|
+
redirectBaseUrl: config.redirectBaseUrl || env.AGENTS_MANAGE_API_URL
|
|
3780
|
+
};
|
|
3781
|
+
}
|
|
3782
|
+
/**
|
|
3783
|
+
* Initiate OAuth flow for an MCP tool
|
|
3784
|
+
*/
|
|
3785
|
+
async initiateOAuthFlow(params) {
|
|
3786
|
+
const { tool, tenantId, projectId, toolId, baseUrl } = params;
|
|
3787
|
+
const oAuthConfig = await agentsCore.discoverOAuthEndpoints(tool.config.mcp.server.url, logger4);
|
|
3788
|
+
if (!oAuthConfig) {
|
|
3789
|
+
throw new Error("OAuth not supported by this server");
|
|
3790
|
+
}
|
|
3791
|
+
const { codeVerifier, codeChallenge } = await this.generatePKCEInternal();
|
|
3792
|
+
const redirectBaseUrl = baseUrl || this.defaultConfig.redirectBaseUrl;
|
|
3793
|
+
const redirectUri = `${redirectBaseUrl}/oauth/callback`;
|
|
3794
|
+
let clientId = this.defaultConfig.defaultClientId;
|
|
3795
|
+
if (oAuthConfig.supportsDynamicRegistration && oAuthConfig.registrationUrl) {
|
|
3796
|
+
clientId = await this.performDynamicClientRegistration(
|
|
3797
|
+
oAuthConfig.registrationUrl,
|
|
3798
|
+
redirectUri
|
|
3799
|
+
);
|
|
3800
|
+
}
|
|
3801
|
+
const state = `tool_${toolId}`;
|
|
3802
|
+
const authUrl = this.buildAuthorizationUrl({
|
|
3803
|
+
oAuthConfig,
|
|
3804
|
+
clientId,
|
|
3805
|
+
redirectUri,
|
|
3806
|
+
state,
|
|
3807
|
+
codeChallenge,
|
|
3808
|
+
resource: tool.config.mcp.server.url
|
|
3809
|
+
});
|
|
3810
|
+
storePKCEVerifier(state, codeVerifier, toolId, tenantId, projectId, clientId);
|
|
3811
|
+
logger4.info({ toolId, oAuthConfig, tenantId, projectId }, "OAuth flow initiated successfully");
|
|
3812
|
+
return {
|
|
3813
|
+
redirectUrl: authUrl,
|
|
3814
|
+
state
|
|
3815
|
+
};
|
|
3816
|
+
}
|
|
3817
|
+
/**
|
|
3818
|
+
* Exchange authorization code for access tokens
|
|
3819
|
+
*/
|
|
3820
|
+
async exchangeCodeForTokens(params) {
|
|
3821
|
+
const { code, codeVerifier, clientId, tool, baseUrl } = params;
|
|
3822
|
+
const oAuthConfig = await agentsCore.discoverOAuthEndpoints(tool.config.mcp.server.url, logger4);
|
|
3823
|
+
if (!oAuthConfig?.tokenUrl) {
|
|
3824
|
+
throw new Error("Could not discover OAuth token endpoint");
|
|
3825
|
+
}
|
|
3826
|
+
const redirectBaseUrl = baseUrl || this.defaultConfig.redirectBaseUrl;
|
|
3827
|
+
const redirectUri = `${redirectBaseUrl}/oauth/callback`;
|
|
3828
|
+
let tokens;
|
|
3829
|
+
try {
|
|
3830
|
+
tokens = await this.exchangeWithOpenIdClient({
|
|
3831
|
+
oAuthConfig,
|
|
3832
|
+
clientId,
|
|
3833
|
+
code,
|
|
3834
|
+
codeVerifier,
|
|
3835
|
+
redirectUri
|
|
3836
|
+
});
|
|
3837
|
+
logger4.info({ tokenType: tokens.token_type }, "Token exchange successful with openid-client");
|
|
3838
|
+
} catch (error) {
|
|
3839
|
+
logger4.warn(
|
|
3840
|
+
{ error: error instanceof Error ? error.message : error },
|
|
3841
|
+
"openid-client failed, falling back to manual token exchange"
|
|
3842
|
+
);
|
|
3843
|
+
tokens = await this.exchangeManually({
|
|
3844
|
+
oAuthConfig,
|
|
3845
|
+
clientId,
|
|
3846
|
+
code,
|
|
3847
|
+
codeVerifier,
|
|
3848
|
+
redirectUri
|
|
3849
|
+
});
|
|
3850
|
+
logger4.info({ tokenType: tokens.token_type }, "Manual token exchange successful");
|
|
3851
|
+
}
|
|
3852
|
+
return { tokens, oAuthConfig };
|
|
3853
|
+
}
|
|
3854
|
+
/**
|
|
3855
|
+
* Perform dynamic client registration
|
|
3856
|
+
*/
|
|
3857
|
+
async performDynamicClientRegistration(registrationUrl, redirectUri) {
|
|
3858
|
+
logger4.info({ registrationUrl }, "Attempting dynamic client registration");
|
|
3859
|
+
try {
|
|
3860
|
+
const registrationResponse = await fetch(registrationUrl, {
|
|
3861
|
+
method: "POST",
|
|
3862
|
+
headers: {
|
|
3863
|
+
"Content-Type": "application/json",
|
|
3864
|
+
Accept: "application/json"
|
|
3865
|
+
},
|
|
3866
|
+
body: JSON.stringify({
|
|
3867
|
+
client_name: this.defaultConfig.clientName,
|
|
3868
|
+
client_uri: this.defaultConfig.clientUri,
|
|
3869
|
+
logo_uri: this.defaultConfig.logoUri,
|
|
3870
|
+
redirect_uris: [redirectUri],
|
|
3871
|
+
grant_types: ["authorization_code"],
|
|
3872
|
+
response_types: ["code"],
|
|
3873
|
+
token_endpoint_auth_method: "none",
|
|
3874
|
+
// PKCE only, no client secret
|
|
3875
|
+
application_type: "native"
|
|
3876
|
+
// For PKCE flows
|
|
3877
|
+
})
|
|
3878
|
+
});
|
|
3879
|
+
if (registrationResponse.ok) {
|
|
3880
|
+
const registration = await registrationResponse.json();
|
|
3881
|
+
logger4.info({ clientId: registration.client_id }, "Dynamic client registration successful");
|
|
3882
|
+
return registration.client_id;
|
|
3883
|
+
} else {
|
|
3884
|
+
const errorText = await registrationResponse.text();
|
|
3885
|
+
logger4.warn(
|
|
3886
|
+
{
|
|
3887
|
+
status: registrationResponse.status,
|
|
3888
|
+
errorText
|
|
3889
|
+
},
|
|
3890
|
+
"Dynamic client registration failed, using default client_id"
|
|
3891
|
+
);
|
|
3892
|
+
}
|
|
3893
|
+
} catch (regError) {
|
|
3894
|
+
logger4.warn(
|
|
3895
|
+
{ error: regError },
|
|
3896
|
+
"Dynamic client registration error, using default client_id"
|
|
3897
|
+
);
|
|
3898
|
+
}
|
|
3899
|
+
return this.defaultConfig.defaultClientId;
|
|
3900
|
+
}
|
|
3901
|
+
/**
|
|
3902
|
+
* Build authorization URL
|
|
3903
|
+
*/
|
|
3904
|
+
buildAuthorizationUrl(params) {
|
|
3905
|
+
const { oAuthConfig, clientId, redirectUri, state, codeChallenge, resource } = params;
|
|
3906
|
+
const authUrl = new URL(oAuthConfig.authorizationUrl);
|
|
3907
|
+
authUrl.searchParams.set("response_type", "code");
|
|
3908
|
+
authUrl.searchParams.set("client_id", clientId);
|
|
3909
|
+
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
3910
|
+
authUrl.searchParams.set("state", state);
|
|
3911
|
+
authUrl.searchParams.set("code_challenge", codeChallenge);
|
|
3912
|
+
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
3913
|
+
authUrl.searchParams.set("resource", resource);
|
|
3914
|
+
return authUrl.toString();
|
|
3915
|
+
}
|
|
3916
|
+
/**
|
|
3917
|
+
* Exchange code using openid-client library
|
|
3918
|
+
*/
|
|
3919
|
+
async exchangeWithOpenIdClient(params) {
|
|
3920
|
+
const { oAuthConfig, clientId, code, codeVerifier, redirectUri } = params;
|
|
3921
|
+
const oauth = await import('openid-client');
|
|
3922
|
+
const tokenUrl = new URL(oAuthConfig.tokenUrl);
|
|
3923
|
+
const oauthServerUrl = `${tokenUrl.protocol}//${tokenUrl.host}`;
|
|
3924
|
+
logger4.info({ oauthServerUrl, clientId }, "Attempting openid-client discovery");
|
|
3925
|
+
const config = await oauth.discovery(
|
|
3926
|
+
new URL(oauthServerUrl),
|
|
3927
|
+
clientId,
|
|
3928
|
+
void 0
|
|
3929
|
+
// No client secret for PKCE
|
|
3930
|
+
);
|
|
3931
|
+
const callbackUrl = new URL(
|
|
3932
|
+
`${redirectUri}?${new URLSearchParams({ code, state: "unused" }).toString()}`
|
|
3933
|
+
);
|
|
3934
|
+
return await oauth.authorizationCodeGrant(config, callbackUrl, {
|
|
3935
|
+
pkceCodeVerifier: codeVerifier
|
|
3936
|
+
});
|
|
3937
|
+
}
|
|
3938
|
+
/**
|
|
3939
|
+
* Internal PKCE generation
|
|
3940
|
+
*/
|
|
3941
|
+
async generatePKCEInternal() {
|
|
3942
|
+
const codeVerifier = Buffer.from(
|
|
3943
|
+
Array.from(crypto.getRandomValues(new Uint8Array(32)))
|
|
3944
|
+
).toString("base64url");
|
|
3945
|
+
const encoder = new TextEncoder();
|
|
3946
|
+
const data = encoder.encode(codeVerifier);
|
|
3947
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
3948
|
+
const codeChallenge = Buffer.from(hash).toString("base64url");
|
|
3949
|
+
return { codeVerifier, codeChallenge };
|
|
3950
|
+
}
|
|
3951
|
+
/**
|
|
3952
|
+
* Manual token exchange fallback
|
|
3953
|
+
*/
|
|
3954
|
+
async exchangeManually(params) {
|
|
3955
|
+
const { oAuthConfig, clientId, code, codeVerifier, redirectUri } = params;
|
|
3956
|
+
logger4.info({ tokenUrl: oAuthConfig.tokenUrl }, "Attempting manual token exchange");
|
|
3957
|
+
const tokenResponse = await fetch(oAuthConfig.tokenUrl, {
|
|
3958
|
+
method: "POST",
|
|
3959
|
+
headers: {
|
|
3960
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
3961
|
+
Accept: "application/json"
|
|
3962
|
+
},
|
|
3963
|
+
body: new URLSearchParams({
|
|
3964
|
+
grant_type: "authorization_code",
|
|
3965
|
+
code,
|
|
3966
|
+
redirect_uri: redirectUri,
|
|
3967
|
+
client_id: clientId,
|
|
3968
|
+
code_verifier: codeVerifier
|
|
3969
|
+
// PKCE verification
|
|
3970
|
+
})
|
|
3971
|
+
});
|
|
3972
|
+
if (!tokenResponse.ok) {
|
|
3973
|
+
const errorText = await tokenResponse.text();
|
|
3974
|
+
logger4.error(
|
|
3975
|
+
{
|
|
3976
|
+
status: tokenResponse.status,
|
|
3977
|
+
statusText: tokenResponse.statusText,
|
|
3978
|
+
...process.env.NODE_ENV === "development" && { errorText },
|
|
3979
|
+
clientId,
|
|
3980
|
+
tokenUrl: oAuthConfig.tokenUrl
|
|
3981
|
+
},
|
|
3982
|
+
"Token exchange failed"
|
|
3983
|
+
);
|
|
3984
|
+
throw new Error("Authentication failed. Please try again or contact support.");
|
|
3985
|
+
}
|
|
3986
|
+
return await tokenResponse.json();
|
|
3987
|
+
}
|
|
3988
|
+
};
|
|
3989
|
+
var oauthService = new OAuthService();
|
|
3990
|
+
|
|
3991
|
+
// src/routes/oauth.ts
|
|
3992
|
+
async function findOrCreateCredential(tenantId, projectId, credentialData) {
|
|
3993
|
+
try {
|
|
3994
|
+
const existingCredential = await agentsCore.getCredentialReferenceWithTools(dbClient_default)({
|
|
3995
|
+
scopes: { tenantId, projectId },
|
|
3996
|
+
id: credentialData.id
|
|
3997
|
+
});
|
|
3998
|
+
if (existingCredential) {
|
|
3999
|
+
const validatedCredential = agentsCore.CredentialReferenceApiSelectSchema.parse(existingCredential);
|
|
4000
|
+
return validatedCredential;
|
|
4001
|
+
}
|
|
4002
|
+
} catch {
|
|
4003
|
+
}
|
|
4004
|
+
try {
|
|
4005
|
+
const credential = await agentsCore.createCredentialReference(dbClient_default)({
|
|
4006
|
+
...credentialData,
|
|
4007
|
+
tenantId,
|
|
4008
|
+
projectId
|
|
4009
|
+
});
|
|
4010
|
+
const validatedCredential = agentsCore.CredentialReferenceApiSelectSchema.parse(credential);
|
|
4011
|
+
return validatedCredential;
|
|
4012
|
+
} catch (error) {
|
|
4013
|
+
console.error("Failed to save credential to database:", error);
|
|
4014
|
+
throw new Error(`Failed to save credential '${credentialData.id}' to database`);
|
|
4015
|
+
}
|
|
4016
|
+
}
|
|
4017
|
+
var app17 = new zodOpenapi.OpenAPIHono();
|
|
4018
|
+
var logger5 = agentsCore.getLogger("oauth-callback");
|
|
4019
|
+
function getBaseUrlFromRequest(c) {
|
|
4020
|
+
const url = new URL(c.req.url);
|
|
4021
|
+
return `${url.protocol}//${url.host}`;
|
|
4022
|
+
}
|
|
4023
|
+
var OAuthLoginQuerySchema = zodOpenapi.z.object({
|
|
4024
|
+
tenantId: zodOpenapi.z.string().min(1, "Tenant ID is required"),
|
|
4025
|
+
projectId: zodOpenapi.z.string().min(1, "Project ID is required"),
|
|
4026
|
+
toolId: zodOpenapi.z.string().min(1, "Tool ID is required")
|
|
4027
|
+
});
|
|
4028
|
+
var OAuthCallbackQuerySchema = zodOpenapi.z.object({
|
|
4029
|
+
code: zodOpenapi.z.string().min(1, "Authorization code is required"),
|
|
4030
|
+
state: zodOpenapi.z.string().min(1, "State parameter is required"),
|
|
4031
|
+
error: zodOpenapi.z.string().optional(),
|
|
4032
|
+
error_description: zodOpenapi.z.string().optional()
|
|
4033
|
+
});
|
|
4034
|
+
app17.openapi(
|
|
4035
|
+
zodOpenapi.createRoute({
|
|
4036
|
+
method: "get",
|
|
4037
|
+
path: "/login",
|
|
4038
|
+
summary: "Initiate OAuth login for MCP tool",
|
|
4039
|
+
description: "Detects OAuth requirements and redirects to authorization server (public endpoint)",
|
|
4040
|
+
operationId: "initiate-oauth-login-public",
|
|
4041
|
+
tags: ["OAuth"],
|
|
4042
|
+
request: {
|
|
4043
|
+
query: OAuthLoginQuerySchema
|
|
4044
|
+
},
|
|
4045
|
+
responses: {
|
|
4046
|
+
302: {
|
|
4047
|
+
description: "Redirect to OAuth authorization server"
|
|
4048
|
+
},
|
|
4049
|
+
400: {
|
|
3986
4050
|
description: "OAuth not supported or configuration error",
|
|
3987
4051
|
content: {
|
|
3988
|
-
"
|
|
3989
|
-
schema:
|
|
4052
|
+
"text/html": {
|
|
4053
|
+
schema: zodOpenapi.z.string()
|
|
3990
4054
|
}
|
|
3991
4055
|
}
|
|
3992
4056
|
},
|
|
3993
4057
|
404: {
|
|
3994
4058
|
description: "Tool not found",
|
|
3995
4059
|
content: {
|
|
3996
|
-
"
|
|
3997
|
-
schema:
|
|
4060
|
+
"text/html": {
|
|
4061
|
+
schema: zodOpenapi.z.string()
|
|
3998
4062
|
}
|
|
3999
4063
|
}
|
|
4000
4064
|
},
|
|
4001
4065
|
500: {
|
|
4002
4066
|
description: "Internal server error",
|
|
4003
4067
|
content: {
|
|
4004
|
-
"
|
|
4005
|
-
schema:
|
|
4068
|
+
"text/html": {
|
|
4069
|
+
schema: zodOpenapi.z.string()
|
|
4006
4070
|
}
|
|
4007
4071
|
}
|
|
4008
4072
|
}
|
|
4009
4073
|
}
|
|
4010
4074
|
}),
|
|
4011
4075
|
async (c) => {
|
|
4012
|
-
const { tenantId, projectId,
|
|
4076
|
+
const { tenantId, projectId, toolId } = c.req.valid("query");
|
|
4013
4077
|
try {
|
|
4014
|
-
const tool = await agentsCore.getToolById(dbClient_default)({ scopes: { tenantId, projectId }, toolId
|
|
4078
|
+
const tool = await agentsCore.getToolById(dbClient_default)({ scopes: { tenantId, projectId }, toolId });
|
|
4015
4079
|
if (!tool) {
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
message: "Tool not found"
|
|
4019
|
-
});
|
|
4080
|
+
logger5.error({ toolId, tenantId, projectId }, "Tool not found for OAuth login");
|
|
4081
|
+
return c.text("Tool not found", 404);
|
|
4020
4082
|
}
|
|
4021
4083
|
const credentialStores = c.get("credentialStores");
|
|
4022
4084
|
const mcpTool = await agentsCore.dbResultToMcpTool(tool, dbClient_default, credentialStores);
|
|
4085
|
+
const baseUrl = getBaseUrlFromRequest(c);
|
|
4023
4086
|
const { redirectUrl } = await oauthService.initiateOAuthFlow({
|
|
4024
4087
|
tool: mcpTool,
|
|
4025
4088
|
tenantId,
|
|
4026
4089
|
projectId,
|
|
4027
|
-
toolId
|
|
4090
|
+
toolId,
|
|
4091
|
+
baseUrl
|
|
4028
4092
|
});
|
|
4029
4093
|
return c.redirect(redirectUrl, 302);
|
|
4030
4094
|
} catch (error) {
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
return c.json({ error: apiError.message }, apiError.code === "not_found" ? 404 : 400);
|
|
4035
|
-
}
|
|
4036
|
-
return c.json(
|
|
4037
|
-
{
|
|
4038
|
-
error: "Failed to initiate OAuth login"
|
|
4039
|
-
},
|
|
4040
|
-
500
|
|
4041
|
-
);
|
|
4095
|
+
logger5.error({ toolId, tenantId, projectId, error }, "OAuth login failed");
|
|
4096
|
+
const errorMessage = error instanceof Error ? error.message : "Failed to initiate OAuth login";
|
|
4097
|
+
return c.text(`OAuth Error: ${errorMessage}`, 500);
|
|
4042
4098
|
}
|
|
4043
4099
|
}
|
|
4044
4100
|
);
|
|
4045
|
-
var tools_default = app15;
|
|
4046
|
-
|
|
4047
|
-
// src/routes/index.ts
|
|
4048
|
-
var app16 = new zodOpenapi.OpenAPIHono();
|
|
4049
|
-
app16.route("/projects", projects_default);
|
|
4050
|
-
app16.route("/projects/:projectId/graphs/:graphId/agents", agents_default);
|
|
4051
|
-
app16.route("/projects/:projectId/graphs/:graphId/agent-relations", agentRelations_default);
|
|
4052
|
-
app16.route("/projects/:projectId/agent-graphs", agentGraph_default);
|
|
4053
|
-
app16.route("/projects/:projectId/graphs/:graphId/agent-tool-relations", agentToolRelations_default);
|
|
4054
|
-
app16.route("/projects/:projectId/graphs/:graphId/agent-artifact-components", agentArtifactComponents_default);
|
|
4055
|
-
app16.route("/projects/:projectId/graphs/:graphId/agent-data-components", agentDataComponents_default);
|
|
4056
|
-
app16.route("/projects/:projectId/artifact-components", artifactComponents_default);
|
|
4057
|
-
app16.route("/projects/:projectId/context-configs", contextConfigs_default);
|
|
4058
|
-
app16.route("/projects/:projectId/credentials", credentials_default);
|
|
4059
|
-
app16.route("/projects/:projectId/data-components", dataComponents_default);
|
|
4060
|
-
app16.route("/projects/:projectId/graphs/:graphId/external-agents", externalAgents_default);
|
|
4061
|
-
app16.route("/projects/:projectId/tools", tools_default);
|
|
4062
|
-
app16.route("/projects/:projectId/api-keys", apiKeys_default);
|
|
4063
|
-
app16.route("/projects/:projectId/graph", graphFull_default);
|
|
4064
|
-
var routes_default = app16;
|
|
4065
|
-
var app17 = new zodOpenapi.OpenAPIHono();
|
|
4066
|
-
var logger5 = agentsCore.getLogger("oauth-callback");
|
|
4067
|
-
var OAuthCallbackQuerySchema = zodOpenapi.z.object({
|
|
4068
|
-
code: zodOpenapi.z.string().min(1, "Authorization code is required"),
|
|
4069
|
-
state: zodOpenapi.z.string().min(1, "State parameter is required"),
|
|
4070
|
-
error: zodOpenapi.z.string().optional(),
|
|
4071
|
-
error_description: zodOpenapi.z.string().optional()
|
|
4072
|
-
});
|
|
4073
4101
|
app17.openapi(
|
|
4074
4102
|
zodOpenapi.createRoute({
|
|
4075
4103
|
method: "get",
|
|
@@ -4132,59 +4160,62 @@ app17.openapi(
|
|
|
4132
4160
|
logger5.info({ toolId }, "Exchanging authorization code for access token");
|
|
4133
4161
|
const credentialStores = c.get("credentialStores");
|
|
4134
4162
|
const mcpTool = await agentsCore.dbResultToMcpTool(tool, dbClient_default, credentialStores);
|
|
4163
|
+
const baseUrl = getBaseUrlFromRequest(c);
|
|
4135
4164
|
const { tokens } = await oauthService.exchangeCodeForTokens({
|
|
4136
4165
|
code,
|
|
4137
4166
|
codeVerifier,
|
|
4138
4167
|
clientId,
|
|
4139
|
-
tool: mcpTool
|
|
4168
|
+
tool: mcpTool,
|
|
4169
|
+
baseUrl
|
|
4140
4170
|
});
|
|
4141
4171
|
logger5.info(
|
|
4142
4172
|
{ toolId, tokenType: tokens.token_type, hasRefresh: !!tokens.refresh_token },
|
|
4143
4173
|
"Token exchange successful"
|
|
4144
4174
|
);
|
|
4175
|
+
const credentialTokenKey = `oauth_token_${toolId}`;
|
|
4176
|
+
let newCredentialData;
|
|
4145
4177
|
const keychainStore = credentialStores.get("keychain-default");
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4178
|
+
if (keychainStore) {
|
|
4179
|
+
try {
|
|
4180
|
+
await keychainStore.set(credentialTokenKey, JSON.stringify(tokens));
|
|
4181
|
+
newCredentialData = {
|
|
4182
|
+
id: mcpTool.name,
|
|
4183
|
+
type: agentsCore.CredentialStoreType.keychain,
|
|
4184
|
+
credentialStoreId: "keychain-default",
|
|
4185
|
+
retrievalParams: {
|
|
4186
|
+
key: credentialTokenKey
|
|
4187
|
+
}
|
|
4188
|
+
};
|
|
4189
|
+
} catch {
|
|
4158
4190
|
}
|
|
4159
|
-
};
|
|
4160
|
-
let credential;
|
|
4161
|
-
if (existingCredential) {
|
|
4162
|
-
logger5.info({ credentialId: existingCredential.id }, "Updating existing credential");
|
|
4163
|
-
credential = await agentsCore.updateCredentialReference(dbClient_default)({
|
|
4164
|
-
scopes: { tenantId, projectId },
|
|
4165
|
-
id: existingCredential.id,
|
|
4166
|
-
data: credentialData
|
|
4167
|
-
});
|
|
4168
|
-
} else {
|
|
4169
|
-
logger5.info({ credentialId }, "Creating new credential");
|
|
4170
|
-
credential = await agentsCore.createCredentialReference(dbClient_default)({
|
|
4171
|
-
tenantId,
|
|
4172
|
-
projectId,
|
|
4173
|
-
id: credentialId,
|
|
4174
|
-
...credentialData
|
|
4175
|
-
});
|
|
4176
4191
|
}
|
|
4177
|
-
if (!
|
|
4178
|
-
|
|
4192
|
+
if (!newCredentialData && process.env.NANGO_SECRET_KEY) {
|
|
4193
|
+
const nangoStore = credentialStores.get("nango-default");
|
|
4194
|
+
await nangoStore?.set(credentialTokenKey, JSON.stringify(tokens));
|
|
4195
|
+
newCredentialData = {
|
|
4196
|
+
id: mcpTool.name,
|
|
4197
|
+
type: agentsCore.CredentialStoreType.nango,
|
|
4198
|
+
credentialStoreId: "nango-default",
|
|
4199
|
+
retrievalParams: {
|
|
4200
|
+
connectionId: credentialTokenKey,
|
|
4201
|
+
providerConfigKey: credentialTokenKey,
|
|
4202
|
+
provider: "private-api-bearer",
|
|
4203
|
+
authMode: "API_KEY"
|
|
4204
|
+
}
|
|
4205
|
+
};
|
|
4206
|
+
}
|
|
4207
|
+
if (!newCredentialData) {
|
|
4208
|
+
throw new Error("No credential store found");
|
|
4179
4209
|
}
|
|
4210
|
+
const newCredential = await findOrCreateCredential(tenantId, projectId, newCredentialData);
|
|
4180
4211
|
await agentsCore.updateTool(dbClient_default)({
|
|
4181
4212
|
scopes: { tenantId, projectId },
|
|
4182
4213
|
toolId,
|
|
4183
4214
|
data: {
|
|
4184
|
-
credentialReferenceId:
|
|
4215
|
+
credentialReferenceId: newCredential.id
|
|
4185
4216
|
}
|
|
4186
4217
|
});
|
|
4187
|
-
logger5.info({ toolId, credentialId:
|
|
4218
|
+
logger5.info({ toolId, credentialId: newCredential.id }, "OAuth flow completed successfully");
|
|
4188
4219
|
const successPage = `
|
|
4189
4220
|
<!DOCTYPE html>
|
|
4190
4221
|
<html>
|