@pixelbyte-software/pixcode 1.51.2 → 1.51.4
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/CODE_OF_CONDUCT.md +41 -41
- package/CONTRIBUTING.md +155 -155
- package/LICENSE +718 -718
- package/README.de.md +169 -169
- package/README.ja.md +167 -167
- package/README.ko.md +167 -167
- package/README.md +419 -419
- package/README.ru.md +169 -169
- package/README.tr.md +298 -298
- package/README.zh-CN.md +167 -167
- package/SECURITY.md +46 -46
- package/dist/api-automation.html +110 -110
- package/dist/api-docs.html +548 -548
- package/dist/assets/index-B9N-gfOQ.css +32 -0
- package/dist/assets/{index-EN9ngyxf.js → index-HfGHXhD6.js} +175 -175
- package/dist/clear-cache.html +85 -85
- package/dist/convert-icons.md +52 -52
- package/dist/docs.html +308 -308
- package/dist/favicon.svg +8 -8
- package/dist/features.html +133 -133
- package/dist/generate-icons.js +48 -48
- package/dist/humans.txt +15 -15
- package/dist/icons/codex-white.svg +3 -3
- package/dist/icons/codex.svg +3 -3
- package/dist/icons/cursor-white.svg +11 -11
- package/dist/icons/icon-128x128.svg +9 -9
- package/dist/icons/icon-144x144.svg +9 -9
- package/dist/icons/icon-152x152.svg +9 -9
- package/dist/icons/icon-192x192.svg +9 -9
- package/dist/icons/icon-384x384.svg +9 -9
- package/dist/icons/icon-512x512.svg +9 -9
- package/dist/icons/icon-72x72.svg +9 -9
- package/dist/icons/icon-96x96.svg +9 -9
- package/dist/icons/icon-template.svg +9 -9
- package/dist/icons/qwen-logo.svg +14 -14
- package/dist/index.html +59 -59
- package/dist/landing.html +268 -268
- package/dist/llms-full.txt +119 -119
- package/dist/llms.txt +53 -53
- package/dist/logo.svg +12 -12
- package/dist/manifest.json +60 -60
- package/dist/openapi.yaml +1696 -1696
- package/dist/orchestration.html +125 -125
- package/dist/robots.txt +4 -4
- package/dist/site.css +692 -692
- package/dist/sitemap.xml +51 -51
- package/dist/sw.js +132 -132
- package/dist-server/server/cli.js +96 -96
- package/dist-server/server/daemon/manager.js +33 -33
- package/dist-server/server/daemon-manager.js +64 -64
- package/dist-server/server/database/db.js +14 -2
- package/dist-server/server/database/db.js.map +1 -1
- package/dist-server/server/index.js +191 -31
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/middleware/auth.js +16 -5
- package/dist-server/server/middleware/auth.js.map +1 -1
- package/dist-server/server/modules/orchestration/a2a/adapters/json-event.adapter.js +84 -0
- package/dist-server/server/modules/orchestration/a2a/adapters/json-event.adapter.js.map +1 -0
- package/dist-server/server/modules/orchestration/a2a/adapters/json-event.adapter.test.js +43 -0
- package/dist-server/server/modules/orchestration/a2a/adapters/json-event.adapter.test.js.map +1 -0
- package/dist-server/server/modules/orchestration/hermes/hermes.routes.js +55 -1
- package/dist-server/server/modules/orchestration/hermes/hermes.routes.js.map +1 -1
- package/dist-server/server/modules/orchestration/index.js +1 -0
- package/dist-server/server/modules/orchestration/index.js.map +1 -1
- package/dist-server/server/routes/auth.js +12 -5
- package/dist-server/server/routes/auth.js.map +1 -1
- package/dist-server/server/routes/commands.js +25 -25
- package/dist-server/server/routes/git.js +29 -17
- package/dist-server/server/routes/git.js.map +1 -1
- package/dist-server/server/routes/live-view.js +46 -46
- package/dist-server/server/routes/platformization.js +7 -6
- package/dist-server/server/routes/platformization.js.map +1 -1
- package/dist-server/server/services/hermes-gateway.js +310 -0
- package/dist-server/server/services/hermes-gateway.js.map +1 -1
- package/dist-server/server/services/platformization.js +58 -2
- package/dist-server/server/services/platformization.js.map +1 -1
- package/dist-server/server/services/public-api-manifest.js +59 -51
- package/dist-server/server/services/public-api-manifest.js.map +1 -1
- package/package.json +222 -222
- package/scripts/fix-node-pty.js +67 -67
- package/scripts/github/create-v1.38-issues.mjs +351 -351
- package/scripts/github/create-vscode-workbench-issues.mjs +121 -121
- package/scripts/hermes/configure-pixcode-mcp.mjs +165 -163
- package/scripts/hermes/pixcode-mcp-server.mjs +1009 -958
- package/scripts/smoke/changes-panel-layout.mjs +48 -48
- package/scripts/smoke/chat-composer-fixed-layout.mjs +55 -55
- package/scripts/smoke/chat-message-timeline-order.mjs +41 -41
- package/scripts/smoke/chat-realtime-hydration.mjs +44 -44
- package/scripts/smoke/chat-session-provider-pools.mjs +35 -35
- package/scripts/smoke/chat-session-state.mjs +19 -19
- package/scripts/smoke/code-editor-theme.mjs +55 -55
- package/scripts/smoke/code-editor-vscode-engine.mjs +91 -91
- package/scripts/smoke/command-center-agent-writes.mjs +79 -79
- package/scripts/smoke/command-center-non-git.mjs +46 -46
- package/scripts/smoke/context-packet.mjs +43 -43
- package/scripts/smoke/control-room-ux-redesign.mjs +91 -91
- package/scripts/smoke/daemon-entrypoint.mjs +20 -20
- package/scripts/smoke/default-landing-routing.mjs +33 -33
- package/scripts/smoke/desktop-native-notifications.mjs +30 -30
- package/scripts/smoke/desktop-tray-icon.mjs +33 -33
- package/scripts/smoke/discord-release-workflow.mjs +24 -24
- package/scripts/smoke/git-install-update.mjs +255 -255
- package/scripts/smoke/handoff-artifact-protocol.mjs +50 -50
- package/scripts/smoke/hermes-api-install.mjs +56 -56
- package/scripts/smoke/hermes-gateway-persistence.mjs +104 -104
- package/scripts/smoke/hermes-mcp-pixcode-roundtrip.mjs +426 -367
- package/scripts/smoke/hermes-rest-chat-api.mjs +162 -162
- package/scripts/smoke/hermes-rest-chat-live.mjs +45 -45
- package/scripts/smoke/hermes-rest-codex-launch.mjs +209 -209
- package/scripts/smoke/hermes-rest-gateway.mjs +79 -70
- package/scripts/smoke/hermes-rest-live.mjs +42 -42
- package/scripts/smoke/hermes-roundtrip.mjs +167 -167
- package/scripts/smoke/hermes-settings-commands.mjs +349 -346
- package/scripts/smoke/hermes-smoke-launcher-guard.mjs +34 -34
- package/scripts/smoke/live-view-diagnostics.mjs +53 -53
- package/scripts/smoke/live-view-environment.mjs +92 -92
- package/scripts/smoke/live-view-integration.mjs +450 -450
- package/scripts/smoke/mac-desktop-runtime.mjs +37 -37
- package/scripts/smoke/mobile-tunnel-guidance.mjs +29 -29
- package/scripts/smoke/model-registry.mjs +36 -36
- package/scripts/smoke/multi-project-ui.mjs +45 -45
- package/scripts/smoke/multi-worker-slots.mjs +42 -42
- package/scripts/smoke/notification-center.mjs +87 -87
- package/scripts/smoke/notification-inapp-preference.mjs +23 -23
- package/scripts/smoke/notification-taxonomy.mjs +58 -58
- package/scripts/smoke/orchestration-api.mjs +172 -172
- package/scripts/smoke/orchestration-execution-dashboard.mjs +33 -33
- package/scripts/smoke/orchestration-live-run.mjs +176 -176
- package/scripts/smoke/orchestration-mobile-scroll.mjs +29 -29
- package/scripts/smoke/orchestration-model-sync.mjs +30 -30
- package/scripts/smoke/orchestration-permission-fallback.mjs +34 -34
- package/scripts/smoke/orchestration-runtime-guards.mjs +48 -48
- package/scripts/smoke/orchestration-user-facing-output.mjs +25 -25
- package/scripts/smoke/permission-policy.mjs +50 -50
- package/scripts/smoke/pixcode-workbench-1-48.mjs +167 -167
- package/scripts/smoke/provider-models-opencode-live.mjs +66 -66
- package/scripts/smoke/provider-rest-api.mjs +124 -124
- package/scripts/smoke/provider-selection-status.mjs +52 -52
- package/scripts/smoke/run-state-refresh.mjs +52 -52
- package/scripts/smoke/runtime-manager.mjs +99 -99
- package/scripts/smoke/shell-manual-disconnect.mjs +30 -30
- package/scripts/smoke/side-panel-editor-layout.mjs +34 -34
- package/scripts/smoke/static-root-routing.mjs +21 -21
- package/scripts/smoke/strict-handoff-compact.mjs +60 -60
- package/scripts/smoke/taskmaster-config.mjs +24 -24
- package/scripts/smoke/taskmaster-execution-telegram.mjs +3 -3
- package/scripts/smoke/taskmaster-onboarding.mjs +3 -3
- package/scripts/smoke/taskmaster-run-graph.mjs +3 -3
- package/scripts/smoke/telegram-control.mjs +242 -242
- package/scripts/smoke/tunnel-persistence.mjs +56 -56
- package/scripts/smoke/update-issue-progress.mjs +69 -69
- package/scripts/smoke/update-ux.mjs +55 -55
- package/scripts/smoke/v138-completion.mjs +132 -132
- package/scripts/smoke/v138-desktop-release-hardening.mjs +69 -69
- package/scripts/smoke/v138-diagnostics.mjs +63 -63
- package/scripts/smoke/v138-issue-planner.mjs +33 -33
- package/scripts/smoke/v143-remote-control.mjs +76 -76
- package/scripts/smoke/v144-production-loop.mjs +47 -47
- package/scripts/smoke/v145-platformization.mjs +46 -46
- package/scripts/smoke/v146-control-room-ui.mjs +150 -150
- package/scripts/smoke/version-modal-autoshow.mjs +29 -29
- package/scripts/smoke/vscode-workbench-layout.mjs +63 -63
- package/scripts/smoke/vscode-workbench-polish.mjs +461 -436
- package/scripts/smoke/workflow-fallback-replay.mjs +56 -56
- package/scripts/smoke/workflow-templates.mjs +43 -43
- package/scripts/smoke/workflow-trace-timeline.mjs +46 -46
- package/scripts/update-git-install.mjs +293 -293
- package/server/claude-sdk.js +920 -920
- package/server/cli.js +1039 -1039
- package/server/constants/config.js +4 -4
- package/server/cursor-cli.js +344 -344
- package/server/daemon/manager.js +563 -563
- package/server/daemon-manager.js +964 -964
- package/server/database/db.js +908 -895
- package/server/database/json-store.js +197 -197
- package/server/gemini-cli.js +550 -550
- package/server/gemini-response-handler.js +79 -79
- package/server/index.js +201 -30
- package/server/load-env.js +35 -35
- package/server/middleware/auth.js +171 -156
- package/server/modules/orchestration/a2a/adapter-registry.ts +108 -108
- package/server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.ts +63 -63
- package/server/modules/orchestration/a2a/adapters/claude-code.adapter.ts +286 -286
- package/server/modules/orchestration/a2a/adapters/codex.adapter.ts +244 -244
- package/server/modules/orchestration/a2a/adapters/cursor.adapter.ts +249 -249
- package/server/modules/orchestration/a2a/adapters/gemini.adapter.ts +248 -248
- package/server/modules/orchestration/a2a/adapters/json-event.adapter.test.ts +60 -0
- package/server/modules/orchestration/a2a/adapters/json-event.adapter.ts +101 -0
- package/server/modules/orchestration/a2a/adapters/opencode.adapter.ts +248 -248
- package/server/modules/orchestration/a2a/adapters/qwen.adapter.ts +248 -248
- package/server/modules/orchestration/a2a/agent-card.ts +55 -55
- package/server/modules/orchestration/a2a/routes.ts +590 -590
- package/server/modules/orchestration/a2a/task-store.ts +178 -178
- package/server/modules/orchestration/a2a/types.ts +126 -126
- package/server/modules/orchestration/a2a/validator.ts +113 -113
- package/server/modules/orchestration/hermes/hermes.routes.ts +642 -583
- package/server/modules/orchestration/index.ts +101 -100
- package/server/modules/orchestration/preview/port-watcher.ts +112 -112
- package/server/modules/orchestration/preview/preview-proxy.ts +60 -60
- package/server/modules/orchestration/preview/types.ts +19 -19
- package/server/modules/orchestration/security/permission-policy.ts +401 -401
- package/server/modules/orchestration/tasks/orchestration-task-store.ts +41 -41
- package/server/modules/orchestration/tasks/orchestration-task.routes.ts +64 -64
- package/server/modules/orchestration/tasks/orchestration-task.service.ts +209 -209
- package/server/modules/orchestration/tasks/orchestration-task.types.ts +40 -40
- package/server/modules/orchestration/tasks/task-run-graph.ts +155 -155
- package/server/modules/orchestration/workflows/approval-queue.ts +106 -106
- package/server/modules/orchestration/workflows/built-in-workflows.ts +127 -127
- package/server/modules/orchestration/workflows/context-packet.ts +186 -186
- package/server/modules/orchestration/workflows/handoff-artifact.ts +175 -175
- package/server/modules/orchestration/workflows/workflow-fallback-policy.ts +161 -161
- package/server/modules/orchestration/workflows/workflow-replay.ts +254 -254
- package/server/modules/orchestration/workflows/workflow-runner.ts +2070 -2070
- package/server/modules/orchestration/workflows/workflow-store.ts +97 -97
- package/server/modules/orchestration/workflows/workflow-templates.ts +272 -272
- package/server/modules/orchestration/workflows/workflow-trace.ts +424 -424
- package/server/modules/orchestration/workflows/workflow.routes.ts +586 -586
- package/server/modules/orchestration/workflows/workflow.types.ts +111 -111
- package/server/modules/orchestration/workflows/workspace-target.ts +122 -122
- package/server/modules/orchestration/workspace/docker-workspace.ts +136 -136
- package/server/modules/orchestration/workspace/path-safety.ts +55 -55
- package/server/modules/orchestration/workspace/types.ts +52 -52
- package/server/modules/orchestration/workspace/workspace-manager.ts +102 -102
- package/server/modules/orchestration/workspace/worktree-workspace.ts +126 -126
- package/server/modules/providers/index.ts +2 -2
- package/server/modules/providers/list/claude/claude-auth.provider.ts +146 -146
- package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -135
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +306 -306
- package/server/modules/providers/list/claude/claude.provider.ts +15 -15
- package/server/modules/providers/list/codex/codex-auth.provider.ts +117 -117
- package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -135
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +319 -319
- package/server/modules/providers/list/codex/codex.provider.ts +15 -15
- package/server/modules/providers/list/cursor/cursor-auth.provider.ts +147 -147
- package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -108
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +421 -421
- package/server/modules/providers/list/cursor/cursor.provider.ts +15 -15
- package/server/modules/providers/list/gemini/gemini-auth.provider.ts +173 -173
- package/server/modules/providers/list/gemini/gemini-mcp.provider.ts +110 -110
- package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +227 -227
- package/server/modules/providers/list/gemini/gemini.provider.ts +15 -15
- package/server/modules/providers/list/opencode/opencode-auth.provider.ts +131 -131
- package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +126 -126
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +286 -286
- package/server/modules/providers/list/opencode/opencode.provider.ts +29 -29
- package/server/modules/providers/list/qwen/qwen-auth.provider.ts +146 -146
- package/server/modules/providers/list/qwen/qwen-mcp.provider.ts +114 -114
- package/server/modules/providers/list/qwen/qwen-sessions.provider.ts +265 -265
- package/server/modules/providers/list/qwen/qwen.provider.ts +21 -21
- package/server/modules/providers/provider.registry.ts +40 -40
- package/server/modules/providers/provider.routes.ts +944 -944
- package/server/modules/providers/services/mcp.service.ts +86 -86
- package/server/modules/providers/services/provider-auth.service.ts +26 -26
- package/server/modules/providers/services/sessions.service.ts +45 -45
- package/server/modules/providers/shared/base/abstract.provider.ts +20 -20
- package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -151
- package/server/modules/providers/shared/provider-configs.ts +142 -142
- package/server/modules/providers/tests/mcp.test.ts +293 -293
- package/server/openai-codex.js +462 -462
- package/server/opencode-cli.js +491 -491
- package/server/opencode-response-handler.js +111 -111
- package/server/projects.js +3008 -3008
- package/server/qwen-code-cli.js +410 -410
- package/server/qwen-response-handler.js +73 -73
- package/server/routes/agent.js +1435 -1435
- package/server/routes/auth.js +154 -146
- package/server/routes/codex.js +20 -20
- package/server/routes/commands.js +570 -570
- package/server/routes/cursor.js +61 -61
- package/server/routes/diagnostics.js +41 -41
- package/server/routes/gemini.js +25 -25
- package/server/routes/git.js +1650 -1635
- package/server/routes/live-view.js +411 -411
- package/server/routes/mcp-utils.js +13 -13
- package/server/routes/messages.js +62 -62
- package/server/routes/network.js +125 -125
- package/server/routes/platformization.js +198 -197
- package/server/routes/plugins.js +320 -320
- package/server/routes/production-agent-loop.js +90 -90
- package/server/routes/projects.js +917 -917
- package/server/routes/public-api.js +34 -34
- package/server/routes/qwen.js +27 -27
- package/server/routes/remote.js +55 -55
- package/server/routes/settings.js +321 -321
- package/server/routes/telegram.js +140 -140
- package/server/routes/user.js +125 -125
- package/server/routes/webhooks.js +63 -63
- package/server/services/control-room.js +102 -102
- package/server/services/diagnostics.js +165 -165
- package/server/services/external-access.js +375 -375
- package/server/services/hermes-gateway.js +1562 -1247
- package/server/services/hermes-install-jobs.js +729 -729
- package/server/services/install-jobs.js +715 -715
- package/server/services/live-view.js +956 -956
- package/server/services/managed-runtimes.js +493 -493
- package/server/services/model-registry.js +144 -144
- package/server/services/notification-orchestrator.js +365 -365
- package/server/services/notification-taxonomy.js +204 -204
- package/server/services/platformization.js +844 -779
- package/server/services/production-agent-loop.js +248 -248
- package/server/services/provider-cli-versions.js +149 -149
- package/server/services/provider-credentials.js +189 -189
- package/server/services/provider-models.js +396 -396
- package/server/services/public-api-manifest.js +190 -182
- package/server/services/remote-connection.js +127 -127
- package/server/services/runtime-manager.js +323 -323
- package/server/services/startup-update.js +234 -234
- package/server/services/telegram/bot.js +331 -331
- package/server/services/telegram/control-center.js +979 -979
- package/server/services/telegram/telegram-http-client.js +151 -151
- package/server/services/telegram/translations.js +340 -340
- package/server/services/vapid-keys.js +36 -36
- package/server/services/webhooks.js +216 -216
- package/server/sessionManager.js +225 -225
- package/server/shared/interfaces.ts +54 -54
- package/server/shared/types.ts +172 -172
- package/server/shared/utils.ts +193 -193
- package/server/tsconfig.json +36 -36
- package/server/utils/colors.js +21 -21
- package/server/utils/commandParser.js +305 -305
- package/server/utils/frontmatter.js +18 -18
- package/server/utils/gitConfig.js +34 -34
- package/server/utils/plugin-loader.js +457 -457
- package/server/utils/plugin-process-manager.js +185 -185
- package/server/utils/port-access.js +209 -209
- package/server/utils/runtime-paths.js +37 -37
- package/server/utils/url-detection.js +71 -71
- package/server/vite-daemon.js +79 -79
- package/shared/modelConstants.js +161 -161
- package/shared/networkHosts.js +22 -22
- package/dist/assets/index-DMz0zv6T.css +0 -32
|
@@ -1,293 +1,293 @@
|
|
|
1
|
-
import assert from 'node:assert/strict';
|
|
2
|
-
import fs from 'node:fs/promises';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import test from 'node:test';
|
|
6
|
-
|
|
7
|
-
import TOML from '@iarna/toml';
|
|
8
|
-
|
|
9
|
-
import { providerMcpService } from '@/modules/providers/services/mcp.service.js';
|
|
10
|
-
import { AppError } from '@/shared/utils.js';
|
|
11
|
-
|
|
12
|
-
const patchHomeDir = (nextHomeDir: string) => {
|
|
13
|
-
const original = os.homedir;
|
|
14
|
-
(os as any).homedir = () => nextHomeDir;
|
|
15
|
-
return () => {
|
|
16
|
-
(os as any).homedir = original;
|
|
17
|
-
};
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const readJson = async (filePath: string): Promise<Record<string, unknown>> => {
|
|
21
|
-
const content = await fs.readFile(filePath, 'utf8');
|
|
22
|
-
return JSON.parse(content) as Record<string, unknown>;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* This test covers Claude MCP support for all scopes (user/local/project) and all transports (stdio/http/sse),
|
|
27
|
-
* including add, update/list, and remove operations.
|
|
28
|
-
*/
|
|
29
|
-
test('providerMcpService handles claude MCP scopes/transports with file-backed persistence', { concurrency: false }, async () => {
|
|
30
|
-
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-mcp-claude-'));
|
|
31
|
-
const workspacePath = path.join(tempRoot, 'workspace');
|
|
32
|
-
await fs.mkdir(workspacePath, { recursive: true });
|
|
33
|
-
|
|
34
|
-
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
35
|
-
try {
|
|
36
|
-
await providerMcpService.upsertProviderMcpServer('claude', {
|
|
37
|
-
name: 'claude-user-stdio',
|
|
38
|
-
scope: 'user',
|
|
39
|
-
transport: 'stdio',
|
|
40
|
-
command: 'npx',
|
|
41
|
-
args: ['-y', 'my-server'],
|
|
42
|
-
env: { API_KEY: 'secret' },
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
await providerMcpService.upsertProviderMcpServer('claude', {
|
|
46
|
-
name: 'claude-local-http',
|
|
47
|
-
scope: 'local',
|
|
48
|
-
transport: 'http',
|
|
49
|
-
url: 'https://example.com/mcp',
|
|
50
|
-
headers: { Authorization: 'Bearer token' },
|
|
51
|
-
workspacePath,
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
await providerMcpService.upsertProviderMcpServer('claude', {
|
|
55
|
-
name: 'claude-project-sse',
|
|
56
|
-
scope: 'project',
|
|
57
|
-
transport: 'sse',
|
|
58
|
-
url: 'https://example.com/sse',
|
|
59
|
-
headers: { 'X-API-Key': 'abc' },
|
|
60
|
-
workspacePath,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const grouped = await providerMcpService.listProviderMcpServers('claude', { workspacePath });
|
|
64
|
-
assert.ok(grouped.user.some((server) => server.name === 'claude-user-stdio' && server.transport === 'stdio'));
|
|
65
|
-
assert.ok(grouped.local.some((server) => server.name === 'claude-local-http' && server.transport === 'http'));
|
|
66
|
-
assert.ok(grouped.project.some((server) => server.name === 'claude-project-sse' && server.transport === 'sse'));
|
|
67
|
-
|
|
68
|
-
// update behavior is the same upsert route with same name
|
|
69
|
-
await providerMcpService.upsertProviderMcpServer('claude', {
|
|
70
|
-
name: 'claude-project-sse',
|
|
71
|
-
scope: 'project',
|
|
72
|
-
transport: 'sse',
|
|
73
|
-
url: 'https://example.com/sse-updated',
|
|
74
|
-
headers: { 'X-API-Key': 'updated' },
|
|
75
|
-
workspacePath,
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
const projectConfig = await readJson(path.join(workspacePath, '.mcp.json'));
|
|
79
|
-
const projectServers = projectConfig.mcpServers as Record<string, unknown>;
|
|
80
|
-
const projectServer = projectServers['claude-project-sse'] as Record<string, unknown>;
|
|
81
|
-
assert.equal(projectServer.url, 'https://example.com/sse-updated');
|
|
82
|
-
|
|
83
|
-
const removeResult = await providerMcpService.removeProviderMcpServer('claude', {
|
|
84
|
-
name: 'claude-local-http',
|
|
85
|
-
scope: 'local',
|
|
86
|
-
workspacePath,
|
|
87
|
-
});
|
|
88
|
-
assert.equal(removeResult.removed, true);
|
|
89
|
-
} finally {
|
|
90
|
-
restoreHomeDir();
|
|
91
|
-
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* This test covers Codex MCP support for user/project scopes, stdio/http formats,
|
|
97
|
-
* and validation for unsupported scope/transport combinations.
|
|
98
|
-
*/
|
|
99
|
-
test('providerMcpService handles codex MCP TOML config and capability validation', { concurrency: false }, async () => {
|
|
100
|
-
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-mcp-codex-'));
|
|
101
|
-
const workspacePath = path.join(tempRoot, 'workspace');
|
|
102
|
-
await fs.mkdir(workspacePath, { recursive: true });
|
|
103
|
-
|
|
104
|
-
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
105
|
-
try {
|
|
106
|
-
await providerMcpService.upsertProviderMcpServer('codex', {
|
|
107
|
-
name: 'codex-user-stdio',
|
|
108
|
-
scope: 'user',
|
|
109
|
-
transport: 'stdio',
|
|
110
|
-
command: 'python',
|
|
111
|
-
args: ['server.py'],
|
|
112
|
-
env: { API_KEY: 'x' },
|
|
113
|
-
envVars: ['API_KEY'],
|
|
114
|
-
cwd: '/tmp',
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
await providerMcpService.upsertProviderMcpServer('codex', {
|
|
118
|
-
name: 'codex-project-http',
|
|
119
|
-
scope: 'project',
|
|
120
|
-
transport: 'http',
|
|
121
|
-
url: 'https://codex.example.com/mcp',
|
|
122
|
-
headers: { 'X-Custom-Header': 'value' },
|
|
123
|
-
envHttpHeaders: { 'X-API-Key': 'MY_API_KEY_ENV' },
|
|
124
|
-
bearerTokenEnvVar: 'MY_API_TOKEN',
|
|
125
|
-
workspacePath,
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
const userTomlPath = path.join(tempRoot, '.codex', 'config.toml');
|
|
129
|
-
const userConfig = TOML.parse(await fs.readFile(userTomlPath, 'utf8')) as Record<string, unknown>;
|
|
130
|
-
const userServers = userConfig.mcp_servers as Record<string, unknown>;
|
|
131
|
-
const userStdio = userServers['codex-user-stdio'] as Record<string, unknown>;
|
|
132
|
-
assert.equal(userStdio.command, 'python');
|
|
133
|
-
|
|
134
|
-
const projectTomlPath = path.join(workspacePath, '.codex', 'config.toml');
|
|
135
|
-
const projectConfig = TOML.parse(await fs.readFile(projectTomlPath, 'utf8')) as Record<string, unknown>;
|
|
136
|
-
const projectServers = projectConfig.mcp_servers as Record<string, unknown>;
|
|
137
|
-
const projectHttp = projectServers['codex-project-http'] as Record<string, unknown>;
|
|
138
|
-
assert.equal(projectHttp.url, 'https://codex.example.com/mcp');
|
|
139
|
-
|
|
140
|
-
await assert.rejects(
|
|
141
|
-
providerMcpService.upsertProviderMcpServer('codex', {
|
|
142
|
-
name: 'codex-local',
|
|
143
|
-
scope: 'local',
|
|
144
|
-
transport: 'stdio',
|
|
145
|
-
command: 'node',
|
|
146
|
-
}),
|
|
147
|
-
(error: unknown) =>
|
|
148
|
-
error instanceof AppError &&
|
|
149
|
-
error.code === 'MCP_SCOPE_NOT_SUPPORTED' &&
|
|
150
|
-
error.statusCode === 400,
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
await assert.rejects(
|
|
154
|
-
providerMcpService.upsertProviderMcpServer('codex', {
|
|
155
|
-
name: 'codex-sse',
|
|
156
|
-
scope: 'project',
|
|
157
|
-
transport: 'sse',
|
|
158
|
-
url: 'https://example.com/sse',
|
|
159
|
-
workspacePath,
|
|
160
|
-
}),
|
|
161
|
-
(error: unknown) =>
|
|
162
|
-
error instanceof AppError &&
|
|
163
|
-
error.code === 'MCP_TRANSPORT_NOT_SUPPORTED' &&
|
|
164
|
-
error.statusCode === 400,
|
|
165
|
-
);
|
|
166
|
-
} finally {
|
|
167
|
-
restoreHomeDir();
|
|
168
|
-
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* This test covers Gemini/Cursor MCP JSON formats and user/project scope persistence.
|
|
174
|
-
*/
|
|
175
|
-
test('providerMcpService handles gemini and cursor MCP JSON config formats', { concurrency: false }, async () => {
|
|
176
|
-
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-mcp-gc-'));
|
|
177
|
-
const workspacePath = path.join(tempRoot, 'workspace');
|
|
178
|
-
await fs.mkdir(workspacePath, { recursive: true });
|
|
179
|
-
|
|
180
|
-
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
181
|
-
try {
|
|
182
|
-
await providerMcpService.upsertProviderMcpServer('gemini', {
|
|
183
|
-
name: 'gemini-stdio',
|
|
184
|
-
scope: 'user',
|
|
185
|
-
transport: 'stdio',
|
|
186
|
-
command: 'node',
|
|
187
|
-
args: ['server.js'],
|
|
188
|
-
env: { TOKEN: '$TOKEN' },
|
|
189
|
-
cwd: './server',
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
await providerMcpService.upsertProviderMcpServer('gemini', {
|
|
193
|
-
name: 'gemini-http',
|
|
194
|
-
scope: 'project',
|
|
195
|
-
transport: 'http',
|
|
196
|
-
url: 'https://gemini.example.com/mcp',
|
|
197
|
-
headers: { Authorization: 'Bearer token' },
|
|
198
|
-
workspacePath,
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
await providerMcpService.upsertProviderMcpServer('cursor', {
|
|
202
|
-
name: 'cursor-stdio',
|
|
203
|
-
scope: 'project',
|
|
204
|
-
transport: 'stdio',
|
|
205
|
-
command: 'npx',
|
|
206
|
-
args: ['-y', 'mcp-server'],
|
|
207
|
-
env: { API_KEY: 'value' },
|
|
208
|
-
workspacePath,
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
await providerMcpService.upsertProviderMcpServer('cursor', {
|
|
212
|
-
name: 'cursor-http',
|
|
213
|
-
scope: 'user',
|
|
214
|
-
transport: 'http',
|
|
215
|
-
url: 'http://localhost:3333/mcp',
|
|
216
|
-
headers: { API_KEY: 'value' },
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
const geminiUserConfig = await readJson(path.join(tempRoot, '.gemini', 'settings.json'));
|
|
220
|
-
const geminiUserServer = (geminiUserConfig.mcpServers as Record<string, unknown>)['gemini-stdio'] as Record<string, unknown>;
|
|
221
|
-
assert.equal(geminiUserServer.command, 'node');
|
|
222
|
-
assert.equal(geminiUserServer.type, undefined);
|
|
223
|
-
|
|
224
|
-
const geminiProjectConfig = await readJson(path.join(workspacePath, '.gemini', 'settings.json'));
|
|
225
|
-
const geminiProjectServer = (geminiProjectConfig.mcpServers as Record<string, unknown>)['gemini-http'] as Record<string, unknown>;
|
|
226
|
-
assert.equal(geminiProjectServer.type, 'http');
|
|
227
|
-
|
|
228
|
-
const cursorUserConfig = await readJson(path.join(tempRoot, '.cursor', 'mcp.json'));
|
|
229
|
-
const cursorHttpServer = (cursorUserConfig.mcpServers as Record<string, unknown>)['cursor-http'] as Record<string, unknown>;
|
|
230
|
-
assert.equal(cursorHttpServer.url, 'http://localhost:3333/mcp');
|
|
231
|
-
assert.equal(cursorHttpServer.type, undefined);
|
|
232
|
-
} finally {
|
|
233
|
-
restoreHomeDir();
|
|
234
|
-
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* This test covers the global MCP adder requirement: only http/stdio are allowed and
|
|
240
|
-
* one payload is written to all providers.
|
|
241
|
-
*/
|
|
242
|
-
test('providerMcpService global adder writes to all providers and rejects unsupported transports', { concurrency: false }, async () => {
|
|
243
|
-
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-mcp-global-'));
|
|
244
|
-
const workspacePath = path.join(tempRoot, 'workspace');
|
|
245
|
-
await fs.mkdir(workspacePath, { recursive: true });
|
|
246
|
-
|
|
247
|
-
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
248
|
-
try {
|
|
249
|
-
const globalResult = await providerMcpService.addMcpServerToAllProviders({
|
|
250
|
-
name: 'global-http',
|
|
251
|
-
scope: 'project',
|
|
252
|
-
transport: 'http',
|
|
253
|
-
url: 'https://global.example.com/mcp',
|
|
254
|
-
workspacePath,
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
const expectCursorGlobal = process.platform !== 'win32';
|
|
258
|
-
assert.equal(globalResult.length, expectCursorGlobal ? 4 : 3);
|
|
259
|
-
assert.ok(globalResult.every((entry) => entry.created === true));
|
|
260
|
-
|
|
261
|
-
const claudeProject = await readJson(path.join(workspacePath, '.mcp.json'));
|
|
262
|
-
assert.ok((claudeProject.mcpServers as Record<string, unknown>)['global-http']);
|
|
263
|
-
|
|
264
|
-
const codexProject = TOML.parse(await fs.readFile(path.join(workspacePath, '.codex', 'config.toml'), 'utf8')) as Record<string, unknown>;
|
|
265
|
-
assert.ok((codexProject.mcp_servers as Record<string, unknown>)['global-http']);
|
|
266
|
-
|
|
267
|
-
const geminiProject = await readJson(path.join(workspacePath, '.gemini', 'settings.json'));
|
|
268
|
-
assert.ok((geminiProject.mcpServers as Record<string, unknown>)['global-http']);
|
|
269
|
-
|
|
270
|
-
if (expectCursorGlobal) {
|
|
271
|
-
const cursorProject = await readJson(path.join(workspacePath, '.cursor', 'mcp.json'));
|
|
272
|
-
assert.ok((cursorProject.mcpServers as Record<string, unknown>)['global-http']);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
await assert.rejects(
|
|
276
|
-
providerMcpService.addMcpServerToAllProviders({
|
|
277
|
-
name: 'global-sse',
|
|
278
|
-
scope: 'project',
|
|
279
|
-
transport: 'sse',
|
|
280
|
-
url: 'https://example.com/sse',
|
|
281
|
-
workspacePath,
|
|
282
|
-
}),
|
|
283
|
-
(error: unknown) =>
|
|
284
|
-
error instanceof AppError &&
|
|
285
|
-
error.code === 'INVALID_GLOBAL_MCP_TRANSPORT' &&
|
|
286
|
-
error.statusCode === 400,
|
|
287
|
-
);
|
|
288
|
-
} finally {
|
|
289
|
-
restoreHomeDir();
|
|
290
|
-
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
|
|
7
|
+
import TOML from '@iarna/toml';
|
|
8
|
+
|
|
9
|
+
import { providerMcpService } from '@/modules/providers/services/mcp.service.js';
|
|
10
|
+
import { AppError } from '@/shared/utils.js';
|
|
11
|
+
|
|
12
|
+
const patchHomeDir = (nextHomeDir: string) => {
|
|
13
|
+
const original = os.homedir;
|
|
14
|
+
(os as any).homedir = () => nextHomeDir;
|
|
15
|
+
return () => {
|
|
16
|
+
(os as any).homedir = original;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const readJson = async (filePath: string): Promise<Record<string, unknown>> => {
|
|
21
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
22
|
+
return JSON.parse(content) as Record<string, unknown>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* This test covers Claude MCP support for all scopes (user/local/project) and all transports (stdio/http/sse),
|
|
27
|
+
* including add, update/list, and remove operations.
|
|
28
|
+
*/
|
|
29
|
+
test('providerMcpService handles claude MCP scopes/transports with file-backed persistence', { concurrency: false }, async () => {
|
|
30
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-mcp-claude-'));
|
|
31
|
+
const workspacePath = path.join(tempRoot, 'workspace');
|
|
32
|
+
await fs.mkdir(workspacePath, { recursive: true });
|
|
33
|
+
|
|
34
|
+
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
35
|
+
try {
|
|
36
|
+
await providerMcpService.upsertProviderMcpServer('claude', {
|
|
37
|
+
name: 'claude-user-stdio',
|
|
38
|
+
scope: 'user',
|
|
39
|
+
transport: 'stdio',
|
|
40
|
+
command: 'npx',
|
|
41
|
+
args: ['-y', 'my-server'],
|
|
42
|
+
env: { API_KEY: 'secret' },
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
await providerMcpService.upsertProviderMcpServer('claude', {
|
|
46
|
+
name: 'claude-local-http',
|
|
47
|
+
scope: 'local',
|
|
48
|
+
transport: 'http',
|
|
49
|
+
url: 'https://example.com/mcp',
|
|
50
|
+
headers: { Authorization: 'Bearer token' },
|
|
51
|
+
workspacePath,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await providerMcpService.upsertProviderMcpServer('claude', {
|
|
55
|
+
name: 'claude-project-sse',
|
|
56
|
+
scope: 'project',
|
|
57
|
+
transport: 'sse',
|
|
58
|
+
url: 'https://example.com/sse',
|
|
59
|
+
headers: { 'X-API-Key': 'abc' },
|
|
60
|
+
workspacePath,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const grouped = await providerMcpService.listProviderMcpServers('claude', { workspacePath });
|
|
64
|
+
assert.ok(grouped.user.some((server) => server.name === 'claude-user-stdio' && server.transport === 'stdio'));
|
|
65
|
+
assert.ok(grouped.local.some((server) => server.name === 'claude-local-http' && server.transport === 'http'));
|
|
66
|
+
assert.ok(grouped.project.some((server) => server.name === 'claude-project-sse' && server.transport === 'sse'));
|
|
67
|
+
|
|
68
|
+
// update behavior is the same upsert route with same name
|
|
69
|
+
await providerMcpService.upsertProviderMcpServer('claude', {
|
|
70
|
+
name: 'claude-project-sse',
|
|
71
|
+
scope: 'project',
|
|
72
|
+
transport: 'sse',
|
|
73
|
+
url: 'https://example.com/sse-updated',
|
|
74
|
+
headers: { 'X-API-Key': 'updated' },
|
|
75
|
+
workspacePath,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const projectConfig = await readJson(path.join(workspacePath, '.mcp.json'));
|
|
79
|
+
const projectServers = projectConfig.mcpServers as Record<string, unknown>;
|
|
80
|
+
const projectServer = projectServers['claude-project-sse'] as Record<string, unknown>;
|
|
81
|
+
assert.equal(projectServer.url, 'https://example.com/sse-updated');
|
|
82
|
+
|
|
83
|
+
const removeResult = await providerMcpService.removeProviderMcpServer('claude', {
|
|
84
|
+
name: 'claude-local-http',
|
|
85
|
+
scope: 'local',
|
|
86
|
+
workspacePath,
|
|
87
|
+
});
|
|
88
|
+
assert.equal(removeResult.removed, true);
|
|
89
|
+
} finally {
|
|
90
|
+
restoreHomeDir();
|
|
91
|
+
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* This test covers Codex MCP support for user/project scopes, stdio/http formats,
|
|
97
|
+
* and validation for unsupported scope/transport combinations.
|
|
98
|
+
*/
|
|
99
|
+
test('providerMcpService handles codex MCP TOML config and capability validation', { concurrency: false }, async () => {
|
|
100
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-mcp-codex-'));
|
|
101
|
+
const workspacePath = path.join(tempRoot, 'workspace');
|
|
102
|
+
await fs.mkdir(workspacePath, { recursive: true });
|
|
103
|
+
|
|
104
|
+
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
105
|
+
try {
|
|
106
|
+
await providerMcpService.upsertProviderMcpServer('codex', {
|
|
107
|
+
name: 'codex-user-stdio',
|
|
108
|
+
scope: 'user',
|
|
109
|
+
transport: 'stdio',
|
|
110
|
+
command: 'python',
|
|
111
|
+
args: ['server.py'],
|
|
112
|
+
env: { API_KEY: 'x' },
|
|
113
|
+
envVars: ['API_KEY'],
|
|
114
|
+
cwd: '/tmp',
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
await providerMcpService.upsertProviderMcpServer('codex', {
|
|
118
|
+
name: 'codex-project-http',
|
|
119
|
+
scope: 'project',
|
|
120
|
+
transport: 'http',
|
|
121
|
+
url: 'https://codex.example.com/mcp',
|
|
122
|
+
headers: { 'X-Custom-Header': 'value' },
|
|
123
|
+
envHttpHeaders: { 'X-API-Key': 'MY_API_KEY_ENV' },
|
|
124
|
+
bearerTokenEnvVar: 'MY_API_TOKEN',
|
|
125
|
+
workspacePath,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const userTomlPath = path.join(tempRoot, '.codex', 'config.toml');
|
|
129
|
+
const userConfig = TOML.parse(await fs.readFile(userTomlPath, 'utf8')) as Record<string, unknown>;
|
|
130
|
+
const userServers = userConfig.mcp_servers as Record<string, unknown>;
|
|
131
|
+
const userStdio = userServers['codex-user-stdio'] as Record<string, unknown>;
|
|
132
|
+
assert.equal(userStdio.command, 'python');
|
|
133
|
+
|
|
134
|
+
const projectTomlPath = path.join(workspacePath, '.codex', 'config.toml');
|
|
135
|
+
const projectConfig = TOML.parse(await fs.readFile(projectTomlPath, 'utf8')) as Record<string, unknown>;
|
|
136
|
+
const projectServers = projectConfig.mcp_servers as Record<string, unknown>;
|
|
137
|
+
const projectHttp = projectServers['codex-project-http'] as Record<string, unknown>;
|
|
138
|
+
assert.equal(projectHttp.url, 'https://codex.example.com/mcp');
|
|
139
|
+
|
|
140
|
+
await assert.rejects(
|
|
141
|
+
providerMcpService.upsertProviderMcpServer('codex', {
|
|
142
|
+
name: 'codex-local',
|
|
143
|
+
scope: 'local',
|
|
144
|
+
transport: 'stdio',
|
|
145
|
+
command: 'node',
|
|
146
|
+
}),
|
|
147
|
+
(error: unknown) =>
|
|
148
|
+
error instanceof AppError &&
|
|
149
|
+
error.code === 'MCP_SCOPE_NOT_SUPPORTED' &&
|
|
150
|
+
error.statusCode === 400,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
await assert.rejects(
|
|
154
|
+
providerMcpService.upsertProviderMcpServer('codex', {
|
|
155
|
+
name: 'codex-sse',
|
|
156
|
+
scope: 'project',
|
|
157
|
+
transport: 'sse',
|
|
158
|
+
url: 'https://example.com/sse',
|
|
159
|
+
workspacePath,
|
|
160
|
+
}),
|
|
161
|
+
(error: unknown) =>
|
|
162
|
+
error instanceof AppError &&
|
|
163
|
+
error.code === 'MCP_TRANSPORT_NOT_SUPPORTED' &&
|
|
164
|
+
error.statusCode === 400,
|
|
165
|
+
);
|
|
166
|
+
} finally {
|
|
167
|
+
restoreHomeDir();
|
|
168
|
+
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* This test covers Gemini/Cursor MCP JSON formats and user/project scope persistence.
|
|
174
|
+
*/
|
|
175
|
+
test('providerMcpService handles gemini and cursor MCP JSON config formats', { concurrency: false }, async () => {
|
|
176
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-mcp-gc-'));
|
|
177
|
+
const workspacePath = path.join(tempRoot, 'workspace');
|
|
178
|
+
await fs.mkdir(workspacePath, { recursive: true });
|
|
179
|
+
|
|
180
|
+
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
181
|
+
try {
|
|
182
|
+
await providerMcpService.upsertProviderMcpServer('gemini', {
|
|
183
|
+
name: 'gemini-stdio',
|
|
184
|
+
scope: 'user',
|
|
185
|
+
transport: 'stdio',
|
|
186
|
+
command: 'node',
|
|
187
|
+
args: ['server.js'],
|
|
188
|
+
env: { TOKEN: '$TOKEN' },
|
|
189
|
+
cwd: './server',
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
await providerMcpService.upsertProviderMcpServer('gemini', {
|
|
193
|
+
name: 'gemini-http',
|
|
194
|
+
scope: 'project',
|
|
195
|
+
transport: 'http',
|
|
196
|
+
url: 'https://gemini.example.com/mcp',
|
|
197
|
+
headers: { Authorization: 'Bearer token' },
|
|
198
|
+
workspacePath,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
await providerMcpService.upsertProviderMcpServer('cursor', {
|
|
202
|
+
name: 'cursor-stdio',
|
|
203
|
+
scope: 'project',
|
|
204
|
+
transport: 'stdio',
|
|
205
|
+
command: 'npx',
|
|
206
|
+
args: ['-y', 'mcp-server'],
|
|
207
|
+
env: { API_KEY: 'value' },
|
|
208
|
+
workspacePath,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
await providerMcpService.upsertProviderMcpServer('cursor', {
|
|
212
|
+
name: 'cursor-http',
|
|
213
|
+
scope: 'user',
|
|
214
|
+
transport: 'http',
|
|
215
|
+
url: 'http://localhost:3333/mcp',
|
|
216
|
+
headers: { API_KEY: 'value' },
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const geminiUserConfig = await readJson(path.join(tempRoot, '.gemini', 'settings.json'));
|
|
220
|
+
const geminiUserServer = (geminiUserConfig.mcpServers as Record<string, unknown>)['gemini-stdio'] as Record<string, unknown>;
|
|
221
|
+
assert.equal(geminiUserServer.command, 'node');
|
|
222
|
+
assert.equal(geminiUserServer.type, undefined);
|
|
223
|
+
|
|
224
|
+
const geminiProjectConfig = await readJson(path.join(workspacePath, '.gemini', 'settings.json'));
|
|
225
|
+
const geminiProjectServer = (geminiProjectConfig.mcpServers as Record<string, unknown>)['gemini-http'] as Record<string, unknown>;
|
|
226
|
+
assert.equal(geminiProjectServer.type, 'http');
|
|
227
|
+
|
|
228
|
+
const cursorUserConfig = await readJson(path.join(tempRoot, '.cursor', 'mcp.json'));
|
|
229
|
+
const cursorHttpServer = (cursorUserConfig.mcpServers as Record<string, unknown>)['cursor-http'] as Record<string, unknown>;
|
|
230
|
+
assert.equal(cursorHttpServer.url, 'http://localhost:3333/mcp');
|
|
231
|
+
assert.equal(cursorHttpServer.type, undefined);
|
|
232
|
+
} finally {
|
|
233
|
+
restoreHomeDir();
|
|
234
|
+
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* This test covers the global MCP adder requirement: only http/stdio are allowed and
|
|
240
|
+
* one payload is written to all providers.
|
|
241
|
+
*/
|
|
242
|
+
test('providerMcpService global adder writes to all providers and rejects unsupported transports', { concurrency: false }, async () => {
|
|
243
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-mcp-global-'));
|
|
244
|
+
const workspacePath = path.join(tempRoot, 'workspace');
|
|
245
|
+
await fs.mkdir(workspacePath, { recursive: true });
|
|
246
|
+
|
|
247
|
+
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
248
|
+
try {
|
|
249
|
+
const globalResult = await providerMcpService.addMcpServerToAllProviders({
|
|
250
|
+
name: 'global-http',
|
|
251
|
+
scope: 'project',
|
|
252
|
+
transport: 'http',
|
|
253
|
+
url: 'https://global.example.com/mcp',
|
|
254
|
+
workspacePath,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const expectCursorGlobal = process.platform !== 'win32';
|
|
258
|
+
assert.equal(globalResult.length, expectCursorGlobal ? 4 : 3);
|
|
259
|
+
assert.ok(globalResult.every((entry) => entry.created === true));
|
|
260
|
+
|
|
261
|
+
const claudeProject = await readJson(path.join(workspacePath, '.mcp.json'));
|
|
262
|
+
assert.ok((claudeProject.mcpServers as Record<string, unknown>)['global-http']);
|
|
263
|
+
|
|
264
|
+
const codexProject = TOML.parse(await fs.readFile(path.join(workspacePath, '.codex', 'config.toml'), 'utf8')) as Record<string, unknown>;
|
|
265
|
+
assert.ok((codexProject.mcp_servers as Record<string, unknown>)['global-http']);
|
|
266
|
+
|
|
267
|
+
const geminiProject = await readJson(path.join(workspacePath, '.gemini', 'settings.json'));
|
|
268
|
+
assert.ok((geminiProject.mcpServers as Record<string, unknown>)['global-http']);
|
|
269
|
+
|
|
270
|
+
if (expectCursorGlobal) {
|
|
271
|
+
const cursorProject = await readJson(path.join(workspacePath, '.cursor', 'mcp.json'));
|
|
272
|
+
assert.ok((cursorProject.mcpServers as Record<string, unknown>)['global-http']);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
await assert.rejects(
|
|
276
|
+
providerMcpService.addMcpServerToAllProviders({
|
|
277
|
+
name: 'global-sse',
|
|
278
|
+
scope: 'project',
|
|
279
|
+
transport: 'sse',
|
|
280
|
+
url: 'https://example.com/sse',
|
|
281
|
+
workspacePath,
|
|
282
|
+
}),
|
|
283
|
+
(error: unknown) =>
|
|
284
|
+
error instanceof AppError &&
|
|
285
|
+
error.code === 'INVALID_GLOBAL_MCP_TRANSPORT' &&
|
|
286
|
+
error.statusCode === 400,
|
|
287
|
+
);
|
|
288
|
+
} finally {
|
|
289
|
+
restoreHomeDir();
|
|
290
|
+
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|