praxis-agent 0.7.0 → 0.9.0
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/README.md +8 -0
- package/dist/application/background-agent-manager.d.ts +5 -0
- package/dist/application/background-agent-manager.js +21 -0
- package/dist/application/background-bash-manager.d.ts +14 -0
- package/dist/application/background-bash-manager.js +42 -4
- package/dist/application/background-task-runtime.d.ts +34 -0
- package/dist/application/background-task-runtime.js +59 -0
- package/dist/application/session-service.d.ts +21 -1
- package/dist/application/session-service.js +188 -17
- package/dist/application/session-worktree.d.ts +1 -0
- package/dist/application/session-worktree.js +13 -2
- package/dist/application/subagent-service.d.ts +3 -0
- package/dist/application/subagent-service.js +6 -0
- package/dist/application/workflow-manager.d.ts +70 -2
- package/dist/application/workflow-manager.js +21 -4
- package/dist/cli/agents-dashboard.d.ts +9 -0
- package/dist/cli/agents-dashboard.js +16 -5
- package/dist/cli/interactive.d.ts +41 -3
- package/dist/cli/interactive.js +1281 -183
- package/dist/cli/tui/claude-style.d.ts +31 -3
- package/dist/cli/tui/claude-style.js +171 -54
- package/dist/cli/tui/config-dashboard.d.ts +63 -0
- package/dist/cli/tui/config-dashboard.js +150 -0
- package/dist/cli/tui/config-settings.d.ts +32 -0
- package/dist/cli/tui/config-settings.js +276 -0
- package/dist/cli/tui/custom-themes.d.ts +21 -0
- package/dist/cli/tui/custom-themes.js +284 -0
- package/dist/cli/tui/file-picker.d.ts +3 -1
- package/dist/cli/tui/file-picker.js +8 -2
- package/dist/cli/tui/keybindings.js +0 -1
- package/dist/cli/tui/mcp-panel-controller.d.ts +32 -0
- package/dist/cli/tui/mcp-panel-controller.js +80 -0
- package/dist/cli/tui/mcp-panel-projector.d.ts +79 -0
- package/dist/cli/tui/mcp-panel-projector.js +181 -0
- package/dist/cli/tui/mcp-panel.d.ts +8 -0
- package/dist/cli/tui/mcp-panel.js +99 -0
- package/dist/cli/tui/recently-denied.d.ts +15 -0
- package/dist/cli/tui/recently-denied.js +19 -0
- package/dist/cli/tui/runtime-interactions.d.ts +17 -0
- package/dist/cli/tui/runtime-interactions.js +100 -0
- package/dist/cli/tui/runtime-settings.d.ts +40 -0
- package/dist/cli/tui/runtime-settings.js +84 -0
- package/dist/cli/tui/slash-commands.js +10 -0
- package/dist/cli/tui/task-panel.d.ts +53 -0
- package/dist/cli/tui/task-panel.js +204 -0
- package/dist/cli/tui/terminal-notifications.d.ts +10 -0
- package/dist/cli/tui/terminal-notifications.js +48 -0
- package/dist/cli/tui/terminal-setup.d.ts +24 -0
- package/dist/cli/tui/terminal-setup.js +618 -0
- package/dist/cli/tui/theme-settings.d.ts +8 -0
- package/dist/cli/tui/theme-settings.js +132 -0
- package/dist/cli/tui/theme.d.ts +53 -0
- package/dist/cli/tui/theme.js +265 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +97 -17
- package/dist/compatibility/claude/schema.js +2 -0
- package/dist/core/runtime.d.ts +12 -0
- package/dist/core/runtime.js +24 -0
- package/dist/maintenance/self-update.js +1 -1
- package/dist/mcp/claude-mcp-management.d.ts +6 -1
- package/dist/mcp/claude-mcp-management.js +191 -44
- package/dist/mcp/claude-mcp-oauth.js +2 -0
- package/dist/mcp/claude-mcp-tools.d.ts +31 -3
- package/dist/mcp/claude-mcp-tools.js +116 -34
- package/dist/permissions/claude-permission-resolver.d.ts +3 -0
- package/dist/permissions/claude-permission-resolver.js +42 -20
- package/dist/platform/claude-version.js +2 -1
- package/dist/plugins/claude-plugin-runtime.js +11 -3
- package/dist/tools/claude-interactive-tools.d.ts +7 -0
- package/dist/tools/claude-interactive-tools.js +21 -8
- package/dist/tools/claude-task-tools.d.ts +4 -0
- package/dist/tools/claude-task-tools.js +7 -0
- package/package.json +2 -1
- package/dist/application/background-agent-manager.d.ts.map +0 -1
- package/dist/application/background-agent-manager.js.map +0 -1
- package/dist/application/background-bash-manager.d.ts.map +0 -1
- package/dist/application/background-bash-manager.js.map +0 -1
- package/dist/application/background-task-id.d.ts.map +0 -1
- package/dist/application/background-task-id.js.map +0 -1
- package/dist/application/claude-project-purge.d.ts.map +0 -1
- package/dist/application/claude-project-purge.js.map +0 -1
- package/dist/application/managed-worktree.d.ts.map +0 -1
- package/dist/application/managed-worktree.js.map +0 -1
- package/dist/application/model-compactor.d.ts.map +0 -1
- package/dist/application/model-compactor.js.map +0 -1
- package/dist/application/scheduled-prompt-manager.d.ts.map +0 -1
- package/dist/application/scheduled-prompt-manager.js.map +0 -1
- package/dist/application/session-service.d.ts.map +0 -1
- package/dist/application/session-service.js.map +0 -1
- package/dist/application/session-worktree.d.ts.map +0 -1
- package/dist/application/session-worktree.js.map +0 -1
- package/dist/application/subagent-service.d.ts.map +0 -1
- package/dist/application/subagent-service.js.map +0 -1
- package/dist/application/tool-use-summary.d.ts.map +0 -1
- package/dist/application/tool-use-summary.js.map +0 -1
- package/dist/application/top-level-agent-manager.d.ts.map +0 -1
- package/dist/application/top-level-agent-manager.js.map +0 -1
- package/dist/application/workflow-manager.d.ts.map +0 -1
- package/dist/application/workflow-manager.js.map +0 -1
- package/dist/application/workflow-meta.d.ts.map +0 -1
- package/dist/application/workflow-meta.js.map +0 -1
- package/dist/application/workflow-runtime.d.ts.map +0 -1
- package/dist/application/workflow-runtime.js.map +0 -1
- package/dist/application/workflow-worktree.d.ts.map +0 -1
- package/dist/application/workflow-worktree.js.map +0 -1
- package/dist/cli/agents-dashboard.d.ts.map +0 -1
- package/dist/cli/agents-dashboard.js.map +0 -1
- package/dist/cli/controls.d.ts.map +0 -1
- package/dist/cli/controls.js.map +0 -1
- package/dist/cli/debug.d.ts.map +0 -1
- package/dist/cli/debug.js.map +0 -1
- package/dist/cli/interactive.d.ts.map +0 -1
- package/dist/cli/interactive.js.map +0 -1
- package/dist/cli/protocol.d.ts.map +0 -1
- package/dist/cli/protocol.js.map +0 -1
- package/dist/cli/tui/claude-style.d.ts.map +0 -1
- package/dist/cli/tui/claude-style.js.map +0 -1
- package/dist/cli/tui/clipboard.d.ts.map +0 -1
- package/dist/cli/tui/clipboard.js.map +0 -1
- package/dist/cli/tui/composer-editor.d.ts.map +0 -1
- package/dist/cli/tui/composer-editor.js.map +0 -1
- package/dist/cli/tui/composer-images.d.ts.map +0 -1
- package/dist/cli/tui/composer-images.js.map +0 -1
- package/dist/cli/tui/conversation-export.d.ts.map +0 -1
- package/dist/cli/tui/conversation-export.js.map +0 -1
- package/dist/cli/tui/external-editor.d.ts.map +0 -1
- package/dist/cli/tui/external-editor.js.map +0 -1
- package/dist/cli/tui/file-picker.d.ts.map +0 -1
- package/dist/cli/tui/file-picker.js.map +0 -1
- package/dist/cli/tui/git-diff.d.ts.map +0 -1
- package/dist/cli/tui/git-diff.js.map +0 -1
- package/dist/cli/tui/hook-settings.d.ts.map +0 -1
- package/dist/cli/tui/hook-settings.js.map +0 -1
- package/dist/cli/tui/keybindings.d.ts.map +0 -1
- package/dist/cli/tui/keybindings.js.map +0 -1
- package/dist/cli/tui/memory-files.d.ts.map +0 -1
- package/dist/cli/tui/memory-files.js.map +0 -1
- package/dist/cli/tui/permission-settings.d.ts.map +0 -1
- package/dist/cli/tui/permission-settings.js.map +0 -1
- package/dist/cli/tui/slash-commands.d.ts.map +0 -1
- package/dist/cli/tui/slash-commands.js.map +0 -1
- package/dist/cli/tui/terminal-suspend.d.ts.map +0 -1
- package/dist/cli/tui/terminal-suspend.js.map +0 -1
- package/dist/cli/tui/workspace-directories.d.ts.map +0 -1
- package/dist/cli/tui/workspace-directories.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/compatibility/claude/compaction.d.ts.map +0 -1
- package/dist/compatibility/claude/compaction.js.map +0 -1
- package/dist/compatibility/claude/context.d.ts.map +0 -1
- package/dist/compatibility/claude/context.js.map +0 -1
- package/dist/compatibility/claude/dynamic-context.d.ts.map +0 -1
- package/dist/compatibility/claude/dynamic-context.js.map +0 -1
- package/dist/compatibility/claude/file-history.d.ts.map +0 -1
- package/dist/compatibility/claude/file-history.js.map +0 -1
- package/dist/compatibility/claude/file-resources.d.ts.map +0 -1
- package/dist/compatibility/claude/file-resources.js.map +0 -1
- package/dist/compatibility/claude/fork.d.ts.map +0 -1
- package/dist/compatibility/claude/fork.js.map +0 -1
- package/dist/compatibility/claude/history.d.ts.map +0 -1
- package/dist/compatibility/claude/history.js.map +0 -1
- package/dist/compatibility/claude/ownership.d.ts.map +0 -1
- package/dist/compatibility/claude/ownership.js.map +0 -1
- package/dist/compatibility/claude/paths.d.ts.map +0 -1
- package/dist/compatibility/claude/paths.js.map +0 -1
- package/dist/compatibility/claude/pr-links.d.ts.map +0 -1
- package/dist/compatibility/claude/pr-links.js.map +0 -1
- package/dist/compatibility/claude/projection.d.ts.map +0 -1
- package/dist/compatibility/claude/projection.js.map +0 -1
- package/dist/compatibility/claude/schema.d.ts.map +0 -1
- package/dist/compatibility/claude/schema.js.map +0 -1
- package/dist/compatibility/claude/shared-resources.d.ts.map +0 -1
- package/dist/compatibility/claude/shared-resources.js.map +0 -1
- package/dist/compatibility/claude/sidechain.d.ts.map +0 -1
- package/dist/compatibility/claude/sidechain.js.map +0 -1
- package/dist/compatibility/claude/tool-links.d.ts.map +0 -1
- package/dist/compatibility/claude/tool-links.js.map +0 -1
- package/dist/compatibility/claude/translation.d.ts.map +0 -1
- package/dist/compatibility/claude/translation.js.map +0 -1
- package/dist/compatibility/claude/workflow-replay.d.ts.map +0 -1
- package/dist/compatibility/claude/workflow-replay.js.map +0 -1
- package/dist/compatibility/claude/workflow.d.ts.map +0 -1
- package/dist/compatibility/claude/workflow.js.map +0 -1
- package/dist/core/compaction.d.ts.map +0 -1
- package/dist/core/compaction.js.map +0 -1
- package/dist/core/context-budget.d.ts.map +0 -1
- package/dist/core/context-budget.js.map +0 -1
- package/dist/core/context.d.ts.map +0 -1
- package/dist/core/context.js.map +0 -1
- package/dist/core/runtime.d.ts.map +0 -1
- package/dist/core/runtime.js.map +0 -1
- package/dist/core/usage.d.ts.map +0 -1
- package/dist/core/usage.js.map +0 -1
- package/dist/extensions/claude-extension-tools.d.ts.map +0 -1
- package/dist/extensions/claude-extension-tools.js.map +0 -1
- package/dist/extensions/claude-extensions.d.ts.map +0 -1
- package/dist/extensions/claude-extensions.js.map +0 -1
- package/dist/hooks/claude-hook-tools.d.ts.map +0 -1
- package/dist/hooks/claude-hook-tools.js.map +0 -1
- package/dist/hooks/claude-hooks.d.ts.map +0 -1
- package/dist/hooks/claude-hooks.js.map +0 -1
- package/dist/maintenance/doctor.d.ts.map +0 -1
- package/dist/maintenance/doctor.js.map +0 -1
- package/dist/maintenance/self-update.d.ts.map +0 -1
- package/dist/maintenance/self-update.js.map +0 -1
- package/dist/mcp/claude-mcp-management.d.ts.map +0 -1
- package/dist/mcp/claude-mcp-management.js.map +0 -1
- package/dist/mcp/claude-mcp-oauth.d.ts.map +0 -1
- package/dist/mcp/claude-mcp-oauth.js.map +0 -1
- package/dist/mcp/claude-mcp-tools.d.ts.map +0 -1
- package/dist/mcp/claude-mcp-tools.js.map +0 -1
- package/dist/mcp/praxis-mcp-server.d.ts.map +0 -1
- package/dist/mcp/praxis-mcp-server.js.map +0 -1
- package/dist/permissions/claude-auto-classifier.d.ts.map +0 -1
- package/dist/permissions/claude-auto-classifier.js.map +0 -1
- package/dist/permissions/claude-permission-resolver.d.ts.map +0 -1
- package/dist/permissions/claude-permission-resolver.js.map +0 -1
- package/dist/persistence/claude-job-store.d.ts.map +0 -1
- package/dist/persistence/claude-job-store.js.map +0 -1
- package/dist/persistence/claude-scheduled-task-store.d.ts.map +0 -1
- package/dist/persistence/claude-scheduled-task-store.js.map +0 -1
- package/dist/persistence/claude-sidechain-store.d.ts.map +0 -1
- package/dist/persistence/claude-sidechain-store.js.map +0 -1
- package/dist/persistence/claude-task-store.d.ts.map +0 -1
- package/dist/persistence/claude-task-store.js.map +0 -1
- package/dist/persistence/claude-transcript-store.d.ts.map +0 -1
- package/dist/persistence/claude-transcript-store.js.map +0 -1
- package/dist/persistence/claude-workflow-store.d.ts.map +0 -1
- package/dist/persistence/claude-workflow-store.js.map +0 -1
- package/dist/persistence/in-memory-sidechain-store.d.ts.map +0 -1
- package/dist/persistence/in-memory-sidechain-store.js.map +0 -1
- package/dist/persistence/in-memory-transcript-store.d.ts.map +0 -1
- package/dist/persistence/in-memory-transcript-store.js.map +0 -1
- package/dist/platform/atomic-write.d.ts.map +0 -1
- package/dist/platform/atomic-write.js.map +0 -1
- package/dist/platform/bounded-process-runner.d.ts.map +0 -1
- package/dist/platform/bounded-process-runner.js.map +0 -1
- package/dist/platform/claude-version.d.ts.map +0 -1
- package/dist/platform/claude-version.js.map +0 -1
- package/dist/platform/command-shell.d.ts.map +0 -1
- package/dist/platform/command-shell.js.map +0 -1
- package/dist/platform/exclusive-file-lease.d.ts.map +0 -1
- package/dist/platform/exclusive-file-lease.js.map +0 -1
- package/dist/platform/sensitive-data.d.ts.map +0 -1
- package/dist/platform/sensitive-data.js.map +0 -1
- package/dist/platform/tmux-worktree.d.ts.map +0 -1
- package/dist/platform/tmux-worktree.js.map +0 -1
- package/dist/plugins/claude-plugin-eval-discovery.d.ts.map +0 -1
- package/dist/plugins/claude-plugin-eval-discovery.js.map +0 -1
- package/dist/plugins/claude-plugin-eval-graders.d.ts.map +0 -1
- package/dist/plugins/claude-plugin-eval-graders.js.map +0 -1
- package/dist/plugins/claude-plugin-eval-runner.d.ts.map +0 -1
- package/dist/plugins/claude-plugin-eval-runner.js.map +0 -1
- package/dist/plugins/claude-plugin-eval-schema.d.ts.map +0 -1
- package/dist/plugins/claude-plugin-eval-schema.js.map +0 -1
- package/dist/plugins/claude-plugin-eval.d.ts.map +0 -1
- package/dist/plugins/claude-plugin-eval.js.map +0 -1
- package/dist/plugins/claude-plugin-maintenance.d.ts.map +0 -1
- package/dist/plugins/claude-plugin-maintenance.js.map +0 -1
- package/dist/plugins/claude-plugin-marketplace.d.ts.map +0 -1
- package/dist/plugins/claude-plugin-marketplace.js.map +0 -1
- package/dist/plugins/claude-plugin-mcpb.d.ts.map +0 -1
- package/dist/plugins/claude-plugin-mcpb.js.map +0 -1
- package/dist/plugins/claude-plugin-runtime.d.ts.map +0 -1
- package/dist/plugins/claude-plugin-runtime.js.map +0 -1
- package/dist/providers/anthropic-compatible.d.ts.map +0 -1
- package/dist/providers/anthropic-compatible.js.map +0 -1
- package/dist/providers/environment.d.ts.map +0 -1
- package/dist/providers/environment.js.map +0 -1
- package/dist/providers/fallback-provider.d.ts.map +0 -1
- package/dist/providers/fallback-provider.js.map +0 -1
- package/dist/providers/openai-compatible.d.ts.map +0 -1
- package/dist/providers/openai-compatible.js.map +0 -1
- package/dist/tools/claude-interactive-tools.d.ts.map +0 -1
- package/dist/tools/claude-interactive-tools.js.map +0 -1
- package/dist/tools/claude-lsp-formatters.d.ts.map +0 -1
- package/dist/tools/claude-lsp-formatters.js.map +0 -1
- package/dist/tools/claude-lsp-tool.d.ts.map +0 -1
- package/dist/tools/claude-lsp-tool.js.map +0 -1
- package/dist/tools/claude-scheduled-tools.d.ts.map +0 -1
- package/dist/tools/claude-scheduled-tools.js.map +0 -1
- package/dist/tools/claude-task-tools.d.ts.map +0 -1
- package/dist/tools/claude-task-tools.js.map +0 -1
- package/dist/tools/claude-user-message.d.ts.map +0 -1
- package/dist/tools/claude-user-message.js.map +0 -1
- package/dist/tools/claude-workflow-tools.d.ts.map +0 -1
- package/dist/tools/claude-workflow-tools.js.map +0 -1
- package/dist/tools/claude-worktree-tools.d.ts.map +0 -1
- package/dist/tools/claude-worktree-tools.js.map +0 -1
- package/dist/tools/filtered-tool-registry.d.ts.map +0 -1
- package/dist/tools/filtered-tool-registry.js.map +0 -1
- package/dist/tools/glob.d.ts.map +0 -1
- package/dist/tools/glob.js.map +0 -1
- package/dist/tools/local-tools.d.ts.map +0 -1
- package/dist/tools/local-tools.js.map +0 -1
- package/dist/tools/notebook.d.ts.map +0 -1
- package/dist/tools/notebook.js.map +0 -1
- package/dist/tools/pdf.d.ts.map +0 -1
- package/dist/tools/pdf.js.map +0 -1
- package/dist/tools/web.d.ts.map +0 -1
- package/dist/tools/web.js.map +0 -1
|
@@ -1,31 +1,92 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { constants } from 'node:fs';
|
|
2
|
+
import { mkdir, open, realpath } from 'node:fs/promises';
|
|
2
3
|
import { homedir } from 'node:os';
|
|
3
|
-
import { join, resolve } from 'node:path';
|
|
4
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { setTimeout } from 'node:timers/promises';
|
|
4
6
|
import { writeFileAtomically } from '../platform/atomic-write.js';
|
|
7
|
+
import { ExclusiveFileLease, } from '../platform/exclusive-file-lease.js';
|
|
5
8
|
import { loadClaudeSharedResources, resolveClaudeProjectIdentity, } from '../compatibility/claude/shared-resources.js';
|
|
6
9
|
function isRecord(value) {
|
|
7
10
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
8
11
|
}
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
export function filterDisabledMcpResources(resources, disabledNames) {
|
|
13
|
+
if (disabledNames.length === 0)
|
|
14
|
+
return resources;
|
|
15
|
+
const disabled = new Set(disabledNames);
|
|
16
|
+
return resources.map((resource) => {
|
|
17
|
+
if (!isRecord(resource.value) || !isRecord(resource.value.mcpServers)) {
|
|
18
|
+
return resource;
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
...resource,
|
|
22
|
+
value: {
|
|
23
|
+
...resource.value,
|
|
24
|
+
mcpServers: Object.fromEntries(Object.entries(resource.value.mcpServers).filter(([name]) => !disabled.has(name))),
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
async function readJsonSource(path, rejectSymlinks = true) {
|
|
30
|
+
let handle;
|
|
11
31
|
try {
|
|
12
|
-
|
|
32
|
+
handle = await open(path, constants.O_RDONLY | (rejectSymlinks ? constants.O_NOFOLLOW : 0));
|
|
33
|
+
const before = await handle.stat();
|
|
34
|
+
const source = await handle.readFile('utf8');
|
|
35
|
+
const after = await handle.stat();
|
|
36
|
+
if (before.dev !== after.dev ||
|
|
37
|
+
before.ino !== after.ino ||
|
|
38
|
+
before.size !== after.size ||
|
|
39
|
+
before.mtimeMs !== after.mtimeMs) {
|
|
40
|
+
throw new Error(`MCP config changed while reading: ${path}`);
|
|
41
|
+
}
|
|
42
|
+
let value;
|
|
43
|
+
try {
|
|
44
|
+
value = JSON.parse(source);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
throw new Error(`Invalid MCP config JSON: ${path}`, { cause: error });
|
|
48
|
+
}
|
|
49
|
+
if (!isRecord(value))
|
|
50
|
+
throw new Error(`MCP config must be an object: ${path}`);
|
|
51
|
+
return {
|
|
52
|
+
value,
|
|
53
|
+
fingerprint: {
|
|
54
|
+
source,
|
|
55
|
+
device: after.dev,
|
|
56
|
+
inode: after.ino,
|
|
57
|
+
size: after.size,
|
|
58
|
+
modifiedMs: after.mtimeMs,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
13
61
|
}
|
|
14
62
|
catch (error) {
|
|
15
63
|
if (error.code === 'ENOENT')
|
|
16
|
-
return {};
|
|
64
|
+
return { value: {} };
|
|
65
|
+
if (error.code === 'ELOOP') {
|
|
66
|
+
throw new Error(`MCP config must not be a symbolic link: ${path}`, {
|
|
67
|
+
cause: error,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
17
70
|
throw error;
|
|
18
71
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
value = JSON.parse(source);
|
|
72
|
+
finally {
|
|
73
|
+
await handle?.close();
|
|
22
74
|
}
|
|
23
|
-
|
|
24
|
-
|
|
75
|
+
}
|
|
76
|
+
async function sourceUnchanged(path, fingerprint) {
|
|
77
|
+
const current = await readJsonSource(path, true);
|
|
78
|
+
if (!fingerprint || !current.fingerprint) {
|
|
79
|
+
return fingerprint === current.fingerprint;
|
|
25
80
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
81
|
+
return (current.fingerprint.source === fingerprint.source &&
|
|
82
|
+
current.fingerprint.device === fingerprint.device &&
|
|
83
|
+
current.fingerprint.inode === fingerprint.inode &&
|
|
84
|
+
current.fingerprint.size === fingerprint.size &&
|
|
85
|
+
current.fingerprint.modifiedMs === fingerprint.modifiedMs);
|
|
86
|
+
}
|
|
87
|
+
async function canonicalTarget(path) {
|
|
88
|
+
await mkdir(dirname(path), { recursive: true });
|
|
89
|
+
return join(await realpath(dirname(path)), basename(path));
|
|
29
90
|
}
|
|
30
91
|
function serverMap(value, path) {
|
|
31
92
|
const servers = value.mcpServers;
|
|
@@ -58,19 +119,66 @@ function setScopedValue(root, target, value) {
|
|
|
58
119
|
projects[target.projectIdentity] = value;
|
|
59
120
|
root.projects = projects;
|
|
60
121
|
}
|
|
61
|
-
async function
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
122
|
+
async function mutateJson(path, mutate, beforeCommit) {
|
|
123
|
+
path = await canonicalTarget(path);
|
|
124
|
+
const lease = new ExclusiveFileLease(`${path}.praxis.lock`);
|
|
125
|
+
let handle = null;
|
|
126
|
+
for (let attempt = 0; attempt < 400; attempt += 1) {
|
|
127
|
+
handle = await lease.tryAcquire();
|
|
128
|
+
if (handle)
|
|
129
|
+
break;
|
|
130
|
+
await setTimeout(5);
|
|
131
|
+
}
|
|
132
|
+
if (!handle)
|
|
133
|
+
throw new Error(`MCP config write lock timed out: ${path}`);
|
|
134
|
+
try {
|
|
135
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
136
|
+
const { value, fingerprint } = await readJsonSource(path, true);
|
|
137
|
+
const next = mutate(value);
|
|
138
|
+
if (!next)
|
|
139
|
+
return;
|
|
140
|
+
await beforeCommit?.(path, attempt);
|
|
141
|
+
const committed = await writeFileAtomically(path, `${JSON.stringify(next, null, 2)}\n`, { beforeCommit: () => sourceUnchanged(path, fingerprint) });
|
|
142
|
+
if (committed)
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
throw new Error(`MCP config changed concurrently: ${path}`);
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
await handle.release();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function projectState(root, identity, path) {
|
|
152
|
+
if (root.projects !== undefined && !isRecord(root.projects)) {
|
|
153
|
+
throw new Error(`MCP config projects must be an object: ${path}`);
|
|
154
|
+
}
|
|
155
|
+
const projects = root.projects;
|
|
156
|
+
const project = projects?.[identity];
|
|
157
|
+
if (project !== undefined && !isRecord(project)) {
|
|
158
|
+
throw new Error(`MCP project state must be an object: ${path}`);
|
|
159
|
+
}
|
|
160
|
+
return project ? { ...project } : {};
|
|
161
|
+
}
|
|
162
|
+
function disabledServerNames(project, path) {
|
|
163
|
+
const disabled = project.disabledMcpServers;
|
|
164
|
+
if (disabled === undefined)
|
|
165
|
+
return [];
|
|
166
|
+
if (!Array.isArray(disabled) ||
|
|
167
|
+
disabled.some((name) => typeof name !== 'string')) {
|
|
168
|
+
throw new Error(`disabledMcpServers must be a string array: ${path}`);
|
|
169
|
+
}
|
|
170
|
+
return [...disabled];
|
|
65
171
|
}
|
|
66
172
|
export class ClaudeMcpManagement {
|
|
67
173
|
configRoot;
|
|
68
174
|
cwd;
|
|
175
|
+
beforeCommit;
|
|
69
176
|
constructor(options) {
|
|
70
177
|
this.configRoot = resolve(options.configRoot ??
|
|
71
178
|
process.env.CLAUDE_CONFIG_DIR ??
|
|
72
179
|
join(homedir(), '.claude'));
|
|
73
180
|
this.cwd = resolve(options.cwd);
|
|
181
|
+
this.beforeCommit = options.beforeCommit;
|
|
74
182
|
}
|
|
75
183
|
async list(scope) {
|
|
76
184
|
const resources = await loadClaudeSharedResources({
|
|
@@ -104,13 +212,16 @@ export class ClaudeMcpManagement {
|
|
|
104
212
|
if (!isRecord(config))
|
|
105
213
|
throw new Error('MCP server config must be an object');
|
|
106
214
|
const target = await this.target(scope);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
215
|
+
await mutateJson(target.path, (root) => {
|
|
216
|
+
const scoped = { ...scopedValue(root, target) };
|
|
217
|
+
const servers = { ...serverMap(scoped, target.path), [name]: config };
|
|
218
|
+
setServerMap(scoped, servers);
|
|
219
|
+
if (!target.projectIdentity)
|
|
220
|
+
return scoped;
|
|
221
|
+
const next = { ...root };
|
|
222
|
+
setScopedValue(next, target, scoped);
|
|
223
|
+
return next;
|
|
224
|
+
}, this.beforeCommit);
|
|
114
225
|
return { name, scope, path: target.path, config };
|
|
115
226
|
}
|
|
116
227
|
async remove(name, scope) {
|
|
@@ -118,31 +229,67 @@ export class ClaudeMcpManagement {
|
|
|
118
229
|
const scopes = scope ? [scope] : ['local', 'project', 'user'];
|
|
119
230
|
for (const candidate of scopes) {
|
|
120
231
|
const target = await this.target(candidate);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
232
|
+
let removed;
|
|
233
|
+
await mutateJson(target.path, (root) => {
|
|
234
|
+
const scoped = { ...scopedValue(root, target) };
|
|
235
|
+
const servers = { ...serverMap(scoped, target.path) };
|
|
236
|
+
removed = servers[name];
|
|
237
|
+
if (!removed)
|
|
238
|
+
return undefined;
|
|
239
|
+
delete servers[name];
|
|
240
|
+
setServerMap(scoped, servers);
|
|
241
|
+
if (!target.projectIdentity)
|
|
242
|
+
return scoped;
|
|
243
|
+
const next = { ...root };
|
|
244
|
+
setScopedValue(next, target, scoped);
|
|
245
|
+
return next;
|
|
246
|
+
}, this.beforeCommit);
|
|
247
|
+
if (removed)
|
|
248
|
+
return { name, scope: candidate, path: target.path, config: removed };
|
|
132
249
|
}
|
|
133
250
|
throw new Error(`MCP server not found: ${name}`);
|
|
134
251
|
}
|
|
135
252
|
async resetProjectChoices() {
|
|
136
253
|
const path = join(this.configRoot, '.claude.json');
|
|
137
|
-
const root = await readJson(path);
|
|
138
254
|
const identity = await resolveClaudeProjectIdentity({ cwd: this.cwd });
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
255
|
+
await mutateJson(path, (root) => {
|
|
256
|
+
const projects = isRecord(root.projects) ? { ...root.projects } : {};
|
|
257
|
+
const project = isRecord(projects[identity]) ? projects[identity] : {};
|
|
258
|
+
projects[identity] = {
|
|
259
|
+
...project,
|
|
260
|
+
enabledMcpjsonServers: [],
|
|
261
|
+
disabledMcpjsonServers: [],
|
|
262
|
+
};
|
|
263
|
+
return { ...root, projects };
|
|
264
|
+
}, this.beforeCommit);
|
|
265
|
+
}
|
|
266
|
+
async disabled() {
|
|
267
|
+
const path = join(this.configRoot, '.claude.json');
|
|
268
|
+
const identity = await resolveClaudeProjectIdentity({ cwd: this.cwd });
|
|
269
|
+
const root = (await readJsonSource(path, true)).value;
|
|
270
|
+
return disabledServerNames(projectState(root, identity, path), path);
|
|
271
|
+
}
|
|
272
|
+
async setEnabled(name, scope, enabled) {
|
|
273
|
+
this.validateName(name);
|
|
274
|
+
await this.get(name, scope);
|
|
275
|
+
const path = join(this.configRoot, '.claude.json');
|
|
276
|
+
const identity = await resolveClaudeProjectIdentity({ cwd: this.cwd });
|
|
277
|
+
await mutateJson(path, (root) => {
|
|
278
|
+
const project = projectState(root, identity, path);
|
|
279
|
+
const disabled = disabledServerNames(project, path);
|
|
280
|
+
const next = enabled
|
|
281
|
+
? disabled.filter((candidate) => candidate !== name)
|
|
282
|
+
: disabled.includes(name)
|
|
283
|
+
? disabled
|
|
284
|
+
: [...disabled, name];
|
|
285
|
+
if (next.length === disabled.length &&
|
|
286
|
+
next.every((candidate, index) => candidate === disabled[index])) {
|
|
287
|
+
return undefined;
|
|
288
|
+
}
|
|
289
|
+
const projects = isRecord(root.projects) ? { ...root.projects } : {};
|
|
290
|
+
projects[identity] = { ...project, disabledMcpServers: next };
|
|
291
|
+
return { ...root, projects };
|
|
292
|
+
}, this.beforeCommit);
|
|
146
293
|
}
|
|
147
294
|
async target(scope) {
|
|
148
295
|
if (scope === 'project')
|
|
@@ -74,6 +74,7 @@ function stableServerConfig(server) {
|
|
|
74
74
|
}
|
|
75
75
|
export function mcpOAuthRecordKey(server) {
|
|
76
76
|
const digest = createHash('sha256')
|
|
77
|
+
// codeql[js/insufficient-password-hash] deterministic key derivation, not password storage
|
|
77
78
|
.update(stableServerConfig(server))
|
|
78
79
|
.digest('hex')
|
|
79
80
|
.slice(0, 16);
|
|
@@ -85,6 +86,7 @@ export function mcpOAuthCredentialService(configRoot) {
|
|
|
85
86
|
if (canonical === defaultRoot)
|
|
86
87
|
return CREDENTIAL_SERVICE;
|
|
87
88
|
const digest = createHash('sha256')
|
|
89
|
+
// codeql[js/insufficient-password-hash] keychain service name from config path, not secret hashing
|
|
88
90
|
.update(canonical)
|
|
89
91
|
.digest('hex')
|
|
90
92
|
.slice(0, 8);
|
|
@@ -18,7 +18,25 @@ export interface ClaudeMcpPromptDefinition {
|
|
|
18
18
|
}
|
|
19
19
|
export interface ClaudeMcpServerStatus {
|
|
20
20
|
name: string;
|
|
21
|
-
status: 'connected' | 'failed';
|
|
21
|
+
status: 'connected' | 'failed' | 'needs-authentication';
|
|
22
|
+
statusDetail?: string;
|
|
23
|
+
authDetail?: string;
|
|
24
|
+
capabilities?: readonly ('tools' | 'resources' | 'prompts')[];
|
|
25
|
+
toolCount?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface ClaudeMcpToolInspection {
|
|
28
|
+
name: string;
|
|
29
|
+
fullName: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ClaudeMcpRuntime {
|
|
33
|
+
inspect(): Promise<readonly ClaudeMcpServerStatus[]>;
|
|
34
|
+
reconnect(name: string): Promise<void>;
|
|
35
|
+
authenticate(name: string): Promise<void>;
|
|
36
|
+
reload(): Promise<void>;
|
|
37
|
+
tools(name: string): Promise<readonly ClaudeMcpToolInspection[]>;
|
|
38
|
+
/** Release MCP transports and child processes owned by this runtime. */
|
|
39
|
+
close?(): Promise<void>;
|
|
22
40
|
}
|
|
23
41
|
export interface ClaudeMcpConfigurationStatus {
|
|
24
42
|
name: string;
|
|
@@ -40,6 +58,8 @@ export interface ClaudeMcpToolRegistryOptions {
|
|
|
40
58
|
signal?: AbortSignal;
|
|
41
59
|
eventSink?: RuntimeEventSink;
|
|
42
60
|
onPromptsChanged?: (prompts: readonly ClaudeMcpPromptDefinition[]) => void;
|
|
61
|
+
authenticateServer?: (name: string) => Promise<void>;
|
|
62
|
+
reloadResources?: () => Promise<readonly ClaudeJsonResource[]>;
|
|
43
63
|
onElicitation?: (request: {
|
|
44
64
|
serverName: string;
|
|
45
65
|
message: ElicitRequest['params']['message'];
|
|
@@ -53,9 +73,9 @@ export interface ClaudeMcpToolRegistryOptions {
|
|
|
53
73
|
}>;
|
|
54
74
|
}
|
|
55
75
|
export declare function validateClaudeMcpConfiguration(resources: readonly ClaudeJsonResource[]): ClaudeMcpConfigurationReport;
|
|
56
|
-
export declare class ClaudeMcpToolRegistry implements ToolRegistry {
|
|
76
|
+
export declare class ClaudeMcpToolRegistry implements ToolRegistry, ClaudeMcpRuntime {
|
|
57
77
|
private readonly options;
|
|
58
|
-
private readonly
|
|
78
|
+
private readonly connectedTools;
|
|
59
79
|
private readonly reservedTools;
|
|
60
80
|
private readonly resourceServers;
|
|
61
81
|
private readonly promptServers;
|
|
@@ -63,6 +83,7 @@ export declare class ClaudeMcpToolRegistry implements ToolRegistry {
|
|
|
63
83
|
private readonly clients;
|
|
64
84
|
private readonly serverClients;
|
|
65
85
|
private readonly reconnectableServers;
|
|
86
|
+
private readonly serverCapabilities;
|
|
66
87
|
private readonly reconnectingServers;
|
|
67
88
|
private readonly promptOperations;
|
|
68
89
|
private promptResultDirectoryPromise;
|
|
@@ -73,6 +94,13 @@ export declare class ClaudeMcpToolRegistry implements ToolRegistry {
|
|
|
73
94
|
static connect(options: ClaudeMcpToolRegistryOptions): Promise<ClaudeMcpToolRegistry>;
|
|
74
95
|
definitions(): readonly ModelToolDefinition[];
|
|
75
96
|
serverStatuses(): readonly ClaudeMcpServerStatus[];
|
|
97
|
+
inspect(): Promise<readonly ClaudeMcpServerStatus[]>;
|
|
98
|
+
reconnect(name: string): Promise<void>;
|
|
99
|
+
authenticate(name: string): Promise<void>;
|
|
100
|
+
reload(): Promise<void>;
|
|
101
|
+
tools(name: string): Promise<readonly ClaudeMcpToolInspection[]>;
|
|
102
|
+
private runtimeStatuses;
|
|
103
|
+
private configure;
|
|
76
104
|
prompts(): readonly ClaudeMcpPromptDefinition[];
|
|
77
105
|
permissionPrompt(name: string): (call: ModelToolCall, originalCall?: ModelToolCall) => Promise<PermissionApproval>;
|
|
78
106
|
prepare(call: ModelToolCall, context: ToolExecutionContext): Promise<ModelToolCall>;
|
|
@@ -641,7 +641,7 @@ async function resourceContent(server, result, context, sensitiveValues) {
|
|
|
641
641
|
}
|
|
642
642
|
export class ClaudeMcpToolRegistry {
|
|
643
643
|
options;
|
|
644
|
-
|
|
644
|
+
connectedTools = new Map();
|
|
645
645
|
reservedTools = new Set();
|
|
646
646
|
resourceServers = new Map();
|
|
647
647
|
promptServers = new Map();
|
|
@@ -649,6 +649,7 @@ export class ClaudeMcpToolRegistry {
|
|
|
649
649
|
clients = new Set();
|
|
650
650
|
serverClients = new Map();
|
|
651
651
|
reconnectableServers = new Map();
|
|
652
|
+
serverCapabilities = new Map();
|
|
652
653
|
reconnectingServers = new Map();
|
|
653
654
|
promptOperations = new Set();
|
|
654
655
|
promptResultDirectoryPromise;
|
|
@@ -663,26 +664,7 @@ export class ClaudeMcpToolRegistry {
|
|
|
663
664
|
const ambientSensitiveValues = sensitiveEnvironmentValues(process.env);
|
|
664
665
|
const warn = (message) => options.onWarning?.(redactSensitiveText(message, ambientSensitiveValues));
|
|
665
666
|
try {
|
|
666
|
-
|
|
667
|
-
registry.statuses.set(server.name, {
|
|
668
|
-
name: server.name,
|
|
669
|
-
status: 'failed',
|
|
670
|
-
});
|
|
671
|
-
let config;
|
|
672
|
-
try {
|
|
673
|
-
config = parseServerConfig(server.name, server.value, server.path);
|
|
674
|
-
}
|
|
675
|
-
catch (error) {
|
|
676
|
-
warn(`MCP server ${server.name} unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
677
|
-
continue;
|
|
678
|
-
}
|
|
679
|
-
registry.reconnectableServers.set(server.name, {
|
|
680
|
-
config,
|
|
681
|
-
sensitiveValues: server.sensitiveValues ?? [],
|
|
682
|
-
});
|
|
683
|
-
await registry.connectServer(server.name, config, server.sensitiveValues);
|
|
684
|
-
}
|
|
685
|
-
registry.publishPrompts();
|
|
667
|
+
await registry.configure(options.resources, warn);
|
|
686
668
|
}
|
|
687
669
|
catch (error) {
|
|
688
670
|
await registry.close();
|
|
@@ -693,14 +675,94 @@ export class ClaudeMcpToolRegistry {
|
|
|
693
675
|
definitions() {
|
|
694
676
|
return [
|
|
695
677
|
...this.options.base.definitions(),
|
|
696
|
-
...[...this.
|
|
678
|
+
...[...this.connectedTools.entries()]
|
|
697
679
|
.filter(([name]) => !this.reservedTools.has(name))
|
|
698
680
|
.map(([, tool]) => tool.definition),
|
|
699
681
|
...(this.resourceServers.size > 0 ? MCP_RESOURCE_TOOL_DEFINITIONS : []),
|
|
700
682
|
];
|
|
701
683
|
}
|
|
702
684
|
serverStatuses() {
|
|
703
|
-
return [...this.statuses.values()]
|
|
685
|
+
return [...this.statuses.values()].map(({ name, status }) => ({
|
|
686
|
+
name,
|
|
687
|
+
status,
|
|
688
|
+
}));
|
|
689
|
+
}
|
|
690
|
+
async inspect() {
|
|
691
|
+
return this.runtimeStatuses();
|
|
692
|
+
}
|
|
693
|
+
async reconnect(name) {
|
|
694
|
+
await this.reconnectServer(name);
|
|
695
|
+
}
|
|
696
|
+
async authenticate(name) {
|
|
697
|
+
if (!this.statuses.has(name))
|
|
698
|
+
throw new Error(`Unknown MCP server ${name}`);
|
|
699
|
+
if (!this.options.authenticateServer) {
|
|
700
|
+
throw new Error('MCP authentication is not available in this runtime');
|
|
701
|
+
}
|
|
702
|
+
await this.options.authenticateServer(name);
|
|
703
|
+
await this.reconnectServer(name);
|
|
704
|
+
}
|
|
705
|
+
async reload() {
|
|
706
|
+
if (this.closed)
|
|
707
|
+
throw new Error('MCP registry is closed');
|
|
708
|
+
this.generation += 1;
|
|
709
|
+
const clients = [...this.serverClients.values()];
|
|
710
|
+
this.serverClients.clear();
|
|
711
|
+
await Promise.allSettled(clients.map((client) => client.close()));
|
|
712
|
+
for (const client of clients)
|
|
713
|
+
this.clients.delete(client);
|
|
714
|
+
this.connectedTools.clear();
|
|
715
|
+
this.reservedTools.clear();
|
|
716
|
+
this.resourceServers.clear();
|
|
717
|
+
this.promptServers.clear();
|
|
718
|
+
this.statuses.clear();
|
|
719
|
+
this.reconnectableServers.clear();
|
|
720
|
+
this.serverCapabilities.clear();
|
|
721
|
+
const ambientSensitiveValues = sensitiveEnvironmentValues(process.env);
|
|
722
|
+
const warn = (message) => this.options.onWarning?.(redactSensitiveText(message, ambientSensitiveValues));
|
|
723
|
+
await this.configure(this.options.reloadResources
|
|
724
|
+
? await this.options.reloadResources()
|
|
725
|
+
: this.options.resources, warn);
|
|
726
|
+
}
|
|
727
|
+
async tools(name) {
|
|
728
|
+
if (!this.statuses.has(name))
|
|
729
|
+
throw new Error(`Unknown MCP server ${name}`);
|
|
730
|
+
return [...this.connectedTools.entries()]
|
|
731
|
+
.filter(([, tool]) => tool.serverName === name)
|
|
732
|
+
.map(([fullName, tool]) => ({
|
|
733
|
+
name: tool.toolName,
|
|
734
|
+
fullName,
|
|
735
|
+
...(tool.definition.description
|
|
736
|
+
? { description: tool.definition.description }
|
|
737
|
+
: {}),
|
|
738
|
+
}))
|
|
739
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
740
|
+
}
|
|
741
|
+
runtimeStatuses() {
|
|
742
|
+
return [...this.statuses.values()].map((status) => {
|
|
743
|
+
const capabilities = this.serverCapabilities.get(status.name) ?? [];
|
|
744
|
+
const toolCount = [...this.connectedTools.values()].filter((tool) => tool.serverName === status.name).length;
|
|
745
|
+
return { ...status, capabilities, toolCount };
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
async configure(resources, warn) {
|
|
749
|
+
for (const server of configuredServers(resources, warn)) {
|
|
750
|
+
this.statuses.set(server.name, { name: server.name, status: 'failed' });
|
|
751
|
+
let config;
|
|
752
|
+
try {
|
|
753
|
+
config = parseServerConfig(server.name, server.value, server.path);
|
|
754
|
+
}
|
|
755
|
+
catch (error) {
|
|
756
|
+
warn(`MCP server ${server.name} unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
this.reconnectableServers.set(server.name, {
|
|
760
|
+
config,
|
|
761
|
+
sensitiveValues: server.sensitiveValues ?? [],
|
|
762
|
+
});
|
|
763
|
+
await this.connectServer(server.name, config, server.sensitiveValues);
|
|
764
|
+
}
|
|
765
|
+
this.publishPrompts();
|
|
704
766
|
}
|
|
705
767
|
prompts() {
|
|
706
768
|
const prompts = [];
|
|
@@ -722,13 +784,13 @@ export class ClaudeMcpToolRegistry {
|
|
|
722
784
|
return prompts;
|
|
723
785
|
}
|
|
724
786
|
permissionPrompt(name) {
|
|
725
|
-
if (!this.
|
|
726
|
-
const available = [...this.
|
|
787
|
+
if (!this.connectedTools.has(name)) {
|
|
788
|
+
const available = [...this.connectedTools.keys()].join(', ') || 'none';
|
|
727
789
|
throw new Error(`MCP tool ${name} (from --permission-prompt-tool) not found. Available MCP tools: ${available}`);
|
|
728
790
|
}
|
|
729
791
|
this.reservedTools.add(name);
|
|
730
792
|
return async (call, originalCall = call) => {
|
|
731
|
-
const tool = this.
|
|
793
|
+
const tool = this.connectedTools.get(name);
|
|
732
794
|
if (!tool)
|
|
733
795
|
return invalidPermissionResult();
|
|
734
796
|
let result;
|
|
@@ -758,7 +820,7 @@ export class ClaudeMcpToolRegistry {
|
|
|
758
820
|
};
|
|
759
821
|
}
|
|
760
822
|
async prepare(call, context) {
|
|
761
|
-
return this.
|
|
823
|
+
return this.connectedTools.has(call.name) ||
|
|
762
824
|
MCP_RESOURCE_TOOL_DEFINITIONS.some((definition) => definition.name === call.name)
|
|
763
825
|
? call
|
|
764
826
|
: this.options.base.prepare(call, context);
|
|
@@ -778,7 +840,7 @@ export class ClaudeMcpToolRegistry {
|
|
|
778
840
|
if (call.name === 'ReadMcpResourceTool') {
|
|
779
841
|
return this.readResource(call.input, context);
|
|
780
842
|
}
|
|
781
|
-
const tool = this.
|
|
843
|
+
const tool = this.connectedTools.get(call.name);
|
|
782
844
|
if (!tool)
|
|
783
845
|
return this.options.base.execute(call, context);
|
|
784
846
|
let result;
|
|
@@ -898,6 +960,7 @@ export class ClaudeMcpToolRegistry {
|
|
|
898
960
|
this.clients.delete(client);
|
|
899
961
|
if (this.closed || this.serverClients.get(serverName) !== client)
|
|
900
962
|
return;
|
|
963
|
+
this.serverCapabilities.delete(serverName);
|
|
901
964
|
this.statuses.set(serverName, { name: serverName, status: 'failed' });
|
|
902
965
|
};
|
|
903
966
|
client.setRequestHandler(ElicitRequestSchema, async (request) => {
|
|
@@ -952,7 +1015,7 @@ export class ClaudeMcpToolRegistry {
|
|
|
952
1015
|
if (redactSensitiveText(name, sensitiveValues) !== name) {
|
|
953
1016
|
throw new Error('MCP tool name contains sensitive data');
|
|
954
1017
|
}
|
|
955
|
-
if (this.
|
|
1018
|
+
if (this.connectedTools.has(name) || connectedTools.has(name)) {
|
|
956
1019
|
throw new Error(`Duplicate MCP tool ${name}`);
|
|
957
1020
|
}
|
|
958
1021
|
connectedTools.set(name, {
|
|
@@ -969,8 +1032,13 @@ export class ClaudeMcpToolRegistry {
|
|
|
969
1032
|
}
|
|
970
1033
|
this.assertOpenGeneration(expectedGeneration);
|
|
971
1034
|
this.serverClients.set(serverName, client);
|
|
1035
|
+
this.serverCapabilities.set(serverName, [
|
|
1036
|
+
...(capabilities?.tools ? ['tools'] : []),
|
|
1037
|
+
...(capabilities?.resources ? ['resources'] : []),
|
|
1038
|
+
...(capabilities?.prompts ? ['prompts'] : []),
|
|
1039
|
+
]);
|
|
972
1040
|
for (const [name, tool] of connectedTools)
|
|
973
|
-
this.
|
|
1041
|
+
this.connectedTools.set(name, tool);
|
|
974
1042
|
if (capabilities?.resources) {
|
|
975
1043
|
this.resourceServers.set(serverName, {
|
|
976
1044
|
client,
|
|
@@ -990,7 +1058,11 @@ export class ClaudeMcpToolRegistry {
|
|
|
990
1058
|
});
|
|
991
1059
|
}
|
|
992
1060
|
}
|
|
993
|
-
this.statuses.set(serverName, {
|
|
1061
|
+
this.statuses.set(serverName, {
|
|
1062
|
+
name: serverName,
|
|
1063
|
+
status: 'connected',
|
|
1064
|
+
statusDetail: 'connected',
|
|
1065
|
+
});
|
|
994
1066
|
}
|
|
995
1067
|
catch (error) {
|
|
996
1068
|
await client.close().catch(() => undefined);
|
|
@@ -999,7 +1071,15 @@ export class ClaudeMcpToolRegistry {
|
|
|
999
1071
|
this.closed ||
|
|
1000
1072
|
expectedGeneration !== this.generation)
|
|
1001
1073
|
throw error;
|
|
1002
|
-
|
|
1074
|
+
const detail = redactSensitiveText(error instanceof Error ? error.message : String(error), sensitiveValues);
|
|
1075
|
+
const needsAuthentication = /(?:401|unauthori[sz]ed|not authenticated|oauth|authentication required)/iu.test(detail);
|
|
1076
|
+
this.statuses.set(serverName, {
|
|
1077
|
+
name: serverName,
|
|
1078
|
+
status: needsAuthentication ? 'needs-authentication' : 'failed',
|
|
1079
|
+
statusDetail: detail,
|
|
1080
|
+
...(needsAuthentication ? { authDetail: `failed: ${detail}` } : {}),
|
|
1081
|
+
});
|
|
1082
|
+
this.options.onWarning?.(`MCP server ${serverName} unavailable: ${detail}`);
|
|
1003
1083
|
}
|
|
1004
1084
|
}
|
|
1005
1085
|
async discoverTools(client, serverName, sensitiveValues) {
|
|
@@ -1202,11 +1282,13 @@ export class ClaudeMcpToolRegistry {
|
|
|
1202
1282
|
if (previous)
|
|
1203
1283
|
this.clients.delete(previous);
|
|
1204
1284
|
this.assertOpenGeneration(expectedGeneration);
|
|
1205
|
-
for (const [name, tool] of this.
|
|
1285
|
+
for (const [name, tool] of this.connectedTools) {
|
|
1206
1286
|
if (tool.serverName === serverName)
|
|
1207
|
-
this.
|
|
1287
|
+
this.connectedTools.delete(name);
|
|
1208
1288
|
}
|
|
1209
1289
|
this.resourceServers.delete(serverName);
|
|
1290
|
+
this.promptServers.delete(serverName);
|
|
1291
|
+
this.serverCapabilities.delete(serverName);
|
|
1210
1292
|
await this.connectServer(serverName, reconnectable.config, reconnectable.sensitiveValues, expectedGeneration);
|
|
1211
1293
|
if (this.statuses.get(serverName)?.status !== 'connected') {
|
|
1212
1294
|
throw new Error(`MCP server ${serverName} could not reconnect`);
|
|
@@ -11,8 +11,10 @@ export interface ClaudePermissionResolverOptions {
|
|
|
11
11
|
permissionMode?: ClaudePermissionMode;
|
|
12
12
|
autoClassifier?: ClaudeAutoClassifier;
|
|
13
13
|
autoModeConfig?: ClaudeAutoModeConfig;
|
|
14
|
+
isSessionActionApproved?: (call: ModelToolCall) => boolean;
|
|
14
15
|
}
|
|
15
16
|
export type ClaudePermissionMode = 'acceptEdits' | 'auto' | 'bypassPermissions' | 'manual' | 'dontAsk' | 'plan' | 'default';
|
|
17
|
+
export declare function claudePermissionActionKey(call: ModelToolCall): string;
|
|
16
18
|
export declare function validateClaudePermissionSettings(settings: readonly ClaudeJsonResource[]): void;
|
|
17
19
|
export declare class ClaudePermissionResolver implements PermissionResolver {
|
|
18
20
|
private readonly rules;
|
|
@@ -22,6 +24,7 @@ export declare class ClaudePermissionResolver implements PermissionResolver {
|
|
|
22
24
|
private readonly permissionMode;
|
|
23
25
|
private readonly autoClassifier;
|
|
24
26
|
private readonly autoModeConfig;
|
|
27
|
+
private readonly isSessionActionApproved;
|
|
25
28
|
constructor(options: ClaudePermissionResolverOptions);
|
|
26
29
|
resolve(call: ModelToolCall, context?: PermissionResolutionContext): Promise<PermissionDecision>;
|
|
27
30
|
private askDecision;
|