fixparser-plugin-mcp 9.1.7-75ded9c1 → 9.1.7-89ae1451

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.
@@ -3,6 +3,103 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { Field, Fields, Messages } from "fixparser";
5
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
+ });
6
103
  var MCPLocal = class {
7
104
  parser;
8
105
  server = new Server(
@@ -161,11 +258,25 @@ var MCPLocal = class {
161
258
  },
162
259
  stockGraph: {
163
260
  description: "Generates a price chart for a given symbol",
164
- uri: "stockGraph/{symbol}"
261
+ uri: "stockGraph",
262
+ parameters: {
263
+ type: "object",
264
+ properties: {
265
+ symbol: { type: "string" }
266
+ },
267
+ required: ["symbol"]
268
+ }
165
269
  },
166
270
  stockPriceHistory: {
167
271
  description: "Returns price history for a given symbol",
168
- uri: "stockPriceHistory/{symbol}"
272
+ uri: "stockPriceHistory",
273
+ parameters: {
274
+ type: "object",
275
+ properties: {
276
+ symbol: { type: "string" }
277
+ },
278
+ required: ["symbol"]
279
+ }
169
280
  }
170
281
  }
171
282
  }
@@ -216,14 +327,6 @@ var MCPLocal = class {
216
327
  level: "info",
217
328
  message: `MCP Server added ${symbol}: ${priceNum}`
218
329
  });
219
- this.server.notification({
220
- method: "priceUpdate",
221
- params: {
222
- symbol: symbolStr,
223
- price: priceNum,
224
- timestamp: Number(timestamp)
225
- }
226
- });
227
330
  }
228
331
  }
