@duckmind/dm-windows-x64 0.63.4 → 0.63.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/dm.exe +0 -0
  2. package/extensions/.dm-extensions.json +236 -13
  3. package/extensions/dm-context/package.json +1 -1
  4. package/extensions/dm-context/skills/context-management/SKILL.md +173 -223
  5. package/extensions/dm-context/skills/context-management/references/development-and-troubleshooting.md +75 -0
  6. package/extensions/dm-context/skills/context-management/references/interleaved-async-work.md +143 -0
  7. package/extensions/dm-context/skills/context-management/references/planning-and-execution.md +81 -0
  8. package/extensions/dm-context/skills/context-management/references/repeated-items-and-batch-work.md +69 -0
  9. package/extensions/dm-context/skills/context-management/references/retry-branch-and-pivot.md +80 -0
  10. package/extensions/dm-context/skills/context-management/references/search-research-and-reading.md +103 -0
  11. package/extensions/dm-context/skills/context-management/references/task-switching-and-cleanup.md +73 -0
  12. package/extensions/dm-context/src/context.js +3 -2
  13. package/extensions/dm-context/src/index.js +168 -84
  14. package/extensions/dm-mcp-adapter/OAUTH.md +3 -0
  15. package/extensions/dm-mcp-adapter/THIRD_PARTY_NOTICES.md +4 -0
  16. package/extensions/dm-mcp-adapter/abort.js +35 -0
  17. package/extensions/dm-mcp-adapter/agent-dir.js +45 -0
  18. package/extensions/dm-mcp-adapter/agent-plugin-loader.js +349 -0
  19. package/extensions/dm-mcp-adapter/app-bridge.bundle.js +5843 -0
  20. package/extensions/dm-mcp-adapter/cli.js +354 -0
  21. package/extensions/dm-mcp-adapter/commands.js +619 -0
  22. package/extensions/dm-mcp-adapter/config.js +1060 -0
  23. package/extensions/dm-mcp-adapter/consent-manager.js +60 -0
  24. package/extensions/dm-mcp-adapter/direct-tools.js +544 -0
  25. package/extensions/dm-mcp-adapter/dist/agent-dir.js +45 -0
  26. package/extensions/dm-mcp-adapter/dist/agent-plugin-loader.js +349 -0
  27. package/extensions/dm-mcp-adapter/dist/config.js +1060 -0
  28. package/extensions/dm-mcp-adapter/dist/mcp-bearer-store.js +311 -0
  29. package/extensions/dm-mcp-adapter/dist/metadata-cache.js +285 -0
  30. package/extensions/dm-mcp-adapter/dist/package-mcp-loader.js +144 -0
  31. package/extensions/dm-mcp-adapter/dist/resource-tools.js +7 -0
  32. package/extensions/dm-mcp-adapter/dist/types.js +247 -0
  33. package/extensions/dm-mcp-adapter/dist/ui-app-bridge-helpers.js +36 -0
  34. package/extensions/dm-mcp-adapter/dist/ui-stream-types.js +58 -0
  35. package/extensions/dm-mcp-adapter/dist/ui-tool-visibility.js +26 -0
  36. package/extensions/dm-mcp-adapter/dist/utils.js +307 -0
  37. package/extensions/dm-mcp-adapter/elicitation-handler.js +306 -0
  38. package/extensions/dm-mcp-adapter/error-signal.js +16 -0
  39. package/extensions/dm-mcp-adapter/errors.js +225 -0
  40. package/extensions/dm-mcp-adapter/failure-backoff.js +19 -0
  41. package/extensions/dm-mcp-adapter/glimpse-ui.js +61 -0
  42. package/extensions/dm-mcp-adapter/host-html-template.js +464 -0
  43. package/extensions/dm-mcp-adapter/index.js +1079 -0
  44. package/extensions/dm-mcp-adapter/init.js +624 -0
  45. package/extensions/dm-mcp-adapter/json-schema-validator.js +53 -0
  46. package/extensions/dm-mcp-adapter/lifecycle.js +385 -0
  47. package/extensions/dm-mcp-adapter/logger.js +115 -0
  48. package/extensions/dm-mcp-adapter/mcp-auth-flow.js +824 -0
  49. package/extensions/dm-mcp-adapter/mcp-auth.js +732 -0
  50. package/extensions/dm-mcp-adapter/mcp-bearer-store.js +311 -0
  51. package/extensions/dm-mcp-adapter/mcp-callback-server.js +388 -0
  52. package/extensions/dm-mcp-adapter/mcp-code.js +291 -0
  53. package/extensions/dm-mcp-adapter/mcp-keyring-helper.cjs +88 -0
  54. package/extensions/dm-mcp-adapter/mcp-oauth-provider.js +490 -0
  55. package/extensions/dm-mcp-adapter/mcp-output-guard.js +386 -0
  56. package/extensions/dm-mcp-adapter/mcp-panel.js +951 -0
  57. package/extensions/dm-mcp-adapter/mcp-probe.js +151 -0
  58. package/extensions/dm-mcp-adapter/mcp-references.js +263 -0
  59. package/extensions/dm-mcp-adapter/mcp-script-worker.mjs +126 -0
  60. package/extensions/dm-mcp-adapter/mcp-setup-panel.js +578 -0
  61. package/extensions/dm-mcp-adapter/mcp-status.js +80 -0
  62. package/extensions/dm-mcp-adapter/mcp-trace.js +215 -0
  63. package/extensions/dm-mcp-adapter/metadata-cache.js +292 -0
  64. package/extensions/dm-mcp-adapter/namespace-tools.js +155 -0
  65. package/extensions/dm-mcp-adapter/npx-resolver.js +465 -0
  66. package/extensions/dm-mcp-adapter/oauth-handler.js +17 -0
  67. package/extensions/dm-mcp-adapter/oauth.js +17 -0
  68. package/extensions/dm-mcp-adapter/onboarding-state.js +62 -0
  69. package/extensions/dm-mcp-adapter/package-mcp-loader.js +144 -0
  70. package/extensions/dm-mcp-adapter/package.json +68 -0
  71. package/extensions/dm-mcp-adapter/panel-keys.js +26 -0
  72. package/extensions/dm-mcp-adapter/prompts.js +258 -0
  73. package/extensions/dm-mcp-adapter/proxy-modes.js +1336 -0
  74. package/extensions/dm-mcp-adapter/request-headers-command.js +293 -0
  75. package/extensions/dm-mcp-adapter/resource-tools.js +7 -0
  76. package/extensions/dm-mcp-adapter/runtime-owner.js +79 -0
  77. package/extensions/dm-mcp-adapter/sampling-handler.js +222 -0
  78. package/extensions/dm-mcp-adapter/sandbox-proxy-template.js +190 -0
  79. package/extensions/dm-mcp-adapter/search-ranking.js +198 -0
  80. package/extensions/dm-mcp-adapter/server-manager.js +1258 -0
  81. package/extensions/dm-mcp-adapter/session-recovery.js +82 -0
  82. package/extensions/dm-mcp-adapter/skills/mcp-scripting/SKILL.md +39 -0
  83. package/extensions/dm-mcp-adapter/state.js +0 -0
  84. package/extensions/dm-mcp-adapter/test-runner.mjs +18 -0
  85. package/extensions/dm-mcp-adapter/tool-approval.js +151 -0
  86. package/extensions/dm-mcp-adapter/tool-metadata.js +247 -0
  87. package/extensions/dm-mcp-adapter/tool-registrar.js +212 -0
  88. package/extensions/dm-mcp-adapter/tool-result-renderer.js +355 -0
  89. package/extensions/dm-mcp-adapter/ts-shape.js +157 -0
  90. package/extensions/dm-mcp-adapter/types.js +264 -0
  91. package/extensions/dm-mcp-adapter/ui-app-bridge-helpers.js +36 -0
  92. package/extensions/dm-mcp-adapter/ui-resource-handler.js +209 -0
  93. package/extensions/dm-mcp-adapter/ui-server.js +815 -0
  94. package/extensions/dm-mcp-adapter/ui-session.js +455 -0
  95. package/extensions/dm-mcp-adapter/ui-stream-types.js +58 -0
  96. package/extensions/dm-mcp-adapter/ui-tool-visibility.js +26 -0
  97. package/extensions/dm-mcp-adapter/unix-socket-transport.js +83 -0
  98. package/extensions/dm-mcp-adapter/utils.js +307 -0
  99. package/extensions/dm-skills-manager/THIRD_PARTY_NOTICES.md +27 -0
  100. package/extensions/dm-skills-manager/extensions/skills-manager/components.js +265 -0
  101. package/extensions/dm-skills-manager/extensions/skills-manager/constants.js +31 -0
  102. package/extensions/dm-skills-manager/extensions/skills-manager/creation-fallback.js +20 -0
  103. package/extensions/dm-skills-manager/extensions/skills-manager/creation.js +145 -0
  104. package/extensions/dm-skills-manager/extensions/skills-manager/dialog.js +738 -0
  105. package/extensions/dm-skills-manager/extensions/skills-manager/dm-ai-compat.js +15 -0
  106. package/extensions/dm-skills-manager/extensions/skills-manager/format.js +125 -0
  107. package/extensions/dm-skills-manager/extensions/skills-manager/glyphs.js +142 -0
  108. package/extensions/dm-skills-manager/extensions/skills-manager/layout.js +93 -0
  109. package/extensions/dm-skills-manager/extensions/skills-manager/paths.js +76 -0
  110. package/extensions/dm-skills-manager/extensions/skills-manager/registry.js +95 -0
  111. package/extensions/dm-skills-manager/extensions/skills-manager/settings.js +93 -0
  112. package/extensions/dm-skills-manager/extensions/skills-manager/startup.js +32 -0
  113. package/extensions/dm-skills-manager/extensions/skills-manager/toggle.js +54 -0
  114. package/extensions/dm-skills-manager/extensions/skills-manager/types.js +14 -0
  115. package/extensions/dm-skills-manager/extensions/skills-manager/ui.js +121 -0
  116. package/extensions/dm-skills-manager/extensions/skills-manager.js +111 -0
  117. package/extensions/dm-skills-manager/package.json +121 -0
  118. package/package.json +13 -1
  119. package/extensions/dm-localllm-provider/detect.test.js +0 -1051
  120. package/extensions/dm-localllm-provider/index.test.js +0 -149
  121. package/extensions/dm-localllm-provider/keychain.test.js +0 -61
