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