fixparser-plugin-mcp 9.1.7-b2bba224 → 9.1.7-b2f9f891

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.
@@ -25,179 +25,9 @@ __export(MCPLocal_exports, {
25
25
  module.exports = __toCommonJS(MCPLocal_exports);
26
26
  var import_server = require("@modelcontextprotocol/sdk/server/index.js");
27
27
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
28
- var import_types = require("@modelcontextprotocol/sdk/types.js");
29
28
  var import_fixparser = require("fixparser");
30
- var parseInputSchema = {
31
- type: "object",
32
- properties: {
33
- fixString: {
34
- type: "string",
35
- description: "FIX message string to parse"
36
- }
37
- },
38
- required: ["fixString"]
39
- };
40
- var parseToJSONInputSchema = {
41
- type: "object",
42
- properties: {
43
- fixString: {
44
- type: "string",
45
- description: "FIX message string to parse"
46
- }
47
- },
48
- required: ["fixString"]
49
- };
50
- var newOrderSingleInputSchema = {
51
- type: "object",
52
- properties: {
53
- clOrdID: {
54
- type: "string",
55
- description: "Client Order ID"
56
- },
57
- handlInst: {
58
- type: "string",
59
- enum: ["1", "2", "3"],
60
- default: import_fixparser.HandlInst.AutomatedExecutionNoIntervention,
61
- description: 'Handling instruction (IMPORTANT: Use the numeric/alphabetic value, not the descriptive name. For example, use "1" for Manual, "2" for Automated, "3" for AutomatedNoIntervention)'
62
- },
63
- quantity: {
64
- type: "number",
65
- description: "Order quantity"
66
- },
67
- price: {
68
- type: "number",
69
- description: "Order price"
70
- },
71
- ordType: {
72
- type: "string",
73
- enum: [
74
- "1",
75
- "2",
76
- "3",
77
- "4",
78
- "5",
79
- "6",
80
- "7",
81
- "8",
82
- "9",
83
- "A",
84
- "B",
85
- "C",
86
- "D",
87
- "E",
88
- "F",
89
- "G",
90
- "H",
91
- "I",
92
- "J",
93
- "K",
94
- "L",
95
- "M",
96
- "P",
97
- "Q",
98
- "R",
99
- "S"
100
- ],
101
- default: import_fixparser.OrdType.Market,
102
- description: 'Order type (IMPORTANT: Use the numeric/alphabetic value, not the descriptive name. For example, use "1" for Market, "2" for Limit, "3" for Stop)'
103
- },
104
- side: {
105
- type: "string",
106
- enum: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H"],
107
- description: 'Order side (IMPORTANT: Use the numeric/alphabetic value, not the descriptive name. For example, use "1" for Buy, "2" for Sell, "3" for BuyMinus, "4" for SellPlus, "5" for SellShort, "6" for SellShortExempt, "7" for Undisclosed, "8" for Cross, "9" for CrossShort, "A" for CrossShortExempt, "B" for AsDefined, "C" for Opposite, "D" for Subscribe, "E" for Redeem, "F" for Lend, "G" for Borrow, "H" for SellUndisclosed)'
108
- },
109
- symbol: {
110
- type: "string",
111
- description: "Trading symbol"
112
- },
113
- timeInForce: {
114
- type: "string",
115
- enum: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C"],
116
- default: import_fixparser.TimeInForce.Day,
117
- description: 'Time in force (IMPORTANT: Use the numeric/alphabetic value, not the descriptive name. For example, use "0" for Day, "1" for Good Till Cancel, "2" for At Opening, "3" for Immediate or Cancel, "4" for Fill or Kill, "5" for Good Till Crossing, "6" for Good Till Date)'
118
- }
119
- },
120
- required: ["clOrdID", "quantity", "price", "side", "symbol"]
121
- };
122
- var marketDataRequestInputSchema = {
123
- type: "object",
124
- properties: {
125
- mdUpdateType: {
126
- type: "string",
127
- enum: ["0", "1"],
128
- default: "0",
129
- description: 'Market data update type (IMPORTANT: Use the numeric/alphabetic value, not the descriptive name. For example, use "0" for FullRefresh, "1" for IncrementalRefresh)'
130
- },
131
- symbol: {
132
- type: "string",
133
- description: "Trading symbol"
134
- },
135
- mdReqID: {
136
- type: "string",
137
- description: "Market data request ID"
138
- },
139
- subscriptionRequestType: {
140
- type: "string",
141
- enum: ["0", "1", "2"],
142
- default: import_fixparser.SubscriptionRequestType.SnapshotAndUpdates,
143
- description: 'Subscription request type (IMPORTANT: Use the numeric/alphabetic value, not the descriptive name. For example, use "0" for Snapshot + Updates, "1" for Snapshot, "2" for Unsubscribe)'
144
- },
145
- mdEntryType: {
146
- type: "string",
147
- enum: [
148
- "0",
149
- "1",
150
- "2",
151
- "3",
152
- "4",
153
- "5",
154
- "6",
155
- "7",
156
- "8",
157
- "9",
158
- "A",
159
- "B",
160
- "C",
161
- "D",
162
- "E",
163
- "F",
164
- "G",
165
- "H",
166
- "J",
167
- "K",
168
- "L",
169
- "M",
170
- "N",
171
- "O",
172
- "P",
173
- "Q",
174
- "S",
175
- "R",
176
- "T",
177
- "U",
178
- "V",
179
- "W",
180
- "X",
181
- "Y",
182
- "Z",
183
- "a",
184
- "b",
185
- "c",
186
- "d",
187
- "e",
188
- "g",
189
- "h",
190
- "i",
191
- "t"
192
- ],
193
- default: import_fixparser.MDEntryType.Bid,
194
- description: 'Market data entry type (IMPORTANT: Use the numeric/alphabetic value, not the descriptive name. For example, use "0" for Bid, "1" for Offer, "2" for Trade, "3" for Index Value, "4" for Opening Price)'
195
- }
196
- },
197
- required: ["symbol", "mdReqID"]
198
- };
29
+ var import_zod = require("zod");
199
30
  var MCPLocal = class {
200
- // private logger: Logger | undefined;
201
31
  parser;
202
32
  server = new import_server.Server(
203
33
  {
@@ -206,43 +36,241 @@ var MCPLocal = class {
206
36
  },
207
37
  {
208
38
  capabilities: {
209
- tools: {},
210
- prompts: {},
211
- resources: {}
39
+ tools: {
40
+ parse: {
41
+ description: "Parses a FIX message and describes it in plain language",
42
+ parameters: {
43
+ type: "object",
44
+ properties: {
45
+ fixString: { type: "string" }
46
+ },
47
+ required: ["fixString"]
48
+ }
49
+ },
50
+ parseToJSON: {
51
+ description: "Parses a FIX message into JSON",
52
+ parameters: {
53
+ type: "object",
54
+ properties: {
55
+ fixString: { type: "string" }
56
+ },
57
+ required: ["fixString"]
58
+ }
59
+ },
60
+ verifyOrder: {
61
+ description: "Verifies order parameters before execution",
62
+ parameters: {
63
+ type: "object",
64
+ properties: {
65
+ clOrdID: { type: "string" },
66
+ handlInst: { type: "string", enum: ["1", "2", "3"] },
67
+ quantity: { type: "string" },
68
+ price: { type: "string" },
69
+ ordType: {
70
+ type: "string",
71
+ enum: [
72
+ "1",
73
+ "2",
74
+ "3",
75
+ "4",
76
+ "5",
77
+ "6",
78
+ "7",
79
+ "8",
80
+ "9",
81
+ "A",
82
+ "B",
83
+ "C",
84
+ "D",
85
+ "E",
86
+ "F",
87
+ "G",
88
+ "H",
89
+ "I",
90
+ "J",
91
+ "K",
92
+ "L",
93
+ "M",
94
+ "P",
95
+ "Q",
96
+ "R",
97
+ "S"
98
+ ]
99
+ },
100
+ side: {
101
+ type: "string",
102
+ enum: [
103
+ "1",
104
+ "2",
105
+ "3",
106
+ "4",
107
+ "5",
108
+ "6",
109
+ "7",
110
+ "8",
111
+ "9",
112
+ "A",
113
+ "B",
114
+ "C",
115
+ "D",
116
+ "E",
117
+ "F",
118
+ "G",
119
+ "H"
120
+ ]
121
+ },
122
+ symbol: { type: "string" },
123
+ timeInForce: {
124
+ type: "string",
125
+ enum: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C"]
126
+ }
127
+ },
128
+ required: [
129
+ "clOrdID",
130
+ "handlInst",
131
+ "quantity",
132
+ "price",
133
+ "ordType",
134
+ "side",
135
+ "symbol",
136
+ "timeInForce"
137
+ ]
138
+ }
139
+ },
140
+ executeOrder: {
141
+ description: "Executes a verified order",
142
+ parameters: {
143
+ type: "object",
144
+ properties: {
145
+ clOrdID: { type: "string" },
146
+ handlInst: { type: "string", enum: ["1", "2", "3"] },
147
+ quantity: { type: "string" },
148
+ price: { type: "string" },
149
+ ordType: { type: "string" },
150
+ side: { type: "string" },
151
+ symbol: { type: "string" },
152
+ timeInForce: { type: "string" }
153
+ },
154
+ required: [
155
+ "clOrdID",
156
+ "handlInst",
157
+ "quantity",
158
+ "price",
159
+ "ordType",
160
+ "side",
161
+ "symbol",
162
+ "timeInForce"
163
+ ]
164
+ }
165
+ },
166
+ marketDataRequest: {
167
+ description: "Requests market data for specified symbols",
168
+ parameters: {
169
+ type: "object",
170
+ properties: {
171
+ mdUpdateType: { type: "string", enum: ["0", "1"] },
172
+ symbols: { type: "array", items: { type: "string" } },
173
+ mdReqID: { type: "string" },
174
+ subscriptionRequestType: { type: "string", enum: ["0", "1", "2"] },
175
+ mdEntryTypes: { type: "array", items: { type: "string" } }
176
+ },
177
+ required: ["mdUpdateType", "symbols", "mdReqID", "subscriptionRequestType", "mdEntryTypes"]
178
+ }
179
+ }
180
+ },
181
+ resources: {
182
+ greeting: {
183
+ description: "A simple greeting resource",
184
+ uri: "greeting-resource"
185
+ },
186
+ stockGraph: {
187
+ description: "Generates a price chart for a given symbol",
188
+ uri: "stockGraph",
189
+ parameters: {
190
+ type: "object",
191
+ properties: {
192
+ symbol: { type: "string" }
193
+ },
194
+ required: ["symbol"]
195
+ }
196
+ },
197
+ stockPriceHistory: {
198
+ description: "Returns price history for a given symbol",
199
+ uri: "stockPriceHistory",
200
+ parameters: {
201
+ type: "object",
202
+ properties: {
203
+ symbol: { type: "string" }
204
+ },
205
+ required: ["symbol"]
206
+ }
207
+ }
208
+ }
212
209
  }
213
210
  }
214
211
  );
215
212
  transport = new import_stdio.StdioServerTransport();
216
213
  onReady = void 0;
217
214
  pendingRequests = /* @__PURE__ */ new Map();
215
+ verifiedOrders = /* @__PURE__ */ new Map();
216
+ // Store market data prices with timestamps
217
+ marketDataPrices = /* @__PURE__ */ new Map();
218
+ MAX_PRICE_HISTORY = 1e5;
219
+ // Maximum number of price points to store per symbol
218
220
  constructor({ logger, onReady }) {
219
221
  if (onReady) this.onReady = onReady;
220
222
  }
221
223
  async register(parser) {
222
224
  this.parser = parser;
223
225
  this.parser.addOnMessageCallback((message) => {
226
+ this.parser?.logger.log({
227
+ level: "info",
228
+ message: `MCP Server received message: ${message.messageType}: ${message.description}`
229
+ });
224
230
  const msgType = message.messageType;
225
- if (msgType === import_fixparser.Messages.MarketDataSnapshotFullRefresh || msgType === import_fixparser.Messages.ExecutionReport || msgType === import_fixparser.Messages.Reject) {
226
- const idField = msgType === import_fixparser.Messages.MarketDataSnapshotFullRefresh ? message.getField(import_fixparser.Fields.MDReqID) : msgType === import_fixparser.Messages.Reject ? message.getField(import_fixparser.Fields.RefSeqNum) : message.getField(import_fixparser.Fields.ClOrdID);
227
- if (idField) {
228
- const id = idField.value;
229
- if (typeof id === "string" || typeof id === "number") {
230
- if (msgType === import_fixparser.Messages.Reject) {
231
- const refMsgType = message.getField(import_fixparser.Fields.RefMsgType);
232
- if (refMsgType && refMsgType.value === import_fixparser.Messages.NewOrderSingle) {
233
- const callback = this.pendingRequests.get(String(id));
234
- if (callback) {
235
- callback(message);
236
- this.pendingRequests.delete(String(id));
237
- }
238
- }
239
- } else {
240
- const callback = this.pendingRequests.get(String(id));
241
- if (callback) {
242
- callback(message);
243
- this.pendingRequests.delete(String(id));
244
- }
231
+ if (msgType === import_fixparser.Messages.MarketDataSnapshotFullRefresh || msgType === import_fixparser.Messages.ExecutionReport || msgType === import_fixparser.Messages.Reject || msgType === import_fixparser.Messages.MarketDataIncrementalRefresh) {
232
+ this.parser?.logger.log({
233
+ level: "info",
234
+ message: `MCP Server handling message type: ${msgType}`
235
+ });
236
+ let id;
237
+ if (msgType === import_fixparser.Messages.MarketDataIncrementalRefresh || msgType === import_fixparser.Messages.MarketDataSnapshotFullRefresh) {
238
+ const symbol = message.getField(import_fixparser.Fields.Symbol);
239
+ const price = message.getField(import_fixparser.Fields.MDEntryPx);
240
+ const timestamp = message.getField(import_fixparser.Fields.MDEntryTime)?.value || Date.now();
241
+ if (symbol?.value && price?.value) {
242
+ const symbolStr = String(symbol.value);
243
+ const priceNum = Number(price.value);
244
+ const priceHistory = this.marketDataPrices.get(symbolStr) || [];
245
+ priceHistory.push({
246
+ timestamp: Number(timestamp),
247
+ price: priceNum
248
+ });
249
+ if (priceHistory.length > this.MAX_PRICE_HISTORY) {
250
+ priceHistory.shift();
245
251
  }
252
+ this.marketDataPrices.set(symbolStr, priceHistory);
253
+ this.parser?.logger.log({
254
+ level: "info",
255
+ message: `MCP Server added ${symbol}: ${priceNum}`
256
+ });
257
+ }
258
+ }
259
+ if (msgType === import_fixparser.Messages.MarketDataSnapshotFullRefresh) {
260
+ const mdReqID = message.getField(import_fixparser.Fields.MDReqID);
261
+ if (mdReqID) id = String(mdReqID.value);
262
+ } else if (msgType === import_fixparser.Messages.ExecutionReport) {
263
+ const clOrdID = message.getField(import_fixparser.Fields.ClOrdID);
264
+ if (clOrdID) id = String(clOrdID.value);
265
+ } else if (msgType === import_fixparser.Messages.Reject) {
266
+ const refSeqNum = message.getField(import_fixparser.Fields.RefSeqNum);
267
+ if (refSeqNum) id = String(refSeqNum.value);
268
+ }
269
+ if (id) {
270
+ const callback = this.pendingRequests.get(id);
271
+ if (callback) {
272
+ callback(message);
273
+ this.pendingRequests.delete(id);
246
274
  }
247
275
  }
248
276
  }
@@ -260,431 +288,665 @@ var MCPLocal = class {
260
288
  if (!this.server) {
261
289
  return;
262
290
  }
263
- const validateArgs = (args, schema) => {
264
- const result = {};
265
- for (const [key, propSchema] of Object.entries(schema.properties || {})) {
266
- const prop = propSchema;
267
- const value = args?.[key];
268
- if (prop.required && (value === void 0 || value === null)) {
269
- throw new Error(`Required property '${key}' is missing`);
270
- }
271
- if (value !== void 0) {
272
- result[key] = value;
273
- } else if (prop.default !== void 0) {
274
- result[key] = prop.default;
275
- }
291
+ this.server.setRequestHandler(
292
+ import_zod.z.object({ method: import_zod.z.literal("resources/list") }),
293
+ async (request, extra) => {
294
+ return {
295
+ resources: [
296
+ {
297
+ name: "greeting",
298
+ description: "A simple greeting resource",
299
+ uri: "greeting-resource"
300
+ },
301
+ {
302
+ name: "stockGraph",
303
+ description: "Generates a price chart for a given symbol",
304
+ uri: "stockGraph",
305
+ parameters: {
306
+ type: "object",
307
+ properties: {
308
+ symbol: { type: "string" }
309
+ },
310
+ required: ["symbol"]
311
+ }
312
+ },
313
+ {
314
+ name: "stockPriceHistory",
315
+ description: "Returns price history for a given symbol",
316
+ uri: "stockPriceHistory",
317
+ parameters: {
318
+ type: "object",
319
+ properties: {
320
+ symbol: { type: "string" }
321
+ },
322
+ required: ["symbol"]
323
+ }
324
+ }
325
+ ]
326
+ };
276
327
  }
277
- return result;
278
- };
279
- this.server.setRequestHandler(import_types.ListResourcesRequestSchema, async () => {
280
- return {
281
- resources: []
282
- };
283
- });
284
- this.server.setRequestHandler(import_types.ListToolsRequestSchema, async () => {
285
- return {
286
- tools: [
287
- {
288
- name: "parse",
289
- description: "Parses a FIX message and describes it in plain language",
290
- inputSchema: parseInputSchema
291
- },
292
- {
293
- name: "parseToJSON",
294
- description: "Parses a FIX message into JSON",
295
- inputSchema: parseToJSONInputSchema
296
- },
297
- {
298
- name: "newOrderSingle",
299
- description: "Creates and sends a New Order Single",
300
- inputSchema: newOrderSingleInputSchema
301
- },
302
- {
303
- name: "marketDataRequest",
304
- description: "Sends a request for Market Data with the given symbol",
305
- inputSchema: marketDataRequestInputSchema
306
- }
307
- ]
308
- };
309
- });
310
- this.server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
311
- const { name, arguments: args } = request.params;
312
- switch (name) {
313
- case "parse": {
314
- try {
315
- const { fixString } = validateArgs(args, parseInputSchema);
316
- const parsedMessage = this.parser?.parse(fixString);
317
- if (!parsedMessage || parsedMessage.length === 0) {
318
- return {
319
- isError: true,
320
- content: [{ type: "text", text: "Error: Failed to parse FIX string" }]
321
- };
328
+ );
329
+ this.server.setRequestHandler(
330
+ import_zod.z.object({ method: import_zod.z.literal("tools/list") }),
331
+ async (request, extra) => {
332
+ return {
333
+ tools: [
334
+ {
335
+ name: "parse",
336
+ description: "Parses a FIX message and describes it in plain language",
337
+ parameters: {
338
+ type: "object",
339
+ properties: {
340
+ fixString: { type: "string" }
341
+ },
342
+ required: ["fixString"]
343
+ }
344
+ },
345
+ {
346
+ name: "parseToJSON",
347
+ description: "Parses a FIX message into JSON",
348
+ parameters: {
349
+ type: "object",
350
+ properties: {
351
+ fixString: { type: "string" }
352
+ },
353
+ required: ["fixString"]
354
+ }
355
+ },
356
+ {
357
+ name: "verifyOrder",
358
+ description: "Verifies order parameters before execution",
359
+ parameters: {
360
+ type: "object",
361
+ properties: {
362
+ clOrdID: { type: "string" },
363
+ handlInst: { type: "string", enum: ["1", "2", "3"] },
364
+ quantity: { type: "string" },
365
+ price: { type: "string" },
366
+ ordType: {
367
+ type: "string",
368
+ enum: [
369
+ "1",
370
+ "2",
371
+ "3",
372
+ "4",
373
+ "5",
374
+ "6",
375
+ "7",
376
+ "8",
377
+ "9",
378
+ "A",
379
+ "B",
380
+ "C",
381
+ "D",
382
+ "E",
383
+ "F",
384
+ "G",
385
+ "H",
386
+ "I",
387
+ "J",
388
+ "K",
389
+ "L",
390
+ "M",
391
+ "P",
392
+ "Q",
393
+ "R",
394
+ "S"
395
+ ]
396
+ },
397
+ side: {
398
+ type: "string",
399
+ enum: [
400
+ "1",
401
+ "2",
402
+ "3",
403
+ "4",
404
+ "5",
405
+ "6",
406
+ "7",
407
+ "8",
408
+ "9",
409
+ "A",
410
+ "B",
411
+ "C",
412
+ "D",
413
+ "E",
414
+ "F",
415
+ "G",
416
+ "H"
417
+ ]
418
+ },
419
+ symbol: { type: "string" },
420
+ timeInForce: {
421
+ type: "string",
422
+ enum: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C"]
423
+ }
424
+ },
425
+ required: [
426
+ "clOrdID",
427
+ "handlInst",
428
+ "quantity",
429
+ "price",
430
+ "ordType",
431
+ "side",
432
+ "symbol",
433
+ "timeInForce"
434
+ ]
435
+ }
436
+ },
437
+ {
438
+ name: "executeOrder",
439
+ description: "Executes a verified order",
440
+ parameters: {
441
+ type: "object",
442
+ properties: {
443
+ clOrdID: { type: "string" },
444
+ handlInst: { type: "string", enum: ["1", "2", "3"] },
445
+ quantity: { type: "string" },
446
+ price: { type: "string" },
447
+ ordType: { type: "string" },
448
+ side: { type: "string" },
449
+ symbol: { type: "string" },
450
+ timeInForce: { type: "string" }
451
+ },
452
+ required: [
453
+ "clOrdID",
454
+ "handlInst",
455
+ "quantity",
456
+ "price",
457
+ "ordType",
458
+ "side",
459
+ "symbol",
460
+ "timeInForce"
461
+ ]
462
+ }
463
+ },
464
+ {
465
+ name: "marketDataRequest",
466
+ description: "Requests market data for specified symbols",
467
+ parameters: {
468
+ type: "object",
469
+ properties: {
470
+ mdUpdateType: { type: "string", enum: ["0", "1"] },
471
+ symbols: { type: "array", items: { type: "string" } },
472
+ mdReqID: { type: "string" },
473
+ subscriptionRequestType: { type: "string", enum: ["0", "1", "2"] },
474
+ mdEntryTypes: { type: "array", items: { type: "string" } }
475
+ },
476
+ required: [
477
+ "mdUpdateType",
478
+ "symbols",
479
+ "mdReqID",
480
+ "subscriptionRequestType",
481
+ "mdEntryTypes"
482
+ ]
483
+ }
322
484
  }
323
- return {
324
- content: [
325
- {
326
- type: "text",
327
- text: `${parsedMessage[0].description}
328
- ${parsedMessage[0].messageTypeDescription}`
329
- }
330
- ]
331
- };
332
- } catch (error) {
333
- return {
334
- isError: true,
335
- content: [
336
- {
337
- type: "text",
338
- text: `Error: ${error instanceof Error ? error.message : "Failed to parse FIX string"}`
339
- }
340
- ]
341
- };
342
- }
485
+ ]
486
+ };
487
+ }
488
+ );
489
+ this.server.setRequestHandler(import_zod.z.object({ method: import_zod.z.literal("parse") }), async (request, extra) => {
490
+ try {
491
+ const args = request.params;
492
+ const parsedMessage = this.parser?.parse(args.fixString);
493
+ if (!parsedMessage || parsedMessage.length === 0) {
494
+ return {
495
+ content: [{ type: "text", text: "Error: Failed to parse FIX string" }],
496
+ isError: true
497
+ };
343
498
  }
344
- case "parseToJSON": {
345
- try {
346
- const { fixString } = validateArgs(args, parseToJSONInputSchema);
347
- const parsedMessage = this.parser?.parse(fixString);
348
- if (!parsedMessage || parsedMessage.length === 0) {
349
- return {
350
- isError: true,
351
- content: [{ type: "text", text: "Error: Failed to parse FIX string" }]
352
- };
499
+ return {
500
+ content: [
501
+ {
502
+ type: "text",
503
+ text: `${parsedMessage[0].description}
504
+ ${parsedMessage[0].messageTypeDescription}`
353
505
  }
506
+ ]
507
+ };
508
+ } catch (error) {
509
+ return {
510
+ content: [
511
+ {
512
+ type: "text",
513
+ text: `Error: ${error instanceof Error ? error.message : "Failed to parse FIX string"}`
514
+ }
515
+ ],
516
+ isError: true
517
+ };
518
+ }
519
+ });
520
+ this.server.setRequestHandler(
521
+ import_zod.z.object({ method: import_zod.z.literal("parseToJSON") }),
522
+ async (request, extra) => {
523
+ try {
524
+ const args = request.params;
525
+ const parsedMessage = this.parser?.parse(args.fixString);
526
+ if (!parsedMessage || parsedMessage.length === 0) {
354
527
  return {
355
- content: [
356
- {
357
- type: "text",
358
- text: `${parsedMessage[0].toFIXJSON()}`
359
- }
360
- ]
361
- };
362
- } catch (error) {
363
- return {
364
- isError: true,
365
- content: [
366
- {
367
- type: "text",
368
- text: `Error: ${error instanceof Error ? error.message : "Failed to parse FIX string"}`
369
- }
370
- ]
528
+ content: [{ type: "text", text: "Error: Failed to parse FIX string" }],
529
+ isError: true
371
530
  };
372
531
  }
532
+ return {
533
+ content: [
534
+ {
535
+ type: "text",
536
+ text: `${parsedMessage[0].toFIXJSON()}`
537
+ }
538
+ ]
539
+ };
540
+ } catch (error) {
541
+ return {
542
+ content: [
543
+ {
544
+ type: "text",
545
+ text: `Error: ${error instanceof Error ? error.message : "Failed to parse FIX string"}`
546
+ }
547
+ ],
548
+ isError: true
549
+ };
373
550
  }
374
- case "newOrderSingle": {
375
- try {
376
- const { clOrdID, handlInst, quantity, price, ordType, side, symbol, timeInForce } = validateArgs(args, newOrderSingleInputSchema);
377
- const response = new Promise((resolve) => {
378
- this.pendingRequests.set(clOrdID, resolve);
379
- });
380
- const order = this.parser?.createMessage(
381
- new import_fixparser.Field(import_fixparser.Fields.MsgType, import_fixparser.Messages.NewOrderSingle),
382
- new import_fixparser.Field(import_fixparser.Fields.MsgSeqNum, this.parser?.getNextTargetMsgSeqNum()),
383
- new import_fixparser.Field(import_fixparser.Fields.SenderCompID, this.parser?.sender),
384
- new import_fixparser.Field(import_fixparser.Fields.TargetCompID, this.parser?.target),
385
- new import_fixparser.Field(import_fixparser.Fields.SendingTime, this.parser?.getTimestamp()),
386
- new import_fixparser.Field(import_fixparser.Fields.ClOrdID, clOrdID),
387
- new import_fixparser.Field(import_fixparser.Fields.Side, side),
388
- new import_fixparser.Field(import_fixparser.Fields.Symbol, symbol),
389
- new import_fixparser.Field(import_fixparser.Fields.OrderQty, quantity),
390
- new import_fixparser.Field(import_fixparser.Fields.Price, price),
391
- new import_fixparser.Field(import_fixparser.Fields.OrdType, ordType),
392
- new import_fixparser.Field(import_fixparser.Fields.HandlInst, handlInst),
393
- new import_fixparser.Field(import_fixparser.Fields.TimeInForce, timeInForce),
394
- new import_fixparser.Field(import_fixparser.Fields.TransactTime, this.parser?.getTimestamp())
395
- );
396
- if (!this.parser?.connected) {
397
- return {
398
- isError: true,
399
- content: [
400
- {
401
- type: "text",
402
- text: "Error: Not connected. Ignoring message."
403
- }
404
- ]
405
- };
406
- }
407
- this.parser?.send(order);
408
- const fixData = await response;
409
- return {
410
- content: [
411
- {
412
- type: "text",
413
- text: fixData.messageType === import_fixparser.Messages.Reject ? `Reject message for order ${clOrdID}: ${JSON.stringify(fixData.toFIXJSON())}` : `Execution Report for order ${clOrdID}: ${JSON.stringify(fixData.toFIXJSON())}`
414
- }
415
- ]
416
- };
417
- } catch (error) {
551
+ }
552
+ );
553
+ this.server.setRequestHandler(
554
+ import_zod.z.object({ method: import_zod.z.literal("verifyOrder") }),
555
+ async (request, extra) => {
556
+ try {
557
+ const args = request.params;
558
+ this.verifiedOrders.set(args.clOrdID, {
559
+ clOrdID: args.clOrdID,
560
+ handlInst: args.handlInst,
561
+ quantity: Number.parseFloat(args.quantity),
562
+ price: Number.parseFloat(args.price),
563
+ ordType: args.ordType,
564
+ side: args.side,
565
+ symbol: args.symbol,
566
+ timeInForce: args.timeInForce
567
+ });
568
+ const ordTypeNames = {
569
+ "1": "Market",
570
+ "2": "Limit",
571
+ "3": "Stop",
572
+ "4": "StopLimit",
573
+ "5": "MarketOnClose",
574
+ "6": "WithOrWithout",
575
+ "7": "LimitOrBetter",
576
+ "8": "LimitWithOrWithout",
577
+ "9": "OnBasis",
578
+ A: "OnClose",
579
+ B: "LimitOnClose",
580
+ C: "ForexMarket",
581
+ D: "PreviouslyQuoted",
582
+ E: "PreviouslyIndicated",
583
+ F: "ForexLimit",
584
+ G: "ForexSwap",
585
+ H: "ForexPreviouslyQuoted",
586
+ I: "Funari",
587
+ J: "MarketIfTouched",
588
+ K: "MarketWithLeftOverAsLimit",
589
+ L: "PreviousFundValuationPoint",
590
+ M: "NextFundValuationPoint",
591
+ P: "Pegged",
592
+ Q: "CounterOrderSelection",
593
+ R: "StopOnBidOrOffer",
594
+ S: "StopLimitOnBidOrOffer"
595
+ };
596
+ const sideNames = {
597
+ "1": "Buy",
598
+ "2": "Sell",
599
+ "3": "BuyMinus",
600
+ "4": "SellPlus",
601
+ "5": "SellShort",
602
+ "6": "SellShortExempt",
603
+ "7": "Undisclosed",
604
+ "8": "Cross",
605
+ "9": "CrossShort",
606
+ A: "CrossShortExempt",
607
+ B: "AsDefined",
608
+ C: "Opposite",
609
+ D: "Subscribe",
610
+ E: "Redeem",
611
+ F: "Lend",
612
+ G: "Borrow",
613
+ H: "SellUndisclosed"
614
+ };
615
+ const timeInForceNames = {
616
+ "0": "Day",
617
+ "1": "GoodTillCancel",
618
+ "2": "AtTheOpening",
619
+ "3": "ImmediateOrCancel",
620
+ "4": "FillOrKill",
621
+ "5": "GoodTillCrossing",
622
+ "6": "GoodTillDate",
623
+ "7": "AtTheClose",
624
+ "8": "GoodThroughCrossing",
625
+ "9": "AtCrossing",
626
+ A: "GoodForTime",
627
+ B: "GoodForAuction",
628
+ C: "GoodForMonth"
629
+ };
630
+ const handlInstNames = {
631
+ "1": "AutomatedExecutionNoIntervention",
632
+ "2": "AutomatedExecutionInterventionOK",
633
+ "3": "ManualOrder"
634
+ };
635
+ return {
636
+ content: [
637
+ {
638
+ type: "text",
639
+ text: `VERIFICATION: All parameters valid. Ready to proceed with order execution.
640
+
641
+ Parameters verified:
642
+ - ClOrdID: ${args.clOrdID}
643
+ - HandlInst: ${args.handlInst} (${handlInstNames[args.handlInst]})
644
+ - Quantity: ${args.quantity}
645
+ - Price: ${args.price}
646
+ - OrdType: ${args.ordType} (${ordTypeNames[args.ordType]})
647
+ - Side: ${args.side} (${sideNames[args.side]})
648
+ - Symbol: ${args.symbol}
649
+ - TimeInForce: ${args.timeInForce} (${timeInForceNames[args.timeInForce]})
650
+
651
+ To execute this order, call the executeOrder tool with these exact same parameters.`
652
+ }
653
+ ]
654
+ };
655
+ } catch (error) {
656
+ return {
657
+ content: [
658
+ {
659
+ type: "text",
660
+ text: `Error: ${error instanceof Error ? error.message : "Failed to verify order parameters"}`
661
+ }
662
+ ],
663
+ isError: true
664
+ };
665
+ }
666
+ }
667
+ );
668
+ this.server.setRequestHandler(
669
+ import_zod.z.object({ method: import_zod.z.literal("executeOrder") }),
670
+ async (request, extra) => {
671
+ try {
672
+ const args = request.params;
673
+ const verifiedOrder = this.verifiedOrders.get(args.clOrdID);
674
+ if (!verifiedOrder) {
418
675
  return {
419
- isError: true,
420
676
  content: [
421
677
  {
422
678
  type: "text",
423
- text: `Error: ${error instanceof Error ? error.message : "Failed to create order"}`
679
+ text: `Error: Order ${args.clOrdID} has not been verified. Please call verifyOrder first.`
424
680
  }
425
- ]
681
+ ],
682
+ isError: true
426
683
  };
427
684
  }
428
- }
429
- case "marketDataRequest": {
430
- try {
431
- const { mdUpdateType, symbol, mdReqID, subscriptionRequestType, mdEntryType } = validateArgs(
432
- args,
433
- marketDataRequestInputSchema
434
- );
435
- const response = new Promise((resolve) => {
436
- this.pendingRequests.set(mdReqID, resolve);
437
- });
438
- const marketDataRequest = this.parser?.createMessage(
439
- new import_fixparser.Field(import_fixparser.Fields.MsgType, import_fixparser.Messages.MarketDataRequest),
440
- new import_fixparser.Field(import_fixparser.Fields.SenderCompID, this.parser?.sender),
441
- new import_fixparser.Field(import_fixparser.Fields.MsgSeqNum, this.parser?.getNextTargetMsgSeqNum()),
442
- new import_fixparser.Field(import_fixparser.Fields.TargetCompID, this.parser?.target),
443
- new import_fixparser.Field(import_fixparser.Fields.SendingTime, this.parser?.getTimestamp()),
444
- new import_fixparser.Field(import_fixparser.Fields.MarketDepth, 0),
445
- new import_fixparser.Field(import_fixparser.Fields.MDUpdateType, mdUpdateType),
446
- new import_fixparser.Field(import_fixparser.Fields.NoRelatedSym, 1),
447
- new import_fixparser.Field(import_fixparser.Fields.Symbol, symbol),
448
- new import_fixparser.Field(import_fixparser.Fields.MDReqID, mdReqID),
449
- new import_fixparser.Field(import_fixparser.Fields.SubscriptionRequestType, subscriptionRequestType),
450
- new import_fixparser.Field(import_fixparser.Fields.NoMDEntryTypes, 1),
451
- new import_fixparser.Field(import_fixparser.Fields.MDEntryType, mdEntryType)
452
- );
453
- if (!this.parser?.connected) {
454
- return {
455
- isError: true,
456
- content: [
457
- {
458
- type: "text",
459
- text: "Error: Not connected. Ignoring message."
460
- }
461
- ]
462
- };
463
- }
464
- this.parser?.send(marketDataRequest);
465
- const fixData = await response;
685
+ 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) {
466
686
  return {
467
687
  content: [
468
688
  {
469
689
  type: "text",
470
- text: `Market data for ${symbol}: ${JSON.stringify(fixData.toFIXJSON())}`
690
+ text: "Error: Order parameters do not match the verified order. Please use the exact same parameters that were verified."
471
691
  }
472
- ]
692
+ ],
693
+ isError: true
473
694
  };
474
- } catch (error) {
695
+ }
696
+ const response = new Promise((resolve) => {
697
+ this.pendingRequests.set(args.clOrdID, resolve);
698
+ });
699
+ const order = this.parser?.createMessage(
700
+ new import_fixparser.Field(import_fixparser.Fields.MsgType, import_fixparser.Messages.NewOrderSingle),
701
+ new import_fixparser.Field(import_fixparser.Fields.MsgSeqNum, this.parser?.getNextTargetMsgSeqNum()),
702
+ new import_fixparser.Field(import_fixparser.Fields.SenderCompID, this.parser?.sender),
703
+ new import_fixparser.Field(import_fixparser.Fields.TargetCompID, this.parser?.target),
704
+ new import_fixparser.Field(import_fixparser.Fields.SendingTime, this.parser?.getTimestamp()),
705
+ new import_fixparser.Field(import_fixparser.Fields.ClOrdID, args.clOrdID),
706
+ new import_fixparser.Field(import_fixparser.Fields.Side, args.side),
707
+ new import_fixparser.Field(import_fixparser.Fields.Symbol, args.symbol),
708
+ new import_fixparser.Field(import_fixparser.Fields.OrderQty, Number.parseFloat(args.quantity)),
709
+ new import_fixparser.Field(import_fixparser.Fields.Price, Number.parseFloat(args.price)),
710
+ new import_fixparser.Field(import_fixparser.Fields.OrdType, args.ordType),
711
+ new import_fixparser.Field(import_fixparser.Fields.HandlInst, args.handlInst),
712
+ new import_fixparser.Field(import_fixparser.Fields.TimeInForce, args.timeInForce),
713
+ new import_fixparser.Field(import_fixparser.Fields.TransactTime, this.parser?.getTimestamp())
714
+ );
715
+ if (!this.parser?.connected) {
475
716
  return {
476
- isError: true,
477
717
  content: [
478
718
  {
479
719
  type: "text",
480
- text: `Error: ${error instanceof Error ? error.message : "Failed to request market data"}`
720
+ text: "Error: Not connected. Ignoring message."
481
721
  }
482
- ]
722
+ ],
723
+ isError: true
483
724
  };
484
725
  }
485
- }
486
- default:
487
- throw new Error(`Unknown tool: ${name}`);
488
- }
489
- });
490
- this.server.setRequestHandler(import_types.ListPromptsRequestSchema, async () => {
491
- return {
492
- prompts: [
493
- {
494
- name: "parse",
495
- description: "Parses a FIX message and describes it in plain language",
496
- arguments: [
497
- {
498
- name: "fixString",
499
- description: "FIX message string to parse",
500
- required: true
501
- }
502
- ]
503
- },
504
- {
505
- name: "parseToJSON",
506
- description: "Parses a FIX message into JSON",
507
- arguments: [
508
- {
509
- name: "fixString",
510
- description: "FIX message string to parse",
511
- required: true
512
- }
513
- ]
514
- },
515
- {
516
- name: "newOrderSingle",
517
- description: "Creates and sends a New Order Single",
518
- arguments: [
519
- {
520
- name: "clOrdID",
521
- description: "Client Order ID",
522
- required: true
523
- },
524
- {
525
- name: "handlInst",
526
- description: "Handling instruction",
527
- required: true
528
- },
529
- {
530
- name: "quantity",
531
- description: "Order quantity",
532
- required: true
533
- },
534
- {
535
- name: "price",
536
- description: "Order price",
537
- required: true
538
- },
539
- {
540
- name: "ordType",
541
- description: "Order type",
542
- required: true
543
- },
544
- {
545
- name: "side",
546
- description: "Order side (1=Buy, 2=Sell, 3=BuyMinus, 4=SellPlus, 5=SellShort, 6=SellShortExempt, 7=Undisclosed, 8=Cross, 9=CrossShort, A=CrossShortExempt, B=AsDefined, C=Opposite, D=Subscribe, E=Redeem, F=Lend, G=Borrow, H=SellUndisclosed)",
547
- required: true
548
- },
549
- {
550
- name: "symbol",
551
- description: "Trading symbol",
552
- required: true
553
- },
554
- {
555
- name: "timeInForce",
556
- description: "Time in force",
557
- required: true
558
- }
559
- ]
560
- },
561
- {
562
- name: "marketDataRequest",
563
- description: "Sends a request for Market Data with the given symbol",
564
- arguments: [
565
- {
566
- name: "mdUpdateType",
567
- description: "Market data update type",
568
- required: true
569
- },
570
- {
571
- name: "symbol",
572
- description: "Trading symbol",
573
- required: true
574
- },
575
- {
576
- name: "mdReqID",
577
- description: "Market data request ID",
578
- required: true
579
- },
580
- {
581
- name: "subscriptionRequestType",
582
- description: "Subscription request type",
583
- required: true
584
- },
726
+ this.parser?.send(order);
727
+ const fixData = await response;
728
+ this.verifiedOrders.delete(args.clOrdID);
729
+ return {
730
+ content: [
585
731
  {
586
- name: "mdEntryType",
587
- description: "Market data entry type",
588
- required: true
732
+ type: "text",
733
+ text: fixData.messageType === import_fixparser.Messages.Reject ? `Reject message for order ${args.clOrdID}: ${JSON.stringify(fixData.toFIXJSON())}` : `Execution Report for order ${args.clOrdID}: ${JSON.stringify(fixData.toFIXJSON())}`
589
734
  }
590
735
  ]
591
- }
592
- ]
593
- };
594
- });
595
- this.server.setRequestHandler(import_types.GetPromptRequestSchema, async (request) => {
596
- const { name, arguments: args } = request.params;
597
- switch (name) {
598
- case "parse": {
599
- const fixString = args?.fixString || "";
736
+ };
737
+ } catch (error) {
600
738
  return {
601
- messages: [
739
+ content: [
602
740
  {
603
- role: "user",
604
- content: {
605
- type: "text",
606
- text: `Please parse and explain this FIX message: ${fixString}`
607
- }
741
+ type: "text",
742
+ text: `Error: ${error instanceof Error ? error.message : "Failed to execute order"}`
608
743
  }
609
- ]
744
+ ],
745
+ isError: true
610
746
  };
611
747
  }
612
- case "parseToJSON": {
613
- const fixString = args?.fixString || "";
614
- return {
615
- messages: [
616
- {
617
- role: "user",
618
- content: {
748
+ }
749
+ );
750
+ this.server.setRequestHandler(
751
+ import_zod.z.object({ method: import_zod.z.literal("marketDataRequest") }),
752
+ async (request, extra) => {
753
+ try {
754
+ const args = request.params;
755
+ const response = new Promise((resolve) => {
756
+ this.pendingRequests.set(args.mdReqID, resolve);
757
+ });
758
+ const messageFields = [
759
+ new import_fixparser.Field(import_fixparser.Fields.MsgType, import_fixparser.Messages.MarketDataRequest),
760
+ new import_fixparser.Field(import_fixparser.Fields.SenderCompID, this.parser?.sender),
761
+ new import_fixparser.Field(import_fixparser.Fields.MsgSeqNum, this.parser?.getNextTargetMsgSeqNum()),
762
+ new import_fixparser.Field(import_fixparser.Fields.TargetCompID, this.parser?.target),
763
+ new import_fixparser.Field(import_fixparser.Fields.SendingTime, this.parser?.getTimestamp()),
764
+ new import_fixparser.Field(import_fixparser.Fields.MDReqID, args.mdReqID),
765
+ new import_fixparser.Field(import_fixparser.Fields.SubscriptionRequestType, args.subscriptionRequestType),
766
+ new import_fixparser.Field(import_fixparser.Fields.MarketDepth, 0),
767
+ new import_fixparser.Field(import_fixparser.Fields.MDUpdateType, args.mdUpdateType)
768
+ ];
769
+ messageFields.push(new import_fixparser.Field(import_fixparser.Fields.NoRelatedSym, args.symbols.length));
770
+ args.symbols.forEach((symbol) => {
771
+ messageFields.push(new import_fixparser.Field(import_fixparser.Fields.Symbol, symbol));
772
+ });
773
+ messageFields.push(new import_fixparser.Field(import_fixparser.Fields.NoMDEntryTypes, args.mdEntryTypes.length));
774
+ args.mdEntryTypes.forEach((entryType) => {
775
+ messageFields.push(new import_fixparser.Field(import_fixparser.Fields.MDEntryType, entryType));
776
+ });
777
+ const mdr = this.parser?.createMessage(...messageFields);
778
+ if (!this.parser?.connected) {
779
+ return {
780
+ content: [
781
+ {
619
782
  type: "text",
620
- text: `Please parse the FIX message to JSON: ${fixString}`
783
+ text: "Error: Not connected. Ignoring message."
621
784
  }
785
+ ],
786
+ isError: true
787
+ };
788
+ }
789
+ this.parser?.send(mdr);
790
+ const fixData = await response;
791
+ return {
792
+ content: [
793
+ {
794
+ type: "text",
795
+ text: `Market data for ${args.symbols.join(", ")}: ${JSON.stringify(fixData.toFIXJSON())}`
622
796
  }
623
797
  ]
624
798
  };
625
- }
626
- case "newOrderSingle": {
627
- const { clOrdID, handlInst, quantity, price, ordType, side, symbol, timeInForce } = args || {};
799
+ } catch (error) {
628
800
  return {
629
- messages: [
801
+ content: [
630
802
  {
631
- role: "user",
632
- content: {
633
- type: "text",
634
- text: [
635
- "Create a New Order Single FIX message with the following parameters:",
636
- `- ClOrdID: ${clOrdID}`,
637
- `- HandlInst: ${handlInst ?? "3"} (IMPORTANT: Use the numeric/alphabetic value, not the descriptive name. For example, use '1' for Manual, '2' for Automated, '3' for AutomatedNoIntervention)`,
638
- `- Quantity: ${quantity}`,
639
- `- Price: ${price}`,
640
- `- OrdType: ${ordType ?? "1"} (IMPORTANT: Use the numeric/alphabetic value, not the descriptive name. For example, use '1' for Market, '2' for Limit, '3' for Stop)`,
641
- `- Side: ${side} (IMPORTANT: Use the numeric/alphabetic value, not the descriptive name. For example, use '1' for Buy, '2' for Sell)`,
642
- `- Symbol: ${symbol}`,
643
- `- TimeInForce: ${timeInForce ?? "0"} (IMPORTANT: Use the numeric/alphabetic value, not the descriptive name. For example, use '0' for Day, '1' for Good Till Cancel, '2' for At Opening, '3' for Immediate or Cancel, '4' for Fill or Kill, '5' for Good Till Crossing, '6' for Good Till Date)`,
644
- "",
645
- 'Note: For the OrdType parameter, always use the numeric/alphabetic value (e.g., "1" for Market, "2" for Limit, "3" for Stop) as defined in the FIX protocol, not the descriptive name.',
646
- 'Note: For the TimeInForce parameter, always use the numeric/alphabetic value (e.g., "0" for Day, "1" for Good Till Cancel, "2" for At Opening) as defined in the FIX protocol, not the descriptive name.',
647
- "",
648
- "IMPORTANT: The response will be either:",
649
- "1. An Execution Report (MsgType=8) if the order was successfully placed",
650
- "2. A Reject message (MsgType=3) if the order failed to execute (e.g., due to missing or invalid parameters)"
651
- ].join("\n")
652
- }
803
+ type: "text",
804
+ text: `Error: ${error instanceof Error ? error.message : "Failed to request market data"}`
653
805
  }
654
- ]
806
+ ],
807
+ isError: true
655
808
  };
656
809
  }
657
- case "marketDataRequest": {
658
- const { mdUpdateType, symbol, mdReqID, subscriptionRequestType, mdEntryType } = args || {};
810
+ }
811
+ );
812
+ this.server.setRequestHandler(
813
+ import_zod.z.object({ method: import_zod.z.literal("greeting-resource") }),
814
+ async (request, extra) => {
815
+ this.parser?.logger.log({
816
+ level: "info",
817
+ message: "MCP Server Resource called: greeting-resource"
818
+ });
819
+ return {
820
+ content: [
821
+ {
822
+ type: "text",
823
+ text: "Hello, world!"
824
+ }
825
+ ]
826
+ };
827
+ }
828
+ );
829
+ this.server.setRequestHandler(
830
+ import_zod.z.object({ method: import_zod.z.literal("stockGraph") }),
831
+ async (request, extra) => {
832
+ this.parser?.logger.log({
833
+ level: "info",
834
+ message: "MCP Server Resource called: stockGraph"
835
+ });
836
+ const args = request.params;
837
+ const symbol = args.symbol;
838
+ const priceHistory = this.marketDataPrices.get(symbol) || [];
839
+ if (priceHistory.length === 0) {
659
840
  return {
660
- messages: [
841
+ content: [
661
842
  {
662
- role: "user",
663
- content: {
664
- type: "text",
665
- text: [
666
- "Create a Market Data Request FIX message with the following parameters:",
667
- `- MDUpdateType: ${mdUpdateType ?? "0"} (IMPORTANT: Use the numeric/alphabetic value, not the descriptive name. For example, use '0' for FullRefresh, '1' for IncrementalRefresh)`,
668
- `- Symbol: ${symbol}`,
669
- `- MDReqID: ${mdReqID}`,
670
- `- SubscriptionRequestType: ${subscriptionRequestType ?? "0"} (IMPORTANT: Use the numeric/alphabetic value, not the descriptive name. For example, use '0' for Snapshot + Updates, '1' for Snapshot, '2' for Unsubscribe)`,
671
- `- MDEntryType: ${mdEntryType ?? "0"} (IMPORTANT: Use the numeric/alphabetic value, not the descriptive name. For example, use '0' for Bid, '1' for Offer, '2' for Trade, '3' for Index Value, '4' for Opening Price)`,
672
- "",
673
- "Format the response as a JSON object with FIX tag numbers as keys and their corresponding values.",
674
- "",
675
- 'Note: For the MDUpdateType parameter, always use the numeric/alphabetic value (e.g., "0" for FullRefresh, "1" for IncrementalRefresh) as defined in the FIX protocol, not the descriptive name.',
676
- 'Note: For the SubscriptionRequestType parameter, always use the numeric/alphabetic value (e.g., "0" for Snapshot + Updates, "1" for Snapshot, "2" for Unsubscribe) as defined in the FIX protocol, not the descriptive name.',
677
- 'Note: For the MDEntryType parameter, always use the numeric/alphabetic value (e.g., "0" for Bid, "1" for Offer, "2" for Trade, "3" for Index Value, "4" for Opening Price) as defined in the FIX protocol, not the descriptive name.'
678
- ].join("\n")
679
- }
843
+ type: "text",
844
+ text: `No price data available for ${symbol}`
680
845
  }
681
846
  ]
682
847
  };
683
848
  }
684
- default:
685
- throw new Error(`Unknown prompt: ${name}`);
849
+ const width = 600;
850
+ const height = 300;
851
+ const padding = 40;
852
+ const xScale = (width - 2 * padding) / (priceHistory.length - 1);
853
+ const yMin = Math.min(...priceHistory.map((d) => d.price));
854
+ const yMax = Math.max(...priceHistory.map((d) => d.price));
855
+ const yScale = (height - 2 * padding) / (yMax - yMin);
856
+ const points = priceHistory.map((d, i) => {
857
+ const x = padding + i * xScale;
858
+ const y = height - padding - (d.price - yMin) * yScale;
859
+ return `${x},${y}`;
860
+ }).join(" L ");
861
+ const svg = `<?xml version="1.0" encoding="UTF-8"?>
862
+ <svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
863
+ <!-- Background -->
864
+ <rect width="100%" height="100%" fill="#f8f9fa"/>
865
+
866
+ <!-- Grid lines -->
867
+ <g stroke="#e9ecef" stroke-width="1">
868
+ ${Array.from({ length: 5 }, (_, i) => {
869
+ const y = padding + (height - 2 * padding) * i / 4;
870
+ return `<line x1="${padding}" y1="${y}" x2="${width - padding}" y2="${y}"/>`;
871
+ }).join("\n")}
872
+ </g>
873
+
874
+ <!-- Price line -->
875
+ <path d="M ${points}"
876
+ fill="none"
877
+ stroke="#007bff"
878
+ stroke-width="2"/>
879
+
880
+ <!-- Data points -->
881
+ ${priceHistory.map((d, i) => {
882
+ const x = padding + i * xScale;
883
+ const y = height - padding - (d.price - yMin) * yScale;
884
+ return `<circle cx="${x}" cy="${y}" r="3" fill="#007bff"/>`;
885
+ }).join("\n")}
886
+
887
+ <!-- Labels -->
888
+ <g font-family="Arial" font-size="12" fill="#495057">
889
+ ${Array.from({ length: 5 }, (_, i) => {
890
+ const x = padding + (width - 2 * padding) * i / 4;
891
+ const index = Math.floor((priceHistory.length - 1) * i / 4);
892
+ const timestamp = new Date(priceHistory[index].timestamp).toLocaleTimeString();
893
+ return `<text x="${x + padding}" y="${height - padding + 20}" text-anchor="middle">${timestamp}</text>`;
894
+ }).join("\n")}
895
+ ${Array.from({ length: 5 }, (_, i) => {
896
+ const y = padding + (height - 2 * padding) * i / 4;
897
+ const price = yMax - (yMax - yMin) * i / 4;
898
+ return `<text x="${padding - 5}" y="${y + 4}" text-anchor="end">$${price.toFixed(2)}</text>`;
899
+ }).join("\n")}
900
+ </g>
901
+
902
+ <!-- Title -->
903
+ <text x="${width / 2}" y="${padding / 2}"
904
+ font-family="Arial" font-size="16" font-weight="bold"
905
+ text-anchor="middle" fill="#212529">
906
+ ${symbol} - Price Chart (${priceHistory.length} points)
907
+ </text>
908
+ </svg>`;
909
+ return {
910
+ content: [
911
+ {
912
+ type: "text",
913
+ text: svg
914
+ }
915
+ ]
916
+ };
686
917
  }
687
- });
918
+ );
919
+ this.server.setRequestHandler(
920
+ import_zod.z.object({ method: import_zod.z.literal("stockPriceHistory") }),
921
+ async (request, extra) => {
922
+ this.parser?.logger.log({
923
+ level: "info",
924
+ message: "MCP Server Resource called: stockPriceHistory"
925
+ });
926
+ const args = request.params;
927
+ const symbol = args.symbol;
928
+ const priceHistory = this.marketDataPrices.get(symbol) || [];
929
+ return {
930
+ content: [
931
+ {
932
+ type: "text",
933
+ text: JSON.stringify(
934
+ {
935
+ symbol,
936
+ count: priceHistory.length,
937
+ prices: priceHistory.map((point) => ({
938
+ timestamp: new Date(point.timestamp).toISOString(),
939
+ price: point.price
940
+ }))
941
+ },
942
+ null,
943
+ 2
944
+ )
945
+ }
946
+ ]
947
+ };
948
+ }
949
+ );
688
950
  process.on("SIGINT", async () => {
689
951
  await this.server.close();
690
952
  process.exit(0);