fixparser-plugin-mcp 9.1.7-27ef5b7b → 9.1.7-28edb130
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/build/cjs/MCPLocal.js +458 -571
- package/build/cjs/MCPLocal.js.map +2 -2
- package/build/esm/MCPLocal.mjs +459 -587
- package/build/esm/MCPLocal.mjs.map +2 -2
- package/build-examples/cjs/example_mcp_local.js +49 -3
- package/build-examples/cjs/example_mcp_local.js.map +4 -4
- package/build-examples/esm/example_mcp_local.mjs +49 -3
- package/build-examples/esm/example_mcp_local.mjs.map +4 -4
- package/package.json +11 -11
- package/types/MCPLocal.d.ts +3 -1
package/build/esm/MCPLocal.mjs
CHANGED
|
@@ -1,194 +1,9 @@
|
|
|
1
1
|
// src/MCPLocal.ts
|
|
2
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
GetPromptRequestSchema,
|
|
7
|
-
ListPromptsRequestSchema,
|
|
8
|
-
ListResourcesRequestSchema,
|
|
9
|
-
ListToolsRequestSchema
|
|
10
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
11
|
-
import {
|
|
12
|
-
Field,
|
|
13
|
-
Fields,
|
|
14
|
-
HandlInst,
|
|
15
|
-
MDEntryType,
|
|
16
|
-
Messages,
|
|
17
|
-
OrdType,
|
|
18
|
-
SubscriptionRequestType,
|
|
19
|
-
TimeInForce
|
|
20
|
-
} from "fixparser";
|
|
21
|
-
var parseInputSchema = {
|
|
22
|
-
type: "object",
|
|
23
|
-
properties: {
|
|
24
|
-
fixString: {
|
|
25
|
-
type: "string",
|
|
26
|
-
description: "FIX message string to parse"
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
required: ["fixString"]
|
|
30
|
-
};
|
|
31
|
-
var parseToJSONInputSchema = {
|
|
32
|
-
type: "object",
|
|
33
|
-
properties: {
|
|
34
|
-
fixString: {
|
|
35
|
-
type: "string",
|
|
36
|
-
description: "FIX message string to parse"
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
required: ["fixString"]
|
|
40
|
-
};
|
|
41
|
-
var newOrderSingleInputSchema = {
|
|
42
|
-
type: "object",
|
|
43
|
-
properties: {
|
|
44
|
-
clOrdID: {
|
|
45
|
-
type: "string",
|
|
46
|
-
description: "Client Order ID"
|
|
47
|
-
},
|
|
48
|
-
handlInst: {
|
|
49
|
-
type: "string",
|
|
50
|
-
enum: ["1", "2", "3"],
|
|
51
|
-
default: HandlInst.AutomatedExecutionNoIntervention,
|
|
52
|
-
description: "Handling instruction"
|
|
53
|
-
},
|
|
54
|
-
quantity: {
|
|
55
|
-
type: "number",
|
|
56
|
-
description: "Order quantity"
|
|
57
|
-
},
|
|
58
|
-
price: {
|
|
59
|
-
type: "number",
|
|
60
|
-
description: "Order price"
|
|
61
|
-
},
|
|
62
|
-
ordType: {
|
|
63
|
-
type: "string",
|
|
64
|
-
enum: [
|
|
65
|
-
"1",
|
|
66
|
-
"2",
|
|
67
|
-
"3",
|
|
68
|
-
"4",
|
|
69
|
-
"5",
|
|
70
|
-
"6",
|
|
71
|
-
"7",
|
|
72
|
-
"8",
|
|
73
|
-
"9",
|
|
74
|
-
"A",
|
|
75
|
-
"B",
|
|
76
|
-
"C",
|
|
77
|
-
"D",
|
|
78
|
-
"E",
|
|
79
|
-
"F",
|
|
80
|
-
"G",
|
|
81
|
-
"H",
|
|
82
|
-
"I",
|
|
83
|
-
"J",
|
|
84
|
-
"K",
|
|
85
|
-
"L",
|
|
86
|
-
"M",
|
|
87
|
-
"P",
|
|
88
|
-
"Q",
|
|
89
|
-
"R",
|
|
90
|
-
"S"
|
|
91
|
-
],
|
|
92
|
-
default: OrdType.Market,
|
|
93
|
-
description: "Order type"
|
|
94
|
-
},
|
|
95
|
-
side: {
|
|
96
|
-
type: "string",
|
|
97
|
-
enum: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H"],
|
|
98
|
-
description: "Order side (1=Buy, 2=Sell)"
|
|
99
|
-
},
|
|
100
|
-
symbol: {
|
|
101
|
-
type: "string",
|
|
102
|
-
description: "Trading symbol"
|
|
103
|
-
},
|
|
104
|
-
timeInForce: {
|
|
105
|
-
type: "string",
|
|
106
|
-
enum: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C"],
|
|
107
|
-
default: TimeInForce.Day,
|
|
108
|
-
description: "Time in force"
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
required: ["clOrdID", "quantity", "price", "side", "symbol"]
|
|
112
|
-
};
|
|
113
|
-
var marketDataRequestInputSchema = {
|
|
114
|
-
type: "object",
|
|
115
|
-
properties: {
|
|
116
|
-
mdUpdateType: {
|
|
117
|
-
type: "string",
|
|
118
|
-
enum: ["0", "1"],
|
|
119
|
-
default: "0",
|
|
120
|
-
description: "Market data update type"
|
|
121
|
-
},
|
|
122
|
-
symbol: {
|
|
123
|
-
type: "string",
|
|
124
|
-
description: "Trading symbol"
|
|
125
|
-
},
|
|
126
|
-
mdReqID: {
|
|
127
|
-
type: "string",
|
|
128
|
-
description: "Market data request ID"
|
|
129
|
-
},
|
|
130
|
-
subscriptionRequestType: {
|
|
131
|
-
type: "string",
|
|
132
|
-
enum: ["0", "1", "2"],
|
|
133
|
-
default: SubscriptionRequestType.SnapshotAndUpdates,
|
|
134
|
-
description: "Subscription request type"
|
|
135
|
-
},
|
|
136
|
-
mdEntryType: {
|
|
137
|
-
type: "string",
|
|
138
|
-
enum: [
|
|
139
|
-
"0",
|
|
140
|
-
"1",
|
|
141
|
-
"2",
|
|
142
|
-
"3",
|
|
143
|
-
"4",
|
|
144
|
-
"5",
|
|
145
|
-
"6",
|
|
146
|
-
"7",
|
|
147
|
-
"8",
|
|
148
|
-
"9",
|
|
149
|
-
"A",
|
|
150
|
-
"B",
|
|
151
|
-
"C",
|
|
152
|
-
"D",
|
|
153
|
-
"E",
|
|
154
|
-
"F",
|
|
155
|
-
"G",
|
|
156
|
-
"H",
|
|
157
|
-
"J",
|
|
158
|
-
"K",
|
|
159
|
-
"L",
|
|
160
|
-
"M",
|
|
161
|
-
"N",
|
|
162
|
-
"O",
|
|
163
|
-
"P",
|
|
164
|
-
"Q",
|
|
165
|
-
"S",
|
|
166
|
-
"R",
|
|
167
|
-
"T",
|
|
168
|
-
"U",
|
|
169
|
-
"V",
|
|
170
|
-
"W",
|
|
171
|
-
"X",
|
|
172
|
-
"Y",
|
|
173
|
-
"Z",
|
|
174
|
-
"a",
|
|
175
|
-
"b",
|
|
176
|
-
"c",
|
|
177
|
-
"d",
|
|
178
|
-
"e",
|
|
179
|
-
"g",
|
|
180
|
-
"h",
|
|
181
|
-
"i",
|
|
182
|
-
"t"
|
|
183
|
-
],
|
|
184
|
-
default: MDEntryType.Bid,
|
|
185
|
-
description: "Market data entry type"
|
|
186
|
-
}
|
|
187
|
-
},
|
|
188
|
-
required: ["symbol", "mdReqID"]
|
|
189
|
-
};
|
|
4
|
+
import { Field, Fields, Messages } from "fixparser";
|
|
5
|
+
import { z } from "zod";
|
|
190
6
|
var MCPLocal = class {
|
|
191
|
-
logger;
|
|
192
7
|
parser;
|
|
193
8
|
server = new Server(
|
|
194
9
|
{
|
|
@@ -198,7 +13,6 @@ var MCPLocal = class {
|
|
|
198
13
|
{
|
|
199
14
|
capabilities: {
|
|
200
15
|
tools: {},
|
|
201
|
-
prompts: {},
|
|
202
16
|
resources: {}
|
|
203
17
|
}
|
|
204
18
|
}
|
|
@@ -206,33 +20,69 @@ var MCPLocal = class {
|
|
|
206
20
|
transport = new StdioServerTransport();
|
|
207
21
|
onReady = void 0;
|
|
208
22
|
pendingRequests = /* @__PURE__ */ new Map();
|
|
23
|
+
verifiedOrders = /* @__PURE__ */ new Map();
|
|
24
|
+
// Store market data prices with timestamps
|
|
25
|
+
marketDataPrices = /* @__PURE__ */ new Map();
|
|
26
|
+
MAX_PRICE_HISTORY = 1e5;
|
|
27
|
+
// Maximum number of price points to store per symbol
|
|
209
28
|
constructor({ logger, onReady }) {
|
|
210
|
-
if (logger) this.logger = logger;
|
|
211
29
|
if (onReady) this.onReady = onReady;
|
|
212
30
|
}
|
|
213
31
|
async register(parser) {
|
|
214
32
|
this.parser = parser;
|
|
215
33
|
this.parser.addOnMessageCallback((message) => {
|
|
216
|
-
this.logger
|
|
34
|
+
this.parser?.logger.log({
|
|
217
35
|
level: "info",
|
|
218
|
-
message: `
|
|
36
|
+
message: `MCP Server received message: ${message.messageType}: ${message.description}`
|
|
219
37
|
});
|
|
220
38
|
const msgType = message.messageType;
|
|
221
|
-
if (msgType === Messages.MarketDataSnapshotFullRefresh || msgType === Messages.ExecutionReport) {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
39
|
+
if (msgType === Messages.MarketDataSnapshotFullRefresh || msgType === Messages.ExecutionReport || msgType === Messages.Reject || msgType === Messages.MarketDataIncrementalRefresh) {
|
|
40
|
+
this.parser?.logger.log({
|
|
41
|
+
level: "info",
|
|
42
|
+
message: `MCP Server handling message type: ${msgType}`
|
|
43
|
+
});
|
|
44
|
+
let id;
|
|
45
|
+
if (msgType === Messages.MarketDataIncrementalRefresh || msgType === Messages.MarketDataSnapshotFullRefresh) {
|
|
46
|
+
const symbol = message.getField(Fields.Symbol);
|
|
47
|
+
const price = message.getField(Fields.MDEntryPx);
|
|
48
|
+
const timestamp = message.getField(Fields.MDEntryTime)?.value || Date.now();
|
|
49
|
+
if (symbol?.value && price?.value) {
|
|
50
|
+
const symbolStr = String(symbol.value);
|
|
51
|
+
const priceNum = Number(price.value);
|
|
52
|
+
const priceHistory = this.marketDataPrices.get(symbolStr) || [];
|
|
53
|
+
priceHistory.push({
|
|
54
|
+
timestamp: Number(timestamp),
|
|
55
|
+
price: priceNum
|
|
56
|
+
});
|
|
57
|
+
if (priceHistory.length > this.MAX_PRICE_HISTORY) {
|
|
58
|
+
priceHistory.shift();
|
|
230
59
|
}
|
|
60
|
+
this.marketDataPrices.set(symbolStr, priceHistory);
|
|
61
|
+
this.parser?.logger.log({
|
|
62
|
+
level: "info",
|
|
63
|
+
message: `MCP Server added ${symbol}: ${priceNum}`
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (msgType === Messages.MarketDataSnapshotFullRefresh) {
|
|
68
|
+
const mdReqID = message.getField(Fields.MDReqID);
|
|
69
|
+
if (mdReqID) id = String(mdReqID.value);
|
|
70
|
+
} else if (msgType === Messages.ExecutionReport) {
|
|
71
|
+
const clOrdID = message.getField(Fields.ClOrdID);
|
|
72
|
+
if (clOrdID) id = String(clOrdID.value);
|
|
73
|
+
} else if (msgType === Messages.Reject) {
|
|
74
|
+
const refSeqNum = message.getField(Fields.RefSeqNum);
|
|
75
|
+
if (refSeqNum) id = String(refSeqNum.value);
|
|
76
|
+
}
|
|
77
|
+
if (id) {
|
|
78
|
+
const callback = this.pendingRequests.get(id);
|
|
79
|
+
if (callback) {
|
|
80
|
+
callback(message);
|
|
81
|
+
this.pendingRequests.delete(id);
|
|
231
82
|
}
|
|
232
83
|
}
|
|
233
84
|
}
|
|
234
85
|
});
|
|
235
|
-
this.logger = parser.logger;
|
|
236
86
|
this.addWorkflows();
|
|
237
87
|
await this.server.connect(this.transport);
|
|
238
88
|
if (this.onReady) {
|
|
@@ -241,450 +91,472 @@ var MCPLocal = class {
|
|
|
241
91
|
}
|
|
242
92
|
addWorkflows() {
|
|
243
93
|
if (!this.parser) {
|
|
244
|
-
this.logger?.log({
|
|
245
|
-
level: "error",
|
|
246
|
-
message: "FIXParser (MCP): -- FIXParser instance not initialized. Ignoring setup of workflows..."
|
|
247
|
-
});
|
|
248
94
|
return;
|
|
249
95
|
}
|
|
250
96
|
if (!this.server) {
|
|
251
|
-
this.logger?.log({
|
|
252
|
-
level: "error",
|
|
253
|
-
message: "FIXParser (MCP): -- MCP Server not initialized. Ignoring setup of workflows..."
|
|
254
|
-
});
|
|
255
97
|
return;
|
|
256
98
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
result[key] = value;
|
|
267
|
-
} else if (prop.default !== void 0) {
|
|
268
|
-
result[key] = prop.default;
|
|
99
|
+
this.server.setRequestHandler(z.object({ method: z.literal("parse") }), async (request, extra) => {
|
|
100
|
+
try {
|
|
101
|
+
const args = request.params;
|
|
102
|
+
const parsedMessage = this.parser?.parse(args.fixString);
|
|
103
|
+
if (!parsedMessage || parsedMessage.length === 0) {
|
|
104
|
+
return {
|
|
105
|
+
content: [{ type: "text", text: "Error: Failed to parse FIX string" }],
|
|
106
|
+
isError: true
|
|
107
|
+
};
|
|
269
108
|
}
|
|
109
|
+
return {
|
|
110
|
+
content: [
|
|
111
|
+
{
|
|
112
|
+
type: "text",
|
|
113
|
+
text: `${parsedMessage[0].description}
|
|
114
|
+
${parsedMessage[0].messageTypeDescription}`
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
};
|
|
118
|
+
} catch (error) {
|
|
119
|
+
return {
|
|
120
|
+
content: [
|
|
121
|
+
{
|
|
122
|
+
type: "text",
|
|
123
|
+
text: `Error: ${error instanceof Error ? error.message : "Failed to parse FIX string"}`
|
|
124
|
+
}
|
|
125
|
+
],
|
|
126
|
+
isError: true
|
|
127
|
+
};
|
|
270
128
|
}
|
|
271
|
-
return result;
|
|
272
|
-
};
|
|
273
|
-
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
274
|
-
return {
|
|
275
|
-
resources: []
|
|
276
|
-
};
|
|
277
129
|
});
|
|
278
|
-
this.server.setRequestHandler(
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
},
|
|
286
|
-
{
|
|
287
|
-
name: "parseToJSON",
|
|
288
|
-
description: "Parses a FIX message into JSON",
|
|
289
|
-
inputSchema: parseToJSONInputSchema
|
|
290
|
-
},
|
|
291
|
-
{
|
|
292
|
-
name: "newOrderSingle",
|
|
293
|
-
description: "Creates and sends a New Order Single",
|
|
294
|
-
inputSchema: newOrderSingleInputSchema
|
|
295
|
-
},
|
|
296
|
-
{
|
|
297
|
-
name: "marketDataRequest",
|
|
298
|
-
description: "Sends a request for Market Data with the given symbol",
|
|
299
|
-
inputSchema: marketDataRequestInputSchema
|
|
300
|
-
}
|
|
301
|
-
]
|
|
302
|
-
};
|
|
303
|
-
});
|
|
304
|
-
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
305
|
-
const { name, arguments: args } = request.params;
|
|
306
|
-
switch (name) {
|
|
307
|
-
case "parse": {
|
|
308
|
-
try {
|
|
309
|
-
const { fixString } = validateArgs(args, parseInputSchema);
|
|
310
|
-
const parsedMessage = this.parser?.parse(fixString);
|
|
311
|
-
if (!parsedMessage || parsedMessage.length === 0) {
|
|
312
|
-
return {
|
|
313
|
-
isError: true,
|
|
314
|
-
content: [{ type: "text", text: "Error: Failed to parse FIX string" }]
|
|
315
|
-
};
|
|
316
|
-
}
|
|
130
|
+
this.server.setRequestHandler(
|
|
131
|
+
z.object({ method: z.literal("parseToJSON") }),
|
|
132
|
+
async (request, extra) => {
|
|
133
|
+
try {
|
|
134
|
+
const args = request.params;
|
|
135
|
+
const parsedMessage = this.parser?.parse(args.fixString);
|
|
136
|
+
if (!parsedMessage || parsedMessage.length === 0) {
|
|
317
137
|
return {
|
|
318
|
-
content: [
|
|
319
|
-
|
|
320
|
-
type: "text",
|
|
321
|
-
text: `Parsed FIX message: ${fixString} (placeholder implementation)`
|
|
322
|
-
}
|
|
323
|
-
]
|
|
324
|
-
};
|
|
325
|
-
} catch (error) {
|
|
326
|
-
return {
|
|
327
|
-
isError: true,
|
|
328
|
-
content: [
|
|
329
|
-
{
|
|
330
|
-
type: "text",
|
|
331
|
-
text: `Error: ${error instanceof Error ? error.message : "Failed to parse FIX string"}`
|
|
332
|
-
}
|
|
333
|
-
]
|
|
138
|
+
content: [{ type: "text", text: "Error: Failed to parse FIX string" }],
|
|
139
|
+
isError: true
|
|
334
140
|
};
|
|
335
141
|
}
|
|
142
|
+
return {
|
|
143
|
+
content: [
|
|
144
|
+
{
|
|
145
|
+
type: "text",
|
|
146
|
+
text: `${parsedMessage[0].toFIXJSON()}`
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
};
|
|
150
|
+
} catch (error) {
|
|
151
|
+
return {
|
|
152
|
+
content: [
|
|
153
|
+
{
|
|
154
|
+
type: "text",
|
|
155
|
+
text: `Error: ${error instanceof Error ? error.message : "Failed to parse FIX string"}`
|
|
156
|
+
}
|
|
157
|
+
],
|
|
158
|
+
isError: true
|
|
159
|
+
};
|
|
336
160
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
this.server.setRequestHandler(
|
|
164
|
+
z.object({ method: z.literal("verifyOrder") }),
|
|
165
|
+
async (request, extra) => {
|
|
166
|
+
try {
|
|
167
|
+
const args = request.params;
|
|
168
|
+
this.verifiedOrders.set(args.clOrdID, {
|
|
169
|
+
clOrdID: args.clOrdID,
|
|
170
|
+
handlInst: args.handlInst,
|
|
171
|
+
quantity: Number.parseFloat(args.quantity),
|
|
172
|
+
price: Number.parseFloat(args.price),
|
|
173
|
+
ordType: args.ordType,
|
|
174
|
+
side: args.side,
|
|
175
|
+
symbol: args.symbol,
|
|
176
|
+
timeInForce: args.timeInForce
|
|
177
|
+
});
|
|
178
|
+
const ordTypeNames = {
|
|
179
|
+
"1": "Market",
|
|
180
|
+
"2": "Limit",
|
|
181
|
+
"3": "Stop",
|
|
182
|
+
"4": "StopLimit",
|
|
183
|
+
"5": "MarketOnClose",
|
|
184
|
+
"6": "WithOrWithout",
|
|
185
|
+
"7": "LimitOrBetter",
|
|
186
|
+
"8": "LimitWithOrWithout",
|
|
187
|
+
"9": "OnBasis",
|
|
188
|
+
A: "OnClose",
|
|
189
|
+
B: "LimitOnClose",
|
|
190
|
+
C: "ForexMarket",
|
|
191
|
+
D: "PreviouslyQuoted",
|
|
192
|
+
E: "PreviouslyIndicated",
|
|
193
|
+
F: "ForexLimit",
|
|
194
|
+
G: "ForexSwap",
|
|
195
|
+
H: "ForexPreviouslyQuoted",
|
|
196
|
+
I: "Funari",
|
|
197
|
+
J: "MarketIfTouched",
|
|
198
|
+
K: "MarketWithLeftOverAsLimit",
|
|
199
|
+
L: "PreviousFundValuationPoint",
|
|
200
|
+
M: "NextFundValuationPoint",
|
|
201
|
+
P: "Pegged",
|
|
202
|
+
Q: "CounterOrderSelection",
|
|
203
|
+
R: "StopOnBidOrOffer",
|
|
204
|
+
S: "StopLimitOnBidOrOffer"
|
|
205
|
+
};
|
|
206
|
+
const sideNames = {
|
|
207
|
+
"1": "Buy",
|
|
208
|
+
"2": "Sell",
|
|
209
|
+
"3": "BuyMinus",
|
|
210
|
+
"4": "SellPlus",
|
|
211
|
+
"5": "SellShort",
|
|
212
|
+
"6": "SellShortExempt",
|
|
213
|
+
"7": "Undisclosed",
|
|
214
|
+
"8": "Cross",
|
|
215
|
+
"9": "CrossShort",
|
|
216
|
+
A: "CrossShortExempt",
|
|
217
|
+
B: "AsDefined",
|
|
218
|
+
C: "Opposite",
|
|
219
|
+
D: "Subscribe",
|
|
220
|
+
E: "Redeem",
|
|
221
|
+
F: "Lend",
|
|
222
|
+
G: "Borrow",
|
|
223
|
+
H: "SellUndisclosed"
|
|
224
|
+
};
|
|
225
|
+
const timeInForceNames = {
|
|
226
|
+
"0": "Day",
|
|
227
|
+
"1": "GoodTillCancel",
|
|
228
|
+
"2": "AtTheOpening",
|
|
229
|
+
"3": "ImmediateOrCancel",
|
|
230
|
+
"4": "FillOrKill",
|
|
231
|
+
"5": "GoodTillCrossing",
|
|
232
|
+
"6": "GoodTillDate",
|
|
233
|
+
"7": "AtTheClose",
|
|
234
|
+
"8": "GoodThroughCrossing",
|
|
235
|
+
"9": "AtCrossing",
|
|
236
|
+
A: "GoodForTime",
|
|
237
|
+
B: "GoodForAuction",
|
|
238
|
+
C: "GoodForMonth"
|
|
239
|
+
};
|
|
240
|
+
const handlInstNames = {
|
|
241
|
+
"1": "AutomatedExecutionNoIntervention",
|
|
242
|
+
"2": "AutomatedExecutionInterventionOK",
|
|
243
|
+
"3": "ManualOrder"
|
|
244
|
+
};
|
|
245
|
+
return {
|
|
246
|
+
content: [
|
|
247
|
+
{
|
|
248
|
+
type: "text",
|
|
249
|
+
text: `VERIFICATION: All parameters valid. Ready to proceed with order execution.
|
|
250
|
+
|
|
251
|
+
Parameters verified:
|
|
252
|
+
- ClOrdID: ${args.clOrdID}
|
|
253
|
+
- HandlInst: ${args.handlInst} (${handlInstNames[args.handlInst]})
|
|
254
|
+
- Quantity: ${args.quantity}
|
|
255
|
+
- Price: ${args.price}
|
|
256
|
+
- OrdType: ${args.ordType} (${ordTypeNames[args.ordType]})
|
|
257
|
+
- Side: ${args.side} (${sideNames[args.side]})
|
|
258
|
+
- Symbol: ${args.symbol}
|
|
259
|
+
- TimeInForce: ${args.timeInForce} (${timeInForceNames[args.timeInForce]})
|
|
260
|
+
|
|
261
|
+
To execute this order, call the executeOrder tool with these exact same parameters.`
|
|
262
|
+
}
|
|
263
|
+
]
|
|
264
|
+
};
|
|
265
|
+
} catch (error) {
|
|
266
|
+
return {
|
|
267
|
+
content: [
|
|
268
|
+
{
|
|
269
|
+
type: "text",
|
|
270
|
+
text: `Error: ${error instanceof Error ? error.message : "Failed to verify order parameters"}`
|
|
271
|
+
}
|
|
272
|
+
],
|
|
273
|
+
isError: true
|
|
274
|
+
};
|
|
366
275
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
new Field(Fields.SenderCompID, this.parser?.sender),
|
|
377
|
-
new Field(Fields.TargetCompID, this.parser?.target),
|
|
378
|
-
new Field(Fields.SendingTime, this.parser?.getTimestamp()),
|
|
379
|
-
new Field(Fields.ClOrdID, clOrdID),
|
|
380
|
-
new Field(Fields.Side, side),
|
|
381
|
-
new Field(Fields.Symbol, symbol),
|
|
382
|
-
new Field(Fields.OrderQty, quantity),
|
|
383
|
-
new Field(Fields.Price, price),
|
|
384
|
-
new Field(Fields.OrdType, ordType),
|
|
385
|
-
new Field(Fields.HandlInst, handlInst),
|
|
386
|
-
new Field(Fields.TimeInForce, timeInForce),
|
|
387
|
-
new Field(Fields.TransactTime, this.parser?.getTimestamp())
|
|
388
|
-
);
|
|
389
|
-
if (!this.parser?.connected) {
|
|
390
|
-
this.logger?.log({
|
|
391
|
-
level: "error",
|
|
392
|
-
message: "FIXParser (MCP): -- Not connected. Ignoring message."
|
|
393
|
-
});
|
|
394
|
-
return {
|
|
395
|
-
isError: true,
|
|
396
|
-
content: [
|
|
397
|
-
{
|
|
398
|
-
type: "text",
|
|
399
|
-
text: "Error: Not connected. Ignoring message."
|
|
400
|
-
}
|
|
401
|
-
]
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
this.parser?.send(order);
|
|
405
|
-
this.logger?.log({
|
|
406
|
-
level: "info",
|
|
407
|
-
message: `FIXParser (MCP): (${this.parser?.protocol?.toUpperCase()}): >> sent ${order?.description}`
|
|
408
|
-
});
|
|
409
|
-
const fixData = await response;
|
|
410
|
-
return {
|
|
411
|
-
content: [
|
|
412
|
-
{
|
|
413
|
-
type: "text",
|
|
414
|
-
text: `Execution Report for order ${clOrdID}: ${JSON.stringify(fixData.toFIXJSON())}`
|
|
415
|
-
}
|
|
416
|
-
]
|
|
417
|
-
};
|
|
418
|
-
} catch (error) {
|
|
276
|
+
}
|
|
277
|
+
);
|
|
278
|
+
this.server.setRequestHandler(
|
|
279
|
+
z.object({ method: z.literal("executeOrder") }),
|
|
280
|
+
async (request, extra) => {
|
|
281
|
+
try {
|
|
282
|
+
const args = request.params;
|
|
283
|
+
const verifiedOrder = this.verifiedOrders.get(args.clOrdID);
|
|
284
|
+
if (!verifiedOrder) {
|
|
419
285
|
return {
|
|
420
|
-
isError: true,
|
|
421
286
|
content: [
|
|
422
287
|
{
|
|
423
288
|
type: "text",
|
|
424
|
-
text: `Error: ${
|
|
289
|
+
text: `Error: Order ${args.clOrdID} has not been verified. Please call verifyOrder first.`
|
|
425
290
|
}
|
|
426
|
-
]
|
|
291
|
+
],
|
|
292
|
+
isError: true
|
|
427
293
|
};
|
|
428
294
|
}
|
|
429
|
-
|
|
430
|
-
case "marketDataRequest": {
|
|
431
|
-
try {
|
|
432
|
-
const { mdUpdateType, symbol, mdReqID, subscriptionRequestType, mdEntryType } = validateArgs(
|
|
433
|
-
args,
|
|
434
|
-
marketDataRequestInputSchema
|
|
435
|
-
);
|
|
436
|
-
const response = new Promise((resolve) => {
|
|
437
|
-
this.pendingRequests.set(mdReqID, resolve);
|
|
438
|
-
});
|
|
439
|
-
const marketDataRequest = this.parser?.createMessage(
|
|
440
|
-
new Field(Fields.MsgType, Messages.MarketDataRequest),
|
|
441
|
-
new Field(Fields.SenderCompID, this.parser?.sender),
|
|
442
|
-
new Field(Fields.MsgSeqNum, this.parser?.getNextTargetMsgSeqNum()),
|
|
443
|
-
new Field(Fields.TargetCompID, this.parser?.target),
|
|
444
|
-
new Field(Fields.SendingTime, this.parser?.getTimestamp()),
|
|
445
|
-
new Field(Fields.MarketDepth, 0),
|
|
446
|
-
new Field(Fields.MDUpdateType, mdUpdateType),
|
|
447
|
-
new Field(Fields.NoRelatedSym, 1),
|
|
448
|
-
new Field(Fields.Symbol, symbol),
|
|
449
|
-
new Field(Fields.MDReqID, mdReqID),
|
|
450
|
-
new Field(Fields.SubscriptionRequestType, subscriptionRequestType),
|
|
451
|
-
new Field(Fields.NoMDEntryTypes, 1),
|
|
452
|
-
new Field(Fields.MDEntryType, mdEntryType)
|
|
453
|
-
);
|
|
454
|
-
if (!this.parser?.connected) {
|
|
455
|
-
this.logger?.log({
|
|
456
|
-
level: "error",
|
|
457
|
-
message: "FIXParser (MCP): -- Not connected. Ignoring message."
|
|
458
|
-
});
|
|
459
|
-
return {
|
|
460
|
-
isError: true,
|
|
461
|
-
content: [
|
|
462
|
-
{
|
|
463
|
-
type: "text",
|
|
464
|
-
text: "Error: Not connected. Ignoring message."
|
|
465
|
-
}
|
|
466
|
-
]
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
this.parser?.send(marketDataRequest);
|
|
470
|
-
this.logger?.log({
|
|
471
|
-
level: "info",
|
|
472
|
-
message: `FIXParser (MCP): (${this.parser?.protocol?.toUpperCase()}): >> sent ${marketDataRequest?.description}`
|
|
473
|
-
});
|
|
474
|
-
const fixData = await response;
|
|
295
|
+
if (verifiedOrder.handlInst !== args.handlInst || verifiedOrder.quantity !== Number.parseFloat(args.quantity) || verifiedOrder.price !== Number.parseFloat(args.price) || verifiedOrder.ordType !== args.ordType || verifiedOrder.side !== args.side || verifiedOrder.symbol !== args.symbol || verifiedOrder.timeInForce !== args.timeInForce) {
|
|
475
296
|
return {
|
|
476
297
|
content: [
|
|
477
298
|
{
|
|
478
299
|
type: "text",
|
|
479
|
-
text:
|
|
300
|
+
text: "Error: Order parameters do not match the verified order. Please use the exact same parameters that were verified."
|
|
480
301
|
}
|
|
481
|
-
]
|
|
302
|
+
],
|
|
303
|
+
isError: true
|
|
482
304
|
};
|
|
483
|
-
}
|
|
305
|
+
}
|
|
306
|
+
const response = new Promise((resolve) => {
|
|
307
|
+
this.pendingRequests.set(args.clOrdID, resolve);
|
|
308
|
+
});
|
|
309
|
+
const order = this.parser?.createMessage(
|
|
310
|
+
new Field(Fields.MsgType, Messages.NewOrderSingle),
|
|
311
|
+
new Field(Fields.MsgSeqNum, this.parser?.getNextTargetMsgSeqNum()),
|
|
312
|
+
new Field(Fields.SenderCompID, this.parser?.sender),
|
|
313
|
+
new Field(Fields.TargetCompID, this.parser?.target),
|
|
314
|
+
new Field(Fields.SendingTime, this.parser?.getTimestamp()),
|
|
315
|
+
new Field(Fields.ClOrdID, args.clOrdID),
|
|
316
|
+
new Field(Fields.Side, args.side),
|
|
317
|
+
new Field(Fields.Symbol, args.symbol),
|
|
318
|
+
new Field(Fields.OrderQty, Number.parseFloat(args.quantity)),
|
|
319
|
+
new Field(Fields.Price, Number.parseFloat(args.price)),
|
|
320
|
+
new Field(Fields.OrdType, args.ordType),
|
|
321
|
+
new Field(Fields.HandlInst, args.handlInst),
|
|
322
|
+
new Field(Fields.TimeInForce, args.timeInForce),
|
|
323
|
+
new Field(Fields.TransactTime, this.parser?.getTimestamp())
|
|
324
|
+
);
|
|
325
|
+
if (!this.parser?.connected) {
|
|
484
326
|
return {
|
|
485
|
-
isError: true,
|
|
486
327
|
content: [
|
|
487
328
|
{
|
|
488
329
|
type: "text",
|
|
489
|
-
text:
|
|
330
|
+
text: "Error: Not connected. Ignoring message."
|
|
490
331
|
}
|
|
491
|
-
]
|
|
332
|
+
],
|
|
333
|
+
isError: true
|
|
492
334
|
};
|
|
493
335
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
500
|
-
return {
|
|
501
|
-
prompts: [
|
|
502
|
-
{
|
|
503
|
-
name: "parse",
|
|
504
|
-
description: "Parses a FIX message and describes it in plain language",
|
|
505
|
-
arguments: [
|
|
506
|
-
{
|
|
507
|
-
name: "fixString",
|
|
508
|
-
description: "FIX message string to parse",
|
|
509
|
-
required: true
|
|
510
|
-
}
|
|
511
|
-
]
|
|
512
|
-
},
|
|
513
|
-
{
|
|
514
|
-
name: "parseToJSON",
|
|
515
|
-
description: "Parses a FIX message into JSON",
|
|
516
|
-
arguments: [
|
|
517
|
-
{
|
|
518
|
-
name: "fixString",
|
|
519
|
-
description: "FIX message string to parse",
|
|
520
|
-
required: true
|
|
521
|
-
}
|
|
522
|
-
]
|
|
523
|
-
},
|
|
524
|
-
{
|
|
525
|
-
name: "newOrderSingle",
|
|
526
|
-
description: "Creates and sends a New Order Single",
|
|
527
|
-
arguments: [
|
|
528
|
-
{
|
|
529
|
-
name: "clOrdID",
|
|
530
|
-
description: "Client Order ID",
|
|
531
|
-
required: true
|
|
532
|
-
},
|
|
533
|
-
{
|
|
534
|
-
name: "handlInst",
|
|
535
|
-
description: "Handling instruction",
|
|
536
|
-
required: false
|
|
537
|
-
},
|
|
538
|
-
{
|
|
539
|
-
name: "quantity",
|
|
540
|
-
description: "Order quantity",
|
|
541
|
-
required: true
|
|
542
|
-
},
|
|
543
|
-
{
|
|
544
|
-
name: "price",
|
|
545
|
-
description: "Order price",
|
|
546
|
-
required: true
|
|
547
|
-
},
|
|
548
|
-
{
|
|
549
|
-
name: "ordType",
|
|
550
|
-
description: "Order type",
|
|
551
|
-
required: false
|
|
552
|
-
},
|
|
553
|
-
{
|
|
554
|
-
name: "side",
|
|
555
|
-
description: "Order side (1=Buy, 2=Sell)",
|
|
556
|
-
required: true
|
|
557
|
-
},
|
|
558
|
-
{
|
|
559
|
-
name: "symbol",
|
|
560
|
-
description: "Trading symbol",
|
|
561
|
-
required: true
|
|
562
|
-
},
|
|
563
|
-
{
|
|
564
|
-
name: "timeInForce",
|
|
565
|
-
description: "Time in force",
|
|
566
|
-
required: false
|
|
567
|
-
}
|
|
568
|
-
]
|
|
569
|
-
},
|
|
570
|
-
{
|
|
571
|
-
name: "marketDataRequest",
|
|
572
|
-
description: "Sends a request for Market Data with the given symbol",
|
|
573
|
-
arguments: [
|
|
574
|
-
{
|
|
575
|
-
name: "mdUpdateType",
|
|
576
|
-
description: "Market data update type",
|
|
577
|
-
required: false
|
|
578
|
-
},
|
|
579
|
-
{
|
|
580
|
-
name: "symbol",
|
|
581
|
-
description: "Trading symbol",
|
|
582
|
-
required: true
|
|
583
|
-
},
|
|
584
|
-
{
|
|
585
|
-
name: "mdReqID",
|
|
586
|
-
description: "Market data request ID",
|
|
587
|
-
required: true
|
|
588
|
-
},
|
|
589
|
-
{
|
|
590
|
-
name: "subscriptionRequestType",
|
|
591
|
-
description: "Subscription request type",
|
|
592
|
-
required: false
|
|
593
|
-
},
|
|
336
|
+
this.parser?.send(order);
|
|
337
|
+
const fixData = await response;
|
|
338
|
+
this.verifiedOrders.delete(args.clOrdID);
|
|
339
|
+
return {
|
|
340
|
+
content: [
|
|
594
341
|
{
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
required: false
|
|
342
|
+
type: "text",
|
|
343
|
+
text: fixData.messageType === Messages.Reject ? `Reject message for order ${args.clOrdID}: ${JSON.stringify(fixData.toFIXJSON())}` : `Execution Report for order ${args.clOrdID}: ${JSON.stringify(fixData.toFIXJSON())}`
|
|
598
344
|
}
|
|
599
345
|
]
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
};
|
|
603
|
-
});
|
|
604
|
-
this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
605
|
-
const { name, arguments: args } = request.params;
|
|
606
|
-
switch (name) {
|
|
607
|
-
case "parse": {
|
|
608
|
-
const fixString = args?.fixString || "";
|
|
346
|
+
};
|
|
347
|
+
} catch (error) {
|
|
609
348
|
return {
|
|
610
|
-
|
|
349
|
+
content: [
|
|
611
350
|
{
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
type: "text",
|
|
615
|
-
text: `Please parse and explain this FIX message: ${fixString}`
|
|
616
|
-
}
|
|
351
|
+
type: "text",
|
|
352
|
+
text: `Error: ${error instanceof Error ? error.message : "Failed to execute order"}`
|
|
617
353
|
}
|
|
618
|
-
]
|
|
354
|
+
],
|
|
355
|
+
isError: true
|
|
619
356
|
};
|
|
620
357
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
358
|
+
}
|
|
359
|
+
);
|
|
360
|
+
this.server.setRequestHandler(
|
|
361
|
+
z.object({ method: z.literal("marketDataRequest") }),
|
|
362
|
+
async (request, extra) => {
|
|
363
|
+
try {
|
|
364
|
+
const args = request.params;
|
|
365
|
+
const response = new Promise((resolve) => {
|
|
366
|
+
this.pendingRequests.set(args.mdReqID, resolve);
|
|
367
|
+
});
|
|
368
|
+
const messageFields = [
|
|
369
|
+
new Field(Fields.MsgType, Messages.MarketDataRequest),
|
|
370
|
+
new Field(Fields.SenderCompID, this.parser?.sender),
|
|
371
|
+
new Field(Fields.MsgSeqNum, this.parser?.getNextTargetMsgSeqNum()),
|
|
372
|
+
new Field(Fields.TargetCompID, this.parser?.target),
|
|
373
|
+
new Field(Fields.SendingTime, this.parser?.getTimestamp()),
|
|
374
|
+
new Field(Fields.MDReqID, args.mdReqID),
|
|
375
|
+
new Field(Fields.SubscriptionRequestType, args.subscriptionRequestType),
|
|
376
|
+
new Field(Fields.MarketDepth, 0),
|
|
377
|
+
new Field(Fields.MDUpdateType, args.mdUpdateType)
|
|
378
|
+
];
|
|
379
|
+
messageFields.push(new Field(Fields.NoRelatedSym, args.symbols.length));
|
|
380
|
+
args.symbols.forEach((symbol) => {
|
|
381
|
+
messageFields.push(new Field(Fields.Symbol, symbol));
|
|
382
|
+
});
|
|
383
|
+
messageFields.push(new Field(Fields.NoMDEntryTypes, args.mdEntryTypes.length));
|
|
384
|
+
args.mdEntryTypes.forEach((entryType) => {
|
|
385
|
+
messageFields.push(new Field(Fields.MDEntryType, entryType));
|
|
386
|
+
});
|
|
387
|
+
const mdr = this.parser?.createMessage(...messageFields);
|
|
388
|
+
if (!this.parser?.connected) {
|
|
389
|
+
return {
|
|
390
|
+
content: [
|
|
391
|
+
{
|
|
628
392
|
type: "text",
|
|
629
|
-
text:
|
|
393
|
+
text: "Error: Not connected. Ignoring message."
|
|
630
394
|
}
|
|
395
|
+
],
|
|
396
|
+
isError: true
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
this.parser?.send(mdr);
|
|
400
|
+
const fixData = await response;
|
|
401
|
+
return {
|
|
402
|
+
content: [
|
|
403
|
+
{
|
|
404
|
+
type: "text",
|
|
405
|
+
text: `Market data for ${args.symbols.join(", ")}: ${JSON.stringify(fixData.toFIXJSON())}`
|
|
631
406
|
}
|
|
632
407
|
]
|
|
633
408
|
};
|
|
634
|
-
}
|
|
635
|
-
case "newOrderSingle": {
|
|
636
|
-
const { clOrdID, handlInst, quantity, price, ordType, side, symbol, timeInForce } = args || {};
|
|
409
|
+
} catch (error) {
|
|
637
410
|
return {
|
|
638
|
-
|
|
411
|
+
content: [
|
|
639
412
|
{
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
type: "text",
|
|
643
|
-
text: [
|
|
644
|
-
"Create a New Order Single FIX message with the following parameters:",
|
|
645
|
-
`- ClOrdID: ${clOrdID}`,
|
|
646
|
-
`- HandlInst: ${handlInst ?? "default"}`,
|
|
647
|
-
`- Quantity: ${quantity}`,
|
|
648
|
-
`- Price: ${price}`,
|
|
649
|
-
`- OrdType: ${ordType ?? "default (Market)"}`,
|
|
650
|
-
`- Side: ${side}`,
|
|
651
|
-
`- Symbol: ${symbol}`,
|
|
652
|
-
`- TimeInForce: ${timeInForce ?? "default (Day)"}`,
|
|
653
|
-
"",
|
|
654
|
-
"Format the response as a JSON object with FIX tag numbers as keys and their corresponding values."
|
|
655
|
-
].join("\n")
|
|
656
|
-
}
|
|
413
|
+
type: "text",
|
|
414
|
+
text: `Error: ${error instanceof Error ? error.message : "Failed to request market data"}`
|
|
657
415
|
}
|
|
658
|
-
]
|
|
416
|
+
],
|
|
417
|
+
isError: true
|
|
659
418
|
};
|
|
660
419
|
}
|
|
661
|
-
|
|
662
|
-
|
|
420
|
+
}
|
|
421
|
+
);
|
|
422
|
+
this.server.setRequestHandler(
|
|
423
|
+
z.object({ method: z.literal("greeting-resource") }),
|
|
424
|
+
async (request, extra) => {
|
|
425
|
+
this.parser?.logger.log({
|
|
426
|
+
level: "info",
|
|
427
|
+
message: "MCP Server Resource called: greeting-resource"
|
|
428
|
+
});
|
|
429
|
+
return {
|
|
430
|
+
content: [
|
|
431
|
+
{
|
|
432
|
+
type: "text",
|
|
433
|
+
text: "Hello, world!"
|
|
434
|
+
}
|
|
435
|
+
]
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
);
|
|
439
|
+
this.server.setRequestHandler(
|
|
440
|
+
z.object({ method: z.literal("stockGraph") }),
|
|
441
|
+
async (request, extra) => {
|
|
442
|
+
this.parser?.logger.log({
|
|
443
|
+
level: "info",
|
|
444
|
+
message: "MCP Server Resource called: stockGraph"
|
|
445
|
+
});
|
|
446
|
+
const args = request.params;
|
|
447
|
+
const symbol = args.symbol;
|
|
448
|
+
const priceHistory = this.marketDataPrices.get(symbol) || [];
|
|
449
|
+
if (priceHistory.length === 0) {
|
|
663
450
|
return {
|
|
664
|
-
|
|
451
|
+
content: [
|
|
665
452
|
{
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
type: "text",
|
|
669
|
-
text: [
|
|
670
|
-
"Create a Market Data Request FIX message with the following parameters:",
|
|
671
|
-
`- MDUpdateType: ${mdUpdateType ?? "default (0 = FullRefresh)"}`,
|
|
672
|
-
`- Symbol: ${symbol}`,
|
|
673
|
-
`- MDReqID: ${mdReqID}`,
|
|
674
|
-
`- SubscriptionRequestType: ${subscriptionRequestType ?? "default (0 = Snapshot + Updates)"}`,
|
|
675
|
-
`- MDEntryType: ${mdEntryType ?? "default (0 = Bid)"}`,
|
|
676
|
-
"",
|
|
677
|
-
"Format the response as a JSON object with FIX tag numbers as keys and their corresponding values."
|
|
678
|
-
].join("\n")
|
|
679
|
-
}
|
|
453
|
+
type: "text",
|
|
454
|
+
text: `No price data available for ${symbol}`
|
|
680
455
|
}
|
|
681
456
|
]
|
|
682
457
|
};
|
|
683
458
|
}
|
|
684
|
-
|
|
685
|
-
|
|
459
|
+
const width = 600;
|
|
460
|
+
const height = 300;
|
|
461
|
+
const padding = 40;
|
|
462
|
+
const xScale = (width - 2 * padding) / (priceHistory.length - 1);
|
|
463
|
+
const yMin = Math.min(...priceHistory.map((d) => d.price));
|
|
464
|
+
const yMax = Math.max(...priceHistory.map((d) => d.price));
|
|
465
|
+
const yScale = (height - 2 * padding) / (yMax - yMin);
|
|
466
|
+
const points = priceHistory.map((d, i) => {
|
|
467
|
+
const x = padding + i * xScale;
|
|
468
|
+
const y = height - padding - (d.price - yMin) * yScale;
|
|
469
|
+
return `${x},${y}`;
|
|
470
|
+
}).join(" L ");
|
|
471
|
+
const svg = `<?xml version="1.0" encoding="UTF-8"?>
|
|
472
|
+
<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
|
|
473
|
+
<!-- Background -->
|
|
474
|
+
<rect width="100%" height="100%" fill="#f8f9fa"/>
|
|
475
|
+
|
|
476
|
+
<!-- Grid lines -->
|
|
477
|
+
<g stroke="#e9ecef" stroke-width="1">
|
|
478
|
+
${Array.from({ length: 5 }, (_, i) => {
|
|
479
|
+
const y = padding + (height - 2 * padding) * i / 4;
|
|
480
|
+
return `<line x1="${padding}" y1="${y}" x2="${width - padding}" y2="${y}"/>`;
|
|
481
|
+
}).join("\n")}
|
|
482
|
+
</g>
|
|
483
|
+
|
|
484
|
+
<!-- Price line -->
|
|
485
|
+
<path d="M ${points}"
|
|
486
|
+
fill="none"
|
|
487
|
+
stroke="#007bff"
|
|
488
|
+
stroke-width="2"/>
|
|
489
|
+
|
|
490
|
+
<!-- Data points -->
|
|
491
|
+
${priceHistory.map((d, i) => {
|
|
492
|
+
const x = padding + i * xScale;
|
|
493
|
+
const y = height - padding - (d.price - yMin) * yScale;
|
|
494
|
+
return `<circle cx="${x}" cy="${y}" r="3" fill="#007bff"/>`;
|
|
495
|
+
}).join("\n")}
|
|
496
|
+
|
|
497
|
+
<!-- Labels -->
|
|
498
|
+
<g font-family="Arial" font-size="12" fill="#495057">
|
|
499
|
+
${Array.from({ length: 5 }, (_, i) => {
|
|
500
|
+
const x = padding + (width - 2 * padding) * i / 4;
|
|
501
|
+
const index = Math.floor((priceHistory.length - 1) * i / 4);
|
|
502
|
+
const timestamp = new Date(priceHistory[index].timestamp).toLocaleTimeString();
|
|
503
|
+
return `<text x="${x + padding}" y="${height - padding + 20}" text-anchor="middle">${timestamp}</text>`;
|
|
504
|
+
}).join("\n")}
|
|
505
|
+
${Array.from({ length: 5 }, (_, i) => {
|
|
506
|
+
const y = padding + (height - 2 * padding) * i / 4;
|
|
507
|
+
const price = yMax - (yMax - yMin) * i / 4;
|
|
508
|
+
return `<text x="${padding - 5}" y="${y + 4}" text-anchor="end">$${price.toFixed(2)}</text>`;
|
|
509
|
+
}).join("\n")}
|
|
510
|
+
</g>
|
|
511
|
+
|
|
512
|
+
<!-- Title -->
|
|
513
|
+
<text x="${width / 2}" y="${padding / 2}"
|
|
514
|
+
font-family="Arial" font-size="16" font-weight="bold"
|
|
515
|
+
text-anchor="middle" fill="#212529">
|
|
516
|
+
${symbol} - Price Chart (${priceHistory.length} points)
|
|
517
|
+
</text>
|
|
518
|
+
</svg>`;
|
|
519
|
+
return {
|
|
520
|
+
content: [
|
|
521
|
+
{
|
|
522
|
+
type: "text",
|
|
523
|
+
text: svg
|
|
524
|
+
}
|
|
525
|
+
]
|
|
526
|
+
};
|
|
686
527
|
}
|
|
687
|
-
|
|
528
|
+
);
|
|
529
|
+
this.server.setRequestHandler(
|
|
530
|
+
z.object({ method: z.literal("stockPriceHistory") }),
|
|
531
|
+
async (request, extra) => {
|
|
532
|
+
this.parser?.logger.log({
|
|
533
|
+
level: "info",
|
|
534
|
+
message: "MCP Server Resource called: stockPriceHistory"
|
|
535
|
+
});
|
|
536
|
+
const args = request.params;
|
|
537
|
+
const symbol = args.symbol;
|
|
538
|
+
const priceHistory = this.marketDataPrices.get(symbol) || [];
|
|
539
|
+
return {
|
|
540
|
+
content: [
|
|
541
|
+
{
|
|
542
|
+
type: "text",
|
|
543
|
+
text: JSON.stringify(
|
|
544
|
+
{
|
|
545
|
+
symbol,
|
|
546
|
+
count: priceHistory.length,
|
|
547
|
+
prices: priceHistory.map((point) => ({
|
|
548
|
+
timestamp: new Date(point.timestamp).toISOString(),
|
|
549
|
+
price: point.price
|
|
550
|
+
}))
|
|
551
|
+
},
|
|
552
|
+
null,
|
|
553
|
+
2
|
|
554
|
+
)
|
|
555
|
+
}
|
|
556
|
+
]
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
);
|
|
688
560
|
process.on("SIGINT", async () => {
|
|
689
561
|
await this.server.close();
|
|
690
562
|
process.exit(0);
|