@duckmind/dm-windows-x64 0.63.6 → 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 (91) hide show
  1. package/dm.exe +0 -0
  2. package/extensions/.dm-extensions.json +174 -11
  3. package/extensions/dm-mcp-adapter/OAUTH.md +3 -0
  4. package/extensions/dm-mcp-adapter/THIRD_PARTY_NOTICES.md +4 -0
  5. package/extensions/dm-mcp-adapter/abort.js +35 -0
  6. package/extensions/dm-mcp-adapter/agent-dir.js +45 -0
  7. package/extensions/dm-mcp-adapter/agent-plugin-loader.js +349 -0
  8. package/extensions/dm-mcp-adapter/app-bridge.bundle.js +5843 -0
  9. package/extensions/dm-mcp-adapter/cli.js +354 -0
  10. package/extensions/dm-mcp-adapter/commands.js +619 -0
  11. package/extensions/dm-mcp-adapter/config.js +1060 -0
  12. package/extensions/dm-mcp-adapter/consent-manager.js +60 -0
  13. package/extensions/dm-mcp-adapter/direct-tools.js +544 -0
  14. package/extensions/dm-mcp-adapter/dist/agent-dir.js +45 -0
  15. package/extensions/dm-mcp-adapter/dist/agent-plugin-loader.js +349 -0
  16. package/extensions/dm-mcp-adapter/dist/config.js +1060 -0
  17. package/extensions/dm-mcp-adapter/dist/mcp-bearer-store.js +311 -0
  18. package/extensions/dm-mcp-adapter/dist/metadata-cache.js +285 -0
  19. package/extensions/dm-mcp-adapter/dist/package-mcp-loader.js +144 -0
  20. package/extensions/dm-mcp-adapter/dist/resource-tools.js +7 -0
  21. package/extensions/dm-mcp-adapter/dist/types.js +247 -0
  22. package/extensions/dm-mcp-adapter/dist/ui-app-bridge-helpers.js +36 -0
  23. package/extensions/dm-mcp-adapter/dist/ui-stream-types.js +58 -0
  24. package/extensions/dm-mcp-adapter/dist/ui-tool-visibility.js +26 -0
  25. package/extensions/dm-mcp-adapter/dist/utils.js +307 -0
  26. package/extensions/dm-mcp-adapter/elicitation-handler.js +306 -0
  27. package/extensions/dm-mcp-adapter/error-signal.js +16 -0
  28. package/extensions/dm-mcp-adapter/errors.js +225 -0
  29. package/extensions/dm-mcp-adapter/failure-backoff.js +19 -0
  30. package/extensions/dm-mcp-adapter/glimpse-ui.js +61 -0
  31. package/extensions/dm-mcp-adapter/host-html-template.js +464 -0
  32. package/extensions/dm-mcp-adapter/index.js +1079 -0
  33. package/extensions/dm-mcp-adapter/init.js +624 -0
  34. package/extensions/dm-mcp-adapter/json-schema-validator.js +53 -0
  35. package/extensions/dm-mcp-adapter/lifecycle.js +385 -0
  36. package/extensions/dm-mcp-adapter/logger.js +115 -0
  37. package/extensions/dm-mcp-adapter/mcp-auth-flow.js +824 -0
  38. package/extensions/dm-mcp-adapter/mcp-auth.js +732 -0
  39. package/extensions/dm-mcp-adapter/mcp-bearer-store.js +311 -0
  40. package/extensions/dm-mcp-adapter/mcp-callback-server.js +388 -0
  41. package/extensions/dm-mcp-adapter/mcp-code.js +291 -0
  42. package/extensions/dm-mcp-adapter/mcp-keyring-helper.cjs +88 -0
  43. package/extensions/dm-mcp-adapter/mcp-oauth-provider.js +490 -0
  44. package/extensions/dm-mcp-adapter/mcp-output-guard.js +386 -0
  45. package/extensions/dm-mcp-adapter/mcp-panel.js +951 -0
  46. package/extensions/dm-mcp-adapter/mcp-probe.js +151 -0
  47. package/extensions/dm-mcp-adapter/mcp-references.js +263 -0
  48. package/extensions/dm-mcp-adapter/mcp-script-worker.mjs +126 -0
  49. package/extensions/dm-mcp-adapter/mcp-setup-panel.js +578 -0
  50. package/extensions/dm-mcp-adapter/mcp-status.js +80 -0
  51. package/extensions/dm-mcp-adapter/mcp-trace.js +215 -0
  52. package/extensions/dm-mcp-adapter/metadata-cache.js +292 -0
  53. package/extensions/dm-mcp-adapter/namespace-tools.js +155 -0
  54. package/extensions/dm-mcp-adapter/npx-resolver.js +465 -0
  55. package/extensions/dm-mcp-adapter/oauth-handler.js +17 -0
  56. package/extensions/dm-mcp-adapter/oauth.js +17 -0
  57. package/extensions/dm-mcp-adapter/onboarding-state.js +62 -0
  58. package/extensions/dm-mcp-adapter/package-mcp-loader.js +144 -0
  59. package/extensions/dm-mcp-adapter/package.json +68 -0
  60. package/extensions/dm-mcp-adapter/panel-keys.js +26 -0
  61. package/extensions/dm-mcp-adapter/prompts.js +258 -0
  62. package/extensions/dm-mcp-adapter/proxy-modes.js +1336 -0
  63. package/extensions/dm-mcp-adapter/request-headers-command.js +293 -0
  64. package/extensions/dm-mcp-adapter/resource-tools.js +7 -0
  65. package/extensions/dm-mcp-adapter/runtime-owner.js +79 -0
  66. package/extensions/dm-mcp-adapter/sampling-handler.js +222 -0
  67. package/extensions/dm-mcp-adapter/sandbox-proxy-template.js +190 -0
  68. package/extensions/dm-mcp-adapter/search-ranking.js +198 -0
  69. package/extensions/dm-mcp-adapter/server-manager.js +1258 -0
  70. package/extensions/dm-mcp-adapter/session-recovery.js +82 -0
  71. package/extensions/dm-mcp-adapter/skills/mcp-scripting/SKILL.md +39 -0
  72. package/extensions/dm-mcp-adapter/state.js +0 -0
  73. package/extensions/dm-mcp-adapter/test-runner.mjs +18 -0
  74. package/extensions/dm-mcp-adapter/tool-approval.js +151 -0
  75. package/extensions/dm-mcp-adapter/tool-metadata.js +247 -0
  76. package/extensions/dm-mcp-adapter/tool-registrar.js +212 -0
  77. package/extensions/dm-mcp-adapter/tool-result-renderer.js +355 -0
  78. package/extensions/dm-mcp-adapter/ts-shape.js +157 -0
  79. package/extensions/dm-mcp-adapter/types.js +264 -0
  80. package/extensions/dm-mcp-adapter/ui-app-bridge-helpers.js +36 -0
  81. package/extensions/dm-mcp-adapter/ui-resource-handler.js +209 -0
  82. package/extensions/dm-mcp-adapter/ui-server.js +815 -0
  83. package/extensions/dm-mcp-adapter/ui-session.js +455 -0
  84. package/extensions/dm-mcp-adapter/ui-stream-types.js +58 -0
  85. package/extensions/dm-mcp-adapter/ui-tool-visibility.js +26 -0
  86. package/extensions/dm-mcp-adapter/unix-socket-transport.js +83 -0
  87. package/extensions/dm-mcp-adapter/utils.js +307 -0
  88. package/package.json +13 -1
  89. package/extensions/dm-localllm-provider/detect.test.js +0 -1051
  90. package/extensions/dm-localllm-provider/index.test.js +0 -149
  91. package/extensions/dm-localllm-provider/keychain.test.js +0 -61
