@evantahler/mcpx 0.18.2 → 0.18.5
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/package.json +63 -62
- package/src/cli.ts +46 -54
- package/src/client/browser.ts +15 -15
- package/src/client/debug-fetch.ts +53 -56
- package/src/client/elicitation.ts +279 -291
- package/src/client/http.ts +1 -1
- package/src/client/manager.ts +481 -514
- package/src/client/oauth.ts +272 -282
- package/src/client/sse.ts +1 -1
- package/src/client/stdio.ts +7 -7
- package/src/client/trace.ts +146 -152
- package/src/client/transport-options.ts +20 -20
- package/src/commands/add.ts +160 -165
- package/src/commands/allow.ts +141 -142
- package/src/commands/auth.ts +86 -90
- package/src/commands/check-update.ts +49 -53
- package/src/commands/deny.ts +114 -117
- package/src/commands/exec.ts +218 -222
- package/src/commands/index.ts +41 -41
- package/src/commands/info.ts +48 -50
- package/src/commands/list.ts +49 -49
- package/src/commands/ping.ts +47 -50
- package/src/commands/prompt.ts +40 -50
- package/src/commands/remove.ts +54 -56
- package/src/commands/resource.ts +31 -36
- package/src/commands/search.ts +35 -39
- package/src/commands/servers.ts +44 -48
- package/src/commands/skill.ts +89 -95
- package/src/commands/task.ts +50 -60
- package/src/commands/upgrade.ts +191 -208
- package/src/commands/with-command.ts +27 -29
- package/src/config/env.ts +26 -28
- package/src/config/loader.ts +99 -102
- package/src/config/schemas.ts +78 -87
- package/src/constants.ts +17 -17
- package/src/context.ts +51 -51
- package/src/lib/client-settings.ts +127 -140
- package/src/lib/input.ts +23 -26
- package/src/output/format-output.ts +12 -16
- package/src/output/format-table.ts +39 -42
- package/src/output/formatter.ts +790 -814
- package/src/output/logger.ts +140 -152
- package/src/sdk.ts +283 -291
- package/src/search/index.ts +50 -54
- package/src/search/indexer.ts +65 -65
- package/src/search/keyword.ts +54 -54
- package/src/search/semantic.ts +39 -39
- package/src/search/staleness.ts +3 -3
- package/src/search/types.ts +4 -4
- package/src/update/background.ts +51 -51
- package/src/update/cache.ts +21 -21
- package/src/update/checker.ts +81 -86
- package/src/validation/schema.ts +52 -58
package/src/client/oauth.ts
CHANGED
|
@@ -1,240 +1,230 @@
|
|
|
1
1
|
import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
2
|
-
import {
|
|
3
|
-
auth,
|
|
4
|
-
discoverOAuthServerInfo,
|
|
5
|
-
refreshAuthorization,
|
|
6
|
-
} from "@modelcontextprotocol/sdk/client/auth.js";
|
|
2
|
+
import { auth, discoverOAuthServerInfo, refreshAuthorization } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
7
3
|
import type {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
OAuthClientInformationMixed,
|
|
5
|
+
OAuthClientMetadata,
|
|
6
|
+
OAuthTokens,
|
|
11
7
|
} from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
12
|
-
import type { AuthFile } from "../config/schemas.ts";
|
|
13
8
|
import { saveAuth } from "../config/loader.ts";
|
|
9
|
+
import type { AuthFile } from "../config/schemas.ts";
|
|
14
10
|
import type { FormatOptions } from "../output/formatter.ts";
|
|
15
11
|
import { logger } from "../output/logger.ts";
|
|
16
12
|
import { openBrowser } from "./browser.ts";
|
|
17
13
|
|
|
18
14
|
export class McpOAuthProvider implements OAuthClientProvider {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
await this.saveTokens(tokens);
|
|
184
|
-
|
|
185
|
-
logger.info(`Token refreshed for "${this.serverName}"`);
|
|
186
|
-
}
|
|
15
|
+
private serverName: string;
|
|
16
|
+
private configDir: string;
|
|
17
|
+
auth: AuthFile;
|
|
18
|
+
private _codeVerifier?: string;
|
|
19
|
+
private _callbackPort = 0;
|
|
20
|
+
|
|
21
|
+
constructor(opts: { serverName: string; configDir: string; auth: AuthFile }) {
|
|
22
|
+
this.serverName = opts.serverName;
|
|
23
|
+
this.configDir = opts.configDir;
|
|
24
|
+
this.auth = opts.auth;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get redirectUrl(): string {
|
|
28
|
+
return `http://127.0.0.1:${this._callbackPort}/callback`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get clientMetadata(): OAuthClientMetadata {
|
|
32
|
+
return {
|
|
33
|
+
redirect_uris: [`http://127.0.0.1:${this._callbackPort}/callback`],
|
|
34
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
35
|
+
response_types: ["code"],
|
|
36
|
+
client_name: "mcpx",
|
|
37
|
+
token_endpoint_auth_method: "none",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
clientInformation(): OAuthClientInformationMixed | undefined {
|
|
42
|
+
const entry = this.auth[this.serverName];
|
|
43
|
+
// During an active auth flow, return client_info even if incomplete.
|
|
44
|
+
// For normal usage (transport), the manager checks isComplete() separately.
|
|
45
|
+
return entry?.client_info;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async saveClientInformation(info: OAuthClientInformationMixed): Promise<void> {
|
|
49
|
+
if (!this.auth[this.serverName]) {
|
|
50
|
+
this.auth[this.serverName] = { tokens: {} as OAuthTokens };
|
|
51
|
+
}
|
|
52
|
+
this.auth[this.serverName]!.client_info = info;
|
|
53
|
+
await saveAuth(this.configDir, this.auth);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
tokens(): OAuthTokens | undefined {
|
|
57
|
+
return this.auth[this.serverName]?.tokens;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async saveTokens(tokens: OAuthTokens): Promise<void> {
|
|
61
|
+
if (!this.auth[this.serverName]) {
|
|
62
|
+
this.auth[this.serverName] = { tokens };
|
|
63
|
+
} else {
|
|
64
|
+
this.auth[this.serverName]!.tokens = tokens;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Compute expires_at from expires_in
|
|
68
|
+
if (tokens.expires_in) {
|
|
69
|
+
const expiresAt = new Date(Date.now() + tokens.expires_in * 1000);
|
|
70
|
+
this.auth[this.serverName]!.expires_at = expiresAt.toISOString();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Mark auth as complete — tokens have been successfully obtained
|
|
74
|
+
this.auth[this.serverName]!.complete = true;
|
|
75
|
+
|
|
76
|
+
await saveAuth(this.configDir, this.auth);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async redirectToAuthorization(url: URL): Promise<void> {
|
|
80
|
+
const urlStr = url.toString();
|
|
81
|
+
logger.info(urlStr);
|
|
82
|
+
await openBrowser(urlStr);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async saveCodeVerifier(v: string): Promise<void> {
|
|
86
|
+
this._codeVerifier = v;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
codeVerifier(): string {
|
|
90
|
+
if (!this._codeVerifier) {
|
|
91
|
+
throw new Error("Code verifier not set");
|
|
92
|
+
}
|
|
93
|
+
return this._codeVerifier;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async invalidateCredentials(scope: "all" | "client" | "tokens" | "verifier" | "discovery"): Promise<void> {
|
|
97
|
+
const entry = this.auth[this.serverName];
|
|
98
|
+
if (!entry) return;
|
|
99
|
+
|
|
100
|
+
switch (scope) {
|
|
101
|
+
case "all":
|
|
102
|
+
delete this.auth[this.serverName];
|
|
103
|
+
break;
|
|
104
|
+
case "client":
|
|
105
|
+
delete entry.client_info;
|
|
106
|
+
break;
|
|
107
|
+
case "tokens":
|
|
108
|
+
delete this.auth[this.serverName];
|
|
109
|
+
// Re-create entry without tokens but keep client_info
|
|
110
|
+
if (entry.client_info) {
|
|
111
|
+
this.auth[this.serverName] = {
|
|
112
|
+
tokens: {} as OAuthTokens,
|
|
113
|
+
client_info: entry.client_info,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
break;
|
|
117
|
+
case "verifier":
|
|
118
|
+
this._codeVerifier = undefined;
|
|
119
|
+
return; // No need to persist
|
|
120
|
+
case "discovery":
|
|
121
|
+
return; // Nothing to clear locally
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
await saveAuth(this.configDir, this.auth);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Whether the auth flow completed successfully (tokens were obtained) */
|
|
128
|
+
isComplete(): boolean {
|
|
129
|
+
return !!this.auth[this.serverName]?.complete;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Clear any incomplete auth state from a previously cancelled flow */
|
|
133
|
+
async clearIncomplete(): Promise<void> {
|
|
134
|
+
const entry = this.auth[this.serverName];
|
|
135
|
+
if (entry && !entry.complete) {
|
|
136
|
+
delete this.auth[this.serverName];
|
|
137
|
+
await saveAuth(this.configDir, this.auth);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
setCallbackPort(port: number): void {
|
|
142
|
+
this._callbackPort = port;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
isExpired(): boolean {
|
|
146
|
+
const entry = this.auth[this.serverName];
|
|
147
|
+
if (!entry?.expires_at) return false;
|
|
148
|
+
return new Date(entry.expires_at) <= new Date();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
hasRefreshToken(): boolean {
|
|
152
|
+
const tokens = this.auth[this.serverName]?.tokens;
|
|
153
|
+
return !!tokens?.refresh_token;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async refreshIfNeeded(serverUrl: string): Promise<void> {
|
|
157
|
+
if (!this.isExpired()) return;
|
|
158
|
+
|
|
159
|
+
if (!this.hasRefreshToken()) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`Token expired for "${this.serverName}" and no refresh token available. Run: mcpx auth ${this.serverName}`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const clientInfo = this.clientInformation();
|
|
166
|
+
if (!clientInfo) {
|
|
167
|
+
throw new Error(`No client information for "${this.serverName}". Run: mcpx auth ${this.serverName}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const tokens = await refreshAuthorization(serverUrl, {
|
|
171
|
+
clientInformation: clientInfo,
|
|
172
|
+
refreshToken: this.auth[this.serverName]?.tokens.refresh_token!,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
await this.saveTokens(tokens);
|
|
176
|
+
|
|
177
|
+
logger.info(`Token refreshed for "${this.serverName}"`);
|
|
178
|
+
}
|
|
187
179
|
}
|
|
188
180
|
|
|
189
181
|
/** Start a local callback server to receive the OAuth authorization code */
|
|
190
182
|
export function startCallbackServer(): {
|
|
191
|
-
|
|
192
|
-
|
|
183
|
+
server: ReturnType<typeof Bun.serve>;
|
|
184
|
+
authCodePromise: Promise<string>;
|
|
193
185
|
} {
|
|
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
|
-
return { server, authCodePromise };
|
|
186
|
+
let resolveCode: (code: string) => void;
|
|
187
|
+
let rejectCode: (err: Error) => void;
|
|
188
|
+
|
|
189
|
+
const authCodePromise = new Promise<string>((resolve, reject) => {
|
|
190
|
+
resolveCode = resolve;
|
|
191
|
+
rejectCode = reject;
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const server = Bun.serve({
|
|
195
|
+
port: 0,
|
|
196
|
+
fetch(req) {
|
|
197
|
+
const url = new URL(req.url);
|
|
198
|
+
if (url.pathname !== "/callback") {
|
|
199
|
+
return new Response("Not found", { status: 404 });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const error = url.searchParams.get("error");
|
|
203
|
+
if (error) {
|
|
204
|
+
const desc = url.searchParams.get("error_description") || error;
|
|
205
|
+
rejectCode?.(new Error(`OAuth error: ${desc}`));
|
|
206
|
+
return new Response(
|
|
207
|
+
"<html><body><h1>Authentication Failed</h1><p>You can close this window.</p></body></html>",
|
|
208
|
+
{ headers: { "Content-Type": "text/html" } },
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const code = url.searchParams.get("code");
|
|
213
|
+
if (!code) {
|
|
214
|
+
rejectCode?.(new Error("No authorization code received"));
|
|
215
|
+
return new Response("<html><body><h1>Error</h1><p>No authorization code received.</p></body></html>", {
|
|
216
|
+
headers: { "Content-Type": "text/html" },
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
resolveCode?.(code);
|
|
221
|
+
return new Response("<html><body><h1>Authenticated!</h1><p>You can close this window.</p></body></html>", {
|
|
222
|
+
headers: { "Content-Type": "text/html" },
|
|
223
|
+
});
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
return { server, authCodePromise };
|
|
238
228
|
}
|
|
239
229
|
|
|
240
230
|
/** Resolve the canonical resource URL for an HTTP MCP server.
|
|
@@ -242,73 +232,73 @@ export function startCallbackServer(): {
|
|
|
242
232
|
* that may differ from the URL provided by the user (e.g. hf.co → huggingface.co).
|
|
243
233
|
* Returns the canonical URL if found, or the original URL otherwise. */
|
|
244
234
|
export async function resolveResourceUrl(serverUrl: string): Promise<string> {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
235
|
+
try {
|
|
236
|
+
const info = await discoverOAuthServerInfo(serverUrl);
|
|
237
|
+
const canonical = info.resourceMetadata?.resource;
|
|
238
|
+
if (canonical && canonical !== serverUrl) {
|
|
239
|
+
// Preserve the path/query/hash from the original URL — the canonical URL
|
|
240
|
+
// from OAuth resource metadata identifies the origin (scheme + host + port),
|
|
241
|
+
// not the endpoint path (e.g. /mcp).
|
|
242
|
+
const orig = new URL(serverUrl);
|
|
243
|
+
const canon = new URL(canonical);
|
|
244
|
+
canon.pathname = orig.pathname;
|
|
245
|
+
canon.search = orig.search;
|
|
246
|
+
canon.hash = orig.hash;
|
|
247
|
+
const merged = canon.toString();
|
|
248
|
+
return merged === serverUrl ? serverUrl : merged;
|
|
249
|
+
}
|
|
250
|
+
} catch {
|
|
251
|
+
// OAuth discovery not available — use original URL
|
|
252
|
+
}
|
|
253
|
+
return serverUrl;
|
|
264
254
|
}
|
|
265
255
|
|
|
266
256
|
/** Probe for OAuth support and run the auth flow if the server supports it.
|
|
267
257
|
* Returns true if auth ran, false if server doesn't support OAuth (silent skip). */
|
|
268
258
|
export async function tryOAuthIfSupported(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
259
|
+
serverName: string,
|
|
260
|
+
serverUrl: string,
|
|
261
|
+
configDir: string,
|
|
262
|
+
auth: AuthFile,
|
|
263
|
+
formatOptions: FormatOptions,
|
|
274
264
|
): Promise<boolean> {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
265
|
+
let oauthSupported: boolean;
|
|
266
|
+
try {
|
|
267
|
+
const info = await discoverOAuthServerInfo(serverUrl);
|
|
268
|
+
oauthSupported = info.authorizationServerMetadata !== undefined;
|
|
269
|
+
} catch {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (!oauthSupported) return false;
|
|
274
|
+
|
|
275
|
+
const provider = new McpOAuthProvider({ serverName, configDir, auth });
|
|
276
|
+
const spinner = logger.startSpinner(`Authenticating with "${serverName}"…`, formatOptions);
|
|
277
|
+
try {
|
|
278
|
+
await runOAuthFlow(serverUrl, provider);
|
|
279
|
+
spinner.success(`Authenticated with "${serverName}"`);
|
|
280
|
+
return true;
|
|
281
|
+
} catch (err) {
|
|
282
|
+
spinner.error(`Authentication failed: ${err instanceof Error ? err.message : err}`);
|
|
283
|
+
throw err;
|
|
284
|
+
}
|
|
295
285
|
}
|
|
296
286
|
|
|
297
287
|
/** Run a full OAuth authorization flow for an HTTP MCP server */
|
|
298
288
|
export async function runOAuthFlow(serverUrl: string, provider: McpOAuthProvider): Promise<void> {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
289
|
+
// Clear any leftover state from a previously cancelled auth flow
|
|
290
|
+
await provider.clearIncomplete();
|
|
291
|
+
|
|
292
|
+
const { server, authCodePromise } = startCallbackServer();
|
|
293
|
+
try {
|
|
294
|
+
provider.setCallbackPort(server.port!);
|
|
295
|
+
|
|
296
|
+
const result = await auth(provider, { serverUrl });
|
|
297
|
+
if (result === "REDIRECT") {
|
|
298
|
+
const code = await authCodePromise;
|
|
299
|
+
await auth(provider, { serverUrl, authorizationCode: code });
|
|
300
|
+
}
|
|
301
|
+
} finally {
|
|
302
|
+
server.stop();
|
|
303
|
+
}
|
|
314
304
|
}
|
package/src/client/sse.ts
CHANGED
|
@@ -2,5 +2,5 @@ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
|
2
2
|
import { buildTransportInit, type TransportDeps } from "./transport-options.ts";
|
|
3
3
|
|
|
4
4
|
export function createSseTransport(deps: TransportDeps): SSEClientTransport {
|
|
5
|
-
|
|
5
|
+
return new SSEClientTransport(new URL(deps.config.url), buildTransportInit(deps));
|
|
6
6
|
}
|
package/src/client/stdio.ts
CHANGED
|
@@ -2,11 +2,11 @@ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
|
|
|
2
2
|
import type { StdioServerConfig } from "../config/schemas.ts";
|
|
3
3
|
|
|
4
4
|
export function createStdioTransport(config: StdioServerConfig): StdioClientTransport {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
return new StdioClientTransport({
|
|
6
|
+
command: config.command,
|
|
7
|
+
args: config.args,
|
|
8
|
+
env: config.env ? ({ ...process.env, ...config.env } as Record<string, string>) : undefined,
|
|
9
|
+
cwd: config.cwd,
|
|
10
|
+
stderr: "pipe",
|
|
11
|
+
});
|
|
12
12
|
}
|