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