@mastra/code-sdk 0.2.0-alpha.8 → 1.0.0-alpha.13

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/mcp/config.ts"],"sourcesContent":["/**\n * MCP server configuration loading from filesystem.\n * Loads from:\n * 1. .claude/settings.local.json (Claude Code compat — lowest priority)\n * 2. ~/.mastracode/mcp.json (global)\n * 3. .mcp.json (project root — Claude Code compatible)\n * 4. .mastracode/mcp.json (project — highest priority)\n *\n * Higher-priority configs override lower ones by server name. The project root\n * `.mcp.json` is read so a project that already keeps MCP servers there for\n * Claude Code does not need to duplicate them under `.mastracode/`.\n */\n\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { DEFAULT_CONFIG_DIR } from '../constants.js';\nimport type { McpConfig, McpHttpOAuthConfig, McpServerConfig, McpSkippedServer } from './types.js';\n\nexport function loadMcpConfig(projectDir: string, configDirName = DEFAULT_CONFIG_DIR): McpConfig {\n const claudeConfig = loadClaudeSettings(projectDir);\n const globalConfig = loadSingleConfig(getGlobalMcpPath(configDirName));\n const rootConfig = loadSingleConfig(getRootMcpPath(projectDir));\n const projectConfig = loadSingleConfig(getProjectMcpPath(projectDir, configDirName));\n\n return mergeConfigs(claudeConfig, globalConfig, rootConfig, projectConfig);\n}\n\nexport function getProjectMcpPath(projectDir: string, configDirName = DEFAULT_CONFIG_DIR): string {\n return path.join(projectDir, configDirName, 'mcp.json');\n}\n\nexport function getRootMcpPath(projectDir: string): string {\n return path.join(projectDir, '.mcp.json');\n}\n\nexport function getGlobalMcpPath(configDirName = DEFAULT_CONFIG_DIR): string {\n return path.join(os.homedir(), configDirName, 'mcp.json');\n}\n\nexport function getClaudeSettingsPath(projectDir: string): string {\n return path.join(projectDir, '.claude', 'settings.local.json');\n}\n\nfunction loadSingleConfig(filePath: string): McpConfig {\n try {\n if (!fs.existsSync(filePath)) return {};\n const raw = fs.readFileSync(filePath, 'utf-8');\n return validateConfig(JSON.parse(raw));\n } catch {\n return {};\n }\n}\n\nfunction loadClaudeSettings(projectDir: string): McpConfig {\n try {\n const filePath = getClaudeSettingsPath(projectDir);\n if (!fs.existsSync(filePath)) return {};\n const raw = fs.readFileSync(filePath, 'utf-8');\n const parsed = JSON.parse(raw);\n // Claude Code stores mcpServers at the top level of settings\n if (parsed?.mcpServers && typeof parsed.mcpServers === 'object') {\n return validateConfig({ mcpServers: parsed.mcpServers });\n }\n return {};\n } catch {\n return {};\n }\n}\n\n/**\n * Expand `${VAR}` and `${VAR:-default}` references in a string from the\n * environment, matching how Claude Code resolves values in `.mcp.json`.\n * A referenced variable that is unset or empty falls back to its default,\n * or to an empty string when no default is given.\n */\nexport function expandEnvVars(value: string, env: NodeJS.ProcessEnv = process.env): string {\n return value.replace(\n /\\$\\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\\}|\\$([A-Za-z_][A-Za-z0-9_]*)/g,\n (_match, bracedName, fallback, bareName) => {\n const name = bracedName ?? bareName;\n const resolved = env[name];\n if (resolved !== undefined && resolved !== '') return resolved;\n return fallback ?? '';\n },\n );\n}\n\n/**\n * Expand `${VAR}` and `$VAR` references in every string-valued HTTP header so that\n * secrets like API keys can be referenced from the environment instead of\n * being hardcoded in `mcp.json`.\n */\nfunction expandHeaderEnvVars(headers: Record<string, unknown>): Record<string, string> {\n const expanded: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (typeof value === 'string') expanded[key] = expandEnvVars(value);\n }\n return expanded;\n}\n\n/**\n * Expand `${VAR}` and `$VAR` references in every string-valued environment\n * variable passed to a stdio server, so that secrets can be referenced from the\n * host environment instead of being hardcoded in `mcp.json`.\n */\nfunction expandEnvValues(env: Record<string, unknown>): Record<string, string> {\n const expanded: Record<string, string> = {};\n for (const [key, value] of Object.entries(env)) {\n if (typeof value === 'string') expanded[key] = expandEnvVars(value);\n }\n return expanded;\n}\n\n/**\n * Classify a raw server entry as stdio, http, or skip (with reason).\n */\nexport function classifyServerEntry(raw: unknown): { kind: 'stdio' | 'http' | 'skip'; reason?: string } {\n if (!raw || typeof raw !== 'object') {\n return { kind: 'skip', reason: 'Invalid entry: expected an object' };\n }\n\n const obj = raw as Record<string, unknown>;\n const hasCommand = typeof obj.command === 'string';\n const hasUrl = typeof obj.url === 'string';\n\n if (hasCommand && hasUrl) {\n return { kind: 'skip', reason: 'Cannot specify both \"command\" and \"url\"' };\n }\n\n if (hasCommand) {\n return { kind: 'stdio' };\n }\n\n if (hasUrl) {\n try {\n new URL(expandEnvVars(obj.url as string));\n } catch {\n return { kind: 'skip', reason: `Invalid URL: \"${obj.url}\"` };\n }\n return { kind: 'http' };\n }\n\n return { kind: 'skip', reason: 'Missing required field: \"command\" (stdio) or \"url\" (http)' };\n}\n\nexport function validateConfig(raw: unknown): McpConfig {\n if (!raw || typeof raw !== 'object') return {};\n const obj = raw as Record<string, unknown>;\n\n if (!obj.mcpServers || typeof obj.mcpServers !== 'object') return {};\n\n const servers: Record<string, McpServerConfig> = {};\n const skippedServers: McpSkippedServer[] = [];\n const rawServers = obj.mcpServers as Record<string, unknown>;\n\n for (const [name, entry] of Object.entries(rawServers)) {\n const classification = classifyServerEntry(entry);\n\n if (classification.kind === 'stdio') {\n const e = entry as Record<string, unknown>;\n servers[name] = {\n command: e.command as string,\n args: Array.isArray(e.args) ? (e.args as string[]) : undefined,\n env:\n typeof e.env === 'object' && e.env !== null ? expandEnvValues(e.env as Record<string, unknown>) : undefined,\n };\n } else if (classification.kind === 'http') {\n const e = entry as Record<string, unknown>;\n const oauthResult = parseOAuthConfig(e.oauth);\n if (oauthResult.reason) {\n skippedServers.push({ name, reason: oauthResult.reason });\n continue;\n }\n servers[name] = {\n url: expandEnvVars(e.url as string),\n headers:\n typeof e.headers === 'object' && e.headers !== null\n ? expandHeaderEnvVars(e.headers as Record<string, unknown>)\n : undefined,\n oauth: oauthResult.config,\n };\n } else {\n skippedServers.push({ name, reason: classification.reason! });\n }\n }\n\n const result: McpConfig = {};\n if (Object.keys(servers).length > 0) {\n result.mcpServers = servers;\n }\n if (skippedServers.length > 0) {\n result.skippedServers = skippedServers;\n }\n return result;\n}\n\nfunction parseOAuthConfig(raw: unknown): { config?: McpHttpOAuthConfig; reason?: string } {\n if (raw === undefined) return {};\n if (!raw || typeof raw !== 'object') {\n return { reason: 'Invalid OAuth config: expected an object' };\n }\n\n const obj = raw as Record<string, unknown>;\n if (typeof obj.redirectUrl !== 'string') {\n return { reason: 'Invalid OAuth config: missing required field \"redirectUrl\"' };\n }\n try {\n const redirectUrl = new URL(obj.redirectUrl);\n const isLoopback =\n redirectUrl.hostname === 'localhost' ||\n redirectUrl.hostname.startsWith('127.') ||\n redirectUrl.hostname === '[::1]';\n if (redirectUrl.protocol !== 'https:' && !(redirectUrl.protocol === 'http:' && isLoopback)) {\n return { reason: 'Invalid OAuth redirectUrl: must use HTTPS unless it is a loopback HTTP URL' };\n }\n } catch {\n return { reason: `Invalid OAuth redirectUrl: \"${obj.redirectUrl}\"` };\n }\n\n if (obj.scopes !== undefined && (!Array.isArray(obj.scopes) || obj.scopes.some(scope => typeof scope !== 'string'))) {\n return { reason: 'Invalid OAuth config: \"scopes\" must be an array of strings' };\n }\n\n return {\n config: {\n redirectUrl: obj.redirectUrl,\n clientName: typeof obj.clientName === 'string' ? obj.clientName : undefined,\n scopes: obj.scopes as string[] | undefined,\n clientId: typeof obj.clientId === 'string' ? obj.clientId : undefined,\n clientSecret: typeof obj.clientSecret === 'string' ? obj.clientSecret : undefined,\n },\n };\n}\n\n/**\n * Merge configs: claude (lowest priority) < global < project (highest).\n * Later configs override earlier by server name.\n * Skipped entries are accumulated, but if a higher-priority config provides\n * a valid entry for a skipped name, the skip is removed.\n */\nfunction mergeConfigs(...configs: McpConfig[]): McpConfig {\n const merged: Record<string, McpServerConfig> = {};\n const allSkipped: McpSkippedServer[] = [];\n\n for (const config of configs) {\n if (config.mcpServers) {\n for (const [name, server] of Object.entries(config.mcpServers)) {\n merged[name] = server;\n }\n }\n if (config.skippedServers) {\n allSkipped.push(...config.skippedServers);\n }\n }\n\n // Remove skipped entries that were resolved by a valid config at any priority\n const validNames = new Set(Object.keys(merged));\n const filteredSkipped = allSkipped.filter(s => !validNames.has(s.name));\n\n // Deduplicate skipped entries by name (keep last occurrence — highest priority reason)\n const skippedMap = new Map<string, McpSkippedServer>();\n for (const s of filteredSkipped) {\n skippedMap.set(s.name, s);\n }\n\n const result: McpConfig = {};\n if (Object.keys(merged).length > 0) {\n result.mcpServers = merged;\n }\n if (skippedMap.size > 0) {\n result.skippedServers = Array.from(skippedMap.values());\n }\n return result;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/mcp/config.ts"],"sourcesContent":["/**\n * MCP server configuration loading from filesystem.\n * Loads from:\n * 1. .claude/settings.local.json (Claude Code compat — lowest priority)\n * 2. ~/.mastracode/mcp.json (global)\n * 3. .mcp.json (project root — Claude Code compatible)\n * 4. .mastracode/mcp.json (project — highest priority)\n *\n * Higher-priority configs override lower ones by server name. The project root\n * `.mcp.json` is read so a project that already keeps MCP servers there for\n * Claude Code does not need to duplicate them under `.mastracode/`.\n */\n\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { DEFAULT_CONFIG_DIR } from '../constants.js';\nimport type { McpConfig, McpHttpOAuthConfig, McpServerConfig, McpSkippedServer } from './types.js';\n\n/**\n * Default OAuth redirect URL for servers that do not configure one.\n * Port 1458 is stable across sessions so persisted tokens keep the same\n * storage fingerprint; it sits clear of the ports the Codex login flow\n * reserves (1455/1457). When 1458 is busy the callback server falls back\n * to the next sequential port, which stays covered by the client\n * registration (see `@mastra/mcp`'s `getCallbackUrlCandidates`).\n */\nexport const DEFAULT_OAUTH_REDIRECT_URL = 'http://127.0.0.1:1458/oauth/callback';\n\n// Matches the entire 127.0.0.0/8 range in dotted-quad form. `URL` normalizes\n// IPv4 hosts to four octets (so `127.1` becomes `127.0.0.1`), so anchoring the\n// pattern is enough — and it rejects lookalikes like `127.evil.com` that a\n// `startsWith('127.')` check would wrongly accept.\nconst LOOPBACK_IPV4 = /^127\\.(?:\\d{1,3})\\.(?:\\d{1,3})\\.(?:\\d{1,3})$/;\n\nfunction isLoopbackHostname(hostname: string): boolean {\n return hostname === 'localhost' || hostname === '[::1]' || hostname === '::1' || LOOPBACK_IPV4.test(hostname);\n}\n\n/**\n * Resolve the effective OAuth redirect URL for a server config.\n *\n * `callbackPort` (the Claude Code / Codex convention) takes precedence and\n * synthesizes `http://localhost:<port>/callback`. Config-file entries are\n * normalized by `parseOAuthConfig` (which also enforces that `callbackPort`\n * and `redirectUrl` are mutually exclusive), but programmatically registered\n * servers bypass the parser, so every consumer of the redirect URL must\n * resolve it through this helper rather than reading `redirectUrl` directly.\n */\nexport function resolveOAuthRedirectUrl(oauth: McpHttpOAuthConfig | undefined): string {\n if (oauth?.callbackPort !== undefined) {\n return `http://localhost:${oauth.callbackPort}/callback`;\n }\n return oauth?.redirectUrl ?? DEFAULT_OAUTH_REDIRECT_URL;\n}\n\nexport function loadMcpConfig(projectDir: string, configDirName = DEFAULT_CONFIG_DIR): McpConfig {\n const claudeConfig = loadClaudeSettings(projectDir);\n const globalConfig = loadSingleConfig(getGlobalMcpPath(configDirName));\n const rootConfig = loadSingleConfig(getRootMcpPath(projectDir));\n const projectConfig = loadSingleConfig(getProjectMcpPath(projectDir, configDirName));\n\n return mergeConfigs(claudeConfig, globalConfig, rootConfig, projectConfig);\n}\n\nexport function getProjectMcpPath(projectDir: string, configDirName = DEFAULT_CONFIG_DIR): string {\n return path.join(projectDir, configDirName, 'mcp.json');\n}\n\nexport function getRootMcpPath(projectDir: string): string {\n return path.join(projectDir, '.mcp.json');\n}\n\nexport function getGlobalMcpPath(configDirName = DEFAULT_CONFIG_DIR): string {\n return path.join(os.homedir(), configDirName, 'mcp.json');\n}\n\nexport function getClaudeSettingsPath(projectDir: string): string {\n return path.join(projectDir, '.claude', 'settings.local.json');\n}\n\nfunction loadSingleConfig(filePath: string): McpConfig {\n try {\n if (!fs.existsSync(filePath)) return {};\n const raw = fs.readFileSync(filePath, 'utf-8');\n return validateConfig(JSON.parse(raw));\n } catch {\n return {};\n }\n}\n\nfunction loadClaudeSettings(projectDir: string): McpConfig {\n try {\n const filePath = getClaudeSettingsPath(projectDir);\n if (!fs.existsSync(filePath)) return {};\n const raw = fs.readFileSync(filePath, 'utf-8');\n const parsed = JSON.parse(raw);\n // Claude Code stores mcpServers at the top level of settings\n if (parsed?.mcpServers && typeof parsed.mcpServers === 'object') {\n return validateConfig({ mcpServers: parsed.mcpServers });\n }\n return {};\n } catch {\n return {};\n }\n}\n\n/**\n * Expand `${VAR}` and `${VAR:-default}` references in a string from the\n * environment, matching how Claude Code resolves values in `.mcp.json`.\n * A referenced variable that is unset or empty falls back to its default,\n * or to an empty string when no default is given.\n */\nexport function expandEnvVars(value: string, env: NodeJS.ProcessEnv = process.env): string {\n return value.replace(\n /\\$\\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\\}|\\$([A-Za-z_][A-Za-z0-9_]*)/g,\n (_match, bracedName, fallback, bareName) => {\n const name = bracedName ?? bareName;\n const resolved = env[name];\n if (resolved !== undefined && resolved !== '') return resolved;\n return fallback ?? '';\n },\n );\n}\n\n/**\n * Expand `${VAR}` and `$VAR` references in every string-valued HTTP header so that\n * secrets like API keys can be referenced from the environment instead of\n * being hardcoded in `mcp.json`.\n */\nfunction expandHeaderEnvVars(headers: Record<string, unknown>): Record<string, string> {\n const expanded: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (typeof value === 'string') expanded[key] = expandEnvVars(value);\n }\n return expanded;\n}\n\n/**\n * Expand `${VAR}` and `$VAR` references in every string-valued environment\n * variable passed to a stdio server, so that secrets can be referenced from the\n * host environment instead of being hardcoded in `mcp.json`.\n */\nfunction expandEnvValues(env: Record<string, unknown>): Record<string, string> {\n const expanded: Record<string, string> = {};\n for (const [key, value] of Object.entries(env)) {\n if (typeof value === 'string') expanded[key] = expandEnvVars(value);\n }\n return expanded;\n}\n\n/**\n * Classify a raw server entry as stdio, http, or skip (with reason).\n */\nexport function classifyServerEntry(raw: unknown): { kind: 'stdio' | 'http' | 'skip'; reason?: string } {\n if (!raw || typeof raw !== 'object') {\n return { kind: 'skip', reason: 'Invalid entry: expected an object' };\n }\n\n const obj = raw as Record<string, unknown>;\n const hasCommand = typeof obj.command === 'string';\n const hasUrl = typeof obj.url === 'string';\n\n if (hasCommand && hasUrl) {\n return { kind: 'skip', reason: 'Cannot specify both \"command\" and \"url\"' };\n }\n\n if (hasCommand) {\n return { kind: 'stdio' };\n }\n\n if (hasUrl) {\n try {\n new URL(expandEnvVars(obj.url as string));\n } catch {\n return { kind: 'skip', reason: `Invalid URL: \"${obj.url}\"` };\n }\n return { kind: 'http' };\n }\n\n return { kind: 'skip', reason: 'Missing required field: \"command\" (stdio) or \"url\" (http)' };\n}\n\nexport function validateConfig(raw: unknown): McpConfig {\n if (!raw || typeof raw !== 'object') return {};\n const obj = raw as Record<string, unknown>;\n\n if (!obj.mcpServers || typeof obj.mcpServers !== 'object') return {};\n\n const servers: Record<string, McpServerConfig> = {};\n const skippedServers: McpSkippedServer[] = [];\n const rawServers = obj.mcpServers as Record<string, unknown>;\n\n for (const [name, entry] of Object.entries(rawServers)) {\n const classification = classifyServerEntry(entry);\n\n if (classification.kind === 'stdio') {\n const e = entry as Record<string, unknown>;\n servers[name] = {\n command: e.command as string,\n args: Array.isArray(e.args) ? (e.args as string[]) : undefined,\n env:\n typeof e.env === 'object' && e.env !== null ? expandEnvValues(e.env as Record<string, unknown>) : undefined,\n };\n } else if (classification.kind === 'http') {\n const e = entry as Record<string, unknown>;\n const oauthResult = parseOAuthConfig(e.oauth);\n if (oauthResult.reason) {\n skippedServers.push({ name, reason: oauthResult.reason });\n continue;\n }\n servers[name] = {\n url: expandEnvVars(e.url as string),\n headers:\n typeof e.headers === 'object' && e.headers !== null\n ? expandHeaderEnvVars(e.headers as Record<string, unknown>)\n : undefined,\n oauth: oauthResult.config,\n };\n } else {\n skippedServers.push({ name, reason: classification.reason! });\n }\n }\n\n const result: McpConfig = {};\n if (Object.keys(servers).length > 0) {\n result.mcpServers = servers;\n }\n if (skippedServers.length > 0) {\n result.skippedServers = skippedServers;\n }\n return result;\n}\n\nfunction parseOAuthConfig(raw: unknown): { config?: McpHttpOAuthConfig; reason?: string } {\n if (raw === undefined) return {};\n if (!raw || typeof raw !== 'object') {\n return { reason: 'Invalid OAuth config: expected an object' };\n }\n\n const obj = raw as Record<string, unknown>;\n if (obj.redirectUrl !== undefined && typeof obj.redirectUrl !== 'string') {\n return { reason: 'Invalid OAuth config: \"redirectUrl\" must be a string' };\n }\n // `callbackPort` is a shorthand for a loopback redirect URL. It synthesizes\n // `http://localhost:<port>/callback` — the convention Claude Code and Codex\n // emit — so those clients' config (e.g. Slack's official MCP plugin config)\n // can be pasted verbatim. `redirectUrl` stays as the full-control escape\n // hatch (any loopback host/path). The two are mutually exclusive to avoid\n // an ambiguous redirect.\n let callbackPortRedirectUrl: string | undefined;\n if (obj.callbackPort !== undefined) {\n if (obj.redirectUrl !== undefined) {\n return { reason: 'Invalid OAuth config: set either \"redirectUrl\" or \"callbackPort\", not both' };\n }\n if (\n typeof obj.callbackPort !== 'number' ||\n !Number.isInteger(obj.callbackPort) ||\n obj.callbackPort < 1 ||\n obj.callbackPort > 65535\n ) {\n return { reason: 'Invalid OAuth config: \"callbackPort\" must be an integer between 1 and 65535' };\n }\n callbackPortRedirectUrl = resolveOAuthRedirectUrl({ callbackPort: obj.callbackPort });\n }\n\n const rawRedirectUrl = callbackPortRedirectUrl ?? obj.redirectUrl ?? DEFAULT_OAUTH_REDIRECT_URL;\n try {\n const redirectUrl = new URL(rawRedirectUrl);\n const isLoopback = isLoopbackHostname(redirectUrl.hostname);\n if (redirectUrl.protocol !== 'https:' && !(redirectUrl.protocol === 'http:' && isLoopback)) {\n return { reason: 'Invalid OAuth redirectUrl: must use HTTPS unless it is a loopback HTTP URL' };\n }\n } catch {\n return { reason: `Invalid OAuth redirectUrl: \"${rawRedirectUrl}\"` };\n }\n\n if (obj.scopes !== undefined && (!Array.isArray(obj.scopes) || obj.scopes.some(scope => typeof scope !== 'string'))) {\n return { reason: 'Invalid OAuth config: \"scopes\" must be an array of strings' };\n }\n\n return {\n config: {\n redirectUrl: rawRedirectUrl,\n clientName: typeof obj.clientName === 'string' ? obj.clientName : undefined,\n scopes: obj.scopes as string[] | undefined,\n clientId: typeof obj.clientId === 'string' ? obj.clientId : undefined,\n clientSecret: typeof obj.clientSecret === 'string' ? obj.clientSecret : undefined,\n },\n };\n}\n\n/**\n * Merge configs: claude (lowest priority) < global < project (highest).\n * Later configs override earlier by server name.\n * Skipped entries are accumulated, but if a higher-priority config provides\n * a valid entry for a skipped name, the skip is removed.\n */\nfunction mergeConfigs(...configs: McpConfig[]): McpConfig {\n const merged: Record<string, McpServerConfig> = {};\n const allSkipped: McpSkippedServer[] = [];\n\n for (const config of configs) {\n if (config.mcpServers) {\n for (const [name, server] of Object.entries(config.mcpServers)) {\n merged[name] = server;\n }\n }\n if (config.skippedServers) {\n allSkipped.push(...config.skippedServers);\n }\n }\n\n // Remove skipped entries that were resolved by a valid config at any priority\n const validNames = new Set(Object.keys(merged));\n const filteredSkipped = allSkipped.filter(s => !validNames.has(s.name));\n\n // Deduplicate skipped entries by name (keep last occurrence — highest priority reason)\n const skippedMap = new Map<string, McpSkippedServer>();\n for (const s of filteredSkipped) {\n skippedMap.set(s.name, s);\n }\n\n const result: McpConfig = {};\n if (Object.keys(merged).length > 0) {\n result.mcpServers = merged;\n }\n if (skippedMap.size > 0) {\n result.skippedServers = Array.from(skippedMap.values());\n }\n return result;\n}\n"],"mappings":"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","names":[]}
@@ -20,6 +20,28 @@ export interface McpManager {
20
20
  reload(): Promise<void>;
21
21
  /** Reconnect a single server by name. Returns updated status. */
22
22
  reconnectServer(name: string): Promise<McpServerStatus>;
23
+ /**
24
+ * Run the OAuth authorization-code flow for an HTTP server, then reconnect it.
25
+ * Servers without an `oauth` config are provisioned with a zero-config default
26
+ * (dynamic client registration). The authorization URL is surfaced through
27
+ * `onAuthorizationUrl` for the caller to open in a browser.
28
+ *
29
+ * Resolves with the resulting {@link McpServerStatus}: a connected status on
30
+ * success, or a status carrying an `error` message on failure (including
31
+ * cancellation) — it does not reject. Inspect the returned status rather than
32
+ * relying on a thrown error.
33
+ */
34
+ authenticateServer(name: string, options?: {
35
+ onAuthorizationUrl?: (url: string) => void;
36
+ timeoutMs?: number;
37
+ }): Promise<McpServerStatus>;
38
+ /**
39
+ * Cancel a pending {@link authenticateServer} flow for a server (e.g. the
40
+ * user closed the browser without completing consent). The pending
41
+ * authenticate call rejects and the server returns to the `needsAuth` state
42
+ * so it can be retried. Returns `true` if a flow was cancelled.
43
+ */
44
+ cancelServerAuthentication(name: string): Promise<boolean>;
23
45
  /** Disconnect from all MCP servers and clean up. */
24
46
  disconnect(): Promise<void>;
25
47
  /** Get all tools from connected MCP servers (namespaced as serverName_toolName). */
@@ -1 +1 @@
1
- {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/mcp/manager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AASH,OAAO,KAAK,EAAE,SAAS,EAAuB,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAIrH,4CAA4C;AAC5C,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,2EAA2E;AAC3E,MAAM,WAAW,UAAU;IACzB,qEAAqE;IACrE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,2FAA2F;IAC3F,gBAAgB,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC;IAC3C,kEAAkE;IAClE,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,iEAAiE;IACjE,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IACxD,oDAAoD;IACpD,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,oFAAoF;IACpF,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAChC,4DAA4D;IAC5D,UAAU,IAAI,OAAO,CAAC;IACtB,iCAAiC;IACjC,iBAAiB,IAAI,eAAe,EAAE,CAAC;IACvC,2DAA2D;IAC3D,iBAAiB,IAAI,gBAAgB,EAAE,CAAC;IACxC,yCAAyC;IACzC,cAAc,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE,6BAA6B;IAC7B,SAAS,IAAI,SAAS,CAAC;IACvB,6CAA6C;IAC7C,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CACvC;AAkED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,EAClB,aAAa,SAAqB,EAClC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAC7C,UAAU,CAwWZ"}
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/mcp/manager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAgBH,OAAO,KAAK,EACV,SAAS,EAGT,eAAe,EACf,eAAe,EACf,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAIpB,4CAA4C;AAC5C,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,2EAA2E;AAC3E,MAAM,WAAW,UAAU;IACzB,qEAAqE;IACrE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,2FAA2F;IAC3F,gBAAgB,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC;IAC3C,kEAAkE;IAClE,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,iEAAiE;IACjE,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IACxD;;;;;;;;;;OAUG;IACH,kBAAkB,CAChB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,kBAAkB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3E,OAAO,CAAC,eAAe,CAAC,CAAC;IAC5B;;;;;OAKG;IACH,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3D,oDAAoD;IACpD,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,oFAAoF;IACpF,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAChC,4DAA4D;IAC5D,UAAU,IAAI,OAAO,CAAC;IACtB,iCAAiC;IACjC,iBAAiB,IAAI,eAAe,EAAE,CAAC;IACvC,2DAA2D;IAC3D,iBAAiB,IAAI,gBAAgB,EAAE,CAAC;IACxC,yCAAyC;IACzC,cAAc,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE,6BAA6B;IAC7B,SAAS,IAAI,SAAS,CAAC;IACvB,6CAA6C;IAC7C,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CACvC;AA0ED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,EAClB,aAAa,SAAqB,EAClC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAC7C,UAAU,CAgiBZ"}
@@ -3,7 +3,14 @@ import { dirname, join } from "node:path";
3
3
  import { MCPClient, MCPOAuthClientProvider } from "@mastra/mcp";
4
4
  import { DEFAULT_CONFIG_DIR } from "../constants.js";
5
5
  import { getAppDataDir } from "../utils/project.js";
6
- import { loadMcpConfig, getProjectMcpPath, getGlobalMcpPath, getClaudeSettingsPath } from "./config.js";
6
+ import {
7
+ DEFAULT_OAUTH_REDIRECT_URL,
8
+ loadMcpConfig,
9
+ getProjectMcpPath,
10
+ getGlobalMcpPath,
11
+ getClaudeSettingsPath,
12
+ resolveOAuthRedirectUrl
13
+ } from "./config.js";
7
14
  const MASTRACODE_MCP_TIMEOUT_MS = 7 * 24 * 60 * 60 * 1e3;
8
15
  function getTransport(cfg) {
9
16
  return "url" in cfg ? "http" : "stdio";
@@ -44,12 +51,13 @@ class FileOAuthStorage {
44
51
  renameSync(tmpPath, this.filePath);
45
52
  }
46
53
  }
54
+ const DEFAULT_OAUTH_CONFIG = { redirectUrl: DEFAULT_OAUTH_REDIRECT_URL };
47
55
  function getOAuthStoragePath(projectDir, name, cfg) {
48
56
  const key = JSON.stringify({
49
57
  projectDir,
50
58
  name,
51
59
  url: cfg.url,
52
- redirectUrl: cfg.oauth?.redirectUrl,
60
+ redirectUrl: resolveOAuthRedirectUrl(cfg.oauth),
53
61
  clientId: cfg.oauth?.clientId,
54
62
  scopes: cfg.oauth?.scopes ?? []
55
63
  });
@@ -70,10 +78,15 @@ function createMcpManager(projectDir, configDirName = DEFAULT_CONFIG_DIR, extraS
70
78
  };
71
79
  let config = applyExtraServers(loadMcpConfig(projectDir, configDirName));
72
80
  let client = null;
81
+ let serverDefs = {};
73
82
  let tools = {};
74
83
  let serverStatuses = /* @__PURE__ */ new Map();
75
84
  let stderrLogs = /* @__PURE__ */ new Map();
76
85
  let initialized = false;
86
+ const authUrlHandlers = /* @__PURE__ */ new Map();
87
+ const authenticatingServers = /* @__PURE__ */ new Set();
88
+ const cancelledAuthServers = /* @__PURE__ */ new Set();
89
+ const withAuthenticating = (status) => authenticatingServers.has(status.name) ? { ...status, authenticating: true } : status;
77
90
  const MAX_STDERR_LINES = 200;
78
91
  function captureStderr(serverName) {
79
92
  if (!client || typeof client.getServerStderr !== "function") return;
@@ -105,21 +118,26 @@ function createMcpManager(projectDir, configDirName = DEFAULT_CONFIG_DIR, extraS
105
118
  });
106
119
  }
107
120
  function createOAuthProvider(name, cfg) {
108
- if (!cfg.oauth) return void 0;
121
+ const oauth = cfg.oauth ?? (existsSync(getOAuthStoragePath(projectDir, name, cfg)) ? DEFAULT_OAUTH_CONFIG : void 0);
122
+ if (!oauth) return void 0;
123
+ const redirectUrl = resolveOAuthRedirectUrl(oauth);
109
124
  return new MCPOAuthClientProvider({
110
- redirectUrl: cfg.oauth.redirectUrl,
125
+ redirectUrl,
111
126
  clientMetadata: {
112
- redirect_uris: [cfg.oauth.redirectUrl],
113
- client_name: cfg.oauth.clientName ?? `Mastra Code MCP ${name}`,
127
+ redirect_uris: [redirectUrl],
128
+ client_name: oauth.clientName ?? `Mastra Code MCP ${name}`,
114
129
  grant_types: ["authorization_code", "refresh_token"],
115
130
  response_types: ["code"],
116
- ...cfg.oauth.scopes?.length ? { scope: cfg.oauth.scopes.join(" ") } : {}
131
+ ...oauth.scopes?.length ? { scope: oauth.scopes.join(" ") } : {}
117
132
  },
118
- clientInformation: cfg.oauth.clientId ? {
119
- client_id: cfg.oauth.clientId,
120
- ...cfg.oauth.clientSecret ? { client_secret: cfg.oauth.clientSecret } : {}
133
+ clientInformation: oauth.clientId ? {
134
+ client_id: oauth.clientId,
135
+ ...oauth.clientSecret ? { client_secret: oauth.clientSecret } : {}
121
136
  } : void 0,
122
- storage: new FileOAuthStorage(getOAuthStoragePath(projectDir, name, cfg))
137
+ storage: new FileOAuthStorage(getOAuthStoragePath(projectDir, name, cfg)),
138
+ onRedirectToAuthorization: (url) => {
139
+ authUrlHandlers.get(name)?.(url.toString());
140
+ }
123
141
  });
124
142
  }
125
143
  function buildServerDefs(servers) {
@@ -154,9 +172,10 @@ function createMcpManager(projectDir, configDirName = DEFAULT_CONFIG_DIR, extraS
154
172
  transport: getTransport(servers[name])
155
173
  });
156
174
  }
175
+ serverDefs = buildServerDefs(servers);
157
176
  client = new MCPClient({
158
177
  id: "mastra-code-mcp",
159
- servers: buildServerDefs(servers),
178
+ servers: serverDefs,
160
179
  timeout: MASTRACODE_MCP_TIMEOUT_MS
161
180
  });
162
181
  try {
@@ -179,13 +198,15 @@ function createMcpManager(projectDir, configDirName = DEFAULT_CONFIG_DIR, extraS
179
198
  transport: getTransport(servers[name])
180
199
  });
181
200
  } else {
201
+ const error = errors[name] ?? "Failed to connect";
182
202
  serverStatuses.set(name, {
183
203
  name,
184
204
  connected: false,
185
205
  toolCount: 0,
186
206
  toolNames: [],
187
207
  transport: getTransport(servers[name]),
188
- error: errors[name] ?? "Failed to connect"
208
+ error,
209
+ ...serverNeedsAuth(name, servers[name], error) ? { needsAuth: true } : {}
189
210
  });
190
211
  }
191
212
  }
@@ -201,11 +222,94 @@ function createMcpManager(projectDir, configDirName = DEFAULT_CONFIG_DIR, extraS
201
222
  toolCount: 0,
202
223
  toolNames: [],
203
224
  transport: getTransport(servers[name]),
204
- error: errMsg
225
+ error: errMsg,
226
+ ...serverNeedsAuth(name, servers[name], errMsg) ? { needsAuth: true } : {}
205
227
  });
206
228
  }
