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,592 +0,0 @@
1
- // src/MCPRemote.ts
2
- import { randomUUID } from "node:crypto";
3
- import { createServer } from "node:http";
4
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
6
- import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
7
- import { z } from "zod";
8
- import {
9
- Field,
10
- Fields,
11
- HandlInst,
12
- MDEntryType,
13
- Messages,
14
- OrdType,
15
- SubscriptionRequestType,
16
- TimeInForce
17
- } from "fixparser";
18
- var transports = {};
19
- var MCPRemote = class {
20
- /**
21
- * Port number the server will listen on.
22
- * @private
23
- */
24
- port;
25
- /**
26
- * Optional logger instance for diagnostics and output.
27
- * @private
28
- */
29
- logger;
30
- /**
31
- * FIXParser instance, set during plugin register().
32
- * @private
33
- */
34
- parser;
35
- /**
36
- * Node.js HTTP server instance created internally.
37
- * @private
38
- */
39
- server;
40
- /**
41
- * MCP server instance handling MCP protocol logic.
42
- * @private
43
- */
44
- mcpServer;
45
- /**
46
- * Optional name of the plugin/server instance.
47
- * @private
48
- */
49
- name;
50
- /**
51
- * Optional version string of the plugin/server.
52
- * @private
53
- */
54
- version;
55
- /**
56
- * Called when server is setup and listening.
57
- * @private
58
- */
59
- onReady = void 0;
60
- /**
61
- * A map of pending market data requests, keyed by MDReqID.
62
- * Each entry contains a resolver function that is called when the corresponding
63
- * FIX Message is received.
64
- * @private
65
- * @type {Map<string, (data: Message) => void>}
66
- */
67
- pendingRequests = /* @__PURE__ */ new Map();
68
- constructor({ port, logger, onReady }) {
69
- this.port = port;
70
- if (logger) this.logger = logger;
71
- if (onReady) this.onReady = onReady;
72
- }
73
- async register(parser) {
74
- this.parser = parser;
75
- this.parser.addOnMessageCallback((message) => {
76
- this.logger?.log({
77
- level: "info",
78
- message: `FIXParser (MCP): (${parser.protocol?.toUpperCase()}): << received ${message.description}`
79
- });
80
- const msgType = message.messageType;
81
- if (msgType === Messages.MarketDataSnapshotFullRefresh || msgType === Messages.ExecutionReport) {
82
- const idField = msgType === Messages.MarketDataSnapshotFullRefresh ? message.getField(Fields.MDReqID) : message.getField(Fields.ClOrdID);
83
- if (idField) {
84
- const id = idField.value;
85
- if (typeof id === "string" || typeof id === "number") {
86
- const callback = this.pendingRequests.get(String(id));
87
- if (callback) {
88
- callback(message);
89
- this.pendingRequests.delete(String(id));
90
- }
91
- }
92
- }
93
- }
94
- });
95
- this.logger = parser.logger;
96
- this.logger?.log({
97
- level: "info",
98
- message: `FIXParser (MCP): -- Plugin registered. Creating MCP server on port ${this.port}...`
99
- });
100
- this.server = createServer(async (req, res) => {
101
- if (!req.url || !req.method) {
102
- res.writeHead(400);
103
- res.end("Bad Request");
104
- return;
105
- }
106
- const url = new URL(req.url, `http://${req.headers.host}`);
107
- if (url.pathname === "/.well-known/oauth-authorization-server") {
108
- const config = {
109
- issuer: "https://accounts.google.com",
110
- authorization_endpoint: "https://accounts.google.com/o/oauth2/v2/auth",
111
- token_endpoint: "https://oauth2.googleapis.com/token",
112
- jwks_uri: "https://www.googleapis.com/oauth2/v3/certs",
113
- response_types_supported: [
114
- "code",
115
- "token",
116
- "id_token",
117
- "code token",
118
- "code id_token",
119
- "token id_token",
120
- "code token id_token"
121
- ],
122
- subject_types_supported: ["public"],
123
- id_token_signing_alg_values_supported: ["RS256"],
124
- scopes_supported: ["openid", "email", "profile"],
125
- token_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"]
126
- };
127
- res.writeHead(200, { "Content-Type": "application/json" });
128
- res.end(JSON.stringify(config));
129
- return;
130
- }
131
- if (url.pathname === "/") {
132
- const sessionId = req.headers["mcp-session-id"];
133
- if (req.method === "POST") {
134
- const bodyChunks = [];
135
- req.on("data", (chunk) => {
136
- bodyChunks.push(chunk);
137
- });
138
- req.on("end", async () => {
139
- let parsed;
140
- const body = Buffer.concat(bodyChunks).toString();
141
- try {
142
- parsed = JSON.parse(body);
143
- } catch (err) {
144
- res.writeHead(400);
145
- res.end(JSON.stringify({ error: "Invalid JSON" }));
146
- return;
147
- }
148
- let transport;
149
- if (sessionId && transports[sessionId]) {
150
- transport = transports[sessionId];
151
- } else if (!sessionId && req.method === "POST" && isInitializeRequest(parsed)) {
152
- transport = new StreamableHTTPServerTransport({
153
- sessionIdGenerator: () => randomUUID(),
154
- onsessioninitialized: (sessionId2) => {
155
- transports[sessionId2] = transport;
156
- }
157
- });
158
- transport.onclose = () => {
159
- if (transport.sessionId) {
160
- delete transports[transport.sessionId];
161
- }
162
- };
163
- this.mcpServer = new McpServer({
164
- name: this.name || "FIXParser",
165
- version: this.version || "1.0.0"
166
- });
167
- this.addWorkflows();
168
- await this.mcpServer.connect(transport);
169
- } else {
170
- res.writeHead(400, { "Content-Type": "application/json" });
171
- res.end(
172
- JSON.stringify({
173
- jsonrpc: "2.0",
174
- error: {
175
- code: -32e3,
176
- message: "Bad Request: No valid session ID provided"
177
- },
178
- id: null
179
- })
180
- );
181
- return;
182
- }
183
- await transport.handleRequest(req, res, parsed);
184
- });
185
- } else if (req.method === "GET" || req.method === "DELETE") {
186
- if (!sessionId || !transports[sessionId]) {
187
- res.writeHead(400);
188
- res.end("Invalid or missing session ID");
189
- return;
190
- }
191
- const transport = transports[sessionId];
192
- await transport.handleRequest(req, res);
193
- } else {
194
- res.writeHead(405);
195
- res.end("Method Not Allowed");
196
- }
197
- return;
198
- }
199
- res.writeHead(404);
200
- res.end("Not Found");
201
- });
202
- this.server.listen(this.port, () => {
203
- this.logger?.log({
204
- level: "info",
205
- message: `FIXParser (MCP): -- Server listening on http://localhost:${this.port}...`
206
- });
207
- });
208
- if (this.onReady) {
209
- this.onReady();
210
- }
211
- }
212
- addWorkflows() {
213
- if (!this.parser) {
214
- this.logger?.log({
215
- level: "error",
216
- message: "FIXParser (MCP): -- FIXParser instance not initialized. Ignoring setup of workflows..."
217
- });
218
- return;
219
- }
220
- if (!this.mcpServer) {
221
- this.logger?.log({
222
- level: "error",
223
- message: "FIXParser (MCP): -- MCP Server not initialized. Ignoring setup of workflows..."
224
- });
225
- return;
226
- }
227
- this.mcpServer.tool(
228
- "parse",
229
- "Parses a FIX message and describes it in plain language",
230
- { fixString: z.string() },
231
- async ({ fixString }) => {
232
- const parsedMessage = this.parser?.parse(fixString);
233
- if (!parsedMessage || parsedMessage.length === 0) {
234
- this.logger?.log({
235
- level: "error",
236
- message: "FIXParser (MCP): -- Failed to parse FIX string"
237
- });
238
- return {
239
- isError: true,
240
- content: [
241
- {
242
- type: "text",
243
- text: "Error: Failed to parse FIX string"
244
- }
245
- ]
246
- };
247
- }
248
- return {
249
- content: [
250
- {
251
- type: "text",
252
- text: parsedMessage ? `${parsedMessage[0].description} - ${parsedMessage[0].messageTypeDescription}` : ""
253
- }
254
- ]
255
- };
256
- }
257
- );
258
- this.mcpServer.prompt(
259
- "parse",
260
- "Parses a FIX message and describes it in plain language",
261
- { fixString: z.string() },
262
- ({ fixString }) => ({
263
- messages: [
264
- {
265
- role: "user",
266
- content: {
267
- type: "text",
268
- text: `Please parse and explain this FIX message: ${fixString}`
269
- }
270
- }
271
- ]
272
- })
273
- );
274
- this.mcpServer.tool(
275
- "parseToJSON",
276
- "Parses a FIX message into JSON",
277
- { fixString: z.string() },
278
- async ({ fixString }) => {
279
- const parsedMessage = this.parser?.parse(fixString);
280
- if (!parsedMessage || parsedMessage.length === 0) {
281
- this.logger?.log({
282
- level: "error",
283
- message: "FIXParser (MCP): -- Failed to parse FIX string"
284
- });
285
- return {
286
- isError: true,
287
- content: [
288
- {
289
- type: "text",
290
- text: "Error: Failed to parse FIX string"
291
- }
292
- ]
293
- };
294
- }
295
- return {
296
- content: [
297
- {
298
- type: "text",
299
- text: parsedMessage ? JSON.stringify(parsedMessage[0].toFIXJSON()) : ""
300
- }
301
- ]
302
- };
303
- }
304
- );
305
- this.mcpServer.prompt(
306
- "parseToJSON",
307
- "Parses a FIX message into JSON",
308
- { fixString: z.string() },
309
- ({ fixString }) => ({
310
- messages: [
311
- {
312
- role: "user",
313
- content: {
314
- type: "text",
315
- text: `Please parse the FIX message to JSON: ${fixString}`
316
- }
317
- }
318
- ]
319
- })
320
- );
321
- this.mcpServer.tool(
322
- "newOrderSingle",
323
- "Creates and sends a New Order Single",
324
- {
325
- clOrdID: z.string(),
326
- handlInst: z.enum(["1", "2", "3"]).default(HandlInst.AutomatedExecutionNoIntervention).optional(),
327
- quantity: z.number(),
328
- price: z.number(),
329
- ordType: z.enum([
330
- "1",
331
- "2",
332
- "3",
333
- "4",
334
- "5",
335
- "6",
336
- "7",
337
- "8",
338
- "9",
339
- "A",
340
- "B",
341
- "C",
342
- "D",
343
- "E",
344
- "F",
345
- "G",
346
- "H",
347
- "I",
348
- "J",
349
- "K",
350
- "L",
351
- "M",
352
- "P",
353
- "Q",
354
- "R",
355
- "S"
356
- ]).default(OrdType.Market).optional(),
357
- side: z.enum(["1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H"]),
358
- // 1 = Buy, 2 = Sell...
359
- symbol: z.string(),
360
- timeInForce: z.enum(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C"]).default(TimeInForce.Day).optional()
361
- },
362
- async ({ clOrdID, handlInst, quantity, price, ordType, side, symbol, timeInForce }) => {
363
- const response = new Promise((resolve) => {
364
- this.pendingRequests.set(clOrdID, resolve);
365
- });
366
- const order = this.parser?.createMessage(
367
- new Field(Fields.MsgType, Messages.NewOrderSingle),
368
- new Field(Fields.MsgSeqNum, this.parser?.getNextTargetMsgSeqNum()),
369
- new Field(Fields.SenderCompID, this.parser?.sender),
370
- new Field(Fields.TargetCompID, this.parser?.target),
371
- new Field(Fields.SendingTime, this.parser?.getTimestamp()),
372
- new Field(Fields.ClOrdID, clOrdID),
373
- new Field(Fields.Side, side),
374
- new Field(Fields.Symbol, symbol),
375
- new Field(Fields.OrderQty, quantity),
376
- new Field(Fields.Price, price),
377
- new Field(Fields.OrdType, ordType),
378
- new Field(Fields.HandlInst, handlInst),
379
- new Field(Fields.TimeInForce, timeInForce),
380
- new Field(Fields.TransactTime, this.parser?.getTimestamp())
381
- );
382
- if (!this.parser?.connected) {
383
- this.logger?.log({
384
- level: "error",
385
- message: "FIXParser (MCP): -- Not connected. Ignoring message."
386
- });
387
- return {
388
- isError: true,
389
- content: [
390
- {
391
- type: "text",
392
- text: "Error: Not connected. Ignoring message."
393
- }
394
- ]
395
- };
396
- }
397
- this.parser?.send(order);
398
- this.logger?.log({
399
- level: "info",
400
- message: `FIXParser (MCP): (${this.parser?.protocol?.toUpperCase()}): >> sent ${order?.description}`
401
- });
402
- const fixData = await response;
403
- return {
404
- content: [
405
- {
406
- type: "text",
407
- text: `Execution Report for order ${clOrdID}: ${JSON.stringify(fixData.toFIXJSON())}`
408
- }
409
- ]
410
- };
411
- }
412
- );
413
- this.mcpServer.prompt(
414
- "newOrderSingle",
415
- "Creates and sends a New Order Single",
416
- {
417
- clOrdID: z.string(),
418
- handlInst: z.string(),
419
- quantity: z.string(),
420
- price: z.string(),
421
- ordType: z.string(),
422
- side: z.string(),
423
- symbol: z.string(),
424
- timeInForce: z.string()
425
- },
426
- ({ clOrdID, handlInst, quantity, price, ordType, side, symbol, timeInForce }) => ({
427
- messages: [
428
- {
429
- role: "user",
430
- content: {
431
- type: "text",
432
- text: [
433
- "Create a New Order Single FIX message with the following parameters:",
434
- `- ClOrdID: ${clOrdID}`,
435
- `- HandlInst: ${handlInst ?? "default"}`,
436
- `- Quantity: ${quantity}`,
437
- `- Price: ${price}`,
438
- `- OrdType: ${ordType ?? "default (Market)"}`,
439
- `- Side: ${side}`,
440
- `- Symbol: ${symbol}`,
441
- `- TimeInForce: ${timeInForce ?? "default (Day)"}`,
442
- "",
443
- "Format the response as a JSON object with FIX tag numbers as keys and their corresponding values."
444
- ].join("\n")
445
- }
446
- }
447
- ]
448
- })
449
- );
450
- this.mcpServer.tool(
451
- "marketDataRequest",
452
- "Sends a request for Market Data with the given symbol",
453
- {
454
- mdUpdateType: z.enum(["0", "1"]).default("0").optional(),
455
- // MDUpdateType.FullRefresh
456
- symbol: z.string(),
457
- mdReqID: z.string(),
458
- subscriptionRequestType: z.enum(["0", "1", "2"]).default(SubscriptionRequestType.SnapshotAndUpdates).optional(),
459
- mdEntryType: z.enum([
460
- "0",
461
- "1",
462
- "2",
463
- "3",
464
- "4",
465
- "5",
466
- "6",
467
- "7",
468
- "8",
469
- "9",
470
- "A",
471
- "B",
472
- "C",
473
- "D",
474
- "E",
475
- "F",
476
- "G",
477
- "H",
478
- "J",
479
- "K",
480
- "L",
481
- "M",
482
- "N",
483
- "O",
484
- "P",
485
- "Q",
486
- "S",
487
- "R",
488
- "T",
489
- "U",
490
- "V",
491
- "W",
492
- "X",
493
- "Y",
494
- "Z",
495
- "a",
496
- "b",
497
- "c",
498
- "d",
499
- "e",
500
- "g",
501
- "h",
502
- "i",
503
- "t"
504
- ]).default(MDEntryType.Bid).optional()
505
- },
506
- async ({ mdUpdateType, symbol, mdReqID, subscriptionRequestType, mdEntryType }) => {
507
- const response = new Promise((resolve) => {
508
- this.pendingRequests.set(mdReqID, resolve);
509
- });
510
- const marketDataRequest = this.parser?.createMessage(
511
- new Field(Fields.MsgType, Messages.MarketDataRequest),
512
- new Field(Fields.SenderCompID, this.parser?.sender),
513
- new Field(Fields.MsgSeqNum, this.parser?.getNextTargetMsgSeqNum()),
514
- new Field(Fields.TargetCompID, this.parser?.target),
515
- new Field(Fields.SendingTime, this.parser?.getTimestamp()),
516
- new Field(Fields.MarketDepth, 0),
517
- new Field(Fields.MDUpdateType, mdUpdateType),
518
- new Field(Fields.NoRelatedSym, 1),
519
- new Field(Fields.Symbol, symbol),
520
- new Field(Fields.MDReqID, mdReqID),
521
- new Field(Fields.SubscriptionRequestType, subscriptionRequestType),
522
- new Field(Fields.NoMDEntryTypes, 1),
523
- new Field(Fields.MDEntryType, mdEntryType)
524
- );
525
- if (!this.parser?.connected) {
526
- this.logger?.log({
527
- level: "error",
528
- message: "FIXParser (MCP): -- Not connected. Ignoring message."
529
- });
530
- return {
531
- isError: true,
532
- content: [
533
- {
534
- type: "text",
535
- text: "Error: Not connected. Ignoring message."
536
- }
537
- ]
538
- };
539
- }
540
- this.parser?.send(marketDataRequest);
541
- this.logger?.log({
542
- level: "info",
543
- message: `FIXParser (MCP): (${this.parser?.protocol?.toUpperCase()}): >> sent ${marketDataRequest?.description}`
544
- });
545
- const fixData = await response;
546
- return {
547
- content: [
548
- {
549
- type: "text",
550
- text: `Market data for ${symbol}: ${JSON.stringify(fixData.toFIXJSON())}`
551
- }
552
- ]
553
- };
554
- }
555
- );
556
- this.mcpServer.prompt(
557
- "marketDataRequest",
558
- "Sends a request for Market Data with the given symbol",
559
- {
560
- mdUpdateType: z.string(),
561
- symbol: z.string(),
562
- mdReqID: z.string(),
563
- subscriptionRequestType: z.string(),
564
- mdEntryType: z.string()
565
- },
566
- ({ mdUpdateType, symbol, mdReqID, subscriptionRequestType, mdEntryType }) => ({
567
- messages: [
568
- {
569
- role: "user",
570
- content: {
571
- type: "text",
572
- text: [
573
- "Create a Market Data Request FIX message with the following parameters:",
574
- `- MDUpdateType: ${mdUpdateType ?? "default (0 = FullRefresh)"}`,
575
- `- Symbol: ${symbol}`,
576
- `- MDReqID: ${mdReqID}`,
577
- `- SubscriptionRequestType: ${subscriptionRequestType ?? "default (0 = Snapshot + Updates)"}`,
578
- `- MDEntryType: ${mdEntryType ?? "default (0 = Bid)"}`,
579
- "",
580
- "Format the response as a JSON object with FIX tag numbers as keys and their corresponding values."
581
- ].join("\n")
582
- }
583
- }
584
- ]
585
- })
586
- );
587
- }
588
- };
589
- export {
590
- MCPRemote
591
- };
592
- //# sourceMappingURL=MCPRemote.mjs.map