@phi-code-admin/phi-code 0.86.0 → 0.88.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/CHANGELOG.md +104 -0
- package/docs/adr/0001-phase-contract.md +57 -0
- package/docs/adr/0002-independent-review.md +47 -0
- package/docs/fork-policy.md +18 -0
- package/extensions/phi/agents.ts +7 -4
- package/extensions/phi/benchmark.ts +129 -49
- package/extensions/phi/browser.ts +10 -37
- package/extensions/phi/btw/btw.ts +1 -7
- package/extensions/phi/chrome/index.ts +1283 -741
- package/extensions/phi/commit.ts +9 -14
- package/extensions/phi/goal/index.ts +10 -33
- package/extensions/phi/init.ts +37 -47
- package/extensions/phi/keys.ts +2 -7
- package/extensions/phi/mcp/callback-server.ts +162 -165
- package/extensions/phi/mcp/config.ts +122 -136
- package/extensions/phi/mcp/errors.ts +18 -23
- package/extensions/phi/mcp/index.ts +322 -355
- package/extensions/phi/mcp/oauth-provider.ts +289 -289
- package/extensions/phi/mcp/server-manager.ts +390 -413
- package/extensions/phi/mcp/tool-bridge.ts +381 -415
- package/extensions/phi/memory.ts +2 -2
- package/extensions/phi/models.ts +27 -26
- package/extensions/phi/orchestrator.ts +451 -237
- package/extensions/phi/productivity.ts +4 -2
- package/extensions/phi/providers/agent-def.ts +1 -1
- package/extensions/phi/providers/alibaba.ts +56 -7
- package/extensions/phi/providers/context-window.ts +4 -1
- package/extensions/phi/providers/live-models.ts +5 -20
- package/extensions/phi/providers/orchestrator-helpers.ts +31 -5
- package/extensions/phi/providers/phase-machine.ts +283 -0
- package/extensions/phi/setup.ts +196 -169
- package/extensions/phi/skill-loader.ts +14 -17
- package/extensions/phi/smart-router.ts +6 -6
- package/extensions/phi/web-search.ts +90 -50
- package/package.json +1 -1
|
@@ -11,36 +11,40 @@
|
|
|
11
11
|
* constructs an OAuthClientProvider and the transport factory wires it in.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import type { OAuthClientProvider, OAuthDiscoveryState } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
15
|
-
import type { OAuthClientMetadata, OAuthClientInformationMixed, OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
16
|
-
import { readFile, writeFile, mkdir, unlink } from "node:fs/promises";
|
|
17
|
-
import { join } from "node:path";
|
|
18
|
-
import { homedir } from "node:os";
|
|
19
14
|
import { createHash } from "node:crypto";
|
|
15
|
+
import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
16
|
+
import { homedir } from "node:os";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import type { OAuthClientProvider, OAuthDiscoveryState } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
19
|
+
import type {
|
|
20
|
+
OAuthClientInformationMixed,
|
|
21
|
+
OAuthClientMetadata,
|
|
22
|
+
OAuthTokens,
|
|
23
|
+
} from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
20
24
|
|
|
21
25
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
22
26
|
|
|
23
27
|
/** Auth config in mcp.json server entry. Matches the Zod AuthConfigSchema in config.ts. */
|
|
24
28
|
export interface AuthConfig {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
29
|
+
/** Auth type. Currently only "oauth" is supported. */
|
|
30
|
+
type?: "oauth" | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Callback URL the OAuth server redirects to after authorization.
|
|
33
|
+
* Default: auto-detected local callback server.
|
|
34
|
+
*/
|
|
35
|
+
redirectUrl?: string | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Optional scope to request during authorization.
|
|
38
|
+
*/
|
|
39
|
+
scope?: string | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Pre-registered client_id (skip dynamic client registration).
|
|
42
|
+
*/
|
|
43
|
+
clientId?: string | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Pre-registered client_secret.
|
|
46
|
+
*/
|
|
47
|
+
clientSecret?: string | undefined;
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
// ─── Callback Server Port ───────────────────────────────────────────────────────
|
|
@@ -49,36 +53,36 @@ let callbackPort = 19876;
|
|
|
49
53
|
|
|
50
54
|
/** Set the callback server port. Called by the auth flow when the server starts. */
|
|
51
55
|
export function setCallbackPort(port: number): void {
|
|
52
|
-
|
|
56
|
+
callbackPort = port;
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
/** Get the current callback server port. */
|
|
56
60
|
export function getCallbackPort(): number {
|
|
57
|
-
|
|
61
|
+
return callbackPort;
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
// ─── Persistent State Types ───────────────────────────────────────────────────
|
|
61
65
|
|
|
62
66
|
interface StoredClientInfo {
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
client_id: string;
|
|
68
|
+
client_secret: string | undefined;
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
interface StoredTokens {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
access_token: string;
|
|
73
|
+
token_type: string | undefined;
|
|
74
|
+
refresh_token: string | undefined;
|
|
75
|
+
expires_in: number | undefined;
|
|
76
|
+
scope: string | undefined;
|
|
77
|
+
/** ISO timestamp when tokens were saved (for expiry estimation). */
|
|
78
|
+
saved_at: string | undefined;
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
interface StoredState {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
clientInfo: StoredClientInfo | undefined;
|
|
83
|
+
tokens: StoredTokens | undefined;
|
|
84
|
+
codeVerifier: string | undefined;
|
|
85
|
+
discoveryState: OAuthDiscoveryState | undefined;
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
// ─── File helpers ─────────────────────────────────────────────────────────────
|
|
@@ -86,247 +90,243 @@ interface StoredState {
|
|
|
86
90
|
const AUTH_DIR = join(homedir(), ".pi", "agent", "mcp-auth");
|
|
87
91
|
|
|
88
92
|
function statePath(serverName: string): string {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
93
|
+
// Hash the server name to avoid filesystem issues with special chars
|
|
94
|
+
const hash = createHash("sha256").update(serverName).digest("hex").slice(0, 16);
|
|
95
|
+
return join(AUTH_DIR, `${hash}.json`);
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
async function loadState(serverName: string): Promise<StoredState> {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
99
|
+
try {
|
|
100
|
+
const raw = await readFile(statePath(serverName), "utf8");
|
|
101
|
+
const parsed = JSON.parse(raw) as StoredState;
|
|
102
|
+
return {
|
|
103
|
+
clientInfo: parsed.clientInfo ?? undefined,
|
|
104
|
+
tokens: parsed.tokens ?? undefined,
|
|
105
|
+
codeVerifier: parsed.codeVerifier ?? undefined,
|
|
106
|
+
discoveryState: parsed.discoveryState ?? undefined,
|
|
107
|
+
};
|
|
108
|
+
} catch {
|
|
109
|
+
return {
|
|
110
|
+
clientInfo: undefined,
|
|
111
|
+
tokens: undefined,
|
|
112
|
+
codeVerifier: undefined,
|
|
113
|
+
discoveryState: undefined,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
async function saveState(serverName: string, state: StoredState): Promise<void> {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
119
|
+
await mkdir(AUTH_DIR, { recursive: true });
|
|
120
|
+
// Only write defined fields
|
|
121
|
+
const toWrite: Record<string, unknown> = {};
|
|
122
|
+
if (state.clientInfo !== undefined) toWrite.clientInfo = state.clientInfo;
|
|
123
|
+
if (state.tokens !== undefined) toWrite.tokens = state.tokens;
|
|
124
|
+
if (state.codeVerifier !== undefined) toWrite.codeVerifier = state.codeVerifier;
|
|
125
|
+
if (state.discoveryState !== undefined) toWrite.discoveryState = state.discoveryState;
|
|
126
|
+
await writeFile(statePath(serverName), JSON.stringify(toWrite, null, 2), "utf8");
|
|
123
127
|
}
|
|
124
128
|
|
|
125
129
|
// ─── OAuthClientProvider Implementation ────────────────────────────────────────
|
|
126
130
|
|
|
127
131
|
export class McpOAuthProvider implements OAuthClientProvider {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
break;
|
|
327
|
-
}
|
|
328
|
-
await saveState(this.serverName, state);
|
|
329
|
-
}
|
|
132
|
+
private serverName: string;
|
|
133
|
+
private authConfig: AuthConfig;
|
|
134
|
+
private _redirectUrl: string | undefined;
|
|
135
|
+
private _onAuthRequired: ((url: URL) => void | Promise<void>) | undefined;
|
|
136
|
+
private _oauthState: string | undefined;
|
|
137
|
+
|
|
138
|
+
constructor(serverName: string, authConfig: AuthConfig, onAuthRequired?: (url: URL) => void | Promise<void>) {
|
|
139
|
+
this.serverName = serverName;
|
|
140
|
+
this.authConfig = authConfig;
|
|
141
|
+
this._redirectUrl = authConfig.redirectUrl;
|
|
142
|
+
this._onAuthRequired = onAuthRequired;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// --- redirectUrl ---
|
|
146
|
+
|
|
147
|
+
get redirectUrl(): string | URL {
|
|
148
|
+
// Use configured redirect URL if provided, otherwise use the callback server
|
|
149
|
+
// Use 127.0.0.1 explicitly (IPv4) to match the callback server binding
|
|
150
|
+
return this._redirectUrl || `http://127.0.0.1:${callbackPort}/callback`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// --- clientMetadata ---
|
|
154
|
+
|
|
155
|
+
get clientMetadata(): OAuthClientMetadata {
|
|
156
|
+
const redirectUrl = String(this.redirectUrl);
|
|
157
|
+
return {
|
|
158
|
+
client_name: `pi-mcp/${this.serverName}`,
|
|
159
|
+
redirect_uris: [redirectUrl],
|
|
160
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
161
|
+
response_types: ["code"],
|
|
162
|
+
token_endpoint_auth_method: this.authConfig.clientSecret ? "client_secret_basic" : "none",
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// --- clientInformation ---
|
|
167
|
+
|
|
168
|
+
async clientInformation(): Promise<OAuthClientInformationMixed | undefined> {
|
|
169
|
+
// If static credentials provided, use those
|
|
170
|
+
if (this.authConfig.clientId) {
|
|
171
|
+
return {
|
|
172
|
+
client_id: this.authConfig.clientId,
|
|
173
|
+
...(this.authConfig.clientSecret && { client_secret: this.authConfig.clientSecret }),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
// Otherwise load from persisted DCR state
|
|
177
|
+
const state = await loadState(this.serverName);
|
|
178
|
+
if (state.clientInfo) {
|
|
179
|
+
return {
|
|
180
|
+
client_id: state.clientInfo.client_id,
|
|
181
|
+
...(state.clientInfo.client_secret && { client_secret: state.clientInfo.client_secret }),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// --- saveClientInformation (DCR) ---
|
|
188
|
+
|
|
189
|
+
async saveClientInformation(clientInformation: OAuthClientInformationMixed): Promise<void> {
|
|
190
|
+
const state = await loadState(this.serverName);
|
|
191
|
+
state.clientInfo = {
|
|
192
|
+
client_id: clientInformation.client_id,
|
|
193
|
+
client_secret: clientInformation.client_secret,
|
|
194
|
+
};
|
|
195
|
+
await saveState(this.serverName, state);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// --- tokens ---
|
|
199
|
+
|
|
200
|
+
async tokens(): Promise<OAuthTokens | undefined> {
|
|
201
|
+
const state = await loadState(this.serverName);
|
|
202
|
+
if (!state.tokens) return undefined;
|
|
203
|
+
|
|
204
|
+
// Always return stored tokens — even if expired.
|
|
205
|
+
// The SDK's auth() function checks tokens?.refresh_token and attempts
|
|
206
|
+
// silent refresh before falling back to a new authorization flow.
|
|
207
|
+
// Returning undefined for expired tokens would prevent that silent refresh
|
|
208
|
+
// and force the user to re-authenticate via browser every time.
|
|
209
|
+
//
|
|
210
|
+
// Flow when we return expired tokens:
|
|
211
|
+
// transport sends expired access_token → 401
|
|
212
|
+
// → auth() sees refresh_token → silent refresh → success → retry
|
|
213
|
+
//
|
|
214
|
+
// Flow when we return undefined (WRONG):
|
|
215
|
+
// transport has no token → auth() → no refresh possible → REDIRECT
|
|
216
|
+
// → user must re-authenticate in browser
|
|
217
|
+
|
|
218
|
+
// Build OAuthTokens — only include defined fields
|
|
219
|
+
const tokens: Record<string, string> = {
|
|
220
|
+
access_token: state.tokens.access_token,
|
|
221
|
+
};
|
|
222
|
+
if (state.tokens.token_type !== undefined) tokens.token_type = state.tokens.token_type;
|
|
223
|
+
if (state.tokens.refresh_token !== undefined) tokens.refresh_token = state.tokens.refresh_token;
|
|
224
|
+
return tokens as unknown as OAuthTokens;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// --- saveTokens ---
|
|
228
|
+
|
|
229
|
+
async saveTokens(tokens: OAuthTokens): Promise<void> {
|
|
230
|
+
const state = await loadState(this.serverName);
|
|
231
|
+
state.tokens = {
|
|
232
|
+
access_token: tokens.access_token,
|
|
233
|
+
token_type: tokens.token_type,
|
|
234
|
+
refresh_token: tokens.refresh_token,
|
|
235
|
+
expires_in: tokens.expires_in,
|
|
236
|
+
scope: tokens.scope,
|
|
237
|
+
saved_at: new Date().toISOString(),
|
|
238
|
+
};
|
|
239
|
+
await saveState(this.serverName, state);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// --- redirectToAuthorization ---
|
|
243
|
+
|
|
244
|
+
async redirectToAuthorization(authorizationUrl: URL): Promise<void> {
|
|
245
|
+
if (this._onAuthRequired) {
|
|
246
|
+
await this._onAuthRequired(authorizationUrl);
|
|
247
|
+
} else {
|
|
248
|
+
// Fallback: just log it
|
|
249
|
+
console.error(
|
|
250
|
+
`[pi-mcp] OAuth authorization required for "${this.serverName}".`,
|
|
251
|
+
`\n Open this URL in your browser: ${authorizationUrl.toString()}`,
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// --- PKCE code verifier ---
|
|
257
|
+
|
|
258
|
+
async saveCodeVerifier(codeVerifier: string): Promise<void> {
|
|
259
|
+
const state = await loadState(this.serverName);
|
|
260
|
+
state.codeVerifier = codeVerifier;
|
|
261
|
+
await saveState(this.serverName, state);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async codeVerifier(): Promise<string> {
|
|
265
|
+
const state = await loadState(this.serverName);
|
|
266
|
+
if (!state.codeVerifier) {
|
|
267
|
+
throw new Error(`[pi-mcp] No PKCE code verifier found for "${this.serverName}"`);
|
|
268
|
+
}
|
|
269
|
+
return state.codeVerifier;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// --- Discovery state caching ---
|
|
273
|
+
|
|
274
|
+
async saveDiscoveryState(discState: OAuthDiscoveryState): Promise<void> {
|
|
275
|
+
const state = await loadState(this.serverName);
|
|
276
|
+
state.discoveryState = discState;
|
|
277
|
+
await saveState(this.serverName, state);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async discoveryState(): Promise<OAuthDiscoveryState | undefined> {
|
|
281
|
+
const state = await loadState(this.serverName);
|
|
282
|
+
return state.discoveryState;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// --- OAuth state parameter (CSRF protection) ---
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Set the OAuth state parameter before calling auth().
|
|
289
|
+
* This should be called with a cryptographically random value.
|
|
290
|
+
*/
|
|
291
|
+
setState(state: string): void {
|
|
292
|
+
this._oauthState = state;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Returns the OAuth state parameter for CSRF protection.
|
|
297
|
+
* This is called by the SDK's auth() function when building the authorization URL.
|
|
298
|
+
* Returns empty string if no state has been set (no CSRF protection).
|
|
299
|
+
*/
|
|
300
|
+
async state(): Promise<string> {
|
|
301
|
+
return this._oauthState || "";
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// --- Credential invalidation ---
|
|
305
|
+
|
|
306
|
+
async invalidateCredentials(scope: "all" | "client" | "tokens" | "verifier" | "discovery"): Promise<void> {
|
|
307
|
+
const state = await loadState(this.serverName);
|
|
308
|
+
switch (scope) {
|
|
309
|
+
case "all":
|
|
310
|
+
state.clientInfo = undefined;
|
|
311
|
+
state.tokens = undefined;
|
|
312
|
+
state.codeVerifier = undefined;
|
|
313
|
+
state.discoveryState = undefined;
|
|
314
|
+
break;
|
|
315
|
+
case "client":
|
|
316
|
+
state.clientInfo = undefined;
|
|
317
|
+
break;
|
|
318
|
+
case "tokens":
|
|
319
|
+
state.tokens = undefined;
|
|
320
|
+
break;
|
|
321
|
+
case "verifier":
|
|
322
|
+
state.codeVerifier = undefined;
|
|
323
|
+
break;
|
|
324
|
+
case "discovery":
|
|
325
|
+
state.discoveryState = undefined;
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
await saveState(this.serverName, state);
|
|
329
|
+
}
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
// ─── Public Helpers ────────────────────────────────────────────────────────────
|
|
@@ -336,26 +336,26 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
|
|
336
336
|
* Returns null if no auth state file exists at all.
|
|
337
337
|
*/
|
|
338
338
|
export async function getAuthStatus(serverName: string): Promise<{
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
339
|
+
hasTokens: boolean;
|
|
340
|
+
hasClientInfo: boolean;
|
|
341
|
+
savedAt: string | undefined;
|
|
342
|
+
scope: string | undefined;
|
|
343
343
|
} | null> {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
344
|
+
const state = await loadState(serverName);
|
|
345
|
+
if (
|
|
346
|
+
state.clientInfo === undefined &&
|
|
347
|
+
state.tokens === undefined &&
|
|
348
|
+
state.codeVerifier === undefined &&
|
|
349
|
+
state.discoveryState === undefined
|
|
350
|
+
) {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
hasTokens: state.tokens !== undefined,
|
|
355
|
+
hasClientInfo: state.clientInfo !== undefined,
|
|
356
|
+
savedAt: state.tokens?.saved_at,
|
|
357
|
+
scope: state.tokens?.scope,
|
|
358
|
+
};
|
|
359
359
|
}
|
|
360
360
|
|
|
361
361
|
/**
|
|
@@ -363,5 +363,5 @@ export async function getAuthStatus(serverName: string): Promise<{
|
|
|
363
363
|
* Used to force re-authorization on next connection.
|
|
364
364
|
*/
|
|
365
365
|
export async function resetAuth(serverName: string): Promise<void> {
|
|
366
|
-
|
|
366
|
+
await unlink(statePath(serverName)).catch(() => {});
|
|
367
367
|
}
|