@bowenqt/qiniu-ai-sdk 0.10.0 → 0.14.0
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/README.md +225 -0
- package/dist/ai/agent-graph.d.ts +101 -0
- package/dist/ai/agent-graph.d.ts.map +1 -0
- package/dist/ai/agent-graph.js +322 -0
- package/dist/ai/agent-graph.js.map +1 -0
- package/dist/ai/agent-graph.mjs +318 -0
- package/dist/ai/generate-text.d.ts +58 -0
- package/dist/ai/generate-text.d.ts.map +1 -1
- package/dist/ai/generate-text.js +157 -0
- package/dist/ai/generate-text.js.map +1 -1
- package/dist/ai/generate-text.mjs +156 -0
- package/dist/ai/graph/checkpointer.d.ts +112 -0
- package/dist/ai/graph/checkpointer.d.ts.map +1 -0
- package/dist/ai/graph/checkpointer.js +131 -0
- package/dist/ai/graph/checkpointer.js.map +1 -0
- package/dist/ai/graph/checkpointer.mjs +126 -0
- package/dist/ai/graph/index.d.ts +13 -0
- package/dist/ai/graph/index.d.ts.map +1 -0
- package/dist/ai/graph/index.js +22 -0
- package/dist/ai/graph/index.js.map +1 -0
- package/dist/ai/graph/index.mjs +12 -0
- package/dist/ai/graph/postgres-checkpointer.d.ts +54 -0
- package/dist/ai/graph/postgres-checkpointer.d.ts.map +1 -0
- package/dist/ai/graph/postgres-checkpointer.js +134 -0
- package/dist/ai/graph/postgres-checkpointer.js.map +1 -0
- package/dist/ai/graph/postgres-checkpointer.mjs +130 -0
- package/dist/ai/graph/redis-checkpointer.d.ts +51 -0
- package/dist/ai/graph/redis-checkpointer.d.ts.map +1 -0
- package/dist/ai/graph/redis-checkpointer.js +124 -0
- package/dist/ai/graph/redis-checkpointer.js.map +1 -0
- package/dist/ai/graph/redis-checkpointer.mjs +120 -0
- package/dist/ai/graph/state-graph.d.ts +41 -0
- package/dist/ai/graph/state-graph.d.ts.map +1 -0
- package/dist/ai/graph/state-graph.js +149 -0
- package/dist/ai/graph/state-graph.js.map +1 -0
- package/dist/ai/graph/state-graph.mjs +144 -0
- package/dist/ai/graph/types.d.ts +41 -0
- package/dist/ai/graph/types.d.ts.map +1 -0
- package/dist/ai/graph/types.js +10 -0
- package/dist/ai/graph/types.js.map +1 -0
- package/dist/ai/graph/types.mjs +7 -0
- package/dist/ai/internal-types.d.ts +109 -0
- package/dist/ai/internal-types.d.ts.map +1 -0
- package/dist/ai/internal-types.js +28 -0
- package/dist/ai/internal-types.js.map +1 -0
- package/dist/ai/internal-types.mjs +23 -0
- package/dist/ai/nodes/execute-node.d.ts +27 -0
- package/dist/ai/nodes/execute-node.d.ts.map +1 -0
- package/dist/ai/nodes/execute-node.js +118 -0
- package/dist/ai/nodes/execute-node.js.map +1 -0
- package/dist/ai/nodes/execute-node.mjs +114 -0
- package/dist/ai/nodes/index.d.ts +8 -0
- package/dist/ai/nodes/index.d.ts.map +1 -0
- package/dist/ai/nodes/index.js +16 -0
- package/dist/ai/nodes/index.js.map +1 -0
- package/dist/ai/nodes/index.mjs +7 -0
- package/dist/ai/nodes/memory-node.d.ts +34 -0
- package/dist/ai/nodes/memory-node.d.ts.map +1 -0
- package/dist/ai/nodes/memory-node.js +164 -0
- package/dist/ai/nodes/memory-node.js.map +1 -0
- package/dist/ai/nodes/memory-node.mjs +158 -0
- package/dist/ai/nodes/predict-node.d.ts +42 -0
- package/dist/ai/nodes/predict-node.d.ts.map +1 -0
- package/dist/ai/nodes/predict-node.js +89 -0
- package/dist/ai/nodes/predict-node.js.map +1 -0
- package/dist/ai/nodes/predict-node.mjs +86 -0
- package/dist/ai/nodes/types.d.ts +44 -0
- package/dist/ai/nodes/types.d.ts.map +1 -0
- package/dist/ai/nodes/types.js +6 -0
- package/dist/ai/nodes/types.js.map +1 -0
- package/dist/ai/nodes/types.mjs +5 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +80 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +34 -0
- package/dist/lib/otel-tracer.d.ts +47 -0
- package/dist/lib/otel-tracer.d.ts.map +1 -0
- package/dist/lib/otel-tracer.js +79 -0
- package/dist/lib/otel-tracer.js.map +1 -0
- package/dist/lib/otel-tracer.mjs +75 -0
- package/dist/lib/token-estimator.d.ts +62 -0
- package/dist/lib/token-estimator.d.ts.map +1 -0
- package/dist/lib/token-estimator.js +106 -0
- package/dist/lib/token-estimator.js.map +1 -0
- package/dist/lib/token-estimator.mjs +100 -0
- package/dist/lib/tool-registry.d.ts +103 -0
- package/dist/lib/tool-registry.d.ts.map +1 -0
- package/dist/lib/tool-registry.js +159 -0
- package/dist/lib/tool-registry.js.map +1 -0
- package/dist/lib/tool-registry.mjs +154 -0
- package/dist/lib/tracer.d.ts +85 -0
- package/dist/lib/tracer.d.ts.map +1 -0
- package/dist/lib/tracer.js +170 -0
- package/dist/lib/tracer.js.map +1 -0
- package/dist/lib/tracer.mjs +161 -0
- package/dist/lib/types.d.ts +11 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/modules/mcp/adapter.d.ts +23 -0
- package/dist/modules/mcp/adapter.d.ts.map +1 -0
- package/dist/modules/mcp/adapter.js +63 -0
- package/dist/modules/mcp/adapter.js.map +1 -0
- package/dist/modules/mcp/adapter.mjs +58 -0
- package/dist/modules/mcp/client.d.ts +75 -0
- package/dist/modules/mcp/client.d.ts.map +1 -0
- package/dist/modules/mcp/client.js +300 -0
- package/dist/modules/mcp/client.js.map +1 -0
- package/dist/modules/mcp/client.mjs +295 -0
- package/dist/modules/mcp/http-transport.d.ts +51 -0
- package/dist/modules/mcp/http-transport.d.ts.map +1 -0
- package/dist/modules/mcp/http-transport.js +146 -0
- package/dist/modules/mcp/http-transport.js.map +1 -0
- package/dist/modules/mcp/http-transport.mjs +141 -0
- package/dist/modules/mcp/index.d.ts +11 -0
- package/dist/modules/mcp/index.d.ts.map +1 -0
- package/dist/modules/mcp/index.js +34 -0
- package/dist/modules/mcp/index.js.map +1 -0
- package/dist/modules/mcp/index.mjs +14 -0
- package/dist/modules/mcp/oauth.d.ts +101 -0
- package/dist/modules/mcp/oauth.d.ts.map +1 -0
- package/dist/modules/mcp/oauth.js +347 -0
- package/dist/modules/mcp/oauth.js.map +1 -0
- package/dist/modules/mcp/oauth.mjs +304 -0
- package/dist/modules/mcp/token-store.d.ts +69 -0
- package/dist/modules/mcp/token-store.d.ts.map +1 -0
- package/dist/modules/mcp/token-store.js +174 -0
- package/dist/modules/mcp/token-store.js.map +1 -0
- package/dist/modules/mcp/token-store.mjs +135 -0
- package/dist/modules/mcp/types.d.ts +91 -0
- package/dist/modules/mcp/types.d.ts.map +1 -0
- package/dist/modules/mcp/types.js +14 -0
- package/dist/modules/mcp/types.js.map +1 -0
- package/dist/modules/mcp/types.mjs +11 -0
- package/dist/modules/skills/index.d.ts +7 -0
- package/dist/modules/skills/index.d.ts.map +1 -0
- package/dist/modules/skills/index.js +14 -0
- package/dist/modules/skills/index.js.map +1 -0
- package/dist/modules/skills/index.mjs +6 -0
- package/dist/modules/skills/loader.d.ts +51 -0
- package/dist/modules/skills/loader.d.ts.map +1 -0
- package/dist/modules/skills/loader.js +237 -0
- package/dist/modules/skills/loader.js.map +1 -0
- package/dist/modules/skills/loader.mjs +198 -0
- package/dist/modules/skills/types.d.ts +60 -0
- package/dist/modules/skills/types.d.ts.map +1 -0
- package/dist/modules/skills/types.js +20 -0
- package/dist/modules/skills/types.js.map +1 -0
- package/dist/modules/skills/types.mjs +17 -0
- package/package.json +4 -1
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.0 flows for MCP HTTP transport.
|
|
3
|
+
* Implements PKCE (browser) and Device Code (headless) flows.
|
|
4
|
+
*
|
|
5
|
+
* @see https://modelcontextprotocol.io/docs/concepts/authorization
|
|
6
|
+
*/
|
|
7
|
+
import * as crypto from 'node:crypto';
|
|
8
|
+
import * as http from 'node:http';
|
|
9
|
+
/** OAuth error */
|
|
10
|
+
export class OAuthError extends Error {
|
|
11
|
+
constructor(message, code, description) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.description = description;
|
|
15
|
+
this.name = 'OAuthError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Generate PKCE code verifier (43-128 chars).
|
|
20
|
+
*/
|
|
21
|
+
export function generateCodeVerifier() {
|
|
22
|
+
return crypto.randomBytes(32).toString('base64url');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Generate PKCE code challenge (S256).
|
|
26
|
+
*/
|
|
27
|
+
export function generateCodeChallenge(verifier) {
|
|
28
|
+
const hash = crypto.createHash('sha256').update(verifier).digest();
|
|
29
|
+
return hash.toString('base64url');
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Generate random state parameter.
|
|
33
|
+
*/
|
|
34
|
+
export function generateState() {
|
|
35
|
+
return crypto.randomBytes(16).toString('hex');
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* PKCE Authorization Code Flow.
|
|
39
|
+
* Opens a browser for user authentication and starts a local callback server.
|
|
40
|
+
*/
|
|
41
|
+
export class PKCEFlow {
|
|
42
|
+
constructor(config) {
|
|
43
|
+
this.server = null;
|
|
44
|
+
this.codeVerifier = '';
|
|
45
|
+
this.config = config;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Build authorization URL.
|
|
49
|
+
*/
|
|
50
|
+
buildAuthorizationUrl(redirectUri, state) {
|
|
51
|
+
this.codeVerifier = generateCodeVerifier();
|
|
52
|
+
const codeChallenge = generateCodeChallenge(this.codeVerifier);
|
|
53
|
+
const params = new URLSearchParams({
|
|
54
|
+
response_type: 'code',
|
|
55
|
+
client_id: this.config.clientId,
|
|
56
|
+
redirect_uri: redirectUri,
|
|
57
|
+
scope: this.config.scopes.join(' '),
|
|
58
|
+
state,
|
|
59
|
+
code_challenge: codeChallenge,
|
|
60
|
+
code_challenge_method: 'S256',
|
|
61
|
+
});
|
|
62
|
+
const authUrl = this.config.authorizationUrl;
|
|
63
|
+
if (!authUrl) {
|
|
64
|
+
throw new OAuthError('Authorization URL not configured');
|
|
65
|
+
}
|
|
66
|
+
return `${authUrl}?${params.toString()}`;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Start local callback server and wait for authorization code.
|
|
70
|
+
* Returns the authorization code.
|
|
71
|
+
* @param options.port - Port to listen on (default: random)
|
|
72
|
+
* @param options.expectedState - Expected state parameter to validate (required for security)
|
|
73
|
+
* @param options.timeoutMs - Timeout in milliseconds (default: 300000 = 5 minutes)
|
|
74
|
+
*/
|
|
75
|
+
async waitForCallback(options) {
|
|
76
|
+
const { port = 0, expectedState, timeoutMs = 300000 } = options;
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
// Setup timeout
|
|
79
|
+
const timeoutId = setTimeout(() => {
|
|
80
|
+
this.stopServer();
|
|
81
|
+
reject(new OAuthError(`Callback timeout after ${timeoutMs}ms`));
|
|
82
|
+
}, timeoutMs);
|
|
83
|
+
this.server = http.createServer((req, res) => {
|
|
84
|
+
const url = new URL(req.url || '', `http://localhost:${port}`);
|
|
85
|
+
const code = url.searchParams.get('code');
|
|
86
|
+
const state = url.searchParams.get('state');
|
|
87
|
+
const error = url.searchParams.get('error');
|
|
88
|
+
const errorDescription = url.searchParams.get('error_description');
|
|
89
|
+
if (error) {
|
|
90
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
91
|
+
res.end(`<h1>Authorization Failed</h1><p>${errorDescription || error}</p>`);
|
|
92
|
+
clearTimeout(timeoutId);
|
|
93
|
+
this.stopServer();
|
|
94
|
+
reject(new OAuthError(`Authorization failed: ${error}`, error, errorDescription || undefined));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (!code || !state) {
|
|
98
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
99
|
+
res.end('<h1>Missing code or state</h1>');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// Validate state (CSRF protection)
|
|
103
|
+
if (state !== expectedState) {
|
|
104
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
105
|
+
res.end('<h1>Invalid state parameter</h1>');
|
|
106
|
+
clearTimeout(timeoutId);
|
|
107
|
+
this.stopServer();
|
|
108
|
+
reject(new OAuthError('State mismatch: possible CSRF attack', 'state_mismatch'));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
112
|
+
res.end('<h1>Authorization Successful</h1><p>You can close this window.</p>');
|
|
113
|
+
clearTimeout(timeoutId);
|
|
114
|
+
this.stopServer();
|
|
115
|
+
resolve({ code, state });
|
|
116
|
+
});
|
|
117
|
+
this.server.listen(port, '127.0.0.1', () => {
|
|
118
|
+
const addr = this.server?.address();
|
|
119
|
+
if (typeof addr === 'object' && addr) {
|
|
120
|
+
// Server started on addr.port
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
this.server.on('error', (err) => {
|
|
124
|
+
clearTimeout(timeoutId);
|
|
125
|
+
reject(new OAuthError(`Callback server error: ${err.message}`));
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Exchange authorization code for tokens.
|
|
131
|
+
*/
|
|
132
|
+
async exchangeCode(code, redirectUri) {
|
|
133
|
+
const tokenUrl = this.config.tokenUrl;
|
|
134
|
+
if (!tokenUrl) {
|
|
135
|
+
throw new OAuthError('Token URL not configured');
|
|
136
|
+
}
|
|
137
|
+
const body = new URLSearchParams({
|
|
138
|
+
grant_type: 'authorization_code',
|
|
139
|
+
code,
|
|
140
|
+
redirect_uri: redirectUri,
|
|
141
|
+
client_id: this.config.clientId,
|
|
142
|
+
code_verifier: this.codeVerifier,
|
|
143
|
+
});
|
|
144
|
+
if (this.config.clientSecret) {
|
|
145
|
+
body.set('client_secret', this.config.clientSecret);
|
|
146
|
+
}
|
|
147
|
+
const response = await fetch(tokenUrl, {
|
|
148
|
+
method: 'POST',
|
|
149
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
150
|
+
body: body.toString(),
|
|
151
|
+
});
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
const error = await response.json().catch(() => ({}));
|
|
154
|
+
throw new OAuthError(`Token exchange failed: ${response.status}`, error.error, error.error_description);
|
|
155
|
+
}
|
|
156
|
+
const data = await response.json();
|
|
157
|
+
return {
|
|
158
|
+
accessToken: data.access_token,
|
|
159
|
+
refreshToken: data.refresh_token,
|
|
160
|
+
expiresAt: data.expires_in ? Date.now() + data.expires_in * 1000 : undefined,
|
|
161
|
+
tokenType: data.token_type,
|
|
162
|
+
scope: data.scope,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Get the local callback server port.
|
|
167
|
+
*/
|
|
168
|
+
getCallbackPort() {
|
|
169
|
+
const addr = this.server?.address();
|
|
170
|
+
if (typeof addr === 'object' && addr) {
|
|
171
|
+
return addr.port;
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Stop the callback server.
|
|
177
|
+
*/
|
|
178
|
+
stopServer() {
|
|
179
|
+
if (this.server) {
|
|
180
|
+
this.server.close();
|
|
181
|
+
this.server = null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Device Code Flow for headless environments.
|
|
187
|
+
*/
|
|
188
|
+
export class DeviceCodeFlow {
|
|
189
|
+
constructor(config) {
|
|
190
|
+
this.config = config;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Request device code from authorization server.
|
|
194
|
+
*/
|
|
195
|
+
async requestDeviceCode() {
|
|
196
|
+
const deviceCodeUrl = this.config.deviceCodeUrl;
|
|
197
|
+
if (!deviceCodeUrl) {
|
|
198
|
+
throw new OAuthError('Device code URL not configured');
|
|
199
|
+
}
|
|
200
|
+
const body = new URLSearchParams({
|
|
201
|
+
client_id: this.config.clientId,
|
|
202
|
+
scope: this.config.scopes.join(' '),
|
|
203
|
+
});
|
|
204
|
+
const response = await fetch(deviceCodeUrl, {
|
|
205
|
+
method: 'POST',
|
|
206
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
207
|
+
body: body.toString(),
|
|
208
|
+
});
|
|
209
|
+
if (!response.ok) {
|
|
210
|
+
const error = await response.json().catch(() => ({}));
|
|
211
|
+
throw new OAuthError(`Device code request failed: ${response.status}`, error.error, error.error_description);
|
|
212
|
+
}
|
|
213
|
+
const data = await response.json();
|
|
214
|
+
return {
|
|
215
|
+
deviceCode: data.device_code,
|
|
216
|
+
userCode: data.user_code,
|
|
217
|
+
verificationUri: data.verification_uri,
|
|
218
|
+
verificationUriComplete: data.verification_uri_complete,
|
|
219
|
+
expiresIn: data.expires_in,
|
|
220
|
+
interval: data.interval || 5,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Poll for token after user completes authentication.
|
|
225
|
+
*/
|
|
226
|
+
async pollForToken(deviceCode, interval, expiresIn, onPending) {
|
|
227
|
+
const tokenUrl = this.config.tokenUrl;
|
|
228
|
+
if (!tokenUrl) {
|
|
229
|
+
throw new OAuthError('Token URL not configured');
|
|
230
|
+
}
|
|
231
|
+
const deadline = Date.now() + expiresIn * 1000;
|
|
232
|
+
let currentInterval = interval * 1000;
|
|
233
|
+
while (Date.now() < deadline) {
|
|
234
|
+
await new Promise((resolve) => setTimeout(resolve, currentInterval));
|
|
235
|
+
const body = new URLSearchParams({
|
|
236
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
237
|
+
device_code: deviceCode,
|
|
238
|
+
client_id: this.config.clientId,
|
|
239
|
+
});
|
|
240
|
+
const response = await fetch(tokenUrl, {
|
|
241
|
+
method: 'POST',
|
|
242
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
243
|
+
body: body.toString(),
|
|
244
|
+
});
|
|
245
|
+
const data = await response.json();
|
|
246
|
+
if (data.access_token) {
|
|
247
|
+
return {
|
|
248
|
+
accessToken: data.access_token,
|
|
249
|
+
refreshToken: data.refresh_token,
|
|
250
|
+
expiresAt: data.expires_in ? Date.now() + data.expires_in * 1000 : undefined,
|
|
251
|
+
tokenType: data.token_type || 'Bearer',
|
|
252
|
+
scope: data.scope,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
if (data.error === 'authorization_pending') {
|
|
256
|
+
onPending?.();
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (data.error === 'slow_down') {
|
|
260
|
+
currentInterval += 5000; // Add 5 seconds
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (data.error) {
|
|
264
|
+
throw new OAuthError(`Device code polling failed: ${data.error}`, data.error, data.error_description);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
throw new OAuthError('Device code expired');
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Refresh an access token using refresh token.
|
|
272
|
+
*/
|
|
273
|
+
export async function refreshAccessToken(config, refreshToken) {
|
|
274
|
+
const tokenUrl = config.tokenUrl;
|
|
275
|
+
if (!tokenUrl) {
|
|
276
|
+
throw new OAuthError('Token URL not configured');
|
|
277
|
+
}
|
|
278
|
+
const body = new URLSearchParams({
|
|
279
|
+
grant_type: 'refresh_token',
|
|
280
|
+
refresh_token: refreshToken,
|
|
281
|
+
client_id: config.clientId,
|
|
282
|
+
});
|
|
283
|
+
if (config.clientSecret) {
|
|
284
|
+
body.set('client_secret', config.clientSecret);
|
|
285
|
+
}
|
|
286
|
+
const response = await fetch(tokenUrl, {
|
|
287
|
+
method: 'POST',
|
|
288
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
289
|
+
body: body.toString(),
|
|
290
|
+
});
|
|
291
|
+
if (!response.ok) {
|
|
292
|
+
const error = await response.json().catch(() => ({}));
|
|
293
|
+
throw new OAuthError(`Token refresh failed: ${response.status}`, error.error, error.error_description);
|
|
294
|
+
}
|
|
295
|
+
const data = await response.json();
|
|
296
|
+
return {
|
|
297
|
+
accessToken: data.access_token,
|
|
298
|
+
refreshToken: data.refresh_token || refreshToken,
|
|
299
|
+
expiresAt: data.expires_in ? Date.now() + data.expires_in * 1000 : undefined,
|
|
300
|
+
tokenType: data.token_type,
|
|
301
|
+
scope: data.scope,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
//# sourceMappingURL=oauth.js.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token storage for OAuth tokens.
|
|
3
|
+
* Provides memory and file-based implementations.
|
|
4
|
+
*/
|
|
5
|
+
import type { OAuthTokens } from './oauth';
|
|
6
|
+
/**
|
|
7
|
+
* Token store interface.
|
|
8
|
+
*/
|
|
9
|
+
export interface TokenStore {
|
|
10
|
+
/** Get stored tokens for a server */
|
|
11
|
+
get(serverName: string): Promise<OAuthTokens | undefined>;
|
|
12
|
+
/** Store tokens for a server */
|
|
13
|
+
set(serverName: string, tokens: OAuthTokens): Promise<void>;
|
|
14
|
+
/** Delete tokens for a server */
|
|
15
|
+
delete(serverName: string): Promise<void>;
|
|
16
|
+
/** Check if token is expired */
|
|
17
|
+
isExpired(tokens: OAuthTokens): boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* In-memory token store.
|
|
21
|
+
* Tokens are lost when process exits.
|
|
22
|
+
*/
|
|
23
|
+
export declare class MemoryTokenStore implements TokenStore {
|
|
24
|
+
private readonly tokens;
|
|
25
|
+
get(serverName: string): Promise<OAuthTokens | undefined>;
|
|
26
|
+
set(serverName: string, tokens: OAuthTokens): Promise<void>;
|
|
27
|
+
delete(serverName: string): Promise<void>;
|
|
28
|
+
isExpired(tokens: OAuthTokens): boolean;
|
|
29
|
+
}
|
|
30
|
+
/** File token store configuration */
|
|
31
|
+
export interface FileTokenStoreConfig {
|
|
32
|
+
/** Directory to store token files */
|
|
33
|
+
directory: string;
|
|
34
|
+
/** File permissions (default: 0o600) */
|
|
35
|
+
fileMode?: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* File-based token store.
|
|
39
|
+
* Stores tokens as JSON files with restrictive permissions.
|
|
40
|
+
*/
|
|
41
|
+
export declare class FileTokenStore implements TokenStore {
|
|
42
|
+
private readonly config;
|
|
43
|
+
constructor(config: FileTokenStoreConfig);
|
|
44
|
+
private getFilePath;
|
|
45
|
+
get(serverName: string): Promise<OAuthTokens | undefined>;
|
|
46
|
+
set(serverName: string, tokens: OAuthTokens): Promise<void>;
|
|
47
|
+
delete(serverName: string): Promise<void>;
|
|
48
|
+
isExpired(tokens: OAuthTokens): boolean;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Token manager combines store with refresh logic.
|
|
52
|
+
*/
|
|
53
|
+
export declare class TokenManager {
|
|
54
|
+
private readonly store;
|
|
55
|
+
constructor(store: TokenStore);
|
|
56
|
+
/**
|
|
57
|
+
* Get valid access token, refreshing if needed.
|
|
58
|
+
*/
|
|
59
|
+
getAccessToken(serverName: string, refresh?: (refreshToken: string) => Promise<OAuthTokens>): Promise<string | undefined>;
|
|
60
|
+
/**
|
|
61
|
+
* Store new tokens.
|
|
62
|
+
*/
|
|
63
|
+
setTokens(serverName: string, tokens: OAuthTokens): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Clear tokens.
|
|
66
|
+
*/
|
|
67
|
+
clearTokens(serverName: string): Promise<void>;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=token-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../../../src/modules/mcp/token-store.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C;;GAEG;AACH,MAAM,WAAW,UAAU;IACvB,qCAAqC;IACrC,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;IAC1D,gCAAgC;IAChC,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,iCAAiC;IACjC,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,gCAAgC;IAChC,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC;CAC3C;AAED;;;GAGG;AACH,qBAAa,gBAAiB,YAAW,UAAU;IAC/C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IAEnD,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAIzD,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3D,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/C,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO;CAK1C;AAED,qCAAqC;AACrC,MAAM,WAAW,oBAAoB;IACjC,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,qBAAa,cAAe,YAAW,UAAU;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiC;gBAE5C,MAAM,EAAE,oBAAoB;IAOxC,OAAO,CAAC,WAAW;IAMb,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAczD,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAY3D,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY/C,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO;CAK1C;AAED;;GAEG;AACH,qBAAa,YAAY;IACrB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;gBAEvB,KAAK,EAAE,UAAU;IAI7B;;OAEG;IACG,cAAc,CAChB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,GACzD,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IA2B9B;;OAEG;IACG,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvE;;OAEG;IACG,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGvD"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Token storage for OAuth tokens.
|
|
4
|
+
* Provides memory and file-based implementations.
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.TokenManager = exports.FileTokenStore = exports.MemoryTokenStore = void 0;
|
|
41
|
+
const fs = __importStar(require("node:fs/promises"));
|
|
42
|
+
const path = __importStar(require("node:path"));
|
|
43
|
+
/**
|
|
44
|
+
* In-memory token store.
|
|
45
|
+
* Tokens are lost when process exits.
|
|
46
|
+
*/
|
|
47
|
+
class MemoryTokenStore {
|
|
48
|
+
constructor() {
|
|
49
|
+
this.tokens = new Map();
|
|
50
|
+
}
|
|
51
|
+
async get(serverName) {
|
|
52
|
+
return this.tokens.get(serverName);
|
|
53
|
+
}
|
|
54
|
+
async set(serverName, tokens) {
|
|
55
|
+
this.tokens.set(serverName, tokens);
|
|
56
|
+
}
|
|
57
|
+
async delete(serverName) {
|
|
58
|
+
this.tokens.delete(serverName);
|
|
59
|
+
}
|
|
60
|
+
isExpired(tokens) {
|
|
61
|
+
if (!tokens.expiresAt)
|
|
62
|
+
return false;
|
|
63
|
+
// Consider expired 60 seconds before actual expiry
|
|
64
|
+
return Date.now() > tokens.expiresAt - 60000;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
exports.MemoryTokenStore = MemoryTokenStore;
|
|
68
|
+
/**
|
|
69
|
+
* File-based token store.
|
|
70
|
+
* Stores tokens as JSON files with restrictive permissions.
|
|
71
|
+
*/
|
|
72
|
+
class FileTokenStore {
|
|
73
|
+
constructor(config) {
|
|
74
|
+
this.config = {
|
|
75
|
+
directory: config.directory,
|
|
76
|
+
fileMode: config.fileMode ?? 0o600,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
getFilePath(serverName) {
|
|
80
|
+
// Sanitize server name for filename
|
|
81
|
+
const safeName = serverName.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
82
|
+
return path.join(this.config.directory, `${safeName}_tokens.json`);
|
|
83
|
+
}
|
|
84
|
+
async get(serverName) {
|
|
85
|
+
const filePath = this.getFilePath(serverName);
|
|
86
|
+
try {
|
|
87
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
88
|
+
return JSON.parse(content);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (error.code === 'ENOENT') {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async set(serverName, tokens) {
|
|
98
|
+
const filePath = this.getFilePath(serverName);
|
|
99
|
+
// Ensure directory exists
|
|
100
|
+
await fs.mkdir(this.config.directory, { recursive: true, mode: 0o700 });
|
|
101
|
+
// Write file with restrictive permissions
|
|
102
|
+
await fs.writeFile(filePath, JSON.stringify(tokens, null, 2), {
|
|
103
|
+
mode: this.config.fileMode,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
async delete(serverName) {
|
|
107
|
+
const filePath = this.getFilePath(serverName);
|
|
108
|
+
try {
|
|
109
|
+
await fs.unlink(filePath);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (error.code !== 'ENOENT') {
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
isExpired(tokens) {
|
|
118
|
+
if (!tokens.expiresAt)
|
|
119
|
+
return false;
|
|
120
|
+
// Consider expired 60 seconds before actual expiry
|
|
121
|
+
return Date.now() > tokens.expiresAt - 60000;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
exports.FileTokenStore = FileTokenStore;
|
|
125
|
+
/**
|
|
126
|
+
* Token manager combines store with refresh logic.
|
|
127
|
+
*/
|
|
128
|
+
class TokenManager {
|
|
129
|
+
constructor(store) {
|
|
130
|
+
this.store = store;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get valid access token, refreshing if needed.
|
|
134
|
+
*/
|
|
135
|
+
async getAccessToken(serverName, refresh) {
|
|
136
|
+
const tokens = await this.store.get(serverName);
|
|
137
|
+
if (!tokens)
|
|
138
|
+
return undefined;
|
|
139
|
+
// Token is still valid
|
|
140
|
+
if (!this.store.isExpired(tokens)) {
|
|
141
|
+
return tokens.accessToken;
|
|
142
|
+
}
|
|
143
|
+
// Try to refresh
|
|
144
|
+
if (tokens.refreshToken && refresh) {
|
|
145
|
+
try {
|
|
146
|
+
const newTokens = await refresh(tokens.refreshToken);
|
|
147
|
+
await this.store.set(serverName, newTokens);
|
|
148
|
+
return newTokens.accessToken;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Refresh failed, delete tokens
|
|
152
|
+
await this.store.delete(serverName);
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Token expired and can't refresh
|
|
157
|
+
await this.store.delete(serverName);
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Store new tokens.
|
|
162
|
+
*/
|
|
163
|
+
async setTokens(serverName, tokens) {
|
|
164
|
+
await this.store.set(serverName, tokens);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Clear tokens.
|
|
168
|
+
*/
|
|
169
|
+
async clearTokens(serverName) {
|
|
170
|
+
await this.store.delete(serverName);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
exports.TokenManager = TokenManager;
|
|
174
|
+
//# sourceMappingURL=token-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-store.js","sourceRoot":"","sources":["../../../src/modules/mcp/token-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token storage for OAuth tokens.
|
|
3
|
+
* Provides memory and file-based implementations.
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'node:fs/promises';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
/**
|
|
8
|
+
* In-memory token store.
|
|
9
|
+
* Tokens are lost when process exits.
|
|
10
|
+
*/
|
|
11
|
+
export class MemoryTokenStore {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.tokens = new Map();
|
|
14
|
+
}
|
|
15
|
+
async get(serverName) {
|
|
16
|
+
return this.tokens.get(serverName);
|
|
17
|
+
}
|
|
18
|
+
async set(serverName, tokens) {
|
|
19
|
+
this.tokens.set(serverName, tokens);
|
|
20
|
+
}
|
|
21
|
+
async delete(serverName) {
|
|
22
|
+
this.tokens.delete(serverName);
|
|
23
|
+
}
|
|
24
|
+
isExpired(tokens) {
|
|
25
|
+
if (!tokens.expiresAt)
|
|
26
|
+
return false;
|
|
27
|
+
// Consider expired 60 seconds before actual expiry
|
|
28
|
+
return Date.now() > tokens.expiresAt - 60000;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* File-based token store.
|
|
33
|
+
* Stores tokens as JSON files with restrictive permissions.
|
|
34
|
+
*/
|
|
35
|
+
export class FileTokenStore {
|
|
36
|
+
constructor(config) {
|
|
37
|
+
this.config = {
|
|
38
|
+
directory: config.directory,
|
|
39
|
+
fileMode: config.fileMode ?? 0o600,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
getFilePath(serverName) {
|
|
43
|
+
// Sanitize server name for filename
|
|
44
|
+
const safeName = serverName.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
45
|
+
return path.join(this.config.directory, `${safeName}_tokens.json`);
|
|
46
|
+
}
|
|
47
|
+
async get(serverName) {
|
|
48
|
+
const filePath = this.getFilePath(serverName);
|
|
49
|
+
try {
|
|
50
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
51
|
+
return JSON.parse(content);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (error.code === 'ENOENT') {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async set(serverName, tokens) {
|
|
61
|
+
const filePath = this.getFilePath(serverName);
|
|
62
|
+
// Ensure directory exists
|
|
63
|
+
await fs.mkdir(this.config.directory, { recursive: true, mode: 0o700 });
|
|
64
|
+
// Write file with restrictive permissions
|
|
65
|
+
await fs.writeFile(filePath, JSON.stringify(tokens, null, 2), {
|
|
66
|
+
mode: this.config.fileMode,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async delete(serverName) {
|
|
70
|
+
const filePath = this.getFilePath(serverName);
|
|
71
|
+
try {
|
|
72
|
+
await fs.unlink(filePath);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error.code !== 'ENOENT') {
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
isExpired(tokens) {
|
|
81
|
+
if (!tokens.expiresAt)
|
|
82
|
+
return false;
|
|
83
|
+
// Consider expired 60 seconds before actual expiry
|
|
84
|
+
return Date.now() > tokens.expiresAt - 60000;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Token manager combines store with refresh logic.
|
|
89
|
+
*/
|
|
90
|
+
export class TokenManager {
|
|
91
|
+
constructor(store) {
|
|
92
|
+
this.store = store;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get valid access token, refreshing if needed.
|
|
96
|
+
*/
|
|
97
|
+
async getAccessToken(serverName, refresh) {
|
|
98
|
+
const tokens = await this.store.get(serverName);
|
|
99
|
+
if (!tokens)
|
|
100
|
+
return undefined;
|
|
101
|
+
// Token is still valid
|
|
102
|
+
if (!this.store.isExpired(tokens)) {
|
|
103
|
+
return tokens.accessToken;
|
|
104
|
+
}
|
|
105
|
+
// Try to refresh
|
|
106
|
+
if (tokens.refreshToken && refresh) {
|
|
107
|
+
try {
|
|
108
|
+
const newTokens = await refresh(tokens.refreshToken);
|
|
109
|
+
await this.store.set(serverName, newTokens);
|
|
110
|
+
return newTokens.accessToken;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Refresh failed, delete tokens
|
|
114
|
+
await this.store.delete(serverName);
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Token expired and can't refresh
|
|
119
|
+
await this.store.delete(serverName);
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Store new tokens.
|
|
124
|
+
*/
|
|
125
|
+
async setTokens(serverName, tokens) {
|
|
126
|
+
await this.store.set(serverName, tokens);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Clear tokens.
|
|
130
|
+
*/
|
|
131
|
+
async clearTokens(serverName) {
|
|
132
|
+
await this.store.delete(serverName);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=token-store.js.map
|