@@ -0,0 +1,385 @@
1
+ import { SdkError, SdkErrorCode } from "@modelcontextprotocol/client";
2
+ import { isServerDisabled } from "./types.js";
3
+ import { isTransientHttpConnectError } from "./server-manager.js";
4
+ import { hasPendingAuth } from "./mcp-auth-flow.js";
5
+ import { logger } from "./logger.js";
6
+ import { formatTerminalError, parallelLimit, sanitizeTerminalText } from "./utils.js";
7
+ import { isTerminatedSession } from "./session-recovery.js";
8
+ const KEEP_ALIVE_RETRY_BASE_MS = 30000;
9
+ const KEEP_ALIVE_RETRY_MAX_MS = 5 * 60000;
10
+ const KEEP_ALIVE_CHECK_CONCURRENCY = 10;
11
+
12
+ export class McpLifecycleManager {
13
+ manager;
14
+ hasPendingAuthForServer;
15
+ keepAliveServers = new Map;
16
+ allServers = new Map;
17
+ serverSettings = new Map;
18
+ globalIdleTimeout = 10 * 60 * 1000;
19
+ healthCheckInterval;
20
+ onReconnect;
21
+ onReconnectFailure;
22
+ onHealthRestored;
23
+ onAuthRequired;
24
+ onIdleShutdown;
25
+ activeHealthCheck;
26
+ activeConvergence;
27
+ shutdownPromise;
28
+ retryStates = new Map;
29
+ pendingMetadataPublications = new Set;
30
+ stopped = false;
31
+ removeHealthAbortListener;
32
+ constructor(manager, hasPendingAuthForServer = hasPendingAuth) {
33
+ this.manager = manager;
34
+ this.hasPendingAuthForServer = hasPendingAuthForServer;
35
+ }
36
+ setReconnectCallback(callback) {
37
+ this.onReconnect = callback;
38
+ }
39
+ setReconnectFailureCallback(callback) {
40
+ this.onReconnectFailure = callback;
41
+ }
42
+ setHealthRestoredCallback(callback) {
43
+ this.onHealthRestored = callback;
44
+ }
45
+ setAuthRequiredCallback(callback) {
46
+ this.onAuthRequired = callback;
47
+ }
48
+ markKeepAlive(name, definition) {
49
+ if (isServerDisabled(definition))
50
+ return;
51
+ this.keepAliveServers.set(name, definition);
52
+ }
53
+ registerServer(name, definition, settings) {
54
+ if (isServerDisabled(definition))
55
+ return;
56
+ this.allServers.set(name, definition);
57
+ if (settings?.idleTimeout !== undefined)
58
+ this.serverSettings.set(name, settings);
59
+ }
60
+ unregisterServer(name) {
61
+ this.allServers.delete(name);
62
+ this.keepAliveServers.delete(name);
63
+ this.serverSettings.delete(name);
64
+ this.retryStates.delete(name);
65
+ this.pendingMetadataPublications.delete(name);
66
+ }
67
+ setGlobalIdleTimeout(minutes) {
68
+ this.globalIdleTimeout = minutes * 60 * 1000;
69
+ }
70
+ setIdleShutdownCallback(callback) {
71
+ this.onIdleShutdown = callback;
72
+ }
73
+ startHealthChecks(signalOrInterval, maybeIntervalMs = 30000) {
74
+ const signal = typeof signalOrInterval === "number" ? undefined : signalOrInterval;
75
+ const intervalMs = typeof signalOrInterval === "number" ? signalOrInterval : maybeIntervalMs;
76
+ this.stopped = false;
77
+ if (signal?.aborted) {
78
+ this.stopped = true;
79
+ return;
80
+ }
81
+ const stop = () => {
82
+ this.stopped = true;
83
+ if (this.healthCheckInterval)
84
+ clearInterval(this.healthCheckInterval);
85
+ this.healthCheckInterval = undefined;
86
+ };
87
+ signal?.addEventListener("abort", stop, { once: true });
88
+ this.removeHealthAbortListener = () => signal?.removeEventListener("abort", stop);
89
+ this.healthCheckInterval = setInterval(() => {
90
+ if (this.stopped || signal?.aborted || this.activeHealthCheck)
91
+ return;
92
+ const check = this.checkConnections(signal).catch((error) => {
93
+ console.error(`MCP: Health check failed: ${formatTerminalError(error)}`);
94
+ }).finally(() => {
95
+ if (this.activeHealthCheck === check)
96
+ this.activeHealthCheck = undefined;
97
+ });
98
+ this.activeHealthCheck = check;
99
+ }, intervalMs);
100
+ this.healthCheckInterval.unref();
101
+ }
102
+ async ensureConverged(signal) {
103
+ if (this.stopped || signal?.aborted)
104
+ return;
105
+ if (this.activeConvergence)
106
+ return this.activeConvergence;
107
+ const check = this.checkKeepAliveConnections(signal);
108
+ this.activeConvergence = check;
109
+ try {
110
+ await check;
111
+ } finally {
112
+ if (this.activeConvergence === check)
113
+ this.activeConvergence = undefined;
114
+ }
115
+ }
116
+ async checkConnections(signal) {
117
+ if (this.stopped || signal?.aborted)
118
+ return;
119
+ await this.ensureConverged(signal);
120
+ if (this.stopped || signal?.aborted)
121
+ return;
122
+ for (const [name] of this.allServers) {
123
+ if (this.keepAliveServers.has(name))
124
+ continue;
125
+ const timeout = this.getIdleTimeout(name);
126
+ if (timeout > 0 && this.manager.isIdle(name, timeout)) {
127
+ await this.manager.close(name);
128
+ if (this.stopped || signal?.aborted)
129
+ return;
130
+ this.onIdleShutdown?.(name);
131
+ }
132
+ }
133
+ }
134
+ async checkKeepAliveConnections(signal) {
135
+ if (this.stopped || signal?.aborted)
136
+ return;
137
+ await parallelLimit([...this.keepAliveServers], KEEP_ALIVE_CHECK_CONCURRENCY, ([name, definition]) => this.checkKeepAliveConnection(name, definition, signal));
138
+ }
139
+ async checkKeepAliveConnection(name, definition, signal, retrySuperseded = true) {
140
+ if (isServerDisabled(definition) || this.stopped || signal?.aborted)
141
+ return;
142
+ if (this.keepAliveServers.get(name) !== definition)
143
+ return;
144
+ const connection = this.manager.getConnection(name);
145
+ if (connection?.status === "needs-auth") {
146
+ this.pendingMetadataPublications.delete(name);
147
+ return;
148
+ }
149
+ if (!this.shouldAttemptConnection(name, connection))
150
+ return;
151
+ if (!connection || connection.status !== "connected") {
152
+ if (this.hasPendingAuthForServer(name)) {
153
+ logger.debug(`Skipping reconnect for ${name} while OAuth authorization is pending`);
154
+ return;
155
+ }
156
+ let freshConnection;
157
+ try {
158
+ freshConnection = await this.manager.connect(name, definition, signal);
159
+ } catch (error) {
160
+ if (this.stopped || signal?.aborted)
161
+ return;
162
+ this.reportConnectionFailure(name, definition, error, "reconnect", this.manager.getConnection(name));
163
+ return;
164
+ }
165
+ if (this.stopped || signal?.aborted)
166
+ return;
167
+ if (freshConnection.status === "needs-auth") {
168
+ await this.notifyAuthRequired(name, definition, freshConnection);
169
+ return;
170
+ }
171
+ if (freshConnection.status !== "connected") {
172
+ this.reportConnectionFailure(name, definition, new Error(`MCP server ${name} did not return a connected session`), "reconnect", freshConnection);
173
+ return;
174
+ }
175
+ logger.debug(`Reconnected to ${name}`);
176
+ await this.publishConnectedMetadata(name, definition, freshConnection);
177
+ return;
178
+ }
179
+ if (this.pendingMetadataPublications.has(name)) {
180
+ await this.publishConnectedMetadata(name, definition, connection);
181
+ return;
182
+ }
183
+ if (!definition.url)
184
+ return;
185
+ const hadSessionId = connection.transport?.sessionId != null;
186
+ let refreshResult;
187
+ try {
188
+ refreshResult = await this.manager.refreshTools(name, connection, signal);
189
+ } catch (error) {
190
+ if (this.stopped || signal?.aborted)
191
+ return;
192
+ const current = this.manager.getConnection(name);
193
+ if (current !== connection || connection.status !== "connected") {
194
+ await this.handleSupersededConnection(name, definition, connection, signal, retrySuperseded);
195
+ return;
196
+ }
197
+ if (!shouldReconnectAfterRefresh(error, hadSessionId)) {
198
+ this.reportConnectionFailure(name, definition, error, "refresh", connection);
199
+ return;
200
+ }
201
+ if (this.hasPendingAuthForServer(name)) {
202
+ logger.debug(`Skipping reconnect for ${name} while OAuth authorization is pending`);
203
+ return;
204
+ }
205
+ let freshConnection;
206
+ try {
207
+ freshConnection = await this.manager.reconnect(name, definition, connection, signal);
208
+ } catch (reconnectError) {
209
+ if (this.stopped || signal?.aborted)
210
+ return;
211
+ this.reportConnectionFailure(name, definition, reconnectError, "reconnect", this.manager.getConnection(name));
212
+ return;
213
+ }
214
+ if (this.stopped || signal?.aborted)
215
+ return;
216
+ if (freshConnection.status === "needs-auth") {
217
+ await this.notifyAuthRequired(name, definition, freshConnection);
218
+ return;
219
+ }
220
+ if (freshConnection.status !== "connected") {
221
+ this.reportConnectionFailure(name, definition, new Error(`MCP server ${name} did not return a connected session`), "reconnect", freshConnection);
222
+ return;
223
+ }
224
+ logger.debug(`Reconnected stale MCP session for ${name}`);
225
+ await this.publishConnectedMetadata(name, definition, freshConnection);
226
+ return;
227
+ }
228
+ if (refreshResult === "superseded") {
229
+ await this.handleSupersededConnection(name, definition, connection, signal, retrySuperseded);
230
+ return;
231
+ }
232
+ if (refreshResult === "refresh-timeout") {
233
+ this.deferRefreshTimeout(name, definition, connection);
234
+ return;
235
+ }
236
+ if (this.keepAliveServers.get(name) !== definition)
237
+ return;
238
+ if (this.retryStates.delete(name)) {
239
+ await this.onHealthRestored?.(name);
240
+ }
241
+ }
242
+ async handleSupersededConnection(name, definition, staleConnection, signal, retrySuperseded) {
243
+ const current = this.manager.getConnection(name);
244
+ if (this.keepAliveServers.get(name) !== definition)
245
+ return;
246
+ if (current === staleConnection && current.status === "connected") {
247
+ if (this.retryStates.delete(name))
248
+ await this.onHealthRestored?.(name);
249
+ return;
250
+ }
251
+ if (current?.status === "connected") {
252
+ await this.publishConnectedMetadata(name, definition, current);
253
+ return;
254
+ }
255
+ if (current?.status === "needs-auth") {
256
+ await this.notifyAuthRequired(name, definition, current);
257
+ return;
258
+ }
259
+ if (retrySuperseded) {
260
+ await this.checkKeepAliveConnection(name, definition, signal, false);
261
+ }
262
+ }
263
+ async publishConnectedMetadata(name, definition, connection) {
264
+ if (this.keepAliveServers.get(name) !== definition) {
265
+ if (this.manager.getConnection(name) === connection)
266
+ await this.manager.close(name);
267
+ return;
268
+ }
269
+ this.pendingMetadataPublications.add(name);
270
+ try {
271
+ await this.onReconnect?.(name);
272
+ this.pendingMetadataPublications.delete(name);
273
+ this.retryStates.delete(name);
274
+ } catch (error) {
275
+ if (this.stopped)
276
+ return;
277
+ this.reportConnectionFailure(name, definition, error, "publish", connection);
278
+ }
279
+ }
280
+ async notifyAuthRequired(name, definition, connection) {
281
+ if (this.keepAliveServers.get(name) !== definition) {
282
+ if (this.manager.getConnection(name) === connection)
283
+ await this.manager.close(name);
284
+ return;
285
+ }
286
+ this.pendingMetadataPublications.delete(name);
287
+ this.retryStates.delete(name);
288
+ try {
289
+ await this.onAuthRequired?.(name);
290
+ } catch (error) {
291
+ const message = error instanceof Error ? error.message : String(error);
292
+ logger.debug(`MCP: auth-required callback failed for ${name}: ${sanitizeTerminalText(message)}`);
293
+ }
294
+ }
295
+ shouldAttemptConnection(name, connection) {
296
+ const retry = this.retryStates.get(name);
297
+ if (!retry)
298
+ return true;
299
+ if (retry.connection !== connection || retry.status !== connection?.status) {
300
+ this.retryStates.delete(name);
301
+ return true;
302
+ }
303
+ return Date.now() >= retry.nextAttemptAt;
304
+ }
305
+ connectionFailureTarget(action, name) {
306
+ if (action === "reconnect")
307
+ return `reconnect to ${name}`;
308
+ if (action === "publish")
309
+ return `publish metadata for ${name}`;
310
+ return `refresh ${name}`;
311
+ }
312
+ reportConnectionFailure(name, definition, error, action, connection) {
313
+ if (!this.recordRetry(name, definition, connection))
314
+ return;
315
+ this.onReconnectFailure?.(name, error);
316
+ if (isTransientHttpConnectError(error))
317
+ return;
318
+ const retry = this.retryStates.get(name);
319
+ if (retry?.warningReported)
320
+ return;
321
+ if (retry)
322
+ retry.warningReported = true;
323
+ const message = error instanceof Error ? error.message : String(error);
324
+ console.error(`MCP: Failed to ${this.connectionFailureTarget(action, name)}: ${sanitizeTerminalText(message)}`);
325
+ }
326
+ deferRefreshTimeout(name, definition, connection) {
327
+ if (!this.recordRetry(name, definition, connection))
328
+ return;
329
+ logger.debug(`MCP: keep-alive tools/list refresh timed out for ${name}; retrying after backoff`);
330
+ }
331
+ recordRetry(name, definition, connection) {
332
+ if (this.keepAliveServers.get(name) !== definition)
333
+ return false;
334
+ const previous = this.retryStates.get(name);
335
+ const attempts = (previous?.attempts ?? 0) + 1;
336
+ const delay = Math.min(KEEP_ALIVE_RETRY_BASE_MS * 2 ** Math.min(attempts - 1, 10), KEEP_ALIVE_RETRY_MAX_MS);
337
+ this.retryStates.set(name, {
338
+ attempts,
339
+ nextAttemptAt: Date.now() + delay,
340
+ connection,
341
+ status: connection?.status,
342
+ warningReported: previous?.warningReported ?? false
343
+ });
344
+ return true;
345
+ }
346
+ getIdleTimeout(name) {
347
+ const perServer = this.serverSettings.get(name)?.idleTimeout;
348
+ if (perServer !== undefined)
349
+ return perServer * 60 * 1000;
350
+ return this.globalIdleTimeout;
351
+ }
352
+ async gracefulShutdown() {
353
+ if (this.shutdownPromise)
354
+ return this.shutdownPromise;
355
+ this.shutdownPromise = this.shutdownOnce();
356
+ return this.shutdownPromise;
357
+ }
358
+ async shutdownOnce() {
359
+ this.stopped = true;
360
+ if (this.healthCheckInterval)
361
+ clearInterval(this.healthCheckInterval);
362
+ this.healthCheckInterval = undefined;
363
+ this.removeHealthAbortListener?.();
364
+ this.removeHealthAbortListener = undefined;
365
+ await this.activeHealthCheck;
366
+ this.activeHealthCheck = undefined;
367
+ await this.activeConvergence;
368
+ this.activeConvergence = undefined;
369
+ this.onReconnect = undefined;
370
+ this.onReconnectFailure = undefined;
371
+ this.onHealthRestored = undefined;
372
+ this.onAuthRequired = undefined;
373
+ this.onIdleShutdown = undefined;
374
+ this.retryStates.clear();
375
+ this.pendingMetadataPublications.clear();
376
+ if (typeof this.manager.closeAll === "function") {
377
+ await this.manager.closeAll();
378
+ }
379
+ }
380
+ }
381
+ function shouldReconnectAfterRefresh(error, hadSessionId) {
382
+ if (isTerminatedSession(error, hadSessionId))
383
+ return true;
384
+ return error instanceof SdkError && (error.code === SdkErrorCode.NotConnected || error.code === SdkErrorCode.ConnectionClosed);
385
+ }
@@ -0,0 +1,115 @@
1
+ const LEVEL_PRIORITY = {
2
+ debug: 0,
3
+ info: 1,
4
+ warn: 2,
5
+ error: 3
6
+ };
7
+ const LEVEL_PREFIX = {
8
+ debug: "[MCP-UI:DEBUG]",
9
+ info: "[MCP-UI]",
10
+ warn: "[MCP-UI:WARN]",
11
+ error: "[MCP-UI:ERROR]"
12
+ };
13
+
14
+ class Logger {
15
+ minLevel = "info";
16
+ handlers = [];
17
+ defaultContext = {};
18
+ setLevel(level) {
19
+ this.minLevel = level;
20
+ }
21
+ setDefaultContext(context) {
22
+ this.defaultContext = context;
23
+ }
24
+ addHandler(handler) {
25
+ this.handlers.push(handler);
26
+ }
27
+ clearHandlers() {
28
+ this.handlers = [];
29
+ }
30
+ shouldLog(level) {
31
+ return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[this.minLevel];
32
+ }
33
+ emit(level, message, context, error) {
34
+ if (!this.shouldLog(level))
35
+ return;
36
+ const entry = {
37
+ level,
38
+ message,
39
+ context: { ...this.defaultContext, ...context },
40
+ ...error !== undefined ? { error } : {},
41
+ timestamp: new Date
42
+ };
43
+ const prefix = LEVEL_PREFIX[level];
44
+ const contextStr = formatContext(entry.context);
45
+ const fullMessage = contextStr ? `${prefix} ${message} ${contextStr}` : `${prefix} ${message}`;
46
+ if (level === "error") {
47
+ console.error(fullMessage, error ?? "");
48
+ } else if (level === "warn") {
49
+ console.warn(fullMessage);
50
+ } else if (level === "debug") {
51
+ console.debug(fullMessage);
52
+ } else {
53
+ console.log(fullMessage);
54
+ }
55
+ for (const handler of this.handlers) {
56
+ try {
57
+ handler(entry);
58
+ } catch {}
59
+ }
60
+ }
61
+ debug(message, context) {
62
+ this.emit("debug", message, context);
63
+ }
64
+ info(message, context) {
65
+ this.emit("info", message, context);
66
+ }
67
+ warn(message, context) {
68
+ this.emit("warn", message, context);
69
+ }
70
+ error(message, error, context) {
71
+ this.emit("error", message, context, error);
72
+ }
73
+ child(context) {
74
+ return new ChildLogger(this, context);
75
+ }
76
+ }
77
+
78
+ class ChildLogger {
79
+ parent;
80
+ context;
81
+ constructor(parent, context) {
82
+ this.parent = parent;
83
+ this.context = context;
84
+ }
85
+ debug(message, context) {
86
+ this.parent.debug(message, { ...this.context, ...context });
87
+ }
88
+ info(message, context) {
89
+ this.parent.info(message, { ...this.context, ...context });
90
+ }
91
+ warn(message, context) {
92
+ this.parent.warn(message, { ...this.context, ...context });
93
+ }
94
+ error(message, error, context) {
95
+ this.parent.error(message, error, { ...this.context, ...context });
96
+ }
97
+ child(context) {
98
+ return new ChildLogger(this.parent, { ...this.context, ...context });
99
+ }
100
+ }
101
+ function formatContext(context) {
102
+ if (!context || Object.keys(context).length === 0)
103
+ return "";
104
+ const parts = [];
105
+ for (const [key, value] of Object.entries(context)) {
106
+ if (value !== undefined && value !== null) {
107
+ parts.push(`${key}=${typeof value === "string" ? value : JSON.stringify(value)}`);
108
+ }
109
+ }
110
+ return parts.length > 0 ? `(${parts.join(", ")})` : "";
111
+ }
112
+ export const logger = new Logger;
113
+ if (process.env.MCP_UI_DEBUG === "1" || process.env.MCP_UI_DEBUG === "true") {
114
+ logger.setLevel("debug");
115
+ }