fixparser-plugin-mcp 9.1.7-ff7241ee → 9.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1431 +0,0 @@
1
- // src/MCPLocal.ts
2
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { Field, Fields, Messages } from "fixparser";
5
- import { z } from "zod";
6
- var symbolSchema = z.object({
7
- symbol: z.string()
8
- });
9
- var fixStringSchema = z.object({
10
- fixString: z.string()
11
- });
12
- var orderSchema = z.object({
13
- clOrdID: z.string(),
14
- handlInst: z.enum(["1", "2", "3"]),
15
- quantity: z.string(),
16
- price: z.string(),
17
- ordType: z.enum([
18
- "1",
19
- "2",
20
- "3",
21
- "4",
22
- "5",
23
- "6",
24
- "7",
25
- "8",
26
- "9",
27
- "A",
28
- "B",
29
- "C",
30
- "D",
31
- "E",
32
- "F",
33
- "G",
34
- "H",
35
- "I",
36
- "J",
37
- "K",
38
- "L",
39
- "M",
40
- "P",
41
- "Q",
42
- "R",
43
- "S"
44
- ]),
45
- side: z.enum(["1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H"]),
46
- symbol: z.string(),
47
- timeInForce: z.enum(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C"])
48
- });
49
- var marketDataRequestSchema = z.object({
50
- mdUpdateType: z.enum(["0", "1"]),
51
- symbols: z.array(z.string()),
52
- mdReqID: z.string(),
53
- subscriptionRequestType: z.enum(["0", "1", "2"]),
54
- mdEntryTypes: z.array(
55
- z.enum([
56
- "0",
57
- "1",
58
- "2",
59
- "3",
60
- "4",
61
- "5",
62
- "6",
63
- "7",
64
- "8",
65
- "9",
66
- "A",
67
- "B",
68
- "C",
69
- "D",
70
- "E",
71
- "F",
72
- "G",
73
- "H",
74
- "J",
75
- "K",
76
- "L",
77
- "M",
78
- "N",
79
- "O",
80
- "P",
81
- "Q",
82
- "R",
83
- "S",
84
- "T",
85
- "U",
86
- "V",
87
- "W",
88
- "X",
89
- "Y",
90
- "Z",
91
- "a",
92
- "b",
93
- "c",
94
- "d",
95
- "e",
96
- "g",
97
- "h",
98
- "i",
99
- "t"
100
- ])
101
- )
102
- });
103
- var MCPLocal = class {
104
- parser;
105
- server = new Server(
106
- {
107
- name: "fixparser",
108
- version: "1.0.0"
109
- },
110
- {
111
- capabilities: {
112
- tools: {
113
- parse: {
114
- description: "Parses a FIX message and describes it in plain language",
115
- parameters: {
116
- type: "object",
117
- properties: {
118
- fixString: { type: "string" }
119
- },
120
- required: ["fixString"]
121
- }
122
- },
123
- parseToJSON: {
124
- description: "Parses a FIX message into JSON",
125
- parameters: {
126
- type: "object",
127
- properties: {
128
- fixString: { type: "string" }
129
- },
130
- required: ["fixString"]
131
- }
132
- },
133
- verifyOrder: {
134
- description: "Verifies order parameters before execution",
135
- parameters: {
136
- type: "object",
137
- properties: {
138
- clOrdID: { type: "string" },
139
- handlInst: { type: "string", enum: ["1", "2", "3"] },
140
- quantity: { type: "string" },
141
- price: { type: "string" },
142
- ordType: {
143
- type: "string",
144
- enum: [
145
- "1",
146
- "2",
147
- "3",
148
- "4",
149
- "5",
150
- "6",
151
- "7",
152
- "8",
153
- "9",
154
- "A",
155
- "B",
156
- "C",
157
- "D",
158
- "E",
159
- "F",
160
- "G",
161
- "H",
162
- "I",
163
- "J",
164
- "K",
165
- "L",
166
- "M",
167
- "P",
168
- "Q",
169
- "R",
170
- "S"
171
- ]
172
- },
173
- side: {
174
- type: "string",
175
- enum: [
176
- "1",
177
- "2",
178
- "3",
179
- "4",
180
- "5",
181
- "6",
182
- "7",
183
- "8",
184
- "9",
185
- "A",
186
- "B",
187
- "C",
188
- "D",
189
- "E",
190
- "F",
191
- "G",
192
- "H"
193
- ]
194
- },
195
- symbol: { type: "string" },
196
- timeInForce: {
197
- type: "string",
198
- enum: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C"]
199
- }
200
- },
201
- required: [
202
- "clOrdID",
203
- "handlInst",
204
- "quantity",
205
- "price",
206
- "ordType",
207
- "side",
208
- "symbol",
209
- "timeInForce"
210
- ]
211
- }
212
- },
213
- executeOrder: {
214
- description: "Executes a verified order",
215
- parameters: {
216
- type: "object",
217
- properties: {
218
- clOrdID: { type: "string" },
219
- handlInst: { type: "string", enum: ["1", "2", "3"] },
220
- quantity: { type: "string" },
221
- price: { type: "string" },
222
- ordType: { type: "string" },
223
- side: { type: "string" },
224
- symbol: { type: "string" },
225
- timeInForce: { type: "string" }
226
- },
227
- required: [
228
- "clOrdID",
229
- "handlInst",
230
- "quantity",
231
- "price",
232
- "ordType",
233
- "side",
234
- "symbol",
235
- "timeInForce"
236
- ]
237
- }
238
- },
239
- marketDataRequest: {
240
- description: "Requests market data for specified symbols",
241
- parameters: {
242
- type: "object",
243
- properties: {
244
- mdUpdateType: { type: "string", enum: ["0", "1"] },
245
- symbols: { type: "array", items: { type: "string" } },
246
- mdReqID: { type: "string" },
247
- subscriptionRequestType: { type: "string", enum: ["0", "1", "2"] },
248
- mdEntryTypes: { type: "array", items: { type: "string" } }
249
- },
250
- required: ["mdUpdateType", "symbols", "mdReqID", "subscriptionRequestType", "mdEntryTypes"]
251
- }
252
- }
253
- },
254
- resources: {
255
- greeting: {
256
- description: "A simple greeting resource",
257
- uri: "greeting-resource"
258
- },
259
- stockGraph: {
260
- description: "Generates a price chart for a given symbol",
261
- uri: "stockGraph",
262
- parameters: {
263
- type: "object",
264
- properties: {
265
- symbol: { type: "string" }
266
- },
267
- required: ["symbol"]
268
- }
269
- },
270
- stockPriceHistory: {
271
- description: "Returns price history for a given symbol",
272
- uri: "stockPriceHistory",
273
- parameters: {
274
- type: "object",
275
- properties: {
276
- symbol: { type: "string" }
277
- },
278
- required: ["symbol"]
279
- }
280
- }
281
- }
282
- }
283
- }
284
- );
285
- transport = new StdioServerTransport();
286
- onReady = void 0;
287
- pendingRequests = /* @__PURE__ */ new Map();
288
- verifiedOrders = /* @__PURE__ */ new Map();
289
- // Store market data prices with timestamps
290
- marketDataPrices = /* @__PURE__ */ new Map();
291
- MAX_PRICE_HISTORY = 1e5;
292
- // Maximum number of price points to store per symbol
293
- constructor({ logger, onReady }) {
294
- if (onReady) this.onReady = onReady;
295
- }
296
- async register(parser) {
297
- this.parser = parser;
298
- this.parser.addOnMessageCallback((message) => {
299
- this.parser?.logger.log({
300
- level: "info",
301
- message: `MCP Server received message: ${message.messageType}: ${message.description}`
302
- });
303
- const msgType = message.messageType;
304
- if (msgType === Messages.MarketDataSnapshotFullRefresh || msgType === Messages.ExecutionReport || msgType === Messages.Reject || msgType === Messages.MarketDataIncrementalRefresh) {
305
- this.parser?.logger.log({
306
- level: "info",
307
- message: `MCP Server handling message type: ${msgType}`
308
- });
309
- let id;
310
- if (msgType === Messages.MarketDataIncrementalRefresh || msgType === Messages.MarketDataSnapshotFullRefresh) {
311
- const symbol = message.getField(Fields.Symbol);
312
- const price = message.getField(Fields.MDEntryPx);
313
- const timestamp = message.getField(Fields.MDEntryTime)?.value || Date.now();
314
- if (symbol?.value && price?.value) {
315
- const symbolStr = String(symbol.value);
316
- const priceNum = Number(price.value);
317
- const priceHistory = this.marketDataPrices.get(symbolStr) || [];
318
- priceHistory.push({
319
- timestamp: Number(timestamp),
320
- price: priceNum
321
- });
322
- if (priceHistory.length > this.MAX_PRICE_HISTORY) {
323
- priceHistory.shift();
324
- }
325
- this.marketDataPrices.set(symbolStr, priceHistory);
326
- this.parser?.logger.log({
327
- level: "info",
328
- message: `MCP Server added ${symbol}: ${priceNum}`
329
- });
330
- }
331
- }
332
- if (msgType === Messages.MarketDataSnapshotFullRefresh) {
333
- const mdReqID = message.getField(Fields.MDReqID);
334
- if (mdReqID) id = String(mdReqID.value);
335
- } else if (msgType === Messages.ExecutionReport) {
336
- const clOrdID = message.getField(Fields.ClOrdID);
337
- if (clOrdID) id = String(clOrdID.value);
338
- } else if (msgType === Messages.Reject) {
339
- const refSeqNum = message.getField(Fields.RefSeqNum);
340
- if (refSeqNum) id = String(refSeqNum.value);
341
- }
342
- if (id) {
343
- const callback = this.pendingRequests.get(id);
344
- if (callback) {
345
- callback(message);
346
- this.pendingRequests.delete(id);
347
- }
348
- }
349
- }
350
- });
351
- this.addWorkflows();
352
- await this.server.connect(this.transport);
353
- if (this.onReady) {
354
- this.onReady();
355
- }
356
- }
357
- addWorkflows() {
358
- if (!this.parser) {
359
- return;
360
- }
361
- if (!this.server) {
362
- return;
363
- }
364
- this.server.setRequestHandler(
365
- z.object({ method: z.literal("resources/list") }),
366
- async (request, extra) => {
367
- return {
368
- resources: [
369
- {
370
- name: "greeting",
371
- description: "A simple greeting resource",
372
- uri: "greeting-resource"
373
- },
374
- {
375
- name: "stockGraph",
376
- description: "Generates a price chart for a given symbol",
377
- uri: "stockGraph",
378
- parameters: {
379
- type: "object",
380
- properties: {
381
- symbol: { type: "string" }
382
- },
383
- required: ["symbol"]
384
- }
385
- },
386
- {
387
- name: "stockPriceHistory",
388
- description: "Returns price history for a given symbol",
389
- uri: "stockPriceHistory",
390
- parameters: {
391
- type: "object",
392
- properties: {
393
- symbol: { type: "string" }
394
- },
395
- required: ["symbol"]
396
- }
397
- }
398
- ]
399
- };
400
- }
401
- );
402
- this.server.setRequestHandler(
403
- z.object({ method: z.literal("tools/list") }),
404
- async (request, extra) => {
405
- return {
406
- tools: [
407
- {
408
- name: "parse",
409
- description: "Parses a FIX message and describes it in plain language",
410
- inputSchema: {
411
- type: "object",
412
- properties: {
413
- fixString: { type: "string" }
414
- },
415
- required: ["fixString"]
416
- }
417
- },
418
- {
419
- name: "parseToJSON",
420
- description: "Parses a FIX message into JSON",
421
- inputSchema: {
422
- type: "object",
423
- properties: {
424
- fixString: { type: "string" }
425
- },
426
- required: ["fixString"]
427
- }
428
- },
429
- {
430
- name: "verifyOrder",
431
- description: "Verifies order parameters before execution",
432
- inputSchema: {
433
- type: "object",
434
- properties: {
435
- clOrdID: { type: "string" },
436
- handlInst: { type: "string", enum: ["1", "2", "3"] },
437
- quantity: { type: "string" },
438
- price: { type: "string" },
439
- ordType: {
440
- type: "string",
441
- enum: [
442
- "1",
443
- "2",
444
- "3",
445
- "4",
446
- "5",
447
- "6",
448
- "7",
449
- "8",
450
- "9",
451
- "A",
452
- "B",
453
- "C",
454
- "D",
455
- "E",
456
- "F",
457
- "G",
458
- "H",
459
- "I",
460
- "J",
461
- "K",
462
- "L",
463
- "M",
464
- "P",
465
- "Q",
466
- "R",
467
- "S"
468
- ]
469
- },
470
- side: {
471
- type: "string",
472
- enum: [
473
- "1",
474
- "2",
475
- "3",
476
- "4",
477
- "5",
478
- "6",
479
- "7",
480
- "8",
481
- "9",
482
- "A",
483
- "B",
484
- "C",
485
- "D",
486
- "E",
487
- "F",
488
- "G",
489
- "H"
490
- ]
491
- },
492
- symbol: { type: "string" },
493
- timeInForce: {
494
- type: "string",
495
- enum: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C"]
496
- }
497
- },
498
- required: [
499
- "clOrdID",
500
- "handlInst",
501
- "quantity",
502
- "price",
503
- "ordType",
504
- "side",
505
- "symbol",
506
- "timeInForce"
507
- ]
508
- }
509
- },
510
- {
511
- name: "executeOrder",
512
- description: "Executes a verified order",
513
- inputSchema: {
514
- type: "object",
515
- properties: {
516
- clOrdID: { type: "string" },
517
- handlInst: { type: "string", enum: ["1", "2", "3"] },
518
- quantity: { type: "string" },
519
- price: { type: "string" },
520
- ordType: { type: "string" },
521
- side: { type: "string" },
522
- symbol: { type: "string" },
523
- timeInForce: { type: "string" }
524
- },
525
- required: [
526
- "clOrdID",
527
- "handlInst",
528
- "quantity",
529
- "price",
530
- "ordType",
531
- "side",
532
- "symbol",
533
- "timeInForce"
534
- ]
535
- }
536
- },
537
- {
538
- name: "marketDataRequest",
539
- description: "Requests market data for specified symbols",
540
- inputSchema: {
541
- type: "object",
542
- properties: {
543
- mdUpdateType: { type: "string", enum: ["0", "1"] },
544
- symbols: { type: "array", items: { type: "string" } },
545
- mdReqID: { type: "string" },
546
- subscriptionRequestType: { type: "string", enum: ["0", "1", "2"] },
547
- mdEntryTypes: { type: "array", items: { type: "string" } }
548
- },
549
- required: [
550
- "mdUpdateType",
551
- "symbols",
552
- "mdReqID",
553
- "subscriptionRequestType",
554
- "mdEntryTypes"
555
- ]
556
- }
557
- }
558
- ]
559
- };
560
- }
561
- );
562
- this.server.setRequestHandler(
563
- z.object({
564
- method: z.literal("resources/read"),
565
- params: z.object({
566
- uri: z.string()
567
- })
568
- }),
569
- async (request, extra) => {
570
- const { uri } = request.params;
571
- switch (uri) {
572
- case "greeting-resource":
573
- return {
574
- contents: [
575
- {
576
- type: "text",
577
- text: "Hello, world!",
578
- uri: "greeting-resource"
579
- }
580
- ]
581
- };
582
- case "stockGraph":
583
- return {
584
- contents: [
585
- {
586
- type: "text",
587
- text: "This resource requires a symbol parameter. Please use the stockGraph resource with a symbol parameter.",
588
- uri: "stockGraph"
589
- }
590
- ]
591
- };
592
- case "stockPriceHistory":
593
- return {
594
- contents: [
595
- {
596
- type: "text",
597
- text: "This resource requires a symbol parameter. Please use the stockPriceHistory resource with a symbol parameter.",
598
- uri: "stockPriceHistory"
599
- }
600
- ]
601
- };
602
- default:
603
- return {
604
- contents: [
605
- {
606
- type: "text",
607
- text: `Resource not found: ${uri}`,
608
- uri
609
- }
610
- ],
611
- isError: true
612
- };
613
- }
614
- }
615
- );
616
- this.server.setRequestHandler(
617
- z.object({
618
- method: z.literal("parse"),
619
- params: fixStringSchema
620
- }),
621
- async (request, extra) => {
622
- try {
623
- const args = request.params;
624
- const parsedMessage = this.parser?.parse(args.fixString);
625
- if (!parsedMessage || parsedMessage.length === 0) {
626
- return {
627
- contents: [{ type: "text", text: "Error: Failed to parse FIX string" }],
628
- isError: true
629
- };
630
- }
631
- return {
632
- contents: [
633
- {
634
- type: "text",
635
- text: `${parsedMessage[0].description}
636
- ${parsedMessage[0].messageTypeDescription}`
637
- }
638
- ]
639
- };
640
- } catch (error) {
641
- return {
642
- contents: [
643
- {
644
- type: "text",
645
- text: `Error: ${error instanceof Error ? error.message : "Failed to parse FIX string"}`
646
- }
647
- ],
648
- isError: true
649
- };
650
- }
651
- }
652
- );
653
- this.server.setRequestHandler(
654
- z.object({
655
- method: z.literal("parseToJSON"),
656
- params: fixStringSchema
657
- }),
658
- async (request, extra) => {
659
- try {
660
- const args = request.params;
661
- const parsedMessage = this.parser?.parse(args.fixString);
662
- if (!parsedMessage || parsedMessage.length === 0) {
663
- return {
664
- contents: [{ type: "text", text: "Error: Failed to parse FIX string" }],
665
- isError: true
666
- };
667
- }
668
- return {
669
- contents: [
670
- {
671
- type: "text",
672
- text: `${parsedMessage[0].toFIXJSON()}`
673
- }
674
- ]
675
- };
676
- } catch (error) {
677
- return {
678
- contents: [
679
- {
680
- type: "text",
681
- text: `Error: ${error instanceof Error ? error.message : "Failed to parse FIX string"}`
682
- }
683
- ],
684
- isError: true
685
- };
686
- }
687
- }
688
- );
689
- this.server.setRequestHandler(
690
- z.object({
691
- method: z.literal("verifyOrder"),
692
- params: orderSchema
693
- }),
694
- async (request, extra) => {
695
- try {
696
- const args = request.params;
697
- this.verifiedOrders.set(args.clOrdID, {
698
- clOrdID: args.clOrdID,
699
- handlInst: args.handlInst,
700
- quantity: Number.parseFloat(args.quantity),
701
- price: Number.parseFloat(args.price),
702
- ordType: args.ordType,
703
- side: args.side,
704
- symbol: args.symbol,
705
- timeInForce: args.timeInForce
706
- });
707
- const ordTypeNames = {
708
- "1": "Market",
709
- "2": "Limit",
710
- "3": "Stop",
711
- "4": "StopLimit",
712
- "5": "MarketOnClose",
713
- "6": "WithOrWithout",
714
- "7": "LimitOrBetter",
715
- "8": "LimitWithOrWithout",
716
- "9": "OnBasis",
717
- A: "OnClose",
718
- B: "LimitOnClose",
719
- C: "ForexMarket",
720
- D: "PreviouslyQuoted",
721
- E: "PreviouslyIndicated",
722
- F: "ForexLimit",
723
- G: "ForexSwap",
724
- H: "ForexPreviouslyQuoted",
725
- I: "Funari",
726
- J: "MarketIfTouched",
727
- K: "MarketWithLeftOverAsLimit",
728
- L: "PreviousFundValuationPoint",
729
- M: "NextFundValuationPoint",
730
- P: "Pegged",
731
- Q: "CounterOrderSelection",
732
- R: "StopOnBidOrOffer",
733
- S: "StopLimitOnBidOrOffer"
734
- };
735
- const sideNames = {
736
- "1": "Buy",
737
- "2": "Sell",
738
- "3": "BuyMinus",
739
- "4": "SellPlus",
740
- "5": "SellShort",
741
- "6": "SellShortExempt",
742
- "7": "Undisclosed",
743
- "8": "Cross",
744
- "9": "CrossShort",
745
- A: "CrossShortExempt",
746
- B: "AsDefined",
747
- C: "Opposite",
748
- D: "Subscribe",
749
- E: "Redeem",
750
- F: "Lend",
751
- G: "Borrow",
752
- H: "SellUndisclosed"
753
- };
754
- const timeInForceNames = {
755
- "0": "Day",
756
- "1": "GoodTillCancel",
757
- "2": "AtTheOpening",
758
- "3": "ImmediateOrCancel",
759
- "4": "FillOrKill",
760
- "5": "GoodTillCrossing",
761
- "6": "GoodTillDate",
762
- "7": "AtTheClose",
763
- "8": "GoodThroughCrossing",
764
- "9": "AtCrossing",
765
- A: "GoodForTime",
766
- B: "GoodForAuction",
767
- C: "GoodForMonth"
768
- };
769
- const handlInstNames = {
770
- "1": "AutomatedExecutionNoIntervention",
771
- "2": "AutomatedExecutionInterventionOK",
772
- "3": "ManualOrder"
773
- };
774
- return {
775
- contents: [
776
- {
777
- type: "text",
778
- text: `VERIFICATION: All parameters valid. Ready to proceed with order execution.
779
-
780
- Parameters verified:
781
- - ClOrdID: ${args.clOrdID}
782
- - HandlInst: ${args.handlInst} (${handlInstNames[args.handlInst]})
783
- - Quantity: ${args.quantity}
784
- - Price: ${args.price}
785
- - OrdType: ${args.ordType} (${ordTypeNames[args.ordType]})
786
- - Side: ${args.side} (${sideNames[args.side]})
787
- - Symbol: ${args.symbol}
788
- - TimeInForce: ${args.timeInForce} (${timeInForceNames[args.timeInForce]})
789
-
790
- To execute this order, call the executeOrder tool with these exact same parameters.`
791
- }
792
- ]
793
- };
794
- } catch (error) {
795
- return {
796
- contents: [
797
- {
798
- type: "text",
799
- text: `Error: ${error instanceof Error ? error.message : "Failed to verify order parameters"}`
800
- }
801
- ],
802
- isError: true
803
- };
804
- }
805
- }
806
- );
807
- this.server.setRequestHandler(
808
- z.object({
809
- method: z.literal("executeOrder"),
810
- params: orderSchema
811
- }),
812
- async (request, extra) => {
813
- try {
814
- const args = request.params;
815
- const verifiedOrder = this.verifiedOrders.get(args.clOrdID);
816
- if (!verifiedOrder) {
817
- return {
818
- contents: [
819
- {
820
- type: "text",
821
- text: `Error: Order ${args.clOrdID} has not been verified. Please call verifyOrder first.`
822
- }
823
- ],
824
- isError: true
825
- };
826
- }
827
- 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) {
828
- return {
829
- contents: [
830
- {
831
- type: "text",
832
- text: "Error: Order parameters do not match the verified order. Please use the exact same parameters that were verified."
833
- }
834
- ],
835
- isError: true
836
- };
837
- }
838
- const response = new Promise((resolve) => {
839
- this.pendingRequests.set(args.clOrdID, resolve);
840
- });
841
- const order = this.parser?.createMessage(
842
- new Field(Fields.MsgType, Messages.NewOrderSingle),
843
- new Field(Fields.MsgSeqNum, this.parser?.getNextTargetMsgSeqNum()),
844
- new Field(Fields.SenderCompID, this.parser?.sender),
845
- new Field(Fields.TargetCompID, this.parser?.target),
846
- new Field(Fields.SendingTime, this.parser?.getTimestamp()),
847
- new Field(Fields.ClOrdID, args.clOrdID),
848
- new Field(Fields.Side, args.side),
849
- new Field(Fields.Symbol, args.symbol),
850
- new Field(Fields.OrderQty, Number.parseFloat(args.quantity)),
851
- new Field(Fields.Price, Number.parseFloat(args.price)),
852
- new Field(Fields.OrdType, args.ordType),
853
- new Field(Fields.HandlInst, args.handlInst),
854
- new Field(Fields.TimeInForce, args.timeInForce),
855
- new Field(Fields.TransactTime, this.parser?.getTimestamp())
856
- );
857
- if (!this.parser?.connected) {
858
- return {
859
- contents: [
860
- {
861
- type: "text",
862
- text: "Error: Not connected. Ignoring message."
863
- }
864
- ],
865
- isError: true
866
- };
867
- }
868
- this.parser?.send(order);
869
- const fixData = await response;
870
- this.verifiedOrders.delete(args.clOrdID);
871
- return {
872
- contents: [
873
- {
874
- type: "text",
875
- 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())}`
876
- }
877
- ]
878
- };
879
- } catch (error) {
880
- return {
881
- contents: [
882
- {
883
- type: "text",
884
- text: `Error: ${error instanceof Error ? error.message : "Failed to execute order"}`
885
- }
886
- ],
887
- isError: true
888
- };
889
- }
890
- }
891
- );
892
- this.server.setRequestHandler(
893
- z.object({
894
- method: z.literal("marketDataRequest"),
895
- params: marketDataRequestSchema
896
- }),
897
- async (request, extra) => {
898
- try {
899
- const args = request.params;
900
- const response = new Promise((resolve) => {
901
- this.pendingRequests.set(args.mdReqID, resolve);
902
- });
903
- const messageFields = [
904
- new Field(Fields.MsgType, Messages.MarketDataRequest),
905
- new Field(Fields.SenderCompID, this.parser?.sender),
906
- new Field(Fields.MsgSeqNum, this.parser?.getNextTargetMsgSeqNum()),
907
- new Field(Fields.TargetCompID, this.parser?.target),
908
- new Field(Fields.SendingTime, this.parser?.getTimestamp()),
909
- new Field(Fields.MDReqID, args.mdReqID),
910
- new Field(Fields.SubscriptionRequestType, args.subscriptionRequestType),
911
- new Field(Fields.MarketDepth, 0),
912
- new Field(Fields.MDUpdateType, args.mdUpdateType)
913
- ];
914
- messageFields.push(new Field(Fields.NoRelatedSym, args.symbols.length));
915
- args.symbols.forEach((symbol) => {
916
- messageFields.push(new Field(Fields.Symbol, symbol));
917
- });
918
- messageFields.push(new Field(Fields.NoMDEntryTypes, args.mdEntryTypes.length));
919
- args.mdEntryTypes.forEach((entryType) => {
920
- messageFields.push(new Field(Fields.MDEntryType, entryType));
921
- });
922
- const mdr = this.parser?.createMessage(...messageFields);
923
- if (!this.parser?.connected) {
924
- return {
925
- contents: [
926
- {
927
- type: "text",
928
- text: "Error: Not connected. Ignoring message."
929
- }
930
- ],
931
- isError: true
932
- };
933
- }
934
- this.parser?.send(mdr);
935
- const fixData = await response;
936
- return {
937
- contents: [
938
- {
939
- type: "text",
940
- text: `Market data for ${args.symbols.join(", ")}: ${JSON.stringify(fixData.toFIXJSON())}`
941
- }
942
- ]
943
- };
944
- } catch (error) {
945
- return {
946
- contents: [
947
- {
948
- type: "text",
949
- text: `Error: ${error instanceof Error ? error.message : "Failed to request market data"}`
950
- }
951
- ],
952
- isError: true
953
- };
954
- }
955
- }
956
- );
957
- this.server.setRequestHandler(
958
- z.object({
959
- method: z.literal("stockGraph"),
960
- params: symbolSchema
961
- }),
962
- async (request, extra) => {
963
- this.parser?.logger.log({
964
- level: "info",
965
- message: "MCP Server Resource called: stockGraph"
966
- });
967
- const args = request.params;
968
- const symbol = args.symbol;
969
- const priceHistory = this.marketDataPrices.get(symbol) || [];
970
- if (priceHistory.length === 0) {
971
- return {
972
- contents: [
973
- {
974
- type: "text",
975
- text: `No price data available for ${symbol}`
976
- }
977
- ]
978
- };
979
- }
980
- const width = 600;
981
- const height = 300;
982
- const padding = 40;
983
- const xScale = (width - 2 * padding) / (priceHistory.length - 1);
984
- const yMin = Math.min(...priceHistory.map((d) => d.price));
985
- const yMax = Math.max(...priceHistory.map((d) => d.price));
986
- const yScale = (height - 2 * padding) / (yMax - yMin);
987
- const points = priceHistory.map((d, i) => {
988
- const x = padding + i * xScale;
989
- const y = height - padding - (d.price - yMin) * yScale;
990
- return `${x},${y}`;
991
- }).join(" L ");
992
- const svg = `<?xml version="1.0" encoding="UTF-8"?>
993
- <svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
994
- <!-- Background -->
995
- <rect width="100%" height="100%" fill="#f8f9fa"/>
996
-
997
- <!-- Grid lines -->
998
- <g stroke="#e9ecef" stroke-width="1">
999
- ${Array.from({ length: 5 }, (_, i) => {
1000
- const y = padding + (height - 2 * padding) * i / 4;
1001
- return `<line x1="${padding}" y1="${y}" x2="${width - padding}" y2="${y}"/>`;
1002
- }).join("\n")}
1003
- </g>
1004
-
1005
- <!-- Price line -->
1006
- <path d="M ${points}"
1007
- fill="none"
1008
- stroke="#007bff"
1009
- stroke-width="2"/>
1010
-
1011
- <!-- Data points -->
1012
- ${priceHistory.map((d, i) => {
1013
- const x = padding + i * xScale;
1014
- const y = height - padding - (d.price - yMin) * yScale;
1015
- return `<circle cx="${x}" cy="${y}" r="3" fill="#007bff"/>`;
1016
- }).join("\n")}
1017
-
1018
- <!-- Labels -->
1019
- <g font-family="Arial" font-size="12" fill="#495057">
1020
- ${Array.from({ length: 5 }, (_, i) => {
1021
- const x = padding + (width - 2 * padding) * i / 4;
1022
- const index = Math.floor((priceHistory.length - 1) * i / 4);
1023
- const timestamp = new Date(priceHistory[index].timestamp).toLocaleTimeString();
1024
- return `<text x="${x + padding}" y="${height - padding + 20}" text-anchor="middle">${timestamp}</text>`;
1025
- }).join("\n")}
1026
- ${Array.from({ length: 5 }, (_, i) => {
1027
- const y = padding + (height - 2 * padding) * i / 4;
1028
- const price = yMax - (yMax - yMin) * i / 4;
1029
- return `<text x="${padding - 5}" y="${y + 4}" text-anchor="end">$${price.toFixed(2)}</text>`;
1030
- }).join("\n")}
1031
- </g>
1032
-
1033
- <!-- Title -->
1034
- <text x="${width / 2}" y="${padding / 2}"
1035
- font-family="Arial" font-size="16" font-weight="bold"
1036
- text-anchor="middle" fill="#212529">
1037
- ${symbol} - Price Chart (${priceHistory.length} points)
1038
- </text>
1039
- </svg>`;
1040
- return {
1041
- contents: [
1042
- {
1043
- type: "text",
1044
- text: svg
1045
- }
1046
- ]
1047
- };
1048
- }
1049
- );
1050
- this.server.setRequestHandler(
1051
- z.object({
1052
- method: z.literal("stockPriceHistory"),
1053
- params: symbolSchema
1054
- }),
1055
- async (request, extra) => {
1056
- this.parser?.logger.log({
1057
- level: "info",
1058
- message: "MCP Server Resource called: stockPriceHistory"
1059
- });
1060
- const args = request.params;
1061
- const symbol = args.symbol;
1062
- const priceHistory = this.marketDataPrices.get(symbol) || [];
1063
- if (priceHistory.length === 0) {
1064
- return {
1065
- contents: [
1066
- {
1067
- type: "text",
1068
- text: `No price data available for ${symbol}`
1069
- }
1070
- ]
1071
- };
1072
- }
1073
- return {
1074
- contents: [
1075
- {
1076
- type: "text",
1077
- text: JSON.stringify(
1078
- {
1079
- symbol,
1080
- count: priceHistory.length,
1081
- prices: priceHistory.map((point) => ({
1082
- timestamp: new Date(point.timestamp).toISOString(),
1083
- price: point.price
1084
- }))
1085
- },
1086
- null,
1087
- 2
1088
- )
1089
- }
1090
- ]
1091
- };
1092
- }
1093
- );
1094
- this.server.setRequestHandler(
1095
- z.object({
1096
- method: z.literal("tools/call"),
1097
- params: z.object({
1098
- name: z.string(),
1099
- arguments: z.record(z.any()),
1100
- _meta: z.object({
1101
- progressToken: z.number()
1102
- }).optional()
1103
- })
1104
- }),
1105
- async (request, extra) => {
1106
- const { name, arguments: args } = request.params;
1107
- switch (name) {
1108
- case "parse":
1109
- try {
1110
- const parsedMessage = this.parser?.parse(args.fixString);
1111
- if (!parsedMessage || parsedMessage.length === 0) {
1112
- return {
1113
- contents: [{ type: "text", text: "Error: Failed to parse FIX string" }],
1114
- isError: true
1115
- };
1116
- }
1117
- return {
1118
- contents: [
1119
- {
1120
- type: "text",
1121
- text: `${parsedMessage[0].description}
1122
- ${parsedMessage[0].messageTypeDescription}`
1123
- }
1124
- ]
1125
- };
1126
- } catch (error) {
1127
- return {
1128
- contents: [
1129
- {
1130
- type: "text",
1131
- text: `Error: ${error instanceof Error ? error.message : "Failed to parse FIX string"}`
1132
- }
1133
- ],
1134
- isError: true
1135
- };
1136
- }
1137
- case "parseToJSON":
1138
- try {
1139
- const parsedMessage = this.parser?.parse(args.fixString);
1140
- if (!parsedMessage || parsedMessage.length === 0) {
1141
- return {
1142
- contents: [{ type: "text", text: "Error: Failed to parse FIX string" }],
1143
- isError: true
1144
- };
1145
- }
1146
- return {
1147
- contents: [
1148
- {
1149
- type: "text",
1150
- text: `${parsedMessage[0].toFIXJSON()}`
1151
- }
1152
- ]
1153
- };
1154
- } catch (error) {
1155
- return {
1156
- contents: [
1157
- {
1158
- type: "text",
1159
- text: `Error: ${error instanceof Error ? error.message : "Failed to parse FIX string"}`
1160
- }
1161
- ],
1162
- isError: true
1163
- };
1164
- }
1165
- case "verifyOrder":
1166
- try {
1167
- this.verifiedOrders.set(args.clOrdID, {
1168
- clOrdID: args.clOrdID,
1169
- handlInst: args.handlInst,
1170
- quantity: Number.parseFloat(args.quantity),
1171
- price: Number.parseFloat(args.price),
1172
- ordType: args.ordType,
1173
- side: args.side,
1174
- symbol: args.symbol,
1175
- timeInForce: args.timeInForce
1176
- });
1177
- const ordTypeNames = {
1178
- "1": "Market",
1179
- "2": "Limit",
1180
- "3": "Stop",
1181
- "4": "StopLimit",
1182
- "5": "MarketOnClose",
1183
- "6": "WithOrWithout",
1184
- "7": "LimitOrBetter",
1185
- "8": "LimitWithOrWithout",
1186
- "9": "OnBasis",
1187
- A: "OnClose",
1188
- B: "LimitOnClose",
1189
- C: "ForexMarket",
1190
- D: "PreviouslyQuoted",
1191
- E: "PreviouslyIndicated",
1192
- F: "ForexLimit",
1193
- G: "ForexSwap",
1194
- H: "ForexPreviouslyQuoted",
1195
- I: "Funari",
1196
- J: "MarketIfTouched",
1197
- K: "MarketWithLeftOverAsLimit",
1198
- L: "PreviousFundValuationPoint",
1199
- M: "NextFundValuationPoint",
1200
- P: "Pegged",
1201
- Q: "CounterOrderSelection",
1202
- R: "StopOnBidOrOffer",
1203
- S: "StopLimitOnBidOrOffer"
1204
- };
1205
- const sideNames = {
1206
- "1": "Buy",
1207
- "2": "Sell",
1208
- "3": "BuyMinus",
1209
- "4": "SellPlus",
1210
- "5": "SellShort",
1211
- "6": "SellShortExempt",
1212
- "7": "Undisclosed",
1213
- "8": "Cross",
1214
- "9": "CrossShort",
1215
- A: "CrossShortExempt",
1216
- B: "AsDefined",
1217
- C: "Opposite",
1218
- D: "Subscribe",
1219
- E: "Redeem",
1220
- F: "Lend",
1221
- G: "Borrow",
1222
- H: "SellUndisclosed"
1223
- };
1224
- const timeInForceNames = {
1225
- "0": "Day",
1226
- "1": "GoodTillCancel",
1227
- "2": "AtTheOpening",
1228
- "3": "ImmediateOrCancel",
1229
- "4": "FillOrKill",
1230
- "5": "GoodTillCrossing",
1231
- "6": "GoodTillDate",
1232
- "7": "AtTheClose",
1233
- "8": "GoodThroughCrossing",
1234
- "9": "AtCrossing",
1235
- A: "GoodForTime",
1236
- B: "GoodForAuction",
1237
- C: "GoodForMonth"
1238
- };
1239
- const handlInstNames = {
1240
- "1": "AutomatedExecutionNoIntervention",
1241
- "2": "AutomatedExecutionInterventionOK",
1242
- "3": "ManualOrder"
1243
- };
1244
- return {
1245
- contents: [
1246
- {
1247
- type: "text",
1248
- text: `VERIFICATION: All parameters valid. Ready to proceed with order execution.
1249
-
1250
- Parameters verified:
1251
- - ClOrdID: ${args.clOrdID}
1252
- - HandlInst: ${args.handlInst} (${handlInstNames[args.handlInst]})
1253
- - Quantity: ${args.quantity}
1254
- - Price: ${args.price}
1255
- - OrdType: ${args.ordType} (${ordTypeNames[args.ordType]})
1256
- - Side: ${args.side} (${sideNames[args.side]})
1257
- - Symbol: ${args.symbol}
1258
- - TimeInForce: ${args.timeInForce} (${timeInForceNames[args.timeInForce]})
1259
-
1260
- To execute this order, call the executeOrder tool with these exact same parameters.`
1261
- }
1262
- ]
1263
- };
1264
- } catch (error) {
1265
- return {
1266
- contents: [
1267
- {
1268
- type: "text",
1269
- text: `Error: ${error instanceof Error ? error.message : "Failed to verify order parameters"}`
1270
- }
1271
- ],
1272
- isError: true
1273
- };
1274
- }
1275
- case "executeOrder":
1276
- try {
1277
- const verifiedOrder = this.verifiedOrders.get(args.clOrdID);
1278
- if (!verifiedOrder) {
1279
- return {
1280
- contents: [
1281
- {
1282
- type: "text",
1283
- text: `Error: Order ${args.clOrdID} has not been verified. Please call verifyOrder first.`
1284
- }
1285
- ],
1286
- isError: true
1287
- };
1288
- }
1289
- 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) {
1290
- return {
1291
- contents: [
1292
- {
1293
- type: "text",
1294
- text: "Error: Order parameters do not match the verified order. Please use the exact same parameters that were verified."
1295
- }
1296
- ],
1297
- isError: true
1298
- };
1299
- }
1300
- const response = new Promise((resolve) => {
1301
- this.pendingRequests.set(args.clOrdID, resolve);
1302
- });
1303
- const order = this.parser?.createMessage(
1304
- new Field(Fields.MsgType, Messages.NewOrderSingle),
1305
- new Field(Fields.MsgSeqNum, this.parser?.getNextTargetMsgSeqNum()),
1306
- new Field(Fields.SenderCompID, this.parser?.sender),
1307
- new Field(Fields.TargetCompID, this.parser?.target),
1308
- new Field(Fields.SendingTime, this.parser?.getTimestamp()),
1309
- new Field(Fields.ClOrdID, args.clOrdID),
1310
- new Field(Fields.Side, args.side),
1311
- new Field(Fields.Symbol, args.symbol),
1312
- new Field(Fields.OrderQty, Number.parseFloat(args.quantity)),
1313
- new Field(Fields.Price, Number.parseFloat(args.price)),
1314
- new Field(Fields.OrdType, args.ordType),
1315
- new Field(Fields.HandlInst, args.handlInst),
1316
- new Field(Fields.TimeInForce, args.timeInForce),
1317
- new Field(Fields.TransactTime, this.parser?.getTimestamp())
1318
- );
1319
- if (!this.parser?.connected) {
1320
- return {
1321
- contents: [
1322
- {
1323
- type: "text",
1324
- text: "Error: Not connected. Ignoring message."
1325
- }
1326
- ],
1327
- isError: true
1328
- };
1329
- }
1330
- this.parser?.send(order);
1331
- const fixData = await response;
1332
- this.verifiedOrders.delete(args.clOrdID);
1333
- return {
1334
- contents: [
1335
- {
1336
- type: "text",
1337
- 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())}`
1338
- }
1339
- ]
1340
- };
1341
- } catch (error) {
1342
- return {
1343
- contents: [
1344
- {
1345
- type: "text",
1346
- text: `Error: ${error instanceof Error ? error.message : "Failed to execute order"}`
1347
- }
1348
- ],
1349
- isError: true
1350
- };
1351
- }
1352
- case "marketDataRequest":
1353
- try {
1354
- const response = new Promise((resolve) => {
1355
- this.pendingRequests.set(args.mdReqID, resolve);
1356
- });
1357
- const messageFields = [
1358
- new Field(Fields.MsgType, Messages.MarketDataRequest),
1359
- new Field(Fields.SenderCompID, this.parser?.sender),
1360
- new Field(Fields.MsgSeqNum, this.parser?.getNextTargetMsgSeqNum()),
1361
- new Field(Fields.TargetCompID, this.parser?.target),
1362
- new Field(Fields.SendingTime, this.parser?.getTimestamp()),
1363
- new Field(Fields.MDReqID, args.mdReqID),
1364
- new Field(Fields.SubscriptionRequestType, args.subscriptionRequestType),
1365
- new Field(Fields.MarketDepth, 0),
1366
- new Field(Fields.MDUpdateType, args.mdUpdateType)
1367
- ];
1368
- messageFields.push(new Field(Fields.NoRelatedSym, args.symbols.length));
1369
- args.symbols.forEach((symbol) => {
1370
- messageFields.push(new Field(Fields.Symbol, symbol));
1371
- });
1372
- messageFields.push(new Field(Fields.NoMDEntryTypes, args.mdEntryTypes.length));
1373
- args.mdEntryTypes.forEach((entryType) => {
1374
- messageFields.push(new Field(Fields.MDEntryType, entryType));
1375
- });
1376
- const mdr = this.parser?.createMessage(...messageFields);
1377
- if (!this.parser?.connected) {
1378
- return {
1379
- contents: [
1380
- {
1381
- type: "text",
1382
- text: "Error: Not connected. Ignoring message."
1383
- }
1384
- ],
1385
- isError: true
1386
- };
1387
- }
1388
- this.parser?.send(mdr);
1389
- const fixData = await response;
1390
- return {
1391
- contents: [
1392
- {
1393
- type: "text",
1394
- text: `Market data for ${args.symbols.join(", ")}: ${JSON.stringify(fixData.toFIXJSON())}`
1395
- }
1396
- ]
1397
- };
1398
- } catch (error) {
1399
- return {
1400
- contents: [
1401
- {
1402
- type: "text",
1403
- text: `Error: ${error instanceof Error ? error.message : "Failed to request market data"}`
1404
- }
1405
- ],
1406
- isError: true
1407
- };
1408
- }
1409
- default:
1410
- return {
1411
- contents: [
1412
- {
1413
- type: "text",
1414
- text: `Tool not found: ${name}`
1415
- }
1416
- ],
1417
- isError: true
1418
- };
1419
- }
1420
- }
1421
- );
1422
- process.on("SIGINT", async () => {
1423
- await this.server.close();
1424
- process.exit(0);
1425
- });
1426
- }
1427
- };
1428
- export {
1429
- MCPLocal
1430
- };
1431
- //# sourceMappingURL=MCPLocal.mjs.map