@crush-protocol/mcp-client 0.2.0 → 0.3.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 +127 -172
- package/dist/__tests__/e2e.test.js +2 -2
- package/dist/cli.js +28 -2
- package/dist/setup/setupClients.d.ts +3 -0
- package/dist/setup/setupClients.js +119 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,245 +1,200 @@
|
|
|
1
|
-
# Crush Protocol MCP
|
|
1
|
+
# Crush Protocol MCP Client
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@crush-protocol/mcp-client)
|
|
4
|
-
[](LICENSE)
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
`@crush-protocol/mcp-client` is the npm entrypoint for Crush Protocol MCP.
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
Crush Protocol is an AI-native quantitative trading product. It lets an MCP host connect to Crush and use trading-focused tools for:
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
- strategy research
|
|
10
|
+
- backtest creation and result retrieval
|
|
11
|
+
- live strategy management
|
|
12
|
+
- market data and signal discovery
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
- ❌ Copy-paste backtest configs and wait for results separately
|
|
14
|
-
- ❌ No way for your AI agent to iterate on strategies automatically
|
|
14
|
+
This package connects your MCP host to the Crush MCP server over Streamable HTTP and handles OAuth automatically.
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
Your AI agent can **create, run, and analyze backtests** in a single conversation:
|
|
16
|
+
Default MCP endpoint:
|
|
19
17
|
|
|
20
18
|
```txt
|
|
21
|
-
|
|
22
|
-
entry when RSI < 30, exit when RSI > 70. Use crush protocol mcp.
|
|
19
|
+
https://crush-mcp-ats.dev.xexlab.com/mcp
|
|
23
20
|
```
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
List my last 10 completed backtests and summarize the best performing strategy.
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
The AI agent calls the Crush Protocol MCP tools directly — no tab-switching, no manual data entry.
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## Authentication
|
|
34
|
-
|
|
35
|
-
Crush Protocol MCP uses **OAuth 2.1 Authorization Code + PKCE**.
|
|
36
|
-
|
|
37
|
-
For a standards-based MCP experience, configure only the MCP server URL. The client can complete browser authorization automatically and persist tokens locally.
|
|
38
|
-
|
|
39
|
-
This means the published CLI and the URL-only MCP setup now follow the same auth model:
|
|
40
|
-
|
|
41
|
-
- `npx -y @crush-protocol/mcp-client --url <mcp-url>` uses automatic OAuth on first use
|
|
42
|
-
- host integrations such as Claude Code / Cursor / Windsurf can also provide only the MCP URL
|
|
43
|
-
- both paths ultimately use the same OAuth access tokens against `/mcp`
|
|
44
|
-
|
|
45
|
-
Non-interactive OAuth overrides are still available for scripts and already-published client workflows:
|
|
46
|
-
|
|
47
|
-
1. `crush-mcp-client login --url <mcp-url>` to pre-authorize locally
|
|
48
|
-
2. `--token <access-token>` for debugging
|
|
49
|
-
3. `CRUSH_OAUTH_ACCESS_TOKEN=<access-token>` for non-interactive automation
|
|
50
|
-
|
|
51
|
-
---
|
|
52
|
-
|
|
53
|
-
## CLI Usage
|
|
54
|
-
|
|
55
|
-
The CLI is now an OAuth-aware MCP client, not a separate legacy auth path.
|
|
56
|
-
|
|
57
|
-
### Common commands
|
|
22
|
+
## Standard MCP Host Integration
|
|
58
23
|
|
|
59
|
-
|
|
60
|
-
# Show help
|
|
61
|
-
npx -y @crush-protocol/mcp-client help
|
|
62
|
-
|
|
63
|
-
# Pre-authorize locally (optional)
|
|
64
|
-
npx -y @crush-protocol/mcp-client login --url https://mcp.crush-protocol.com/mcp
|
|
65
|
-
|
|
66
|
-
# List tools
|
|
67
|
-
npx -y @crush-protocol/mcp-client tools:list --url https://mcp.crush-protocol.com/mcp
|
|
68
|
-
|
|
69
|
-
# Ping MCP server
|
|
70
|
-
npx -y @crush-protocol/mcp-client ping --url https://mcp.crush-protocol.com/mcp
|
|
24
|
+
This is the recommended way to use the package.
|
|
71
25
|
|
|
72
|
-
|
|
73
|
-
npx -y @crush-protocol/mcp-client tool:call \
|
|
74
|
-
--url https://mcp.crush-protocol.com/mcp \
|
|
75
|
-
--name get_backtest_config_schema
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### Backtest commands
|
|
79
|
-
|
|
80
|
-
```sh
|
|
81
|
-
# Read backtest schema
|
|
82
|
-
npx -y @crush-protocol/mcp-client backtest:schema --url https://mcp.crush-protocol.com/mcp
|
|
26
|
+
Your MCP host launches `@crush-protocol/mcp-client` locally, and the package connects to the hosted Crush MCP endpoint.
|
|
83
27
|
|
|
84
|
-
|
|
85
|
-
npx -y @crush-protocol/mcp-client backtest:tokens --url https://mcp.crush-protocol.com/mcp
|
|
28
|
+
### Cursor
|
|
86
29
|
|
|
87
|
-
|
|
88
|
-
npx -y @crush-protocol/mcp-client backtest:list --url https://mcp.crush-protocol.com/mcp --limit 10
|
|
89
|
-
```
|
|
30
|
+
Official one-click install:
|
|
90
31
|
|
|
91
|
-
|
|
32
|
+
[](cursor://anysphere.cursor-deeplink/mcp/install?name=crush-protocol&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBjcnVzaC1wcm90b2NvbC9tY3AtY2xpZW50IiwiLS11cmwiLCJodHRwczovL2NydXNoLW1jcC1hdHMuZGV2LnhleGxhYi5jb20vbWNwIl19)
|
|
92
33
|
|
|
93
|
-
|
|
94
|
-
# Recommended: URL only, browser OAuth happens automatically if needed
|
|
95
|
-
npx -y @crush-protocol/mcp-client tools:list --url https://mcp.crush-protocol.com/mcp
|
|
34
|
+
Manual config:
|
|
96
35
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"mcpServers": {
|
|
39
|
+
"crush-protocol": {
|
|
40
|
+
"command": "npx",
|
|
41
|
+
"args": ["-y", "@crush-protocol/mcp-client", "--url", "https://crush-mcp-ats.dev.xexlab.com/mcp"]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
101
45
|
```
|
|
102
46
|
|
|
103
|
-
Behavior priority is:
|
|
104
|
-
|
|
105
|
-
1. `--token`
|
|
106
|
-
2. `CRUSH_OAUTH_ACCESS_TOKEN`
|
|
107
|
-
3. automatic OAuth with local token cache
|
|
108
|
-
|
|
109
|
-
So the CLI remains compatible with script usage, but the default interactive path is the same URL-only OAuth flow used by MCP hosts.
|
|
110
|
-
|
|
111
|
-
---
|
|
112
|
-
|
|
113
|
-
## Installation
|
|
114
|
-
|
|
115
47
|
### Claude Code
|
|
116
48
|
|
|
117
49
|
```sh
|
|
118
50
|
claude mcp add --scope user crush-protocol \
|
|
119
51
|
-- npx -y @crush-protocol/mcp-client \
|
|
120
|
-
--url https://
|
|
52
|
+
--url https://crush-mcp-ats.dev.xexlab.com/mcp
|
|
121
53
|
```
|
|
122
54
|
|
|
123
|
-
|
|
55
|
+
### Codex
|
|
124
56
|
|
|
125
|
-
|
|
57
|
+
Add to `~/.codex/config.toml`:
|
|
126
58
|
|
|
127
|
-
|
|
59
|
+
```toml
|
|
60
|
+
[mcp_servers.crush-protocol]
|
|
61
|
+
command = "npx"
|
|
62
|
+
args = ["-y", "@crush-protocol/mcp-client", "--url", "https://crush-mcp-ats.dev.xexlab.com/mcp"]
|
|
63
|
+
startup_timeout_ms = 20000
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Gemini CLI
|
|
67
|
+
|
|
68
|
+
Add to `~/.gemini/settings.json`:
|
|
128
69
|
|
|
129
70
|
```json
|
|
130
71
|
{
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
"env": {
|
|
136
|
-
"CRUSH_MCP_SERVER_URL": "https://mcp.crush-protocol.com/mcp"
|
|
137
|
-
}
|
|
138
|
-
}
|
|
72
|
+
"mcpServers": {
|
|
73
|
+
"crush-protocol": {
|
|
74
|
+
"command": "npx",
|
|
75
|
+
"args": ["-y", "@crush-protocol/mcp-client", "--url", "https://crush-mcp-ats.dev.xexlab.com/mcp"]
|
|
139
76
|
}
|
|
77
|
+
}
|
|
140
78
|
}
|
|
141
79
|
```
|
|
142
80
|
|
|
143
|
-
###
|
|
81
|
+
### OpenCode
|
|
144
82
|
|
|
145
|
-
Add to
|
|
83
|
+
Add to `~/.config/opencode/opencode.json`:
|
|
146
84
|
|
|
147
85
|
```json
|
|
148
86
|
{
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
}
|
|
87
|
+
"$schema": "https://opencode.ai/config.json",
|
|
88
|
+
"mcp": {
|
|
89
|
+
"crush-protocol": {
|
|
90
|
+
"type": "local",
|
|
91
|
+
"command": ["npx", "-y", "@crush-protocol/mcp-client", "--url", "https://crush-mcp-ats.dev.xexlab.com/mcp"],
|
|
92
|
+
"enabled": true
|
|
157
93
|
}
|
|
94
|
+
}
|
|
158
95
|
}
|
|
159
96
|
```
|
|
160
97
|
|
|
161
|
-
|
|
98
|
+
## Quick Setup Helper
|
|
162
99
|
|
|
163
|
-
|
|
100
|
+
If you do not want to edit config files manually, the CLI can write the MCP configuration for you.
|
|
164
101
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
2. Let the client attempt `/mcp`
|
|
169
|
-
3. When the server returns `401` with `WWW-Authenticate`, start OAuth discovery
|
|
170
|
-
4. Complete Authorization Code + PKCE in the browser
|
|
171
|
-
5. Persist tokens locally and reconnect
|
|
102
|
+
```sh
|
|
103
|
+
npx -y @crush-protocol/mcp-client setup --all
|
|
104
|
+
```
|
|
172
105
|
|
|
173
|
-
|
|
106
|
+
Single target setup:
|
|
174
107
|
|
|
175
108
|
```sh
|
|
176
|
-
crush-mcp-client
|
|
109
|
+
npx -y @crush-protocol/mcp-client setup --cursor
|
|
110
|
+
npx -y @crush-protocol/mcp-client setup --claude
|
|
111
|
+
npx -y @crush-protocol/mcp-client setup --codex
|
|
112
|
+
npx -y @crush-protocol/mcp-client setup --gemini
|
|
113
|
+
npx -y @crush-protocol/mcp-client setup --opencode
|
|
177
114
|
```
|
|
178
115
|
|
|
179
|
-
|
|
116
|
+
## What Users Get
|
|
117
|
+
|
|
118
|
+
With this package, an MCP client can call Crush tools such as:
|
|
180
119
|
|
|
181
|
-
|
|
120
|
+
- `get_backtest_config_schema`
|
|
121
|
+
- `get_available_tokens`
|
|
122
|
+
- `create_backtest`
|
|
123
|
+
- `get_backtest_result`
|
|
124
|
+
- `list_backtests`
|
|
125
|
+
- `create_strategy`
|
|
126
|
+
- `list_strategies`
|
|
127
|
+
- `toggle_strategy`
|
|
128
|
+
- `get_strategy_logs`
|
|
182
129
|
|
|
183
|
-
|
|
130
|
+
Detailed tool guidance is in [INSTRUCTIONS.md](./INSTRUCTIONS.md).
|
|
184
131
|
|
|
185
|
-
|
|
186
|
-
2. The MCP server returns OAuth discovery hints when auth is required
|
|
187
|
-
3. The client dynamically registers, runs Authorization Code + PKCE, and persists credentials locally
|
|
188
|
-
4. Authorization codes, access tokens, and refresh tokens are all managed server-side under the OAuth data model
|
|
132
|
+
## Authentication
|
|
189
133
|
|
|
190
|
-
|
|
134
|
+
Crush MCP uses OAuth 2.1 Authorization Code + PKCE.
|
|
191
135
|
|
|
192
|
-
|
|
136
|
+
Default behavior:
|
|
193
137
|
|
|
194
|
-
|
|
138
|
+
- you provide only the MCP server URL
|
|
139
|
+
- the client completes browser OAuth when needed
|
|
140
|
+
- tokens are stored locally for reuse
|
|
195
141
|
|
|
196
|
-
|
|
197
|
-
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
198
|
-
| **Signal Discovery** | `get_signal_metadata`, `get_signals_by_category` |
|
|
199
|
-
| **Backtest** | `get_backtest_config_schema`, `get_available_tokens`, `validate_expression`, `create_backtest`, `list_backtests` |
|
|
200
|
-
| **Market Data** | `list_tables`, `list_tokens`, `list_indicators`, `list_timeframes`, `get_data_range`, `check_query_size`, `fetch_ohlcv`, `fetch_indicator`, `get_connection_config` |
|
|
201
|
-
| **Custom Indicators** | `save_custom_indicator`, `list_custom_indicators`, `get_custom_indicator`, `delete_custom_indicator` |
|
|
202
|
-
| **Strategy Management** | `create_strategy`, `list_strategies`, `get_strategy`, `update_strategy`, `delete_strategy`, `toggle_strategy`, `get_strategy_logs` |
|
|
203
|
-
| **Trading** | `place_order`, `get_positions`, `get_account_info`, `get_portfolio` |
|
|
204
|
-
| **Market Intelligence** | `search_tokens`, `get_token_info`, `get_trending_tokens`, `get_alpha_feed`, `get_token_feed`, `fetch_news` |
|
|
142
|
+
Optional token overrides are also supported:
|
|
205
143
|
|
|
206
|
-
|
|
144
|
+
- `--token <access-token>`
|
|
145
|
+
- `CRUSH_OAUTH_ACCESS_TOKEN=<access-token>`
|
|
207
146
|
|
|
208
|
-
|
|
147
|
+
Priority order:
|
|
209
148
|
|
|
210
|
-
|
|
149
|
+
1. `--token`
|
|
150
|
+
2. `CRUSH_OAUTH_ACCESS_TOKEN`
|
|
151
|
+
3. automatic OAuth with local token cache
|
|
211
152
|
|
|
212
|
-
|
|
213
|
-
| --------------------------- | --------------------------------------------------------------- |
|
|
214
|
-
| `CRUSH_MCP_SERVER_URL` | MCP server URL (default: `https://mcp.crush-protocol.com/mcp`) |
|
|
215
|
-
| `CRUSH_OAUTH_ACCESS_TOKEN` | Optional override for a pre-issued OAuth access token |
|
|
153
|
+
Optional pre-login:
|
|
216
154
|
|
|
217
|
-
|
|
155
|
+
```sh
|
|
156
|
+
npx -y @crush-protocol/mcp-client login --url https://crush-mcp-ats.dev.xexlab.com/mcp
|
|
157
|
+
```
|
|
218
158
|
|
|
219
|
-
## SDK Usage
|
|
159
|
+
## CLI / SDK Usage
|
|
220
160
|
|
|
221
|
-
|
|
161
|
+
Use this when you want to use Crush from Node.js directly.
|
|
222
162
|
|
|
223
|
-
|
|
224
|
-
|
|
163
|
+
CLI examples:
|
|
164
|
+
|
|
165
|
+
```sh
|
|
166
|
+
npx -y @crush-protocol/mcp-client help
|
|
167
|
+
npx -y @crush-protocol/mcp-client tools:list --url https://crush-mcp-ats.dev.xexlab.com/mcp
|
|
168
|
+
npx -y @crush-protocol/mcp-client ping --url https://crush-mcp-ats.dev.xexlab.com/mcp
|
|
169
|
+
npx -y @crush-protocol/mcp-client backtest:schema --url https://crush-mcp-ats.dev.xexlab.com/mcp
|
|
170
|
+
npx -y @crush-protocol/mcp-client backtest:list --url https://crush-mcp-ats.dev.xexlab.com/mcp --limit 10
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
SDK example:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
import { OAuthRemoteMcpClient, BacktestClient } from "@crush-protocol/mcp-client";
|
|
225
177
|
|
|
226
178
|
const mcp = new OAuthRemoteMcpClient({
|
|
227
|
-
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
await mcp.close()
|
|
179
|
+
serverUrl: "https://crush-mcp-ats.dev.xexlab.com/mcp",
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await mcp.connect();
|
|
183
|
+
|
|
184
|
+
const backtests = new BacktestClient(mcp);
|
|
185
|
+
const result = await backtests.list({ limit: 10 });
|
|
186
|
+
|
|
187
|
+
console.log(result);
|
|
188
|
+
|
|
189
|
+
await mcp.close();
|
|
240
190
|
```
|
|
241
191
|
|
|
242
|
-
|
|
192
|
+
## Environment Variables
|
|
193
|
+
|
|
194
|
+
| Variable | Description |
|
|
195
|
+
| --- | --- |
|
|
196
|
+
| `CRUSH_MCP_SERVER_URL` | MCP server URL. Default: `https://crush-mcp-ats.dev.xexlab.com/mcp` |
|
|
197
|
+
| `CRUSH_OAUTH_ACCESS_TOKEN` | Optional OAuth access token override |
|
|
243
198
|
|
|
244
199
|
## License
|
|
245
200
|
|
|
@@ -5,10 +5,10 @@ import { OAuthRemoteMcpClient } from "../mcp/oauthRemoteClient.js";
|
|
|
5
5
|
* E2E 测试:验证 MCP client 能成功连接服务端并调用工具。
|
|
6
6
|
*
|
|
7
7
|
* 前置条件(通过环境变量或 .env.e2e 配置):
|
|
8
|
-
* CRUSH_MCP_SERVER_URL — 指向运行中的 MCP server(默认
|
|
8
|
+
* CRUSH_MCP_SERVER_URL — 指向运行中的 MCP server(默认 https://crush-mcp-ats.dev.xexlab.com/mcp)
|
|
9
9
|
* CRUSH_OAUTH_ACCESS_TOKEN — 有效的 OAuth access token
|
|
10
10
|
*/
|
|
11
|
-
const serverUrl = process.env.CRUSH_MCP_SERVER_URL ?? "
|
|
11
|
+
const serverUrl = process.env.CRUSH_MCP_SERVER_URL ?? "https://crush-mcp-ats.dev.xexlab.com/mcp";
|
|
12
12
|
const token = process.env.CRUSH_OAUTH_ACCESS_TOKEN ?? "";
|
|
13
13
|
// 没有 token 时跳过所有 e2e 测试
|
|
14
14
|
const describeE2E = token ? describe : describe.skip;
|
package/dist/cli.js
CHANGED
|
@@ -4,8 +4,10 @@ import { BacktestClient } from "./backtest/backtestClient.js";
|
|
|
4
4
|
import { ClickHouseDirectClient } from "./clickhouse/directClient.js";
|
|
5
5
|
import { OAuthRemoteMcpClient } from "./mcp/oauthRemoteClient.js";
|
|
6
6
|
import { RemoteMcpClient } from "./mcp/remoteClient.js";
|
|
7
|
+
import { installClientConfig } from "./setup/setupClients.js";
|
|
8
|
+
const DEFAULT_MCP_SERVER_URL = "https://crush-mcp-ats.dev.xexlab.com/mcp";
|
|
7
9
|
const printUsage = () => {
|
|
8
|
-
console.log(`\ncrush-mcp-client\n\nGeneral:\n login [--url URL]\n tools:list [--url URL] [--token TOKEN]\n tool:call --name TOOL_NAME [--args JSON] [--url URL] [--token TOKEN]\n ping [--url URL] [--token TOKEN]\n\nBacktest:\n backtest:schema [--url URL] [--token TOKEN]\n backtest:tokens [--platform PLATFORM] [--url URL] [--token TOKEN]\n backtest:validate --expression JSON [--data-source kline|factors] [--url URL] [--token TOKEN]\n backtest:create --config JSON [--backtest-id ID] [--url URL] [--token TOKEN]\n backtest:list [--status STATUS] [--limit N] [--offset N] [--url URL] [--token TOKEN]\n\nClickHouse:\n clickhouse:list-tables [--ch-host HOST --ch-port PORT --ch-user USER --ch-password PASS --ch-database DB]\n clickhouse:query --sql SQL [--ch-host HOST --ch-port PORT --ch-user USER --ch-password PASS --ch-database DB --ch-row-cap N]\n\nAuth:\n --token TOKEN
|
|
10
|
+
console.log(`\ncrush-mcp-client\n\nGeneral:\n login [--url URL]\n setup [--cursor] [--claude] [--codex] [--gemini] [--opencode] [--all] [--scope user|project] [--url URL]\n tools:list [--url URL] [--token TOKEN]\n tool:call --name TOOL_NAME [--args JSON] [--url URL] [--token TOKEN]\n ping [--url URL] [--token TOKEN]\n\nBacktest:\n backtest:schema [--url URL] [--token TOKEN]\n backtest:tokens [--platform PLATFORM] [--url URL] [--token TOKEN]\n backtest:validate --expression JSON [--data-source kline|factors] [--url URL] [--token TOKEN]\n backtest:create --config JSON [--backtest-id ID] [--url URL] [--token TOKEN]\n backtest:list [--status STATUS] [--limit N] [--offset N] [--url URL] [--token TOKEN]\n\nClickHouse:\n clickhouse:list-tables [--ch-host HOST --ch-port PORT --ch-user USER --ch-password PASS --ch-database DB]\n clickhouse:query --sql SQL [--ch-host HOST --ch-port PORT --ch-user USER --ch-password PASS --ch-database DB --ch-row-cap N]\n\nAuth:\n --token TOKEN uses a provided OAuth access token.\n Without --token, OAuth runs automatically in the browser when needed.\n\nEnv:\n CRUSH_MCP_SERVER_URL, CRUSH_OAUTH_ACCESS_TOKEN\n CH_HOST, CH_PORT, CH_USER, CH_PASSWORD, CH_DATABASE, CH_ROW_CAP\n`);
|
|
9
11
|
};
|
|
10
12
|
const parseFlags = (args) => {
|
|
11
13
|
const flags = {};
|
|
@@ -32,7 +34,14 @@ const requireString = (value, message) => {
|
|
|
32
34
|
};
|
|
33
35
|
const getServerUrl = (flags) => typeof flags.url === "string"
|
|
34
36
|
? flags.url
|
|
35
|
-
: (process.env.CRUSH_MCP_SERVER_URL ??
|
|
37
|
+
: (process.env.CRUSH_MCP_SERVER_URL ?? DEFAULT_MCP_SERVER_URL);
|
|
38
|
+
const getSetupTargets = (flags) => {
|
|
39
|
+
const explicitTargets = ["cursor", "claude", "codex", "gemini", "opencode"].filter((target) => flags[target] === true);
|
|
40
|
+
if (flags.all === true) {
|
|
41
|
+
return ["cursor", "claude", "codex", "gemini", "opencode"];
|
|
42
|
+
}
|
|
43
|
+
return explicitTargets;
|
|
44
|
+
};
|
|
36
45
|
/**
|
|
37
46
|
* 创建 MCP 客户端(统一认证入口)
|
|
38
47
|
*
|
|
@@ -118,6 +127,23 @@ const run = async () => {
|
|
|
118
127
|
}
|
|
119
128
|
return;
|
|
120
129
|
}
|
|
130
|
+
case "setup": {
|
|
131
|
+
const targets = getSetupTargets(flags);
|
|
132
|
+
if (targets.length === 0) {
|
|
133
|
+
throw new Error("Specify at least one setup target: --cursor, --claude, --codex, --gemini, --opencode, or --all");
|
|
134
|
+
}
|
|
135
|
+
const rawScope = typeof flags.scope === "string" ? flags.scope : "user";
|
|
136
|
+
if (rawScope !== "user" && rawScope !== "project") {
|
|
137
|
+
throw new Error("Invalid --scope. Expected 'user' or 'project'.");
|
|
138
|
+
}
|
|
139
|
+
const scope = rawScope;
|
|
140
|
+
const serverUrl = getServerUrl(flags);
|
|
141
|
+
for (const target of targets) {
|
|
142
|
+
const location = installClientConfig(target, serverUrl, scope);
|
|
143
|
+
console.log(`[setup] ${target}: configured (${location})`);
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
121
147
|
case "tools:list": {
|
|
122
148
|
const client = await connectClient(flags);
|
|
123
149
|
try {
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
const SERVER_NAME = "crush-protocol";
|
|
6
|
+
const PACKAGE_NAME = "@crush-protocol/mcp-client";
|
|
7
|
+
const ensureDir = (filePath) => {
|
|
8
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
9
|
+
};
|
|
10
|
+
const readJson = (filePath) => {
|
|
11
|
+
if (!fs.existsSync(filePath))
|
|
12
|
+
return {};
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
throw new Error(`Failed to parse JSON config at ${filePath}: ${error.message}`);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const writeJson = (filePath, value) => {
|
|
21
|
+
ensureDir(filePath);
|
|
22
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
23
|
+
};
|
|
24
|
+
const createNpxConfig = (serverUrl) => ({
|
|
25
|
+
command: "npx",
|
|
26
|
+
args: ["-y", PACKAGE_NAME, "--url", serverUrl],
|
|
27
|
+
});
|
|
28
|
+
const getCursorConfigPath = (scope) => scope === "project"
|
|
29
|
+
? path.join(process.cwd(), ".cursor", "mcp.json")
|
|
30
|
+
: path.join(os.homedir(), ".cursor", "mcp.json");
|
|
31
|
+
const getGeminiConfigPath = () => path.join(os.homedir(), ".gemini", "settings.json");
|
|
32
|
+
const getOpenCodeConfigPath = (scope) => scope === "project"
|
|
33
|
+
? path.join(process.cwd(), "opencode.json")
|
|
34
|
+
: path.join(os.homedir(), ".config", "opencode", "opencode.json");
|
|
35
|
+
const getCodexConfigPath = () => path.join(os.homedir(), ".codex", "config.toml");
|
|
36
|
+
const installCursor = (serverUrl, scope) => {
|
|
37
|
+
const filePath = getCursorConfigPath(scope);
|
|
38
|
+
const config = readJson(filePath);
|
|
39
|
+
const mcpServers = (config.mcpServers ?? {});
|
|
40
|
+
mcpServers[SERVER_NAME] = createNpxConfig(serverUrl);
|
|
41
|
+
config.mcpServers = mcpServers;
|
|
42
|
+
writeJson(filePath, config);
|
|
43
|
+
return filePath;
|
|
44
|
+
};
|
|
45
|
+
const installGemini = (serverUrl) => {
|
|
46
|
+
const filePath = getGeminiConfigPath();
|
|
47
|
+
const config = readJson(filePath);
|
|
48
|
+
const mcpServers = (config.mcpServers ?? {});
|
|
49
|
+
mcpServers[SERVER_NAME] = createNpxConfig(serverUrl);
|
|
50
|
+
config.mcpServers = mcpServers;
|
|
51
|
+
writeJson(filePath, config);
|
|
52
|
+
return filePath;
|
|
53
|
+
};
|
|
54
|
+
const installOpenCode = (serverUrl, scope) => {
|
|
55
|
+
const filePath = getOpenCodeConfigPath(scope);
|
|
56
|
+
const config = readJson(filePath);
|
|
57
|
+
const mcp = (config.mcp ?? {});
|
|
58
|
+
if (typeof config.$schema !== "string") {
|
|
59
|
+
config.$schema = "https://opencode.ai/config.json";
|
|
60
|
+
}
|
|
61
|
+
mcp[SERVER_NAME] = {
|
|
62
|
+
type: "local",
|
|
63
|
+
command: ["npx", "-y", PACKAGE_NAME, "--url", serverUrl],
|
|
64
|
+
enabled: true,
|
|
65
|
+
};
|
|
66
|
+
config.mcp = mcp;
|
|
67
|
+
writeJson(filePath, config);
|
|
68
|
+
return filePath;
|
|
69
|
+
};
|
|
70
|
+
const installCodex = (serverUrl) => {
|
|
71
|
+
const filePath = getCodexConfigPath();
|
|
72
|
+
const section = `[mcp_servers.${SERVER_NAME}]
|
|
73
|
+
command = "npx"
|
|
74
|
+
args = ["-y", "${PACKAGE_NAME}", "--url", "${serverUrl}"]
|
|
75
|
+
startup_timeout_ms = 20000
|
|
76
|
+
`;
|
|
77
|
+
ensureDir(filePath);
|
|
78
|
+
const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : "";
|
|
79
|
+
if (!existing.includes(`[mcp_servers.${SERVER_NAME}]`)) {
|
|
80
|
+
const next = existing.trim().length > 0 ? `${existing.trimEnd()}\n\n${section}` : section;
|
|
81
|
+
fs.writeFileSync(filePath, next, "utf8");
|
|
82
|
+
}
|
|
83
|
+
return filePath;
|
|
84
|
+
};
|
|
85
|
+
const installClaude = (serverUrl, scope) => {
|
|
86
|
+
const configJson = JSON.stringify({
|
|
87
|
+
type: "stdio",
|
|
88
|
+
command: "npx",
|
|
89
|
+
args: ["-y", PACKAGE_NAME, "--url", serverUrl],
|
|
90
|
+
});
|
|
91
|
+
const result = spawnSync("claude", ["mcp", "add-json", "--scope", scope, SERVER_NAME, configJson], {
|
|
92
|
+
stdio: "pipe",
|
|
93
|
+
encoding: "utf8",
|
|
94
|
+
});
|
|
95
|
+
if (result.error) {
|
|
96
|
+
throw new Error(`Failed to run Claude CLI: ${result.error.message}`);
|
|
97
|
+
}
|
|
98
|
+
if (result.status !== 0) {
|
|
99
|
+
const output = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
100
|
+
throw new Error(output || "Claude CLI returned a non-zero exit code.");
|
|
101
|
+
}
|
|
102
|
+
return "claude-managed-config";
|
|
103
|
+
};
|
|
104
|
+
export const installClientConfig = (target, serverUrl, scope) => {
|
|
105
|
+
switch (target) {
|
|
106
|
+
case "cursor":
|
|
107
|
+
return installCursor(serverUrl, scope);
|
|
108
|
+
case "claude":
|
|
109
|
+
return installClaude(serverUrl, scope);
|
|
110
|
+
case "codex":
|
|
111
|
+
return installCodex(serverUrl);
|
|
112
|
+
case "gemini":
|
|
113
|
+
return installGemini(serverUrl);
|
|
114
|
+
case "opencode":
|
|
115
|
+
return installOpenCode(serverUrl, scope);
|
|
116
|
+
default:
|
|
117
|
+
throw new Error(`Unsupported setup target: ${target}`);
|
|
118
|
+
}
|
|
119
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crush-protocol/mcp-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Crush MCP npm client package (remote Streamable HTTP + optional ClickHouse direct)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
27
27
|
"dotenv": "^17.2.1",
|
|
28
28
|
"zod": "^3.25.76",
|
|
29
|
-
"@crush-protocol/mcp-contracts": "0.1.
|
|
29
|
+
"@crush-protocol/mcp-contracts": "0.1.2"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^24.3.0",
|