@agent-deck/backend 1.1.4 → 1.1.6
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/.tsbuildinfo +1 -1
- package/dist/config/oauth-redirect.d.ts +18 -0
- package/dist/config/oauth-redirect.d.ts.map +1 -0
- package/dist/config/oauth-redirect.js +57 -0
- package/dist/config/oauth-redirect.js.map +1 -0
- package/dist/config/shared-oauth-apps.d.ts +18 -0
- package/dist/config/shared-oauth-apps.d.ts.map +1 -0
- package/dist/config/shared-oauth-apps.js +35 -0
- package/dist/config/shared-oauth-apps.js.map +1 -0
- package/dist/data/default-mcp-services.d.ts +14 -0
- package/dist/data/default-mcp-services.d.ts.map +1 -0
- package/dist/data/default-mcp-services.js +52 -0
- package/dist/data/default-mcp-services.js.map +1 -0
- package/dist/data/oauth-provider-guides.d.ts +35 -0
- package/dist/data/oauth-provider-guides.d.ts.map +1 -0
- package/dist/data/oauth-provider-guides.js +298 -0
- package/dist/data/oauth-provider-guides.js.map +1 -0
- package/dist/data/seed-default-services.d.ts +3 -0
- package/dist/data/seed-default-services.d.ts.map +1 -0
- package/dist/data/seed-default-services.js +14 -0
- package/dist/data/seed-default-services.js.map +1 -0
- package/dist/lib/mcp-connection-error.d.ts +4 -0
- package/dist/lib/mcp-connection-error.d.ts.map +1 -0
- package/dist/lib/mcp-connection-error.js +45 -0
- package/dist/lib/mcp-connection-error.js.map +1 -0
- package/dist/lib/mcp-oauth-discovery.d.ts +16 -0
- package/dist/lib/mcp-oauth-discovery.d.ts.map +1 -0
- package/dist/lib/mcp-oauth-discovery.js +161 -0
- package/dist/lib/mcp-oauth-discovery.js.map +1 -0
- package/dist/lib/oauth-token-response.d.ts +11 -0
- package/dist/lib/oauth-token-response.d.ts.map +1 -0
- package/dist/lib/oauth-token-response.js +48 -0
- package/dist/lib/oauth-token-response.js.map +1 -0
- package/dist/lib/pkce.d.ts +7 -0
- package/dist/lib/pkce.d.ts.map +1 -0
- package/dist/lib/pkce.js +11 -0
- package/dist/lib/pkce.js.map +1 -0
- package/dist/mcp-server.d.ts +4 -3
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +128 -32
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp-session-binding.d.ts +29 -0
- package/dist/mcp-session-binding.d.ts.map +1 -0
- package/dist/mcp-session-binding.js +74 -0
- package/dist/mcp-session-binding.js.map +1 -0
- package/dist/models/database.d.ts +1 -0
- package/dist/models/database.d.ts.map +1 -1
- package/dist/models/database.js +4 -0
- package/dist/models/database.js.map +1 -1
- package/dist/routes/oauth.d.ts.map +1 -1
- package/dist/routes/oauth.js +85 -48
- package/dist/routes/oauth.js.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +6 -0
- package/dist/server/index.js.map +1 -1
- package/dist/services/collection-warning-service.d.ts.map +1 -1
- package/dist/services/collection-warning-service.js +1 -5
- package/dist/services/collection-warning-service.js.map +1 -1
- package/dist/services/config-manager.d.ts.map +1 -1
- package/dist/services/config-manager.js +5 -5
- package/dist/services/config-manager.js.map +1 -1
- package/dist/services/mcp-client-manager.d.ts.map +1 -1
- package/dist/services/mcp-client-manager.js +6 -1
- package/dist/services/mcp-client-manager.js.map +1 -1
- package/dist/services/mcp-discovery-service.d.ts +1 -0
- package/dist/services/mcp-discovery-service.d.ts.map +1 -1
- package/dist/services/mcp-discovery-service.js +2 -79
- package/dist/services/mcp-discovery-service.js.map +1 -1
- package/dist/services/oauth-connect-service.d.ts +26 -0
- package/dist/services/oauth-connect-service.d.ts.map +1 -0
- package/dist/services/oauth-connect-service.js +129 -0
- package/dist/services/oauth-connect-service.js.map +1 -0
- package/dist/services/oauth-manager.d.ts.map +1 -1
- package/dist/services/oauth-manager.js +74 -70
- package/dist/services/oauth-manager.js.map +1 -1
- package/dist/services/service-manager.d.ts +2 -0
- package/dist/services/service-manager.d.ts.map +1 -1
- package/dist/services/service-manager.js +22 -3
- package/dist/services/service-manager.js.map +1 -1
- package/package.json +4 -4
- package/static-ui/assets/AgentDeckLogo3-CEDxeHlz.png +0 -0
- package/static-ui/assets/index-CPNBMDJn.js +354 -0
- package/static-ui/assets/index-Df5pC3f-.css +1 -0
- package/static-ui/favicon.png +0 -0
- package/static-ui/index.html +2 -2
- package/static-ui/assets/AgentDeckLogo2-z3pVqJJ3.png +0 -0
- package/static-ui/assets/index-BMpKtwqp.js +0 -339
- package/static-ui/assets/index-BnA3AsqY.css +0 -1
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.discoverMcpOAuthRequirements = discoverMcpOAuthRequirements;
|
|
4
|
+
const oauth_provider_guides_1 = require("../data/oauth-provider-guides");
|
|
5
|
+
function finalizeOAuthDiscovery(serviceUrl, oauth, authServers = []) {
|
|
6
|
+
const provider = oauth.provider ?? (0, oauth_provider_guides_1.inferOAuthProvider)(serviceUrl, authServers);
|
|
7
|
+
const setupMode = (0, oauth_provider_guides_1.resolveOAuthSetupMode)(provider, oauth.supportsDynamicRegistration);
|
|
8
|
+
return {
|
|
9
|
+
...oauth,
|
|
10
|
+
provider,
|
|
11
|
+
setupMode,
|
|
12
|
+
supportsDynamicRegistration: setupMode === 'dynamic' ? true : oauth.supportsDynamicRegistration ?? false,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function protectedResourceCandidates(serviceUrl) {
|
|
16
|
+
const parsed = new URL(serviceUrl);
|
|
17
|
+
const path = parsed.pathname.replace(/\/$/, '');
|
|
18
|
+
const candidates = new Set([
|
|
19
|
+
`${parsed.origin}/.well-known/oauth-protected-resource`,
|
|
20
|
+
]);
|
|
21
|
+
if (path && path !== '/') {
|
|
22
|
+
candidates.add(`${parsed.origin}/.well-known/oauth-protected-resource${path}`);
|
|
23
|
+
candidates.add(`${parsed.origin}/.well-known/oauth-protected-resource${path}/`);
|
|
24
|
+
}
|
|
25
|
+
return [...candidates];
|
|
26
|
+
}
|
|
27
|
+
async function fetchJson(url) {
|
|
28
|
+
try {
|
|
29
|
+
const response = await fetch(url, {
|
|
30
|
+
headers: { Accept: 'application/json' },
|
|
31
|
+
});
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return response.json();
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function fetchAuthorizationServerMetadata(issuerOrAsUrl) {
|
|
42
|
+
const trimmed = issuerOrAsUrl.replace(/\/$/, '');
|
|
43
|
+
const candidates = trimmed.includes('/.well-known/')
|
|
44
|
+
? [trimmed]
|
|
45
|
+
: [
|
|
46
|
+
`${trimmed}/.well-known/oauth-authorization-server`,
|
|
47
|
+
`${trimmed}/.well-known/openid-configuration`,
|
|
48
|
+
];
|
|
49
|
+
for (const candidate of candidates) {
|
|
50
|
+
const data = await fetchJson(candidate);
|
|
51
|
+
if (data && typeof data === 'object') {
|
|
52
|
+
return data;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
function applyKnownProviderFallbacks(serviceUrl, authServers, oauth) {
|
|
58
|
+
if (authServers.some((server) => server.includes('github.com/login/oauth'))) {
|
|
59
|
+
return finalizeOAuthDiscovery(serviceUrl, {
|
|
60
|
+
...oauth,
|
|
61
|
+
required: true,
|
|
62
|
+
provider: 'github',
|
|
63
|
+
authorizationUrl: 'https://github.com/login/oauth/authorize',
|
|
64
|
+
tokenUrl: 'https://github.com/login/oauth/access_token',
|
|
65
|
+
supportsDynamicRegistration: false,
|
|
66
|
+
}, authServers);
|
|
67
|
+
}
|
|
68
|
+
return finalizeOAuthDiscovery(serviceUrl, oauth, authServers);
|
|
69
|
+
}
|
|
70
|
+
async function resolveFromProtectedResource(serviceUrl, metadata) {
|
|
71
|
+
const oauth = {
|
|
72
|
+
required: true,
|
|
73
|
+
resourceName: metadata.resource_name ?? metadata.resource,
|
|
74
|
+
scopesSupported: metadata.scopes_supported ?? [],
|
|
75
|
+
bearerMethodsSupported: metadata.bearer_methods_supported ?? [],
|
|
76
|
+
};
|
|
77
|
+
const authServers = metadata.authorization_servers ?? [];
|
|
78
|
+
if (authServers.length === 0) {
|
|
79
|
+
return finalizeOAuthDiscovery(serviceUrl, oauth, authServers);
|
|
80
|
+
}
|
|
81
|
+
const asMeta = await fetchAuthorizationServerMetadata(authServers[0]);
|
|
82
|
+
if (asMeta) {
|
|
83
|
+
oauth.issuer = asMeta.issuer;
|
|
84
|
+
oauth.authorizationUrl = asMeta.authorization_endpoint;
|
|
85
|
+
oauth.tokenUrl = asMeta.token_endpoint;
|
|
86
|
+
oauth.scopesSupported = asMeta.scopes_supported ?? oauth.scopesSupported;
|
|
87
|
+
oauth.supportsDynamicRegistration = Boolean(asMeta.registration_endpoint);
|
|
88
|
+
}
|
|
89
|
+
return applyKnownProviderFallbacks(serviceUrl, authServers, oauth);
|
|
90
|
+
}
|
|
91
|
+
async function probeWwwAuthenticate(serviceUrl) {
|
|
92
|
+
const headers = {
|
|
93
|
+
Accept: 'application/json, text/event-stream',
|
|
94
|
+
'Content-Type': 'application/json',
|
|
95
|
+
};
|
|
96
|
+
for (const method of ['GET', 'POST']) {
|
|
97
|
+
try {
|
|
98
|
+
const init = method === 'GET'
|
|
99
|
+
? { method, headers: { Accept: headers.Accept } }
|
|
100
|
+
: {
|
|
101
|
+
method,
|
|
102
|
+
headers,
|
|
103
|
+
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} }),
|
|
104
|
+
};
|
|
105
|
+
const response = await fetch(serviceUrl, init);
|
|
106
|
+
const wwwAuth = response.headers.get('www-authenticate');
|
|
107
|
+
if (wwwAuth) {
|
|
108
|
+
return wwwAuth;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// try next method
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
function parseResourceMetadataUrl(wwwAuthenticate) {
|
|
118
|
+
const match = wwwAuthenticate.match(/resource_metadata="([^"]+)"/i);
|
|
119
|
+
return match?.[1] ?? null;
|
|
120
|
+
}
|
|
121
|
+
async function discoverMcpOAuthRequirements(serviceUrl) {
|
|
122
|
+
const oauth = { required: false };
|
|
123
|
+
try {
|
|
124
|
+
for (const candidate of protectedResourceCandidates(serviceUrl)) {
|
|
125
|
+
const data = await fetchJson(candidate);
|
|
126
|
+
if (data && typeof data === 'object') {
|
|
127
|
+
return resolveFromProtectedResource(serviceUrl, data);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const parsed = new URL(serviceUrl);
|
|
131
|
+
const asData = await fetchAuthorizationServerMetadata(parsed.origin);
|
|
132
|
+
if (asData?.authorization_endpoint) {
|
|
133
|
+
return finalizeOAuthDiscovery(serviceUrl, {
|
|
134
|
+
required: true,
|
|
135
|
+
issuer: asData.issuer,
|
|
136
|
+
authorizationUrl: asData.authorization_endpoint,
|
|
137
|
+
tokenUrl: asData.token_endpoint,
|
|
138
|
+
scopesSupported: asData.scopes_supported ?? [],
|
|
139
|
+
supportsDynamicRegistration: Boolean(asData.registration_endpoint),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const wwwAuth = await probeWwwAuthenticate(serviceUrl);
|
|
143
|
+
if (wwwAuth) {
|
|
144
|
+
const resourceMetadataUrl = parseResourceMetadataUrl(wwwAuth);
|
|
145
|
+
if (resourceMetadataUrl) {
|
|
146
|
+
const data = await fetchJson(resourceMetadataUrl);
|
|
147
|
+
if (data && typeof data === 'object') {
|
|
148
|
+
return resolveFromProtectedResource(serviceUrl, data);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (wwwAuth.toLowerCase().includes('oauth') || wwwAuth.toLowerCase().includes('bearer')) {
|
|
152
|
+
return finalizeOAuthDiscovery(serviceUrl, { required: true });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
oauth.error = error instanceof Error ? error.message : 'OAuth detection failed';
|
|
158
|
+
}
|
|
159
|
+
return oauth;
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=mcp-oauth-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-oauth-discovery.js","sourceRoot":"","sources":["../../src/lib/mcp-oauth-discovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type OAuthTokenResponseBody = {
|
|
2
|
+
access_token: string;
|
|
3
|
+
refresh_token?: string;
|
|
4
|
+
expires_in?: number;
|
|
5
|
+
token_type?: string;
|
|
6
|
+
scope?: string;
|
|
7
|
+
};
|
|
8
|
+
/** Parse OAuth token endpoint body (JSON or application/x-www-form-urlencoded). */
|
|
9
|
+
export declare function parseOAuthTokenResponse(response: Response): Promise<OAuthTokenResponseBody>;
|
|
10
|
+
export declare function oauthTokenRequestHeaders(): Record<string, string>;
|
|
11
|
+
//# sourceMappingURL=oauth-token-response.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-token-response.d.ts","sourceRoot":"","sources":["../../src/lib/oauth-token-response.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,sBAAsB,GAAG;IACnC,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,mFAAmF;AACnF,wBAAsB,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,sBAAsB,CAAC,CA0CjG;AAED,wBAAgB,wBAAwB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAKjE"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseOAuthTokenResponse = parseOAuthTokenResponse;
|
|
4
|
+
exports.oauthTokenRequestHeaders = oauthTokenRequestHeaders;
|
|
5
|
+
/** Parse OAuth token endpoint body (JSON or application/x-www-form-urlencoded). */
|
|
6
|
+
async function parseOAuthTokenResponse(response) {
|
|
7
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
8
|
+
const text = await response.text();
|
|
9
|
+
const trimmed = text.trim();
|
|
10
|
+
if (!trimmed) {
|
|
11
|
+
throw new Error('OAuth token response was empty');
|
|
12
|
+
}
|
|
13
|
+
if (contentType.includes('application/json') || trimmed.startsWith('{')) {
|
|
14
|
+
const data = JSON.parse(trimmed);
|
|
15
|
+
if (data.error) {
|
|
16
|
+
throw new Error(`OAuth token error: ${data.error}${data.error_description ? ` — ${data.error_description}` : ''}`);
|
|
17
|
+
}
|
|
18
|
+
if (!data.access_token) {
|
|
19
|
+
throw new Error('OAuth token JSON response missing access_token');
|
|
20
|
+
}
|
|
21
|
+
return data;
|
|
22
|
+
}
|
|
23
|
+
const params = new URLSearchParams(trimmed);
|
|
24
|
+
const oauthError = params.get('error');
|
|
25
|
+
if (oauthError) {
|
|
26
|
+
const description = params.get('error_description');
|
|
27
|
+
throw new Error(`OAuth token error: ${oauthError}${description ? ` — ${description}` : ''}`);
|
|
28
|
+
}
|
|
29
|
+
const accessToken = params.get('access_token');
|
|
30
|
+
if (!accessToken) {
|
|
31
|
+
throw new Error(`OAuth token response missing access_token: ${trimmed.slice(0, 200)}`);
|
|
32
|
+
}
|
|
33
|
+
const expiresIn = params.get('expires_in');
|
|
34
|
+
return {
|
|
35
|
+
access_token: accessToken,
|
|
36
|
+
refresh_token: params.get('refresh_token') ?? undefined,
|
|
37
|
+
expires_in: expiresIn ? Number(expiresIn) : undefined,
|
|
38
|
+
token_type: params.get('token_type') ?? undefined,
|
|
39
|
+
scope: params.get('scope') ?? undefined,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function oauthTokenRequestHeaders() {
|
|
43
|
+
return {
|
|
44
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
45
|
+
Accept: 'application/json',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=oauth-token-response.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-token-response.js","sourceRoot":"","sources":["../../src/lib/oauth-token-response.ts"],"names":[],"mappings":";;AASA,0DA0CC;AAED,4DAKC;AAlDD,mFAAmF;AAC5E,KAAK,UAAU,uBAAuB,CAAC,QAAkB;IAC9D,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;IAC/D,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAE5B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,IAAI,WAAW,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAA4E,CAAC;QAC5G,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACb,sBAAsB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAClG,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;QACpE,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,OAAO,CAAC,CAAC;IAC5C,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACvC,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,sBAAsB,UAAU,GAAG,WAAW,CAAC,CAAC,CAAC,MAAM,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC/F,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC/C,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,8CAA8C,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IACzF,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC3C,OAAO;QACL,YAAY,EAAE,WAAW;QACzB,aAAa,EAAE,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,SAAS;QACvD,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS;QACrD,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,SAAS;QACjD,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,SAAS;KACxC,CAAC;AACJ,CAAC;AAED,SAAgB,wBAAwB;IACtC,OAAO;QACL,cAAc,EAAE,mCAAmC;QACnD,MAAM,EAAE,kBAAkB;KAC3B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../../src/lib/pkce.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,QAAQ;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,8CAA8C;AAC9C,wBAAgB,gBAAgB,IAAI,QAAQ,CAI3C"}
|
package/dist/lib/pkce.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generatePkcePair = generatePkcePair;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
/** RFC 7636 PKCE with S256 code challenge. */
|
|
6
|
+
function generatePkcePair() {
|
|
7
|
+
const codeVerifier = (0, crypto_1.randomBytes)(32).toString('base64url');
|
|
8
|
+
const codeChallenge = (0, crypto_1.createHash)('sha256').update(codeVerifier).digest('base64url');
|
|
9
|
+
return { codeVerifier, codeChallenge };
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=pkce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../src/lib/pkce.ts"],"names":[],"mappings":";;AAQA,4CAIC;AAZD,mCAAiD;AAOjD,8CAA8C;AAC9C,SAAgB,gBAAgB;IAC9B,MAAM,YAAY,GAAG,IAAA,oBAAW,EAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC3D,MAAM,aAAa,GAAG,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IACpF,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,CAAC;AACzC,CAAC"}
|
package/dist/mcp-server.d.ts
CHANGED
|
@@ -6,15 +6,16 @@ export declare class AgentDeckMCPServer {
|
|
|
6
6
|
private sessions;
|
|
7
7
|
/** Set only while registerTools/registerResources run for a new session server. */
|
|
8
8
|
private mcpServerForRegistration;
|
|
9
|
-
/**
|
|
10
|
-
private
|
|
9
|
+
/** Per-session workspace + optional deck override (see mcp-session-binding.ts). */
|
|
10
|
+
private sessionBinding;
|
|
11
11
|
private activeSessionId;
|
|
12
12
|
private get server();
|
|
13
13
|
constructor(port?: number, backendUrl?: string);
|
|
14
14
|
private createMcpServer;
|
|
15
15
|
private getSessionId;
|
|
16
|
-
private getBoundWorkspace;
|
|
17
16
|
private getAgentHeaders;
|
|
17
|
+
private fetchDeck;
|
|
18
|
+
private buildBindingPayload;
|
|
18
19
|
private callBackendAPI;
|
|
19
20
|
private getBoundDeckId;
|
|
20
21
|
private toolResult;
|
package/dist/mcp-server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"AAQA,OAAO,OAA8B,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"AAQA,OAAO,OAA8B,MAAM,SAAS,CAAC;AA0BrD,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,GAAG,CAAsB;IACjC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAiC;IACjD,mFAAmF;IACnF,OAAO,CAAC,wBAAwB,CAAwB;IACxD,mFAAmF;IACnF,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,eAAe,CAAqB;IAE5C,OAAO,KAAK,MAAM,GAKjB;gBAEW,IAAI,GAAE,MAAa,EAAE,UAAU,GAAE,MAAgC;IAc7E,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,eAAe;YAIT,SAAS;YAkBT,mBAAmB;YAYnB,cAAc;YAwCd,cAAc;IAQ5B,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,UAAU;IAm2BlB,OAAO,CAAC,cAAc;IA2LtB,OAAO,CAAC,WAAW;IA2CnB,OAAO,CAAC,kBAAkB;YAKZ,aAAa;YAuDb,uBAAuB;IAiB/B,KAAK;IAmCL,IAAI;CAeX"}
|
package/dist/mcp-server.js
CHANGED
|
@@ -14,8 +14,8 @@ const promises_1 = __importDefault(require("node:fs/promises"));
|
|
|
14
14
|
const node_path_1 = __importDefault(require("node:path"));
|
|
15
15
|
const zod_1 = require("zod");
|
|
16
16
|
const repo_deck_1 = require("./scope/repo-deck");
|
|
17
|
-
const shared_2 = require("@agent-deck/shared");
|
|
18
17
|
const version_1 = require("./lib/version");
|
|
18
|
+
const mcp_session_binding_1 = require("./mcp-session-binding");
|
|
19
19
|
const agentClientHeaders = {
|
|
20
20
|
[shared_1.AGENT_DECK_CLIENT_HEADER]: shared_1.AGENT_DECK_AGENT_CLIENT,
|
|
21
21
|
Accept: 'application/json',
|
|
@@ -27,8 +27,8 @@ class AgentDeckMCPServer {
|
|
|
27
27
|
sessions = new Map();
|
|
28
28
|
/** Set only while registerTools/registerResources run for a new session server. */
|
|
29
29
|
mcpServerForRegistration;
|
|
30
|
-
/**
|
|
31
|
-
|
|
30
|
+
/** Per-session workspace + optional deck override (see mcp-session-binding.ts). */
|
|
31
|
+
sessionBinding;
|
|
32
32
|
activeSessionId;
|
|
33
33
|
get server() {
|
|
34
34
|
if (!this.mcpServerForRegistration) {
|
|
@@ -42,10 +42,10 @@ class AgentDeckMCPServer {
|
|
|
42
42
|
this.app = (0, express_1.default)();
|
|
43
43
|
this.app.use(express_1.default.json());
|
|
44
44
|
this.setupRoutes();
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
45
|
+
this.sessionBinding = new mcp_session_binding_1.McpSessionBindingStore({
|
|
46
|
+
workspace: process.env.AGENT_DECK_WORKSPACE,
|
|
47
|
+
deckId: process.env.AGENT_DECK_DECK_ID,
|
|
48
|
+
});
|
|
49
49
|
}
|
|
50
50
|
createMcpServer() {
|
|
51
51
|
this.mcpServerForRegistration = new mcp_js_1.McpServer({
|
|
@@ -61,15 +61,31 @@ class AgentDeckMCPServer {
|
|
|
61
61
|
getSessionId() {
|
|
62
62
|
return this.activeSessionId ?? 'default';
|
|
63
63
|
}
|
|
64
|
-
getBoundWorkspace() {
|
|
65
|
-
const sessionId = this.getSessionId();
|
|
66
|
-
return this.workspaceBySession.get(sessionId) ?? process.env.AGENT_DECK_WORKSPACE?.trim();
|
|
67
|
-
}
|
|
68
64
|
getAgentHeaders() {
|
|
69
|
-
|
|
65
|
+
return this.sessionBinding.getAgentHeaders(this.getSessionId());
|
|
66
|
+
}
|
|
67
|
+
async fetchDeck(deckId) {
|
|
68
|
+
const deck = await fetch(`${this.backendUrl}/api/decks/${deckId}`, {
|
|
69
|
+
headers: {
|
|
70
|
+
...agentClientHeaders,
|
|
71
|
+
Accept: 'application/json',
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
const body = (await deck.json());
|
|
75
|
+
if (!deck.ok || !body.success || !body.data?.id) {
|
|
76
|
+
throw new Error(body.error ?? `Deck not found: ${deckId}`);
|
|
77
|
+
}
|
|
78
|
+
return body.data;
|
|
79
|
+
}
|
|
80
|
+
async buildBindingPayload(sessionId, manifestDeckId) {
|
|
81
|
+
const snapshot = this.sessionBinding.getBinding(sessionId);
|
|
82
|
+
const deck = await this.callBackendAPI('/api/scope/deck');
|
|
70
83
|
return {
|
|
71
|
-
|
|
72
|
-
|
|
84
|
+
workspaceRoot: snapshot.workspaceRoot,
|
|
85
|
+
deck_id: deck?.id,
|
|
86
|
+
deck_name: deck?.name,
|
|
87
|
+
deck_source: (0, mcp_session_binding_1.resolveDeckBindingSource)(snapshot, manifestDeckId),
|
|
88
|
+
session_deck_override: this.sessionBinding.hasSessionDeckOverride(sessionId),
|
|
73
89
|
};
|
|
74
90
|
}
|
|
75
91
|
async callBackendAPI(endpoint, init = {}) {
|
|
@@ -116,7 +132,7 @@ class AgentDeckMCPServer {
|
|
|
116
132
|
async getBoundDeckId() {
|
|
117
133
|
const deck = await this.callBackendAPI('/api/scope/deck');
|
|
118
134
|
if (!deck?.id) {
|
|
119
|
-
throw new Error('No bound deck — call bind_workspace or
|
|
135
|
+
throw new Error('No bound deck — call bind_workspace (optionally with deckId) or switch_bound_deck first');
|
|
120
136
|
}
|
|
121
137
|
return deck.id;
|
|
122
138
|
}
|
|
@@ -129,12 +145,31 @@ class AgentDeckMCPServer {
|
|
|
129
145
|
setupTools() {
|
|
130
146
|
this.server.registerTool("bind_workspace", {
|
|
131
147
|
title: "Bind Workspace",
|
|
132
|
-
description: "Bind
|
|
133
|
-
inputSchema: {
|
|
134
|
-
|
|
148
|
+
description: "Bind this MCP session to a workspace root. Pass deckId for a session-only deck override (same path, different concurrent agents). Without deckId, uses .agent-deck/deck.yaml when present.",
|
|
149
|
+
inputSchema: {
|
|
150
|
+
workspaceRoot: zod_1.z.string(),
|
|
151
|
+
deckId: zod_1.z.string().uuid().optional(),
|
|
152
|
+
},
|
|
153
|
+
}, async ({ workspaceRoot, deckId }) => {
|
|
135
154
|
try {
|
|
136
155
|
const sessionId = this.getSessionId();
|
|
137
|
-
this.
|
|
156
|
+
this.sessionBinding.setWorkspace(sessionId, workspaceRoot);
|
|
157
|
+
if (deckId) {
|
|
158
|
+
const deck = await this.fetchDeck(deckId);
|
|
159
|
+
this.sessionBinding.setDeckId(sessionId, deckId);
|
|
160
|
+
const payload = await this.buildBindingPayload(sessionId);
|
|
161
|
+
return {
|
|
162
|
+
content: [{
|
|
163
|
+
type: "text",
|
|
164
|
+
text: JSON.stringify({
|
|
165
|
+
...payload,
|
|
166
|
+
deck_name: deck.name,
|
|
167
|
+
message: 'Session bound with deck override. .agent-deck/deck.yaml is unchanged; other MCP sessions can use a different deck at the same path.',
|
|
168
|
+
}, null, 2),
|
|
169
|
+
}],
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
this.sessionBinding.clearDeckId(sessionId);
|
|
138
173
|
const resolved = await fetch(`${this.backendUrl}/api/scope/resolve`, {
|
|
139
174
|
method: 'POST',
|
|
140
175
|
headers: { ...agentClientHeaders, 'Content-Type': 'application/json' },
|
|
@@ -142,31 +177,90 @@ class AgentDeckMCPServer {
|
|
|
142
177
|
});
|
|
143
178
|
const body = (await resolved.json());
|
|
144
179
|
if (!body.success || !body.data) {
|
|
145
|
-
throw new Error(body.error ??
|
|
180
|
+
throw new Error(body.error ??
|
|
181
|
+
'Failed to resolve workspace deck. Pass deckId to bind without deck.yaml, or call setup_repo_deck.');
|
|
146
182
|
}
|
|
147
183
|
const { data } = body;
|
|
184
|
+
const payload = await this.buildBindingPayload(sessionId, data.manifest.deck_id);
|
|
148
185
|
return {
|
|
149
186
|
content: [{
|
|
150
187
|
type: "text",
|
|
151
188
|
text: JSON.stringify({
|
|
152
|
-
|
|
189
|
+
...payload,
|
|
153
190
|
manifestPath: data.manifestPath,
|
|
154
|
-
|
|
155
|
-
deck_name: data.deck.name,
|
|
191
|
+
message: 'Session bound via .agent-deck/deck.yaml.',
|
|
156
192
|
}, null, 2),
|
|
157
193
|
}],
|
|
158
194
|
};
|
|
159
195
|
}
|
|
160
196
|
catch (error) {
|
|
161
197
|
const message = String(error);
|
|
162
|
-
const hint = message.includes('deck.yaml')
|
|
163
|
-
? '
|
|
198
|
+
const hint = message.includes('deck.yaml') || message.includes('No .agent-deck')
|
|
199
|
+
? ' Pass deckId on bind_workspace, call setup_repo_deck, or get_repo_deck_status to diagnose.'
|
|
164
200
|
: '';
|
|
165
201
|
return {
|
|
166
202
|
content: [{ type: "text", text: JSON.stringify({ error: message + hint }, null, 2) }],
|
|
167
203
|
};
|
|
168
204
|
}
|
|
169
205
|
});
|
|
206
|
+
this.server.registerTool("switch_bound_deck", {
|
|
207
|
+
title: "Switch Bound Deck",
|
|
208
|
+
description: "Switch the deck for this MCP session only. Does not modify .agent-deck/deck.yaml. Use when multiple agent sessions share the same workspace path.",
|
|
209
|
+
inputSchema: { deckId: zod_1.z.string().uuid() },
|
|
210
|
+
}, async ({ deckId }) => {
|
|
211
|
+
try {
|
|
212
|
+
const sessionId = this.getSessionId();
|
|
213
|
+
const deck = await this.fetchDeck(deckId);
|
|
214
|
+
this.sessionBinding.setDeckId(sessionId, deckId);
|
|
215
|
+
const payload = await this.buildBindingPayload(sessionId);
|
|
216
|
+
return {
|
|
217
|
+
content: [{
|
|
218
|
+
type: "text",
|
|
219
|
+
text: JSON.stringify({
|
|
220
|
+
...payload,
|
|
221
|
+
deck_name: deck.name,
|
|
222
|
+
message: 'Session deck switched. Other sessions and deck.yaml are unchanged.',
|
|
223
|
+
}, null, 2),
|
|
224
|
+
}],
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
return this.toolError(error);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
this.server.registerTool("get_session_binding", {
|
|
232
|
+
title: "Get Session Binding",
|
|
233
|
+
description: "Show workspace and effective deck for this MCP session, including whether the deck comes from a session override or repo deck.yaml.",
|
|
234
|
+
inputSchema: {},
|
|
235
|
+
}, async () => {
|
|
236
|
+
try {
|
|
237
|
+
const sessionId = this.getSessionId();
|
|
238
|
+
const snapshot = this.sessionBinding.getBinding(sessionId);
|
|
239
|
+
let manifestDeckId;
|
|
240
|
+
if (snapshot.workspaceRoot && !snapshot.deckSource) {
|
|
241
|
+
const manifest = await (0, repo_deck_1.loadRepoDeckManifest)(snapshot.workspaceRoot);
|
|
242
|
+
manifestDeckId = manifest?.deck_id;
|
|
243
|
+
}
|
|
244
|
+
const deck = await this.callBackendAPI('/api/scope/deck');
|
|
245
|
+
return {
|
|
246
|
+
content: [{
|
|
247
|
+
type: "text",
|
|
248
|
+
text: JSON.stringify({
|
|
249
|
+
workspaceRoot: snapshot.workspaceRoot,
|
|
250
|
+
session_deck_override: snapshot.deckId,
|
|
251
|
+
session_deck_source: snapshot.deckSource,
|
|
252
|
+
repo_manifest_deck_id: manifestDeckId,
|
|
253
|
+
effective_deck_id: deck?.id,
|
|
254
|
+
effective_deck_name: deck?.name,
|
|
255
|
+
effective_deck_source: (0, mcp_session_binding_1.resolveDeckBindingSource)(snapshot, manifestDeckId),
|
|
256
|
+
}, null, 2),
|
|
257
|
+
}],
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
return this.toolError(error);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
170
264
|
this.server.registerTool("get_repo_deck_status", {
|
|
171
265
|
title: "Get Repo Deck Status",
|
|
172
266
|
description: "Check whether .agent-deck/deck.yaml exists in a workspace and whether it links to a valid deck",
|
|
@@ -183,7 +277,7 @@ class AgentDeckMCPServer {
|
|
|
183
277
|
text: JSON.stringify({
|
|
184
278
|
status: 'missing',
|
|
185
279
|
workspaceRoot,
|
|
186
|
-
expectedPath:
|
|
280
|
+
expectedPath: shared_1.REPO_DECK_MANIFEST_PATH,
|
|
187
281
|
message: 'No .agent-deck/deck.yaml found. Use setup_repo_deck to create one.',
|
|
188
282
|
availableDecks: Array.isArray(decks)
|
|
189
283
|
? decks.map((d) => ({ id: d.id, name: d.name }))
|
|
@@ -232,7 +326,7 @@ class AgentDeckMCPServer {
|
|
|
232
326
|
const sessionId = this.getSessionId();
|
|
233
327
|
const existing = await (0, repo_deck_1.loadRepoDeckManifest)(workspaceRoot);
|
|
234
328
|
if (existing) {
|
|
235
|
-
this.
|
|
329
|
+
this.sessionBinding.setWorkspace(sessionId, workspaceRoot);
|
|
236
330
|
const resolved = await fetch(`${this.backendUrl}/api/scope/resolve`, {
|
|
237
331
|
method: 'POST',
|
|
238
332
|
headers: { ...agentClientHeaders, 'Content-Type': 'application/json' },
|
|
@@ -264,7 +358,7 @@ class AgentDeckMCPServer {
|
|
|
264
358
|
text: JSON.stringify({
|
|
265
359
|
status: 'needs_deck_id',
|
|
266
360
|
workspaceRoot,
|
|
267
|
-
expectedPath:
|
|
361
|
+
expectedPath: shared_1.REPO_DECK_MANIFEST_PATH,
|
|
268
362
|
message: 'Pick a deck id from the dashboard (My Decks → copy icon) and call setup_repo_deck again with deckId.',
|
|
269
363
|
availableDecks: Array.isArray(decks)
|
|
270
364
|
? decks.map((d) => ({ id: d.id, name: d.name }))
|
|
@@ -284,7 +378,7 @@ class AgentDeckMCPServer {
|
|
|
284
378
|
await promises_1.default.mkdir(dirPath, { recursive: true });
|
|
285
379
|
await promises_1.default.writeFile(filePath, content, 'utf8');
|
|
286
380
|
}
|
|
287
|
-
this.
|
|
381
|
+
this.sessionBinding.setWorkspace(sessionId, workspaceRoot);
|
|
288
382
|
return {
|
|
289
383
|
content: [{
|
|
290
384
|
type: "text",
|
|
@@ -328,7 +422,7 @@ class AgentDeckMCPServer {
|
|
|
328
422
|
});
|
|
329
423
|
this.server.registerTool("get_bound_deck", {
|
|
330
424
|
title: "Get Bound Deck",
|
|
331
|
-
description: "Get the deck bound to this session
|
|
425
|
+
description: "Get the deck bound to this session (session deckId override, env, or .agent-deck/deck.yaml)",
|
|
332
426
|
inputSchema: {},
|
|
333
427
|
}, async () => {
|
|
334
428
|
try {
|
|
@@ -1121,7 +1215,7 @@ class AgentDeckMCPServer {
|
|
|
1121
1215
|
const sessionId = transport.sessionId;
|
|
1122
1216
|
if (sessionId) {
|
|
1123
1217
|
this.sessions.delete(sessionId);
|
|
1124
|
-
this.
|
|
1218
|
+
this.sessionBinding.clearSession(sessionId);
|
|
1125
1219
|
}
|
|
1126
1220
|
};
|
|
1127
1221
|
await server.connect(transport);
|
|
@@ -1152,7 +1246,9 @@ class AgentDeckMCPServer {
|
|
|
1152
1246
|
this.app.listen(this.port, () => {
|
|
1153
1247
|
console.log(`✅ Agent Deck MCP Server is ready to accept connections`);
|
|
1154
1248
|
console.log(`📋 Available tools:`);
|
|
1155
|
-
console.log(` - bind_workspace: Bind session to
|
|
1249
|
+
console.log(` - bind_workspace: Bind session to workspace; optional deckId override`);
|
|
1250
|
+
console.log(` - switch_bound_deck: Switch deck for this session only`);
|
|
1251
|
+
console.log(` - get_session_binding: Show session workspace + effective deck`);
|
|
1156
1252
|
console.log(` - get_repo_deck_status: Check repo deck manifest`);
|
|
1157
1253
|
console.log(` - setup_repo_deck: Create .agent-deck/deck.yaml for a repo`);
|
|
1158
1254
|
console.log(` - get_bound_deck: Get workspace-bound deck`);
|