@mastra/code-sdk 1.1.4-alpha.0 → 1.2.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +83 -0
- package/dist/agents/mastracode-gateway.d.ts.map +1 -1
- package/dist/agents/mastracode-gateway.js +27 -13
- package/dist/agents/mastracode-gateway.js.map +1 -1
- package/dist/agents/model.d.ts +16 -1
- package/dist/agents/model.d.ts.map +1 -1
- package/dist/agents/model.js +26 -7
- package/dist/agents/model.js.map +1 -1
- package/dist/headless/flags.js +1 -1
- package/dist/headless/flags.js.map +1 -1
- package/dist/headless/types.d.ts +2 -2
- package/dist/headless/types.d.ts.map +1 -1
- package/dist/headless/types.js +2 -1
- package/dist/headless/types.js.map +1 -1
- package/dist/hooks/executor.d.ts.map +1 -1
- package/dist/hooks/executor.js +1 -0
- package/dist/hooks/executor.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp/manager.d.ts +27 -0
- package/dist/mcp/manager.d.ts.map +1 -1
- package/dist/mcp/manager.js +128 -10
- package/dist/mcp/manager.js.map +1 -1
- package/dist/mcp/state.d.ts +33 -0
- package/dist/mcp/state.d.ts.map +1 -0
- package/dist/mcp/state.js +88 -0
- package/dist/mcp/state.js.map +1 -0
- package/dist/mcp/types.d.ts +13 -0
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/onboarding/settings.d.ts +37 -1
- package/dist/onboarding/settings.d.ts.map +1 -1
- package/dist/onboarding/settings.js +53 -3
- package/dist/onboarding/settings.js.map +1 -1
- package/dist/providers/claude-max.d.ts +13 -0
- package/dist/providers/claude-max.d.ts.map +1 -1
- package/dist/providers/claude-max.js +83 -3
- package/dist/providers/claude-max.js.map +1 -1
- package/dist/providers/openai-codex.d.ts +3 -1
- package/dist/providers/openai-codex.d.ts.map +1 -1
- package/dist/providers/openai-codex.js +13 -2
- package/dist/providers/openai-codex.js.map +1 -1
- package/dist/schema.d.ts +8 -2
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +3 -2
- package/dist/schema.js.map +1 -1
- package/package.json +12 -12
package/dist/mcp/manager.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DEFAULT_CONFIG_DIR } from "../constants.js";
|
|
2
2
|
import { getAppDataDir } from "../utils/project.js";
|
|
3
3
|
import { DEFAULT_OAUTH_REDIRECT_URL, getClaudeSettingsPath, getGlobalMcpPath, getProjectMcpPath, loadMcpConfig, resolveOAuthRedirectUrl } from "./config.js";
|
|
4
|
+
import { loadDisabledServers, loadGlobalDisableState, saveDisabledServers, saveGlobalDisableState } from "./state.js";
|
|
4
5
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
5
6
|
import { dirname, join } from "path";
|
|
6
7
|
import { MCPClient, MCPOAuthClientProvider } from "@mastra/mcp";
|
|
@@ -94,6 +95,16 @@ function createMcpManager(projectDir, configDirName = DEFAULT_CONFIG_DIR, extraS
|
|
|
94
95
|
};
|
|
95
96
|
};
|
|
96
97
|
let config = applyExtraServers(loadMcpConfig(projectDir, configDirName));
|
|
98
|
+
let disabledServers = new Set(loadDisabledServers(projectDir));
|
|
99
|
+
let globalDisableState = loadGlobalDisableState();
|
|
100
|
+
let globallyDisabledServers = new Set(globalDisableState.disabledServers);
|
|
101
|
+
/** Whether a server is disabled in any scope (global kill switch, global list, or project list). */
|
|
102
|
+
const isDisabled = (name) => globalDisableState.allDisabled || globallyDisabledServers.has(name) || disabledServers.has(name);
|
|
103
|
+
/** Which scope disables a server. Global takes precedence — project-level enable can't undo it. */
|
|
104
|
+
const disabledScopeOf = (name) => {
|
|
105
|
+
if (globalDisableState.allDisabled || globallyDisabledServers.has(name)) return "global";
|
|
106
|
+
if (disabledServers.has(name)) return "project";
|
|
107
|
+
};
|
|
97
108
|
let client = null;
|
|
98
109
|
let serverDefs = {};
|
|
99
110
|
let tools = {};
|
|
@@ -187,9 +198,26 @@ function createMcpManager(projectDir, configDirName = DEFAULT_CONFIG_DIR, extraS
|
|
|
187
198
|
};
|
|
188
199
|
return defs;
|
|
189
200
|
}
|
|
201
|
+
/** Seed a `disabled` status for every disabled server so it stays visible. */
|
|
202
|
+
function setDisabledStatuses() {
|
|
203
|
+
for (const [name, cfg] of Object.entries(config.mcpServers ?? {})) {
|
|
204
|
+
const scope = disabledScopeOf(name);
|
|
205
|
+
if (!scope) continue;
|
|
206
|
+
serverStatuses.set(name, {
|
|
207
|
+
name,
|
|
208
|
+
connected: false,
|
|
209
|
+
toolCount: 0,
|
|
210
|
+
toolNames: [],
|
|
211
|
+
transport: getTransport(cfg),
|
|
212
|
+
disabled: true,
|
|
213
|
+
disabledScope: scope
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
190
217
|
async function connectAndCollectTools() {
|
|
191
|
-
|
|
192
|
-
|
|
218
|
+
setDisabledStatuses();
|
|
219
|
+
const servers = Object.fromEntries(Object.entries(config.mcpServers ?? {}).filter(([name]) => !isDisabled(name)));
|
|
220
|
+
if (Object.keys(servers).length === 0) return;
|
|
193
221
|
const serverNames = Object.keys(servers);
|
|
194
222
|
for (const name of serverNames) serverStatuses.set(name, {
|
|
195
223
|
name,
|
|
@@ -343,6 +371,48 @@ function createMcpManager(projectDir, configDirName = DEFAULT_CONFIG_DIR, extraS
|
|
|
343
371
|
client = null;
|
|
344
372
|
}
|
|
345
373
|
}
|
|
374
|
+
/** Tear down all connections and reconnect every enabled server. */
|
|
375
|
+
async function rebuildConnections() {
|
|
376
|
+
await disconnect();
|
|
377
|
+
tools = {};
|
|
378
|
+
serverStatuses = /* @__PURE__ */ new Map();
|
|
379
|
+
stderrLogs = /* @__PURE__ */ new Map();
|
|
380
|
+
initialized = false;
|
|
381
|
+
await connectAndCollectTools();
|
|
382
|
+
initialized = true;
|
|
383
|
+
}
|
|
384
|
+
function disabledStatus(name) {
|
|
385
|
+
const cfg = config.mcpServers?.[name];
|
|
386
|
+
return {
|
|
387
|
+
name,
|
|
388
|
+
connected: false,
|
|
389
|
+
toolCount: 0,
|
|
390
|
+
toolNames: [],
|
|
391
|
+
transport: cfg ? getTransport(cfg) : "stdio",
|
|
392
|
+
disabled: true,
|
|
393
|
+
disabledScope: disabledScopeOf(name)
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
function persistProjectDelta(mutate) {
|
|
397
|
+
const fresh = new Set(loadDisabledServers(projectDir));
|
|
398
|
+
mutate(fresh);
|
|
399
|
+
disabledServers = fresh;
|
|
400
|
+
saveDisabledServers(projectDir, Array.from(fresh));
|
|
401
|
+
}
|
|
402
|
+
function persistGlobalDelta(mutate) {
|
|
403
|
+
const onDisk = loadGlobalDisableState();
|
|
404
|
+
const fresh = {
|
|
405
|
+
allDisabled: onDisk.allDisabled,
|
|
406
|
+
disabledServers: new Set(onDisk.disabledServers)
|
|
407
|
+
};
|
|
408
|
+
mutate(fresh);
|
|
409
|
+
globallyDisabledServers = fresh.disabledServers;
|
|
410
|
+
globalDisableState = {
|
|
411
|
+
allDisabled: fresh.allDisabled,
|
|
412
|
+
disabledServers: Array.from(fresh.disabledServers)
|
|
413
|
+
};
|
|
414
|
+
saveGlobalDisableState(globalDisableState);
|
|
415
|
+
}
|
|
346
416
|
return {
|
|
347
417
|
async init() {
|
|
348
418
|
if (initialized) return;
|
|
@@ -355,22 +425,66 @@ function createMcpManager(projectDir, configDirName = DEFAULT_CONFIG_DIR, extraS
|
|
|
355
425
|
const connected = statuses.filter((s) => s.connected);
|
|
356
426
|
return {
|
|
357
427
|
connected,
|
|
358
|
-
failed: statuses.filter((s) => !s.connected),
|
|
428
|
+
failed: statuses.filter((s) => !s.connected && !s.disabled),
|
|
359
429
|
skipped: [...config.skippedServers ?? []],
|
|
360
430
|
totalTools: connected.reduce((sum, s) => sum + s.toolCount, 0)
|
|
361
431
|
};
|
|
362
432
|
},
|
|
363
433
|
async reload() {
|
|
364
|
-
await disconnect();
|
|
365
434
|
config = applyExtraServers(loadMcpConfig(projectDir, configDirName));
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
435
|
+
disabledServers = new Set(loadDisabledServers(projectDir));
|
|
436
|
+
globalDisableState = loadGlobalDisableState();
|
|
437
|
+
globallyDisabledServers = new Set(globalDisableState.disabledServers);
|
|
438
|
+
await rebuildConnections();
|
|
439
|
+
},
|
|
440
|
+
async setServerDisabled(name, disabled, options) {
|
|
441
|
+
if (!config.mcpServers?.[name]) return {
|
|
442
|
+
name,
|
|
443
|
+
connected: false,
|
|
444
|
+
toolCount: 0,
|
|
445
|
+
toolNames: [],
|
|
446
|
+
transport: "stdio",
|
|
447
|
+
error: `Server "${name}" not found in config`
|
|
448
|
+
};
|
|
449
|
+
const wasEffectivelyDisabled = isDisabled(name);
|
|
450
|
+
if (options?.global) persistGlobalDelta((state) => {
|
|
451
|
+
if (disabled) state.disabledServers.add(name);
|
|
452
|
+
else state.disabledServers.delete(name);
|
|
453
|
+
});
|
|
454
|
+
else persistProjectDelta((names) => {
|
|
455
|
+
if (disabled) names.add(name);
|
|
456
|
+
else names.delete(name);
|
|
457
|
+
});
|
|
458
|
+
if (isDisabled(name) !== wasEffectivelyDisabled) {
|
|
459
|
+
await rebuildConnections();
|
|
460
|
+
return withAuthenticating(serverStatuses.get(name) ?? disabledStatus(name));
|
|
461
|
+
}
|
|
462
|
+
return withAuthenticating(isDisabled(name) ? disabledStatus(name) : serverStatuses.get(name) ?? disabledStatus(name));
|
|
463
|
+
},
|
|
464
|
+
async setAllDisabled(disabled, options) {
|
|
465
|
+
const configuredNames = Object.keys(config.mcpServers ?? {});
|
|
466
|
+
const effectiveBefore = configuredNames.filter((name) => isDisabled(name)).join(",");
|
|
467
|
+
if (options?.global) persistGlobalDelta((state) => {
|
|
468
|
+
state.allDisabled = disabled;
|
|
469
|
+
if (!disabled) state.disabledServers.clear();
|
|
470
|
+
});
|
|
471
|
+
else persistProjectDelta((names) => {
|
|
472
|
+
if (disabled) for (const name of configuredNames) names.add(name);
|
|
473
|
+
else names.clear();
|
|
474
|
+
});
|
|
475
|
+
if (configuredNames.filter((name) => isDisabled(name)).join(",") !== effectiveBefore) await rebuildConnections();
|
|
476
|
+
},
|
|
477
|
+
getDisabledServers() {
|
|
478
|
+
return Object.keys(config.mcpServers ?? {}).filter((name) => isDisabled(name)).sort();
|
|
479
|
+
},
|
|
480
|
+
isAllDisabledGlobally() {
|
|
481
|
+
return globalDisableState.allDisabled;
|
|
372
482
|
},
|
|
373
483
|
async reconnectServer(name) {
|
|
484
|
+
if (isDisabled(name)) return {
|
|
485
|
+
...disabledStatus(name),
|
|
486
|
+
error: `Server "${name}" is disabled — enable it first`
|
|
487
|
+
};
|
|
374
488
|
const cfg = config.mcpServers?.[name];
|
|
375
489
|
if (!cfg) return {
|
|
376
490
|
name,
|
|
@@ -400,6 +514,10 @@ function createMcpManager(projectDir, configDirName = DEFAULT_CONFIG_DIR, extraS
|
|
|
400
514
|
transport: "stdio",
|
|
401
515
|
error: `Server "${name}" not found in config`
|
|
402
516
|
};
|
|
517
|
+
if (isDisabled(name)) return {
|
|
518
|
+
...disabledStatus(name),
|
|
519
|
+
error: `Server "${name}" is disabled — enable it first`
|
|
520
|
+
};
|
|
403
521
|
if (!client) return {
|
|
404
522
|
name,
|
|
405
523
|
connected: false,
|
package/dist/mcp/manager.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.js","names":[],"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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"}
|
|
1
|
+
{"version":3,"file":"manager.js","names":[],"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 { loadDisabledServers, loadGlobalDisableState, saveDisabledServers, saveGlobalDisableState } from './state.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 /**\n * Disable or enable a single server by name. The change is persisted (in\n * mastracode's app data, not the user's config files) and survives restarts.\n * With `global: true` the change applies to every project; otherwise it is\n * scoped to this project. Enabling only removes the server from the given\n * scope — a server disabled in the other scope stays disabled, and the\n * returned status's `disabledScope` says which scope still applies.\n * Connections are rebuilt, so other servers reconnect — same behavior as\n * {@link reload}. Returns the server's resulting status.\n */\n setServerDisabled(name: string, disabled: boolean, options?: { global?: boolean }): Promise<McpServerStatus>;\n /**\n * Disable or enable all servers at once. Project scope records every\n * currently-configured server name; enabling clears the project list.\n * Global scope sets/clears a persisted all-MCP kill switch (and, when\n * enabling, also clears globally disabled server names). Persisted like\n * {@link setServerDisabled}.\n */\n setAllDisabled(disabled: boolean, options?: { global?: boolean }): Promise<void>;\n /** Names of configured servers that are currently disabled (any scope). */\n getDisabledServers(): string[];\n /** Whether all MCP is disabled globally (across every project). */\n isAllDisabledGlobally(): 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 disabledServers = new Set(loadDisabledServers(projectDir));\n let globalDisableState = loadGlobalDisableState();\n let globallyDisabledServers = new Set(globalDisableState.disabledServers);\n\n /** Whether a server is disabled in any scope (global kill switch, global list, or project list). */\n const isDisabled = (name: string): boolean =>\n globalDisableState.allDisabled || globallyDisabledServers.has(name) || disabledServers.has(name);\n\n /** Which scope disables a server. Global takes precedence — project-level enable can't undo it. */\n const disabledScopeOf = (name: string): 'project' | 'global' | undefined => {\n if (globalDisableState.allDisabled || globallyDisabledServers.has(name)) return 'global';\n if (disabledServers.has(name)) return 'project';\n return undefined;\n };\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 /** Seed a `disabled` status for every disabled server so it stays visible. */\n function setDisabledStatuses(): void {\n for (const [name, cfg] of Object.entries(config.mcpServers ?? {})) {\n const scope = disabledScopeOf(name);\n if (!scope) continue;\n serverStatuses.set(name, {\n name,\n connected: false,\n toolCount: 0,\n toolNames: [],\n transport: getTransport(cfg),\n disabled: true,\n disabledScope: scope,\n });\n }\n }\n\n async function connectAndCollectTools(): Promise<void> {\n setDisabledStatuses();\n\n const servers = Object.fromEntries(Object.entries(config.mcpServers ?? {}).filter(([name]) => !isDisabled(name)));\n if (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 /** Tear down all connections and reconnect every enabled server. */\n async function rebuildConnections(): Promise<void> {\n await disconnect();\n tools = {};\n serverStatuses = new Map();\n stderrLogs = new Map();\n initialized = false;\n await connectAndCollectTools();\n initialized = true;\n }\n\n function disabledStatus(name: string): McpServerStatus {\n const cfg = config.mcpServers?.[name];\n return {\n name,\n connected: false,\n toolCount: 0,\n toolNames: [],\n transport: cfg ? getTransport(cfg) : 'stdio',\n disabled: true,\n disabledScope: disabledScopeOf(name),\n };\n }\n\n // Read-merge-write persistence: re-read the persisted state and apply this\n // operation's delta on top, then adopt the merged result in memory. This\n // way a concurrent mastracode process's changes (e.g. another window\n // flipping the global kill switch) are never clobbered by this manager's\n // construction-time snapshot.\n function persistProjectDelta(mutate: (names: Set<string>) => void): void {\n const fresh = new Set(loadDisabledServers(projectDir));\n mutate(fresh);\n disabledServers = fresh;\n saveDisabledServers(projectDir, Array.from(fresh));\n }\n\n function persistGlobalDelta(mutate: (state: { allDisabled: boolean; disabledServers: Set<string> }) => void): void {\n const onDisk = loadGlobalDisableState();\n const fresh = { allDisabled: onDisk.allDisabled, disabledServers: new Set(onDisk.disabledServers) };\n mutate(fresh);\n globallyDisabledServers = fresh.disabledServers;\n globalDisableState = { allDisabled: fresh.allDisabled, disabledServers: Array.from(fresh.disabledServers) };\n saveGlobalDisableState(globalDisableState);\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 && !s.disabled);\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 config = applyExtraServers(loadMcpConfig(projectDir, configDirName));\n disabledServers = new Set(loadDisabledServers(projectDir));\n globalDisableState = loadGlobalDisableState();\n globallyDisabledServers = new Set(globalDisableState.disabledServers);\n await rebuildConnections();\n },\n\n async setServerDisabled(name: string, disabled: boolean, options?: { global?: boolean }): Promise<McpServerStatus> {\n if (!config.mcpServers?.[name]) {\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 const wasEffectivelyDisabled = isDisabled(name);\n\n if (options?.global) {\n persistGlobalDelta(state => {\n if (disabled) {\n state.disabledServers.add(name);\n } else {\n state.disabledServers.delete(name);\n }\n });\n } else {\n persistProjectDelta(names => {\n if (disabled) {\n names.add(name);\n } else {\n names.delete(name);\n }\n });\n }\n\n // Only rebuild connections when the server's effective state actually\n // flipped — e.g. removing it from one scope while the other scope (or\n // the global kill switch) still disables it leaves connections alone.\n if (isDisabled(name) !== wasEffectivelyDisabled) {\n await rebuildConnections();\n return withAuthenticating(serverStatuses.get(name) ?? disabledStatus(name));\n }\n // Effective state unchanged. If still disabled, report a fresh status so\n // `disabledScope` reflects the scope that (still) applies.\n return withAuthenticating(\n isDisabled(name) ? disabledStatus(name) : (serverStatuses.get(name) ?? disabledStatus(name)),\n );\n },\n\n async setAllDisabled(disabled: boolean, options?: { global?: boolean }): Promise<void> {\n const configuredNames = Object.keys(config.mcpServers ?? {});\n const effectiveBefore = configuredNames.filter(name => isDisabled(name)).join(',');\n if (options?.global) {\n persistGlobalDelta(state => {\n state.allDisabled = disabled;\n if (!disabled) {\n // Enabling globally also clears globally disabled server names so\n // \"/mcp enable all --global\" fully restores global state.\n state.disabledServers.clear();\n }\n });\n } else {\n persistProjectDelta(names => {\n if (disabled) {\n for (const name of configuredNames) {\n names.add(name);\n }\n } else {\n names.clear();\n }\n });\n }\n // Skip the disconnect/reconnect cycle when the effective disabled set is\n // unchanged — e.g. repeating \"/mcp disable all\", or a project-scope\n // \"enable all\" while the global kill switch still disables everything.\n const effectiveAfter = configuredNames.filter(name => isDisabled(name)).join(',');\n if (effectiveAfter !== effectiveBefore) {\n await rebuildConnections();\n }\n },\n\n getDisabledServers() {\n return Object.keys(config.mcpServers ?? {})\n .filter(name => isDisabled(name))\n .sort();\n },\n\n isAllDisabledGlobally() {\n return globalDisableState.allDisabled;\n },\n\n async reconnectServer(name: string): Promise<McpServerStatus> {\n if (isDisabled(name)) {\n return { ...disabledStatus(name), error: `Server \"${name}\" is disabled — enable it first` };\n }\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 (isDisabled(name)) {\n return { ...disabledStatus(name), error: `Server \"${name}\" is disabled — enable it first` };\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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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persisted MCP disable state — mastracode-owned so user config files
|
|
3
|
+
* (mcp.json, .mcp.json, .claude/settings.local.json) are never mutated.
|
|
4
|
+
*
|
|
5
|
+
* Stored as a single JSON file in the app data dir, with a global section
|
|
6
|
+
* (applies to every project) and per-project entries:
|
|
7
|
+
*
|
|
8
|
+
* {
|
|
9
|
+
* "global": { "allDisabled": true, "disabledServers": ["name"] },
|
|
10
|
+
* "projects": { "/path/to/project": { "disabledServers": ["name"] } }
|
|
11
|
+
* }
|
|
12
|
+
*
|
|
13
|
+
* Disabled names are kept even if the server disappears from config, so a
|
|
14
|
+
* server that is removed and later re-added stays disabled until the user
|
|
15
|
+
* re-enables it.
|
|
16
|
+
*/
|
|
17
|
+
/** Global (all-projects) MCP disable state. */
|
|
18
|
+
export interface McpGlobalDisableState {
|
|
19
|
+
/** When true, every MCP server is disabled regardless of per-server state. */
|
|
20
|
+
allDisabled: boolean;
|
|
21
|
+
/** Server names disabled across all projects. */
|
|
22
|
+
disabledServers: string[];
|
|
23
|
+
}
|
|
24
|
+
export declare function getMcpStatePath(): string;
|
|
25
|
+
/** Load the persisted disabled server names for a project. */
|
|
26
|
+
export declare function loadDisabledServers(projectDir: string): string[];
|
|
27
|
+
/** Persist the disabled server names for a project. */
|
|
28
|
+
export declare function saveDisabledServers(projectDir: string, disabledServers: string[]): void;
|
|
29
|
+
/** Load the persisted global disable state (applies to all projects). */
|
|
30
|
+
export declare function loadGlobalDisableState(): McpGlobalDisableState;
|
|
31
|
+
/** Persist the global disable state. Prunes the section when empty. */
|
|
32
|
+
export declare function saveGlobalDisableState(globalState: McpGlobalDisableState): void;
|
|
33
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/mcp/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAWH,+CAA+C;AAC/C,MAAM,WAAW,qBAAqB;IACpC,8EAA8E;IAC9E,WAAW,EAAE,OAAO,CAAC;IACrB,iDAAiD;IACjD,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAgCD,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAEhE;AAED,uDAAuD;AACvD,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,GAAG,IAAI,CASvF;AAED,yEAAyE;AACzE,wBAAgB,sBAAsB,IAAI,qBAAqB,CAM9D;AAED,uEAAuE;AACvE,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,qBAAqB,GAAG,IAAI,CAc/E"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { getAppDataDir } from "../utils/project.js";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
3
|
+
import { dirname, join } from "path";
|
|
4
|
+
//#region src/mcp/state.ts
|
|
5
|
+
/**
|
|
6
|
+
* Persisted MCP disable state — mastracode-owned so user config files
|
|
7
|
+
* (mcp.json, .mcp.json, .claude/settings.local.json) are never mutated.
|
|
8
|
+
*
|
|
9
|
+
* Stored as a single JSON file in the app data dir, with a global section
|
|
10
|
+
* (applies to every project) and per-project entries:
|
|
11
|
+
*
|
|
12
|
+
* {
|
|
13
|
+
* "global": { "allDisabled": true, "disabledServers": ["name"] },
|
|
14
|
+
* "projects": { "/path/to/project": { "disabledServers": ["name"] } }
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* Disabled names are kept even if the server disappears from config, so a
|
|
18
|
+
* server that is removed and later re-added stays disabled until the user
|
|
19
|
+
* re-enables it.
|
|
20
|
+
*/
|
|
21
|
+
function getMcpStatePath() {
|
|
22
|
+
return join(getAppDataDir(), "mcp-state.json");
|
|
23
|
+
}
|
|
24
|
+
function readStateFile() {
|
|
25
|
+
const filePath = getMcpStatePath();
|
|
26
|
+
if (!existsSync(filePath)) return {};
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
29
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
30
|
+
} catch {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function writeStateFile(state) {
|
|
35
|
+
const filePath = getMcpStatePath();
|
|
36
|
+
const dir = dirname(filePath);
|
|
37
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
38
|
+
const tmpPath = `${filePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
39
|
+
writeFileSync(tmpPath, JSON.stringify(state, null, 2), "utf-8");
|
|
40
|
+
renameSync(tmpPath, filePath);
|
|
41
|
+
}
|
|
42
|
+
function cleanNames(names) {
|
|
43
|
+
if (!Array.isArray(names)) return [];
|
|
44
|
+
return names.filter((name) => typeof name === "string");
|
|
45
|
+
}
|
|
46
|
+
/** Load the persisted disabled server names for a project. */
|
|
47
|
+
function loadDisabledServers(projectDir) {
|
|
48
|
+
return cleanNames(readStateFile().projects?.[projectDir]?.disabledServers);
|
|
49
|
+
}
|
|
50
|
+
/** Persist the disabled server names for a project. */
|
|
51
|
+
function saveDisabledServers(projectDir, disabledServers) {
|
|
52
|
+
const state = readStateFile();
|
|
53
|
+
const projects = state.projects ?? {};
|
|
54
|
+
if (disabledServers.length > 0) projects[projectDir] = { disabledServers: [...disabledServers].sort() };
|
|
55
|
+
else delete projects[projectDir];
|
|
56
|
+
writeStateFile({
|
|
57
|
+
...state,
|
|
58
|
+
projects
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/** Load the persisted global disable state (applies to all projects). */
|
|
62
|
+
function loadGlobalDisableState() {
|
|
63
|
+
const global = readStateFile().global;
|
|
64
|
+
return {
|
|
65
|
+
allDisabled: global?.allDisabled === true,
|
|
66
|
+
disabledServers: cleanNames(global?.disabledServers)
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/** Persist the global disable state. Prunes the section when empty. */
|
|
70
|
+
function saveGlobalDisableState(globalState) {
|
|
71
|
+
const state = readStateFile();
|
|
72
|
+
if (!globalState.allDisabled && globalState.disabledServers.length === 0) {
|
|
73
|
+
delete state.global;
|
|
74
|
+
writeStateFile(state);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
writeStateFile({
|
|
78
|
+
...state,
|
|
79
|
+
global: {
|
|
80
|
+
...globalState.allDisabled ? { allDisabled: true } : {},
|
|
81
|
+
...globalState.disabledServers.length > 0 ? { disabledServers: [...globalState.disabledServers].sort() } : {}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
export { getMcpStatePath, loadDisabledServers, loadGlobalDisableState, saveDisabledServers, saveGlobalDisableState };
|
|
87
|
+
|
|
88
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","names":[],"sources":["../../src/mcp/state.ts"],"sourcesContent":["/**\n * Persisted MCP disable state — mastracode-owned so user config files\n * (mcp.json, .mcp.json, .claude/settings.local.json) are never mutated.\n *\n * Stored as a single JSON file in the app data dir, with a global section\n * (applies to every project) and per-project entries:\n *\n * {\n * \"global\": { \"allDisabled\": true, \"disabledServers\": [\"name\"] },\n * \"projects\": { \"/path/to/project\": { \"disabledServers\": [\"name\"] } }\n * }\n *\n * Disabled names are kept even if the server disappears from config, so a\n * server that is removed and later re-added stays disabled until the user\n * re-enables it.\n */\n\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { getAppDataDir } from '../utils/project.js';\n\ninterface McpStateFile {\n global?: { allDisabled?: boolean; disabledServers?: string[] };\n projects?: Record<string, { disabledServers?: string[] }>;\n}\n\n/** Global (all-projects) MCP disable state. */\nexport interface McpGlobalDisableState {\n /** When true, every MCP server is disabled regardless of per-server state. */\n allDisabled: boolean;\n /** Server names disabled across all projects. */\n disabledServers: string[];\n}\n\nexport function getMcpStatePath(): string {\n return join(getAppDataDir(), 'mcp-state.json');\n}\n\nfunction readStateFile(): McpStateFile {\n const filePath = getMcpStatePath();\n if (!existsSync(filePath)) return {};\n try {\n const parsed = JSON.parse(readFileSync(filePath, 'utf-8'));\n return parsed && typeof parsed === 'object' ? (parsed as McpStateFile) : {};\n } catch {\n return {};\n }\n}\n\nfunction writeStateFile(state: McpStateFile): void {\n const filePath = getMcpStatePath();\n const dir = dirname(filePath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n // Atomic write (same pattern as FileOAuthStorage) so a crash mid-write\n // never leaves a truncated state file. The temp name is process-unique so\n // two concurrent mastracode processes never share a partially written file.\n const tmpPath = `${filePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;\n writeFileSync(tmpPath, JSON.stringify(state, null, 2), 'utf-8');\n renameSync(tmpPath, filePath);\n}\n\nfunction cleanNames(names: unknown): string[] {\n if (!Array.isArray(names)) return [];\n return names.filter((name): name is string => typeof name === 'string');\n}\n\n/** Load the persisted disabled server names for a project. */\nexport function loadDisabledServers(projectDir: string): string[] {\n return cleanNames(readStateFile().projects?.[projectDir]?.disabledServers);\n}\n\n/** Persist the disabled server names for a project. */\nexport function saveDisabledServers(projectDir: string, disabledServers: string[]): void {\n const state = readStateFile();\n const projects = state.projects ?? {};\n if (disabledServers.length > 0) {\n projects[projectDir] = { disabledServers: [...disabledServers].sort() };\n } else {\n delete projects[projectDir];\n }\n writeStateFile({ ...state, projects });\n}\n\n/** Load the persisted global disable state (applies to all projects). */\nexport function loadGlobalDisableState(): McpGlobalDisableState {\n const global = readStateFile().global;\n return {\n allDisabled: global?.allDisabled === true,\n disabledServers: cleanNames(global?.disabledServers),\n };\n}\n\n/** Persist the global disable state. Prunes the section when empty. */\nexport function saveGlobalDisableState(globalState: McpGlobalDisableState): void {\n const state = readStateFile();\n if (!globalState.allDisabled && globalState.disabledServers.length === 0) {\n delete state.global;\n writeStateFile(state);\n return;\n }\n writeStateFile({\n ...state,\n global: {\n ...(globalState.allDisabled ? { allDisabled: true } : {}),\n ...(globalState.disabledServers.length > 0 ? { disabledServers: [...globalState.disabledServers].sort() } : {}),\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAkCA,SAAgB,kBAA0B;CACxC,OAAO,KAAK,cAAc,GAAG,gBAAgB;AAC/C;AAEA,SAAS,gBAA8B;CACrC,MAAM,WAAW,gBAAgB;CACjC,IAAI,CAAC,WAAW,QAAQ,GAAG,OAAO,CAAC;CACnC,IAAI;EACF,MAAM,SAAS,KAAK,MAAM,aAAa,UAAU,OAAO,CAAC;EACzD,OAAO,UAAU,OAAO,WAAW,WAAY,SAA0B,CAAC;CAC5E,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AAEA,SAAS,eAAe,OAA2B;CACjD,MAAM,WAAW,gBAAgB;CACjC,MAAM,MAAM,QAAQ,QAAQ;CAC5B,IAAI,CAAC,WAAW,GAAG,GACjB,UAAU,KAAK,EAAE,WAAW,KAAK,CAAC;CAKpC,MAAM,UAAU,GAAG,SAAS,GAAG,QAAQ,IAAI,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE;CAClF,cAAc,SAAS,KAAK,UAAU,OAAO,MAAM,CAAC,GAAG,OAAO;CAC9D,WAAW,SAAS,QAAQ;AAC9B;AAEA,SAAS,WAAW,OAA0B;CAC5C,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO,CAAC;CACnC,OAAO,MAAM,QAAQ,SAAyB,OAAO,SAAS,QAAQ;AACxE;;AAGA,SAAgB,oBAAoB,YAA8B;CAChE,OAAO,WAAW,cAAc,CAAC,CAAC,WAAW,WAAW,EAAE,eAAe;AAC3E;;AAGA,SAAgB,oBAAoB,YAAoB,iBAAiC;CACvF,MAAM,QAAQ,cAAc;CAC5B,MAAM,WAAW,MAAM,YAAY,CAAC;CACpC,IAAI,gBAAgB,SAAS,GAC3B,SAAS,cAAc,EAAE,iBAAiB,CAAC,GAAG,eAAe,CAAC,CAAC,KAAK,EAAE;MAEtE,OAAO,SAAS;CAElB,eAAe;EAAE,GAAG;EAAO;CAAS,CAAC;AACvC;;AAGA,SAAgB,yBAAgD;CAC9D,MAAM,SAAS,cAAc,CAAC,CAAC;CAC/B,OAAO;EACL,aAAa,QAAQ,gBAAgB;EACrC,iBAAiB,WAAW,QAAQ,eAAe;CACrD;AACF;;AAGA,SAAgB,uBAAuB,aAA0C;CAC/E,MAAM,QAAQ,cAAc;CAC5B,IAAI,CAAC,YAAY,eAAe,YAAY,gBAAgB,WAAW,GAAG;EACxE,OAAO,MAAM;EACb,eAAe,KAAK;EACpB;CACF;CACA,eAAe;EACb,GAAG;EACH,QAAQ;GACN,GAAI,YAAY,cAAc,EAAE,aAAa,KAAK,IAAI,CAAC;GACvD,GAAI,YAAY,gBAAgB,SAAS,IAAI,EAAE,iBAAiB,CAAC,GAAG,YAAY,eAAe,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC;EAC/G;CACF,CAAC;AACH"}
|
package/dist/mcp/types.d.ts
CHANGED
|
@@ -103,5 +103,18 @@ export interface McpServerStatus {
|
|
|
103
103
|
* The UI uses it to suppress a misleading "Failed to authenticate" message.
|
|
104
104
|
*/
|
|
105
105
|
cancelled?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Whether the user disabled this server. Disabled servers stay visible in
|
|
108
|
+
* status listings (so they can be re-enabled) but are never connected and
|
|
109
|
+
* contribute no tools.
|
|
110
|
+
*/
|
|
111
|
+
disabled?: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Where the disable state comes from when `disabled` is true. `global`
|
|
114
|
+
* means the server (or all of MCP) is disabled across every project and
|
|
115
|
+
* must be re-enabled globally; `project` means only this project disabled
|
|
116
|
+
* it. Global takes precedence when both apply.
|
|
117
|
+
*/
|
|
118
|
+
disabledScope?: 'project' | 'global';
|
|
106
119
|
}
|
|
107
120
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/mcp/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/mcp/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,gCAAgC;IAChC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,kEAAkE;IAClE,KAAK,CAAC,EAAE,kBAAkB,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,6FAA6F;IAC7F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,oBAAoB,GAAG,mBAAmB,CAAC;AAEzE;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC7C,cAAc,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,SAAS,EAAE,OAAO,CAAC;IACnB,iDAAiD;IACjD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,kCAAkC;IAClC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,wCAAwC;IACxC,SAAS,EAAE,OAAO,GAAG,MAAM,CAAC;IAC5B,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/mcp/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,gCAAgC;IAChC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,kEAAkE;IAClE,KAAK,CAAC,EAAE,kBAAkB,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,6FAA6F;IAC7F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,oBAAoB,GAAG,mBAAmB,CAAC;AAEzE;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC7C,cAAc,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,SAAS,EAAE,OAAO,CAAC;IACnB,iDAAiD;IACjD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,kCAAkC;IAClC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,wCAAwC;IACxC,SAAS,EAAE,OAAO,GAAG,MAAM,CAAC;IAC5B,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;CACtC"}
|
|
@@ -55,7 +55,7 @@ export declare const MEMORY_GATEWAY_PROVIDER = "mastra-gateway";
|
|
|
55
55
|
/** @deprecated Renamed to {@link MASTRA_GATEWAY_DEFAULT_URL}. */
|
|
56
56
|
export declare const MEMORY_GATEWAY_DEFAULT_URL = "https://gateway-api.mastra.ai";
|
|
57
57
|
/** Valid persisted thinking level values. */
|
|
58
|
-
export type ThinkingLevelSetting = 'off' | 'low' | 'medium' | 'high' | 'xhigh';
|
|
58
|
+
export type ThinkingLevelSetting = 'off' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
59
59
|
/** Browser provider type. */
|
|
60
60
|
export type BrowserProvider = 'stagehand' | 'agent-browser';
|
|
61
61
|
/** Direct TUI `!` shell passthrough mode. */
|
|
@@ -142,6 +142,13 @@ export interface GlobalSettings {
|
|
|
142
142
|
activeModelPackId: string | null;
|
|
143
143
|
/** Explicit per-mode overrides — used when no activeModelPackId is set. */
|
|
144
144
|
modeDefaults: Record<string, string>;
|
|
145
|
+
/**
|
|
146
|
+
* Per-mode reasoning-effort defaults (e.g. { build: "high", plan: "xhigh" }).
|
|
147
|
+
* Resolved at request time; falls back to `preferences.thinkingLevel` for
|
|
148
|
+
* modes without an entry. Overridden per-session via /think or the session
|
|
149
|
+
* settings panel.
|
|
150
|
+
*/
|
|
151
|
+
modeThinkingDefaults: Record<string, ThinkingLevelSetting>;
|
|
145
152
|
/**
|
|
146
153
|
* Active OM pack ID (e.g. "gemini", "anthropic", "custom").
|
|
147
154
|
* When set, the OM model is resolved from the pack at startup so pack
|
|
@@ -237,9 +244,22 @@ export declare const OBSERVABILITY_AUTH_PREFIX = "observability:";
|
|
|
237
244
|
export declare const STORAGE_DEFAULTS: StorageSettings;
|
|
238
245
|
/** Default STT engine: on-device macOS recognizer where available, else cloud. */
|
|
239
246
|
export declare function defaultVoiceEngine(): VoiceEngine;
|
|
247
|
+
export declare const THINKING_LEVEL_VALUES: ThinkingLevelSetting[];
|
|
248
|
+
export declare function isThinkingLevelSetting(value: unknown): value is ThinkingLevelSetting;
|
|
240
249
|
export declare function getSettingsPath(): string;
|
|
241
250
|
export declare function getCustomProviderId(name: string): string;
|
|
242
251
|
export declare function toCustomProviderModelId(providerName: string, modelName: string): string;
|
|
252
|
+
/**
|
|
253
|
+
* The shared gateway catalog namespaces provider keys under their owning
|
|
254
|
+
* gateway id, so a custom provider's models surface in the `/models` catalog
|
|
255
|
+
* as `mastracode/<providerId>/<model>` instead of the canonical
|
|
256
|
+
* `<providerId>/<model>` that model resolution expects. Persisting the
|
|
257
|
+
* gateway-qualified id verbatim breaks lookup later (the provider is parsed
|
|
258
|
+
* as `mastracode`). Strip the prefix before saving — but only when the
|
|
259
|
+
* middle segment matches one of the user's configured custom providers, so
|
|
260
|
+
* legitimate `mastracode/...` gateway-routed ids are left untouched.
|
|
261
|
+
*/
|
|
262
|
+
export declare function stripMastraCodeCustomProviderPrefix(modelId: string, customProviders: Array<Pick<CustomProviderSetting, 'name'>>): string;
|
|
243
263
|
export declare function parseCustomProviders(rawProviders: unknown): CustomProviderSetting[];
|
|
244
264
|
export declare function migrateLegacyVariedPack(settings: GlobalSettings): boolean;
|
|
245
265
|
export declare function loadSettings(filePath?: string): GlobalSettings;
|
|
@@ -275,6 +295,22 @@ export declare function resolveModelDefaults(settings: GlobalSettings, builtinPa
|
|
|
275
295
|
id: string;
|
|
276
296
|
models: Record<string, string>;
|
|
277
297
|
}>): Record<string, string>;
|
|
298
|
+
/** Where a resolved default thinking level came from. */
|
|
299
|
+
export type ThinkingLevelSource = 'mode-default' | 'global';
|
|
300
|
+
/**
|
|
301
|
+
* Resolve the default reasoning-effort level for a mode.
|
|
302
|
+
*
|
|
303
|
+
* Lookup order:
|
|
304
|
+
* 1. `models.modeThinkingDefaults[mode]` when set for the mode.
|
|
305
|
+
* 2. The global `preferences.thinkingLevel`.
|
|
306
|
+
*
|
|
307
|
+
* Session-level overrides (via /think or the session settings panel) take
|
|
308
|
+
* precedence over both and are handled by the caller.
|
|
309
|
+
*/
|
|
310
|
+
export declare function resolveDefaultThinkingLevel(settings: GlobalSettings, mode?: string | null): {
|
|
311
|
+
level: ThinkingLevelSetting;
|
|
312
|
+
source: ThinkingLevelSource;
|
|
313
|
+
};
|
|
278
314
|
/**
|
|
279
315
|
* Resolve the effective model ID for one of the two OM roles.
|
|
280
316
|
*
|