fixparser-plugin-mcp 9.1.7-75ded9c1 → 9.1.7-81ea0211
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/MCPLocal.js +442 -374
- package/build/cjs/MCPLocal.js.map +2 -2
- package/build/esm/MCPLocal.mjs +442 -374
- package/build/esm/MCPLocal.mjs.map +2 -2
- package/build-examples/cjs/example_mcp_local.js +22 -22
- package/build-examples/cjs/example_mcp_local.js.map +3 -3
- package/build-examples/esm/example_mcp_local.mjs +23 -23
- package/build-examples/esm/example_mcp_local.mjs.map +3 -3
- package/package.json +2 -2
package/build/esm/MCPLocal.mjs
CHANGED
|
@@ -3,6 +3,100 @@ 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 fixStringSchema = z.object({
|
|
7
|
+
fixString: z.string()
|
|
8
|
+
});
|
|
9
|
+
var orderSchema = z.object({
|
|
10
|
+
clOrdID: z.string(),
|
|
11
|
+
handlInst: z.enum(["1", "2", "3"]),
|
|
12
|
+
quantity: z.string(),
|
|
13
|
+
price: z.string(),
|
|
14
|
+
ordType: z.enum([
|
|
15
|
+
"1",
|
|
16
|
+
"2",
|
|
17
|
+
"3",
|
|
18
|
+
"4",
|
|
19
|
+
"5",
|
|
20
|
+
"6",
|
|
21
|
+
"7",
|
|
22
|
+
"8",
|
|
23
|
+
"9",
|
|
24
|
+
"A",
|
|
25
|
+
"B",
|
|
26
|
+
"C",
|
|
27
|
+
"D",
|
|
28
|
+
"E",
|
|
29
|
+
"F",
|
|
30
|
+
"G",
|
|
31
|
+
"H",
|
|
32
|
+
"I",
|
|
33
|
+
"J",
|
|
34
|
+
"K",
|
|
35
|
+
"L",
|
|
36
|
+
"M",
|
|
37
|
+
"P",
|
|
38
|
+
"Q",
|
|
39
|
+
"R",
|
|
40
|
+
"S"
|
|
41
|
+
]),
|
|
42
|
+
side: z.enum(["1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H"]),
|
|
43
|
+
symbol: z.string(),
|
|
44
|
+
timeInForce: z.enum(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C"])
|
|
45
|
+
});
|
|
46
|
+
var marketDataRequestSchema = z.object({
|
|
47
|
+
mdUpdateType: z.enum(["0", "1"]),
|
|
48
|
+
symbols: z.array(z.string()),
|
|
49
|
+
mdReqID: z.string(),
|
|
50
|
+
subscriptionRequestType: z.enum(["0", "1", "2"]),
|
|
51
|
+
mdEntryTypes: z.array(
|
|
52
|
+
z.enum([
|
|
53
|
+
"0",
|
|
54
|
+
"1",
|
|
55
|
+
"2",
|
|
56
|
+
"3",
|
|
57
|
+
"4",
|
|
58
|
+
"5",
|
|
59
|
+
"6",
|
|
60
|
+
"7",
|
|
61
|
+
"8",
|
|
62
|
+
"9",
|
|
63
|
+
"A",
|
|
64
|
+
"B",
|
|
65
|
+
"C",
|
|
66
|
+
"D",
|
|
67
|
+
"E",
|
|
68
|
+
"F",
|
|
69
|
+
"G",
|
|
70
|
+
"H",
|
|
71
|
+
"J",
|
|
72
|
+
"K",
|
|
73
|
+
"L",
|
|
74
|
+
"M",
|
|
75
|
+
"N",
|
|
76
|
+
"O",
|
|
77
|
+
"P",
|
|
78
|
+
"Q",
|
|
79
|
+
"R",
|
|
80
|
+
"S",
|
|
81
|
+
"T",
|
|
82
|
+
"U",
|
|
83
|
+
"V",
|
|
84
|
+
"W",
|
|
85
|
+
"X",
|
|
86
|
+
"Y",
|
|
87
|
+
"Z",
|
|
88
|
+
"a",
|
|
89
|
+
"b",
|
|
90
|
+
"c",
|
|
91
|
+
"d",
|
|
92
|
+
"e",
|
|
93
|
+
"g",
|
|
94
|
+
"h",
|
|
95
|
+
"i",
|
|
96
|
+
"t"
|
|
97
|
+
])
|
|
98
|
+
)
|
|
99
|
+
});
|
|
6
100
|
var MCPLocal = class {
|
|
7
101
|
parser;
|
|
8
102
|
server = new Server(
|
|
@@ -216,14 +310,6 @@ var MCPLocal = class {
|
|
|
216
310
|
level: "info",
|
|
217
311
|
message: `MCP Server added ${symbol}: ${priceNum}`
|
|
218
312
|
});
|
|
219
|
-
this.server.notification({
|
|
220
|
-
method: "priceUpdate",
|
|
221
|
-
params: {
|
|
222
|
-
symbol: symbolStr,
|
|
223
|
-
price: priceNum,
|
|
224
|
-
timestamp: Number(timestamp)
|
|
225
|
-
}
|
|
226
|
-
});
|
|
227
313
|
}
|
|
228
314
|
}
|
|
229
315
|
if (msgType === Messages.MarketDataSnapshotFullRefresh) {
|
|
@@ -276,11 +362,11 @@ var MCPLocal = class {
|
|
|
276
362
|
z.object({ method: z.literal("resources/templates/list") }),
|
|
277
363
|
async (request, extra) => {
|
|
278
364
|
return {
|
|
279
|
-
|
|
365
|
+
templates: [
|
|
280
366
|
{
|
|
281
367
|
name: "stockGraph",
|
|
282
368
|
description: "Generates a price chart for a given symbol",
|
|
283
|
-
|
|
369
|
+
uri: "stockGraph/{symbol}",
|
|
284
370
|
parameters: {
|
|
285
371
|
type: "object",
|
|
286
372
|
properties: {
|
|
@@ -292,7 +378,7 @@ var MCPLocal = class {
|
|
|
292
378
|
{
|
|
293
379
|
name: "stockPriceHistory",
|
|
294
380
|
description: "Returns price history for a given symbol",
|
|
295
|
-
|
|
381
|
+
uri: "stockPriceHistory/{symbol}",
|
|
296
382
|
parameters: {
|
|
297
383
|
type: "object",
|
|
298
384
|
properties: {
|
|
@@ -465,361 +551,6 @@ var MCPLocal = class {
|
|
|
465
551
|
};
|
|
466
552
|
}
|
|
467
553
|
);
|
|
468
|
-
this.server.setRequestHandler(
|
|
469
|
-
z.object({
|
|
470
|
-
method: z.literal("tools/call"),
|
|
471
|
-
params: z.object({
|
|
472
|
-
name: z.string(),
|
|
473
|
-
arguments: z.any(),
|
|
474
|
-
_meta: z.object({
|
|
475
|
-
progressToken: z.number()
|
|
476
|
-
}).optional()
|
|
477
|
-
})
|
|
478
|
-
}),
|
|
479
|
-
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
|
-
};
|
|
496
|
-
}
|
|
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
|
-
};
|
|
533
|
-
}
|
|
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
|
-
|
|
640
|
-
Parameters verified:
|
|
641
|
-
- ClOrdID: ${args.clOrdID}
|
|
642
|
-
- HandlInst: ${args.handlInst} (${handlInstNames[args.handlInst]})
|
|
643
|
-
- Quantity: ${args.quantity}
|
|
644
|
-
- Price: ${args.price}
|
|
645
|
-
- OrdType: ${args.ordType} (${ordTypeNames[args.ordType]})
|
|
646
|
-
- Side: ${args.side} (${sideNames[args.side]})
|
|
647
|
-
- Symbol: ${args.symbol}
|
|
648
|
-
- TimeInForce: ${args.timeInForce} (${timeInForceNames[args.timeInForce]})
|
|
649
|
-
|
|
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
|
-
};
|
|
693
|
-
}
|
|
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
|
-
};
|
|
785
|
-
}
|
|
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
|
-
};
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
);
|
|
823
554
|
this.server.setRequestHandler(
|
|
824
555
|
z.object({
|
|
825
556
|
method: z.literal("resources/read"),
|
|
@@ -869,8 +600,7 @@ To execute this order, call the executeOrder tool with these exact same paramete
|
|
|
869
600
|
contents: [
|
|
870
601
|
{
|
|
871
602
|
type: "text",
|
|
872
|
-
text: `No price data available for ${symbol}
|
|
873
|
-
uri
|
|
603
|
+
text: `No price data available for ${symbol}`
|
|
874
604
|
}
|
|
875
605
|
]
|
|
876
606
|
};
|
|
@@ -939,8 +669,7 @@ To execute this order, call the executeOrder tool with these exact same paramete
|
|
|
939
669
|
contents: [
|
|
940
670
|
{
|
|
941
671
|
type: "text",
|
|
942
|
-
text: svg
|
|
943
|
-
uri
|
|
672
|
+
text: svg
|
|
944
673
|
}
|
|
945
674
|
]
|
|
946
675
|
};
|
|
@@ -953,8 +682,7 @@ To execute this order, call the executeOrder tool with these exact same paramete
|
|
|
953
682
|
contents: [
|
|
954
683
|
{
|
|
955
684
|
type: "text",
|
|
956
|
-
text: `No price data available for ${symbol}
|
|
957
|
-
uri
|
|
685
|
+
text: `No price data available for ${symbol}`
|
|
958
686
|
}
|
|
959
687
|
]
|
|
960
688
|
};
|
|
@@ -974,8 +702,7 @@ To execute this order, call the executeOrder tool with these exact same paramete
|
|
|
974
702
|
},
|
|
975
703
|
null,
|
|
976
704
|
2
|
|
977
|
-
)
|
|
978
|
-
uri
|
|
705
|
+
)
|
|
979
706
|
}
|
|
980
707
|
]
|
|
981
708
|
};
|
|
@@ -993,6 +720,347 @@ To execute this order, call the executeOrder tool with these exact same paramete
|
|
|
993
720
|
}
|
|
994
721
|
}
|
|
995
722
|
);
|
|
723
|
+
this.server.setRequestHandler(
|
|
724
|
+
z.object({
|
|
725
|
+
method: z.literal("parse"),
|
|
726
|
+
params: fixStringSchema
|
|
727
|
+
}),
|
|
728
|
+
async (request, extra) => {
|
|
729
|
+
try {
|
|
730
|
+
const args = request.params;
|
|
731
|
+
const parsedMessage = this.parser?.parse(args.fixString);
|
|
732
|
+
if (!parsedMessage || parsedMessage.length === 0) {
|
|
733
|
+
return {
|
|
734
|
+
contents: [{ type: "text", text: "Error: Failed to parse FIX string" }],
|
|
735
|
+
isError: true
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
return {
|
|
739
|
+
contents: [
|
|
740
|
+
{
|
|
741
|
+
type: "text",
|
|
742
|
+
text: `${parsedMessage[0].description}
|
|
743
|
+
${parsedMessage[0].messageTypeDescription}`
|
|
744
|
+
}
|
|
745
|
+
]
|
|
746
|
+
};
|
|
747
|
+
} catch (error) {
|
|
748
|
+
return {
|
|
749
|
+
contents: [
|
|
750
|
+
{
|
|
751
|
+
type: "text",
|
|
752
|
+
text: `Error: ${error instanceof Error ? error.message : "Failed to parse FIX string"}`
|
|
753
|
+
}
|
|
754
|
+
],
|
|
755
|
+
isError: true
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
);
|
|
760
|
+
this.server.setRequestHandler(
|
|
761
|
+
z.object({
|
|
762
|
+
method: z.literal("parseToJSON"),
|
|
763
|
+
params: fixStringSchema
|
|
764
|
+
}),
|
|
765
|
+
async (request, extra) => {
|
|
766
|
+
try {
|
|
767
|
+
const args = request.params;
|
|
768
|
+
const parsedMessage = this.parser?.parse(args.fixString);
|
|
769
|
+
if (!parsedMessage || parsedMessage.length === 0) {
|
|
770
|
+
return {
|
|
771
|
+
contents: [{ type: "text", text: "Error: Failed to parse FIX string" }],
|
|
772
|
+
isError: true
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
return {
|
|
776
|
+
contents: [
|
|
777
|
+
{
|
|
778
|
+
type: "text",
|
|
779
|
+
text: `${parsedMessage[0].toFIXJSON()}`
|
|
780
|
+
}
|
|
781
|
+
]
|
|
782
|
+
};
|
|
783
|
+
} catch (error) {
|
|
784
|
+
return {
|
|
785
|
+
contents: [
|
|
786
|
+
{
|
|
787
|
+
type: "text",
|
|
788
|
+
text: `Error: ${error instanceof Error ? error.message : "Failed to parse FIX string"}`
|
|
789
|
+
}
|
|
790
|
+
],
|
|
791
|
+
isError: true
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
);
|
|
796
|
+
this.server.setRequestHandler(
|
|
797
|
+
z.object({
|
|
798
|
+
method: z.literal("verifyOrder"),
|
|
799
|
+
params: orderSchema
|
|
800
|
+
}),
|
|
801
|
+
async (request, extra) => {
|
|
802
|
+
try {
|
|
803
|
+
const args = request.params;
|
|
804
|
+
this.verifiedOrders.set(args.clOrdID, {
|
|
805
|
+
clOrdID: args.clOrdID,
|
|
806
|
+
handlInst: args.handlInst,
|
|
807
|
+
quantity: Number.parseFloat(args.quantity),
|
|
808
|
+
price: Number.parseFloat(args.price),
|
|
809
|
+
ordType: args.ordType,
|
|
810
|
+
side: args.side,
|
|
811
|
+
symbol: args.symbol,
|
|
812
|
+
timeInForce: args.timeInForce
|
|
813
|
+
});
|
|
814
|
+
const ordTypeNames = {
|
|
815
|
+
"1": "Market",
|
|
816
|
+
"2": "Limit",
|
|
817
|
+
"3": "Stop",
|
|
818
|
+
"4": "StopLimit",
|
|
819
|
+
"5": "MarketOnClose",
|
|
820
|
+
"6": "WithOrWithout",
|
|
821
|
+
"7": "LimitOrBetter",
|
|
822
|
+
"8": "LimitWithOrWithout",
|
|
823
|
+
"9": "OnBasis",
|
|
824
|
+
A: "OnClose",
|
|
825
|
+
B: "LimitOnClose",
|
|
826
|
+
C: "ForexMarket",
|
|
827
|
+
D: "PreviouslyQuoted",
|
|
828
|
+
E: "PreviouslyIndicated",
|
|
829
|
+
F: "ForexLimit",
|
|
830
|
+
G: "ForexSwap",
|
|
831
|
+
H: "ForexPreviouslyQuoted",
|
|
832
|
+
I: "Funari",
|
|
833
|
+
J: "MarketIfTouched",
|
|
834
|
+
K: "MarketWithLeftOverAsLimit",
|
|
835
|
+
L: "PreviousFundValuationPoint",
|
|
836
|
+
M: "NextFundValuationPoint",
|
|
837
|
+
P: "Pegged",
|
|
838
|
+
Q: "CounterOrderSelection",
|
|
839
|
+
R: "StopOnBidOrOffer",
|
|
840
|
+
S: "StopLimitOnBidOrOffer"
|
|
841
|
+
};
|
|
842
|
+
const sideNames = {
|
|
843
|
+
"1": "Buy",
|
|
844
|
+
"2": "Sell",
|
|
845
|
+
"3": "BuyMinus",
|
|
846
|
+
"4": "SellPlus",
|
|
847
|
+
"5": "SellShort",
|
|
848
|
+
"6": "SellShortExempt",
|
|
849
|
+
"7": "Undisclosed",
|
|
850
|
+
"8": "Cross",
|
|
851
|
+
"9": "CrossShort",
|
|
852
|
+
A: "CrossShortExempt",
|
|
853
|
+
B: "AsDefined",
|
|
854
|
+
C: "Opposite",
|
|
855
|
+
D: "Subscribe",
|
|
856
|
+
E: "Redeem",
|
|
857
|
+
F: "Lend",
|
|
858
|
+
G: "Borrow",
|
|
859
|
+
H: "SellUndisclosed"
|
|
860
|
+
};
|
|
861
|
+
const timeInForceNames = {
|
|
862
|
+
"0": "Day",
|
|
863
|
+
"1": "GoodTillCancel",
|
|
864
|
+
"2": "AtTheOpening",
|
|
865
|
+
"3": "ImmediateOrCancel",
|
|
866
|
+
"4": "FillOrKill",
|
|
867
|
+
"5": "GoodTillCrossing",
|
|
868
|
+
"6": "GoodTillDate",
|
|
869
|
+
"7": "AtTheClose",
|
|
870
|
+
"8": "GoodThroughCrossing",
|
|
871
|
+
"9": "AtCrossing",
|
|
872
|
+
A: "GoodForTime",
|
|
873
|
+
B: "GoodForAuction",
|
|
874
|
+
C: "GoodForMonth"
|
|
875
|
+
};
|
|
876
|
+
const handlInstNames = {
|
|
877
|
+
"1": "AutomatedExecutionNoIntervention",
|
|
878
|
+
"2": "AutomatedExecutionInterventionOK",
|
|
879
|
+
"3": "ManualOrder"
|
|
880
|
+
};
|
|
881
|
+
return {
|
|
882
|
+
contents: [
|
|
883
|
+
{
|
|
884
|
+
type: "text",
|
|
885
|
+
text: `VERIFICATION: All parameters valid. Ready to proceed with order execution.
|
|
886
|
+
|
|
887
|
+
Parameters verified:
|
|
888
|
+
- ClOrdID: ${args.clOrdID}
|
|
889
|
+
- HandlInst: ${args.handlInst} (${handlInstNames[args.handlInst]})
|
|
890
|
+
- Quantity: ${args.quantity}
|
|
891
|
+
- Price: ${args.price}
|
|
892
|
+
- OrdType: ${args.ordType} (${ordTypeNames[args.ordType]})
|
|
893
|
+
- Side: ${args.side} (${sideNames[args.side]})
|
|
894
|
+
- Symbol: ${args.symbol}
|
|
895
|
+
- TimeInForce: ${args.timeInForce} (${timeInForceNames[args.timeInForce]})
|
|
896
|
+
|
|
897
|
+
To execute this order, call the executeOrder tool with these exact same parameters.`
|
|
898
|
+
}
|
|
899
|
+
]
|
|
900
|
+
};
|
|
901
|
+
} catch (error) {
|
|
902
|
+
return {
|
|
903
|
+
contents: [
|
|
904
|
+
{
|
|
905
|
+
type: "text",
|
|
906
|
+
text: `Error: ${error instanceof Error ? error.message : "Failed to verify order parameters"}`
|
|
907
|
+
}
|
|
908
|
+
],
|
|
909
|
+
isError: true
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
);
|
|
914
|
+
this.server.setRequestHandler(
|
|
915
|
+
z.object({
|
|
916
|
+
method: z.literal("executeOrder"),
|
|
917
|
+
params: orderSchema
|
|
918
|
+
}),
|
|
919
|
+
async (request, extra) => {
|
|
920
|
+
try {
|
|
921
|
+
const args = request.params;
|
|
922
|
+
const verifiedOrder = this.verifiedOrders.get(args.clOrdID);
|
|
923
|
+
if (!verifiedOrder) {
|
|
924
|
+
return {
|
|
925
|
+
contents: [
|
|
926
|
+
{
|
|
927
|
+
type: "text",
|
|
928
|
+
text: `Error: Order ${args.clOrdID} has not been verified. Please call verifyOrder first.`
|
|
929
|
+
}
|
|
930
|
+
],
|
|
931
|
+
isError: true
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
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) {
|
|
935
|
+
return {
|
|
936
|
+
contents: [
|
|
937
|
+
{
|
|
938
|
+
type: "text",
|
|
939
|
+
text: "Error: Order parameters do not match the verified order. Please use the exact same parameters that were verified."
|
|
940
|
+
}
|
|
941
|
+
],
|
|
942
|
+
isError: true
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
const response = new Promise((resolve) => {
|
|
946
|
+
this.pendingRequests.set(args.clOrdID, resolve);
|
|
947
|
+
});
|
|
948
|
+
const order = this.parser?.createMessage(
|
|
949
|
+
new Field(Fields.MsgType, Messages.NewOrderSingle),
|
|
950
|
+
new Field(Fields.MsgSeqNum, this.parser?.getNextTargetMsgSeqNum()),
|
|
951
|
+
new Field(Fields.SenderCompID, this.parser?.sender),
|
|
952
|
+
new Field(Fields.TargetCompID, this.parser?.target),
|
|
953
|
+
new Field(Fields.SendingTime, this.parser?.getTimestamp()),
|
|
954
|
+
new Field(Fields.ClOrdID, args.clOrdID),
|
|
955
|
+
new Field(Fields.Side, args.side),
|
|
956
|
+
new Field(Fields.Symbol, args.symbol),
|
|
957
|
+
new Field(Fields.OrderQty, Number.parseFloat(args.quantity)),
|
|
958
|
+
new Field(Fields.Price, Number.parseFloat(args.price)),
|
|
959
|
+
new Field(Fields.OrdType, args.ordType),
|
|
960
|
+
new Field(Fields.HandlInst, args.handlInst),
|
|
961
|
+
new Field(Fields.TimeInForce, args.timeInForce),
|
|
962
|
+
new Field(Fields.TransactTime, this.parser?.getTimestamp())
|
|
963
|
+
);
|
|
964
|
+
if (!this.parser?.connected) {
|
|
965
|
+
return {
|
|
966
|
+
contents: [
|
|
967
|
+
{
|
|
968
|
+
type: "text",
|
|
969
|
+
text: "Error: Not connected. Ignoring message."
|
|
970
|
+
}
|
|
971
|
+
],
|
|
972
|
+
isError: true
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
this.parser?.send(order);
|
|
976
|
+
const fixData = await response;
|
|
977
|
+
this.verifiedOrders.delete(args.clOrdID);
|
|
978
|
+
return {
|
|
979
|
+
contents: [
|
|
980
|
+
{
|
|
981
|
+
type: "text",
|
|
982
|
+
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())}`
|
|
983
|
+
}
|
|
984
|
+
]
|
|
985
|
+
};
|
|
986
|
+
} catch (error) {
|
|
987
|
+
return {
|
|
988
|
+
contents: [
|
|
989
|
+
{
|
|
990
|
+
type: "text",
|
|
991
|
+
text: `Error: ${error instanceof Error ? error.message : "Failed to execute order"}`
|
|
992
|
+
}
|
|
993
|
+
],
|
|
994
|
+
isError: true
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
);
|
|
999
|
+
this.server.setRequestHandler(
|
|
1000
|
+
z.object({
|
|
1001
|
+
method: z.literal("marketDataRequest"),
|
|
1002
|
+
params: marketDataRequestSchema
|
|
1003
|
+
}),
|
|
1004
|
+
async (request, extra) => {
|
|
1005
|
+
try {
|
|
1006
|
+
const args = request.params;
|
|
1007
|
+
const response = new Promise((resolve) => {
|
|
1008
|
+
this.pendingRequests.set(args.mdReqID, resolve);
|
|
1009
|
+
});
|
|
1010
|
+
const messageFields = [
|
|
1011
|
+
new Field(Fields.MsgType, Messages.MarketDataRequest),
|
|
1012
|
+
new Field(Fields.SenderCompID, this.parser?.sender),
|
|
1013
|
+
new Field(Fields.MsgSeqNum, this.parser?.getNextTargetMsgSeqNum()),
|
|
1014
|
+
new Field(Fields.TargetCompID, this.parser?.target),
|
|
1015
|
+
new Field(Fields.SendingTime, this.parser?.getTimestamp()),
|
|
1016
|
+
new Field(Fields.MDReqID, args.mdReqID),
|
|
1017
|
+
new Field(Fields.SubscriptionRequestType, args.subscriptionRequestType),
|
|
1018
|
+
new Field(Fields.MarketDepth, 0),
|
|
1019
|
+
new Field(Fields.MDUpdateType, args.mdUpdateType)
|
|
1020
|
+
];
|
|
1021
|
+
messageFields.push(new Field(Fields.NoRelatedSym, args.symbols.length));
|
|
1022
|
+
args.symbols.forEach((symbol) => {
|
|
1023
|
+
messageFields.push(new Field(Fields.Symbol, symbol));
|
|
1024
|
+
});
|
|
1025
|
+
messageFields.push(new Field(Fields.NoMDEntryTypes, args.mdEntryTypes.length));
|
|
1026
|
+
args.mdEntryTypes.forEach((entryType) => {
|
|
1027
|
+
messageFields.push(new Field(Fields.MDEntryType, entryType));
|
|
1028
|
+
});
|
|
1029
|
+
const mdr = this.parser?.createMessage(...messageFields);
|
|
1030
|
+
if (!this.parser?.connected) {
|
|
1031
|
+
return {
|
|
1032
|
+
contents: [
|
|
1033
|
+
{
|
|
1034
|
+
type: "text",
|
|
1035
|
+
text: "Error: Not connected. Ignoring message."
|
|
1036
|
+
}
|
|
1037
|
+
],
|
|
1038
|
+
isError: true
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
this.parser?.send(mdr);
|
|
1042
|
+
const fixData = await response;
|
|
1043
|
+
return {
|
|
1044
|
+
contents: [
|
|
1045
|
+
{
|
|
1046
|
+
type: "text",
|
|
1047
|
+
text: `Market data for ${args.symbols.join(", ")}: ${JSON.stringify(fixData.toFIXJSON())}`
|
|
1048
|
+
}
|
|
1049
|
+
]
|
|
1050
|
+
};
|
|
1051
|
+
} catch (error) {
|
|
1052
|
+
return {
|
|
1053
|
+
contents: [
|
|
1054
|
+
{
|
|
1055
|
+
type: "text",
|
|
1056
|
+
text: `Error: ${error instanceof Error ? error.message : "Failed to request market data"}`
|
|
1057
|
+
}
|
|
1058
|
+
],
|
|
1059
|
+
isError: true
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
);
|
|
996
1064
|
process.on("SIGINT", async () => {
|
|
997
1065
|
await this.server.close();
|
|
998
1066
|
process.exit(0);
|