@mcp-z/client 1.0.1 â 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/auth/capability-discovery.d.cts +0 -19
- package/dist/cjs/auth/capability-discovery.d.ts +0 -19
- package/dist/cjs/auth/capability-discovery.js +123 -52
- package/dist/cjs/auth/capability-discovery.js.map +1 -1
- package/dist/cjs/auth/index.js.map +1 -1
- package/dist/cjs/auth/interactive-oauth-flow.js.map +1 -1
- package/dist/cjs/auth/oauth-callback-listener.js.map +1 -1
- package/dist/cjs/auth/pkce.js.map +1 -1
- package/dist/cjs/auth/rfc9728-discovery.d.cts +7 -0
- package/dist/cjs/auth/rfc9728-discovery.d.ts +7 -0
- package/dist/cjs/auth/rfc9728-discovery.js +208 -46
- package/dist/cjs/auth/rfc9728-discovery.js.map +1 -1
- package/dist/cjs/auth/types.js.map +1 -1
- package/dist/cjs/client-helpers.js.map +1 -1
- package/dist/cjs/config/server-loader.js.map +1 -1
- package/dist/cjs/config/validate-config.js +3 -7
- package/dist/cjs/config/validate-config.js.map +1 -1
- package/dist/cjs/connection/connect-client.js.map +1 -1
- package/dist/cjs/connection/existing-process-transport.js.map +1 -1
- package/dist/cjs/connection/types.js.map +1 -1
- package/dist/cjs/connection/wait-for-http-ready.js.map +1 -1
- package/dist/cjs/dcr/dcr-authenticator.js.map +1 -1
- package/dist/cjs/dcr/dynamic-client-registrar.js.map +1 -1
- package/dist/cjs/dcr/index.js.map +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/monkey-patches.js.map +1 -1
- package/dist/cjs/response-wrappers.js.map +1 -1
- package/dist/cjs/search/index.js.map +1 -1
- package/dist/cjs/search/search.js.map +1 -1
- package/dist/cjs/search/types.js.map +1 -1
- package/dist/cjs/spawn/spawn-server.js +4 -0
- package/dist/cjs/spawn/spawn-server.js.map +1 -1
- package/dist/cjs/spawn/spawn-servers.js.map +1 -1
- package/dist/cjs/types.js.map +1 -1
- package/dist/cjs/utils/logger.js.map +1 -1
- package/dist/cjs/utils/path-utils.js.map +1 -1
- package/dist/cjs/utils/sanitizer.js.map +1 -1
- package/dist/esm/auth/capability-discovery.d.ts +0 -19
- package/dist/esm/auth/capability-discovery.js +43 -41
- package/dist/esm/auth/capability-discovery.js.map +1 -1
- package/dist/esm/auth/index.js.map +1 -1
- package/dist/esm/auth/interactive-oauth-flow.js.map +1 -1
- package/dist/esm/auth/oauth-callback-listener.js.map +1 -1
- package/dist/esm/auth/pkce.js.map +1 -1
- package/dist/esm/auth/rfc9728-discovery.d.ts +7 -0
- package/dist/esm/auth/rfc9728-discovery.js +67 -0
- package/dist/esm/auth/rfc9728-discovery.js.map +1 -1
- package/dist/esm/auth/types.js.map +1 -1
- package/dist/esm/client-helpers.js.map +1 -1
- package/dist/esm/config/server-loader.js.map +1 -1
- package/dist/esm/config/validate-config.js +3 -7
- package/dist/esm/config/validate-config.js.map +1 -1
- package/dist/esm/connection/connect-client.js.map +1 -1
- package/dist/esm/connection/existing-process-transport.js.map +1 -1
- package/dist/esm/connection/types.js.map +1 -1
- package/dist/esm/connection/wait-for-http-ready.js.map +1 -1
- package/dist/esm/dcr/dcr-authenticator.js.map +1 -1
- package/dist/esm/dcr/dynamic-client-registrar.js.map +1 -1
- package/dist/esm/dcr/index.js.map +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/monkey-patches.js.map +1 -1
- package/dist/esm/response-wrappers.js.map +1 -1
- package/dist/esm/search/index.js.map +1 -1
- package/dist/esm/search/search.js.map +1 -1
- package/dist/esm/search/types.js.map +1 -1
- package/dist/esm/spawn/spawn-server.js +4 -0
- package/dist/esm/spawn/spawn-server.js.map +1 -1
- package/dist/esm/spawn/spawn-servers.js.map +1 -1
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/utils/logger.js.map +1 -1
- package/dist/esm/utils/path-utils.js.map +1 -1
- package/dist/esm/utils/sanitizer.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/Projects/
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/client/src/auth/oauth-callback-listener.ts"],"sourcesContent":["/**\n * OAuth Callback Server for CLI Authentication\n * Listens for OAuth authorization callbacks and captures authorization code\n */\n\nimport http from 'node:http';\nimport { logger as defaultLogger, type Logger } from '../utils/logger.ts';\nimport type { CallbackResult } from './types.ts';\n\nexport interface OAuthCallbackListenerOptions {\n /** Port to listen on (required - use get-port package to find available port) */\n port: number;\n /** Optional logger for debug output (defaults to singleton logger) */\n logger?: Logger;\n}\n\n/**\n * OAuthCallbackListener handles OAuth redirect callbacks\n * Starts a temporary HTTP server to receive authorization code\n *\n * Note: Caller is responsible for finding an available port using get-port package\n */\nexport class OAuthCallbackListener {\n private server: http.Server | undefined;\n private resolveCallback?: (result: CallbackResult) => void;\n private rejectCallback?: (error: Error) => void;\n private timeout: NodeJS.Timeout | undefined;\n private port: number;\n private logger: Logger;\n\n constructor(options: OAuthCallbackListenerOptions) {\n this.port = options.port;\n this.logger = options.logger ?? defaultLogger;\n }\n\n /**\n * Start the callback server\n * Fails fast if port is already in use - caller should use get-port to find available port\n */\n async start(): Promise<void> {\n await this.listen(this.port);\n this.logger.debug(`â
Callback server listening on http://localhost:${this.port}/callback`);\n }\n\n /**\n * Listen on a specific port\n */\n private listen(port: number): Promise<void> {\n return new Promise((resolve, reject) => {\n this.server = http.createServer((req, res) => {\n this.handleRequest(req, res);\n });\n\n this.server.on('error', (error) => {\n reject(error);\n });\n\n this.server.listen(port, () => {\n resolve();\n });\n });\n }\n\n /**\n * Handle incoming HTTP requests\n */\n private handleRequest(req: http.IncomingMessage, res: http.ServerResponse): void {\n const url = new URL(req.url || '', `http://localhost:${this.port}`);\n\n if (url.pathname === '/callback') {\n this.handleCallback(url, res);\n } else {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not Found');\n }\n }\n\n /**\n * Handle OAuth callback\n */\n private handleCallback(url: URL, res: http.ServerResponse): void {\n const code = url.searchParams.get('code');\n const state = url.searchParams.get('state');\n const error = url.searchParams.get('error');\n const errorDescription = url.searchParams.get('error_description');\n\n // Handle OAuth errors\n if (error) {\n const errorMessage = errorDescription ? `${error}: ${errorDescription}` : error;\n\n res.writeHead(400, { 'Content-Type': 'text/html' });\n res.end(`\n <html>\n <body>\n <h1>Authorization Failed</h1>\n <p>${errorMessage}</p>\n <script>setTimeout(() => window.close(), 3000);</script>\n </body>\n </html>\n `);\n\n if (this.rejectCallback) {\n this.rejectCallback(new Error(errorMessage));\n }\n return;\n }\n\n // Validate code parameter\n if (!code) {\n res.writeHead(400, { 'Content-Type': 'text/html' });\n res.end(`\n <html>\n <body>\n <h1>Invalid Callback</h1>\n <p>Missing authorization code</p>\n <script>setTimeout(() => window.close(), 3000);</script>\n </body>\n </html>\n `);\n\n if (this.rejectCallback) {\n this.rejectCallback(new Error('Missing authorization code'));\n }\n return;\n }\n\n // Success - send confirmation page\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(`\n <html>\n <head>\n <meta charset=\"UTF-8\">\n </head>\n <body>\n <h1>Authorization Successful</h1>\n <p>You can close this window and return to the terminal.</p>\n <script>setTimeout(() => window.close(), 2000);</script>\n </body>\n </html>\n `);\n\n // Resolve the promise with authorization code\n if (this.resolveCallback) {\n const result: CallbackResult = { code };\n if (state) {\n result.state = state;\n }\n this.resolveCallback(result);\n }\n }\n\n /**\n * Wait for OAuth callback with timeout\n */\n async waitForCallback(timeoutMs = 300000): Promise<CallbackResult> {\n return new Promise((resolve, reject) => {\n this.resolveCallback = resolve;\n this.rejectCallback = reject;\n\n // Set timeout to prevent hanging forever\n this.timeout = setTimeout(() => {\n reject(new Error(`Authorization timeout - no callback received within ${timeoutMs / 1000} seconds`));\n this.stop();\n }, timeoutMs);\n });\n }\n\n /**\n * Stop the callback server and close\n */\n async stop(): Promise<void> {\n // Clear the timeout\n if (this.timeout) {\n clearTimeout(this.timeout);\n this.timeout = undefined;\n }\n\n // Close the server\n if (this.server) {\n await new Promise<void>((resolve) => {\n this.server?.close(() => {\n this.logger.debug('đ Callback server closed');\n resolve();\n });\n });\n this.server = undefined;\n }\n }\n\n /**\n * Get the callback URL for this server\n */\n getCallbackUrl(): string {\n if (!this.port) {\n throw new Error('Server not started - call start() first');\n }\n return `http://localhost:${this.port}/callback`;\n }\n}\n"],"names":["http","logger","defaultLogger","OAuthCallbackListener","start","listen","port","debug","Promise","resolve","reject","server","createServer","req","res","handleRequest","on","error","url","URL","pathname","handleCallback","writeHead","end","code","searchParams","get","state","errorDescription","errorMessage","rejectCallback","Error","resolveCallback","result","waitForCallback","timeoutMs","timeout","setTimeout","stop","clearTimeout","undefined","close","getCallbackUrl","options"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/Projects/
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/client/src/auth/pkce.ts"],"sourcesContent":["/**\n * PKCE (Proof Key for Code Exchange) utilities\n * Implements RFC 7636 for OAuth 2.0 public client security\n */\n\nimport { createHash, randomBytes } from 'node:crypto';\nimport type { PkceParams } from './types.ts';\n\n/**\n * Generate random code verifier for PKCE (RFC 7636 Section 4.1)\n * Returns cryptographically random string of 43-128 characters using base64url encoding\n */\nfunction generateRandomCodeVerifier(): string {\n // RFC 7636 recommends 43-128 characters\n // Using 32 random bytes -> 43 base64url characters\n return randomBytes(32).toString('base64url');\n}\n\n/**\n * Calculate PKCE code challenge from code verifier (RFC 7636 Section 4.2)\n * Uses S256 method: BASE64URL(SHA256(ASCII(code_verifier)))\n */\nasync function calculatePKCECodeChallenge(codeVerifier: string): Promise<string> {\n const hash = createHash('sha256').update(codeVerifier, 'ascii').digest();\n return Buffer.from(hash).toString('base64url');\n}\n\n/**\n * Generate PKCE parameters for OAuth 2.0 authorization code flow\n * Uses S256 method (SHA-256 hash) as recommended by RFC 7636\n *\n * @returns PkceParams with code verifier, challenge, and method\n *\n * @example\n * const pkce = await generatePkce();\n * // Use pkce.codeChallenge and pkce.codeChallengeMethod in authorization URL\n * // Store pkce.codeVerifier for token exchange\n */\nexport async function generatePkce(): Promise<PkceParams> {\n // Generate cryptographically random code verifier (RFC 7636 § 4.1)\n const codeVerifier = generateRandomCodeVerifier();\n\n // Generate code challenge using S256 method (RFC 7636 § 4.2)\n // S256: BASE64URL(SHA256(ASCII(code_verifier)))\n const codeChallenge = await calculatePKCECodeChallenge(codeVerifier);\n\n return {\n codeVerifier,\n codeChallenge,\n codeChallengeMethod: 'S256',\n };\n}\n"],"names":["createHash","randomBytes","generateRandomCodeVerifier","toString","calculatePKCECodeChallenge","codeVerifier","hash","update","digest","Buffer","from","generatePkce","codeChallenge","codeChallengeMethod"],"mappings":"AAAA;;;CAGC,GAED,SAASA,UAAU,EAAEC,WAAW,QAAQ,cAAc;AAGtD;;;CAGC,GACD,SAASC;IACP,wCAAwC;IACxC,mDAAmD;IACnD,OAAOD,YAAY,IAAIE,QAAQ,CAAC;AAClC;AAEA;;;CAGC,GACD,eAAeC,2BAA2BC,YAAoB;IAC5D,MAAMC,OAAON,WAAW,UAAUO,MAAM,CAACF,cAAc,SAASG,MAAM;IACtE,OAAOC,OAAOC,IAAI,CAACJ,MAAMH,QAAQ,CAAC;AACpC;AAEA;;;;;;;;;;CAUC,GACD,OAAO,eAAeQ;IACpB,mEAAmE;IACnE,MAAMN,eAAeH;IAErB,6DAA6D;IAC7D,gDAAgD;IAChD,MAAMU,gBAAgB,MAAMR,2BAA2BC;IAEvD,OAAO;QACLA;QACAO;QACAC,qBAAqB;IACvB;AACF"}
|
|
@@ -32,3 +32,10 @@ export declare function discoverProtectedResourceMetadata(resourceUrl: string):
|
|
|
32
32
|
* // Returns: { issuer: "https://todoist.com", authorization_endpoint: "...", ... }
|
|
33
33
|
*/
|
|
34
34
|
export declare function discoverAuthorizationServerMetadata(authServerUrl: string): Promise<AuthorizationServerMetadata | null>;
|
|
35
|
+
/**
|
|
36
|
+
* Discover OAuth Authorization Server Issuer from resource response (RFC 9207)
|
|
37
|
+
*
|
|
38
|
+
* @param resourceUrl - URL of the protected resource
|
|
39
|
+
* @returns Issuer URL if present in WWW-Authenticate header, null otherwise
|
|
40
|
+
*/
|
|
41
|
+
export declare function discoverAuthorizationServerIssuer(resourceUrl: string): Promise<string | null>;
|
|
@@ -47,6 +47,8 @@
|
|
|
47
47
|
* // Returns: { resource: "https://ai.todoist.net/mcp", authorization_servers: ["https://todoist.com"] }
|
|
48
48
|
*/ export async function discoverProtectedResourceMetadata(resourceUrl) {
|
|
49
49
|
try {
|
|
50
|
+
const headerMetadata = await discoverProtectedResourceMetadataFromHeader(resourceUrl);
|
|
51
|
+
if (headerMetadata) return headerMetadata;
|
|
50
52
|
const origin = getOrigin(resourceUrl);
|
|
51
53
|
const path = getPath(resourceUrl);
|
|
52
54
|
// Strategy 1: Try root location (REQUIRED by RFC 9728)
|
|
@@ -126,6 +128,47 @@
|
|
|
126
128
|
return null;
|
|
127
129
|
}
|
|
128
130
|
}
|
|
131
|
+
async function discoverProtectedResourceMetadataFromHeader(resourceUrl) {
|
|
132
|
+
try {
|
|
133
|
+
const response = await fetch(resourceUrl, {
|
|
134
|
+
method: 'GET',
|
|
135
|
+
headers: {
|
|
136
|
+
Accept: 'application/json',
|
|
137
|
+
Connection: 'close'
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
let header = response.headers.get('www-authenticate');
|
|
141
|
+
if (!header) {
|
|
142
|
+
const postResponse = await fetch(resourceUrl, {
|
|
143
|
+
method: 'POST',
|
|
144
|
+
headers: {
|
|
145
|
+
Accept: 'application/json',
|
|
146
|
+
Connection: 'close',
|
|
147
|
+
'Content-Type': 'application/json'
|
|
148
|
+
},
|
|
149
|
+
body: '{}'
|
|
150
|
+
});
|
|
151
|
+
header = postResponse.headers.get('www-authenticate');
|
|
152
|
+
}
|
|
153
|
+
if (!header) return null;
|
|
154
|
+
const match = header.match(/resource_metadata="([^"]+)"/i);
|
|
155
|
+
if (!match || !match[1]) return null;
|
|
156
|
+
const metadataUrl = match[1];
|
|
157
|
+
const metadataResponse = await fetch(metadataUrl, {
|
|
158
|
+
method: 'GET',
|
|
159
|
+
headers: {
|
|
160
|
+
Accept: 'application/json',
|
|
161
|
+
Connection: 'close'
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
if (!metadataResponse.ok) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
return await metadataResponse.json();
|
|
168
|
+
} catch (_error) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
129
172
|
/**
|
|
130
173
|
* Discover OAuth 2.0 Authorization Server Metadata (RFC 8414)
|
|
131
174
|
* Probes .well-known/oauth-authorization-server endpoint
|
|
@@ -155,3 +198,27 @@
|
|
|
155
198
|
return null;
|
|
156
199
|
}
|
|
157
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Discover OAuth Authorization Server Issuer from resource response (RFC 9207)
|
|
203
|
+
*
|
|
204
|
+
* @param resourceUrl - URL of the protected resource
|
|
205
|
+
* @returns Issuer URL if present in WWW-Authenticate header, null otherwise
|
|
206
|
+
*/ export async function discoverAuthorizationServerIssuer(resourceUrl) {
|
|
207
|
+
try {
|
|
208
|
+
var _match_;
|
|
209
|
+
const response = await fetch(resourceUrl, {
|
|
210
|
+
method: 'GET',
|
|
211
|
+
headers: {
|
|
212
|
+
Accept: 'application/json',
|
|
213
|
+
Connection: 'close'
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
const header = response.headers.get('www-authenticate');
|
|
217
|
+
if (!header) return null;
|
|
218
|
+
const match = header.match(/(?:authorization_server|issuer)="([^"]+)"/i);
|
|
219
|
+
if (!match) return null;
|
|
220
|
+
return (_match_ = match[1]) !== null && _match_ !== void 0 ? _match_ : null;
|
|
221
|
+
} catch (_error) {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/libs/client/src/auth/rfc9728-discovery.ts"],"sourcesContent":["/**\n * RFC 9728 Protected Resource Metadata Discovery\n * Probes .well-known/oauth-protected-resource endpoint\n */\n\nimport type { AuthorizationServerMetadata, ProtectedResourceMetadata } from './types.ts';\n\n/**\n * Extract origin (protocol + host) from a URL\n * @param url - Full URL that may include a path\n * @returns Origin (e.g., \"https://example.com\") or original string if invalid URL\n *\n * @example\n * getOrigin('https://example.com/mcp') // â 'https://example.com'\n * getOrigin('http://localhost:9999/api/v1/mcp') // â 'http://localhost:9999'\n */\nfunction getOrigin(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n // Invalid URL - return as-is for graceful degradation\n return url;\n }\n}\n\n/**\n * Extract path from a URL (without origin)\n * @param url - Full URL\n * @returns Path component (e.g., \"/mcp\", \"/api/v1/mcp\") or empty string if no path\n */\nfunction getPath(url: string): string {\n try {\n const parsed = new URL(url);\n // pathname includes leading slash, e.g., \"/mcp\"\n return parsed.pathname === '/' ? '' : parsed.pathname;\n } catch {\n return '';\n }\n}\n\n/**\n * Discover OAuth 2.0 Protected Resource Metadata (RFC 9728)\n * Probes .well-known/oauth-protected-resource endpoint\n *\n * Discovery Strategy:\n * 1. Try origin root: {origin}/.well-known/oauth-protected-resource\n * 2. If 404, try sub-path: {origin}/.well-known/oauth-protected-resource{path}\n *\n * @param resourceUrl - URL of the protected resource (e.g., https://ai.todoist.net/mcp)\n * @returns ProtectedResourceMetadata if discovered, null otherwise\n *\n * @example\n * // Todoist case: MCP at ai.todoist.net/mcp, OAuth at todoist.com\n * const metadata = await discoverProtectedResourceMetadata('https://ai.todoist.net/mcp');\n * // Returns: { resource: \"https://ai.todoist.net/mcp\", authorization_servers: [\"https://todoist.com\"] }\n */\nexport async function discoverProtectedResourceMetadata(resourceUrl: string): Promise<ProtectedResourceMetadata | null> {\n try {\n const origin = getOrigin(resourceUrl);\n const path = getPath(resourceUrl);\n\n // Strategy 1: Try root location (REQUIRED by RFC 9728)\n const rootUrl = `${origin}/.well-known/oauth-protected-resource`;\n\n try {\n const response = await fetch(rootUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n if (response.ok) {\n const metadata = (await response.json()) as ProtectedResourceMetadata;\n // Check if the discovered resource matches what we're looking for\n if (metadata.resource === resourceUrl) {\n return metadata;\n }\n // If there's no path component, return root metadata\n // (e.g., looking for http://example.com and found it)\n if (!path) {\n return metadata;\n }\n // If requested URL starts with metadata.resource, the root metadata applies to sub-paths\n // (e.g., looking for http://example.com/api/v1/mcp, found http://example.com)\n if (resourceUrl.startsWith(metadata.resource)) {\n // Still try sub-path location to see if there's more specific metadata\n // But save root metadata as fallback\n const rootMetadata = metadata;\n\n // Try sub-path location for more specific metadata\n const subPathUrl = `${origin}/.well-known/oauth-protected-resource${path}`;\n try {\n const subPathResponse = await fetch(subPathUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n if (subPathResponse.ok) {\n return (await subPathResponse.json()) as ProtectedResourceMetadata;\n }\n } catch {\n // Sub-path failed, use root metadata\n }\n\n // Return root metadata as it applies to this resource\n return rootMetadata;\n }\n // Otherwise, try sub-path location before giving up\n }\n } catch {\n // Continue to sub-path location\n }\n\n // Strategy 2: Try sub-path location (MCP spec extension)\n // Only try if there's a path component\n if (path) {\n const subPathUrl = `${origin}/.well-known/oauth-protected-resource${path}`;\n\n try {\n const response = await fetch(subPathUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n if (response.ok) {\n return (await response.json()) as ProtectedResourceMetadata;\n }\n } catch {\n // Fall through to return null\n }\n }\n\n // Neither location found or resource didn't match\n return null;\n } catch (_error) {\n // Network error, invalid URL, or other failure\n return null;\n }\n}\n\n/**\n * Discover OAuth 2.0 Authorization Server Metadata (RFC 8414)\n * Probes .well-known/oauth-authorization-server endpoint\n *\n * @param authServerUrl - URL of the authorization server (typically from RFC 9728 discovery)\n * @returns AuthorizationServerMetadata if discovered, null otherwise\n *\n * @example\n * const metadata = await discoverAuthorizationServerMetadata('https://todoist.com');\n * // Returns: { issuer: \"https://todoist.com\", authorization_endpoint: \"...\", ... }\n */\nexport async function discoverAuthorizationServerMetadata(authServerUrl: string): Promise<AuthorizationServerMetadata | null> {\n try {\n const origin = getOrigin(authServerUrl);\n const wellKnownUrl = `${origin}/.well-known/oauth-authorization-server`;\n\n const response = await fetch(wellKnownUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n if (!response.ok) {\n return null;\n }\n\n return (await response.json()) as AuthorizationServerMetadata;\n } catch (_error) {\n return null;\n }\n}\n"],"names":["getOrigin","url","URL","origin","getPath","parsed","pathname","discoverProtectedResourceMetadata","resourceUrl","path","rootUrl","response","fetch","method","headers","Accept","Connection","ok","metadata","json","resource","startsWith","rootMetadata","subPathUrl","subPathResponse","_error","discoverAuthorizationServerMetadata","authServerUrl","wellKnownUrl"],"mappings":"AAAA;;;CAGC,GAID;;;;;;;;CAQC,GACD,SAASA,UAAUC,GAAW;IAC5B,IAAI;QACF,OAAO,IAAIC,IAAID,KAAKE,MAAM;IAC5B,EAAE,OAAM;QACN,sDAAsD;QACtD,OAAOF;IACT;AACF;AAEA;;;;CAIC,GACD,SAASG,QAAQH,GAAW;IAC1B,IAAI;QACF,MAAMI,SAAS,IAAIH,IAAID;QACvB,gDAAgD;QAChD,OAAOI,OAAOC,QAAQ,KAAK,MAAM,KAAKD,OAAOC,QAAQ;IACvD,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAEA;;;;;;;;;;;;;;;CAeC,GACD,OAAO,eAAeC,kCAAkCC,WAAmB;IACzE,IAAI;QACF,MAAML,SAASH,UAAUQ;QACzB,MAAMC,OAAOL,QAAQI;QAErB,uDAAuD;QACvD,MAAME,UAAU,GAAGP,OAAO,qCAAqC,CAAC;QAEhE,IAAI;YACF,MAAMQ,WAAW,MAAMC,MAAMF,SAAS;gBACpCG,QAAQ;gBACRC,SAAS;oBAAEC,QAAQ;oBAAoBC,YAAY;gBAAQ;YAC7D;YAEA,IAAIL,SAASM,EAAE,EAAE;gBACf,MAAMC,WAAY,MAAMP,SAASQ,IAAI;gBACrC,kEAAkE;gBAClE,IAAID,SAASE,QAAQ,KAAKZ,aAAa;oBACrC,OAAOU;gBACT;gBACA,qDAAqD;gBACrD,sDAAsD;gBACtD,IAAI,CAACT,MAAM;oBACT,OAAOS;gBACT;gBACA,yFAAyF;gBACzF,8EAA8E;gBAC9E,IAAIV,YAAYa,UAAU,CAACH,SAASE,QAAQ,GAAG;oBAC7C,uEAAuE;oBACvE,qCAAqC;oBACrC,MAAME,eAAeJ;oBAErB,mDAAmD;oBACnD,MAAMK,aAAa,GAAGpB,OAAO,qCAAqC,EAAEM,MAAM;oBAC1E,IAAI;wBACF,MAAMe,kBAAkB,MAAMZ,MAAMW,YAAY;4BAC9CV,QAAQ;4BACRC,SAAS;gCAAEC,QAAQ;gCAAoBC,YAAY;4BAAQ;wBAC7D;wBACA,IAAIQ,gBAAgBP,EAAE,EAAE;4BACtB,OAAQ,MAAMO,gBAAgBL,IAAI;wBACpC;oBACF,EAAE,OAAM;oBACN,qCAAqC;oBACvC;oBAEA,sDAAsD;oBACtD,OAAOG;gBACT;YACA,oDAAoD;YACtD;QACF,EAAE,OAAM;QACN,gCAAgC;QAClC;QAEA,yDAAyD;QACzD,uCAAuC;QACvC,IAAIb,MAAM;YACR,MAAMc,aAAa,GAAGpB,OAAO,qCAAqC,EAAEM,MAAM;YAE1E,IAAI;gBACF,MAAME,WAAW,MAAMC,MAAMW,YAAY;oBACvCV,QAAQ;oBACRC,SAAS;wBAAEC,QAAQ;wBAAoBC,YAAY;oBAAQ;gBAC7D;gBAEA,IAAIL,SAASM,EAAE,EAAE;oBACf,OAAQ,MAAMN,SAASQ,IAAI;gBAC7B;YACF,EAAE,OAAM;YACN,8BAA8B;YAChC;QACF;QAEA,kDAAkD;QAClD,OAAO;IACT,EAAE,OAAOM,QAAQ;QACf,+CAA+C;QAC/C,OAAO;IACT;AACF;AAEA;;;;;;;;;;CAUC,GACD,OAAO,eAAeC,oCAAoCC,aAAqB;IAC7E,IAAI;QACF,MAAMxB,SAASH,UAAU2B;QACzB,MAAMC,eAAe,GAAGzB,OAAO,uCAAuC,CAAC;QAEvE,MAAMQ,WAAW,MAAMC,MAAMgB,cAAc;YACzCf,QAAQ;YACRC,SAAS;gBAAEC,QAAQ;gBAAoBC,YAAY;YAAQ;QAC7D;QAEA,IAAI,CAACL,SAASM,EAAE,EAAE;YAChB,OAAO;QACT;QAEA,OAAQ,MAAMN,SAASQ,IAAI;IAC7B,EAAE,OAAOM,QAAQ;QACf,OAAO;IACT;AACF"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/client/src/auth/rfc9728-discovery.ts"],"sourcesContent":["/**\n * RFC 9728 Protected Resource Metadata Discovery\n * Probes .well-known/oauth-protected-resource endpoint\n */\n\nimport type { AuthorizationServerMetadata, ProtectedResourceMetadata } from './types.ts';\n\n/**\n * Extract origin (protocol + host) from a URL\n * @param url - Full URL that may include a path\n * @returns Origin (e.g., \"https://example.com\") or original string if invalid URL\n *\n * @example\n * getOrigin('https://example.com/mcp') // â 'https://example.com'\n * getOrigin('http://localhost:9999/api/v1/mcp') // â 'http://localhost:9999'\n */\nfunction getOrigin(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n // Invalid URL - return as-is for graceful degradation\n return url;\n }\n}\n\n/**\n * Extract path from a URL (without origin)\n * @param url - Full URL\n * @returns Path component (e.g., \"/mcp\", \"/api/v1/mcp\") or empty string if no path\n */\nfunction getPath(url: string): string {\n try {\n const parsed = new URL(url);\n // pathname includes leading slash, e.g., \"/mcp\"\n return parsed.pathname === '/' ? '' : parsed.pathname;\n } catch {\n return '';\n }\n}\n\n/**\n * Discover OAuth 2.0 Protected Resource Metadata (RFC 9728)\n * Probes .well-known/oauth-protected-resource endpoint\n *\n * Discovery Strategy:\n * 1. Try origin root: {origin}/.well-known/oauth-protected-resource\n * 2. If 404, try sub-path: {origin}/.well-known/oauth-protected-resource{path}\n *\n * @param resourceUrl - URL of the protected resource (e.g., https://ai.todoist.net/mcp)\n * @returns ProtectedResourceMetadata if discovered, null otherwise\n *\n * @example\n * // Todoist case: MCP at ai.todoist.net/mcp, OAuth at todoist.com\n * const metadata = await discoverProtectedResourceMetadata('https://ai.todoist.net/mcp');\n * // Returns: { resource: \"https://ai.todoist.net/mcp\", authorization_servers: [\"https://todoist.com\"] }\n */\nexport async function discoverProtectedResourceMetadata(resourceUrl: string): Promise<ProtectedResourceMetadata | null> {\n try {\n const headerMetadata = await discoverProtectedResourceMetadataFromHeader(resourceUrl);\n if (headerMetadata) return headerMetadata;\n\n const origin = getOrigin(resourceUrl);\n const path = getPath(resourceUrl);\n\n // Strategy 1: Try root location (REQUIRED by RFC 9728)\n const rootUrl = `${origin}/.well-known/oauth-protected-resource`;\n\n try {\n const response = await fetch(rootUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n if (response.ok) {\n const metadata = (await response.json()) as ProtectedResourceMetadata;\n // Check if the discovered resource matches what we're looking for\n if (metadata.resource === resourceUrl) {\n return metadata;\n }\n // If there's no path component, return root metadata\n // (e.g., looking for http://example.com and found it)\n if (!path) {\n return metadata;\n }\n // If requested URL starts with metadata.resource, the root metadata applies to sub-paths\n // (e.g., looking for http://example.com/api/v1/mcp, found http://example.com)\n if (resourceUrl.startsWith(metadata.resource)) {\n // Still try sub-path location to see if there's more specific metadata\n // But save root metadata as fallback\n const rootMetadata = metadata;\n\n // Try sub-path location for more specific metadata\n const subPathUrl = `${origin}/.well-known/oauth-protected-resource${path}`;\n try {\n const subPathResponse = await fetch(subPathUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n if (subPathResponse.ok) {\n return (await subPathResponse.json()) as ProtectedResourceMetadata;\n }\n } catch {\n // Sub-path failed, use root metadata\n }\n\n // Return root metadata as it applies to this resource\n return rootMetadata;\n }\n // Otherwise, try sub-path location before giving up\n }\n } catch {\n // Continue to sub-path location\n }\n\n // Strategy 2: Try sub-path location (MCP spec extension)\n // Only try if there's a path component\n if (path) {\n const subPathUrl = `${origin}/.well-known/oauth-protected-resource${path}`;\n\n try {\n const response = await fetch(subPathUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n if (response.ok) {\n return (await response.json()) as ProtectedResourceMetadata;\n }\n } catch {\n // Fall through to return null\n }\n }\n\n // Neither location found or resource didn't match\n return null;\n } catch (_error) {\n // Network error, invalid URL, or other failure\n return null;\n }\n}\n\nasync function discoverProtectedResourceMetadataFromHeader(resourceUrl: string): Promise<ProtectedResourceMetadata | null> {\n try {\n const response = await fetch(resourceUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n let header = response.headers.get('www-authenticate');\n if (!header) {\n const postResponse = await fetch(resourceUrl, {\n method: 'POST',\n headers: { Accept: 'application/json', Connection: 'close', 'Content-Type': 'application/json' },\n body: '{}',\n });\n header = postResponse.headers.get('www-authenticate');\n }\n\n if (!header) return null;\n\n const match = header.match(/resource_metadata=\"([^\"]+)\"/i);\n if (!match || !match[1]) return null;\n\n const metadataUrl = match[1];\n const metadataResponse = await fetch(metadataUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n if (!metadataResponse.ok) {\n return null;\n }\n\n return (await metadataResponse.json()) as ProtectedResourceMetadata;\n } catch (_error) {\n return null;\n }\n}\n\n/**\n * Discover OAuth 2.0 Authorization Server Metadata (RFC 8414)\n * Probes .well-known/oauth-authorization-server endpoint\n *\n * @param authServerUrl - URL of the authorization server (typically from RFC 9728 discovery)\n * @returns AuthorizationServerMetadata if discovered, null otherwise\n *\n * @example\n * const metadata = await discoverAuthorizationServerMetadata('https://todoist.com');\n * // Returns: { issuer: \"https://todoist.com\", authorization_endpoint: \"...\", ... }\n */\nexport async function discoverAuthorizationServerMetadata(authServerUrl: string): Promise<AuthorizationServerMetadata | null> {\n try {\n const origin = getOrigin(authServerUrl);\n const wellKnownUrl = `${origin}/.well-known/oauth-authorization-server`;\n\n const response = await fetch(wellKnownUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n if (!response.ok) {\n return null;\n }\n\n return (await response.json()) as AuthorizationServerMetadata;\n } catch (_error) {\n return null;\n }\n}\n\n/**\n * Discover OAuth Authorization Server Issuer from resource response (RFC 9207)\n *\n * @param resourceUrl - URL of the protected resource\n * @returns Issuer URL if present in WWW-Authenticate header, null otherwise\n */\nexport async function discoverAuthorizationServerIssuer(resourceUrl: string): Promise<string | null> {\n try {\n const response = await fetch(resourceUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n const header = response.headers.get('www-authenticate');\n if (!header) return null;\n\n const match = header.match(/(?:authorization_server|issuer)=\"([^\"]+)\"/i);\n if (!match) return null;\n\n return match[1] ?? null;\n } catch (_error) {\n return null;\n }\n}\n"],"names":["getOrigin","url","URL","origin","getPath","parsed","pathname","discoverProtectedResourceMetadata","resourceUrl","headerMetadata","discoverProtectedResourceMetadataFromHeader","path","rootUrl","response","fetch","method","headers","Accept","Connection","ok","metadata","json","resource","startsWith","rootMetadata","subPathUrl","subPathResponse","_error","header","get","postResponse","body","match","metadataUrl","metadataResponse","discoverAuthorizationServerMetadata","authServerUrl","wellKnownUrl","discoverAuthorizationServerIssuer"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/Projects/
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/client/src/auth/types.ts"],"sourcesContent":["/**\n * Shared types for OAuth and DCR authentication\n */\n\n/**\n * OAuth callback result from authorization server\n */\nexport interface CallbackResult {\n /** Authorization code from OAuth server */\n code: string;\n /** State parameter for CSRF protection */\n state?: string;\n}\n\n/**\n * PKCE (Proof Key for Code Exchange) parameters (RFC 7636)\n * Used to secure OAuth 2.0 authorization code flow for public clients\n */\nexport interface PkceParams {\n /** Code verifier - cryptographically random string (43-128 characters) */\n codeVerifier: string;\n /** Code challenge - derived from code verifier using challenge method */\n codeChallenge: string;\n /** Code challenge method - S256 (SHA-256) or plain */\n codeChallengeMethod: 'S256' | 'plain';\n}\n\n/**\n * OAuth token set with access and refresh tokens\n */\nexport interface TokenSet {\n /** Access token for API requests */\n accessToken: string;\n /** Refresh token for obtaining new access tokens */\n refreshToken: string;\n /** Timestamp when access token expires (milliseconds since epoch) */\n expiresAt: number;\n /** Scopes granted for this token set */\n scopes?: string[];\n /** Client ID used for DCR registration (stored for future use) */\n clientId?: string;\n /** Client secret used for DCR registration (stored for future use) */\n clientSecret?: string;\n}\n\n/**\n * OAuth 2.0 Protected Resource Metadata (RFC 9728)\n * Response from .well-known/oauth-protected-resource endpoint\n */\nexport interface ProtectedResourceMetadata {\n /** The protected resource identifier */\n resource: string;\n /** List of authorization server URLs that can issue tokens for this resource */\n authorization_servers: string[];\n /** Optional list of scopes supported by this resource */\n scopes_supported?: string[];\n /** Optional list of bearer token methods supported (header, query, body) */\n bearer_methods_supported?: string[];\n}\n\n/**\n * OAuth 2.0 Authorization Server Metadata (RFC 8414)\n * Response from .well-known/oauth-authorization-server endpoint\n */\nexport interface AuthorizationServerMetadata {\n /** The authorization server's issuer identifier */\n issuer?: string;\n /** URL of the authorization endpoint */\n authorization_endpoint?: string;\n /** URL of the token endpoint */\n token_endpoint?: string;\n /** URL of the client registration endpoint (DCR - RFC 7591) */\n registration_endpoint?: string;\n /** URL of the token introspection endpoint */\n introspection_endpoint?: string;\n /** List of OAuth scopes supported by the authorization server */\n scopes_supported?: string[];\n /** Response types supported (code, token, etc.) */\n response_types_supported?: string[];\n /** Grant types supported (authorization_code, refresh_token, etc.) */\n grant_types_supported?: string[];\n /** Token endpoint authentication methods supported */\n token_endpoint_auth_methods_supported?: string[];\n}\n\n/**\n * OAuth server capabilities discovered from .well-known endpoint\n */\nexport interface AuthCapabilities {\n /** Whether the server supports Dynamic Client Registration (RFC 7591) */\n supportsDcr: boolean;\n /** DCR client registration endpoint */\n registrationEndpoint?: string;\n /** OAuth authorization endpoint */\n authorizationEndpoint?: string;\n /** OAuth token endpoint */\n tokenEndpoint?: string;\n /** Token introspection endpoint */\n introspectionEndpoint?: string;\n /** Supported OAuth scopes */\n scopes?: string[];\n}\n\n/**\n * Client credentials from DCR registration\n */\nexport interface ClientCredentials {\n /** OAuth client ID */\n clientId: string;\n /** OAuth client secret */\n clientSecret: string;\n /** Timestamp when client was registered */\n issuedAt?: number;\n}\n\n/**\n * Options for DCR client registration\n */\nexport interface DcrRegistrationOptions {\n /** Client name to register */\n clientName?: string;\n /** Redirect URI for OAuth callback */\n redirectUri?: string;\n}\n\n/**\n * Options for OAuth authorization flow\n */\nexport interface OAuthFlowOptions {\n /** Port for OAuth callback listener (required - use get-port to find available port) */\n port: number;\n /** Redirect URI for OAuth callback (optional - will be built from port if not provided) */\n redirectUri?: string;\n /** OAuth scopes to request */\n scopes?: string[];\n /** Resource parameter (RFC 8707) - target resource server identifier */\n resource?: string;\n /** Enable PKCE (RFC 7636) - recommended for all clients, required for public clients */\n pkce?: boolean;\n /** Headless mode (don't open browser) */\n headless?: boolean;\n /** Timeout for callback (milliseconds) */\n timeout?: number;\n /** Optional logger for debug output (defaults to singleton logger) */\n logger?: import('../utils/logger.ts').Logger;\n}\n"],"names":[],"mappings":"AAAA;;CAEC,GAED;;CAEC,GAuHD;;CAEC,GACD,WAiBC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/Projects/
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/client/src/client-helpers.ts"],"sourcesContent":["import type { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport type { RequestOptions } from '@modelcontextprotocol/sdk/shared/protocol.js';\nimport type { ToolArguments } from './connection/types.ts';\nimport { type NativeCallToolResponse, type NativeGetPromptResponse, type NativeReadResourceResponse, PromptResponseWrapper, ResourceResponseWrapper, ToolResponseWrapper } from './response-wrappers.ts';\n\nexport type PromptArguments = Record<string, string>;\n\ntype NativeCallToolParams = Parameters<Client['callTool']>;\ntype NativeCallToolReturn = ReturnType<Client['callTool']>;\n/**\n * Fetch-style wrapper returned by `ManagedClient.callTool()`.\n * @public\n */\nexport type WrappedCallToolReturn = Promise<ToolResponseWrapper>;\n\ntype NativeGetPromptParams = Parameters<Client['getPrompt']>;\ntype NativeGetPromptReturn = ReturnType<Client['getPrompt']>;\n/**\n * Fetch-style wrapper returned by `ManagedClient.getPrompt()`.\n * @public\n */\nexport type WrappedGetPromptReturn = Promise<PromptResponseWrapper>;\n\ntype NativeReadResourceParams = Parameters<Client['readResource']>;\ntype NativeReadResourceReturn = ReturnType<Client['readResource']>;\n/**\n * Fetch-style wrapper returned by `ManagedClient.readResource()`.\n * @public\n */\nexport type WrappedReadResourceReturn = Promise<ResourceResponseWrapper>;\n\n/**\n * Client returned by registry.connect() with convenience overloads for\n * calling tools, reading resources, and getting prompts using simple arguments.\n */\nexport type ManagedClient = Omit<Client, 'callTool' | 'getPrompt' | 'readResource'> & {\n /** Name of the server this client is connected to. */\n readonly serverName: string;\n /** Underlying MCP SDK client for advanced scenarios. */\n readonly nativeClient: Client;\n\n callTool(toolName: string, args?: ToolArguments, requestOptions?: RequestOptions): WrappedCallToolReturn;\n callTool(invocation: NativeCallToolParams[0], sessionId?: NativeCallToolParams[1], requestOptions?: NativeCallToolParams[2]): WrappedCallToolReturn;\n callToolRaw(toolName: string, args?: ToolArguments, requestOptions?: RequestOptions): NativeCallToolReturn;\n callToolRaw(invocation: NativeCallToolParams[0], sessionId?: NativeCallToolParams[1], requestOptions?: NativeCallToolParams[2]): NativeCallToolReturn;\n\n getPrompt(name: string, args?: PromptArguments, requestOptions?: NativeGetPromptParams[1]): WrappedGetPromptReturn;\n getPrompt(invocation: NativeGetPromptParams[0], requestOptions?: NativeGetPromptParams[1]): WrappedGetPromptReturn;\n getPromptRaw(name: string, args?: PromptArguments, requestOptions?: NativeGetPromptParams[1]): NativeGetPromptReturn;\n getPromptRaw(invocation: NativeGetPromptParams[0], requestOptions?: NativeGetPromptParams[1]): NativeGetPromptReturn;\n\n readResource(uri: string, requestOptions?: NativeReadResourceParams[1]): WrappedReadResourceReturn;\n readResource(request: NativeReadResourceParams[0], requestOptions?: NativeReadResourceParams[1]): WrappedReadResourceReturn;\n readResourceRaw(uri: string, requestOptions?: NativeReadResourceParams[1]): NativeReadResourceReturn;\n readResourceRaw(request: NativeReadResourceParams[0], requestOptions?: NativeReadResourceParams[1]): NativeReadResourceReturn;\n};\n\n/**\n * Enhance an MCP SDK client with convenience overloads.\n */\nexport function decorateClient(client: Client, metadata: { serverName: string }): ManagedClient {\n const enhanced = client as unknown as ManagedClient;\n\n Object.defineProperty(enhanced, 'serverName', {\n value: metadata.serverName,\n enumerable: true,\n configurable: false,\n writable: false,\n });\n Object.defineProperty(enhanced, 'nativeClient', {\n value: client,\n enumerable: false,\n configurable: false,\n writable: false,\n });\n\n const nativeCallTool = client.callTool.bind(client);\n const wrapCallTool = (promise: NativeCallToolReturn): WrappedCallToolReturn => promise.then((payload) => new ToolResponseWrapper(payload as NativeCallToolResponse));\n\n enhanced.callTool = ((nameOrInvocation: string | NativeCallToolParams[0], argsOrSession?: ToolArguments | NativeCallToolParams[1], requestOptions?: NativeCallToolParams[2]) => {\n if (typeof nameOrInvocation === 'string') {\n return wrapCallTool(nativeCallTool({ name: nameOrInvocation, arguments: (argsOrSession as ToolArguments) ?? {} }, undefined, requestOptions));\n }\n return wrapCallTool(nativeCallTool(nameOrInvocation, argsOrSession as NativeCallToolParams[1], requestOptions));\n }) as ManagedClient['callTool'];\n\n enhanced.callToolRaw = ((nameOrInvocation: string | NativeCallToolParams[0], argsOrSession?: ToolArguments | NativeCallToolParams[1], requestOptions?: NativeCallToolParams[2]) => {\n if (typeof nameOrInvocation === 'string') {\n return nativeCallTool({ name: nameOrInvocation, arguments: (argsOrSession as ToolArguments) ?? {} }, undefined, requestOptions);\n }\n return nativeCallTool(nameOrInvocation, argsOrSession as NativeCallToolParams[1], requestOptions);\n }) as ManagedClient['callToolRaw'];\n\n const nativeGetPrompt = client.getPrompt.bind(client);\n const wrapPrompt = (promise: NativeGetPromptReturn): WrappedGetPromptReturn => promise.then((payload) => new PromptResponseWrapper(payload as NativeGetPromptResponse));\n\n enhanced.getPrompt = ((nameOrParams: string | NativeGetPromptParams[0], argsOrOptions?: PromptArguments | NativeGetPromptParams[1], requestOptions?: NativeGetPromptParams[1]) => {\n if (typeof nameOrParams === 'string') {\n return wrapPrompt(nativeGetPrompt({ name: nameOrParams, ...(argsOrOptions ? { arguments: argsOrOptions as PromptArguments } : {}) }, requestOptions));\n }\n return wrapPrompt(nativeGetPrompt(nameOrParams, argsOrOptions as NativeGetPromptParams[1]));\n }) as ManagedClient['getPrompt'];\n\n enhanced.getPromptRaw = ((nameOrParams: string | NativeGetPromptParams[0], argsOrOptions?: PromptArguments | NativeGetPromptParams[1], requestOptions?: NativeGetPromptParams[1]) => {\n if (typeof nameOrParams === 'string') {\n return nativeGetPrompt({ name: nameOrParams, ...(argsOrOptions ? { arguments: argsOrOptions as PromptArguments } : {}) }, requestOptions);\n }\n return nativeGetPrompt(nameOrParams, argsOrOptions as NativeGetPromptParams[1]);\n }) as ManagedClient['getPromptRaw'];\n\n const nativeReadResource = client.readResource.bind(client);\n const wrapResource = (promise: NativeReadResourceReturn): WrappedReadResourceReturn => promise.then((payload) => new ResourceResponseWrapper(payload as NativeReadResourceResponse));\n\n enhanced.readResource = ((uriOrRequest: string | NativeReadResourceParams[0], requestOptions?: NativeReadResourceParams[1]) => {\n if (typeof uriOrRequest === 'string') {\n return wrapResource(nativeReadResource({ uri: uriOrRequest }, requestOptions));\n }\n return wrapResource(nativeReadResource(uriOrRequest, requestOptions));\n }) as ManagedClient['readResource'];\n\n enhanced.readResourceRaw = ((uriOrRequest: string | NativeReadResourceParams[0], requestOptions?: NativeReadResourceParams[1]) => {\n if (typeof uriOrRequest === 'string') {\n return nativeReadResource({ uri: uriOrRequest }, requestOptions);\n }\n return nativeReadResource(uriOrRequest, requestOptions);\n }) as ManagedClient['readResourceRaw'];\n\n return enhanced;\n}\n"],"names":["PromptResponseWrapper","ResourceResponseWrapper","ToolResponseWrapper","decorateClient","client","metadata","enhanced","Object","defineProperty","value","serverName","enumerable","configurable","writable","nativeCallTool","callTool","bind","wrapCallTool","promise","then","payload","nameOrInvocation","argsOrSession","requestOptions","name","arguments","undefined","callToolRaw","nativeGetPrompt","getPrompt","wrapPrompt","nameOrParams","argsOrOptions","getPromptRaw","nativeReadResource","readResource","wrapResource","uriOrRequest","uri","readResourceRaw"],"mappings":"AAGA,SAAqGA,qBAAqB,EAAEC,uBAAuB,EAAEC,mBAAmB,QAAQ,yBAAyB;AAsDzM;;CAEC,GACD,OAAO,SAASC,eAAeC,MAAc,EAAEC,QAAgC;IAC7E,MAAMC,WAAWF;IAEjBG,OAAOC,cAAc,CAACF,UAAU,cAAc;QAC5CG,OAAOJ,SAASK,UAAU;QAC1BC,YAAY;QACZC,cAAc;QACdC,UAAU;IACZ;IACAN,OAAOC,cAAc,CAACF,UAAU,gBAAgB;QAC9CG,OAAOL;QACPO,YAAY;QACZC,cAAc;QACdC,UAAU;IACZ;IAEA,MAAMC,iBAAiBV,OAAOW,QAAQ,CAACC,IAAI,CAACZ;IAC5C,MAAMa,eAAe,CAACC,UAAyDA,QAAQC,IAAI,CAAC,CAACC,UAAY,IAAIlB,oBAAoBkB;IAEjId,SAASS,QAAQ,GAAI,CAACM,kBAAoDC,eAAyDC;QACjI,IAAI,OAAOF,qBAAqB,UAAU;YACxC,OAAOJ,aAAaH,eAAe;gBAAEU,MAAMH;gBAAkBI,SAAS,EAAGH,0BAAAA,2BAAAA,gBAAmC,CAAC;YAAE,GAAGI,WAAWH;QAC/H;QACA,OAAON,aAAaH,eAAeO,kBAAkBC,eAA0CC;IACjG;IAEAjB,SAASqB,WAAW,GAAI,CAACN,kBAAoDC,eAAyDC;QACpI,IAAI,OAAOF,qBAAqB,UAAU;YACxC,OAAOP,eAAe;gBAAEU,MAAMH;gBAAkBI,SAAS,EAAGH,0BAAAA,2BAAAA,gBAAmC,CAAC;YAAE,GAAGI,WAAWH;QAClH;QACA,OAAOT,eAAeO,kBAAkBC,eAA0CC;IACpF;IAEA,MAAMK,kBAAkBxB,OAAOyB,SAAS,CAACb,IAAI,CAACZ;IAC9C,MAAM0B,aAAa,CAACZ,UAA2DA,QAAQC,IAAI,CAAC,CAACC,UAAY,IAAIpB,sBAAsBoB;IAEnId,SAASuB,SAAS,GAAI,CAACE,cAAiDC,eAA4DT;QAClI,IAAI,OAAOQ,iBAAiB,UAAU;YACpC,OAAOD,WAAWF,gBAAgB;gBAAEJ,MAAMO;gBAAc,GAAIC,gBAAgB;oBAAEP,WAAWO;gBAAiC,IAAI,CAAC,CAAC;YAAE,GAAGT;QACvI;QACA,OAAOO,WAAWF,gBAAgBG,cAAcC;IAClD;IAEA1B,SAAS2B,YAAY,GAAI,CAACF,cAAiDC,eAA4DT;QACrI,IAAI,OAAOQ,iBAAiB,UAAU;YACpC,OAAOH,gBAAgB;gBAAEJ,MAAMO;gBAAc,GAAIC,gBAAgB;oBAAEP,WAAWO;gBAAiC,IAAI,CAAC,CAAC;YAAE,GAAGT;QAC5H;QACA,OAAOK,gBAAgBG,cAAcC;IACvC;IAEA,MAAME,qBAAqB9B,OAAO+B,YAAY,CAACnB,IAAI,CAACZ;IACpD,MAAMgC,eAAe,CAAClB,UAAiEA,QAAQC,IAAI,CAAC,CAACC,UAAY,IAAInB,wBAAwBmB;IAE7Id,SAAS6B,YAAY,GAAI,CAACE,cAAoDd;QAC5E,IAAI,OAAOc,iBAAiB,UAAU;YACpC,OAAOD,aAAaF,mBAAmB;gBAAEI,KAAKD;YAAa,GAAGd;QAChE;QACA,OAAOa,aAAaF,mBAAmBG,cAAcd;IACvD;IAEAjB,SAASiC,eAAe,GAAI,CAACF,cAAoDd;QAC/E,IAAI,OAAOc,iBAAiB,UAAU;YACpC,OAAOH,mBAAmB;gBAAEI,KAAKD;YAAa,GAAGd;QACnD;QACA,OAAOW,mBAAmBG,cAAcd;IAC1C;IAEA,OAAOjB;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/Projects/
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/client/src/config/server-loader.ts"],"sourcesContent":["/**\n * Load server definitions from .mcp.json (data-driven)\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { fileURLToPath } from 'url';\n\n// Get package root: dist/esm/lib -> ../../../ or dist/cjs/lib -> ../../../\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst packageRoot = path.resolve(__dirname, '../../..');\n\nexport interface McpServerConfig {\n command: string;\n args: string[];\n env?: Record<string, string>;\n}\n\nexport interface MCPConfiguration {\n mcpServers: Record<string, McpServerConfig>;\n}\n\n/**\n * Load available servers from resources/.mcp.json\n */\nexport function loadAvailableServers(): MCPConfiguration {\n const configPath = path.join(packageRoot, 'resources/.mcp.json');\n\n if (!fs.existsSync(configPath)) {\n throw new Error(`Server configuration not found: ${configPath}`);\n }\n\n const content = fs.readFileSync(configPath, 'utf-8');\n return JSON.parse(content) as MCPConfiguration;\n}\n\n/**\n * Get list of all available server names\n */\nexport function getAllServerNames(): string[] {\n const config = loadAvailableServers();\n return Object.keys(config.mcpServers);\n}\n\n/**\n * Parse comma-separated server list or \"all\"\n */\nexport function parseServerList(input: string): string[] {\n const allServers = getAllServerNames();\n\n if (input === 'all') {\n return allServers;\n }\n\n const requested = input.split(',').map((s) => s.trim());\n const invalid = requested.filter((s) => !allServers.includes(s));\n\n if (invalid.length > 0) {\n throw new Error(`Invalid server names: ${invalid.join(', ')}.\\nAvailable: ${allServers.join(', ')}`);\n }\n\n return requested;\n}\n\n/**\n * Get server configuration by name\n */\nexport function getServerConfig(serverName: string): McpServerConfig {\n const config = loadAvailableServers();\n const serverConfig = config.mcpServers[serverName];\n\n if (!serverConfig) {\n throw new Error(`Unknown server: ${serverName}.\\nAvailable: ${getAllServerNames().join(', ')}`);\n }\n\n return serverConfig;\n}\n"],"names":["fs","path","fileURLToPath","__filename","url","__dirname","dirname","packageRoot","resolve","loadAvailableServers","configPath","join","existsSync","Error","content","readFileSync","JSON","parse","getAllServerNames","config","Object","keys","mcpServers","parseServerList","input","allServers","requested","split","map","s","trim","invalid","filter","includes","length","getServerConfig","serverName","serverConfig"],"mappings":"AAAA;;CAEC,GAED,YAAYA,QAAQ,KAAK;AACzB,YAAYC,UAAU,OAAO;AAC7B,SAASC,aAAa,QAAQ,MAAM;AAEpC,2EAA2E;AAC3E,MAAMC,aAAaD,cAAc,YAAYE,GAAG;AAChD,MAAMC,YAAYJ,KAAKK,OAAO,CAACH;AAC/B,MAAMI,cAAcN,KAAKO,OAAO,CAACH,WAAW;AAY5C;;CAEC,GACD,OAAO,SAASI;IACd,MAAMC,aAAaT,KAAKU,IAAI,CAACJ,aAAa;IAE1C,IAAI,CAACP,GAAGY,UAAU,CAACF,aAAa;QAC9B,MAAM,IAAIG,MAAM,CAAC,gCAAgC,EAAEH,YAAY;IACjE;IAEA,MAAMI,UAAUd,GAAGe,YAAY,CAACL,YAAY;IAC5C,OAAOM,KAAKC,KAAK,CAACH;AACpB;AAEA;;CAEC,GACD,OAAO,SAASI;IACd,MAAMC,SAASV;IACf,OAAOW,OAAOC,IAAI,CAACF,OAAOG,UAAU;AACtC;AAEA;;CAEC,GACD,OAAO,SAASC,gBAAgBC,KAAa;IAC3C,MAAMC,aAAaP;IAEnB,IAAIM,UAAU,OAAO;QACnB,OAAOC;IACT;IAEA,MAAMC,YAAYF,MAAMG,KAAK,CAAC,KAAKC,GAAG,CAAC,CAACC,IAAMA,EAAEC,IAAI;IACpD,MAAMC,UAAUL,UAAUM,MAAM,CAAC,CAACH,IAAM,CAACJ,WAAWQ,QAAQ,CAACJ;IAE7D,IAAIE,QAAQG,MAAM,GAAG,GAAG;QACtB,MAAM,IAAIrB,MAAM,CAAC,sBAAsB,EAAEkB,QAAQpB,IAAI,CAAC,MAAM,cAAc,EAAEc,WAAWd,IAAI,CAAC,OAAO;IACrG;IAEA,OAAOe;AACT;AAEA;;CAEC,GACD,OAAO,SAASS,gBAAgBC,UAAkB;IAChD,MAAMjB,SAASV;IACf,MAAM4B,eAAelB,OAAOG,UAAU,CAACc,WAAW;IAElD,IAAI,CAACC,cAAc;QACjB,MAAM,IAAIxB,MAAM,CAAC,gBAAgB,EAAEuB,WAAW,cAAc,EAAElB,oBAAoBP,IAAI,CAAC,OAAO;IAChG;IAEA,OAAO0B;AACT"}
|
|
@@ -13,10 +13,8 @@ let validatorCache = null;
|
|
|
13
13
|
/**
|
|
14
14
|
* Get servers schema (loads once from bundled file, then caches)
|
|
15
15
|
*/ function getSchema() {
|
|
16
|
-
if (schemaCache)
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
const schemaPath = path.join(packageRoot, 'schemas/servers.schema.json');
|
|
16
|
+
if (schemaCache) return schemaCache;
|
|
17
|
+
const schemaPath = path.join(packageRoot, './schemas/servers.schema.json');
|
|
20
18
|
if (!fs.existsSync(schemaPath)) {
|
|
21
19
|
throw new Error(`Servers schema not found at: ${schemaPath}`);
|
|
22
20
|
}
|
|
@@ -26,9 +24,7 @@ let validatorCache = null;
|
|
|
26
24
|
/**
|
|
27
25
|
* Get compiled AJV validator (creates once, then caches)
|
|
28
26
|
*/ function getValidator() {
|
|
29
|
-
if (validatorCache)
|
|
30
|
-
return validatorCache;
|
|
31
|
-
}
|
|
27
|
+
if (validatorCache) return validatorCache;
|
|
32
28
|
const schema = getSchema();
|
|
33
29
|
const ajv = new Ajv({
|
|
34
30
|
allErrors: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/Projects/
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/client/src/config/validate-config.ts"],"sourcesContent":["import { Ajv, type ErrorObject } from 'ajv';\nimport addFormats from 'ajv-formats';\nimport * as fs from 'fs';\nimport moduleRoot from 'module-root-sync';\nimport * as path from 'path';\nimport * as url from 'url';\n\n// Import ajv-formats (CommonJS module - use createRequire for ESM compatibility)\nconst __dirname = path.dirname(typeof __filename !== 'undefined' ? __filename : url.fileURLToPath(import.meta.url));\nconst packageRoot = moduleRoot(__dirname);\n\n/**\n * Validation result for servers configuration\n */\nexport interface ValidationResult {\n valid: boolean;\n errors?: string[];\n warnings?: string[];\n}\n\n// Module-level cache for schema and validator\nlet schemaCache: object | null = null;\nlet validatorCache: ReturnType<Ajv['compile']> | null = null;\n\n/**\n * Get servers schema (loads once from bundled file, then caches)\n */\nfunction getSchema(): object {\n if (schemaCache) return schemaCache;\n\n const schemaPath = path.join(packageRoot, './schemas/servers.schema.json');\n if (!fs.existsSync(schemaPath)) {\n throw new Error(`Servers schema not found at: ${schemaPath}`);\n }\n\n schemaCache = JSON.parse(fs.readFileSync(schemaPath, 'utf8')) as object;\n return schemaCache;\n}\n\n/**\n * Get compiled AJV validator (creates once, then caches)\n */\nfunction getValidator(): ReturnType<Ajv['compile']> {\n if (validatorCache) return validatorCache;\n\n const schema = getSchema();\n const ajv = new Ajv({\n allErrors: true,\n verbose: true,\n strictSchema: false, // Allow non-standard keywords like \"example\"\n });\n\n // Add format validators (uri, email, etc.)\n addFormats(ajv);\n\n validatorCache = ajv.compile(schema);\n return validatorCache;\n}\n\n/**\n * Validate servers configuration against JSON Schema\n *\n * @param servers - Servers configuration object to validate (map of server names to configs)\n * @returns ValidationResult with valid flag, errors, and warnings\n */\nexport function validateServers(servers: unknown): ValidationResult {\n try {\n const validate = getValidator();\n const valid = validate(servers);\n\n if (!valid) {\n const errors =\n validate.errors?.map((e: ErrorObject) => {\n const path = e.instancePath || '(root)';\n return `${path}: ${e.message}`;\n }) || [];\n\n return { valid: false, errors };\n }\n\n const warnings: string[] = [];\n return { valid: true, warnings };\n } catch (err) {\n return {\n valid: false,\n errors: [`Configuration validation failed: ${(err as Error).message}`],\n };\n }\n}\n"],"names":["Ajv","addFormats","fs","moduleRoot","path","url","__dirname","dirname","__filename","fileURLToPath","packageRoot","schemaCache","validatorCache","getSchema","schemaPath","join","existsSync","Error","JSON","parse","readFileSync","getValidator","schema","ajv","allErrors","verbose","strictSchema","compile","validateServers","servers","validate","valid","errors","map","e","instancePath","message","warnings","err"],"mappings":"AAAA,SAASA,GAAG,QAA0B,MAAM;AAC5C,OAAOC,gBAAgB,cAAc;AACrC,YAAYC,QAAQ,KAAK;AACzB,OAAOC,gBAAgB,mBAAmB;AAC1C,YAAYC,UAAU,OAAO;AAC7B,YAAYC,SAAS,MAAM;AAE3B,iFAAiF;AACjF,MAAMC,YAAYF,KAAKG,OAAO,CAAC,OAAOC,eAAe,cAAcA,aAAaH,IAAII,aAAa,CAAC,YAAYJ,GAAG;AACjH,MAAMK,cAAcP,WAAWG;AAW/B,8CAA8C;AAC9C,IAAIK,cAA6B;AACjC,IAAIC,iBAAoD;AAExD;;CAEC,GACD,SAASC;IACP,IAAIF,aAAa,OAAOA;IAExB,MAAMG,aAAaV,KAAKW,IAAI,CAACL,aAAa;IAC1C,IAAI,CAACR,GAAGc,UAAU,CAACF,aAAa;QAC9B,MAAM,IAAIG,MAAM,CAAC,6BAA6B,EAAEH,YAAY;IAC9D;IAEAH,cAAcO,KAAKC,KAAK,CAACjB,GAAGkB,YAAY,CAACN,YAAY;IACrD,OAAOH;AACT;AAEA;;CAEC,GACD,SAASU;IACP,IAAIT,gBAAgB,OAAOA;IAE3B,MAAMU,SAAST;IACf,MAAMU,MAAM,IAAIvB,IAAI;QAClBwB,WAAW;QACXC,SAAS;QACTC,cAAc;IAChB;IAEA,2CAA2C;IAC3CzB,WAAWsB;IAEXX,iBAAiBW,IAAII,OAAO,CAACL;IAC7B,OAAOV;AACT;AAEA;;;;;CAKC,GACD,OAAO,SAASgB,gBAAgBC,OAAgB;IAC9C,IAAI;QACF,MAAMC,WAAWT;QACjB,MAAMU,QAAQD,SAASD;QAEvB,IAAI,CAACE,OAAO;gBAERD;YADF,MAAME,SACJF,EAAAA,mBAAAA,SAASE,MAAM,cAAfF,uCAAAA,iBAAiBG,GAAG,CAAC,CAACC;gBACpB,MAAM9B,OAAO8B,EAAEC,YAAY,IAAI;gBAC/B,OAAO,GAAG/B,KAAK,EAAE,EAAE8B,EAAEE,OAAO,EAAE;YAChC,OAAM,EAAE;YAEV,OAAO;gBAAEL,OAAO;gBAAOC;YAAO;QAChC;QAEA,MAAMK,WAAqB,EAAE;QAC7B,OAAO;YAAEN,OAAO;YAAMM;QAAS;IACjC,EAAE,OAAOC,KAAK;QACZ,OAAO;YACLP,OAAO;YACPC,QAAQ;gBAAC,CAAC,iCAAiC,EAAE,AAACM,IAAcF,OAAO,EAAE;aAAC;QACxE;IACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/libs/client/src/connection/connect-client.ts"],"sourcesContent":["/**\n * connect-mcp-client.ts\n *\n * Helper to connect MCP SDK clients to servers with intelligent transport inference.\n * Automatically detects transport type from URL protocol or type field.\n */\n\nimport '../monkey-patches.ts';\n\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';\nimport { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';\nimport type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';\nimport getPort from 'get-port';\nimport { probeAuthCapabilities } from '../auth/index.ts';\nimport { DcrAuthenticator, type DcrAuthenticatorOptions } from '../dcr/index.ts';\nimport type { ServerProcess } from '../spawn/spawn-server.ts';\nimport type { ServersConfig } from '../spawn/spawn-servers.ts';\n\n/**\n * Minimal interface for connecting to servers.\n * Only needs config and servers map for connection logic.\n */\ninterface RegistryLike {\n config: ServersConfig;\n servers: Map<string, ServerProcess>;\n}\n\nimport type { McpServerEntry, TransportType } from '../types.ts';\nimport { logger as defaultLogger, type Logger } from '../utils/logger.ts';\nimport { ExistingProcessTransport } from './existing-process-transport.ts';\nimport { waitForHttpReady } from './wait-for-http-ready.ts';\n\n/**\n * Wrap promise with timeout - throws if promise takes too long\n * Clears timeout when promise completes to prevent hanging event loop\n * @param promise - Promise to wrap\n * @param ms - Timeout in milliseconds\n * @param operation - Description of operation for error message\n * @returns Promise result or timeout error\n */\nasync function withTimeout<T>(promise: Promise<T>, ms: number, operation: string): Promise<T> {\n let timeoutId: NodeJS.Timeout;\n\n return Promise.race([\n promise.finally(() => clearTimeout(timeoutId)),\n new Promise<T>((_, reject) => {\n timeoutId = setTimeout(() => reject(new Error(`Timeout after ${ms}ms: ${operation}`)), ms);\n }),\n ]);\n}\n\n/**\n * Extract base URL from MCP server URL\n * @param mcpUrl - Full MCP endpoint URL (e.g., https://example.com/mcp)\n * @returns Base URL (e.g., https://example.com)\n */\nfunction extractBaseUrl(mcpUrl: string): string {\n const url = new URL(mcpUrl);\n return `${url.protocol}//${url.host}`;\n}\n\n/**\n * Infer transport type from server configuration with validation.\n *\n * Priority:\n * 1. Explicit type field (if present)\n * 2. URL protocol (if URL present): http://, https://\n * 3. Default to 'stdio' (if neither present)\n *\n * @param config - Server configuration\n * @returns Transport type\n * @throws Error if configuration is invalid or has conflicts\n */\nfunction inferTransportType(config: McpServerEntry): TransportType {\n // Priority 1: Explicit type field\n if (config.type) {\n // Validate consistency with URL if both present\n if (config.url) {\n const url = new URL(config.url);\n const protocol = url.protocol;\n\n if ((protocol === 'http:' || protocol === 'https:') && config.type !== 'http' && config.type !== 'sse-ide') {\n throw new Error(`Conflicting transport: URL protocol '${protocol}' requires type 'http', but got '${config.type}'`);\n }\n }\n\n // Return normalized type\n if (config.type === 'http' || config.type === 'sse-ide') return 'http';\n if (config.type === 'stdio') return 'stdio';\n\n throw new Error(`Unsupported transport type: ${config.type}`);\n }\n\n // Priority 2: Infer from URL protocol\n if (config.url) {\n const url = new URL(config.url);\n const protocol = url.protocol;\n\n if (protocol === 'http:' || protocol === 'https:') {\n return 'http';\n }\n throw new Error(`Unsupported URL protocol: ${protocol}`);\n }\n\n // Priority 3: Default to stdio\n return 'stdio';\n}\n\n/**\n * Connect MCP SDK client to server with full readiness handling.\n * @internal - Use registry.connect() instead\n *\n * **Completely handles readiness**: transport availability + MCP protocol handshake.\n *\n * Transport is intelligently inferred and handled:\n * - **Stdio servers**: Direct MCP connect (fast for spawned processes)\n * - **HTTP servers**: Transport polling (/mcp endpoint) + MCP connect\n * - **Registry result**: Handles both spawned and external servers\n *\n * Returns only when server is fully MCP-ready (initialize handshake complete).\n *\n * @param registryOrConfig - Result from createServerRegistry() or servers config object\n * @param serverName - Server name from servers config\n * @returns Connected MCP SDK Client (guaranteed ready)\n *\n * @example\n * // Using registry (recommended)\n * const registry = createServerRegistry({ echo: { command: 'node', args: ['server.ts'] } });\n * const client = await registry.connect('echo');\n * // Server is fully ready - transport available + MCP handshake complete\n *\n * @example\n * // HTTP server readiness (waits for /mcp polling + MCP handshake)\n * const registry = createServerRegistry(\n * { http: { type: 'http', url: 'http://localhost:3000/mcp', start: {...} } },\n * { dialects: ['start'] }\n * );\n * const client = await registry.connect('http');\n * // 1. Waits for HTTP server to respond on /mcp\n * // 2. Performs MCP initialize handshake\n * // 3. Returns ready client\n */\nexport async function connectMcpClient(\n registryOrConfig: RegistryLike | ServersConfig,\n serverName: string,\n options?: {\n dcrAuthenticator?: Partial<DcrAuthenticatorOptions>;\n logger?: Logger;\n }\n): Promise<Client> {\n // Detect whether we have a RegistryLike instance or just config\n const isRegistry = 'servers' in registryOrConfig && registryOrConfig.servers instanceof Map;\n const serversConfig = isRegistry ? (registryOrConfig as RegistryLike).config : registryOrConfig;\n const registry = isRegistry ? (registryOrConfig as RegistryLike) : undefined;\n const logger = options?.logger ?? defaultLogger;\n\n const serverConfig = serversConfig[serverName];\n\n if (!serverConfig) {\n const available = Object.keys(serversConfig).join(', ');\n throw new Error(`Server '${serverName}' not found in config. Available servers: ${available || 'none'}`);\n }\n\n // Infer transport type with validation\n const transportType = inferTransportType(serverConfig);\n\n // Create MCP client\n const client = new Client({ name: 'mcp-cli-client', version: '1.0.0' }, { capabilities: {} });\n\n // Connect based on inferred transport\n if (transportType === 'stdio') {\n // Check if we have a spawned process in the registry\n const serverHandle = registry?.servers.get(serverName);\n\n if (serverHandle) {\n // Reuse the already-spawned process\n const transport = new ExistingProcessTransport(serverHandle.process);\n await client.connect(transport);\n } else {\n // No registry or server not in registry - spawn new process directly\n // This is the standard fallback when process management is not used\n if (!serverConfig.command) {\n throw new Error(`Server '${serverName}' has stdio transport but missing 'command' field`);\n }\n\n const transport = new StdioClientTransport({\n command: serverConfig.command,\n args: serverConfig.args || [],\n env: serverConfig.env || {},\n });\n\n // client.connect() performs initialize handshake - when it resolves, server is ready\n await client.connect(transport);\n }\n } else if (transportType === 'http') {\n if (!('url' in serverConfig) || !serverConfig.url) {\n throw new Error(`Server '${serverName}' has http transport but missing 'url' field`);\n }\n\n // Check if this is a freshly spawned HTTP server (from registry)\n // that might not be ready yet - transport readiness check needed\n const isSpawnedHttp = registry?.servers.has(serverName);\n\n if (isSpawnedHttp) {\n logger.debug(`[connectMcpClient] waiting for HTTP server '${serverName}' at ${serverConfig.url}`);\n await waitForHttpReady(serverConfig.url);\n logger.debug(`[connectMcpClient] HTTP server '${serverName}' ready`);\n }\n\n const url = new URL(serverConfig.url);\n\n // Check for DCR support and handle authentication automatically\n const baseUrl = extractBaseUrl(serverConfig.url);\n const capabilities = await withTimeout(probeAuthCapabilities(baseUrl), 5000, 'DCR capability discovery');\n\n let authToken: string | undefined;\n\n if (capabilities.supportsDcr) {\n logger.debug(`đ Server '${serverName}' supports DCR authentication`);\n\n // Get available port and create the exact redirect URI to use\n const port = await getPort();\n const redirectUri = `http://localhost:${port}/callback`;\n\n // Handle authentication using DcrAuthenticator with fully resolved redirectUri\n const authenticator = new DcrAuthenticator({\n headless: false,\n redirectUri,\n logger,\n ...options?.dcrAuthenticator,\n });\n\n // Ensure we have valid tokens (performs DCR + OAuth if needed)\n const tokens = await authenticator.ensureAuthenticated(baseUrl, capabilities);\n authToken = tokens.accessToken;\n\n logger.debug(`â
Authentication complete for '${serverName}'`);\n } else {\n logger.debug(`âšī¸ Server '${serverName}' does not support DCR - connecting without authentication`);\n }\n\n try {\n // Try modern Streamable HTTP first (protocol version 2025-03-26)\n // Merge static headers from config with DCR auth headers (DCR Authorization takes precedence)\n const staticHeaders = serverConfig.headers || {};\n const dcrHeaders = authToken ? { Authorization: `Bearer ${authToken}` } : {};\n const mergedHeaders = { ...staticHeaders, ...dcrHeaders };\n\n const transportOptions =\n Object.keys(mergedHeaders).length > 0\n ? {\n requestInit: {\n headers: mergedHeaders,\n },\n }\n : undefined;\n\n const transport = new StreamableHTTPClientTransport(url, transportOptions);\n // Type assertion: SDK transport has sessionId: string | undefined but Transport expects string\n // This is safe at runtime - the undefined is valid per MCP spec\n await withTimeout(client.connect(transport as unknown as Transport), 30000, 'StreamableHTTP connection');\n } catch (error) {\n // Fall back to SSE transport (MCP protocol version 2024-11-05)\n // SSE is a standard MCP transport used by many servers (e.g., FastMCP ecosystem)\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n // Fast-fail: Don't try SSE if connection was refused (server not running)\n // Check error.cause.code for ECONNREFUSED (fetch errors wrap the actual error in cause)\n const cause = error instanceof Error ? (error as Error & { cause?: { code?: string } }).cause : undefined;\n const isConnectionRefused = cause?.code === 'ECONNREFUSED' || errorMessage.includes('Connection refused');\n\n if (isConnectionRefused) {\n // Clean up client resources before throwing\n await client.close().catch(() => {});\n throw new Error(`Server not running at ${url}`);\n }\n\n // Check for known errors that indicate SSE fallback is needed\n const shouldFallback =\n errorMessage.includes('Missing session ID') || // FastMCP specific\n errorMessage.includes('404') || // Server doesn't have streamable HTTP endpoint\n errorMessage.includes('405'); // Method not allowed\n\n if (shouldFallback) {\n logger.warn(`Streamable HTTP failed (${errorMessage}), falling back to SSE transport`);\n } else {\n logger.warn('Streamable HTTP connection failed, trying SSE transport as fallback');\n }\n\n // Create new client for SSE transport (required per SDK pattern)\n const sseClient = new Client({ name: 'mcp-cli-client', version: '1.0.0' }, { capabilities: {} });\n\n // SSE transport with merged headers (static + DCR auth)\n // Reuse the same header merging logic as Streamable HTTP\n const staticHeaders = serverConfig.headers || {};\n const dcrHeaders = authToken ? { Authorization: `Bearer ${authToken}` } : {};\n const mergedHeaders = { ...staticHeaders, ...dcrHeaders };\n\n const sseTransportOptions =\n Object.keys(mergedHeaders).length > 0\n ? {\n requestInit: {\n headers: mergedHeaders,\n },\n }\n : undefined;\n\n const sseTransport = new SSEClientTransport(url, sseTransportOptions);\n\n try {\n await withTimeout(sseClient.connect(sseTransport), 30000, 'SSE connection');\n // Return SSE client instead of original\n return sseClient;\n } catch (sseError) {\n // SSE connection failed - clean up both clients before throwing\n await Promise.all([client.close().catch(() => {}), sseClient.close().catch(() => {})]);\n throw sseError;\n }\n }\n }\n\n return client; // Guaranteed ready when returned\n}\n"],"names":["Client","SSEClientTransport","StdioClientTransport","StreamableHTTPClientTransport","getPort","probeAuthCapabilities","DcrAuthenticator","logger","defaultLogger","ExistingProcessTransport","waitForHttpReady","withTimeout","promise","ms","operation","timeoutId","Promise","race","finally","clearTimeout","_","reject","setTimeout","Error","extractBaseUrl","mcpUrl","url","URL","protocol","host","inferTransportType","config","type","connectMcpClient","registryOrConfig","serverName","options","isRegistry","servers","Map","serversConfig","registry","undefined","serverConfig","available","Object","keys","join","transportType","client","name","version","capabilities","serverHandle","get","transport","process","connect","command","args","env","isSpawnedHttp","has","debug","baseUrl","authToken","supportsDcr","port","redirectUri","authenticator","headless","dcrAuthenticator","tokens","ensureAuthenticated","accessToken","staticHeaders","headers","dcrHeaders","Authorization","mergedHeaders","transportOptions","length","requestInit","error","errorMessage","message","String","cause","isConnectionRefused","code","includes","close","catch","shouldFallback","warn","sseClient","sseTransportOptions","sseTransport","sseError","all"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/client/src/connection/connect-client.ts"],"sourcesContent":["/**\n * connect-mcp-client.ts\n *\n * Helper to connect MCP SDK clients to servers with intelligent transport inference.\n * Automatically detects transport type from URL protocol or type field.\n */\n\nimport '../monkey-patches.ts';\n\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';\nimport { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';\nimport type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';\nimport getPort from 'get-port';\nimport { probeAuthCapabilities } from '../auth/index.ts';\nimport { DcrAuthenticator, type DcrAuthenticatorOptions } from '../dcr/index.ts';\nimport type { ServerProcess } from '../spawn/spawn-server.ts';\nimport type { ServersConfig } from '../spawn/spawn-servers.ts';\n\n/**\n * Minimal interface for connecting to servers.\n * Only needs config and servers map for connection logic.\n */\ninterface RegistryLike {\n config: ServersConfig;\n servers: Map<string, ServerProcess>;\n}\n\nimport type { McpServerEntry, TransportType } from '../types.ts';\nimport { logger as defaultLogger, type Logger } from '../utils/logger.ts';\nimport { ExistingProcessTransport } from './existing-process-transport.ts';\nimport { waitForHttpReady } from './wait-for-http-ready.ts';\n\n/**\n * Wrap promise with timeout - throws if promise takes too long\n * Clears timeout when promise completes to prevent hanging event loop\n * @param promise - Promise to wrap\n * @param ms - Timeout in milliseconds\n * @param operation - Description of operation for error message\n * @returns Promise result or timeout error\n */\nasync function withTimeout<T>(promise: Promise<T>, ms: number, operation: string): Promise<T> {\n let timeoutId: NodeJS.Timeout;\n\n return Promise.race([\n promise.finally(() => clearTimeout(timeoutId)),\n new Promise<T>((_, reject) => {\n timeoutId = setTimeout(() => reject(new Error(`Timeout after ${ms}ms: ${operation}`)), ms);\n }),\n ]);\n}\n\n/**\n * Extract base URL from MCP server URL\n * @param mcpUrl - Full MCP endpoint URL (e.g., https://example.com/mcp)\n * @returns Base URL (e.g., https://example.com)\n */\nfunction extractBaseUrl(mcpUrl: string): string {\n const url = new URL(mcpUrl);\n return `${url.protocol}//${url.host}`;\n}\n\n/**\n * Infer transport type from server configuration with validation.\n *\n * Priority:\n * 1. Explicit type field (if present)\n * 2. URL protocol (if URL present): http://, https://\n * 3. Default to 'stdio' (if neither present)\n *\n * @param config - Server configuration\n * @returns Transport type\n * @throws Error if configuration is invalid or has conflicts\n */\nfunction inferTransportType(config: McpServerEntry): TransportType {\n // Priority 1: Explicit type field\n if (config.type) {\n // Validate consistency with URL if both present\n if (config.url) {\n const url = new URL(config.url);\n const protocol = url.protocol;\n\n if ((protocol === 'http:' || protocol === 'https:') && config.type !== 'http' && config.type !== 'sse-ide') {\n throw new Error(`Conflicting transport: URL protocol '${protocol}' requires type 'http', but got '${config.type}'`);\n }\n }\n\n // Return normalized type\n if (config.type === 'http' || config.type === 'sse-ide') return 'http';\n if (config.type === 'stdio') return 'stdio';\n\n throw new Error(`Unsupported transport type: ${config.type}`);\n }\n\n // Priority 2: Infer from URL protocol\n if (config.url) {\n const url = new URL(config.url);\n const protocol = url.protocol;\n\n if (protocol === 'http:' || protocol === 'https:') {\n return 'http';\n }\n throw new Error(`Unsupported URL protocol: ${protocol}`);\n }\n\n // Priority 3: Default to stdio\n return 'stdio';\n}\n\n/**\n * Connect MCP SDK client to server with full readiness handling.\n * @internal - Use registry.connect() instead\n *\n * **Completely handles readiness**: transport availability + MCP protocol handshake.\n *\n * Transport is intelligently inferred and handled:\n * - **Stdio servers**: Direct MCP connect (fast for spawned processes)\n * - **HTTP servers**: Transport polling (/mcp endpoint) + MCP connect\n * - **Registry result**: Handles both spawned and external servers\n *\n * Returns only when server is fully MCP-ready (initialize handshake complete).\n *\n * @param registryOrConfig - Result from createServerRegistry() or servers config object\n * @param serverName - Server name from servers config\n * @returns Connected MCP SDK Client (guaranteed ready)\n *\n * @example\n * // Using registry (recommended)\n * const registry = createServerRegistry({ echo: { command: 'node', args: ['server.ts'] } });\n * const client = await registry.connect('echo');\n * // Server is fully ready - transport available + MCP handshake complete\n *\n * @example\n * // HTTP server readiness (waits for /mcp polling + MCP handshake)\n * const registry = createServerRegistry(\n * { http: { type: 'http', url: 'http://localhost:3000/mcp', start: {...} } },\n * { dialects: ['start'] }\n * );\n * const client = await registry.connect('http');\n * // 1. Waits for HTTP server to respond on /mcp\n * // 2. Performs MCP initialize handshake\n * // 3. Returns ready client\n */\nexport async function connectMcpClient(\n registryOrConfig: RegistryLike | ServersConfig,\n serverName: string,\n options?: {\n dcrAuthenticator?: Partial<DcrAuthenticatorOptions>;\n logger?: Logger;\n }\n): Promise<Client> {\n // Detect whether we have a RegistryLike instance or just config\n const isRegistry = 'servers' in registryOrConfig && registryOrConfig.servers instanceof Map;\n const serversConfig = isRegistry ? (registryOrConfig as RegistryLike).config : registryOrConfig;\n const registry = isRegistry ? (registryOrConfig as RegistryLike) : undefined;\n const logger = options?.logger ?? defaultLogger;\n\n const serverConfig = serversConfig[serverName];\n\n if (!serverConfig) {\n const available = Object.keys(serversConfig).join(', ');\n throw new Error(`Server '${serverName}' not found in config. Available servers: ${available || 'none'}`);\n }\n\n // Infer transport type with validation\n const transportType = inferTransportType(serverConfig);\n\n // Create MCP client\n const client = new Client({ name: 'mcp-cli-client', version: '1.0.0' }, { capabilities: {} });\n\n // Connect based on inferred transport\n if (transportType === 'stdio') {\n // Check if we have a spawned process in the registry\n const serverHandle = registry?.servers.get(serverName);\n\n if (serverHandle) {\n // Reuse the already-spawned process\n const transport = new ExistingProcessTransport(serverHandle.process);\n await client.connect(transport);\n } else {\n // No registry or server not in registry - spawn new process directly\n // This is the standard fallback when process management is not used\n if (!serverConfig.command) {\n throw new Error(`Server '${serverName}' has stdio transport but missing 'command' field`);\n }\n\n const transport = new StdioClientTransport({\n command: serverConfig.command,\n args: serverConfig.args || [],\n env: serverConfig.env || {},\n });\n\n // client.connect() performs initialize handshake - when it resolves, server is ready\n await client.connect(transport);\n }\n } else if (transportType === 'http') {\n if (!('url' in serverConfig) || !serverConfig.url) {\n throw new Error(`Server '${serverName}' has http transport but missing 'url' field`);\n }\n\n // Check if this is a freshly spawned HTTP server (from registry)\n // that might not be ready yet - transport readiness check needed\n const isSpawnedHttp = registry?.servers.has(serverName);\n\n if (isSpawnedHttp) {\n logger.debug(`[connectMcpClient] waiting for HTTP server '${serverName}' at ${serverConfig.url}`);\n await waitForHttpReady(serverConfig.url);\n logger.debug(`[connectMcpClient] HTTP server '${serverName}' ready`);\n }\n\n const url = new URL(serverConfig.url);\n\n // Check for DCR support and handle authentication automatically\n const baseUrl = extractBaseUrl(serverConfig.url);\n const capabilities = await withTimeout(probeAuthCapabilities(baseUrl), 5000, 'DCR capability discovery');\n\n let authToken: string | undefined;\n\n if (capabilities.supportsDcr) {\n logger.debug(`đ Server '${serverName}' supports DCR authentication`);\n\n // Get available port and create the exact redirect URI to use\n const port = await getPort();\n const redirectUri = `http://localhost:${port}/callback`;\n\n // Handle authentication using DcrAuthenticator with fully resolved redirectUri\n const authenticator = new DcrAuthenticator({\n headless: false,\n redirectUri,\n logger,\n ...options?.dcrAuthenticator,\n });\n\n // Ensure we have valid tokens (performs DCR + OAuth if needed)\n const tokens = await authenticator.ensureAuthenticated(baseUrl, capabilities);\n authToken = tokens.accessToken;\n\n logger.debug(`â
Authentication complete for '${serverName}'`);\n } else {\n logger.debug(`âšī¸ Server '${serverName}' does not support DCR - connecting without authentication`);\n }\n\n try {\n // Try modern Streamable HTTP first (protocol version 2025-03-26)\n // Merge static headers from config with DCR auth headers (DCR Authorization takes precedence)\n const staticHeaders = serverConfig.headers || {};\n const dcrHeaders = authToken ? { Authorization: `Bearer ${authToken}` } : {};\n const mergedHeaders = { ...staticHeaders, ...dcrHeaders };\n\n const transportOptions =\n Object.keys(mergedHeaders).length > 0\n ? {\n requestInit: {\n headers: mergedHeaders,\n },\n }\n : undefined;\n\n const transport = new StreamableHTTPClientTransport(url, transportOptions);\n // Type assertion: SDK transport has sessionId: string | undefined but Transport expects string\n // This is safe at runtime - the undefined is valid per MCP spec\n await withTimeout(client.connect(transport as unknown as Transport), 30000, 'StreamableHTTP connection');\n } catch (error) {\n // Fall back to SSE transport (MCP protocol version 2024-11-05)\n // SSE is a standard MCP transport used by many servers (e.g., FastMCP ecosystem)\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n // Fast-fail: Don't try SSE if connection was refused (server not running)\n // Check error.cause.code for ECONNREFUSED (fetch errors wrap the actual error in cause)\n const cause = error instanceof Error ? (error as Error & { cause?: { code?: string } }).cause : undefined;\n const isConnectionRefused = cause?.code === 'ECONNREFUSED' || errorMessage.includes('Connection refused');\n\n if (isConnectionRefused) {\n // Clean up client resources before throwing\n await client.close().catch(() => {});\n throw new Error(`Server not running at ${url}`);\n }\n\n // Check for known errors that indicate SSE fallback is needed\n const shouldFallback =\n errorMessage.includes('Missing session ID') || // FastMCP specific\n errorMessage.includes('404') || // Server doesn't have streamable HTTP endpoint\n errorMessage.includes('405'); // Method not allowed\n\n if (shouldFallback) {\n logger.warn(`Streamable HTTP failed (${errorMessage}), falling back to SSE transport`);\n } else {\n logger.warn('Streamable HTTP connection failed, trying SSE transport as fallback');\n }\n\n // Create new client for SSE transport (required per SDK pattern)\n const sseClient = new Client({ name: 'mcp-cli-client', version: '1.0.0' }, { capabilities: {} });\n\n // SSE transport with merged headers (static + DCR auth)\n // Reuse the same header merging logic as Streamable HTTP\n const staticHeaders = serverConfig.headers || {};\n const dcrHeaders = authToken ? { Authorization: `Bearer ${authToken}` } : {};\n const mergedHeaders = { ...staticHeaders, ...dcrHeaders };\n\n const sseTransportOptions =\n Object.keys(mergedHeaders).length > 0\n ? {\n requestInit: {\n headers: mergedHeaders,\n },\n }\n : undefined;\n\n const sseTransport = new SSEClientTransport(url, sseTransportOptions);\n\n try {\n await withTimeout(sseClient.connect(sseTransport), 30000, 'SSE connection');\n // Return SSE client instead of original\n return sseClient;\n } catch (sseError) {\n // SSE connection failed - clean up both clients before throwing\n await Promise.all([client.close().catch(() => {}), sseClient.close().catch(() => {})]);\n throw sseError;\n }\n }\n }\n\n return client; // Guaranteed ready when returned\n}\n"],"names":["Client","SSEClientTransport","StdioClientTransport","StreamableHTTPClientTransport","getPort","probeAuthCapabilities","DcrAuthenticator","logger","defaultLogger","ExistingProcessTransport","waitForHttpReady","withTimeout","promise","ms","operation","timeoutId","Promise","race","finally","clearTimeout","_","reject","setTimeout","Error","extractBaseUrl","mcpUrl","url","URL","protocol","host","inferTransportType","config","type","connectMcpClient","registryOrConfig","serverName","options","isRegistry","servers","Map","serversConfig","registry","undefined","serverConfig","available","Object","keys","join","transportType","client","name","version","capabilities","serverHandle","get","transport","process","connect","command","args","env","isSpawnedHttp","has","debug","baseUrl","authToken","supportsDcr","port","redirectUri","authenticator","headless","dcrAuthenticator","tokens","ensureAuthenticated","accessToken","staticHeaders","headers","dcrHeaders","Authorization","mergedHeaders","transportOptions","length","requestInit","error","errorMessage","message","String","cause","isConnectionRefused","code","includes","close","catch","shouldFallback","warn","sseClient","sseTransportOptions","sseTransport","sseError","all"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/Projects/
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/client/src/connection/existing-process-transport.ts"],"sourcesContent":["/**\n * existing-process-transport.ts\n *\n * MCP transport that wraps an existing child process for stdio communication.\n * Used when connecting to servers already spawned by initServers().\n */\n\nimport { ReadBuffer, serializeMessage } from '@modelcontextprotocol/sdk/shared/stdio.js';\nimport type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';\nimport type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';\nimport type { ChildProcess } from 'child_process';\n\n/**\n * Transport that communicates with an existing child process via stdio.\n * Does NOT spawn a new process - uses the one provided.\n */\nexport class ExistingProcessTransport implements Transport {\n private _process: ChildProcess;\n private _readBuffer: ReadBuffer;\n private _dataHandler: ((chunk: Buffer) => void) | null = null;\n private _errorHandler: ((error: Error) => void) | null = null;\n\n // Transport interface callbacks\n onclose?: () => void;\n onerror?: (error: Error) => void;\n onmessage?: (message: JSONRPCMessage) => void;\n\n constructor(process: ChildProcess) {\n if (!process.stdin || !process.stdout) {\n throw new Error('Child process must have stdin and stdout pipes');\n }\n\n this._process = process;\n this._readBuffer = new ReadBuffer();\n }\n\n /**\n * Start the transport - sets up stdio listeners on existing process.\n */\n async start(): Promise<void> {\n return new Promise((resolve, reject) => {\n // Listen for process events\n this._process.on('error', (error) => {\n this.onerror?.(error);\n reject(error);\n });\n\n this._process.on('close', () => {\n this.onclose?.();\n });\n\n // Create and save data handler for close\n this._dataHandler = (chunk: Buffer) => {\n this._readBuffer.append(chunk);\n this.processReadBuffer();\n };\n\n // Create and save error handler for close\n this._errorHandler = (error: Error) => {\n this.onerror?.(error);\n };\n\n // Listen for stdout data (MCP messages)\n this._process.stdout?.on('data', this._dataHandler);\n this._process.stdout?.on('error', this._errorHandler);\n this._process.stdin?.on('error', this._errorHandler);\n\n // Process is already running - resolve immediately\n resolve();\n });\n }\n\n /**\n * Process buffered messages from stdout.\n */\n private processReadBuffer(): void {\n while (true) {\n try {\n const message = this._readBuffer.readMessage();\n if (message === null) {\n break;\n }\n this.onmessage?.(message);\n } catch (error) {\n this.onerror?.(error as Error);\n }\n }\n }\n\n /**\n * Close the transport - close without killing the shared process.\n * The process is managed by the cluster and may have other active connections.\n */\n async close(): Promise<void> {\n if (this._dataHandler) {\n this._process.stdout?.off('data', this._dataHandler);\n this._dataHandler = null;\n }\n\n if (this._errorHandler) {\n this._process.stdout?.off('error', this._errorHandler);\n this._process.stdin?.off('error', this._errorHandler);\n this._errorHandler = null;\n }\n\n this._readBuffer.clear();\n }\n\n /**\n * Send a message to the server via stdin.\n */\n async send(message: JSONRPCMessage): Promise<void> {\n return new Promise((resolve, reject) => {\n if (!this._process.stdin) {\n reject(new Error('stdin is not available'));\n return;\n }\n\n const json = serializeMessage(message);\n\n if (this._process.stdin.write(json)) {\n resolve();\n } else {\n this._process.stdin.once('drain', resolve);\n }\n });\n }\n}\n"],"names":["ReadBuffer","serializeMessage","ExistingProcessTransport","start","Promise","resolve","reject","_process","on","error","onerror","onclose","_dataHandler","chunk","_readBuffer","append","processReadBuffer","_errorHandler","stdout","stdin","message","readMessage","onmessage","close","off","clear","send","Error","json","write","once","process"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/Projects/
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/client/src/connection/types.ts"],"sourcesContent":["/**\n * Type definitions and type guards for CLI\n * Per project convention, all types and runtime validators in single file\n */\n\n/**\n * JSON-serializable value type per MCP spec\n * Used for tool arguments and responses\n */\nexport type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };\n\n/**\n * Tool arguments must be JSON-serializable\n */\nexport type ToolArguments = Record<string, JsonValue>;\n\n/**\n * Spawn metadata for logging and sanitization\n */\nexport interface SpawnMetadata {\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n cwd?: string;\n pid?: number;\n [key: string]: JsonValue;\n}\n\n/**\n * MCP prompt argument structure\n * Explicit undefined for exactOptionalPropertyTypes compatibility\n */\nexport interface PromptArgument {\n name: string;\n description?: string | undefined;\n required?: boolean | undefined;\n}\n\n/**\n * Parsed MCP response structure\n */\nexport interface ParsedResponse {\n result?: JsonValue;\n}\n\n/**\n * Result object structure for error detection\n */\nexport interface ResultObject {\n type?: string;\n error?: string;\n message?: string;\n}\n\n// Type Guards (runtime validation functions)\n\n/**\n * Type guard for JSON-serializable values\n */\nexport function isJsonValue(value: unknown): value is JsonValue {\n if (value === null) return true;\n if (typeof value === 'string') return true;\n if (typeof value === 'number') return true;\n if (typeof value === 'boolean') return true;\n\n if (Array.isArray(value)) {\n return value.every(isJsonValue);\n }\n\n if (typeof value === 'object') {\n return Object.values(value).every(isJsonValue);\n }\n\n return false;\n}\n\n/**\n * Type guard for parsed MCP response\n */\nexport function isParsedResponse(value: unknown): value is ParsedResponse {\n return typeof value === 'object' && value !== null && 'result' in value;\n}\n\n/**\n * Type guard for result object\n */\nexport function isResultObject(value: unknown): value is ResultObject {\n if (typeof value !== 'object' || value === null) return false;\n const obj = value as Record<string, unknown>;\n return (obj.type === undefined || typeof obj.type === 'string') && (obj.error === undefined || typeof obj.error === 'string') && (obj.message === undefined || typeof obj.message === 'string');\n}\n"],"names":["isJsonValue","value","Array","isArray","every","Object","values","isParsedResponse","isResultObject","obj","type","undefined","error","message"],"mappings":"AAAA;;;CAGC,GAED;;;CAGC,GA8CD,6CAA6C;AAE7C;;CAEC,GACD,OAAO,SAASA,YAAYC,KAAc;IACxC,IAAIA,UAAU,MAAM,OAAO;IAC3B,IAAI,OAAOA,UAAU,UAAU,OAAO;IACtC,IAAI,OAAOA,UAAU,UAAU,OAAO;IACtC,IAAI,OAAOA,UAAU,WAAW,OAAO;IAEvC,IAAIC,MAAMC,OAAO,CAACF,QAAQ;QACxB,OAAOA,MAAMG,KAAK,CAACJ;IACrB;IAEA,IAAI,OAAOC,UAAU,UAAU;QAC7B,OAAOI,OAAOC,MAAM,CAACL,OAAOG,KAAK,CAACJ;IACpC;IAEA,OAAO;AACT;AAEA;;CAEC,GACD,OAAO,SAASO,iBAAiBN,KAAc;IAC7C,OAAO,OAAOA,UAAU,YAAYA,UAAU,QAAQ,YAAYA;AACpE;AAEA;;CAEC,GACD,OAAO,SAASO,eAAeP,KAAc;IAC3C,IAAI,OAAOA,UAAU,YAAYA,UAAU,MAAM,OAAO;IACxD,MAAMQ,MAAMR;IACZ,OAAO,AAACQ,CAAAA,IAAIC,IAAI,KAAKC,aAAa,OAAOF,IAAIC,IAAI,KAAK,QAAO,KAAOD,CAAAA,IAAIG,KAAK,KAAKD,aAAa,OAAOF,IAAIG,KAAK,KAAK,QAAO,KAAOH,CAAAA,IAAII,OAAO,KAAKF,aAAa,OAAOF,IAAII,OAAO,KAAK,QAAO;AAC/L"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/Projects/
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/client/src/connection/wait-for-http-ready.ts"],"sourcesContent":["/**\n * wait-for-http-ready.ts\n *\n * Utility for waiting for HTTP servers to become ready.\n */\n\n/**\n * Wait for HTTP server to be ready by checking endpoint accessibility.\n * Used to handle HTTP servers that need time to start listening.\n *\n * @param url - URL to check for server readiness\n * @param timeoutMs - Maximum time to wait in milliseconds (default: 30000)\n * @returns Promise that resolves when server is ready\n * @throws Error if server doesn't become ready within timeout\n */\nexport async function waitForHttpReady(url: string, timeoutMs = 30000): Promise<void> {\n const start = Date.now();\n const maxRetries = Math.ceil(timeoutMs / 100); // Check every 100ms\n\n for (let i = 0; i < maxRetries; i++) {\n try {\n // Use HEAD request to check server is responding\n const response = await fetch(url, {\n method: 'HEAD',\n headers: { Connection: 'close' },\n signal: AbortSignal.timeout(500), // 500ms per attempt\n });\n\n // Server is responding if we get any HTTP status\n if (response.status >= 200 && response.status < 500) {\n return;\n }\n\n // Server error (5xx) - keep trying as it might still be starting\n } catch (_error) {\n // Connection refused, timeout, or network error\n // Server not ready yet, continue polling\n if (i === maxRetries - 1) {\n const elapsed = Date.now() - start;\n throw new Error(`HTTP server ${url} not ready after ${elapsed}ms`);\n }\n }\n\n // Wait before retry\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n\n throw new Error(`HTTP server ${url} not ready after ${timeoutMs}ms`);\n}\n"],"names":["waitForHttpReady","url","timeoutMs","start","Date","now","maxRetries","Math","ceil","i","response","fetch","method","headers","Connection","signal","AbortSignal","timeout","status","_error","elapsed","Error","Promise","resolve","setTimeout"],"mappings":"AAAA;;;;CAIC,GAED;;;;;;;;CAQC,GACD,OAAO,eAAeA,iBAAiBC,GAAW,EAAEC,YAAY,KAAK;IACnE,MAAMC,QAAQC,KAAKC,GAAG;IACtB,MAAMC,aAAaC,KAAKC,IAAI,CAACN,YAAY,MAAM,oBAAoB;IAEnE,IAAK,IAAIO,IAAI,GAAGA,IAAIH,YAAYG,IAAK;QACnC,IAAI;YACF,iDAAiD;YACjD,MAAMC,WAAW,MAAMC,MAAMV,KAAK;gBAChCW,QAAQ;gBACRC,SAAS;oBAAEC,YAAY;gBAAQ;gBAC/BC,QAAQC,YAAYC,OAAO,CAAC;YAC9B;YAEA,iDAAiD;YACjD,IAAIP,SAASQ,MAAM,IAAI,OAAOR,SAASQ,MAAM,GAAG,KAAK;gBACnD;YACF;QAEA,iEAAiE;QACnE,EAAE,OAAOC,QAAQ;YACf,gDAAgD;YAChD,yCAAyC;YACzC,IAAIV,MAAMH,aAAa,GAAG;gBACxB,MAAMc,UAAUhB,KAAKC,GAAG,KAAKF;gBAC7B,MAAM,IAAIkB,MAAM,CAAC,YAAY,EAAEpB,IAAI,iBAAiB,EAAEmB,QAAQ,EAAE,CAAC;YACnE;QACF;QAEA,oBAAoB;QACpB,MAAM,IAAIE,QAAQ,CAACC,UAAYC,WAAWD,SAAS;IACrD;IAEA,MAAM,IAAIF,MAAM,CAAC,YAAY,EAAEpB,IAAI,iBAAiB,EAAEC,UAAU,EAAE,CAAC;AACrE"}
|