207
229
  }
208
230
  }
231
+ function serverNeedsAuth(name, cfg, error) {
232
+ if (getTransport(cfg) !== "http") return false;
233
+ if (client?.getServerAuthState?.(name) === "needs-auth") return true;
234
+ if (serverDefs[name]?.authProvider) return false;
235
+ return error !== void 0 && /\b401\b|unauthorized|invalid_token/i.test(error);
236
+ }
237
+ async function connectSingleServer(name, cfg, connect) {
238
+ const transport = getTransport(cfg);
239
+ const prefix = `${name}_`;
240
+ for (const key of Object.keys(tools)) {
241
+ if (key.startsWith(prefix)) {
242
+ delete tools[key];
243
+ }
244
+ }
245
+ stderrLogs.delete(name);
246
+ serverStatuses.set(name, {
247
+ name,
248
+ connected: false,
249
+ connecting: true,
250
+ toolCount: 0,
251
+ toolNames: [],
252
+ transport
253
+ });
254
+ try {
255
+ await connect();
256
+ captureStderr(name);
257
+ const { toolsets, errors } = await client.listToolsetsWithErrors();
258
+ const serverTools = toolsets[name];
259
+ const serverError = errors[name];
260
+ if (serverError) {
261
+ const status = {
262
+ name,
263
+ connected: false,
264
+ toolCount: 0,
265
+ toolNames: [],
266
+ transport,
267
+ error: serverError,
268
+ ...serverNeedsAuth(name, cfg, serverError) ? { needsAuth: true } : {}
269
+ };
270
+ serverStatuses.set(name, status);
271
+ return status;
272
+ } else if (serverTools && Object.keys(serverTools).length > 0) {
273
+ const toolNames = Object.keys(serverTools).map((t) => `${name}_${t}`);
274
+ for (const [toolName, toolConfig] of Object.entries(serverTools)) {
275
+ tools[`${name}_${toolName}`] = toolConfig;
276
+ }
277
+ const status = {
278
+ name,
279
+ connected: true,
280
+ toolCount: toolNames.length,
281
+ toolNames,
282
+ transport
283
+ };
284
+ serverStatuses.set(name, status);
285
+ return status;
286
+ } else {
287
+ const status = {
288
+ name,
289
+ connected: false,
290
+ toolCount: 0,
291
+ toolNames: [],
292
+ transport,
293
+ error: "Failed to connect"
294
+ };
295
+ serverStatuses.set(name, status);
296
+ return status;
297
+ }
298
+ } catch (error) {
299
+ const errMsg = error instanceof Error ? error.message : String(error);
300
+ const status = {
301
+ name,
302
+ connected: false,
303
+ toolCount: 0,
304
+ toolNames: [],
305
+ transport,
306
+ error: errMsg,
307
+ ...serverNeedsAuth(name, cfg, errMsg) ? { needsAuth: true } : {}
308
+ };
309
+ serverStatuses.set(name, status);
310
+ return status;
311
+ }
312
+ }
209
313
  async function disconnect() {
210
314
  if (client) {
211
315
  try {
@@ -265,78 +369,87 @@ function createMcpManager(projectDir, configDirName = DEFAULT_CONFIG_DIR, extraS
265
369
  error: "MCP client not initialized"
266
370
  };
267
371
  }
268
- const transport = getTransport(cfg);
269
- const prefix = `${name}_`;
270
- for (const key of Object.keys(tools)) {
271
- if (key.startsWith(prefix)) {
272
- delete tools[key];
273
- }
372
+ return connectSingleServer(name, cfg, () => client.reconnectServer(name));
373
+ },
374
+ async authenticateServer(name, options) {
375
+ const cfg = config.mcpServers?.[name];
376
+ if (!cfg) {
377
+ return {
378
+ name,
379
+ connected: false,
380
+ toolCount: 0,
381
+ toolNames: [],
382
+ transport: "stdio",
383
+ error: `Server "${name}" not found in config`
384
+ };
274
385
  }
275
- stderrLogs.delete(name);
276
- serverStatuses.set(name, {
277
- name,
278
- connected: false,
279
- connecting: true,
280
- toolCount: 0,
281
- toolNames: [],
282
- transport
283
- });
284
- try {
285
- await client.reconnectServer(name);
286
- captureStderr(name);
287
- const { toolsets, errors } = await client.listToolsetsWithErrors();
288
- const serverTools = toolsets[name];
289
- const serverError = errors[name];
290
- if (serverError) {
291
- const status = {
292
- name,
293
- connected: false,
294
- toolCount: 0,
295
- toolNames: [],
296
- transport,
297
- error: serverError
298
- };
299
- serverStatuses.set(name, status);
300
- return status;
301
- } else if (serverTools && Object.keys(serverTools).length > 0) {
302
- const toolNames = Object.keys(serverTools).map((t) => `${name}_${t}`);
303
- for (const [toolName, toolConfig] of Object.entries(serverTools)) {
304
- tools[`${name}_${toolName}`] = toolConfig;
305
- }
306
- const status = {
307
- name,
308
- connected: true,
309
- toolCount: toolNames.length,
310
- toolNames,
311
- transport
312
- };
313
- serverStatuses.set(name, status);
314
- return status;
315
- } else {
316
- const status = {
317
- name,
318
- connected: false,
319
- toolCount: 0,
320
- toolNames: [],
321
- transport,
322
- error: "Failed to connect"
323
- };
324
- serverStatuses.set(name, status);
325
- return status;
326
- }
327
- } catch (error) {
328
- const errMsg = error instanceof Error ? error.message : String(error);
329
- const status = {
386
+ if (!client) {
387
+ return {
330
388
  name,
331
389
  connected: false,
332
390
  toolCount: 0,
333
391
  toolNames: [],
334
- transport,
335
- error: errMsg
392
+ transport: getTransport(cfg),
393
+ error: "MCP client not initialized"
336
394
  };
337
- serverStatuses.set(name, status);
338
- return status;
339
395
  }
396
+ if (getTransport(cfg) !== "http") {
397
+ return {
398
+ name,
399
+ connected: false,
400
+ toolCount: 0,
401
+ toolNames: [],
402
+ transport: "stdio",
403
+ error: `Server "${name}" uses stdio transport, which does not support OAuth`
404
+ };
405
+ }
406
+ const def = serverDefs[name];
407
+ if (def?.url && !def.authProvider) {
408
+ def.authProvider = createOAuthProvider(name, { ...cfg, oauth: DEFAULT_OAUTH_CONFIG });
409
+ }
410
+ if (authenticatingServers.has(name)) {
411
+ const current = serverStatuses.get(name);
412
+ return {
413
+ name,
414
+ connected: current?.connected ?? false,
415
+ connecting: current?.connecting,
416
+ needsAuth: current?.needsAuth,
417
+ toolCount: current?.toolCount ?? 0,
418
+ toolNames: current?.toolNames ?? [],
419
+ transport: current?.transport ?? getTransport(cfg),
420
+ error: `Authentication for "${name}" is already in progress`
421
+ };
422
+ }
423
+ if (options?.onAuthorizationUrl) {
424
+ authUrlHandlers.set(name, options.onAuthorizationUrl);
425
+ }
426
+ authenticatingServers.add(name);
427
+ cancelledAuthServers.delete(name);
428
+ try {
429
+ const result = await connectSingleServer(
430
+ name,
431
+ cfg,
432
+ () => client.authenticate(name, options?.timeoutMs === void 0 ? void 0 : { timeoutMs: options.timeoutMs })
433
+ );
434
+ const finalStatus = cancelledAuthServers.has(name) && !result.connected ? { ...result, cancelled: true } : result;
435
+ serverStatuses.set(name, finalStatus);
436
+ return finalStatus;
437
+ } finally {
438
+ authUrlHandlers.delete(name);
439
+ authenticatingServers.delete(name);
440
+ cancelledAuthServers.delete(name);
441
+ }
442
+ },
443
+ async cancelServerAuthentication(name) {
444
+ if (!client) {
445
+ return false;
446
+ }
447
+ cancelledAuthServers.add(name);
448
+ const cancelled = await client.cancelAuthentication(name);
449
+ if (!cancelled) {
450
+ cancelledAuthServers.delete(name);
451
+ }
452
+ return cancelled;
340
453
  },
341
454
  disconnect,
342
455
  getTools() {
@@ -348,7 +461,7 @@ function createMcpManager(projectDir, configDirName = DEFAULT_CONFIG_DIR, extraS
348
461
  return hasConfigured || hasSkipped;
349
462
  },
350
463
  getServerStatuses() {
351
- return Array.from(serverStatuses.values());
464
+ return Array.from(serverStatuses.values(), withAuthenticating);
352
465
  },
353
466
  getSkippedServers() {
354
467
  return [...config.skippedServers ?? []];
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/mcp/manager.ts"],"sourcesContent":["/**\n * MCP manager — orchestrates MCP server connections using MCPClient directly.\n * Created once at startup, provides tools from connected MCP servers.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { MCPClient, MCPOAuthClientProvider } from '@mastra/mcp';\nimport type { MastraMCPServerDefinition, OAuthClientInformation, OAuthStorage } from '@mastra/mcp';\nimport { DEFAULT_CONFIG_DIR } from '../constants.js';\nimport { getAppDataDir } from '../utils/project.js';\nimport { loadMcpConfig, getProjectMcpPath, getGlobalMcpPath, getClaudeSettingsPath } from './config.js';\nimport type { McpConfig, McpHttpServerConfig, McpServerConfig, McpServerStatus, McpSkippedServer } from './types.js';\n\nconst MASTRACODE_MCP_TIMEOUT_MS = 7 * 24 * 60 * 60 * 1000; // 7 days\n\n/** Summary of MCP initialization result. */\nexport interface McpInitResult {\n connected: McpServerStatus[];\n failed: McpServerStatus[];\n skipped: McpSkippedServer[];\n totalTools: number;\n}\n\n/** Public interface for the MCP manager returned by createMcpManager(). */\nexport interface McpManager {\n /** Connect to all configured MCP servers and collect their tools. */\n init(): Promise<void>;\n /** Start init in the background. Returns a promise that resolves with status when done. */\n initInBackground(): Promise<McpInitResult>;\n /** Disconnect all servers, reload config from disk, reconnect. */\n reload(): Promise<void>;\n /** Reconnect a single server by name. Returns updated status. */\n reconnectServer(name: string): Promise<McpServerStatus>;\n /** Disconnect from all MCP servers and clean up. */\n disconnect(): Promise<void>;\n /** Get all tools from connected MCP servers (namespaced as serverName_toolName). */\n getTools(): Record<string, any>;\n /** Check if any MCP servers are configured (or skipped). */\n hasServers(): boolean;\n /** Get status of all servers. */\n getServerStatuses(): McpServerStatus[];\n /** Get servers that were skipped during config loading. */\n getSkippedServers(): McpSkippedServer[];\n /** Get config file paths for display. */\n getConfigPaths(): { project: string; global: string; claude: string };\n /** Get the merged config. */\n getConfig(): McpConfig;\n /** Get captured stderr logs for a server. */\n getServerLogs(name: string): string[];\n}\n\nfunction getTransport(cfg: McpServerConfig): 'stdio' | 'http' {\n return 'url' in cfg ? 'http' : 'stdio';\n}\n\nclass FileOAuthStorage implements OAuthStorage {\n constructor(private filePath: string) {}\n\n get(key: string): string | undefined {\n return this.read()[key];\n }\n\n set(key: string, value: string): void {\n const data = this.read();\n data[key] = value;\n this.write(data);\n }\n\n delete(key: string): void {\n const data = this.read();\n delete data[key];\n this.write(data);\n }\n\n private read(): Record<string, string> {\n if (!existsSync(this.filePath)) return {};\n try {\n return JSON.parse(readFileSync(this.filePath, 'utf-8')) as Record<string, string>;\n } catch {\n return {};\n }\n }\n\n private write(data: Record<string, string>): void {\n const dir = dirname(this.filePath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n const tmpPath = `${this.filePath}.tmp`;\n writeFileSync(tmpPath, JSON.stringify(data, null, 2), { encoding: 'utf-8', mode: 0o600 });\n renameSync(tmpPath, this.filePath);\n }\n}\n\nfunction getOAuthStoragePath(projectDir: string, name: string, cfg: McpHttpServerConfig): string {\n const key = JSON.stringify({\n projectDir,\n name,\n url: cfg.url,\n redirectUrl: cfg.oauth?.redirectUrl,\n clientId: cfg.oauth?.clientId,\n scopes: cfg.oauth?.scopes ?? [],\n });\n return join(getAppDataDir(), 'mcp-oauth', `${getStorageKeyFingerprint(key)}.json`);\n}\n\nfunction getStorageKeyFingerprint(value: string): string {\n let fingerprint = 0xcbf29ce484222325n;\n for (let i = 0; i < value.length; i += 1) {\n fingerprint ^= BigInt(value.charCodeAt(i));\n fingerprint = BigInt.asUintN(64, fingerprint * 0x100000001b3n);\n }\n return fingerprint.toString(16).padStart(16, '0');\n}\n\n/**\n * Create an MCP manager that wraps MCPClient with config-file discovery\n * and per-server status tracking.\n */\nexport function createMcpManager(\n projectDir: string,\n configDirName = DEFAULT_CONFIG_DIR,\n extraServers?: Record<string, McpServerConfig>,\n): McpManager {\n /** Merge programmatic servers into a base config (highest priority). */\n const applyExtraServers = (base: McpConfig): McpConfig => {\n if (!extraServers || Object.keys(extraServers).length === 0) return base;\n return { ...base, mcpServers: { ...base.mcpServers, ...extraServers } };\n };\n\n let config = applyExtraServers(loadMcpConfig(projectDir, configDirName));\n let client: MCPClient | null = null;\n let tools: Record<string, any> = {};\n let serverStatuses = new Map<string, McpServerStatus>();\n let stderrLogs = new Map<string, string[]>();\n let initialized = false;\n\n const MAX_STDERR_LINES = 200;\n\n /** Hook into a server's stderr stream and buffer its output. */\n function captureStderr(serverName: string): void {\n if (!client || typeof client.getServerStderr !== 'function') return;\n const stream = client.getServerStderr(serverName);\n if (!stream) return;\n\n let buffer = '';\n const lines = stderrLogs.get(serverName) ?? [];\n stderrLogs.set(serverName, lines);\n\n stream.on('data', (chunk: Buffer) => {\n buffer += chunk.toString();\n const parts = buffer.split('\\n');\n // Last element is incomplete line (or empty if ended with \\n)\n buffer = parts.pop()!;\n for (const line of parts) {\n if (line.trim()) {\n lines.push(line);\n if (lines.length > MAX_STDERR_LINES) {\n lines.shift();\n }\n }\n }\n });\n\n stream.on('end', () => {\n if (buffer.trim()) {\n lines.push(buffer);\n if (lines.length > MAX_STDERR_LINES) {\n lines.shift();\n }\n }\n });\n }\n\n function createOAuthProvider(name: string, cfg: McpHttpServerConfig) {\n if (!cfg.oauth) return undefined;\n\n return new MCPOAuthClientProvider({\n redirectUrl: cfg.oauth.redirectUrl,\n clientMetadata: {\n redirect_uris: [cfg.oauth.redirectUrl],\n client_name: cfg.oauth.clientName ?? `Mastra Code MCP ${name}`,\n grant_types: ['authorization_code', 'refresh_token'],\n response_types: ['code'],\n ...(cfg.oauth.scopes?.length ? { scope: cfg.oauth.scopes.join(' ') } : {}),\n },\n clientInformation: cfg.oauth.clientId\n ? ({\n client_id: cfg.oauth.clientId,\n ...(cfg.oauth.clientSecret ? { client_secret: cfg.oauth.clientSecret } : {}),\n } satisfies OAuthClientInformation)\n : undefined,\n storage: new FileOAuthStorage(getOAuthStoragePath(projectDir, name, cfg)),\n });\n }\n\n function buildServerDefs(servers: Record<string, McpServerConfig>): Record<string, MastraMCPServerDefinition> {\n const defs: Record<string, MastraMCPServerDefinition> = {};\n for (const [name, cfg] of Object.entries(servers)) {\n if ('url' in cfg) {\n const httpCfg = cfg as McpHttpServerConfig;\n defs[name] = {\n url: new URL(httpCfg.url),\n requestInit: httpCfg.headers ? { headers: httpCfg.headers } : undefined,\n authProvider: createOAuthProvider(name, httpCfg),\n };\n } else {\n defs[name] = { command: cfg.command, args: cfg.args, env: cfg.env, stderr: 'pipe' };\n }\n }\n return defs;\n }\n\n async function connectAndCollectTools(): Promise<void> {\n const servers = config.mcpServers;\n if (!servers || Object.keys(servers).length === 0) {\n return;\n }\n\n // Pre-populate statuses as \"connecting\" so callers can see in-progress state\n const serverNames = Object.keys(servers);\n for (const name of serverNames) {\n serverStatuses.set(name, {\n name,\n connected: false,\n connecting: true,\n toolCount: 0,\n toolNames: [],\n transport: getTransport(servers[name]!),\n });\n }\n\n client = new MCPClient({\n id: 'mastra-code-mcp',\n servers: buildServerDefs(servers),\n timeout: MASTRACODE_MCP_TIMEOUT_MS,\n });\n\n // Use listToolsetsWithErrors() to get tools grouped by server name,\n // plus per-server error messages for servers that failed to connect.\n\n try {\n const { toolsets, errors } = await client.listToolsetsWithErrors();\n const typedToolsets = toolsets as Record<string, Record<string, any>>;\n\n // Flatten toolsets into the namespaced tools map (serverName_toolName)\n for (const [serverName, serverTools] of Object.entries(typedToolsets)) {\n for (const [toolName, toolConfig] of Object.entries(serverTools)) {\n tools[`${serverName}_${toolName}`] = toolConfig;\n }\n }\n\n for (const name of serverNames) {\n const serverTools = typedToolsets[name];\n if (serverTools && Object.keys(serverTools).length > 0) {\n const toolNames = Object.keys(serverTools).map(t => `${name}_${t}`);\n serverStatuses.set(name, {\n name,\n connected: true,\n toolCount: toolNames.length,\n toolNames,\n transport: getTransport(servers[name]!),\n });\n } else {\n // Server failed — use the real error from listToolsetsWithErrors()\n serverStatuses.set(name, {\n name,\n connected: false,\n toolCount: 0,\n toolNames: [],\n transport: getTransport(servers[name]!),\n error: errors[name] ?? 'Failed to connect',\n });\n }\n }\n\n // Capture stderr from all stdio servers (connected or failed)\n for (const name of serverNames) {\n captureStderr(name);\n }\n } catch (error) {\n const errMsg = error instanceof Error ? error.message : String(error);\n\n for (const name of serverNames) {\n serverStatuses.set(name, {\n name,\n connected: false,\n toolCount: 0,\n toolNames: [],\n transport: getTransport(servers[name]!),\n error: errMsg,\n });\n }\n }\n }\n\n async function disconnect(): Promise<void> {\n if (client) {\n try {\n await client.disconnect();\n } catch {\n // Ignore disconnect errors\n }\n client = null;\n }\n }\n\n return {\n async init() {\n if (initialized) return;\n await connectAndCollectTools();\n initialized = true;\n },\n\n async initInBackground(): Promise<McpInitResult> {\n await this.init();\n const statuses = Array.from(serverStatuses.values());\n const connected = statuses.filter(s => s.connected);\n const failed = statuses.filter(s => !s.connected);\n return {\n connected,\n failed,\n skipped: [...(config.skippedServers ?? [])],\n totalTools: connected.reduce((sum, s) => sum + s.toolCount, 0),\n };\n },\n\n async reload() {\n await disconnect();\n config = applyExtraServers(loadMcpConfig(projectDir, configDirName));\n tools = {};\n serverStatuses = new Map();\n stderrLogs = new Map();\n initialized = false;\n await connectAndCollectTools();\n initialized = true;\n },\n\n async reconnectServer(name: string): Promise<McpServerStatus> {\n const cfg = config.mcpServers?.[name];\n if (!cfg) {\n return {\n name,\n connected: false,\n toolCount: 0,\n toolNames: [],\n transport: 'stdio',\n error: `Server \"${name}\" not found in config`,\n };\n }\n\n if (!client) {\n return {\n name,\n connected: false,\n toolCount: 0,\n toolNames: [],\n transport: getTransport(cfg),\n error: 'MCP client not initialized',\n };\n }\n\n const transport = getTransport(cfg);\n\n // Remove old tools for this server\n const prefix = `${name}_`;\n for (const key of Object.keys(tools)) {\n if (key.startsWith(prefix)) {\n delete tools[key];\n }\n }\n\n // Clear old logs and mark as connecting\n stderrLogs.delete(name);\n serverStatuses.set(name, {\n name,\n connected: false,\n connecting: true,\n toolCount: 0,\n toolNames: [],\n transport,\n });\n\n try {\n // Use MCPClient's per-server reconnect\n await client.reconnectServer(name);\n\n // Recapture stderr for the reconnected server\n captureStderr(name);\n\n // Fetch updated toolsets to get this server's tools\n const { toolsets, errors } = await client.listToolsetsWithErrors();\n const serverTools = toolsets[name];\n const serverError = errors[name];\n\n if (serverError) {\n const status: McpServerStatus = {\n name,\n connected: false,\n toolCount: 0,\n toolNames: [],\n transport,\n error: serverError,\n };\n serverStatuses.set(name, status);\n return status;\n } else if (serverTools && Object.keys(serverTools).length > 0) {\n const toolNames = Object.keys(serverTools).map(t => `${name}_${t}`);\n for (const [toolName, toolConfig] of Object.entries(serverTools)) {\n tools[`${name}_${toolName}`] = toolConfig;\n }\n const status: McpServerStatus = {\n name,\n connected: true,\n toolCount: toolNames.length,\n toolNames,\n transport,\n };\n serverStatuses.set(name, status);\n return status;\n } else {\n const status: McpServerStatus = {\n name,\n connected: false,\n toolCount: 0,\n toolNames: [],\n transport,\n error: 'Failed to connect',\n };\n serverStatuses.set(name, status);\n return status;\n }\n } catch (error) {\n const errMsg = error instanceof Error ? error.message : String(error);\n const status: McpServerStatus = {\n name,\n connected: false,\n toolCount: 0,\n toolNames: [],\n transport,\n error: errMsg,\n };\n serverStatuses.set(name, status);\n return status;\n }\n },\n\n disconnect,\n\n getTools() {\n return { ...tools };\n },\n\n hasServers() {\n const hasConfigured = config.mcpServers !== undefined && Object.keys(config.mcpServers).length > 0;\n const hasSkipped = config.skippedServers !== undefined && config.skippedServers.length > 0;\n return hasConfigured || hasSkipped;\n },\n\n getServerStatuses() {\n return Array.from(serverStatuses.values());\n },\n\n getSkippedServers() {\n return [...(config.skippedServers ?? [])];\n },\n\n getConfigPaths() {\n return {\n project: getProjectMcpPath(projectDir, configDirName),\n global: getGlobalMcpPath(configDirName),\n claude: getClaudeSettingsPath(projectDir),\n };\n },\n\n getConfig() {\n return config;\n },\n\n getServerLogs(name: string) {\n return [...(stderrLogs.get(name) ?? [])];\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/mcp/manager.ts"],"sourcesContent":["/**\n * MCP manager — orchestrates MCP server connections using MCPClient directly.\n * Created once at startup, provides tools from connected MCP servers.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { MCPClient, MCPOAuthClientProvider } from '@mastra/mcp';\nimport type { MastraMCPServerDefinition, OAuthClientInformation, OAuthStorage } from '@mastra/mcp';\nimport { DEFAULT_CONFIG_DIR } from '../constants.js';\nimport { getAppDataDir } from '../utils/project.js';\nimport {\n DEFAULT_OAUTH_REDIRECT_URL,\n loadMcpConfig,\n getProjectMcpPath,\n getGlobalMcpPath,\n getClaudeSettingsPath,\n resolveOAuthRedirectUrl,\n} from './config.js';\nimport type {\n McpConfig,\n McpHttpOAuthConfig,\n McpHttpServerConfig,\n McpServerConfig,\n McpServerStatus,\n McpSkippedServer,\n} from './types.js';\n\nconst MASTRACODE_MCP_TIMEOUT_MS = 7 * 24 * 60 * 60 * 1000; // 7 days\n\n/** Summary of MCP initialization result. */\nexport interface McpInitResult {\n connected: McpServerStatus[];\n failed: McpServerStatus[];\n skipped: McpSkippedServer[];\n totalTools: number;\n}\n\n/** Public interface for the MCP manager returned by createMcpManager(). */\nexport interface McpManager {\n /** Connect to all configured MCP servers and collect their tools. */\n init(): Promise<void>;\n /** Start init in the background. Returns a promise that resolves with status when done. */\n initInBackground(): Promise<McpInitResult>;\n /** Disconnect all servers, reload config from disk, reconnect. */\n reload(): Promise<void>;\n /** Reconnect a single server by name. Returns updated status. */\n reconnectServer(name: string): Promise<McpServerStatus>;\n /**\n * Run the OAuth authorization-code flow for an HTTP server, then reconnect it.\n * Servers without an `oauth` config are provisioned with a zero-config default\n * (dynamic client registration). The authorization URL is surfaced through\n * `onAuthorizationUrl` for the caller to open in a browser.\n *\n * Resolves with the resulting {@link McpServerStatus}: a connected status on\n * success, or a status carrying an `error` message on failure (including\n * cancellation) — it does not reject. Inspect the returned status rather than\n * relying on a thrown error.\n */\n authenticateServer(\n name: string,\n options?: { onAuthorizationUrl?: (url: string) => void; timeoutMs?: number },\n ): Promise<McpServerStatus>;\n /**\n * Cancel a pending {@link authenticateServer} flow for a server (e.g. the\n * user closed the browser without completing consent). The pending\n * authenticate call rejects and the server returns to the `needsAuth` state\n * so it can be retried. Returns `true` if a flow was cancelled.\n */\n cancelServerAuthentication(name: string): Promise<boolean>;\n /** Disconnect from all MCP servers and clean up. */\n disconnect(): Promise<void>;\n /** Get all tools from connected MCP servers (namespaced as serverName_toolName). */\n getTools(): Record<string, any>;\n /** Check if any MCP servers are configured (or skipped). */\n hasServers(): boolean;\n /** Get status of all servers. */\n getServerStatuses(): McpServerStatus[];\n /** Get servers that were skipped during config loading. */\n getSkippedServers(): McpSkippedServer[];\n /** Get config file paths for display. */\n getConfigPaths(): { project: string; global: string; claude: string };\n /** Get the merged config. */\n getConfig(): McpConfig;\n /** Get captured stderr logs for a server. */\n getServerLogs(name: string): string[];\n}\n\nfunction getTransport(cfg: McpServerConfig): 'stdio' | 'http' {\n return 'url' in cfg ? 'http' : 'stdio';\n}\n\nclass FileOAuthStorage implements OAuthStorage {\n constructor(private filePath: string) {}\n\n get(key: string): string | undefined {\n return this.read()[key];\n }\n\n set(key: string, value: string): void {\n const data = this.read();\n data[key] = value;\n this.write(data);\n }\n\n delete(key: string): void {\n const data = this.read();\n delete data[key];\n this.write(data);\n }\n\n private read(): Record<string, string> {\n if (!existsSync(this.filePath)) return {};\n try {\n return JSON.parse(readFileSync(this.filePath, 'utf-8')) as Record<string, string>;\n } catch {\n return {};\n }\n }\n\n private write(data: Record<string, string>): void {\n const dir = dirname(this.filePath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n const tmpPath = `${this.filePath}.tmp`;\n writeFileSync(tmpPath, JSON.stringify(data, null, 2), { encoding: 'utf-8', mode: 0o600 });\n renameSync(tmpPath, this.filePath);\n }\n}\n\n/**\n * Zero-config OAuth defaults for servers with a bare `url` entry. Dynamic\n * client registration provisions the client, so no `clientId` is needed.\n */\nconst DEFAULT_OAUTH_CONFIG: McpHttpOAuthConfig = { redirectUrl: DEFAULT_OAUTH_REDIRECT_URL };\n\nfunction getOAuthStoragePath(projectDir: string, name: string, cfg: McpHttpServerConfig): string {\n // The fingerprint always uses the resolved redirect URL so a bare `url`\n // entry keeps the same token file before and after zero-config provisioning.\n const key = JSON.stringify({\n projectDir,\n name,\n url: cfg.url,\n redirectUrl: resolveOAuthRedirectUrl(cfg.oauth),\n clientId: cfg.oauth?.clientId,\n scopes: cfg.oauth?.scopes ?? [],\n });\n return join(getAppDataDir(), 'mcp-oauth', `${getStorageKeyFingerprint(key)}.json`);\n}\n\nfunction getStorageKeyFingerprint(value: string): string {\n let fingerprint = 0xcbf29ce484222325n;\n for (let i = 0; i < value.length; i += 1) {\n fingerprint ^= BigInt(value.charCodeAt(i));\n fingerprint = BigInt.asUintN(64, fingerprint * 0x100000001b3n);\n }\n return fingerprint.toString(16).padStart(16, '0');\n}\n\n/**\n * Create an MCP manager that wraps MCPClient with config-file discovery\n * and per-server status tracking.\n */\nexport function createMcpManager(\n projectDir: string,\n configDirName = DEFAULT_CONFIG_DIR,\n extraServers?: Record<string, McpServerConfig>,\n): McpManager {\n /** Merge programmatic servers into a base config (highest priority). */\n const applyExtraServers = (base: McpConfig): McpConfig => {\n if (!extraServers || Object.keys(extraServers).length === 0) return base;\n return { ...base, mcpServers: { ...base.mcpServers, ...extraServers } };\n };\n\n let config = applyExtraServers(loadMcpConfig(projectDir, configDirName));\n let client: MCPClient | null = null;\n let serverDefs: Record<string, MastraMCPServerDefinition> = {};\n let tools: Record<string, any> = {};\n let serverStatuses = new Map<string, McpServerStatus>();\n let stderrLogs = new Map<string, string[]>();\n let initialized = false;\n\n /** Per-server handlers that receive the OAuth authorization URL during authenticateServer(). */\n const authUrlHandlers = new Map<string, (url: string) => void>();\n\n /**\n * Servers with an OAuth authorization flow currently in flight. Owned by the\n * manager (not the TUI) so the state survives a `/mcp` selector being closed\n * and reopened — the reopened selector reads it back off the server status and\n * can still offer \"Cancel authentication\".\n */\n const authenticatingServers = new Set<string>();\n\n /**\n * Servers whose in-flight authentication was cancelled by the caller. Set by\n * cancelServerAuthentication() so the resolving authenticateServer() call can\n * mark its failed status as a deliberate cancel rather than a genuine failure.\n * This lives on the manager (not the TUI selector) so the signal survives the\n * selector being closed and reopened mid-flow.\n */\n const cancelledAuthServers = new Set<string>();\n\n /** Overlay the manager-owned `authenticating` flag onto a status snapshot. */\n const withAuthenticating = (status: McpServerStatus): McpServerStatus =>\n authenticatingServers.has(status.name) ? { ...status, authenticating: true } : status;\n\n const MAX_STDERR_LINES = 200;\n\n /** Hook into a server's stderr stream and buffer its output. */\n function captureStderr(serverName: string): void {\n if (!client || typeof client.getServerStderr !== 'function') return;\n const stream = client.getServerStderr(serverName);\n if (!stream) return;\n\n let buffer = '';\n const lines = stderrLogs.get(serverName) ?? [];\n stderrLogs.set(serverName, lines);\n\n stream.on('data', (chunk: Buffer) => {\n buffer += chunk.toString();\n const parts = buffer.split('\\n');\n // Last element is incomplete line (or empty if ended with \\n)\n buffer = parts.pop()!;\n for (const line of parts) {\n if (line.trim()) {\n lines.push(line);\n if (lines.length > MAX_STDERR_LINES) {\n lines.shift();\n }\n }\n }\n });\n\n stream.on('end', () => {\n if (buffer.trim()) {\n lines.push(buffer);\n if (lines.length > MAX_STDERR_LINES) {\n lines.shift();\n }\n }\n });\n }\n\n function createOAuthProvider(name: string, cfg: McpHttpServerConfig) {\n // Bare `url` entries get no eager provider — auth is provisioned lazily\n // when the user authenticates — unless a previous session already stored\n // OAuth state for this server, in which case the provider is needed to\n // attach the persisted tokens on connect.\n const oauth =\n cfg.oauth ?? (existsSync(getOAuthStoragePath(projectDir, name, cfg)) ? DEFAULT_OAUTH_CONFIG : undefined);\n if (!oauth) return undefined;\n\n // redirectUrl is optional in the user-supplied config; resolve the stable\n // default (or the `callbackPort` shorthand, for programmatically registered\n // servers that bypass config parsing) so provider metadata always carries\n // a concrete URL.\n const redirectUrl = resolveOAuthRedirectUrl(oauth);\n\n return new MCPOAuthClientProvider({\n redirectUrl,\n clientMetadata: {\n redirect_uris: [redirectUrl],\n client_name: oauth.clientName ?? `Mastra Code MCP ${name}`,\n grant_types: ['authorization_code', 'refresh_token'],\n response_types: ['code'],\n ...(oauth.scopes?.length ? { scope: oauth.scopes.join(' ') } : {}),\n },\n clientInformation: oauth.clientId\n ? ({\n client_id: oauth.clientId,\n ...(oauth.clientSecret ? { client_secret: oauth.clientSecret } : {}),\n } satisfies OAuthClientInformation)\n : undefined,\n storage: new FileOAuthStorage(getOAuthStoragePath(projectDir, name, cfg)),\n onRedirectToAuthorization: url => {\n authUrlHandlers.get(name)?.(url.toString());\n },\n });\n }\n\n function buildServerDefs(servers: Record<string, McpServerConfig>): Record<string, MastraMCPServerDefinition> {\n const defs: Record<string, MastraMCPServerDefinition> = {};\n for (const [name, cfg] of Object.entries(servers)) {\n if ('url' in cfg) {\n const httpCfg = cfg as McpHttpServerConfig;\n defs[name] = {\n url: new URL(httpCfg.url),\n requestInit: httpCfg.headers ? { headers: httpCfg.headers } : undefined,\n authProvider: createOAuthProvider(name, httpCfg),\n };\n } else {\n defs[name] = { command: cfg.command, args: cfg.args, env: cfg.env, stderr: 'pipe' };\n }\n }\n return defs;\n }\n\n async function connectAndCollectTools(): Promise<void> {\n const servers = config.mcpServers;\n if (!servers || Object.keys(servers).length === 0) {\n return;\n }\n\n // Pre-populate statuses as \"connecting\" so callers can see in-progress state\n const serverNames = Object.keys(servers);\n for (const name of serverNames) {\n serverStatuses.set(name, {\n name,\n connected: false,\n connecting: true,\n toolCount: 0,\n toolNames: [],\n transport: getTransport(servers[name]!),\n });\n }\n\n serverDefs = buildServerDefs(servers);\n client = new MCPClient({\n id: 'mastra-code-mcp',\n servers: serverDefs,\n timeout: MASTRACODE_MCP_TIMEOUT_MS,\n });\n\n // Use listToolsetsWithErrors() to get tools grouped by server name,\n // plus per-server error messages for servers that failed to connect.\n\n try {\n const { toolsets, errors } = await client.listToolsetsWithErrors();\n const typedToolsets = toolsets as Record<string, Record<string, any>>;\n\n // Flatten toolsets into the namespaced tools map (serverName_toolName)\n for (const [serverName, serverTools] of Object.entries(typedToolsets)) {\n for (const [toolName, toolConfig] of Object.entries(serverTools)) {\n tools[`${serverName}_${toolName}`] = toolConfig;\n }\n }\n\n for (const name of serverNames) {\n const serverTools = typedToolsets[name];\n if (serverTools && Object.keys(serverTools).length > 0) {\n const toolNames = Object.keys(serverTools).map(t => `${name}_${t}`);\n serverStatuses.set(name, {\n name,\n connected: true,\n toolCount: toolNames.length,\n toolNames,\n transport: getTransport(servers[name]!),\n });\n } else {\n // Server failed — use the real error from listToolsetsWithErrors()\n const error = errors[name] ?? 'Failed to connect';\n serverStatuses.set(name, {\n name,\n connected: false,\n toolCount: 0,\n toolNames: [],\n transport: getTransport(servers[name]!),\n error,\n ...(serverNeedsAuth(name, servers[name]!, error) ? { needsAuth: true } : {}),\n });\n }\n }\n\n // Capture stderr from all stdio servers (connected or failed)\n for (const name of serverNames) {\n captureStderr(name);\n }\n } catch (error) {\n const errMsg = error instanceof Error ? error.message : String(error);\n\n for (const name of serverNames) {\n serverStatuses.set(name, {\n name,\n connected: false,\n toolCount: 0,\n toolNames: [],\n transport: getTransport(servers[name]!),\n error: errMsg,\n ...(serverNeedsAuth(name, servers[name]!, errMsg) ? { needsAuth: true } : {}),\n });\n }\n }\n }\n\n /**\n * Whether a failed HTTP server is blocked on OAuth authorization.\n *\n * Provider-backed servers report the exact state tracked by `@mastra/mcp`.\n * Bare `url` entries carry no provider until the user authenticates, so the\n * signal is a 401 in the connect error — surfaced either as the status text\n * or as the RFC 6750 `invalid_token` bearer error code in the response body.\n */\n function serverNeedsAuth(name: string, cfg: McpServerConfig, error?: string): boolean {\n if (getTransport(cfg) !== 'http') return false;\n if (client?.getServerAuthState?.(name) === 'needs-auth') return true;\n if (serverDefs[name]?.authProvider) return false;\n return error !== undefined && /\\b401\\b|unauthorized|invalid_token/i.test(error);\n }\n\n /**\n * Runs a single-server connect action (reconnect or authenticate), then\n * refreshes that server's tools and status from the client.\n */\n async function connectSingleServer(\n name: string,\n cfg: McpServerConfig,\n connect: () => Promise<unknown>,\n ): Promise<McpServerStatus> {\n const transport = getTransport(cfg);\n\n // Remove old tools for this server\n const prefix = `${name}_`;\n for (const key of Object.keys(tools)) {\n if (key.startsWith(prefix)) {\n delete tools[key];\n }\n }\n\n // Clear old logs and mark as connecting\n stderrLogs.delete(name);\n serverStatuses.set(name, {\n name,\n connected: false,\n connecting: true,\n toolCount: 0,\n toolNames: [],\n transport,\n });\n\n try {\n await connect();\n\n // Recapture stderr for the reconnected server\n captureStderr(name);\n\n // Fetch updated toolsets to get this server's tools\n const { toolsets, errors } = await client!.listToolsetsWithErrors();\n const serverTools = toolsets[name];\n const serverError = errors[name];\n\n if (serverError) {\n const status: McpServerStatus = {\n name,\n connected: false,\n toolCount: 0,\n toolNames: [],\n transport,\n error: serverError,\n ...(serverNeedsAuth(name, cfg, serverError) ? { needsAuth: true } : {}),\n };\n serverStatuses.set(name, status);\n return status;\n } else if (serverTools && Object.keys(serverTools).length > 0) {\n const toolNames = Object.keys(serverTools).map(t => `${name}_${t}`);\n for (const [toolName, toolConfig] of Object.entries(serverTools)) {\n tools[`${name}_${toolName}`] = toolConfig;\n }\n const status: McpServerStatus = {\n name,\n connected: true,\n toolCount: toolNames.length,\n toolNames,\n transport,\n };\n serverStatuses.set(name, status);\n return status;\n } else {\n const status: McpServerStatus = {\n name,\n connected: false,\n toolCount: 0,\n toolNames: [],\n transport,\n error: 'Failed to connect',\n };\n serverStatuses.set(name, status);\n return status;\n }\n } catch (error) {\n const errMsg = error instanceof Error ? error.message : String(error);\n const status: McpServerStatus = {\n name,\n connected: false,\n toolCount: 0,\n toolNames: [],\n transport,\n error: errMsg,\n ...(serverNeedsAuth(name, cfg, errMsg) ? { needsAuth: true } : {}),\n };\n serverStatuses.set(name, status);\n return status;\n }\n }\n\n async function disconnect(): Promise<void> {\n if (client) {\n try {\n await client.disconnect();\n } catch {\n // Ignore disconnect errors\n }\n client = null;\n }\n }\n\n return {\n async init() {\n if (initialized) return;\n await connectAndCollectTools();\n initialized = true;\n },\n\n async initInBackground(): Promise<McpInitResult> {\n await this.init();\n const statuses = Array.from(serverStatuses.values());\n const connected = statuses.filter(s => s.connected);\n const failed = statuses.filter(s => !s.connected);\n return {\n connected,\n failed,\n skipped: [...(config.skippedServers ?? [])],\n totalTools: connected.reduce((sum, s) => sum + s.toolCount, 0),\n };\n },\n\n async reload() {\n await disconnect();\n config = applyExtraServers(loadMcpConfig(projectDir, configDirName));\n tools = {};\n serverStatuses = new Map();\n stderrLogs = new Map();\n initialized = false;\n await connectAndCollectTools();\n initialized = true;\n },\n\n async reconnectServer(name: string): Promise<McpServerStatus> {\n const cfg = config.mcpServers?.[name];\n if (!cfg) {\n return {\n name,\n connected: false,\n toolCount: 0,\n toolNames: [],\n transport: 'stdio',\n error: `Server \"${name}\" not found in config`,\n };\n }\n\n if (!client) {\n return {\n name,\n connected: false,\n toolCount: 0,\n toolNames: [],\n transport: getTransport(cfg),\n error: 'MCP client not initialized',\n };\n }\n\n // Use MCPClient's per-server reconnect\n return connectSingleServer(name, cfg, () => client!.reconnectServer(name));\n },\n\n async authenticateServer(\n name: string,\n options?: { onAuthorizationUrl?: (url: string) => void; timeoutMs?: number },\n ): Promise<McpServerStatus> {\n const cfg = config.mcpServers?.[name];\n if (!cfg) {\n return {\n name,\n connected: false,\n toolCount: 0,\n toolNames: [],\n transport: 'stdio',\n error: `Server \"${name}\" not found in config`,\n };\n }\n\n if (!client) {\n return {\n name,\n connected: false,\n toolCount: 0,\n toolNames: [],\n transport: getTransport(cfg),\n error: 'MCP client not initialized',\n };\n }\n\n if (getTransport(cfg) !== 'http') {\n return {\n name,\n connected: false,\n toolCount: 0,\n toolNames: [],\n transport: 'stdio',\n error: `Server \"${name}\" uses stdio transport, which does not support OAuth`,\n };\n }\n\n // Zero-config provisioning: a bare `url` entry gets a provider with the\n // default redirect URL the first time the user authenticates. Dynamic\n // client registration takes care of the client credentials.\n //\n // NOTE: `serverDefs[name]` is the same object reference the MCPClient was\n // constructed with, and connectHttp reads `authProvider` live off it at\n // connect time, so mutating it here reaches the already-created client.\n // If MCPClient ever defensively copies its server config, zero-config auth\n // would need an explicit \"set this server's provider\" API instead.\n const def = serverDefs[name];\n if (def?.url && !def.authProvider) {\n def.authProvider = createOAuthProvider(name, { ...(cfg as McpHttpServerConfig), oauth: DEFAULT_OAUTH_CONFIG });\n }\n\n // Reject a concurrent second attempt for the same server. authUrlHandlers\n // has a single slot per server, so a second call with an onAuthorizationUrl\n // would overwrite the first caller's handler (which then never sees the\n // URL) and its finally would delete the handler out from under the other\n // in-flight call. Gate on authenticatingServers rather than authUrlHandlers\n // so callers without a URL handler are caught too. The underlying\n // client.authenticate already coalesces same-server calls into one flow.\n if (authenticatingServers.has(name)) {\n const current = serverStatuses.get(name);\n return {\n name,\n connected: current?.connected ?? false,\n connecting: current?.connecting,\n needsAuth: current?.needsAuth,\n toolCount: current?.toolCount ?? 0,\n toolNames: current?.toolNames ?? [],\n transport: current?.transport ?? getTransport(cfg),\n error: `Authentication for \"${name}\" is already in progress`,\n };\n }\n\n if (options?.onAuthorizationUrl) {\n authUrlHandlers.set(name, options.onAuthorizationUrl);\n }\n authenticatingServers.add(name);\n cancelledAuthServers.delete(name);\n try {\n const result = await connectSingleServer(name, cfg, () =>\n client!.authenticate(name, options?.timeoutMs === undefined ? undefined : { timeoutMs: options.timeoutMs }),\n );\n // A cancelled flow resolves with a failed status; mark it so callers can\n // distinguish a deliberate cancel from a genuine authentication failure.\n // Write the marker back to the durable status map (not just the returned\n // value) so a selector reopened after cancellation still sees it.\n const finalStatus =\n cancelledAuthServers.has(name) && !result.connected ? { ...result, cancelled: true } : result;\n serverStatuses.set(name, finalStatus);\n return finalStatus;\n } finally {\n authUrlHandlers.delete(name);\n authenticatingServers.delete(name);\n cancelledAuthServers.delete(name);\n }\n },\n\n async cancelServerAuthentication(name: string): Promise<boolean> {\n if (!client) {\n return false;\n }\n // Record the intent before aborting so the resolving authenticateServer()\n // call can tag its failed status as cancelled rather than failed.\n cancelledAuthServers.add(name);\n const cancelled = await client.cancelAuthentication(name);\n if (!cancelled) {\n cancelledAuthServers.delete(name);\n }\n return cancelled;\n },\n\n disconnect,\n\n getTools() {\n return { ...tools };\n },\n\n hasServers() {\n const hasConfigured = config.mcpServers !== undefined && Object.keys(config.mcpServers).length > 0;\n const hasSkipped = config.skippedServers !== undefined && config.skippedServers.length > 0;\n return hasConfigured || hasSkipped;\n },\n\n getServerStatuses() {\n return Array.from(serverStatuses.values(), withAuthenticating);\n },\n\n getSkippedServers() {\n return [...(config.skippedServers ?? [])];\n },\n\n getConfigPaths() {\n return {\n project: getProjectMcpPath(projectDir, configDirName),\n global: getGlobalMcpPath(configDirName),\n claude: getClaudeSettingsPath(projectDir),\n };\n },\n\n getConfig() {\n return config;\n },\n\n getServerLogs(name: string) {\n return [...(stderrLogs.get(name) ?? [])];\n },\n };\n}\n"],"mappings":"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","names":[]}