@@ -0,0 +1,619 @@
1
+ import { isServerDisabled } from "./types.js";
2
+ import {
3
+ ensureCompatibilityImports,
4
+ getSharedConfigPath,
5
+ getMcpDiscoverySummary,
6
+ getMcpStandardConfigSummary,
7
+ getServerProvenance,
8
+ previewCompatibilityImports,
9
+ previewSharedServerEntry,
10
+ previewStarterSharedConfig,
11
+ writeDirectToolsConfig,
12
+ writeProjectServerDisabledOverride,
13
+ writeSharedServerEntry,
14
+ writeStarterSharedConfig
15
+ } from "./config.js";
16
+ import { markKeepAliveAfterConnect, notifyToolMetadataUpdated, updateMetadataCache, updateStatusBar, getFailureAgeSeconds, getFailureMessage, clearFailure, recordFailure } from "./init.js";
17
+ import { isServerInActiveFailureBackoff } from "./failure-backoff.js";
18
+ import { loadMetadataCache, reconstructPromptMetadata } from "./metadata-cache.js";
19
+ import { buildToolMetadata } from "./tool-metadata.js";
20
+ import { supportsOAuth, authenticate, removeAuth } from "./mcp-auth-flow.js";
21
+ import { getAuthStorageOptions, inspectAuthForUrl } from "./mcp-auth.js";
22
+ import { inspectBearerTokenForUrl, removeBearerToken } from "./mcp-bearer-store.js";
23
+ import { loadOnboardingState, markSetupCompleted as persistSetupCompleted, markSharedConfigHintShown } from "./onboarding-state.js";
24
+ import { openPath, resolveServerUrl, sanitizeTerminalText } from "./utils.js";
25
+ import { isAbortError } from "./runtime-owner.js";
26
+ function terminalHyperlink(label, url) {
27
+ return `\x1B]8;;${sanitizeTerminalText(url)}\x1B\\${sanitizeTerminalText(label)}\x1B]8;;\x1B\\`;
28
+ }
29
+ function canRenderPanel(ctx) {
30
+ return ctx.hasUI && ctx.mode === "tui";
31
+ }
32
+ export async function showStatus(state, ctx) {
33
+ if (!ctx.hasUI)
34
+ return;
35
+ const lines = ["MCP Server Status:", ""];
36
+ if (!state.programmaticConfig) {
37
+ lines.push("Shared MCP config: .mcp.json for this project/team or ~/.config/mcp/mcp.json for all projects.", "DM-owned files hold compatibility imports and adapter-specific overrides.", "");
38
+ }
39
+ for (const name of Object.keys(state.config.mcpServers)) {
40
+ const definition = state.config.mcpServers[name];
41
+ if (isServerDisabled(definition)) {
42
+ lines.push(`⊘ ${name}: disabled (run /mcp enable ${name}, then /reload)`);
43
+ continue;
44
+ }
45
+ const connection = state.manager.getConnection(name);
46
+ const metadata = state.toolMetadata.get(name);
47
+ const toolCount = metadata?.length ?? 0;
48
+ const failedAgo = getFailureAgeSeconds(state, name);
49
+ let status = "not listening (disconnected)";
50
+ let statusIcon = "○";
51
+ let failed = false;
52
+ if (connection?.status === "connected") {
53
+ status = connection.listenState === "active" ? connection.listenCatalogStale ? "connected; listen active; catalog may be stale" : "connected; listen active" : connection.listenState === "dropped" ? "connected; catalog may be stale — will reconcile on next keep-alive or tool use" : connection.listenState === "re-establishing" ? "connected; re-establishing listen" : connection.listenState === "legacy" ? "connected; legacy notification path" : "connected; not listening for catalog updates";
54
+ statusIcon = "✓";
55
+ } else if (connection?.status === "needs-auth") {
56
+ status = "needs auth";
57
+ statusIcon = "⚠";
58
+ } else if (failedAgo !== null) {
59
+ const reason = sanitizeTerminalText(getFailureMessage(state, name) ?? "");
60
+ status = reason ? `failed ${failedAgo}s ago — ${reason}` : `failed ${failedAgo}s ago`;
61
+ statusIcon = "✗";
62
+ failed = true;
63
+ } else if (metadata !== undefined) {
64
+ status = "cached; not listening (disconnected)";
65
+ }
66
+ const toolSuffix = failed ? "" : ` (${toolCount} tools${status.startsWith("cached") ? ", cached" : ""})`;
67
+ lines.push(`${statusIcon} ${name}: ${status}${toolSuffix}`);
68
+ }
69
+ if (state.config.settings?.freezeDirectTools === true) {
70
+ lines.push("", "Direct tools frozen; active registrations may differ from current metadata.");
71
+ }
72
+ if (Object.keys(state.config.mcpServers).length === 0) {
73
+ lines.push("No MCP servers configured");
74
+ lines.push("Run /mcp setup to add a server to .mcp.json or ~/.config/mcp/mcp.json");
75
+ }
76
+ ctx.ui.notify(lines.join(`
77
+ `), "info");
78
+ }
79
+ export async function showPrompts(state, ctx) {
80
+ if (!ctx.hasUI)
81
+ return;
82
+ const allPrompts = [...state.promptMetadata?.values() ?? []].flat();
83
+ const failedPromptServers = [...state.manager.getAllConnections?.() ?? []].filter(([, connection]) => connection.status === "connected" && connection.promptDiscoveryFailed).map(([serverName]) => serverName).sort();
84
+ if (allPrompts.length === 0) {
85
+ const failureNote = failedPromptServers.length > 0 ? ` Prompt discovery failed for: ${failedPromptServers.join(", ")}.` : "";
86
+ ctx.ui.notify(`No MCP prompts available. Prompts are discovered when servers with the \`prompts\` capability connect.${failureNote}`, "info");
87
+ return;
88
+ }
89
+ const lines = ["MCP Prompts:", ""];
90
+ const grouped = new Map;
91
+ for (const prompt of allPrompts) {
92
+ const list = grouped.get(prompt.serverName) ?? [];
93
+ list.push(prompt);
94
+ grouped.set(prompt.serverName, list);
95
+ }
96
+ for (const [serverName, prompts] of [...grouped.entries()].sort(([a], [b]) => a.localeCompare(b))) {
97
+ lines.push(`${serverName}:`);
98
+ for (const prompt of prompts.sort((a, b) => a.commandName.localeCompare(b.commandName))) {
99
+ const args = prompt.arguments.map((argument) => argument.required ? `<${argument.name}>` : `[${argument.name}]`).join(" ");
100
+ lines.push(` /${prompt.commandName}${args ? ` ${args}` : ""}`);
101
+ if (prompt.description)
102
+ lines.push(` ${prompt.description}`);
103
+ }
104
+ lines.push("");
105
+ }
106
+ lines.push(`Total: ${allPrompts.length} prompt${allPrompts.length === 1 ? "" : "s"}`);
107
+ if (failedPromptServers.length > 0) {
108
+ lines.push(`Prompt discovery failed for: ${failedPromptServers.join(", ")}. Cached prompt metadata may be stale.`);
109
+ }
110
+ ctx.ui.notify(lines.join(`
111
+ `), "info");
112
+ }
113
+ export async function showTools(state, ctx) {
114
+ if (!ctx.hasUI)
115
+ return;
116
+ const allTools = [...state.toolMetadata.entries()].filter(([serverName]) => !isServerDisabled(state.config.mcpServers[serverName])).filter(([serverName]) => !isServerInActiveFailureBackoff(state, serverName)).flatMap(([, metadata]) => metadata.map((m) => m.name));
117
+ if (allTools.length === 0) {
118
+ ctx.ui.notify("No MCP tools available", "info");
119
+ return;
120
+ }
121
+ const lines = [
122
+ "MCP Tools:",
123
+ "",
124
+ ...allTools.map((t) => ` ${t}`),
125
+ "",
126
+ `Total: ${allTools.length} tools`
127
+ ];
128
+ ctx.ui.notify(lines.join(`
129
+ `), "info");
130
+ }
131
+ export async function reconnectServer(state, ctx, name) {
132
+ const definition = state.config.mcpServers[name];
133
+ const ui = ctx.hasUI ? ctx.ui : undefined;
134
+ const signal = state.owner?.signal;
135
+ if (!definition) {
136
+ if (ui) {
137
+ ui.notify(`Server "${name}" not found in config`, "error");
138
+ }
139
+ return false;
140
+ }
141
+ if (isServerDisabled(definition)) {
142
+ if (ui)
143
+ ui.notify(`MCP: ${name} is disabled. Run /mcp enable ${name}, then /reload.`, "warning");
144
+ return false;
145
+ }
146
+ try {
147
+ await state.manager.close(name);
148
+ state.owner?.throwIfInactive();
149
+ const connection = signal ? await state.manager.connect(name, definition, signal) : await state.manager.connect(name, definition);
150
+ state.owner?.throwIfInactive();
151
+ if (connection.status === "needs-auth") {
152
+ if (ui) {
153
+ ui.notify(`MCP: ${name} requires OAuth. Run /mcp-auth ${name} first.`, "warning");
154
+ }
155
+ updateStatusBar(state);
156
+ return false;
157
+ }
158
+ const prefix = state.config.settings?.toolPrefix ?? "server";
159
+ const { metadata, failedTools } = buildToolMetadata(connection.tools, connection.resources, definition, name, prefix, state.config.mcpServers, state.toolMetadata);
160
+ state.toolMetadata.set(name, metadata);
161
+ if (!connection.promptDiscoveryFailed) {
162
+ state.promptMetadata?.set(name, reconstructPromptMetadata(name, connection.prompts ?? [], prefix, definition));
163
+ state.promptMetadataLive?.add(name);
164
+ }
165
+ if (connection.instructions) {
166
+ state.serverInstructions.set(name, connection.instructions);
167
+ } else {
168
+ state.serverInstructions.delete(name);
169
+ }
170
+ updateMetadataCache(state, name);
171
+ const restored = clearFailure(state, name, "command-reconnect");
172
+ if (!restored)
173
+ notifyToolMetadataUpdated(state, name, "command-reconnect");
174
+ markKeepAliveAfterConnect(state, name);
175
+ if (ui) {
176
+ ui.notify(`MCP: Reconnected to ${name} (${connection.tools.length} tools, ${connection.resources.length} resources)`, "info");
177
+ if (failedTools.length > 0) {
178
+ ui.notify(`MCP: ${name} - ${failedTools.length} tools skipped`, "warning");
179
+ }
180
+ }
181
+ updateStatusBar(state);
182
+ return true;
183
+ } catch (error) {
184
+ const message = error instanceof Error ? error.message : String(error);
185
+ if (isAbortError(error, signal))
186
+ throw error;
187
+ recordFailure(state, name, message);
188
+ if (ui) {
189
+ ui.notify(`MCP: Failed to reconnect to ${name}: ${sanitizeTerminalText(message)}`, "error");
190
+ }
191
+ updateStatusBar(state);
192
+ return false;
193
+ }
194
+ }
195
+ export async function reconnectServers(state, ctx, targetServer) {
196
+ if (targetServer && !state.config.mcpServers[targetServer]) {
197
+ if (ctx.hasUI) {
198
+ ctx.ui.notify(`Server "${targetServer}" not found in config`, "error");
199
+ }
200
+ return;
201
+ }
202
+ const names = targetServer ? [targetServer] : Object.keys(state.config.mcpServers);
203
+ for (const name of names) {
204
+ await reconnectServer(state, ctx, name);
205
+ }
206
+ updateStatusBar(state);
207
+ }
208
+ export async function authenticateServer(serverName, config, ctx, signal, runtime) {
209
+ const ui = ctx.hasUI ? ctx.ui : undefined;
210
+ const cwd = ctx.cwd;
211
+ signal ??= ctx.signal;
212
+ if (!ui)
213
+ return { ok: false, message: "OAuth authentication requires an interactive session." };
214
+ const definition = config.mcpServers[serverName];
215
+ if (!definition) {
216
+ const message = `Server "${serverName}" not found in config`;
217
+ ui.notify(message, "error");
218
+ return { ok: false, message };
219
+ }
220
+ if (isServerDisabled(definition)) {
221
+ const message = `Server "${serverName}" is disabled. Run /mcp enable ${serverName}, then /reload.`;
222
+ ui.notify(message, "warning");
223
+ return { ok: false, message };
224
+ }
225
+ if (!supportsOAuth(definition)) {
226
+ const message = `Server "${serverName}" does not use OAuth authentication. Set "auth": "oauth" or omit auth for auto-detection.`;
227
+ ui.notify(`Server "${serverName}" does not use OAuth authentication.
228
+ ` + `Set "auth": "oauth" or omit auth for auto-detection.`, "error");
229
+ return { ok: false, message };
230
+ }
231
+ try {
232
+ const serverUrl = resolveServerUrl(definition);
233
+ if (!serverUrl) {
234
+ const message = `Server "${serverName}" has no URL configured (OAuth requires HTTP transport)`;
235
+ ui.notify(message, "error");
236
+ return { ok: false, message };
237
+ }
238
+ ui.setStatus("mcp-auth", `Authenticating ${serverName}...`);
239
+ const authStorageOptions = getAuthStorageOptions(config.settings?.oauthDir, cwd);
240
+ const status = await authenticate(serverName, serverUrl, definition, {
241
+ ...authStorageOptions.baseDir ? { authStorageOptions } : {},
242
+ onAuthorizationUrl: () => {},
243
+ onAuthorizationInput: async (authorizationUrl, inputSignal) => {
244
+ if (inputSignal.aborted)
245
+ return;
246
+ return ui.input(`Complete ${serverName} OAuth
247
+
248
+ ` + `${terminalHyperlink("Open authorization page", authorizationUrl)}
249
+ ${authorizationUrl}
250
+
251
+ ` + "Approve access, then paste the full callback URL from the browser address bar below.", undefined, { signal: inputSignal });
252
+ },
253
+ ...signal ? { signal } : {},
254
+ ...runtime ? { runtime } : {}
255
+ });
256
+ if (signal?.aborted)
257
+ signal.throwIfAborted();
258
+ if (status === "authenticated") {
259
+ const message = `OAuth authentication successful for "${serverName}".`;
260
+ ui.notify(message, "info");
261
+ return { ok: true, message };
262
+ }
263
+ const message = `OAuth authentication failed for "${serverName}".`;
264
+ ui.notify(message, "error");
265
+ return { ok: false, message };
266
+ } catch (error) {
267
+ if (signal?.aborted)
268
+ throw error;
269
+ const message = error instanceof Error ? error.message : String(error);
270
+ ui.notify(`Failed to authenticate "${serverName}": ${message}`, "error");
271
+ return { ok: false, message };
272
+ } finally {
273
+ if (!signal?.aborted)
274
+ ui.setStatus("mcp-auth", undefined);
275
+ }
276
+ }
277
+ export async function logoutServer(serverName, state, ctx) {
278
+ const definition = state.config.mcpServers[serverName];
279
+ const ui = ctx.hasUI ? ctx.ui : undefined;
280
+ if (!definition) {
281
+ const message = `Server "${serverName}" not found in config`;
282
+ if (ui)
283
+ ui.notify(message, "error");
284
+ return { ok: false, message };
285
+ }
286
+ const signal = state.owner?.signal;
287
+ try {
288
+ await removeAuth(serverName, { authStorageOptions: state.authStorageOptions, signal, runtime: state.oauthRuntime });
289
+ } catch (error) {
290
+ if (isAbortError(error, signal))
291
+ throw error;
292
+ const message = error instanceof Error ? error.message : String(error);
293
+ if (ui) {
294
+ ui.notify(`Failed to clear OAuth credentials for "${serverName}": ${sanitizeTerminalText(message)}`, "error");
295
+ }
296
+ return { ok: false, message };
297
+ }
298
+ state.owner?.throwIfInactive();
299
+ try {
300
+ await state.manager.close(serverName);
301
+ } catch (error) {
302
+ if (isAbortError(error, signal))
303
+ throw error;
304
+ const message = error instanceof Error ? error.message : String(error);
305
+ if (ui) {
306
+ ui.notify(`OAuth credentials were cleared for "${serverName}", but its connection could not be closed: ${sanitizeTerminalText(message)}`, "error");
307
+ }
308
+ return { ok: false, message };
309
+ }
310
+ state.owner?.throwIfInactive();
311
+ updateStatusBar(state);
312
+ const message = `OAuth credentials cleared for "${serverName}". Run /mcp-auth ${serverName} to authenticate again.`;
313
+ if (ui)
314
+ ui.notify(message, "info");
315
+ return { ok: true, message };
316
+ }
317
+ function validateBearerTokenStoreServer(serverName, state) {
318
+ const safeName = sanitizeTerminalText(serverName);
319
+ const definition = state.config.mcpServers[serverName];
320
+ if (!definition)
321
+ return { ok: false, message: `Server "${safeName}" not found in config`, type: "error" };
322
+ if (isServerDisabled(definition))
323
+ return { ok: false, message: `Server "${safeName}" is disabled. Run /mcp enable ${safeName}, then /reload.`, type: "warning" };
324
+ if (definition.auth !== "bearer" || definition.bearerTokenStore !== true) {
325
+ return { ok: false, message: `Server "${safeName}" is not configured for bearerTokenStore.`, type: "error" };
326
+ }
327
+ let serverUrl;
328
+ try {
329
+ serverUrl = resolveServerUrl(definition);
330
+ } catch {
331
+ return { ok: false, message: `Server "${safeName}" has an invalid or unresolvable URL.`, type: "error" };
332
+ }
333
+ if (!serverUrl)
334
+ return { ok: false, message: `Server "${safeName}" has no URL configured.`, type: "error" };
335
+ return { ok: true, serverUrl };
336
+ }
337
+ export async function manageBearerToken(action, serverName, state, ctx) {
338
+ const ui = ctx.hasUI ? ctx.ui : undefined;
339
+ const safeName = sanitizeTerminalText(serverName);
340
+ const validation = validateBearerTokenStoreServer(serverName, state);
341
+ if (!validation.ok) {
342
+ if (ui)
343
+ ui.notify(validation.message, validation.type);
344
+ return { ok: false, message: validation.message };
345
+ }
346
+ if (action === "set") {
347
+ const message = `Cannot store bearer token here: DM extension UI has no masked secret input primitive. Run \`dm-mcp-adapter token set ${safeName}\` in a terminal; it reads the token from stdin only.`;
348
+ if (ui)
349
+ ui.notify(message, "error");
350
+ return { ok: false, message };
351
+ }
352
+ if (action === "status") {
353
+ const status = inspectBearerTokenForUrl(serverName, validation.serverUrl);
354
+ const message = status.status === "present" ? `Bearer token is stored for "${safeName}".` : status.status === "url-mismatch" ? `Bearer token is stored for "${safeName}", but its URL does not match the current server URL.` : status.status === "unavailable" ? status.message : `No bearer token is stored for "${safeName}".`;
355
+ if (ui)
356
+ ui.notify(message, status.status === "unavailable" ? "error" : "info");
357
+ return { ok: status.status !== "unavailable", message };
358
+ }
359
+ try {
360
+ removeBearerToken(serverName);
361
+ } catch (error) {
362
+ const message = error instanceof Error ? error.message : String(error);
363
+ if (ui)
364
+ ui.notify(`Failed to remove bearer token for "${safeName}": ${sanitizeTerminalText(message)}`, "error");
365
+ return { ok: false, message };
366
+ }
367
+ const message = `Bearer token removed for "${safeName}".`;
368
+ if (ui)
369
+ ui.notify(message, "info");
370
+ return { ok: true, message };
371
+ }
372
+ function buildSharedConfigNoticeLines(configOverridePath, cwd) {
373
+ const discovery = getMcpStandardConfigSummary(configOverridePath, cwd);
374
+ const onboardingState = loadOnboardingState();
375
+ const sharedSources = discovery.sources.filter((source) => (source.id === "shared-project" || source.id === "shared-global") && source.serverCount > 0);
376
+ if (sharedSources.length === 0 || onboardingState.sharedConfigHintShown) {
377
+ return { lines: [], fingerprint: null };
378
+ }
379
+ const sourceList = sharedSources.map((source) => source.path).join(", ");
380
+ return {
381
+ lines: [
382
+ `Using standard MCP config from ${sourceList}.`,
383
+ "Use .mcp.json for project/team config or ~/.config/mcp/mcp.json for all projects. DM only writes compatibility imports and adapter-specific overrides into DM-owned files when needed."
384
+ ],
385
+ fingerprint: discovery.fingerprint
386
+ };
387
+ }
388
+ export async function openMcpSetup(state, pi, ctx, configOverridePath, mode = "setup", options = {}) {
389
+ if (!ctx.hasUI)
390
+ return { configChanged: false };
391
+ if (!canRenderPanel(ctx)) {
392
+ ctx.ui.notify(`The interactive MCP setup panel is only available in the terminal UI (current mode: ${ctx.mode}). Edit .mcp.json directly, or run /mcp status to review servers.`, "info");
393
+ return { configChanged: false };
394
+ }
395
+ if (state.programmaticConfig) {
396
+ ctx.ui.notify("MCP setup is unavailable when config is supplied by createMcpAdapter().", "info");
397
+ return { configChanged: false };
398
+ }
399
+ const discovery = getMcpDiscoverySummary(configOverridePath, ctx.cwd, options);
400
+ const onboardingState = loadOnboardingState();
401
+ const { createMcpSetupPanel } = await import("./mcp-setup-panel.js");
402
+ let configChanged = false;
403
+ const callbacks = {
404
+ previewImports: (imports) => previewCompatibilityImports(imports, configOverridePath),
405
+ previewStarterConfig: (target) => previewStarterSharedConfig(target, ctx.cwd),
406
+ previewRepoPrompt: (target) => {
407
+ const repoPrompt = getMcpDiscoverySummary(configOverridePath, ctx.cwd, options).repoPrompt;
408
+ if (!repoPrompt.entry || !repoPrompt.targetPath || !repoPrompt.serverName)
409
+ return null;
410
+ return previewSharedServerEntry(getSharedConfigPath(target, ctx.cwd), repoPrompt.serverName, repoPrompt.entry);
411
+ },
412
+ previewKnownServer: (preset, target) => previewSharedServerEntry(getSharedConfigPath(target, ctx.cwd), preset.id, preset.entry),
413
+ adoptImports: async (imports) => {
414
+ const result = ensureCompatibilityImports(imports, configOverridePath);
415
+ if (result.added.length > 0)
416
+ configChanged = true;
417
+ return result;
418
+ },
419
+ scaffoldConfig: async (target) => {
420
+ const path = writeStarterSharedConfig(target, ctx.cwd);
421
+ configChanged = true;
422
+ return { path };
423
+ },
424
+ addRepoPrompt: async (target) => {
425
+ const repoPrompt = getMcpDiscoverySummary(configOverridePath, ctx.cwd, options).repoPrompt;
426
+ if (!repoPrompt.entry || !repoPrompt.targetPath || !repoPrompt.serverName) {
427
+ throw new Error("RepoPrompt is not available to add from this setup screen.");
428
+ }
429
+ const path = writeSharedServerEntry(getSharedConfigPath(target, ctx.cwd), repoPrompt.serverName, repoPrompt.entry);
430
+ configChanged = true;
431
+ return { path, serverName: repoPrompt.serverName };
432
+ },
433
+ addKnownServer: async (preset, target) => {
434
+ const path = writeSharedServerEntry(getSharedConfigPath(target, ctx.cwd), preset.id, preset.entry);
435
+ configChanged = true;
436
+ return { path, serverName: preset.name };
437
+ },
438
+ openPath: async (targetPath) => {
439
+ await openPath(pi, targetPath);
440
+ },
441
+ markSetupCompleted: () => {
442
+ persistSetupCompleted(discovery.fingerprint);
443
+ }
444
+ };
445
+ return new Promise((resolve) => {
446
+ ctx.ui.custom((tui, _theme, keybindings, done) => {
447
+ return createMcpSetupPanel(discovery, callbacks, { mode, onboardingState, keybindings }, tui, () => {
448
+ done(undefined);
449
+ resolve({ configChanged });
450
+ });
451
+ }, { overlay: true, overlayOptions: { anchor: "center", width: 92 } });
452
+ });
453
+ }
454
+ function buildMcpPanelCallbacks(state, config, ctx, getOverlayHandle) {
455
+ const authStatusFailures = new Map;
456
+ return {
457
+ reconnect: (serverName) => reconnectServer(state, ctx, serverName),
458
+ canAuthenticate: (serverName) => {
459
+ const definition = config.mcpServers[serverName];
460
+ return definition ? !isServerDisabled(definition) && supportsOAuth(definition) : false;
461
+ },
462
+ authenticate: async (serverName) => {
463
+ const overlay = getOverlayHandle?.();
464
+ overlay?.setHidden(true);
465
+ try {
466
+ return await authenticateServer(serverName, config, ctx, state.owner?.signal, state.oauthRuntime);
467
+ } finally {
468
+ overlay?.setHidden(false);
469
+ overlay?.focus();
470
+ }
471
+ },
472
+ getConnectionStatus: (serverName) => {
473
+ authStatusFailures.delete(serverName);
474
+ const definition = config.mcpServers[serverName];
475
+ if (isServerDisabled(definition))
476
+ return "disabled";
477
+ const connection = state.manager.getConnection(serverName);
478
+ let serverUrl;
479
+ try {
480
+ serverUrl = definition ? resolveServerUrl(definition) : undefined;
481
+ } catch {
482
+ return "failed";
483
+ }
484
+ if (definition?.auth === "oauth" && serverUrl && definition.oauth !== false && definition.oauth?.grantType !== "client_credentials") {
485
+ const authStatus = inspectAuthForUrl(serverName, serverUrl, state.authStorageOptions);
486
+ if (authStatus.status === "unavailable") {
487
+ authStatusFailures.set(serverName, authStatus.message);
488
+ return "failed";
489
+ }
490
+ if (authStatus.status === "absent" || !authStatus.entry.tokens) {
491
+ return "needs-auth";
492
+ }
493
+ }
494
+ if (connection?.status === "needs-auth")
495
+ return "needs-auth";
496
+ if (connection?.status === "connected")
497
+ return "connected";
498
+ if (getFailureAgeSeconds(state, serverName) !== null)
499
+ return "failed";
500
+ return "idle";
501
+ },
502
+ getFailureMessage: (serverName) => authStatusFailures.get(serverName) ?? getFailureMessage(state, serverName),
503
+ refreshCacheAfterReconnect: (serverName) => {
504
+ const freshCache = loadMetadataCache();
505
+ return freshCache?.servers?.[serverName] ?? null;
506
+ }
507
+ };
508
+ }
509
+ export async function openMcpPanel(state, pi, ctx, configOverridePath, onDirectToolsConfigChanged) {
510
+ if (state.programmaticConfig) {
511
+ if (ctx.hasUI) {
512
+ ctx.ui.notify("MCP status is shown from the in-memory SDK config; configuration discovery is unavailable.", "info");
513
+ await showStatus(state, ctx);
514
+ }
515
+ return { configChanged: false };
516
+ }
517
+ if (!canRenderPanel(ctx)) {
518
+ await showStatus(state, ctx);
519
+ return { configChanged: false };
520
+ }
521
+ if (Object.keys(state.config.mcpServers).length === 0) {
522
+ return openMcpSetup(state, pi, ctx, configOverridePath, "empty", { includeHostConfigs: false });
523
+ }
524
+ const config = state.config;
525
+ const cache = loadMetadataCache();
526
+ const configPath = pi.getFlag("mcp-config") ?? configOverridePath;
527
+ const provenanceMap = getServerProvenance(configPath, ctx.cwd);
528
+ const { lines: noticeLines, fingerprint } = buildSharedConfigNoticeLines(configPath, ctx.cwd);
529
+ let overlayHandle;
530
+ const callbacks = buildMcpPanelCallbacks(state, config, ctx, () => overlayHandle);
531
+ const { createMcpPanel } = await import("./mcp-panel.js");
532
+ let configChanged = false;
533
+ await new Promise((resolve) => {
534
+ ctx.ui.custom((tui, _theme, keybindings, done) => {
535
+ return createMcpPanel(config, cache, provenanceMap, callbacks, tui, (result) => {
536
+ (async () => {
537
+ if (!result.cancelled && result.disabledChanges.size > 0) {
538
+ for (const [serverName, disabled] of result.disabledChanges) {
539
+ try {
540
+ const override = writeProjectServerDisabledOverride(configPath, ctx.cwd, serverName, disabled);
541
+ if (override.changed) {
542
+ configChanged = true;
543
+ }
544
+ } catch (error) {
545
+ ctx.ui.notify(`Failed to ${disabled ? "disable" : "enable"} server "${serverName}": ${error instanceof Error ? error.message : String(error)}`, "error");
546
+ }
547
+ }
548
+ }
549
+ if (!result.cancelled && result.changes.size > 0) {
550
+ writeDirectToolsConfig(result.changes, provenanceMap, config);
551
+ await onDirectToolsConfigChanged?.(result.changes);
552
+ ctx.ui.notify("Direct tools updated for this session.", "info");
553
+ }
554
+ done(undefined);
555
+ resolve();
556
+ })().catch((error) => {
557
+ const message = error instanceof Error ? error.message : String(error);
558
+ ctx.ui.notify(`Direct tools updated, but live refresh failed: ${message}`, "error");
559
+ configChanged = true;
560
+ done(undefined);
561
+ resolve();
562
+ });
563
+ }, { noticeLines, keybindings });
564
+ }, {
565
+ overlay: true,
566
+ overlayOptions: { anchor: "center", width: 82 },
567
+ onHandle: (handle) => {
568
+ overlayHandle = handle;
569
+ }
570
+ });
571
+ });
572
+ if (noticeLines.length > 0 && fingerprint) {
573
+ markSharedConfigHintShown(fingerprint);
574
+ }
575
+ return { configChanged };
576
+ }
577
+ export async function openMcpAuthPanel(state, pi, ctx, configOverridePath) {
578
+ if (!ctx.hasUI)
579
+ return { configChanged: false };
580
+ if (!canRenderPanel(ctx)) {
581
+ ctx.ui.notify(`The interactive MCP auth panel is only available in the terminal UI (current mode: ${ctx.mode}). Use /mcp-auth <server> to authenticate a specific server.`, "info");
582
+ return { configChanged: false };
583
+ }
584
+ if (state.programmaticConfig) {
585
+ ctx.ui.notify("Use /mcp-auth <server> to authenticate a server from the in-memory SDK config.", "info");
586
+ return { configChanged: false };
587
+ }
588
+ const config = state.config;
589
+ const oauthServers = Object.entries(config.mcpServers).filter(([, definition]) => !isServerDisabled(definition) && supportsOAuth(definition));
590
+ if (oauthServers.length === 0) {
591
+ ctx.ui.notify("No OAuth-capable MCP servers are configured.", "warning");
592
+ return { configChanged: false };
593
+ }
594
+ const cache = loadMetadataCache();
595
+ const configPath = pi.getFlag("mcp-config") ?? configOverridePath;
596
+ const provenanceMap = getServerProvenance(configPath, ctx.cwd);
597
+ let overlayHandle;
598
+ const callbacks = buildMcpPanelCallbacks(state, config, ctx, () => overlayHandle);
599
+ const { createMcpPanel } = await import("./mcp-panel.js");
600
+ await new Promise((resolve) => {
601
+ ctx.ui.custom((tui, _theme, keybindings, done) => {
602
+ return createMcpPanel(config, cache, provenanceMap, callbacks, tui, () => {
603
+ done(undefined);
604
+ resolve();
605
+ }, {
606
+ authOnly: true,
607
+ keybindings,
608
+ noticeLines: ["Select an OAuth MCP server and press Enter or ctrl+a to authenticate."]
609
+ });
610
+ }, {
611
+ overlay: true,
612
+ overlayOptions: { anchor: "center", width: 82 },
613
+ onHandle: (handle) => {
614
+ overlayHandle = handle;
615
+ }
616
+ });
617
+ });
618
+ return { configChanged: false };
619
+ }