@363045841yyt/klinechart-ai-runtime 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/README.md +130 -0
- package/dist/createWithMcp.d.ts +9 -0
- package/dist/createWithMcp.d.ts.map +1 -0
- package/dist/createWithMcp.js +15 -0
- package/dist/createWithMcp.js.map +1 -0
- package/dist/describeControllers.d.ts +34 -0
- package/dist/describeControllers.d.ts.map +1 -0
- package/dist/describeControllers.js +104 -0
- package/dist/describeControllers.js.map +1 -0
- package/dist/executeTool.d.ts +12 -0
- package/dist/executeTool.d.ts.map +1 -0
- package/dist/executeTool.js +63 -0
- package/dist/executeTool.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/mcpServer.d.ts +35 -0
- package/dist/mcpServer.d.ts.map +1 -0
- package/dist/mcpServer.js +189 -0
- package/dist/mcpServer.js.map +1 -0
- package/dist/serialization.d.ts +28 -0
- package/dist/serialization.d.ts.map +1 -0
- package/dist/serialization.js +53 -0
- package/dist/serialization.js.map +1 -0
- package/dist/sessionRegistry.d.ts +19 -0
- package/dist/sessionRegistry.d.ts.map +1 -0
- package/dist/sessionRegistry.js +41 -0
- package/dist/sessionRegistry.js.map +1 -0
- package/dist/toolSchemas.d.ts +14 -0
- package/dist/toolSchemas.d.ts.map +1 -0
- package/dist/toolSchemas.js +216 -0
- package/dist/toolSchemas.js.map +1 -0
- package/dist/types.d.ts +75 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +63 -0
- package/src/__tests__/chartBridge.integration.test.ts +100 -0
- package/src/__tests__/describeControllers.test.ts +163 -0
- package/src/__tests__/executeTool.test.ts +187 -0
- package/src/__tests__/mcpServer.integration.test.ts +155 -0
- package/src/__tests__/mcpServer.test.ts +30 -0
- package/src/__tests__/serialization.test.ts +116 -0
- package/src/__tests__/sessionRegistry.test.ts +139 -0
- package/src/__tests__/toolSchemas.test.ts +149 -0
- package/src/createWithMcp.ts +28 -0
- package/src/describeControllers.ts +166 -0
- package/src/executeTool.ts +92 -0
- package/src/index.ts +38 -0
- package/src/mcpServer.ts +268 -0
- package/src/serialization.ts +88 -0
- package/src/sessionRegistry.ts +61 -0
- package/src/toolSchemas.ts +235 -0
- package/src/types.ts +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# @363045841yyt/klinechart-ai-runtime
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server and AI tool schemas for
|
|
4
|
+
[@363045841yyt/klinechart-core](https://github.com/363045841/KLineChartQuant/tree/main/packages/core)
|
|
5
|
+
([npm](https://www.npmjs.com/package/@363045841yyt/klinechart-core))
|
|
6
|
+
/
|
|
7
|
+
[@363045841yyt/klinechart](https://github.com/363045841/KLineChartQuant/tree/main/packages/vue)
|
|
8
|
+
([npm](https://www.npmjs.com/package/@363045841yyt/klinechart)).
|
|
9
|
+
|
|
10
|
+
Optional addon — install only if you need AI agent / MCP control of your charts.
|
|
11
|
+
|
|
12
|
+
Provides a WebSocket-bridged MCP server that enables AI agents (via MCP Inspector
|
|
13
|
+
or any MCP client) to control K-line chart operations — zoom, pan, add/remove
|
|
14
|
+
indicators, change theme, and more.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pnpm add @363045841yyt/klinechart-ai-runtime
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Requires `@363045841yyt/klinechart-core` as a peer dependency.
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
### Start the MCP server
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { createMcpServer } from '@363045841yyt/klinechart-ai-runtime/mcp-server'
|
|
30
|
+
|
|
31
|
+
const { start, stop } = createMcpServer({
|
|
32
|
+
ws: { port: 8080 },
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
await start()
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Integrate with KLineChart (Vue example)
|
|
39
|
+
|
|
40
|
+
```vue
|
|
41
|
+
<script setup lang="ts">
|
|
42
|
+
import KLineChart from '@363045841yyt/klinechart'
|
|
43
|
+
import { executeTool } from '@363045841yyt/klinechart-ai-runtime'
|
|
44
|
+
|
|
45
|
+
const chartRef = ref<InstanceType<typeof KLineChart> | null>(null)
|
|
46
|
+
|
|
47
|
+
const mcpConfig = {
|
|
48
|
+
wsUrl: 'ws://localhost:8080',
|
|
49
|
+
autoReconnect: true,
|
|
50
|
+
onToolCall: (call) => {
|
|
51
|
+
const ctrl = chartRef.value?.getController?.()
|
|
52
|
+
if (!ctrl) return { success: false, error: 'Controller not ready' }
|
|
53
|
+
return executeTool(ctrl, call)
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<template>
|
|
59
|
+
<KLineChart ref="chartRef" :mcp="mcpConfig" />
|
|
60
|
+
</template>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Connect from MCP Inspector
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
cd packages/ai-runtime
|
|
67
|
+
pnpm inspect
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Then call tools like `chart.zoomToLevel` with `{ "level": 5 }`.
|
|
71
|
+
|
|
72
|
+
## Exports
|
|
73
|
+
|
|
74
|
+
### Main entry (`@363045841yyt/klinechart-ai-runtime`)
|
|
75
|
+
|
|
76
|
+
| Export | Description |
|
|
77
|
+
|--------|-------------|
|
|
78
|
+
| `executeTool` | Dispatch a tool call to a chart controller |
|
|
79
|
+
| `ALL_TOOLS` | Array of all supported tool schemas |
|
|
80
|
+
| `TOOL_GROUPS` | Grouped tool definitions |
|
|
81
|
+
| `findTool(name)` | Look up a tool schema by name |
|
|
82
|
+
| `describeVolumeProfileState` | Generate VP state summary |
|
|
83
|
+
| `describeAnchoredVwap` | Generate anchored VWAP summary |
|
|
84
|
+
| `describeFootprintLatestBar` | Generate footprint summary |
|
|
85
|
+
| `describeAlerts` | Generate alerts summary |
|
|
86
|
+
| `serialize` / `deserialize` | Chart state serialization |
|
|
87
|
+
| `SessionRegistry` | WebSocket session manager |
|
|
88
|
+
|
|
89
|
+
### MCP Server (`@363045841yyt/klinechart-ai-runtime/mcp-server`)
|
|
90
|
+
|
|
91
|
+
| Export | Description |
|
|
92
|
+
|--------|-------------|
|
|
93
|
+
| `createMcpServer(options)` | Create MCP + WebSocket server instance |
|
|
94
|
+
|
|
95
|
+
### Create with MCP (`@363045841yyt/klinechart-ai-runtime/create-with-mcp`)
|
|
96
|
+
|
|
97
|
+
Legacy helper — prefer `executeTool` + `mcp` prop pattern above.
|
|
98
|
+
|
|
99
|
+
## Architecture
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
┌─────────────────┐ WebSocket ┌───────────────────┐
|
|
103
|
+
│ Browser │◄─────────────────────────►│ MCP Server │
|
|
104
|
+
│ │ register / tool:call │ (Node.js) │
|
|
105
|
+
│ KLineChart │ tool:result / │ │
|
|
106
|
+
│ └─ ChartBridge │ state:update │ ┌─ SessionRegistry│
|
|
107
|
+
│ ↓ │ │ └─ WsSessionHandle│
|
|
108
|
+
│ └─ onToolCall ─┼───────────────────────────┼──► executeTool │
|
|
109
|
+
└─────────────────┘ └────────┬──────────┘
|
|
110
|
+
│ stdio
|
|
111
|
+
▼
|
|
112
|
+
┌────────────────────┐
|
|
113
|
+
│ MCP Client │
|
|
114
|
+
│ (Inspector / AI) │
|
|
115
|
+
└────────────────────┘
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Available Tools
|
|
119
|
+
|
|
120
|
+
| Tool | Description |
|
|
121
|
+
|------|-------------|
|
|
122
|
+
| `chart.zoomToLevel` | Zoom to a specific level |
|
|
123
|
+
| `chart.setTheme` | Switch between light/dark theme |
|
|
124
|
+
| `indicators.add` | Add an indicator by definition ID |
|
|
125
|
+
| `indicators.remove` | Remove an indicator by instance ID |
|
|
126
|
+
| `indicators.updateParams` | Update indicator parameters |
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
MIT
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ChartMountOptions, type ChartController } from '@363045841yyt/klinechart-core';
|
|
2
|
+
export interface CreateChartWithMcpOptions extends ChartMountOptions {
|
|
3
|
+
mcp: {
|
|
4
|
+
wsUrl?: string;
|
|
5
|
+
autoReconnect?: boolean;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export declare function createChartControllerWithMcp(opts: CreateChartWithMcpOptions): ChartController;
|
|
9
|
+
//# sourceMappingURL=createWithMcp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createWithMcp.d.ts","sourceRoot":"","sources":["../src/createWithMcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACrB,MAAM,+BAA+B,CAAA;AAGtC,MAAM,WAAW,yBAA0B,SAAQ,iBAAiB;IAClE,GAAG,EAAE;QACH,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,aAAa,CAAC,EAAE,OAAO,CAAA;KACxB,CAAA;CACF;AAED,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,yBAAyB,GAC9B,eAAe,CAWjB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createChartController, } from '@363045841yyt/klinechart-core';
|
|
2
|
+
import { executeTool } from './executeTool';
|
|
3
|
+
export function createChartControllerWithMcp(opts) {
|
|
4
|
+
let ctrl;
|
|
5
|
+
ctrl = createChartController({
|
|
6
|
+
...opts,
|
|
7
|
+
mcp: {
|
|
8
|
+
wsUrl: opts.mcp.wsUrl,
|
|
9
|
+
autoReconnect: opts.mcp.autoReconnect,
|
|
10
|
+
onToolCall: (call) => executeTool(ctrl, call),
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
return ctrl;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=createWithMcp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createWithMcp.js","sourceRoot":"","sources":["../src/createWithMcp.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,GAGtB,MAAM,+BAA+B,CAAA;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAS3C,MAAM,UAAU,4BAA4B,CAC1C,IAA+B;IAE/B,IAAI,IAAqB,CAAA;IACzB,IAAI,GAAG,qBAAqB,CAAC;QAC3B,GAAG,IAAI;QACP,GAAG,EAAE;YACH,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK;YACrB,aAAa,EAAE,IAAI,CAAC,GAAG,CAAC,aAAa;YACrC,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC;SAC9C;KACF,CAAC,CAAA;IACF,OAAO,IAAI,CAAA;AACb,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ControllerDescription } from './types';
|
|
2
|
+
export interface VolumeProfileSnapshot {
|
|
3
|
+
poc: number;
|
|
4
|
+
vah: number;
|
|
5
|
+
val: number;
|
|
6
|
+
totalVolume: number;
|
|
7
|
+
vaVolume: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function describeVolumeProfileState(state: VolumeProfileSnapshot | null): ControllerDescription;
|
|
10
|
+
export interface AnchoredVwapSeriesSnapshot {
|
|
11
|
+
label: string;
|
|
12
|
+
barIndex: number;
|
|
13
|
+
vwap: number;
|
|
14
|
+
upper1: number;
|
|
15
|
+
lower1: number;
|
|
16
|
+
upper2: number;
|
|
17
|
+
lower2: number;
|
|
18
|
+
}
|
|
19
|
+
export declare function describeAnchoredVwap(activeAnchors: ReadonlyArray<AnchoredVwapSeriesSnapshot>, latestPrice: number | null): ControllerDescription;
|
|
20
|
+
export interface FootprintLatestBarSnapshot {
|
|
21
|
+
barIndex: number;
|
|
22
|
+
delta: number;
|
|
23
|
+
totalVolume: number;
|
|
24
|
+
imbalanceCount: number;
|
|
25
|
+
maxImbalanceRatio: number;
|
|
26
|
+
}
|
|
27
|
+
export declare function describeFootprintLatestBar(bar: FootprintLatestBarSnapshot | null, cumulativeDelta: number): ControllerDescription;
|
|
28
|
+
export interface AlertSnapshot {
|
|
29
|
+
rulesEnabled: number;
|
|
30
|
+
rulesTotal: number;
|
|
31
|
+
recentEventsCount: number;
|
|
32
|
+
}
|
|
33
|
+
export declare function describeAlerts(state: AlertSnapshot): ControllerDescription;
|
|
34
|
+
//# sourceMappingURL=describeControllers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"describeControllers.d.ts","sourceRoot":"","sources":["../src/describeControllers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AAEpD,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,qBAAqB,GAAG,IAAI,GAClC,qBAAqB,CA8BvB;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,oBAAoB,CAClC,aAAa,EAAE,aAAa,CAAC,0BAA0B,CAAC,EACxD,WAAW,EAAE,MAAM,GAAG,IAAI,GACzB,qBAAqB,CAmCvB;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,EAAE,MAAM,CAAA;IACtB,iBAAiB,EAAE,MAAM,CAAA;CAC1B;AAED,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,0BAA0B,GAAG,IAAI,EACtC,eAAe,EAAE,MAAM,GACtB,qBAAqB,CAsCvB;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,iBAAiB,EAAE,MAAM,CAAA;CAC1B;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,qBAAqB,CAc1E"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export function describeVolumeProfileState(state) {
|
|
2
|
+
if (state === null) {
|
|
3
|
+
return {
|
|
4
|
+
controllerId: 'volumeProfile',
|
|
5
|
+
summary: 'Volume Profile has not been computed yet — no bars have been ingested.',
|
|
6
|
+
facts: { ready: false },
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
const vaPercent = state.totalVolume > 0 ? (state.vaVolume / state.totalVolume) * 100 : 0;
|
|
10
|
+
const vaSpan = state.vah - state.val;
|
|
11
|
+
return {
|
|
12
|
+
controllerId: 'volumeProfile',
|
|
13
|
+
summary: `Volume Profile shows the Point of Control at ${state.poc.toFixed(2)} — ` +
|
|
14
|
+
`the price level with the highest traded volume. The Value Area runs from ` +
|
|
15
|
+
`${state.val.toFixed(2)} (VAL) to ${state.vah.toFixed(2)} (VAH), spanning ` +
|
|
16
|
+
`${vaSpan.toFixed(2)} and containing ${vaPercent.toFixed(1)}% of total volume.`,
|
|
17
|
+
facts: {
|
|
18
|
+
poc: state.poc,
|
|
19
|
+
vah: state.vah,
|
|
20
|
+
val: state.val,
|
|
21
|
+
vaSpan: Number(vaSpan.toFixed(8)),
|
|
22
|
+
vaPercent: Number(vaPercent.toFixed(2)),
|
|
23
|
+
totalVolume: state.totalVolume,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function describeAnchoredVwap(activeAnchors, latestPrice) {
|
|
28
|
+
if (activeAnchors.length === 0) {
|
|
29
|
+
return {
|
|
30
|
+
controllerId: 'anchoredVwap',
|
|
31
|
+
summary: 'No Anchored VWAP series are active.',
|
|
32
|
+
facts: { count: 0 },
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const lines = [];
|
|
36
|
+
for (const a of activeAnchors) {
|
|
37
|
+
const rel = latestPrice === null
|
|
38
|
+
? ''
|
|
39
|
+
: latestPrice > a.upper1
|
|
40
|
+
? ' (price above 1\u03c3 upper band \u2014 overextended)'
|
|
41
|
+
: latestPrice < a.lower1
|
|
42
|
+
? ' (price below 1\u03c3 lower band \u2014 overextended)'
|
|
43
|
+
: '';
|
|
44
|
+
lines.push(`"${a.label}" at ${a.vwap.toFixed(2)}, \u00b11\u03c3 [${a.lower1.toFixed(2)}, ${a.upper1.toFixed(2)}]${rel}`);
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
controllerId: 'anchoredVwap',
|
|
48
|
+
summary: `${activeAnchors.length} Anchored VWAP series active. ` +
|
|
49
|
+
lines.join('; ') +
|
|
50
|
+
'.',
|
|
51
|
+
facts: {
|
|
52
|
+
count: activeAnchors.length,
|
|
53
|
+
anchors: lines.join(' | '),
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function describeFootprintLatestBar(bar, cumulativeDelta) {
|
|
58
|
+
if (bar === null) {
|
|
59
|
+
return {
|
|
60
|
+
controllerId: 'footprint',
|
|
61
|
+
summary: 'Footprint controller has no bars yet.',
|
|
62
|
+
facts: { ready: false },
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const tone = bar.delta > 0
|
|
66
|
+
? 'buy-dominated'
|
|
67
|
+
: bar.delta < 0
|
|
68
|
+
? 'sell-dominated'
|
|
69
|
+
: 'balanced';
|
|
70
|
+
const imbalance = bar.imbalanceCount > 0
|
|
71
|
+
? `${bar.imbalanceCount} diagonal imbalance${bar.imbalanceCount === 1 ? '' : 's'} ` +
|
|
72
|
+
`(max ratio ${bar.maxImbalanceRatio.toFixed(1)}\u00d7)`
|
|
73
|
+
: 'no imbalances flagged';
|
|
74
|
+
return {
|
|
75
|
+
controllerId: 'footprint',
|
|
76
|
+
summary: `Latest footprint bar #${bar.barIndex} is ${tone} with delta ${bar.delta.toFixed(0)} ` +
|
|
77
|
+
`against ${bar.totalVolume.toFixed(0)} total volume. ${imbalance}. ` +
|
|
78
|
+
`Cumulative delta across visible bars: ${cumulativeDelta.toFixed(0)}.`,
|
|
79
|
+
facts: {
|
|
80
|
+
barIndex: bar.barIndex,
|
|
81
|
+
delta: bar.delta,
|
|
82
|
+
tone,
|
|
83
|
+
totalVolume: bar.totalVolume,
|
|
84
|
+
imbalanceCount: bar.imbalanceCount,
|
|
85
|
+
maxImbalanceRatio: Number(bar.maxImbalanceRatio.toFixed(2)),
|
|
86
|
+
cumulativeDelta,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export function describeAlerts(state) {
|
|
91
|
+
return {
|
|
92
|
+
controllerId: 'alerts',
|
|
93
|
+
summary: state.rulesTotal === 0
|
|
94
|
+
? 'No alert rules configured.'
|
|
95
|
+
: `${state.rulesEnabled} of ${state.rulesTotal} alert rules are enabled. ` +
|
|
96
|
+
`${state.recentEventsCount} recent events buffered.`,
|
|
97
|
+
facts: {
|
|
98
|
+
rulesEnabled: state.rulesEnabled,
|
|
99
|
+
rulesTotal: state.rulesTotal,
|
|
100
|
+
recentEventsCount: state.recentEventsCount,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=describeControllers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"describeControllers.js","sourceRoot":"","sources":["../src/describeControllers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ChartController } from '@363045841yyt/klinechart-core';
|
|
2
|
+
export interface ToolCall {
|
|
3
|
+
name: string;
|
|
4
|
+
input: Record<string, unknown>;
|
|
5
|
+
}
|
|
6
|
+
export interface ToolResult {
|
|
7
|
+
success: boolean;
|
|
8
|
+
error?: string;
|
|
9
|
+
data?: unknown;
|
|
10
|
+
}
|
|
11
|
+
export declare function executeTool(chart: ChartController, call: ToolCall): ToolResult;
|
|
12
|
+
//# sourceMappingURL=executeTool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executeTool.d.ts","sourceRoot":"","sources":["../src/executeTool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAGpE,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC/B;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE,QAAQ,GACb,UAAU,CA0EZ"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { findTool } from './toolSchemas';
|
|
2
|
+
export function executeTool(chart, call) {
|
|
3
|
+
const schema = findTool(call.name);
|
|
4
|
+
if (!schema) {
|
|
5
|
+
return { success: false, error: `Unknown tool: ${call.name}` };
|
|
6
|
+
}
|
|
7
|
+
switch (call.name) {
|
|
8
|
+
case 'chart.zoomToLevel': {
|
|
9
|
+
const { level, anchorX } = call.input;
|
|
10
|
+
chart.zoomToLevel(level, anchorX);
|
|
11
|
+
return { success: true };
|
|
12
|
+
}
|
|
13
|
+
case 'chart.setTheme': {
|
|
14
|
+
const { theme } = call.input;
|
|
15
|
+
chart.setTheme(theme);
|
|
16
|
+
return { success: true };
|
|
17
|
+
}
|
|
18
|
+
case 'indicators.add': {
|
|
19
|
+
const { definitionId } = call.input;
|
|
20
|
+
const def = chart.catalog.find((d) => d.id === definitionId);
|
|
21
|
+
const role = def?.role ?? 'main';
|
|
22
|
+
const instanceId = chart.addIndicator(definitionId, role);
|
|
23
|
+
return { success: true, data: { instanceId } };
|
|
24
|
+
}
|
|
25
|
+
case 'indicators.remove': {
|
|
26
|
+
const { instanceId } = call.input;
|
|
27
|
+
const ok = chart.removeIndicator(instanceId);
|
|
28
|
+
return ok
|
|
29
|
+
? { success: true }
|
|
30
|
+
: { success: false, error: `Indicator ${instanceId} not found` };
|
|
31
|
+
}
|
|
32
|
+
case 'indicators.updateParams': {
|
|
33
|
+
const { instanceId, params } = call.input;
|
|
34
|
+
const ok = chart.updateIndicatorParams(instanceId, params);
|
|
35
|
+
return ok
|
|
36
|
+
? { success: true }
|
|
37
|
+
: { success: false, error: `Indicator ${instanceId} not found` };
|
|
38
|
+
}
|
|
39
|
+
// Alerts controller does not exist on main yet — placeholder
|
|
40
|
+
case 'alerts.addPriceCross':
|
|
41
|
+
case 'alerts.addIndicatorCross':
|
|
42
|
+
case 'alerts.remove': {
|
|
43
|
+
return {
|
|
44
|
+
success: false,
|
|
45
|
+
error: `"${call.name}" is not implemented — alerts controller is not available`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// Replay controller does not exist on main yet — placeholder
|
|
49
|
+
case 'replay.seekTo':
|
|
50
|
+
case 'replay.play':
|
|
51
|
+
case 'replay.pause':
|
|
52
|
+
case 'replay.setSpeed': {
|
|
53
|
+
return {
|
|
54
|
+
success: false,
|
|
55
|
+
error: `"${call.name}" is not implemented — replay controller is not available`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
default: {
|
|
59
|
+
return { success: false, error: `No handler registered for ${call.name}` };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=executeTool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executeTool.js","sourceRoot":"","sources":["../src/executeTool.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type * from './types';
|
|
2
|
+
export { ALL_TOOLS, TOOL_GROUPS, CHART_NAVIGATION_TOOLS, INDICATOR_TOOLS, ALERT_TOOLS, REPLAY_TOOLS, findTool, } from './toolSchemas';
|
|
3
|
+
export { describeVolumeProfileState, describeAnchoredVwap, describeFootprintLatestBar, describeAlerts, type VolumeProfileSnapshot, type AnchoredVwapSeriesSnapshot, type FootprintLatestBarSnapshot, type AlertSnapshot, } from './describeControllers';
|
|
4
|
+
export { serialize, deserialize, ChartSerializationError, type ChartSnapshotInput, } from './serialization';
|
|
5
|
+
export { executeTool, type ToolCall, type ToolResult } from './executeTool';
|
|
6
|
+
export { SessionRegistry, type SessionHandle } from './sessionRegistry';
|
|
7
|
+
export { createChartControllerWithMcp, type CreateChartWithMcpOptions, } from './createWithMcp';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,SAAS,CAAA;AAE5B,OAAO,EACL,SAAS,EACT,WAAW,EACX,sBAAsB,EACtB,eAAe,EACf,WAAW,EACX,YAAY,EACZ,QAAQ,GACT,MAAM,eAAe,CAAA;AAEtB,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,0BAA0B,EAC1B,cAAc,EACd,KAAK,qBAAqB,EAC1B,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,EAC/B,KAAK,aAAa,GACnB,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EACL,SAAS,EACT,WAAW,EACX,uBAAuB,EACvB,KAAK,kBAAkB,GACxB,MAAM,iBAAiB,CAAA;AAExB,OAAO,EAAE,WAAW,EAAE,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,MAAM,eAAe,CAAA;AAE3E,OAAO,EAAE,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEvE,OAAO,EACL,4BAA4B,EAC5B,KAAK,yBAAyB,GAC/B,MAAM,iBAAiB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { ALL_TOOLS, TOOL_GROUPS, CHART_NAVIGATION_TOOLS, INDICATOR_TOOLS, ALERT_TOOLS, REPLAY_TOOLS, findTool, } from './toolSchemas';
|
|
2
|
+
export { describeVolumeProfileState, describeAnchoredVwap, describeFootprintLatestBar, describeAlerts, } from './describeControllers';
|
|
3
|
+
export { serialize, deserialize, ChartSerializationError, } from './serialization';
|
|
4
|
+
export { executeTool } from './executeTool';
|
|
5
|
+
export { SessionRegistry } from './sessionRegistry';
|
|
6
|
+
export { createChartControllerWithMcp, } from './createWithMcp';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,SAAS,EACT,WAAW,EACX,sBAAsB,EACtB,eAAe,EACf,WAAW,EACX,YAAY,EACZ,QAAQ,GACT,MAAM,eAAe,CAAA;AAEtB,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,0BAA0B,EAC1B,cAAc,GAKf,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EACL,SAAS,EACT,WAAW,EACX,uBAAuB,GAExB,MAAM,iBAAiB,CAAA;AAExB,OAAO,EAAE,WAAW,EAAkC,MAAM,eAAe,CAAA;AAE3E,OAAO,EAAE,eAAe,EAAsB,MAAM,mBAAmB,CAAA;AAEvE,OAAO,EACL,4BAA4B,GAE7B,MAAM,iBAAiB,CAAA"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { WebSocketServer, type WebSocket } from 'ws';
|
|
3
|
+
import type { ToolCall, ToolResult } from './executeTool';
|
|
4
|
+
import { SessionRegistry, type SessionHandle } from './sessionRegistry';
|
|
5
|
+
declare class WsSessionHandle implements SessionHandle {
|
|
6
|
+
readonly sessionId: string;
|
|
7
|
+
private ws;
|
|
8
|
+
private pending;
|
|
9
|
+
private msgSeq;
|
|
10
|
+
constructor(sessionId: string, ws: WebSocket);
|
|
11
|
+
executeTool(call: ToolCall): Promise<ToolResult>;
|
|
12
|
+
handleMessage(msg: Record<string, unknown>): void;
|
|
13
|
+
isAlive(): boolean;
|
|
14
|
+
}
|
|
15
|
+
export type { WsSessionHandle };
|
|
16
|
+
export interface McpServerOptions {
|
|
17
|
+
serverInfo?: {
|
|
18
|
+
name?: string;
|
|
19
|
+
version?: string;
|
|
20
|
+
};
|
|
21
|
+
ws?: {
|
|
22
|
+
port?: number;
|
|
23
|
+
host?: string;
|
|
24
|
+
};
|
|
25
|
+
registry?: SessionRegistry;
|
|
26
|
+
}
|
|
27
|
+
export interface McpServerInstance {
|
|
28
|
+
server: Server;
|
|
29
|
+
registry: SessionRegistry;
|
|
30
|
+
wss: WebSocketServer;
|
|
31
|
+
start(): Promise<void>;
|
|
32
|
+
stop(): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
export declare function createMcpServer(options?: McpServerOptions): McpServerInstance;
|
|
35
|
+
//# sourceMappingURL=mcpServer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcpServer.d.ts","sourceRoot":"","sources":["../src/mcpServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAA;AAMlE,OAAO,EAAE,eAAe,EAAE,KAAK,SAAS,EAAE,MAAM,IAAI,CAAA;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAGzD,OAAO,EAAE,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEvE,cAAM,eAAgB,YAAW,aAAa;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,OAAO,CAAC,EAAE,CAAW;IACrB,OAAO,CAAC,OAAO,CAGZ;IACH,OAAO,CAAC,MAAM,CAAI;gBAGhB,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,SAAS;IAMT,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC;IA0BtD,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAWjD,OAAO,IAAI,OAAO;CAGnB;AAED,YAAY,EAAE,eAAe,EAAE,CAAA;AAO/B,MAAM,WAAW,gBAAgB;IAC/B,UAAU,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAChD,EAAE,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACrC,QAAQ,CAAC,EAAE,eAAe,CAAA;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,eAAe,CAAA;IACzB,GAAG,EAAE,eAAe,CAAA;IACpB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACtB;AAED,wBAAgB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,iBAAiB,CA+KjF"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { ListToolsRequestSchema, CallToolRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
import { WebSocketServer } from 'ws';
|
|
5
|
+
import { ALL_TOOLS } from './toolSchemas';
|
|
6
|
+
import { SessionRegistry } from './sessionRegistry';
|
|
7
|
+
class WsSessionHandle {
|
|
8
|
+
sessionId;
|
|
9
|
+
ws;
|
|
10
|
+
pending = new Map();
|
|
11
|
+
msgSeq = 0;
|
|
12
|
+
constructor(sessionId, ws) {
|
|
13
|
+
this.sessionId = sessionId;
|
|
14
|
+
this.ws = ws;
|
|
15
|
+
}
|
|
16
|
+
async executeTool(call) {
|
|
17
|
+
const requestId = `${this.sessionId}:${++this.msgSeq}`;
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
this.pending.set(requestId, { resolve, reject });
|
|
20
|
+
if (this.ws.readyState !== this.ws.OPEN) {
|
|
21
|
+
this.pending.delete(requestId);
|
|
22
|
+
reject(new Error('WebSocket is not open'));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
this.ws.send(JSON.stringify({ type: 'tool:call', requestId, call }));
|
|
26
|
+
setTimeout(() => {
|
|
27
|
+
const p = this.pending.get(requestId);
|
|
28
|
+
if (p) {
|
|
29
|
+
this.pending.delete(requestId);
|
|
30
|
+
reject(new Error(`Tool call timed out: ${call.name}`));
|
|
31
|
+
}
|
|
32
|
+
}, 30_000);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
handleMessage(msg) {
|
|
36
|
+
if (msg.type === 'tool:result') {
|
|
37
|
+
const requestId = msg.requestId;
|
|
38
|
+
const pending = this.pending.get(requestId);
|
|
39
|
+
if (pending) {
|
|
40
|
+
this.pending.delete(requestId);
|
|
41
|
+
pending.resolve(msg.result);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
isAlive() {
|
|
46
|
+
return this.ws.readyState === this.ws.OPEN;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function createMcpServer(options = {}) {
|
|
50
|
+
const registry = options.registry ?? new SessionRegistry();
|
|
51
|
+
const wsPort = options.ws?.port ?? 8080;
|
|
52
|
+
const wsHost = options.ws?.host ?? '0.0.0.0';
|
|
53
|
+
const serverInfoName = options.serverInfo?.name ?? 'klinechart-ai-mcp';
|
|
54
|
+
const serverInfoVersion = options.serverInfo?.version ?? '0.0.0';
|
|
55
|
+
const server = new Server({
|
|
56
|
+
name: serverInfoName,
|
|
57
|
+
version: serverInfoVersion,
|
|
58
|
+
}, {
|
|
59
|
+
capabilities: {
|
|
60
|
+
tools: {},
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
64
|
+
tools: ALL_TOOLS.map((t) => ({
|
|
65
|
+
name: t.name,
|
|
66
|
+
description: t.description,
|
|
67
|
+
inputSchema: t.inputSchema,
|
|
68
|
+
})),
|
|
69
|
+
}));
|
|
70
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
71
|
+
const { name, arguments: args } = request.params;
|
|
72
|
+
const schema = ALL_TOOLS.find((t) => t.name === name);
|
|
73
|
+
if (!schema) {
|
|
74
|
+
return {
|
|
75
|
+
content: [
|
|
76
|
+
{
|
|
77
|
+
type: 'text',
|
|
78
|
+
text: JSON.stringify({
|
|
79
|
+
success: false,
|
|
80
|
+
error: `Unknown tool: ${name}`,
|
|
81
|
+
}),
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
isError: true,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const sessions = registry.getActiveSessionIds();
|
|
88
|
+
if (sessions.length === 0) {
|
|
89
|
+
console.warn(`[MCP] CallTool "${name}" but no sessions registered`);
|
|
90
|
+
return {
|
|
91
|
+
content: [
|
|
92
|
+
{
|
|
93
|
+
type: 'text',
|
|
94
|
+
text: JSON.stringify({
|
|
95
|
+
success: false,
|
|
96
|
+
error: 'No browser chart session connected.',
|
|
97
|
+
}),
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
isError: true,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const sessionId = sessions[0];
|
|
104
|
+
const handle = registry.get(sessionId);
|
|
105
|
+
if (!handle) {
|
|
106
|
+
return {
|
|
107
|
+
content: [
|
|
108
|
+
{
|
|
109
|
+
type: 'text',
|
|
110
|
+
text: JSON.stringify({
|
|
111
|
+
success: false,
|
|
112
|
+
error: `Session ${sessionId} not found.`,
|
|
113
|
+
}),
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
isError: true,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const result = await handle.executeTool({
|
|
120
|
+
name,
|
|
121
|
+
input: args ?? {},
|
|
122
|
+
});
|
|
123
|
+
const summary = registry.getSummary(sessionId);
|
|
124
|
+
const texts = [JSON.stringify(result)];
|
|
125
|
+
if (summary)
|
|
126
|
+
texts.push(`Chart state: ${summary}`);
|
|
127
|
+
return {
|
|
128
|
+
content: texts.map((text) => ({ type: 'text', text })),
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
const wss = new WebSocketServer({ port: wsPort, host: wsHost });
|
|
132
|
+
wss.on('error', (err) => {
|
|
133
|
+
console.error(`[MCP] WebSocket server error: ${err.message}`);
|
|
134
|
+
if (err.code === 'EADDRINUSE') {
|
|
135
|
+
console.error(`[MCP] Port ${wsPort} is already in use. Use a different port via WS_PORT env or ws.port option.`);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
wss.on('connection', (ws) => {
|
|
139
|
+
console.error(`[MCP] WS client connected`);
|
|
140
|
+
let handle = null;
|
|
141
|
+
ws.on('message', (raw) => {
|
|
142
|
+
let msg;
|
|
143
|
+
try {
|
|
144
|
+
msg = JSON.parse(raw.toString());
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (msg.type === 'register') {
|
|
150
|
+
const sessionId = msg.sessionId ?? crypto.randomUUID();
|
|
151
|
+
handle = new WsSessionHandle(sessionId, ws);
|
|
152
|
+
registry.register(sessionId, handle);
|
|
153
|
+
console.error(`[MCP] Session registered: ${sessionId} (total=${registry.getActiveSessionIds().length})`);
|
|
154
|
+
ws.send(JSON.stringify({ type: 'registered', sessionId }));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (handle) {
|
|
158
|
+
handle.handleMessage(msg);
|
|
159
|
+
}
|
|
160
|
+
if (msg.type === 'state:update' && handle) {
|
|
161
|
+
registry.updateState(handle.sessionId, msg.descriptions);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
ws.on('close', () => {
|
|
165
|
+
if (handle) {
|
|
166
|
+
console.error(`[MCP] Session disconnected: ${handle.sessionId}`);
|
|
167
|
+
registry.unregister(handle.sessionId);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
ws.on('error', () => {
|
|
171
|
+
if (handle) {
|
|
172
|
+
registry.unregister(handle.sessionId);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
async function start() {
|
|
177
|
+
const transport = new StdioServerTransport();
|
|
178
|
+
await server.connect(transport);
|
|
179
|
+
}
|
|
180
|
+
async function stop() {
|
|
181
|
+
await server.close();
|
|
182
|
+
for (const ws of wss.clients) {
|
|
183
|
+
ws.terminate();
|
|
184
|
+
}
|
|
185
|
+
wss.close();
|
|
186
|
+
}
|
|
187
|
+
return { server, registry, wss, start, stop };
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=mcpServer.js.map
|