229
332
  if (msgType === Messages.MarketDataSnapshotFullRefresh) {
@@ -267,20 +370,11 @@ var MCPLocal = class {
267
370
  name: "greeting",
268
371
  description: "A simple greeting resource",
269
372
  uri: "greeting-resource"
270
- }
271
- ]
272
- };
273
- }
274
- );
275
- this.server.setRequestHandler(
276
- z.object({ method: z.literal("resources/templates/list") }),
277
- async (request, extra) => {
278
- return {
279
- resourceTemplates: [
373
+ },
280
374
  {
281
375
  name: "stockGraph",
282
376
  description: "Generates a price chart for a given symbol",
283
- uriTemplate: "stockGraph/{symbol}",
377
+ uri: "stockGraph",
284
378
  parameters: {
285
379
  type: "object",
286
380
  properties: {
@@ -292,7 +386,7 @@ var MCPLocal = class {
292
386
  {
293
387
  name: "stockPriceHistory",
294
388
  description: "Returns price history for a given symbol",
295
- uriTemplate: "stockPriceHistory/{symbol}",
389
+ uri: "stockPriceHistory",
296
390
  parameters: {
297
391
  type: "object",
298
392
  properties: {
@@ -467,176 +561,218 @@ var MCPLocal = class {
467
561
  );
468
562
  this.server.setRequestHandler(
469
563
  z.object({
470
- method: z.literal("tools/call"),
564
+ method: z.literal("resources/read"),
471
565
  params: z.object({
472
- name: z.string(),
473
- arguments: z.any(),
474
- _meta: z.object({
475
- progressToken: z.number()
476
- }).optional()
566
+ uri: z.string()
477
567
  })
478
568
  }),
479
569
  async (request, extra) => {
480
- const { name, arguments: args } = request.params;
481
- switch (name) {
482
- case "parse":
483
- try {
484
- const parsedMessage = this.parser?.parse(args.fixString);
485
- if (!parsedMessage || parsedMessage.length === 0) {
486
- return {
487
- contents: [
488
- {
489
- type: "text",
490
- text: "Error: Failed to parse FIX string",
491
- uri: "parse"
492
- }
493
- ],
494
- isError: true
495
- };
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
+ }
579
+ ]
580
+ };
581
+ case "stockGraph":
582
+ return {
583
+ contents: [
584
+ {
585
+ type: "text",
586
+ text: "This resource requires a symbol parameter. Please use the stockGraph resource with a symbol parameter."
587
+ }
588
+ ]
589
+ };
590
+ case "stockPriceHistory":
591
+ return {
592
+ contents: [
593
+ {
594
+ type: "text",
595
+ text: "This resource requires a symbol parameter. Please use the stockPriceHistory resource with a symbol parameter."
596
+ }
597
+ ]
598
+ };
599
+ default:
600
+ return {
601
+ contents: [
602
+ {
603
+ type: "text",
604
+ text: `Resource not found: ${uri}`
605
+ }
606
+ ],
607
+ isError: true
608
+ };
609
+ }
610
+ }
611
+ );
612
+ this.server.setRequestHandler(
613
+ z.object({
614
+ method: z.literal("parse"),
615
+ params: fixStringSchema
616
+ }),
617
+ async (request, extra) => {
618
+ try {
619
+ const args = request.params;
620
+ const parsedMessage = this.parser?.parse(args.fixString);
621
+ if (!parsedMessage || parsedMessage.length === 0) {
622
+ return {
623
+ content: [{ type: "text", text: "Error: Failed to parse FIX string" }],
624
+ isError: true
625
+ };
626
+ }
627
+ return {
628
+ content: [
629
+ {
630
+ type: "text",
631
+ text: `${parsedMessage[0].description}
632
+ ${parsedMessage[0].messageTypeDescription}`
496
633
  }
497
- return {
498
- contents: [
499
- {
500
- type: "text",
501
- text: `${parsedMessage[0].description}
502
- ${parsedMessage[0].messageTypeDescription}`,
503
- uri: "parse"
504
- }
505
- ]
506
- };
507
- } catch (error) {
508
- return {
509
- contents: [
510
- {
511
- type: "text",
512
- text: `Error: ${error instanceof Error ? error.message : "Failed to parse FIX string"}`,
513
- uri: "parse"
514
- }
515
- ],
516
- isError: true
517
- };
518
- }
519
- case "parseToJSON":
520
- try {
521
- const parsedMessage = this.parser?.parse(args.fixString);
522
- if (!parsedMessage || parsedMessage.length === 0) {
523
- return {
524
- contents: [
525
- {
526
- type: "text",
527
- text: "Error: Failed to parse FIX string",
528
- uri: "parseToJSON"
529
- }
530
- ],
531
- isError: true
532
- };
634
+ ]
635
+ };
636
+ } catch (error) {
637
+ return {
638
+ content: [
639
+ {
640
+ type: "text",
641
+ text: `Error: ${error instanceof Error ? error.message : "Failed to parse FIX string"}`
533
642
  }
534
- return {
535
- contents: [
536
- {
537
- type: "text",
538
- text: `${parsedMessage[0].toFIXJSON()}`,
539
- uri: "parseToJSON"
540
- }
541
- ]
542
- };
543
- } catch (error) {
544
- return {
545
- contents: [
546
- {
547
- type: "text",
548
- text: `Error: ${error instanceof Error ? error.message : "Failed to parse FIX string"}`,
549
- uri: "parseToJSON"
550
- }
551
- ],
552
- isError: true
553
- };
554
- }
555
- case "verifyOrder":
556
- try {
557
- this.verifiedOrders.set(args.clOrdID, {
558
- clOrdID: args.clOrdID,
559
- handlInst: args.handlInst,
560
- quantity: Number.parseFloat(args.quantity),
561
- price: Number.parseFloat(args.price),
562
- ordType: args.ordType,
563
- side: args.side,
564
- symbol: args.symbol,
565
- timeInForce: args.timeInForce
566
- });
567
- const ordTypeNames = {
568
- "1": "Market",
569
- "2": "Limit",
570
- "3": "Stop",
571
- "4": "StopLimit",
572
- "5": "MarketOnClose",
573
- "6": "WithOrWithout",
574
- "7": "LimitOrBetter",
575
- "8": "LimitWithOrWithout",
576
- "9": "OnBasis",
577
- A: "OnClose",
578
- B: "LimitOnClose",
579
- C: "ForexMarket",
580
- D: "PreviouslyQuoted",
581
- E: "PreviouslyIndicated",
582
- F: "ForexLimit",
583
- G: "ForexSwap",
584
- H: "ForexPreviouslyQuoted",
585
- I: "Funari",
586
- J: "MarketIfTouched",
587
- K: "MarketWithLeftOverAsLimit",
588
- L: "PreviousFundValuationPoint",
589
- M: "NextFundValuationPoint",
590
- P: "Pegged",
591
- Q: "CounterOrderSelection",
592
- R: "StopOnBidOrOffer",
593
- S: "StopLimitOnBidOrOffer"
594
- };
595
- const sideNames = {
596
- "1": "Buy",
597
- "2": "Sell",
598
- "3": "BuyMinus",
599
- "4": "SellPlus",
600
- "5": "SellShort",
601
- "6": "SellShortExempt",
602
- "7": "Undisclosed",
603
- "8": "Cross",
604
- "9": "CrossShort",
605
- A: "CrossShortExempt",
606
- B: "AsDefined",
607
- C: "Opposite",
608
- D: "Subscribe",
609
- E: "Redeem",
610
- F: "Lend",
611
- G: "Borrow",
612
- H: "SellUndisclosed"
613
- };
614
- const timeInForceNames = {
615
- "0": "Day",
616
- "1": "GoodTillCancel",
617
- "2": "AtTheOpening",
618
- "3": "ImmediateOrCancel",
619
- "4": "FillOrKill",
620
- "5": "GoodTillCrossing",
621
- "6": "GoodTillDate",
622
- "7": "AtTheClose",
623
- "8": "GoodThroughCrossing",
624
- "9": "AtCrossing",
625
- A: "GoodForTime",
626
- B: "GoodForAuction",
627
- C: "GoodForMonth"
628
- };
629
- const handlInstNames = {
630
- "1": "AutomatedExecutionNoIntervention",
631
- "2": "AutomatedExecutionInterventionOK",
632
- "3": "ManualOrder"
633
- };
634
- return {
635
- contents: [
636
- {
637
- type: "text",
638
- text: `VERIFICATION: All parameters valid. Ready to proceed with order execution.
639
-
643
+ ],
644
+ isError: true
645
+ };
646
+ }
647
+ }
648
+ );
649
+ this.server.setRequestHandler(
650
+ z.object({
651
+ method: z.literal("parseToJSON"),
652
+ params: fixStringSchema
653
+ }),
654
+ async (request, extra) => {
655
+ try {
656
+ const args = request.params;
657
+ const parsedMessage = this.parser?.parse(args.fixString);
658
+ if (!parsedMessage || parsedMessage.length === 0) {
659
+ return {
660
+ content: [{ type: "text", text: "Error: Failed to parse FIX string" }],
661
+ isError: true
662
+ };
663
+ }
664
+ return {
665
+ content: [
666
+ {
667
+ type: "text",
668
+ text: `${parsedMessage[0].toFIXJSON()}`
669
+ }
670
+ ]
671
+ };
672
+ } catch (error) {
673
+ return {
674
+ content: [
675
+ {
676
+ type: "text",
677
+ text: `Error: ${error instanceof Error ? error.message : "Failed to parse FIX string"}`
678
+ }
679
+ ],
680
+ isError: true
681
+ };
682
+ }
683
+ }
684
+ );
685
+ this.server.setRequestHandler(
686
+ z.object({
687
+ method: z.literal("verifyOrder"),
688
+ params: orderSchema
689
+ }),
690
+ async (request, extra) => {
691
+ try {
692
+ const args = request.params;
693
+ this.verifiedOrders.set(args.clOrdID, {
694
+ clOrdID: args.clOrdID,
695
+ handlInst: args.handlInst,
696
+ quantity: Number.parseFloat(args.quantity),
697
+ price: Number.parseFloat(args.price),
698
+ ordType: args.ordType,
699
+ side: args.side,
700
+ symbol: args.symbol,
701
+ timeInForce: args.timeInForce
702
+ });
703
+ const ordTypeNames = {
704
+ "1": "Market",
705
+ "2": "Limit",
706
+ "3": "Stop",
707
+ "4": "StopLimit",
708
+ "5": "MarketOnClose",
709
+ "6": "WithOrWithout",
710
+ "7": "LimitOrBetter",
711
+ "8": "LimitWithOrWithout",
712
+ "9": "OnBasis",
713
+ A: "OnClose",
714
+ B: "LimitOnClose",
715
+ C: "ForexMarket",
716
+ D: "PreviouslyQuoted",
717
+ E: "PreviouslyIndicated",
718
+ F: "ForexLimit",
719
+ G: "ForexSwap",
720
+ H: "ForexPreviouslyQuoted",
721
+ I: "Funari",
722
+ J: "MarketIfTouched",
723
+ K: "MarketWithLeftOverAsLimit",
724
+ L: "PreviousFundValuationPoint",
725
+ M: "NextFundValuationPoint",
726
+ P: "Pegged",
727
+ Q: "CounterOrderSelection",
728
+ R: "StopOnBidOrOffer",
729
+ S: "StopLimitOnBidOrOffer"
730
+ };
731
+ const sideNames = {
732
+ "1": "Buy",
733
+ "2": "Sell",
734
+ "3": "BuyMinus",
735
+ "4": "SellPlus",
736
+ "5": "SellShort",
737
+ "6": "SellShortExempt",
738
+ "7": "Undisclosed",
739
+ "8": "Cross",
740
+ "9": "CrossShort",
741
+ A: "CrossShortExempt",
742
+ B: "AsDefined",
743
+ C: "Opposite",
744
+ D: "Subscribe",
745
+ E: "Redeem",
746
+ F: "Lend",
747
+ G: "Borrow",
748
+ H: "SellUndisclosed"
749
+ };
750
+ const timeInForceNames = {
751
+ "0": "Day",
752
+ "1": "GoodTillCancel",
753
+ "2": "AtTheOpening",
754
+ "3": "ImmediateOrCancel",
755
+ "4": "FillOrKill",
756
+ "5": "GoodTillCrossing",
757
+ "6": "GoodTillDate",
758
+ "7": "AtTheClose",
759
+ "8": "GoodThroughCrossing",
760
+ "9": "AtCrossing",
761
+ A: "GoodForTime",
762
+ B: "GoodForAuction",
763
+ C: "GoodForMonth"
764
+ };
765
+ const handlInstNames = {
766
+ "1": "AutomatedExecutionNoIntervention",
767
+ "2": "AutomatedExecutionInterventionOK",
768
+ "3": "ManualOrder"
769
+ };
770
+ return {
771
+ content: [
772
+ {
773
+ type: "text",
774
+ text: `VERIFICATION: All parameters valid. Ready to proceed with order execution.
775
+
640
776
  Parameters verified:
641
777
  - ClOrdID: ${args.clOrdID}
642
778
  - HandlInst: ${args.handlInst} (${handlInstNames[args.handlInst]})
@@ -647,247 +783,209 @@ Parameters verified:
647
783
  - Symbol: ${args.symbol}
648
784
  - TimeInForce: ${args.timeInForce} (${timeInForceNames[args.timeInForce]})
649
785
 
650
- To execute this order, call the executeOrder tool with these exact same parameters.`,
651
- uri: "verifyOrder"
652
- }
653
- ]
654
- };
655
- } catch (error) {
656
- return {
657
- contents: [
658
- {
659
- type: "text",
660
- text: `Error: ${error instanceof Error ? error.message : "Failed to verify order parameters"}`,
661
- uri: "verifyOrder"
662
- }
663
- ],
664
- isError: true
665
- };
666
- }
667
- case "executeOrder":
668
- try {
669
- const verifiedOrder = this.verifiedOrders.get(args.clOrdID);
670
- if (!verifiedOrder) {
671
- return {
672
- contents: [
673
- {
674
- type: "text",
675
- text: `Error: Order ${args.clOrdID} has not been verified. Please call verifyOrder first.`,
676
- uri: "executeOrder"
677
- }
678
- ],
679
- isError: true
680
- };
681
- }
682
- 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) {
683
- return {
684
- contents: [
685
- {
686
- type: "text",
687
- text: "Error: Order parameters do not match the verified order. Please use the exact same parameters that were verified.",
688
- uri: "executeOrder"
689
- }
690
- ],
691
- isError: true
692
- };
786
+ To execute this order, call the executeOrder tool with these exact same parameters.`
693
787
  }
694
- const response = new Promise((resolve) => {
695
- this.pendingRequests.set(args.clOrdID, resolve);
696
- });
697
- const order = this.parser?.createMessage(
698
- new Field(Fields.MsgType, Messages.NewOrderSingle),
699
- new Field(Fields.MsgSeqNum, this.parser?.getNextTargetMsgSeqNum()),
700
- new Field(Fields.SenderCompID, this.parser?.sender),
701
- new Field(Fields.TargetCompID, this.parser?.target),
702
- new Field(Fields.SendingTime, this.parser?.getTimestamp()),
703
- new Field(Fields.ClOrdID, args.clOrdID),
704
- new Field(Fields.Side, args.side),
705
- new Field(Fields.Symbol, args.symbol),
706
- new Field(Fields.OrderQty, Number.parseFloat(args.quantity)),
707
- new Field(Fields.Price, Number.parseFloat(args.price)),
708
- new Field(Fields.OrdType, args.ordType),
709
- new Field(Fields.HandlInst, args.handlInst),
710
- new Field(Fields.TimeInForce, args.timeInForce),
711
- new Field(Fields.TransactTime, this.parser?.getTimestamp())
712
- );
713
- if (!this.parser?.connected) {
714
- return {
715
- contents: [
716
- {
717
- type: "text",
718
- text: "Error: Not connected. Ignoring message.",
719
- uri: "executeOrder"
720
- }
721
- ],
722
- isError: true
723
- };
724
- }
725
- this.parser?.send(order);
726
- const fixData = await response;
727
- this.verifiedOrders.delete(args.clOrdID);
728
- return {
729
- contents: [
730
- {
731
- type: "text",
732
- 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())}`,
733
- uri: "executeOrder"
734
- }
735
- ]
736
- };
737
- } catch (error) {
738
- return {
739
- contents: [
740
- {
741
- type: "text",
742
- text: `Error: ${error instanceof Error ? error.message : "Failed to execute order"}`,
743
- uri: "executeOrder"
744
- }
745
- ],
746
- isError: true
747
- };
748
- }
749
- case "marketDataRequest":
750
- try {
751
- const response = new Promise((resolve) => {
752
- this.pendingRequests.set(args.mdReqID, resolve);
753
- });
754
- const messageFields = [
755
- new Field(Fields.MsgType, Messages.MarketDataRequest),
756
- new Field(Fields.SenderCompID, this.parser?.sender),
757
- new Field(Fields.MsgSeqNum, this.parser?.getNextTargetMsgSeqNum()),
758
- new Field(Fields.TargetCompID, this.parser?.target),
759
- new Field(Fields.SendingTime, this.parser?.getTimestamp()),
760
- new Field(Fields.MDReqID, args.mdReqID),
761
- new Field(Fields.SubscriptionRequestType, args.subscriptionRequestType),
762
- new Field(Fields.MarketDepth, 0),
763
- new Field(Fields.MDUpdateType, args.mdUpdateType)
764
- ];
765
- messageFields.push(new Field(Fields.NoRelatedSym, args.symbols.length));
766
- args.symbols.forEach((symbol) => {
767
- messageFields.push(new Field(Fields.Symbol, symbol));
768
- });
769
- messageFields.push(new Field(Fields.NoMDEntryTypes, args.mdEntryTypes.length));
770
- args.mdEntryTypes.forEach((entryType) => {
771
- messageFields.push(new Field(Fields.MDEntryType, entryType));
772
- });
773
- const mdr = this.parser?.createMessage(...messageFields);
774
- if (!this.parser?.connected) {
775
- return {
776
- contents: [
777
- {
778
- type: "text",
779
- text: "Error: Not connected. Ignoring message.",
780
- uri: "marketDataRequest"
781
- }
782
- ],
783
- isError: true
784
- };
788
+ ]
789
+ };
790
+ } catch (error) {
791
+ return {
792
+ content: [
793
+ {
794
+ type: "text",
795
+ text: `Error: ${error instanceof Error ? error.message : "Failed to verify order parameters"}`
785
796
  }
786
- this.parser?.send(mdr);
787
- const fixData = await response;
788
- return {
789
- contents: [
790
- {
791
- type: "text",
792
- text: `Market data for ${args.symbols.join(", ")}: ${JSON.stringify(fixData.toFIXJSON())}`,
793
- uri: "marketDataRequest"
794
- }
795
- ]
796
- };
797
- } catch (error) {
798
- return {
799
- contents: [
800
- {
801
- type: "text",
802
- text: `Error: ${error instanceof Error ? error.message : "Failed to request market data"}`,
803
- uri: "marketDataRequest"
804
- }
805
- ],
806
- isError: true
807
- };
808
- }
809
- default:
810
- return {
811
- contents: [
812
- {
813
- type: "text",
814
- text: `Tool not found: ${name}`,
815
- uri: name
816
- }
817
- ],
818
- isError: true
819
- };
797
+ ],
798
+ isError: true
799
+ };
820
800
  }
821
801
  }
822
802
  );
823
803
  this.server.setRequestHandler(
824
804
  z.object({
825
- method: z.literal("resources/read"),
826
- params: z.object({
827
- uri: z.string()
828
- })
805
+ method: z.literal("executeOrder"),
806
+ params: orderSchema
829
807
  }),
830
808
  async (request, extra) => {
831
- const { uri } = request.params;
832
- switch (uri) {
833
- case "greeting-resource":
809
+ try {
810
+ const args = request.params;
811
+ const verifiedOrder = this.verifiedOrders.get(args.clOrdID);
812
+ if (!verifiedOrder) {
834
813
  return {
835
- contents: [
814
+ content: [
836
815
  {
837
816
  type: "text",
838
- text: "Hello, world!",
839
- uri: "greeting-resource"
817
+ text: `Error: Order ${args.clOrdID} has not been verified. Please call verifyOrder first.`
840
818
  }
841
- ]
819
+ ],
820
+ isError: true
842
821
  };
843
- case "stockGraph":
822
+ }
823
+ 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) {
844
824
  return {
845
- contents: [
825
+ content: [
846
826
  {
847
827
  type: "text",
848
- text: "This resource requires a symbol parameter. Please use the stockGraph/{symbol} resource.",
849
- uri: "stockGraph"
828
+ text: "Error: Order parameters do not match the verified order. Please use the exact same parameters that were verified."
850
829
  }
851
- ]
830
+ ],
831
+ isError: true
852
832
  };
853
- case "stockPriceHistory":
833
+ }
834
+ const response = new Promise((resolve) => {
835
+ this.pendingRequests.set(args.clOrdID, resolve);
836
+ });
837
+ const order = this.parser?.createMessage(
838
+ new Field(Fields.MsgType, Messages.NewOrderSingle),
839
+ new Field(Fields.MsgSeqNum, this.parser?.getNextTargetMsgSeqNum()),
840
+ new Field(Fields.SenderCompID, this.parser?.sender),
841
+ new Field(Fields.TargetCompID, this.parser?.target),
842
+ new Field(Fields.SendingTime, this.parser?.getTimestamp()),
843
+ new Field(Fields.ClOrdID, args.clOrdID),
844
+ new Field(Fields.Side, args.side),
845
+ new Field(Fields.Symbol, args.symbol),
846
+ new Field(Fields.OrderQty, Number.parseFloat(args.quantity)),
847
+ new Field(Fields.Price, Number.parseFloat(args.price)),
848
+ new Field(Fields.OrdType, args.ordType),
849
+ new Field(Fields.HandlInst, args.handlInst),
850
+ new Field(Fields.TimeInForce, args.timeInForce),
851
+ new Field(Fields.TransactTime, this.parser?.getTimestamp())
852
+ );
853
+ if (!this.parser?.connected) {
854
854
  return {
855
- contents: [
855
+ content: [
856
856
  {
857
857
  type: "text",
858
- text: "This resource requires a symbol parameter. Please use the stockPriceHistory/{symbol} resource.",
859
- uri: "stockPriceHistory"
858
+ text: "Error: Not connected. Ignoring message."
860
859
  }
861
- ]
860
+ ],
861
+ isError: true
862
862
  };
863
- default:
864
- if (uri.startsWith("stockGraph/")) {
865
- const symbol = uri.split("/")[1];
866
- const priceHistory = this.marketDataPrices.get(symbol) || [];
867
- if (priceHistory.length === 0) {
868
- return {
869
- contents: [
870
- {
871
- type: "text",
872
- text: `No price data available for ${symbol}`,
873
- uri
874
- }
875
- ]
876
- };
863
+ }
864
+ this.parser?.send(order);
865
+ const fixData = await response;
866
+ this.verifiedOrders.delete(args.clOrdID);
867
+ return {
868
+ content: [
869
+ {
870
+ type: "text",
871
+ 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())}`
872
+ }
873
+ ]
874
+ };
875
+ } catch (error) {
876
+ return {
877
+ content: [
878
+ {
879
+ type: "text",
880
+ text: `Error: ${error instanceof Error ? error.message : "Failed to execute order"}`
877
881
  }
878
- const width = 600;
879
- const height = 300;
880
- const padding = 40;
881
- const xScale = (width - 2 * padding) / (priceHistory.length - 1);
882
- const yMin = Math.min(...priceHistory.map((d) => d.price));
883
- const yMax = Math.max(...priceHistory.map((d) => d.price));
884
- const yScale = (height - 2 * padding) / (yMax - yMin);
885
- const points = priceHistory.map((d, i) => {
886
- const x = padding + i * xScale;
887
- const y = height - padding - (d.price - yMin) * yScale;
888
- return `${x},${y}`;
889
- }).join(" L ");
890
- const svg = `<?xml version="1.0" encoding="UTF-8"?>
882
+ ],
883
+ isError: true
884
+ };
885
+ }
886
+ }
887
+ );
888
+ this.server.setRequestHandler(
889
+ z.object({
890
+ method: z.literal("marketDataRequest"),
891
+ params: marketDataRequestSchema
892
+ }),
893
+ async (request, extra) => {
894
+ try {
895
+ const args = request.params;
896
+ const response = new Promise((resolve) => {
897
+ this.pendingRequests.set(args.mdReqID, resolve);
898
+ });
899
+ const messageFields = [
900
+ new Field(Fields.MsgType, Messages.MarketDataRequest),
901
+ new Field(Fields.SenderCompID, this.parser?.sender),
902
+ new Field(Fields.MsgSeqNum, this.parser?.getNextTargetMsgSeqNum()),
903
+ new Field(Fields.TargetCompID, this.parser?.target),
904
+ new Field(Fields.SendingTime, this.parser?.getTimestamp()),
905
+ new Field(Fields.MDReqID, args.mdReqID),
906
+ new Field(Fields.SubscriptionRequestType, args.subscriptionRequestType),
907
+ new Field(Fields.MarketDepth, 0),
908
+ new Field(Fields.MDUpdateType, args.mdUpdateType)
909
+ ];
910
+ messageFields.push(new Field(Fields.NoRelatedSym, args.symbols.length));
911
+ args.symbols.forEach((symbol) => {
912
+ messageFields.push(new Field(Fields.Symbol, symbol));
913
+ });
914
+ messageFields.push(new Field(Fields.NoMDEntryTypes, args.mdEntryTypes.length));
915
+ args.mdEntryTypes.forEach((entryType) => {
916
+ messageFields.push(new Field(Fields.MDEntryType, entryType));
917
+ });
918
+ const mdr = this.parser?.createMessage(...messageFields);
919
+ if (!this.parser?.connected) {
920
+ return {
921
+ content: [
922
+ {
923
+ type: "text",
924
+ text: "Error: Not connected. Ignoring message."
925
+ }
926
+ ],
927
+ isError: true
928
+ };
929
+ }
930
+ this.parser?.send(mdr);
931
+ const fixData = await response;
932
+ return {
933
+ content: [
934
+ {
935
+ type: "text",
936
+ text: `Market data for ${args.symbols.join(", ")}: ${JSON.stringify(fixData.toFIXJSON())}`
937
+ }
938
+ ]
939
+ };
940
+ } catch (error) {
941
+ return {
942
+ content: [
943
+ {
944
+ type: "text",
945
+ text: `Error: ${error instanceof Error ? error.message : "Failed to request market data"}`
946
+ }
947
+ ],
948
+ isError: true
949
+ };
950
+ }
951
+ }
952
+ );
953
+ this.server.setRequestHandler(
954
+ z.object({
955
+ method: z.literal("stockGraph"),
956
+ params: symbolSchema
957
+ }),
958
+ async (request, extra) => {
959
+ this.parser?.logger.log({
960
+ level: "info",
961
+ message: "MCP Server Resource called: stockGraph"
962
+ });
963
+ const args = request.params;
964
+ const symbol = args.symbol;
965
+ const priceHistory = this.marketDataPrices.get(symbol) || [];
966
+ if (priceHistory.length === 0) {
967
+ return {
968
+ content: [
969
+ {
970
+ type: "text",
971
+ text: `No price data available for ${symbol}`
972
+ }
973
+ ]
974
+ };
975
+ }
976
+ const width = 600;
977
+ const height = 300;
978
+ const padding = 40;
979
+ const xScale = (width - 2 * padding) / (priceHistory.length - 1);
980
+ const yMin = Math.min(...priceHistory.map((d) => d.price));
981
+ const yMax = Math.max(...priceHistory.map((d) => d.price));
982
+ const yScale = (height - 2 * padding) / (yMax - yMin);
983
+ const points = priceHistory.map((d, i) => {
984
+ const x = padding + i * xScale;
985
+ const y = height - padding - (d.price - yMin) * yScale;
986
+ return `${x},${y}`;
987
+ }).join(" L ");
988
+ const svg = `<?xml version="1.0" encoding="UTF-8"?>
891
989
  <svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
892
990
  <!-- Background -->
893
991
  <rect width="100%" height="100%" fill="#f8f9fa"/>
@@ -895,9 +993,9 @@ To execute this order, call the executeOrder tool with these exact same paramete
895
993
  <!-- Grid lines -->
896
994
  <g stroke="#e9ecef" stroke-width="1">
897
995
  ${Array.from({ length: 5 }, (_, i) => {
898
- const y = padding + (height - 2 * padding) * i / 4;
899
- return `<line x1="${padding}" y1="${y}" x2="${width - padding}" y2="${y}"/>`;
900
- }).join("\n")}
996
+ const y = padding + (height - 2 * padding) * i / 4;
997
+ return `<line x1="${padding}" y1="${y}" x2="${width - padding}" y2="${y}"/>`;
998
+ }).join("\n")}
901
999
  </g>
902
1000
 
903
1001
  <!-- Price line -->
@@ -908,24 +1006,24 @@ To execute this order, call the executeOrder tool with these exact same paramete
908
1006
 
909
1007
  <!-- Data points -->
910
1008
  ${priceHistory.map((d, i) => {
911
- const x = padding + i * xScale;
912
- const y = height - padding - (d.price - yMin) * yScale;
913
- return `<circle cx="${x}" cy="${y}" r="3" fill="#007bff"/>`;
914
- }).join("\n")}
1009
+ const x = padding + i * xScale;
1010
+ const y = height - padding - (d.price - yMin) * yScale;
1011
+ return `<circle cx="${x}" cy="${y}" r="3" fill="#007bff"/>`;
1012
+ }).join("\n")}
915
1013
 
916
1014
  <!-- Labels -->
917
1015
  <g font-family="Arial" font-size="12" fill="#495057">
918
1016
  ${Array.from({ length: 5 }, (_, i) => {
919
- const x = padding + (width - 2 * padding) * i / 4;
920
- const index = Math.floor((priceHistory.length - 1) * i / 4);
921
- const timestamp = new Date(priceHistory[index].timestamp).toLocaleTimeString();
922
- return `<text x="${x + padding}" y="${height - padding + 20}" text-anchor="middle">${timestamp}</text>`;
923
- }).join("\n")}
1017
+ const x = padding + (width - 2 * padding) * i / 4;
1018
+ const index = Math.floor((priceHistory.length - 1) * i / 4);
1019
+ const timestamp = new Date(priceHistory[index].timestamp).toLocaleTimeString();
1020
+ return `<text x="${x + padding}" y="${height - padding + 20}" text-anchor="middle">${timestamp}</text>`;
1021
+ }).join("\n")}
924
1022
  ${Array.from({ length: 5 }, (_, i) => {
925
- const y = padding + (height - 2 * padding) * i / 4;
926
- const price = yMax - (yMax - yMin) * i / 4;
927
- return `<text x="${padding - 5}" y="${y + 4}" text-anchor="end">$${price.toFixed(2)}</text>`;
928
- }).join("\n")}
1023
+ const y = padding + (height - 2 * padding) * i / 4;
1024
+ const price = yMax - (yMax - yMin) * i / 4;
1025
+ return `<text x="${padding - 5}" y="${y + 4}" text-anchor="end">$${price.toFixed(2)}</text>`;
1026
+ }).join("\n")}
929
1027
  </g>
930
1028
 
931
1029
  <!-- Title -->
@@ -935,62 +1033,58 @@ To execute this order, call the executeOrder tool with these exact same paramete
935
1033
  ${symbol} - Price Chart (${priceHistory.length} points)
936
1034
  </text>
937
1035
  </svg>`;
938
- return {
939
- contents: [
940
- {
941
- type: "text",
942
- text: svg,
943
- uri
944
- }
945
- ]
946
- };
1036
+ return {
1037
+ content: [
1038
+ {
1039
+ type: "text",
1040
+ text: svg
947
1041
  }
948
- if (uri.startsWith("stockPriceHistory/")) {
949
- const symbol = uri.split("/")[1];
950
- const priceHistory = this.marketDataPrices.get(symbol) || [];
951
- if (priceHistory.length === 0) {
952
- return {
953
- contents: [
954
- {
955
- type: "text",
956
- text: `No price data available for ${symbol}`,
957
- uri
958
- }
959
- ]
960
- };
1042
+ ]
1043
+ };
1044
+ }
1045
+ );
1046
+ this.server.setRequestHandler(
1047
+ z.object({
1048
+ method: z.literal("stockPriceHistory"),
1049
+ params: symbolSchema
1050
+ }),
1051
+ async (request, extra) => {
1052
+ this.parser?.logger.log({
1053
+ level: "info",
1054
+ message: "MCP Server Resource called: stockPriceHistory"
1055
+ });
1056
+ const args = request.params;
1057
+ const symbol = args.symbol;
1058
+ const priceHistory = this.marketDataPrices.get(symbol) || [];
1059
+ if (priceHistory.length === 0) {
1060
+ return {
1061
+ content: [
1062
+ {
1063
+ type: "text",
1064
+ text: `No price data available for ${symbol}`
961
1065
  }
962
- return {
963
- contents: [
964
- {
965
- type: "text",
966
- text: JSON.stringify(
967
- {
968
- symbol,
969
- count: priceHistory.length,
970
- prices: priceHistory.map((point) => ({
971
- timestamp: new Date(point.timestamp).toISOString(),
972
- price: point.price
973
- }))
974
- },
975
- null,
976
- 2
977
- ),
978
- uri
979
- }
980
- ]
981
- };
982
- }
983
- return {
984
- contents: [
985
- {
986
- type: "text",
987
- text: `Resource not found: ${uri}`,
988
- uri
989
- }
990
- ],
991
- isError: true
992
- };
1066
+ ]
1067
+ };
993
1068
  }
1069
+ return {
1070
+ content: [
1071
+ {
1072
+ type: "text",
1073
+ text: JSON.stringify(
1074
+ {
1075
+ symbol,
1076
+ count: priceHistory.length,
1077
+ prices: priceHistory.map((point) => ({
1078
+ timestamp: new Date(point.timestamp).toISOString(),
1079
+ price: point.price
1080
+ }))
1081
+ },
1082
+ null,
1083
+ 2
1084
+ )
1085
+ }
1086
+ ]
1087
+ };
994
1088
  }
995
1089
  );
996
1090
  process.on("SIGINT", async () => {