@kwonye/mcpx 0.1.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/LICENSE +21 -0
- package/README.md +150 -0
- package/dist/adapters/claude.d.ts +7 -0
- package/dist/adapters/claude.js +69 -0
- package/dist/adapters/claude.js.map +1 -0
- package/dist/adapters/cline.d.ts +7 -0
- package/dist/adapters/cline.js +65 -0
- package/dist/adapters/cline.js.map +1 -0
- package/dist/adapters/codex.d.ts +7 -0
- package/dist/adapters/codex.js +52 -0
- package/dist/adapters/codex.js.map +1 -0
- package/dist/adapters/cursor.d.ts +7 -0
- package/dist/adapters/cursor.js +52 -0
- package/dist/adapters/cursor.js.map +1 -0
- package/dist/adapters/index.d.ts +2 -0
- package/dist/adapters/index.js +15 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/utils.d.ts +10 -0
- package/dist/adapters/utils.js +69 -0
- package/dist/adapters/utils.js.map +1 -0
- package/dist/adapters/vscode.d.ts +7 -0
- package/dist/adapters/vscode.js +52 -0
- package/dist/adapters/vscode.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +577 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/config.d.ts +4 -0
- package/dist/core/config.js +71 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/daemon.d.ts +25 -0
- package/dist/core/daemon.js +174 -0
- package/dist/core/daemon.js.map +1 -0
- package/dist/core/managed-index.d.ts +4 -0
- package/dist/core/managed-index.js +22 -0
- package/dist/core/managed-index.js.map +1 -0
- package/dist/core/paths.d.ts +12 -0
- package/dist/core/paths.js +46 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/registry.d.ts +7 -0
- package/dist/core/registry.js +33 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/secrets.d.ts +16 -0
- package/dist/core/secrets.js +108 -0
- package/dist/core/secrets.js.map +1 -0
- package/dist/core/server-auth.d.ts +19 -0
- package/dist/core/server-auth.js +112 -0
- package/dist/core/server-auth.js.map +1 -0
- package/dist/core/sync.d.ts +9 -0
- package/dist/core/sync.js +63 -0
- package/dist/core/sync.js.map +1 -0
- package/dist/gateway/server.d.ts +9 -0
- package/dist/gateway/server.js +960 -0
- package/dist/gateway/server.js.map +1 -0
- package/dist/types.d.ts +86 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/util/fs.d.ts +4 -0
- package/dist/util/fs.js +34 -0
- package/dist/util/fs.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kwonye
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# mcpx
|
|
2
|
+
|
|
3
|
+
Install MCP servers once, expose them to multiple clients through one local HTTP gateway.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
- Stores upstream MCP server definitions in one place: `~/.config/mcpx/config.json`
|
|
8
|
+
- Runs a local gateway at `http://127.0.0.1:<port>/mcp`
|
|
9
|
+
- Syncs managed entries into supported clients (Claude, Codex, Cursor, Cline, VS Code)
|
|
10
|
+
- Creates one managed entry per upstream server name
|
|
11
|
+
- Routes client traffic through one daemon while preserving per-upstream auth/headers
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
Prerequisite: Node.js `>=20`
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g @kwonye/mcpx@latest
|
|
19
|
+
mcpx --help
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### Path A: Add servers with CLI (recommended)
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# HTTP upstream
|
|
28
|
+
mcpx add vercel --transport http https://example.com/mcp
|
|
29
|
+
|
|
30
|
+
# stdio upstream
|
|
31
|
+
mcpx add next-devtools --transport stdio npx next-devtools-mcp@latest
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`mcpx add` and `mcpx remove` auto-sync by default. Run `mcpx sync` when you want a manual re-sync or to target specific clients.
|
|
35
|
+
|
|
36
|
+
### Path B: Add servers manually in JSON config
|
|
37
|
+
|
|
38
|
+
Edit `~/.config/mcpx/config.json` and add entries under `servers`:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"servers": {
|
|
43
|
+
"vercel": {
|
|
44
|
+
"transport": "http",
|
|
45
|
+
"url": "https://example.com/mcp",
|
|
46
|
+
"headers": {
|
|
47
|
+
"Authorization": "secret://vercel_auth_header"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"next-devtools": {
|
|
51
|
+
"transport": "stdio",
|
|
52
|
+
"command": "npx",
|
|
53
|
+
"args": ["next-devtools-mcp@latest"],
|
|
54
|
+
"env": {
|
|
55
|
+
"FOO": "bar"
|
|
56
|
+
},
|
|
57
|
+
"cwd": "/path/to/project"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
After manual edits, you must run:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
mcpx sync
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Manual config changes do not update client configs until `mcpx sync` runs.
|
|
70
|
+
|
|
71
|
+
## Supported Clients
|
|
72
|
+
|
|
73
|
+
- Claude
|
|
74
|
+
- Codex
|
|
75
|
+
- Cursor
|
|
76
|
+
- Cline
|
|
77
|
+
- VS Code
|
|
78
|
+
|
|
79
|
+
## Claude Convention
|
|
80
|
+
|
|
81
|
+
`mcpx` follows Claude-style MCP server conventions by syncing per-upstream entries keyed by server name under `mcpServers` in Claude config. Each entry is an HTTP endpoint to the local gateway (`/mcp?upstream=<name>`) and includes the required local auth header.
|
|
82
|
+
|
|
83
|
+
## How it works
|
|
84
|
+
|
|
85
|
+
1. Define upstream servers in central `mcpx` config.
|
|
86
|
+
2. `mcpx` ensures local gateway auth and daemon state.
|
|
87
|
+
3. `mcpx sync` writes managed client entries that point to the local gateway.
|
|
88
|
+
|
|
89
|
+
## Advanced Usage
|
|
90
|
+
|
|
91
|
+
### Auth and secrets
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
mcpx secret set vercel_auth_header --value "Bearer <token>"
|
|
95
|
+
mcpx secret ls
|
|
96
|
+
mcpx secret rm vercel_auth_header
|
|
97
|
+
|
|
98
|
+
mcpx auth set vercel --header Authorization --value "Bearer <token>"
|
|
99
|
+
mcpx auth set next-devtools --env NEXT_DEVTOOLS_TOKEN --value "<token>"
|
|
100
|
+
mcpx auth show
|
|
101
|
+
mcpx auth rm vercel --header Authorization --delete-secret
|
|
102
|
+
mcpx auth rotate-local-token
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Daemon lifecycle
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
mcpx daemon start
|
|
109
|
+
mcpx daemon status
|
|
110
|
+
mcpx daemon logs
|
|
111
|
+
mcpx daemon stop
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Targeted sync
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
mcpx sync
|
|
118
|
+
mcpx sync claude
|
|
119
|
+
mcpx sync --client claude --client codex
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Config/data/state path overrides
|
|
123
|
+
|
|
124
|
+
- `MCPX_CONFIG_HOME`
|
|
125
|
+
- `MCPX_DATA_HOME`
|
|
126
|
+
- `MCPX_STATE_HOME`
|
|
127
|
+
|
|
128
|
+
## Troubleshooting
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
mcpx doctor
|
|
132
|
+
mcpx status
|
|
133
|
+
mcpx daemon logs
|
|
134
|
+
mcpx sync --json
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Build and test from source
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npm install
|
|
141
|
+
npm run build
|
|
142
|
+
npm test
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Notes
|
|
146
|
+
|
|
147
|
+
- Client connectivity is HTTP-first.
|
|
148
|
+
- Upstreams can be HTTP or stdio.
|
|
149
|
+
- macOS keychain is the secure secret backend.
|
|
150
|
+
- In CI/headless environments, `MCPX_SECRET_<name>` env vars can override keychain lookups.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ClientAdapter, McpxConfig, SyncClientOptions, SyncResult } from "../types.js";
|
|
2
|
+
export declare class ClaudeAdapter implements ClientAdapter {
|
|
3
|
+
readonly id: "claude";
|
|
4
|
+
detectConfigPath(): string | null;
|
|
5
|
+
supportsHttp(): boolean;
|
|
6
|
+
syncGateway(_config: McpxConfig, options: SyncClientOptions): SyncResult;
|
|
7
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { readJsonFile, writeJsonAtomic } from "../util/fs.js";
|
|
4
|
+
import { ensureManagedEntryWritable, errorResult, okResult, pruneStaleManagedEntries, setManagedEntries } from "./utils.js";
|
|
5
|
+
export class ClaudeAdapter {
|
|
6
|
+
id = "claude";
|
|
7
|
+
detectConfigPath() {
|
|
8
|
+
return path.join(os.homedir(), ".claude.json");
|
|
9
|
+
}
|
|
10
|
+
supportsHttp() {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
syncGateway(_config, options) {
|
|
14
|
+
const configPath = this.detectConfigPath();
|
|
15
|
+
if (!configPath) {
|
|
16
|
+
return errorResult(this.id, undefined, "Unable to resolve Claude config path.");
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const raw = readJsonFile(configPath, {});
|
|
20
|
+
const managedNames = options.managedEntries.map((entry) => entry.name);
|
|
21
|
+
const serverEntries = Object.fromEntries(options.managedEntries.map((entry) => [entry.name, {
|
|
22
|
+
type: "http",
|
|
23
|
+
url: entry.url,
|
|
24
|
+
headers: entry.headers
|
|
25
|
+
}]));
|
|
26
|
+
const topLevelServers = (raw.mcpServers ?? {});
|
|
27
|
+
for (const name of managedNames) {
|
|
28
|
+
const topLevelConflict = ensureManagedEntryWritable(options.managedIndex, this.id, name, topLevelServers[name]);
|
|
29
|
+
if (topLevelConflict) {
|
|
30
|
+
return errorResult(this.id, configPath, topLevelConflict);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
pruneStaleManagedEntries(options.managedIndex, this.id, topLevelServers, managedNames);
|
|
34
|
+
for (const [name, entry] of Object.entries(serverEntries)) {
|
|
35
|
+
topLevelServers[name] = entry;
|
|
36
|
+
}
|
|
37
|
+
raw.mcpServers = topLevelServers;
|
|
38
|
+
if (raw.projects && typeof raw.projects === "object") {
|
|
39
|
+
const projects = raw.projects;
|
|
40
|
+
for (const [projectPath, projectValue] of Object.entries(projects)) {
|
|
41
|
+
if (!projectValue || typeof projectValue !== "object") {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const project = projectValue;
|
|
45
|
+
const projectServers = (project.mcpServers ?? {});
|
|
46
|
+
for (const name of managedNames) {
|
|
47
|
+
const projectConflict = ensureManagedEntryWritable(options.managedIndex, this.id, name, projectServers[name]);
|
|
48
|
+
if (projectConflict) {
|
|
49
|
+
return errorResult(this.id, configPath, `${projectConflict} (project: ${projectPath})`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
pruneStaleManagedEntries(options.managedIndex, this.id, projectServers, managedNames);
|
|
53
|
+
for (const [name, entry] of Object.entries(serverEntries)) {
|
|
54
|
+
projectServers[name] = entry;
|
|
55
|
+
}
|
|
56
|
+
project.mcpServers = projectServers;
|
|
57
|
+
projects[projectPath] = project;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
writeJsonAtomic(configPath, raw);
|
|
61
|
+
setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(Object.entries(serverEntries).map(([name, entry]) => [name, JSON.stringify(entry)])));
|
|
62
|
+
return okResult(this.id, configPath);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
return errorResult(this.id, configPath, error.message);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/adapters/claude.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ClientAdapter, McpxConfig, SyncClientOptions, SyncResult } from "../types.js";
|
|
2
|
+
export declare class ClineAdapter implements ClientAdapter {
|
|
3
|
+
readonly id: "cline";
|
|
4
|
+
detectConfigPath(): string | null;
|
|
5
|
+
supportsHttp(): boolean;
|
|
6
|
+
syncGateway(_config: McpxConfig, options: SyncClientOptions): SyncResult;
|
|
7
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import { readJsonFile, writeJsonAtomic } from "../util/fs.js";
|
|
5
|
+
import { ensureManagedEntryWritable, errorResult, okResult, pruneStaleManagedEntries, setManagedEntries } from "./utils.js";
|
|
6
|
+
function getCandidatePaths() {
|
|
7
|
+
return [
|
|
8
|
+
path.join(os.homedir(), "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
|
|
9
|
+
path.join(os.homedir(), "Library", "Application Support", "Cursor", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json")
|
|
10
|
+
];
|
|
11
|
+
}
|
|
12
|
+
export class ClineAdapter {
|
|
13
|
+
id = "cline";
|
|
14
|
+
detectConfigPath() {
|
|
15
|
+
const candidates = getCandidatePaths();
|
|
16
|
+
for (const candidate of candidates) {
|
|
17
|
+
if (fs.existsSync(candidate)) {
|
|
18
|
+
return candidate;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return candidates[0] ?? null;
|
|
22
|
+
}
|
|
23
|
+
supportsHttp() {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
syncGateway(_config, options) {
|
|
27
|
+
const configPath = this.detectConfigPath();
|
|
28
|
+
if (!configPath) {
|
|
29
|
+
return errorResult(this.id, undefined, "Unable to resolve Cline MCP config path.");
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const raw = readJsonFile(configPath, {});
|
|
33
|
+
const servers = {
|
|
34
|
+
...(raw.mcpServers ?? {})
|
|
35
|
+
};
|
|
36
|
+
const managedNames = options.managedEntries.map((entry) => entry.name);
|
|
37
|
+
const serverEntries = Object.fromEntries(options.managedEntries.map((entry) => [entry.name, {
|
|
38
|
+
transportType: "http",
|
|
39
|
+
url: entry.url,
|
|
40
|
+
headers: entry.headers
|
|
41
|
+
}]));
|
|
42
|
+
for (const name of managedNames) {
|
|
43
|
+
const conflict = ensureManagedEntryWritable(options.managedIndex, this.id, name, servers[name]);
|
|
44
|
+
if (conflict) {
|
|
45
|
+
return errorResult(this.id, configPath, conflict);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
pruneStaleManagedEntries(options.managedIndex, this.id, servers, managedNames);
|
|
49
|
+
for (const [name, entry] of Object.entries(serverEntries)) {
|
|
50
|
+
servers[name] = entry;
|
|
51
|
+
}
|
|
52
|
+
const next = {
|
|
53
|
+
...raw,
|
|
54
|
+
mcpServers: servers
|
|
55
|
+
};
|
|
56
|
+
writeJsonAtomic(configPath, next);
|
|
57
|
+
setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(Object.entries(serverEntries).map(([name, entry]) => [name, JSON.stringify(entry)])));
|
|
58
|
+
return okResult(this.id, configPath);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
return errorResult(this.id, configPath, error.message);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=cline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cline.js","sourceRoot":"","sources":["../../src/adapters/cline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ClientAdapter, McpxConfig, SyncClientOptions, SyncResult } from "../types.js";
|
|
2
|
+
export declare class CodexAdapter implements ClientAdapter {
|
|
3
|
+
readonly id: "codex";
|
|
4
|
+
detectConfigPath(): string | null;
|
|
5
|
+
supportsHttp(): boolean;
|
|
6
|
+
syncGateway(_config: McpxConfig, options: SyncClientOptions): SyncResult;
|
|
7
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import { parse, stringify } from "@iarna/toml";
|
|
5
|
+
import { ensureManagedEntryWritable, errorResult, okResult, pruneStaleManagedEntries, setManagedEntries } from "./utils.js";
|
|
6
|
+
export class CodexAdapter {
|
|
7
|
+
id = "codex";
|
|
8
|
+
detectConfigPath() {
|
|
9
|
+
return path.join(os.homedir(), ".codex", "config.toml");
|
|
10
|
+
}
|
|
11
|
+
supportsHttp() {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
syncGateway(_config, options) {
|
|
15
|
+
const configPath = this.detectConfigPath();
|
|
16
|
+
if (!configPath) {
|
|
17
|
+
return errorResult(this.id, undefined, "Unable to resolve Codex config.toml path.");
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const existing = fs.existsSync(configPath) ? fs.readFileSync(configPath, "utf8") : "";
|
|
21
|
+
const doc = existing.trim().length > 0 ? parse(existing) : {};
|
|
22
|
+
const mcpServers = (doc.mcp_servers ?? {});
|
|
23
|
+
const managedNames = options.managedEntries.map((entry) => entry.name);
|
|
24
|
+
const serverEntries = Object.fromEntries(options.managedEntries.map((entry) => [entry.name, {
|
|
25
|
+
enabled: true,
|
|
26
|
+
url: entry.url,
|
|
27
|
+
headers: entry.headers
|
|
28
|
+
}]));
|
|
29
|
+
for (const name of managedNames) {
|
|
30
|
+
const conflict = ensureManagedEntryWritable(options.managedIndex, this.id, name, mcpServers[name]);
|
|
31
|
+
if (conflict) {
|
|
32
|
+
return errorResult(this.id, configPath, conflict);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
pruneStaleManagedEntries(options.managedIndex, this.id, mcpServers, managedNames);
|
|
36
|
+
for (const [name, entry] of Object.entries(serverEntries)) {
|
|
37
|
+
mcpServers[name] = entry;
|
|
38
|
+
}
|
|
39
|
+
doc.mcp_servers = mcpServers;
|
|
40
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
41
|
+
const tmpPath = `${configPath}.tmp-${process.pid}-${Date.now()}`;
|
|
42
|
+
fs.writeFileSync(tmpPath, stringify(doc), { mode: 0o600 });
|
|
43
|
+
fs.renameSync(tmpPath, configPath);
|
|
44
|
+
setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(Object.entries(serverEntries).map(([name, entry]) => [name, JSON.stringify(entry)])));
|
|
45
|
+
return okResult(this.id, configPath);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
return errorResult(this.id, configPath, error.message);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=codex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex.js","sourceRoot":"","sources":["../../src/adapters/codex.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ClientAdapter, McpxConfig, SyncClientOptions, SyncResult } from "../types.js";
|
|
2
|
+
export declare class CursorAdapter implements ClientAdapter {
|
|
3
|
+
readonly id: "cursor";
|
|
4
|
+
detectConfigPath(): string | null;
|
|
5
|
+
supportsHttp(): boolean;
|
|
6
|
+
syncGateway(_config: McpxConfig, options: SyncClientOptions): SyncResult;
|
|
7
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { readJsonFile, writeJsonAtomic } from "../util/fs.js";
|
|
4
|
+
import { ensureManagedEntryWritable, errorResult, okResult, pruneStaleManagedEntries, setManagedEntries } from "./utils.js";
|
|
5
|
+
export class CursorAdapter {
|
|
6
|
+
id = "cursor";
|
|
7
|
+
detectConfigPath() {
|
|
8
|
+
return path.join(os.homedir(), "Library", "Application Support", "Cursor", "User", "mcp.json");
|
|
9
|
+
}
|
|
10
|
+
supportsHttp() {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
syncGateway(_config, options) {
|
|
14
|
+
const configPath = this.detectConfigPath();
|
|
15
|
+
if (!configPath) {
|
|
16
|
+
return errorResult(this.id, undefined, "Unable to resolve Cursor MCP config path.");
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const raw = readJsonFile(configPath, {});
|
|
20
|
+
const servers = {
|
|
21
|
+
...(raw.servers ?? {})
|
|
22
|
+
};
|
|
23
|
+
const managedNames = options.managedEntries.map((entry) => entry.name);
|
|
24
|
+
const serverEntries = Object.fromEntries(options.managedEntries.map((entry) => [entry.name, {
|
|
25
|
+
type: "http",
|
|
26
|
+
url: entry.url,
|
|
27
|
+
headers: entry.headers
|
|
28
|
+
}]));
|
|
29
|
+
for (const name of managedNames) {
|
|
30
|
+
const conflict = ensureManagedEntryWritable(options.managedIndex, this.id, name, servers[name]);
|
|
31
|
+
if (conflict) {
|
|
32
|
+
return errorResult(this.id, configPath, conflict);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
pruneStaleManagedEntries(options.managedIndex, this.id, servers, managedNames);
|
|
36
|
+
for (const [name, entry] of Object.entries(serverEntries)) {
|
|
37
|
+
servers[name] = entry;
|
|
38
|
+
}
|
|
39
|
+
const next = {
|
|
40
|
+
...raw,
|
|
41
|
+
servers
|
|
42
|
+
};
|
|
43
|
+
writeJsonAtomic(configPath, next);
|
|
44
|
+
setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(Object.entries(serverEntries).map(([name, entry]) => [name, JSON.stringify(entry)])));
|
|
45
|
+
return okResult(this.id, configPath);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
return errorResult(this.id, configPath, error.message);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/adapters/cursor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ClaudeAdapter } from "./claude.js";
|
|
2
|
+
import { CodexAdapter } from "./codex.js";
|
|
3
|
+
import { CursorAdapter } from "./cursor.js";
|
|
4
|
+
import { ClineAdapter } from "./cline.js";
|
|
5
|
+
import { VsCodeAdapter } from "./vscode.js";
|
|
6
|
+
export function getAdapters() {
|
|
7
|
+
return [
|
|
8
|
+
new ClaudeAdapter(),
|
|
9
|
+
new CodexAdapter(),
|
|
10
|
+
new CursorAdapter(),
|
|
11
|
+
new ClineAdapter(),
|
|
12
|
+
new VsCodeAdapter()
|
|
13
|
+
];
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,UAAU,WAAW;IACzB,OAAO;QACL,IAAI,aAAa,EAAE;QACnB,IAAI,YAAY,EAAE;QAClB,IAAI,aAAa,EAAE;QACnB,IAAI,YAAY,EAAE;QAClB,IAAI,aAAa,EAAE;KACpB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ClientId, ManagedIndex, SyncResult } from "../types.js";
|
|
2
|
+
export declare function getManagedEntryNames(managedIndex: ManagedIndex, clientId: ClientId): string[];
|
|
3
|
+
export declare function isManagedEntry(managedIndex: ManagedIndex, clientId: ClientId, entryName: string): boolean;
|
|
4
|
+
export declare function pruneStaleManagedEntries(managedIndex: ManagedIndex, clientId: ClientId, servers: Record<string, unknown>, keepEntryNames: string[]): void;
|
|
5
|
+
export declare function ensureManagedEntryWritable(managedIndex: ManagedIndex, clientId: ClientId, entryName: string, existingValue: unknown): string | null;
|
|
6
|
+
export declare function setManagedEntries(managedIndex: ManagedIndex, clientId: ClientId, configPath: string, entries: Record<string, string>): void;
|
|
7
|
+
export declare function okResult(clientId: ClientId, configPath: string, message?: string): SyncResult;
|
|
8
|
+
export declare function unsupportedResult(clientId: ClientId, message: string): SyncResult;
|
|
9
|
+
export declare function errorResult(clientId: ClientId, configPath: string | undefined, message: string): SyncResult;
|
|
10
|
+
export declare function skippedResult(clientId: ClientId, message: string): SyncResult;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { sha256 } from "../util/fs.js";
|
|
2
|
+
export function getManagedEntryNames(managedIndex, clientId) {
|
|
3
|
+
return Object.keys(managedIndex.managed[clientId]?.entries ?? {});
|
|
4
|
+
}
|
|
5
|
+
export function isManagedEntry(managedIndex, clientId, entryName) {
|
|
6
|
+
return Boolean(managedIndex.managed[clientId]?.entries?.[entryName]);
|
|
7
|
+
}
|
|
8
|
+
export function pruneStaleManagedEntries(managedIndex, clientId, servers, keepEntryNames) {
|
|
9
|
+
const keep = new Set(keepEntryNames);
|
|
10
|
+
const managedNames = getManagedEntryNames(managedIndex, clientId);
|
|
11
|
+
for (const name of managedNames) {
|
|
12
|
+
if (!keep.has(name)) {
|
|
13
|
+
delete servers[name];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function ensureManagedEntryWritable(managedIndex, clientId, entryName, existingValue) {
|
|
18
|
+
if (existingValue === undefined || existingValue === null) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
if (isManagedEntry(managedIndex, clientId, entryName)) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return `Cannot sync managed entry \"${entryName}\" because an unmanaged entry already exists.`;
|
|
25
|
+
}
|
|
26
|
+
export function setManagedEntries(managedIndex, clientId, configPath, entries) {
|
|
27
|
+
if (!managedIndex.managed[clientId]) {
|
|
28
|
+
managedIndex.managed[clientId] = {
|
|
29
|
+
configPath,
|
|
30
|
+
entries: {}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
managedIndex.managed[clientId].configPath = configPath;
|
|
34
|
+
managedIndex.managed[clientId].entries = Object.fromEntries(Object.entries(entries).map(([entryName, serializedEntry]) => [entryName, {
|
|
35
|
+
fingerprint: sha256(serializedEntry),
|
|
36
|
+
lastSyncedAt: new Date().toISOString()
|
|
37
|
+
}]));
|
|
38
|
+
}
|
|
39
|
+
export function okResult(clientId, configPath, message) {
|
|
40
|
+
return {
|
|
41
|
+
clientId,
|
|
42
|
+
status: "SYNCED",
|
|
43
|
+
configPath,
|
|
44
|
+
message
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function unsupportedResult(clientId, message) {
|
|
48
|
+
return {
|
|
49
|
+
clientId,
|
|
50
|
+
status: "UNSUPPORTED_HTTP",
|
|
51
|
+
message
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function errorResult(clientId, configPath, message) {
|
|
55
|
+
return {
|
|
56
|
+
clientId,
|
|
57
|
+
status: "ERROR",
|
|
58
|
+
configPath,
|
|
59
|
+
message
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function skippedResult(clientId, message) {
|
|
63
|
+
return {
|
|
64
|
+
clientId,
|
|
65
|
+
status: "SKIPPED",
|
|
66
|
+
message
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/adapters/utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ClientAdapter, McpxConfig, SyncClientOptions, SyncResult } from "../types.js";
|
|
2
|
+
export declare class VsCodeAdapter implements ClientAdapter {
|
|
3
|
+
readonly id: "vscode";
|
|
4
|
+
detectConfigPath(): string | null;
|
|
5
|
+
supportsHttp(): boolean;
|
|
6
|
+
syncGateway(_config: McpxConfig, options: SyncClientOptions): SyncResult;
|
|
7
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { readJsonFile, writeJsonAtomic } from "../util/fs.js";
|
|
4
|
+
import { ensureManagedEntryWritable, errorResult, okResult, pruneStaleManagedEntries, setManagedEntries } from "./utils.js";
|
|
5
|
+
export class VsCodeAdapter {
|
|
6
|
+
id = "vscode";
|
|
7
|
+
detectConfigPath() {
|
|
8
|
+
return path.join(os.homedir(), "Library", "Application Support", "Code", "User", "mcp.json");
|
|
9
|
+
}
|
|
10
|
+
supportsHttp() {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
syncGateway(_config, options) {
|
|
14
|
+
const configPath = this.detectConfigPath();
|
|
15
|
+
if (!configPath) {
|
|
16
|
+
return errorResult(this.id, undefined, "Unable to resolve VS Code MCP config path.");
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const raw = readJsonFile(configPath, {});
|
|
20
|
+
const servers = {
|
|
21
|
+
...(raw.servers ?? {})
|
|
22
|
+
};
|
|
23
|
+
const managedNames = options.managedEntries.map((entry) => entry.name);
|
|
24
|
+
const serverEntries = Object.fromEntries(options.managedEntries.map((entry) => [entry.name, {
|
|
25
|
+
type: "http",
|
|
26
|
+
url: entry.url,
|
|
27
|
+
headers: entry.headers
|
|
28
|
+
}]));
|
|
29
|
+
for (const name of managedNames) {
|
|
30
|
+
const conflict = ensureManagedEntryWritable(options.managedIndex, this.id, name, servers[name]);
|
|
31
|
+
if (conflict) {
|
|
32
|
+
return errorResult(this.id, configPath, conflict);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
pruneStaleManagedEntries(options.managedIndex, this.id, servers, managedNames);
|
|
36
|
+
for (const [name, entry] of Object.entries(serverEntries)) {
|
|
37
|
+
servers[name] = entry;
|
|
38
|
+
}
|
|
39
|
+
const next = {
|
|
40
|
+
...raw,
|
|
41
|
+
servers
|
|
42
|
+
};
|
|
43
|
+
writeJsonAtomic(configPath, next);
|
|
44
|
+
setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(Object.entries(serverEntries).map(([name, entry]) => [name, JSON.stringify(entry)])));
|
|
45
|
+
return okResult(this.id, configPath);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
return errorResult(this.id, configPath, error.message);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=vscode.js.map
|