@crush-protocol/mcp-client 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 +110 -0
- package/dist/backtest/backtestClient.d.ts +114 -0
- package/dist/backtest/backtestClient.js +77 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +268 -0
- package/dist/clickhouse/directClient.d.ts +21 -0
- package/dist/clickhouse/directClient.js +86 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/mcp/remoteClient.d.ts +160 -0
- package/dist/mcp/remoteClient.js +45 -0
- package/package.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Edwin Hernandez
|
|
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,110 @@
|
|
|
1
|
+
# @crush-protocol/mcp-client
|
|
2
|
+
|
|
3
|
+
Crush MCP npm 客户端 — CLI + SDK。
|
|
4
|
+
|
|
5
|
+
提供三种能力:
|
|
6
|
+
|
|
7
|
+
1. **Remote MCP**(默认):通过 Streamable HTTP 访问远程 `/mcp`,强制 `Bearer mcp_xxx` 鉴权。
|
|
8
|
+
2. **Backtest SDK**:类型化的回测工具封装(`client.backtest.*`),基于 Remote MCP。
|
|
9
|
+
3. **ClickHouse direct**(可选):客户端只读直连 ClickHouse(仅 SELECT + 行数上限)。
|
|
10
|
+
|
|
11
|
+
## 安装
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @crush-protocol/mcp-client
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## SDK 使用
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { RemoteMcpClient, BacktestClient } from "@crush-protocol/mcp-client";
|
|
21
|
+
|
|
22
|
+
const mcp = new RemoteMcpClient({
|
|
23
|
+
serverUrl: "https://your-host/mcp",
|
|
24
|
+
token: "mcp_xxx",
|
|
25
|
+
});
|
|
26
|
+
await mcp.connect();
|
|
27
|
+
|
|
28
|
+
const backtest = new BacktestClient(mcp);
|
|
29
|
+
|
|
30
|
+
// 获取回测配置 schema
|
|
31
|
+
const schema = await backtest.getConfigSchema();
|
|
32
|
+
|
|
33
|
+
// 获取可用交易对
|
|
34
|
+
const { tokens } = await backtest.getAvailableTokens({ platform: "hyperliquid_perps" });
|
|
35
|
+
|
|
36
|
+
// 验证表达式
|
|
37
|
+
const validation = await backtest.validateExpression({
|
|
38
|
+
expression: { type: "comparison", field: "close", operator: ">", value: 100 },
|
|
39
|
+
dataSource: "kline",
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// 创建回测
|
|
43
|
+
const bt = await backtest.createBacktest({
|
|
44
|
+
config: {
|
|
45
|
+
token: { symbol: "ETHUSDT" },
|
|
46
|
+
platform: "hyperliquid_perps",
|
|
47
|
+
timeframe: "240",
|
|
48
|
+
entry: { /* ... */ },
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// 获取回测结果
|
|
53
|
+
const result = await backtest.getResult({ backtestId: bt.backtestId });
|
|
54
|
+
|
|
55
|
+
// 列出回测
|
|
56
|
+
const list = await backtest.list({ status: "COMPLETED", limit: 10 });
|
|
57
|
+
|
|
58
|
+
await mcp.close();
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## CLI 使用
|
|
62
|
+
|
|
63
|
+
### General
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
crush-mcp-client tools:list --url https://your-host/mcp --token mcp_xxx
|
|
67
|
+
crush-mcp-client tool:call --name list_tables --args '{}' --token mcp_xxx
|
|
68
|
+
crush-mcp-client ping --token mcp_xxx
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Backtest
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
crush-mcp-client backtest:schema --token mcp_xxx
|
|
75
|
+
crush-mcp-client backtest:tokens --platform hyperliquid_perps --token mcp_xxx
|
|
76
|
+
crush-mcp-client backtest:validate --expression '{"type":"comparison","field":"close","operator":">","value":100}' --token mcp_xxx
|
|
77
|
+
crush-mcp-client backtest:create --config '{"token":{"symbol":"ETHUSDT"},"platform":"hyperliquid_perps","timeframe":"240","entry":{}}' --token mcp_xxx
|
|
78
|
+
crush-mcp-client backtest:get --backtest-id 665a1b2c3d4e5f6a7b8c9d0e --token mcp_xxx
|
|
79
|
+
crush-mcp-client backtest:list --status COMPLETED --limit 10 --token mcp_xxx
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### ClickHouse 直连(只读)
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
crush-mcp-client clickhouse:list-tables --ch-host localhost --ch-port 8123 --ch-user default --ch-password '' --ch-database crush_ats
|
|
86
|
+
crush-mcp-client clickhouse:query --sql 'SELECT * FROM system.tables LIMIT 10' --ch-database system
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## 环境变量
|
|
90
|
+
|
|
91
|
+
可复制 `.env.example` 并设置:
|
|
92
|
+
|
|
93
|
+
- `CRUSH_MCP_SERVER_URL` — 远程 MCP 服务地址(默认 `http://localhost:8080/mcp`)
|
|
94
|
+
- `CRUSH_MCP_TOKEN` — MCP 鉴权 token(`mcp_xxx`)
|
|
95
|
+
- `CH_HOST` / `CH_PORT` / `CH_USER` / `CH_PASSWORD` / `CH_DATABASE` — ClickHouse 连接
|
|
96
|
+
- `CH_ROW_CAP`(可选,默认 5000)
|
|
97
|
+
|
|
98
|
+
## 安全说明
|
|
99
|
+
|
|
100
|
+
1. 远程 MCP 模式要求 token 形如 `mcp_xxx`,通过 `Authorization: Bearer ...` 发送。
|
|
101
|
+
2. Token 缺失或格式错误时直接失败。
|
|
102
|
+
3. ClickHouse 直连模式仅允许 `SELECT`,强制 `readonly=1`,结果有行数上限。
|
|
103
|
+
|
|
104
|
+
## 本地开发
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
pnpm install
|
|
108
|
+
pnpm build
|
|
109
|
+
pnpm dev -- help
|
|
110
|
+
```
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { type BacktestStatus, type Platform } from "@crush-protocol/mcp-contracts";
|
|
2
|
+
import type { RemoteMcpClient } from "../mcp/remoteClient.js";
|
|
3
|
+
export type GetAvailableTokensInput = {
|
|
4
|
+
platform?: Platform;
|
|
5
|
+
};
|
|
6
|
+
export type ValidateExpressionInput = {
|
|
7
|
+
expression: Record<string, unknown>;
|
|
8
|
+
dataSource?: "kline" | "factors";
|
|
9
|
+
};
|
|
10
|
+
export type CreateBacktestInput = {
|
|
11
|
+
config: Record<string, unknown>;
|
|
12
|
+
backtestId?: string;
|
|
13
|
+
};
|
|
14
|
+
export type GetBacktestResultInput = {
|
|
15
|
+
backtestId: string;
|
|
16
|
+
};
|
|
17
|
+
export type ListBacktestsInput = {
|
|
18
|
+
status?: BacktestStatus;
|
|
19
|
+
limit?: number;
|
|
20
|
+
offset?: number;
|
|
21
|
+
};
|
|
22
|
+
export type BacktestConfigSchema = {
|
|
23
|
+
platforms: string[];
|
|
24
|
+
timeframes: {
|
|
25
|
+
value: string;
|
|
26
|
+
label: string;
|
|
27
|
+
}[];
|
|
28
|
+
directions: string[];
|
|
29
|
+
positionSizeTypes: string[];
|
|
30
|
+
exitStrategyTypes: {
|
|
31
|
+
type: string;
|
|
32
|
+
label: string;
|
|
33
|
+
description: string;
|
|
34
|
+
distanceTypes: string[];
|
|
35
|
+
}[];
|
|
36
|
+
leverageRange: {
|
|
37
|
+
min: number;
|
|
38
|
+
max: number;
|
|
39
|
+
};
|
|
40
|
+
dateRange: {
|
|
41
|
+
min: string;
|
|
42
|
+
};
|
|
43
|
+
expressionLimits: {
|
|
44
|
+
maxDepth: number;
|
|
45
|
+
maxNodes: number;
|
|
46
|
+
};
|
|
47
|
+
context: {
|
|
48
|
+
privyId: string;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
export type TokenInfo = {
|
|
52
|
+
symbol: string;
|
|
53
|
+
name: string;
|
|
54
|
+
platform: string;
|
|
55
|
+
};
|
|
56
|
+
export type ValidationResult = {
|
|
57
|
+
valid: boolean;
|
|
58
|
+
error?: string;
|
|
59
|
+
compiledExpression?: string;
|
|
60
|
+
};
|
|
61
|
+
export type BacktestRecord = {
|
|
62
|
+
backtestId: string;
|
|
63
|
+
status: BacktestStatus;
|
|
64
|
+
config: Record<string, unknown>;
|
|
65
|
+
error?: string | null;
|
|
66
|
+
createdAt?: string;
|
|
67
|
+
updatedAt?: string;
|
|
68
|
+
summary?: Record<string, unknown> | null;
|
|
69
|
+
portfolios?: unknown[];
|
|
70
|
+
trades?: unknown[];
|
|
71
|
+
};
|
|
72
|
+
export type ListBacktestsResult = {
|
|
73
|
+
backtests: BacktestRecord[];
|
|
74
|
+
total: number;
|
|
75
|
+
limit: number;
|
|
76
|
+
offset: number;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Typed SDK wrapper for the 6 backtest MCP tools.
|
|
80
|
+
* Requires a connected RemoteMcpClient instance.
|
|
81
|
+
*/
|
|
82
|
+
export declare class BacktestClient {
|
|
83
|
+
private readonly mcp;
|
|
84
|
+
constructor(mcp: RemoteMcpClient);
|
|
85
|
+
/**
|
|
86
|
+
* Return supported backtest configuration schema.
|
|
87
|
+
*/
|
|
88
|
+
getConfigSchema(): Promise<BacktestConfigSchema>;
|
|
89
|
+
/**
|
|
90
|
+
* Return supported token list, optionally filtered by platform.
|
|
91
|
+
*/
|
|
92
|
+
getAvailableTokens(input?: GetAvailableTokensInput): Promise<{
|
|
93
|
+
tokens: TokenInfo[];
|
|
94
|
+
context: {
|
|
95
|
+
privyId: string;
|
|
96
|
+
};
|
|
97
|
+
}>;
|
|
98
|
+
/**
|
|
99
|
+
* Validate and compile an AST expression for backtest entry/exit rules.
|
|
100
|
+
*/
|
|
101
|
+
validateExpression(input: ValidateExpressionInput): Promise<ValidationResult>;
|
|
102
|
+
/**
|
|
103
|
+
* Create a new backtest or update an existing one.
|
|
104
|
+
*/
|
|
105
|
+
createBacktest(input: CreateBacktestInput): Promise<BacktestRecord>;
|
|
106
|
+
/**
|
|
107
|
+
* Get a backtest by ID with summary, portfolio history and trades.
|
|
108
|
+
*/
|
|
109
|
+
getResult(input: GetBacktestResultInput): Promise<BacktestRecord>;
|
|
110
|
+
/**
|
|
111
|
+
* List backtests for the current user with optional status filter and pagination.
|
|
112
|
+
*/
|
|
113
|
+
list(input?: ListBacktestsInput): Promise<ListBacktestsResult>;
|
|
114
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { BacktestTools, } from "@crush-protocol/mcp-contracts";
|
|
2
|
+
const extractContent = (result) => {
|
|
3
|
+
// Compatibility path: some SDK result types expose `toolResult` directly.
|
|
4
|
+
if ("toolResult" in result && result.toolResult !== undefined) {
|
|
5
|
+
return result.toolResult;
|
|
6
|
+
}
|
|
7
|
+
if (!Array.isArray(result.content) || result.content.length === 0) {
|
|
8
|
+
throw new Error("Unexpected MCP tool result format: missing content.");
|
|
9
|
+
}
|
|
10
|
+
const first = result.content[0];
|
|
11
|
+
if (first?.type === "text" && typeof first.text === "string") {
|
|
12
|
+
return JSON.parse(first.text);
|
|
13
|
+
}
|
|
14
|
+
throw new Error("Unexpected MCP tool result format: unsupported content item.");
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Typed SDK wrapper for the 6 backtest MCP tools.
|
|
18
|
+
* Requires a connected RemoteMcpClient instance.
|
|
19
|
+
*/
|
|
20
|
+
export class BacktestClient {
|
|
21
|
+
mcp;
|
|
22
|
+
constructor(mcp) {
|
|
23
|
+
this.mcp = mcp;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Return supported backtest configuration schema.
|
|
27
|
+
*/
|
|
28
|
+
async getConfigSchema() {
|
|
29
|
+
const result = await this.mcp.callTool(BacktestTools.GET_BACKTEST_CONFIG_SCHEMA);
|
|
30
|
+
return extractContent(result);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Return supported token list, optionally filtered by platform.
|
|
34
|
+
*/
|
|
35
|
+
async getAvailableTokens(input) {
|
|
36
|
+
const args = {};
|
|
37
|
+
if (input?.platform)
|
|
38
|
+
args.platform = input.platform;
|
|
39
|
+
const result = await this.mcp.callTool(BacktestTools.GET_AVAILABLE_TOKENS, args);
|
|
40
|
+
return extractContent(result);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Validate and compile an AST expression for backtest entry/exit rules.
|
|
44
|
+
*/
|
|
45
|
+
async validateExpression(input) {
|
|
46
|
+
const result = await this.mcp.callTool(BacktestTools.VALIDATE_EXPRESSION, input);
|
|
47
|
+
return extractContent(result);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Create a new backtest or update an existing one.
|
|
51
|
+
*/
|
|
52
|
+
async createBacktest(input) {
|
|
53
|
+
const result = await this.mcp.callTool(BacktestTools.CREATE_BACKTEST, input);
|
|
54
|
+
return extractContent(result);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get a backtest by ID with summary, portfolio history and trades.
|
|
58
|
+
*/
|
|
59
|
+
async getResult(input) {
|
|
60
|
+
const result = await this.mcp.callTool(BacktestTools.GET_BACKTEST_RESULT, input);
|
|
61
|
+
return extractContent(result);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* List backtests for the current user with optional status filter and pagination.
|
|
65
|
+
*/
|
|
66
|
+
async list(input) {
|
|
67
|
+
const args = {};
|
|
68
|
+
if (input?.status)
|
|
69
|
+
args.status = input.status;
|
|
70
|
+
if (input?.limit !== undefined)
|
|
71
|
+
args.limit = input.limit;
|
|
72
|
+
if (input?.offset !== undefined)
|
|
73
|
+
args.offset = input.offset;
|
|
74
|
+
const result = await this.mcp.callTool(BacktestTools.LIST_BACKTESTS, args);
|
|
75
|
+
return extractContent(result);
|
|
76
|
+
}
|
|
77
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "dotenv/config";
|
|
3
|
+
import { BacktestClient } from "./backtest/backtestClient.js";
|
|
4
|
+
import { ClickHouseDirectClient } from "./clickhouse/directClient.js";
|
|
5
|
+
import { RemoteMcpClient } from "./mcp/remoteClient.js";
|
|
6
|
+
const printUsage = () => {
|
|
7
|
+
console.log(`\ncrush-mcp-client\n\nGeneral:\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:get --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\nEnv fallback:\n CRUSH_MCP_SERVER_URL, CRUSH_MCP_TOKEN\n CH_HOST, CH_PORT, CH_USER, CH_PASSWORD, CH_DATABASE, CH_ROW_CAP\n`);
|
|
8
|
+
};
|
|
9
|
+
const parseFlags = (args) => {
|
|
10
|
+
const flags = {};
|
|
11
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
12
|
+
const token = args[i];
|
|
13
|
+
if (!token.startsWith("--"))
|
|
14
|
+
continue;
|
|
15
|
+
const key = token.slice(2);
|
|
16
|
+
const next = args[i + 1];
|
|
17
|
+
if (!next || next.startsWith("--")) {
|
|
18
|
+
flags[key] = true;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
flags[key] = next;
|
|
22
|
+
i += 1;
|
|
23
|
+
}
|
|
24
|
+
return flags;
|
|
25
|
+
};
|
|
26
|
+
const requireString = (value, message) => {
|
|
27
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
28
|
+
throw new Error(message);
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
};
|
|
32
|
+
const createRemoteClient = (flags) => {
|
|
33
|
+
const serverUrl = typeof flags.url === "string"
|
|
34
|
+
? flags.url
|
|
35
|
+
: (process.env.CRUSH_MCP_SERVER_URL ?? "http://localhost:8080/mcp");
|
|
36
|
+
const token = typeof flags.token === "string"
|
|
37
|
+
? flags.token
|
|
38
|
+
: requireString(process.env.CRUSH_MCP_TOKEN, "Missing token. Use --token or CRUSH_MCP_TOKEN");
|
|
39
|
+
return new RemoteMcpClient({ serverUrl, token });
|
|
40
|
+
};
|
|
41
|
+
const createClickHouseClient = (flags) => {
|
|
42
|
+
const host = typeof flags["ch-host"] === "string"
|
|
43
|
+
? flags["ch-host"]
|
|
44
|
+
: (process.env.CH_HOST ?? "localhost");
|
|
45
|
+
const portRaw = typeof flags["ch-port"] === "string"
|
|
46
|
+
? flags["ch-port"]
|
|
47
|
+
: (process.env.CH_PORT ?? "8123");
|
|
48
|
+
const user = typeof flags["ch-user"] === "string"
|
|
49
|
+
? flags["ch-user"]
|
|
50
|
+
: (process.env.CH_USER ?? "default");
|
|
51
|
+
const password = typeof flags["ch-password"] === "string"
|
|
52
|
+
? flags["ch-password"]
|
|
53
|
+
: (process.env.CH_PASSWORD ?? "");
|
|
54
|
+
const database = typeof flags["ch-database"] === "string"
|
|
55
|
+
? flags["ch-database"]
|
|
56
|
+
: (process.env.CH_DATABASE ?? "crush_ats");
|
|
57
|
+
const rowCapRaw = typeof flags["ch-row-cap"] === "string"
|
|
58
|
+
? flags["ch-row-cap"]
|
|
59
|
+
: process.env.CH_ROW_CAP;
|
|
60
|
+
const port = Number(portRaw);
|
|
61
|
+
if (!Number.isFinite(port) || port <= 0) {
|
|
62
|
+
throw new Error(`Invalid ClickHouse port: ${portRaw}`);
|
|
63
|
+
}
|
|
64
|
+
let rowCap;
|
|
65
|
+
if (rowCapRaw !== undefined) {
|
|
66
|
+
const parsed = Number(rowCapRaw);
|
|
67
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
68
|
+
throw new Error(`Invalid ClickHouse row cap: ${rowCapRaw}`);
|
|
69
|
+
}
|
|
70
|
+
rowCap = parsed;
|
|
71
|
+
}
|
|
72
|
+
return new ClickHouseDirectClient({
|
|
73
|
+
host,
|
|
74
|
+
port,
|
|
75
|
+
user,
|
|
76
|
+
password,
|
|
77
|
+
database,
|
|
78
|
+
rowCap,
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
const run = async () => {
|
|
82
|
+
const [, , command, ...rest] = process.argv;
|
|
83
|
+
if (!command ||
|
|
84
|
+
command === "help" ||
|
|
85
|
+
command === "--help" ||
|
|
86
|
+
command === "-h") {
|
|
87
|
+
printUsage();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const flags = parseFlags(rest);
|
|
91
|
+
switch (command) {
|
|
92
|
+
case "tools:list": {
|
|
93
|
+
const client = createRemoteClient(flags);
|
|
94
|
+
await client.connect();
|
|
95
|
+
try {
|
|
96
|
+
const result = await client.listTools();
|
|
97
|
+
console.log(JSON.stringify(result, null, 2));
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
await client.close();
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
case "tool:call": {
|
|
105
|
+
const name = requireString(flags.name, "Missing --name for tool:call");
|
|
106
|
+
const rawArgs = typeof flags.args === "string" ? flags.args : "{}";
|
|
107
|
+
let parsedArgs;
|
|
108
|
+
try {
|
|
109
|
+
parsedArgs = JSON.parse(rawArgs);
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
throw new Error(`Invalid --args JSON: ${e.message}`);
|
|
113
|
+
}
|
|
114
|
+
const client = createRemoteClient(flags);
|
|
115
|
+
await client.connect();
|
|
116
|
+
try {
|
|
117
|
+
const result = await client.callTool(name, parsedArgs);
|
|
118
|
+
console.log(JSON.stringify(result, null, 2));
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
await client.close();
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
case "ping": {
|
|
126
|
+
const client = createRemoteClient(flags);
|
|
127
|
+
await client.connect();
|
|
128
|
+
try {
|
|
129
|
+
const result = await client.ping();
|
|
130
|
+
console.log(JSON.stringify(result, null, 2));
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
await client.close();
|
|
134
|
+
}
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
case "backtest:schema": {
|
|
138
|
+
const client = createRemoteClient(flags);
|
|
139
|
+
await client.connect();
|
|
140
|
+
try {
|
|
141
|
+
const bt = new BacktestClient(client);
|
|
142
|
+
const result = await bt.getConfigSchema();
|
|
143
|
+
console.log(JSON.stringify(result, null, 2));
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
await client.close();
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
case "backtest:tokens": {
|
|
151
|
+
const client = createRemoteClient(flags);
|
|
152
|
+
await client.connect();
|
|
153
|
+
try {
|
|
154
|
+
const bt = new BacktestClient(client);
|
|
155
|
+
const platform = typeof flags.platform === "string" ? flags.platform : undefined;
|
|
156
|
+
const result = await bt.getAvailableTokens(platform
|
|
157
|
+
? {
|
|
158
|
+
platform: platform,
|
|
159
|
+
}
|
|
160
|
+
: undefined);
|
|
161
|
+
console.log(JSON.stringify(result, null, 2));
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
await client.close();
|
|
165
|
+
}
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
case "backtest:validate": {
|
|
169
|
+
const exprRaw = requireString(flags.expression, "Missing --expression JSON for backtest:validate");
|
|
170
|
+
let expression;
|
|
171
|
+
try {
|
|
172
|
+
expression = JSON.parse(exprRaw);
|
|
173
|
+
}
|
|
174
|
+
catch (e) {
|
|
175
|
+
throw new Error(`Invalid --expression JSON: ${e.message}`);
|
|
176
|
+
}
|
|
177
|
+
const dataSource = typeof flags["data-source"] === "string"
|
|
178
|
+
? flags["data-source"]
|
|
179
|
+
: undefined;
|
|
180
|
+
const client = createRemoteClient(flags);
|
|
181
|
+
await client.connect();
|
|
182
|
+
try {
|
|
183
|
+
const bt = new BacktestClient(client);
|
|
184
|
+
const result = await bt.validateExpression({ expression, dataSource });
|
|
185
|
+
console.log(JSON.stringify(result, null, 2));
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
await client.close();
|
|
189
|
+
}
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
case "backtest:create": {
|
|
193
|
+
const configRaw = requireString(flags.config, "Missing --config JSON for backtest:create");
|
|
194
|
+
let config;
|
|
195
|
+
try {
|
|
196
|
+
config = JSON.parse(configRaw);
|
|
197
|
+
}
|
|
198
|
+
catch (e) {
|
|
199
|
+
throw new Error(`Invalid --config JSON: ${e.message}`);
|
|
200
|
+
}
|
|
201
|
+
const backtestId = typeof flags["backtest-id"] === "string"
|
|
202
|
+
? flags["backtest-id"]
|
|
203
|
+
: undefined;
|
|
204
|
+
const client = createRemoteClient(flags);
|
|
205
|
+
await client.connect();
|
|
206
|
+
try {
|
|
207
|
+
const bt = new BacktestClient(client);
|
|
208
|
+
const result = await bt.createBacktest({ config, backtestId });
|
|
209
|
+
console.log(JSON.stringify(result, null, 2));
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
await client.close();
|
|
213
|
+
}
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
case "backtest:get": {
|
|
217
|
+
const backtestId = requireString(flags["backtest-id"], "Missing --backtest-id for backtest:get");
|
|
218
|
+
const client = createRemoteClient(flags);
|
|
219
|
+
await client.connect();
|
|
220
|
+
try {
|
|
221
|
+
const bt = new BacktestClient(client);
|
|
222
|
+
const result = await bt.getResult({ backtestId });
|
|
223
|
+
console.log(JSON.stringify(result, null, 2));
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
await client.close();
|
|
227
|
+
}
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
case "backtest:list": {
|
|
231
|
+
const status = typeof flags.status === "string"
|
|
232
|
+
? flags.status
|
|
233
|
+
: undefined;
|
|
234
|
+
const limit = typeof flags.limit === "string" ? Number(flags.limit) : undefined;
|
|
235
|
+
const offset = typeof flags.offset === "string" ? Number(flags.offset) : undefined;
|
|
236
|
+
const client = createRemoteClient(flags);
|
|
237
|
+
await client.connect();
|
|
238
|
+
try {
|
|
239
|
+
const bt = new BacktestClient(client);
|
|
240
|
+
const result = await bt.list({ status, limit, offset });
|
|
241
|
+
console.log(JSON.stringify(result, null, 2));
|
|
242
|
+
}
|
|
243
|
+
finally {
|
|
244
|
+
await client.close();
|
|
245
|
+
}
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
case "clickhouse:list-tables": {
|
|
249
|
+
const ch = createClickHouseClient(flags);
|
|
250
|
+
const tables = await ch.listTables();
|
|
251
|
+
console.log(JSON.stringify({ tables }, null, 2));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
case "clickhouse:query": {
|
|
255
|
+
const sql = requireString(flags.sql, "Missing --sql for clickhouse:query");
|
|
256
|
+
const ch = createClickHouseClient(flags);
|
|
257
|
+
const rows = await ch.query(sql);
|
|
258
|
+
console.log(JSON.stringify({ rows }, null, 2));
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
default:
|
|
262
|
+
throw new Error(`Unknown command: ${command}`);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
run().catch((error) => {
|
|
266
|
+
console.error(`Error: ${error.message}`);
|
|
267
|
+
process.exit(1);
|
|
268
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type ClickHouseDirectConfig = {
|
|
2
|
+
host: string;
|
|
3
|
+
port: number;
|
|
4
|
+
user: string;
|
|
5
|
+
password: string;
|
|
6
|
+
database: string;
|
|
7
|
+
rowCap?: number;
|
|
8
|
+
};
|
|
9
|
+
export declare const DEFAULT_ROW_CAP = 5000;
|
|
10
|
+
export declare class ClickHouseDirectClient {
|
|
11
|
+
private readonly config;
|
|
12
|
+
constructor(config: ClickHouseDirectConfig);
|
|
13
|
+
private guardReadOnlySql;
|
|
14
|
+
private execute;
|
|
15
|
+
query(sql: string, database?: string): Promise<Record<string, unknown>[]>;
|
|
16
|
+
listTables(database?: string): Promise<{
|
|
17
|
+
name: string;
|
|
18
|
+
rows: number;
|
|
19
|
+
comment: string;
|
|
20
|
+
}[]>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
export const DEFAULT_ROW_CAP = 5000;
|
|
3
|
+
const WRITE_SQL_PATTERN = /\b(insert|update|delete|drop|alter|truncate|create|grant|revoke|attach|optimize|system|rename)\b/i;
|
|
4
|
+
const SAFE_DB_NAME_PATTERN = /^[A-Za-z0-9_]+$/;
|
|
5
|
+
export class ClickHouseDirectClient {
|
|
6
|
+
config;
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.config = {
|
|
9
|
+
...config,
|
|
10
|
+
rowCap: config.rowCap ?? DEFAULT_ROW_CAP,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
guardReadOnlySql(sql) {
|
|
14
|
+
const normalized = sql.trim().replace(/;$/, "");
|
|
15
|
+
if (!normalized.toLowerCase().startsWith("select")) {
|
|
16
|
+
throw new Error("Only SELECT statements are allowed for direct ClickHouse mode.");
|
|
17
|
+
}
|
|
18
|
+
if (WRITE_SQL_PATTERN.test(normalized)) {
|
|
19
|
+
throw new Error("Potential write/admin SQL detected. Query rejected.");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
execute(sql, database) {
|
|
23
|
+
const db = database || this.config.database;
|
|
24
|
+
const params = new URLSearchParams({
|
|
25
|
+
database: db,
|
|
26
|
+
query: `${sql} FORMAT JSONEachRow`,
|
|
27
|
+
readonly: "1",
|
|
28
|
+
});
|
|
29
|
+
const authHeader = `Basic ${Buffer.from(`${this.config.user}:${this.config.password}`).toString("base64")}`;
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const req = http.request({
|
|
32
|
+
hostname: this.config.host,
|
|
33
|
+
port: this.config.port,
|
|
34
|
+
path: `/?${params.toString()}`,
|
|
35
|
+
method: "GET",
|
|
36
|
+
headers: { Authorization: authHeader },
|
|
37
|
+
timeout: 30000,
|
|
38
|
+
}, (res) => {
|
|
39
|
+
let data = "";
|
|
40
|
+
res.on("data", (chunk) => {
|
|
41
|
+
data += chunk.toString();
|
|
42
|
+
});
|
|
43
|
+
res.on("end", () => {
|
|
44
|
+
if (res.statusCode !== 200) {
|
|
45
|
+
reject(new Error(`ClickHouse error (${res.statusCode}): ${data.slice(0, 300)}`));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!data.trim()) {
|
|
49
|
+
resolve([]);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
resolve(data.trim().split("\n").map((line) => JSON.parse(line)));
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
reject(new Error(`Failed to parse ClickHouse JSONEachRow: ${e.message}`));
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
req.on("error", (e) => reject(new Error(`ClickHouse request failed: ${e.message}`)));
|
|
61
|
+
req.on("timeout", () => {
|
|
62
|
+
req.destroy();
|
|
63
|
+
reject(new Error("ClickHouse request timeout after 30s"));
|
|
64
|
+
});
|
|
65
|
+
req.end();
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async query(sql, database) {
|
|
69
|
+
this.guardReadOnlySql(sql);
|
|
70
|
+
const normalized = sql.trim().replace(/;$/, "");
|
|
71
|
+
const wrapped = `SELECT * FROM (${normalized}) AS q LIMIT ${this.config.rowCap}`;
|
|
72
|
+
return this.execute(wrapped, database);
|
|
73
|
+
}
|
|
74
|
+
async listTables(database) {
|
|
75
|
+
const db = database || this.config.database;
|
|
76
|
+
if (!SAFE_DB_NAME_PATTERN.test(db)) {
|
|
77
|
+
throw new Error(`Invalid database name: ${db}`);
|
|
78
|
+
}
|
|
79
|
+
const rows = await this.execute(`SELECT name, total_rows AS rows, comment FROM system.tables WHERE database = '${db}' ORDER BY name`, "system");
|
|
80
|
+
return rows.map((r) => ({
|
|
81
|
+
name: String(r.name),
|
|
82
|
+
rows: Number(r.rows ?? r.total_rows ?? 0),
|
|
83
|
+
comment: String(r.comment ?? ""),
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { ClickHouseDirectClient, DEFAULT_ROW_CAP, type ClickHouseDirectConfig, } from "./clickhouse/directClient.js";
|
|
2
|
+
export { RemoteMcpClient, type RemoteMcpClientOptions, } from "./mcp/remoteClient.js";
|
|
3
|
+
export { BacktestClient, type BacktestConfigSchema, type BacktestRecord, type CreateBacktestInput, type GetAvailableTokensInput, type GetBacktestResultInput, type ListBacktestsInput, type ListBacktestsResult, type TokenInfo, type ValidationResult, } from "./backtest/backtestClient.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
export type RemoteMcpClientOptions = {
|
|
2
|
+
serverUrl: string;
|
|
3
|
+
token: string;
|
|
4
|
+
clientName?: string;
|
|
5
|
+
clientVersion?: string;
|
|
6
|
+
};
|
|
7
|
+
export declare class RemoteMcpClient {
|
|
8
|
+
private readonly client;
|
|
9
|
+
private readonly transport;
|
|
10
|
+
constructor(options: RemoteMcpClientOptions);
|
|
11
|
+
connect(): Promise<void>;
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
terminateSession(): Promise<void>;
|
|
14
|
+
ping(): Promise<{
|
|
15
|
+
_meta?: {
|
|
16
|
+
[x: string]: unknown;
|
|
17
|
+
progressToken?: string | number | undefined;
|
|
18
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
19
|
+
taskId: string;
|
|
20
|
+
} | undefined;
|
|
21
|
+
} | undefined;
|
|
22
|
+
}>;
|
|
23
|
+
listTools(): Promise<{
|
|
24
|
+
[x: string]: unknown;
|
|
25
|
+
tools: {
|
|
26
|
+
inputSchema: {
|
|
27
|
+
[x: string]: unknown;
|
|
28
|
+
type: "object";
|
|
29
|
+
properties?: Record<string, object> | undefined;
|
|
30
|
+
required?: string[] | undefined;
|
|
31
|
+
};
|
|
32
|
+
name: string;
|
|
33
|
+
description?: string | undefined;
|
|
34
|
+
outputSchema?: {
|
|
35
|
+
[x: string]: unknown;
|
|
36
|
+
type: "object";
|
|
37
|
+
properties?: Record<string, object> | undefined;
|
|
38
|
+
required?: string[] | undefined;
|
|
39
|
+
} | undefined;
|
|
40
|
+
annotations?: {
|
|
41
|
+
title?: string | undefined;
|
|
42
|
+
readOnlyHint?: boolean | undefined;
|
|
43
|
+
destructiveHint?: boolean | undefined;
|
|
44
|
+
idempotentHint?: boolean | undefined;
|
|
45
|
+
openWorldHint?: boolean | undefined;
|
|
46
|
+
} | undefined;
|
|
47
|
+
execution?: {
|
|
48
|
+
taskSupport?: "optional" | "required" | "forbidden" | undefined;
|
|
49
|
+
} | undefined;
|
|
50
|
+
_meta?: Record<string, unknown> | undefined;
|
|
51
|
+
icons?: {
|
|
52
|
+
src: string;
|
|
53
|
+
mimeType?: string | undefined;
|
|
54
|
+
sizes?: string[] | undefined;
|
|
55
|
+
theme?: "light" | "dark" | undefined;
|
|
56
|
+
}[] | undefined;
|
|
57
|
+
title?: string | undefined;
|
|
58
|
+
}[];
|
|
59
|
+
_meta?: {
|
|
60
|
+
[x: string]: unknown;
|
|
61
|
+
progressToken?: string | number | undefined;
|
|
62
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
63
|
+
taskId: string;
|
|
64
|
+
} | undefined;
|
|
65
|
+
} | undefined;
|
|
66
|
+
nextCursor?: string | undefined;
|
|
67
|
+
}>;
|
|
68
|
+
callTool(name: string, args?: Record<string, unknown>): Promise<{
|
|
69
|
+
[x: string]: unknown;
|
|
70
|
+
content: ({
|
|
71
|
+
type: "text";
|
|
72
|
+
text: string;
|
|
73
|
+
annotations?: {
|
|
74
|
+
audience?: ("user" | "assistant")[] | undefined;
|
|
75
|
+
priority?: number | undefined;
|
|
76
|
+
lastModified?: string | undefined;
|
|
77
|
+
} | undefined;
|
|
78
|
+
_meta?: Record<string, unknown> | undefined;
|
|
79
|
+
} | {
|
|
80
|
+
type: "image";
|
|
81
|
+
data: string;
|
|
82
|
+
mimeType: string;
|
|
83
|
+
annotations?: {
|
|
84
|
+
audience?: ("user" | "assistant")[] | undefined;
|
|
85
|
+
priority?: number | undefined;
|
|
86
|
+
lastModified?: string | undefined;
|
|
87
|
+
} | undefined;
|
|
88
|
+
_meta?: Record<string, unknown> | undefined;
|
|
89
|
+
} | {
|
|
90
|
+
type: "audio";
|
|
91
|
+
data: string;
|
|
92
|
+
mimeType: string;
|
|
93
|
+
annotations?: {
|
|
94
|
+
audience?: ("user" | "assistant")[] | undefined;
|
|
95
|
+
priority?: number | undefined;
|
|
96
|
+
lastModified?: string | undefined;
|
|
97
|
+
} | undefined;
|
|
98
|
+
_meta?: Record<string, unknown> | undefined;
|
|
99
|
+
} | {
|
|
100
|
+
type: "resource";
|
|
101
|
+
resource: {
|
|
102
|
+
uri: string;
|
|
103
|
+
text: string;
|
|
104
|
+
mimeType?: string | undefined;
|
|
105
|
+
_meta?: Record<string, unknown> | undefined;
|
|
106
|
+
} | {
|
|
107
|
+
uri: string;
|
|
108
|
+
blob: string;
|
|
109
|
+
mimeType?: string | undefined;
|
|
110
|
+
_meta?: Record<string, unknown> | undefined;
|
|
111
|
+
};
|
|
112
|
+
annotations?: {
|
|
113
|
+
audience?: ("user" | "assistant")[] | undefined;
|
|
114
|
+
priority?: number | undefined;
|
|
115
|
+
lastModified?: string | undefined;
|
|
116
|
+
} | undefined;
|
|
117
|
+
_meta?: Record<string, unknown> | undefined;
|
|
118
|
+
} | {
|
|
119
|
+
uri: string;
|
|
120
|
+
name: string;
|
|
121
|
+
type: "resource_link";
|
|
122
|
+
description?: string | undefined;
|
|
123
|
+
mimeType?: string | undefined;
|
|
124
|
+
annotations?: {
|
|
125
|
+
audience?: ("user" | "assistant")[] | undefined;
|
|
126
|
+
priority?: number | undefined;
|
|
127
|
+
lastModified?: string | undefined;
|
|
128
|
+
} | undefined;
|
|
129
|
+
_meta?: {
|
|
130
|
+
[x: string]: unknown;
|
|
131
|
+
} | undefined;
|
|
132
|
+
icons?: {
|
|
133
|
+
src: string;
|
|
134
|
+
mimeType?: string | undefined;
|
|
135
|
+
sizes?: string[] | undefined;
|
|
136
|
+
theme?: "light" | "dark" | undefined;
|
|
137
|
+
}[] | undefined;
|
|
138
|
+
title?: string | undefined;
|
|
139
|
+
})[];
|
|
140
|
+
_meta?: {
|
|
141
|
+
[x: string]: unknown;
|
|
142
|
+
progressToken?: string | number | undefined;
|
|
143
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
144
|
+
taskId: string;
|
|
145
|
+
} | undefined;
|
|
146
|
+
} | undefined;
|
|
147
|
+
structuredContent?: Record<string, unknown> | undefined;
|
|
148
|
+
isError?: boolean | undefined;
|
|
149
|
+
} | {
|
|
150
|
+
[x: string]: unknown;
|
|
151
|
+
toolResult: unknown;
|
|
152
|
+
_meta?: {
|
|
153
|
+
[x: string]: unknown;
|
|
154
|
+
progressToken?: string | number | undefined;
|
|
155
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
156
|
+
taskId: string;
|
|
157
|
+
} | undefined;
|
|
158
|
+
} | undefined;
|
|
159
|
+
}>;
|
|
160
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
3
|
+
export class RemoteMcpClient {
|
|
4
|
+
client;
|
|
5
|
+
transport;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
if (!options.token.startsWith("mcp_")) {
|
|
8
|
+
throw new Error("Invalid token format. Expected mcp_xxx");
|
|
9
|
+
}
|
|
10
|
+
this.client = new Client({
|
|
11
|
+
name: options.clientName || "crush-mcp-client",
|
|
12
|
+
version: options.clientVersion || "0.1.0",
|
|
13
|
+
}, {
|
|
14
|
+
capabilities: {},
|
|
15
|
+
});
|
|
16
|
+
this.transport = new StreamableHTTPClientTransport(new URL(options.serverUrl), {
|
|
17
|
+
requestInit: {
|
|
18
|
+
headers: {
|
|
19
|
+
Authorization: `Bearer ${options.token}`,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
async connect() {
|
|
25
|
+
await this.client.connect(this.transport);
|
|
26
|
+
}
|
|
27
|
+
async close() {
|
|
28
|
+
await this.transport.close();
|
|
29
|
+
}
|
|
30
|
+
async terminateSession() {
|
|
31
|
+
await this.transport.terminateSession();
|
|
32
|
+
}
|
|
33
|
+
async ping() {
|
|
34
|
+
return this.client.ping();
|
|
35
|
+
}
|
|
36
|
+
async listTools() {
|
|
37
|
+
return this.client.listTools();
|
|
38
|
+
}
|
|
39
|
+
async callTool(name, args = {}) {
|
|
40
|
+
return this.client.callTool({
|
|
41
|
+
name,
|
|
42
|
+
arguments: args,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@crush-protocol/mcp-client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Crush MCP npm client package (remote Streamable HTTP + optional ClickHouse direct)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"crush-mcp-client": "dist/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
23
|
+
"dotenv": "^17.2.1",
|
|
24
|
+
"zod": "^3.25.76",
|
|
25
|
+
"@crush-protocol/mcp-contracts": "0.1.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^24.3.0",
|
|
29
|
+
"tsx": "^4.20.4",
|
|
30
|
+
"typescript": "^5.9.2"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=20"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsc -p tsconfig.json",
|
|
37
|
+
"dev": "tsx src/cli.ts"
|
|
38
|
+
}
|
|
39
|
+
}
|