@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.
Files changed (55) hide show
  1. package/README.md +130 -0
  2. package/dist/createWithMcp.d.ts +9 -0
  3. package/dist/createWithMcp.d.ts.map +1 -0
  4. package/dist/createWithMcp.js +15 -0
  5. package/dist/createWithMcp.js.map +1 -0
  6. package/dist/describeControllers.d.ts +34 -0
  7. package/dist/describeControllers.d.ts.map +1 -0
  8. package/dist/describeControllers.js +104 -0
  9. package/dist/describeControllers.js.map +1 -0
  10. package/dist/executeTool.d.ts +12 -0
  11. package/dist/executeTool.d.ts.map +1 -0
  12. package/dist/executeTool.js +63 -0
  13. package/dist/executeTool.js.map +1 -0
  14. package/dist/index.d.ts +8 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +7 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/mcpServer.d.ts +35 -0
  19. package/dist/mcpServer.d.ts.map +1 -0
  20. package/dist/mcpServer.js +189 -0
  21. package/dist/mcpServer.js.map +1 -0
  22. package/dist/serialization.d.ts +28 -0
  23. package/dist/serialization.d.ts.map +1 -0
  24. package/dist/serialization.js +53 -0
  25. package/dist/serialization.js.map +1 -0
  26. package/dist/sessionRegistry.d.ts +19 -0
  27. package/dist/sessionRegistry.d.ts.map +1 -0
  28. package/dist/sessionRegistry.js +41 -0
  29. package/dist/sessionRegistry.js.map +1 -0
  30. package/dist/toolSchemas.d.ts +14 -0
  31. package/dist/toolSchemas.d.ts.map +1 -0
  32. package/dist/toolSchemas.js +216 -0
  33. package/dist/toolSchemas.js.map +1 -0
  34. package/dist/types.d.ts +75 -0
  35. package/dist/types.d.ts.map +1 -0
  36. package/dist/types.js +2 -0
  37. package/dist/types.js.map +1 -0
  38. package/package.json +63 -0
  39. package/src/__tests__/chartBridge.integration.test.ts +100 -0
  40. package/src/__tests__/describeControllers.test.ts +163 -0
  41. package/src/__tests__/executeTool.test.ts +187 -0
  42. package/src/__tests__/mcpServer.integration.test.ts +155 -0
  43. package/src/__tests__/mcpServer.test.ts +30 -0
  44. package/src/__tests__/serialization.test.ts +116 -0
  45. package/src/__tests__/sessionRegistry.test.ts +139 -0
  46. package/src/__tests__/toolSchemas.test.ts +149 -0
  47. package/src/createWithMcp.ts +28 -0
  48. package/src/describeControllers.ts +166 -0
  49. package/src/executeTool.ts +92 -0
  50. package/src/index.ts +38 -0
  51. package/src/mcpServer.ts +268 -0
  52. package/src/serialization.ts +88 -0
  53. package/src/sessionRegistry.ts +61 -0
  54. package/src/toolSchemas.ts +235 -0
  55. 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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"}
@@ -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