@mastra/code-sdk 0.2.0-alpha.9 → 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.
- package/CHANGELOG.md +124 -0
- package/README.md +20 -0
- package/dist/agents/workspace.d.ts.map +1 -1
- package/dist/agents/workspace.js +4 -4
- package/dist/agents/workspace.js.map +1 -1
- package/dist/auth/storage.js +2 -2
- package/dist/auth/storage.js.map +1 -1
- package/dist/constants.js +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/goal-manager.d.ts +2 -7
- package/dist/goal-manager.d.ts.map +1 -1
- package/dist/goal-manager.js +28 -37
- package/dist/goal-manager.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/config.d.ts +21 -1
- package/dist/mcp/config.d.ts.map +1 -1
- package/dist/mcp/config.js +30 -6
- package/dist/mcp/config.js.map +1 -1
- package/dist/mcp/manager.d.ts +22 -0
- package/dist/mcp/manager.d.ts.map +1 -1
- package/dist/mcp/manager.js +193 -80
- package/dist/mcp/manager.js.map +1 -1
- package/dist/mcp/types.d.ts +24 -2
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/onboarding/packs.js +4 -4
- package/dist/onboarding/packs.js.map +1 -1
- package/dist/schema.d.ts +6 -3
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +2 -1
- package/dist/schema.js.map +1 -1
- package/package.json +14 -14
package/dist/mcp/config.js.map
CHANGED
|
@@ -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":[]}
|
package/dist/mcp/manager.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/mcp/manager.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
-
|
|
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
|
|
125
|
+
redirectUrl,
|
|
111
126
|
clientMetadata: {
|
|
112
|
-
redirect_uris: [
|
|
113
|
-
client_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
|
-
...
|
|
131
|
+
...oauth.scopes?.length ? { scope: oauth.scopes.join(" ") } : {}
|
|
117
132
|
},
|
|
118
|
-
clientInformation:
|
|
119
|
-
client_id:
|
|
120
|
-
...
|
|
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:
|
|
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
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
276
|
-
|
|
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:
|
|
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 ?? []];
|
package/dist/mcp/manager.js.map
CHANGED
|
@@ -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":[]}
|