langflow-mcp-server 4.11.0 → 4.12.2
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/.env.example +19 -1
- package/README.md +150 -31
- package/dist/__tests__/fixtures/advanced-execution.d.ts.map +1 -1
- package/dist/__tests__/fixtures/advanced-execution.js +7 -4
- package/dist/__tests__/fixtures/advanced-execution.js.map +1 -1
- package/dist/__tests__/fixtures/api-keys.js.map +1 -1
- package/dist/__tests__/fixtures/auth.d.ts.map +1 -1
- package/dist/__tests__/fixtures/components.d.ts.map +1 -1
- package/dist/__tests__/fixtures/custom-components.js.map +1 -1
- package/dist/__tests__/fixtures/files.d.ts.map +1 -1
- package/dist/__tests__/fixtures/files.js.map +1 -1
- package/dist/__tests__/fixtures/flows.js.map +1 -1
- package/dist/__tests__/fixtures/folders.js.map +1 -1
- package/dist/__tests__/fixtures/knowledge-bases.d.ts.map +1 -1
- package/dist/__tests__/fixtures/knowledge-bases.js.map +1 -1
- package/dist/__tests__/fixtures/misc.js.map +1 -1
- package/dist/__tests__/fixtures/monitoring.js.map +1 -1
- package/dist/__tests__/fixtures/projects.js.map +1 -1
- package/dist/__tests__/fixtures/run.d.ts.map +1 -1
- package/dist/__tests__/fixtures/store.d.ts.map +1 -1
- package/dist/__tests__/fixtures/store.js.map +1 -1
- package/dist/__tests__/fixtures/users.js.map +1 -1
- package/dist/__tests__/fixtures/variables.js.map +1 -1
- package/dist/__tests__/fixtures/vertices.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/http.d.ts +69 -0
- package/dist/mcp/http.d.ts.map +1 -0
- package/dist/mcp/http.js +595 -0
- package/dist/mcp/http.js.map +1 -0
- package/dist/mcp/index.js +39 -5
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/prompts.js.map +1 -1
- package/dist/mcp/resources.d.ts.map +1 -1
- package/dist/mcp/resources.js +6 -17
- package/dist/mcp/resources.js.map +1 -1
- package/dist/mcp/server-consolidated.d.ts +3 -0
- package/dist/mcp/server-consolidated.d.ts.map +1 -1
- package/dist/mcp/server-consolidated.js +94 -8
- package/dist/mcp/server-consolidated.js.map +1 -1
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +122 -12
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools-consolidated.d.ts.map +1 -1
- package/dist/mcp/tools-consolidated.js +119 -32
- package/dist/mcp/tools-consolidated.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +338 -58
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/validation-consolidated.d.ts +234 -48
- package/dist/mcp/validation-consolidated.d.ts.map +1 -1
- package/dist/mcp/validation-consolidated.js +148 -31
- package/dist/mcp/validation-consolidated.js.map +1 -1
- package/dist/mcp/validation.d.ts +240 -51
- package/dist/mcp/validation.d.ts.map +1 -1
- package/dist/mcp/validation.js +150 -35
- package/dist/mcp/validation.js.map +1 -1
- package/dist/services/langflow-client.d.ts +27 -7
- package/dist/services/langflow-client.d.ts.map +1 -1
- package/dist/services/langflow-client.js +214 -12
- package/dist/services/langflow-client.js.map +1 -1
- package/dist/types/index.d.ts +162 -27
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +2 -3
- package/dist/utils/logger.js.map +1 -1
- package/package.json +13 -14
package/dist/index.js
CHANGED
|
@@ -14,10 +14,14 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.LangflowMCPServer = exports.LangflowClient = void 0;
|
|
17
|
+
exports.LangflowMCPHttpServer = exports.LangflowMCPServerConsolidated = exports.LangflowMCPServer = exports.LangflowClient = void 0;
|
|
18
18
|
__exportStar(require("./types"), exports);
|
|
19
19
|
var langflow_client_1 = require("./services/langflow-client");
|
|
20
20
|
Object.defineProperty(exports, "LangflowClient", { enumerable: true, get: function () { return langflow_client_1.LangflowClient; } });
|
|
21
21
|
var server_1 = require("./mcp/server");
|
|
22
22
|
Object.defineProperty(exports, "LangflowMCPServer", { enumerable: true, get: function () { return server_1.LangflowMCPServer; } });
|
|
23
|
+
var server_consolidated_1 = require("./mcp/server-consolidated");
|
|
24
|
+
Object.defineProperty(exports, "LangflowMCPServerConsolidated", { enumerable: true, get: function () { return server_consolidated_1.LangflowMCPServerConsolidated; } });
|
|
25
|
+
var http_1 = require("./mcp/http");
|
|
26
|
+
Object.defineProperty(exports, "LangflowMCPHttpServer", { enumerable: true, get: function () { return http_1.LangflowMCPHttpServer; } });
|
|
23
27
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,0CAAwB;AACxB,8DAA4D;AAAnD,iHAAA,cAAc,OAAA;AACvB,uCAAiD;AAAxC,2GAAA,iBAAiB,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,0CAAwB;AACxB,8DAA4D;AAAnD,iHAAA,cAAc,OAAA;AACvB,uCAAiD;AAAxC,2GAAA,iBAAiB,OAAA;AAC1B,iEAA0E;AAAjE,oIAAA,6BAA6B,OAAA;AACtC,mCAAmD;AAA1C,6GAAA,qBAAqB,OAAA"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { AddressInfo } from 'node:net';
|
|
2
|
+
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
3
|
+
interface McpServerInstance {
|
|
4
|
+
connectTransport(transport: Transport): Promise<void>;
|
|
5
|
+
shutdown(): Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
export interface LangflowMCPHttpServerOptions {
|
|
8
|
+
host?: string;
|
|
9
|
+
port?: number;
|
|
10
|
+
authToken?: string;
|
|
11
|
+
allowedOrigins?: string[];
|
|
12
|
+
enableJsonResponse?: boolean;
|
|
13
|
+
maxSessions?: number;
|
|
14
|
+
sessionIdleTimeoutMs?: number;
|
|
15
|
+
maxConcurrentRequests?: number;
|
|
16
|
+
maxConcurrentBodyBytes?: number;
|
|
17
|
+
requestBodyTimeoutMs?: number;
|
|
18
|
+
shutdownTimeoutMs?: number;
|
|
19
|
+
createMcpServer: () => McpServerInstance;
|
|
20
|
+
}
|
|
21
|
+
export declare class LangflowMCPHttpServer {
|
|
22
|
+
private readonly host;
|
|
23
|
+
private readonly port;
|
|
24
|
+
private readonly authToken?;
|
|
25
|
+
private readonly allowedOrigins;
|
|
26
|
+
private readonly enableJsonResponse;
|
|
27
|
+
private readonly maxSessions;
|
|
28
|
+
private readonly sessionIdleTimeoutMs;
|
|
29
|
+
private readonly maxConcurrentRequests;
|
|
30
|
+
private readonly maxConcurrentBodyBytes;
|
|
31
|
+
private readonly requestBodyTimeoutMs;
|
|
32
|
+
private readonly shutdownTimeoutMs;
|
|
33
|
+
private readonly createMcpServer;
|
|
34
|
+
private readonly httpServer;
|
|
35
|
+
private readonly sockets;
|
|
36
|
+
private readonly sessions;
|
|
37
|
+
private readonly activeRequests;
|
|
38
|
+
private activeBodyBytes;
|
|
39
|
+
private pendingSessions;
|
|
40
|
+
private lifecycle;
|
|
41
|
+
private sessionSweep?;
|
|
42
|
+
private startPromise?;
|
|
43
|
+
private shutdownPromise?;
|
|
44
|
+
constructor(options: LangflowMCPHttpServerOptions);
|
|
45
|
+
getAddress(): AddressInfo | null;
|
|
46
|
+
start(): Promise<void>;
|
|
47
|
+
shutdown(): Promise<void>;
|
|
48
|
+
private acceptRequest;
|
|
49
|
+
private performShutdown;
|
|
50
|
+
private waitForCompletion;
|
|
51
|
+
private closeHttpServer;
|
|
52
|
+
private handleRequest;
|
|
53
|
+
private handlePost;
|
|
54
|
+
private handleSessionRequest;
|
|
55
|
+
private createSession;
|
|
56
|
+
private closeSession;
|
|
57
|
+
private readJsonBody;
|
|
58
|
+
private readBodyContent;
|
|
59
|
+
private handleSessionTransport;
|
|
60
|
+
private startSessionSweep;
|
|
61
|
+
private stopSessionSweep;
|
|
62
|
+
private isAuthorized;
|
|
63
|
+
private isAllowedOrigin;
|
|
64
|
+
private setCorsHeaders;
|
|
65
|
+
private sendJson;
|
|
66
|
+
private sendError;
|
|
67
|
+
}
|
|
68
|
+
export {};
|
|
69
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/mcp/http.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAU,MAAM,UAAU,CAAC;AAIpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AAE/E,UAAU,iBAAiB;IACzB,gBAAgB,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAID,MAAM,WAAW,4BAA4B;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,iBAAiB,CAAC;CAC1C;AA6FD,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAW;IAC1C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAU;IAC7C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;IAC9C,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAS;IAC/C,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAS;IAChD,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;IAC9C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA0B;IAC1D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAiB;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiC;IAC1D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAgC;IAC/D,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,SAAS,CAA4B;IAC7C,OAAO,CAAC,YAAY,CAAC,CAAiB;IACtC,OAAO,CAAC,YAAY,CAAC,CAAgB;IACrC,OAAO,CAAC,eAAe,CAAC,CAAgB;IAExC,YAAY,OAAO,EAAE,4BAA4B,EAsEhD;IAED,UAAU,IAAI,WAAW,GAAG,IAAI,CAG/B;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAwC3B;IAEK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAQ9B;IAED,OAAO,CAAC,aAAa;YA+BP,eAAe;YAwBf,iBAAiB;YAiBjB,eAAe;YAsBf,aAAa;YAyEb,UAAU;YAuCV,oBAAoB;YAgBpB,aAAa;YAsEb,YAAY;YAwBZ,YAAY;YAiCZ,eAAe;YAyCf,sBAAsB;IAoBpC,OAAO,CAAC,iBAAiB;IAezB,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,QAAQ;IAMhB,OAAO,CAAC,SAAS;CAiBlB"}
|
package/dist/mcp/http.js
ADDED
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LangflowMCPHttpServer = void 0;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const node_http_1 = require("node:http");
|
|
6
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
7
|
+
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
8
|
+
const logger_1 = require("../utils/logger");
|
|
9
|
+
class HttpRequestError extends Error {
|
|
10
|
+
constructor(statusCode, errorCode, message, headers = {}) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.statusCode = statusCode;
|
|
13
|
+
this.errorCode = errorCode;
|
|
14
|
+
this.headers = headers;
|
|
15
|
+
this.name = 'HttpRequestError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const DEFAULT_HOST = '127.0.0.1';
|
|
19
|
+
const DEFAULT_PORT = 3000;
|
|
20
|
+
const MAX_BODY_BYTES = 20 * 1024 * 1024;
|
|
21
|
+
const DEFAULT_MAX_SESSIONS = 100;
|
|
22
|
+
const DEFAULT_SESSION_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
|
|
23
|
+
const DEFAULT_MAX_CONCURRENT_REQUESTS = 100;
|
|
24
|
+
const DEFAULT_MAX_CONCURRENT_BODY_BYTES = 64 * 1024 * 1024;
|
|
25
|
+
const DEFAULT_REQUEST_BODY_TIMEOUT_MS = 30 * 1000;
|
|
26
|
+
const DEFAULT_SHUTDOWN_TIMEOUT_MS = 5 * 1000;
|
|
27
|
+
const MCP_PATH = '/mcp';
|
|
28
|
+
function parsePort(value) {
|
|
29
|
+
if (value === undefined || value.trim() === '')
|
|
30
|
+
return DEFAULT_PORT;
|
|
31
|
+
const port = Number(value);
|
|
32
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
33
|
+
throw new Error('PORT must be an integer between 0 and 65535');
|
|
34
|
+
}
|
|
35
|
+
return port;
|
|
36
|
+
}
|
|
37
|
+
function validatePort(port) {
|
|
38
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
39
|
+
throw new Error('PORT must be an integer between 0 and 65535');
|
|
40
|
+
}
|
|
41
|
+
return port;
|
|
42
|
+
}
|
|
43
|
+
function parsePositiveInteger(value, fallback, name) {
|
|
44
|
+
if (value === undefined || value.trim() === '')
|
|
45
|
+
return fallback;
|
|
46
|
+
const parsed = Number(value);
|
|
47
|
+
if (!Number.isSafeInteger(parsed) || parsed < 1) {
|
|
48
|
+
throw new Error(`${name} must be a positive integer`);
|
|
49
|
+
}
|
|
50
|
+
return parsed;
|
|
51
|
+
}
|
|
52
|
+
function validatePositiveInteger(value, name) {
|
|
53
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
54
|
+
throw new Error(`${name} must be a positive integer`);
|
|
55
|
+
}
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
function parseOrigins(value) {
|
|
59
|
+
if (!value)
|
|
60
|
+
return [];
|
|
61
|
+
return value
|
|
62
|
+
.split(',')
|
|
63
|
+
.map(origin => origin.trim())
|
|
64
|
+
.filter(Boolean);
|
|
65
|
+
}
|
|
66
|
+
function singleHeader(value) {
|
|
67
|
+
return Array.isArray(value) ? value[0] : value;
|
|
68
|
+
}
|
|
69
|
+
function isLoopbackHost(host) {
|
|
70
|
+
const normalized = host.trim().toLowerCase().replace(/^\[|\]$/g, '');
|
|
71
|
+
return normalized === 'localhost' || normalized === '127.0.0.1' || normalized === '::1';
|
|
72
|
+
}
|
|
73
|
+
class LangflowMCPHttpServer {
|
|
74
|
+
constructor(options) {
|
|
75
|
+
this.sockets = new Set();
|
|
76
|
+
this.sessions = new Map();
|
|
77
|
+
this.activeRequests = new Set();
|
|
78
|
+
this.activeBodyBytes = 0;
|
|
79
|
+
this.pendingSessions = 0;
|
|
80
|
+
this.lifecycle = 'created';
|
|
81
|
+
this.host = options.host ?? process.env.HOST ?? DEFAULT_HOST;
|
|
82
|
+
this.port = options.port === undefined
|
|
83
|
+
? parsePort(process.env.PORT)
|
|
84
|
+
: validatePort(options.port);
|
|
85
|
+
this.authToken = options.authToken ?? (process.env.AUTH_TOKEN?.trim() || undefined);
|
|
86
|
+
this.allowedOrigins = options.allowedOrigins ?? parseOrigins(process.env.MCP_ALLOWED_ORIGINS);
|
|
87
|
+
this.enableJsonResponse = options.enableJsonResponse ?? process.env.MCP_HTTP_JSON_RESPONSES === 'true';
|
|
88
|
+
this.maxSessions = validatePositiveInteger(options.maxSessions ?? parsePositiveInteger(process.env.MCP_MAX_SESSIONS, DEFAULT_MAX_SESSIONS, 'MCP_MAX_SESSIONS'), 'maxSessions');
|
|
89
|
+
this.sessionIdleTimeoutMs = validatePositiveInteger(options.sessionIdleTimeoutMs ?? parsePositiveInteger(process.env.MCP_SESSION_IDLE_TIMEOUT_MS, DEFAULT_SESSION_IDLE_TIMEOUT_MS, 'MCP_SESSION_IDLE_TIMEOUT_MS'), 'sessionIdleTimeoutMs');
|
|
90
|
+
this.maxConcurrentRequests = validatePositiveInteger(options.maxConcurrentRequests ?? parsePositiveInteger(process.env.MCP_MAX_CONCURRENT_REQUESTS, DEFAULT_MAX_CONCURRENT_REQUESTS, 'MCP_MAX_CONCURRENT_REQUESTS'), 'maxConcurrentRequests');
|
|
91
|
+
this.maxConcurrentBodyBytes = validatePositiveInteger(options.maxConcurrentBodyBytes ?? parsePositiveInteger(process.env.MCP_MAX_CONCURRENT_BODY_BYTES, DEFAULT_MAX_CONCURRENT_BODY_BYTES, 'MCP_MAX_CONCURRENT_BODY_BYTES'), 'maxConcurrentBodyBytes');
|
|
92
|
+
if (this.maxConcurrentBodyBytes < MAX_BODY_BYTES) {
|
|
93
|
+
throw new Error(`maxConcurrentBodyBytes must be at least ${MAX_BODY_BYTES}`);
|
|
94
|
+
}
|
|
95
|
+
this.requestBodyTimeoutMs = validatePositiveInteger(options.requestBodyTimeoutMs ?? parsePositiveInteger(process.env.MCP_REQUEST_BODY_TIMEOUT_MS, DEFAULT_REQUEST_BODY_TIMEOUT_MS, 'MCP_REQUEST_BODY_TIMEOUT_MS'), 'requestBodyTimeoutMs');
|
|
96
|
+
this.shutdownTimeoutMs = validatePositiveInteger(options.shutdownTimeoutMs ?? parsePositiveInteger(process.env.MCP_SHUTDOWN_TIMEOUT_MS, DEFAULT_SHUTDOWN_TIMEOUT_MS, 'MCP_SHUTDOWN_TIMEOUT_MS'), 'shutdownTimeoutMs');
|
|
97
|
+
this.createMcpServer = options.createMcpServer;
|
|
98
|
+
if (this.allowedOrigins.includes('*')) {
|
|
99
|
+
throw new Error('MCP_ALLOWED_ORIGINS must contain exact origins; wildcard origins are not allowed');
|
|
100
|
+
}
|
|
101
|
+
if (!isLoopbackHost(this.host) && !this.authToken) {
|
|
102
|
+
throw new Error('AUTH_TOKEN is required when HTTP mode binds to a non-loopback host');
|
|
103
|
+
}
|
|
104
|
+
this.httpServer = (0, node_http_1.createServer)((request, response) => this.acceptRequest(request, response));
|
|
105
|
+
this.httpServer.on('connection', socket => {
|
|
106
|
+
this.sockets.add(socket);
|
|
107
|
+
socket.once('close', () => this.sockets.delete(socket));
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
getAddress() {
|
|
111
|
+
const address = this.httpServer.address();
|
|
112
|
+
return address && typeof address !== 'string' ? address : null;
|
|
113
|
+
}
|
|
114
|
+
async start() {
|
|
115
|
+
if (this.lifecycle !== 'created') {
|
|
116
|
+
throw new Error('HTTP server is already running');
|
|
117
|
+
}
|
|
118
|
+
this.lifecycle = 'starting';
|
|
119
|
+
const listenPromise = new Promise((resolve, reject) => {
|
|
120
|
+
const onError = (error) => {
|
|
121
|
+
this.httpServer.off('listening', onListening);
|
|
122
|
+
reject(error);
|
|
123
|
+
};
|
|
124
|
+
const onListening = () => {
|
|
125
|
+
this.httpServer.off('error', onError);
|
|
126
|
+
resolve();
|
|
127
|
+
};
|
|
128
|
+
this.httpServer.once('error', onError);
|
|
129
|
+
this.httpServer.once('listening', onListening);
|
|
130
|
+
this.httpServer.listen(this.port, this.host);
|
|
131
|
+
});
|
|
132
|
+
this.startPromise = listenPromise;
|
|
133
|
+
try {
|
|
134
|
+
await listenPromise;
|
|
135
|
+
if (this.lifecycle !== 'starting') {
|
|
136
|
+
await this.closeHttpServer();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
this.lifecycle = 'running';
|
|
140
|
+
this.startSessionSweep();
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
this.lifecycle = 'stopped';
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
if (this.startPromise === listenPromise)
|
|
148
|
+
this.startPromise = undefined;
|
|
149
|
+
}
|
|
150
|
+
const address = this.getAddress();
|
|
151
|
+
logger_1.logger.info(`Langflow MCP server running on Streamable HTTP at ${this.host}:${address?.port ?? this.port}${MCP_PATH}`);
|
|
152
|
+
}
|
|
153
|
+
async shutdown() {
|
|
154
|
+
if (this.shutdownPromise)
|
|
155
|
+
return this.shutdownPromise;
|
|
156
|
+
if (this.lifecycle === 'stopped')
|
|
157
|
+
return;
|
|
158
|
+
this.lifecycle = 'stopping';
|
|
159
|
+
const shutdownPromise = this.performShutdown();
|
|
160
|
+
this.shutdownPromise = shutdownPromise;
|
|
161
|
+
await shutdownPromise;
|
|
162
|
+
}
|
|
163
|
+
acceptRequest(request, response) {
|
|
164
|
+
if (this.lifecycle !== 'running') {
|
|
165
|
+
request.resume();
|
|
166
|
+
this.sendError(response, 503, -32000, 'MCP server is shutting down');
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (this.activeRequests.size >= this.maxConcurrentRequests) {
|
|
170
|
+
request.resume();
|
|
171
|
+
this.sendError(response, 429, -32000, 'Too many concurrent MCP requests', { 'Retry-After': '1' });
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const promise = this.handleRequest(request, response);
|
|
175
|
+
const activeRequest = { request, response, promise };
|
|
176
|
+
this.activeRequests.add(activeRequest);
|
|
177
|
+
void promise
|
|
178
|
+
.catch(error => {
|
|
179
|
+
logger_1.logger.error('MCP HTTP request failed unexpectedly:', error);
|
|
180
|
+
if (!response.headersSent) {
|
|
181
|
+
this.sendError(response, 500, -32603, 'Internal server error');
|
|
182
|
+
}
|
|
183
|
+
else if (!response.writableEnded) {
|
|
184
|
+
response.destroy(error instanceof Error ? error : undefined);
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
.finally(() => {
|
|
188
|
+
this.activeRequests.delete(activeRequest);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
async performShutdown() {
|
|
192
|
+
this.stopSessionSweep();
|
|
193
|
+
if (this.startPromise) {
|
|
194
|
+
await this.waitForCompletion(this.startPromise);
|
|
195
|
+
}
|
|
196
|
+
const sessions = Array.from(this.sessions.values());
|
|
197
|
+
this.sessions.clear();
|
|
198
|
+
for (const session of sessions)
|
|
199
|
+
session.closing = true;
|
|
200
|
+
const sessionCleanup = Promise.allSettled(sessions.map(session => this.closeSession(session)));
|
|
201
|
+
await this.waitForCompletion(sessionCleanup);
|
|
202
|
+
for (const activeRequest of this.activeRequests) {
|
|
203
|
+
activeRequest.request.destroy();
|
|
204
|
+
activeRequest.response.destroy();
|
|
205
|
+
}
|
|
206
|
+
for (const socket of this.sockets)
|
|
207
|
+
socket.destroy();
|
|
208
|
+
await this.closeHttpServer();
|
|
209
|
+
this.lifecycle = 'stopped';
|
|
210
|
+
}
|
|
211
|
+
async waitForCompletion(promise) {
|
|
212
|
+
let timer;
|
|
213
|
+
try {
|
|
214
|
+
await Promise.race([
|
|
215
|
+
promise,
|
|
216
|
+
new Promise(resolve => {
|
|
217
|
+
timer = setTimeout(resolve, this.shutdownTimeoutMs);
|
|
218
|
+
timer.unref();
|
|
219
|
+
})
|
|
220
|
+
]);
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
logger_1.logger.error('MCP HTTP cleanup failed:', error);
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
if (timer)
|
|
227
|
+
clearTimeout(timer);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
async closeHttpServer() {
|
|
231
|
+
if (!this.httpServer.listening)
|
|
232
|
+
return;
|
|
233
|
+
const closePromise = new Promise((resolve, reject) => {
|
|
234
|
+
this.httpServer.close(error => {
|
|
235
|
+
if (error && error.code !== 'ERR_SERVER_NOT_RUNNING') {
|
|
236
|
+
reject(error);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
resolve();
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
this.httpServer.closeAllConnections();
|
|
243
|
+
this.httpServer.closeIdleConnections();
|
|
244
|
+
for (const socket of this.sockets)
|
|
245
|
+
socket.destroy();
|
|
246
|
+
await this.waitForCompletion(closePromise);
|
|
247
|
+
this.httpServer.closeAllConnections();
|
|
248
|
+
this.httpServer.closeIdleConnections();
|
|
249
|
+
for (const socket of this.sockets)
|
|
250
|
+
socket.destroy();
|
|
251
|
+
}
|
|
252
|
+
async handleRequest(request, response) {
|
|
253
|
+
this.setCorsHeaders(request, response);
|
|
254
|
+
const requestPath = new URL(request.url ?? '/', 'http://localhost').pathname;
|
|
255
|
+
if (requestPath === '/health' && request.method === 'GET') {
|
|
256
|
+
this.sendJson(response, 200, { status: 'ok' });
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (requestPath !== MCP_PATH) {
|
|
260
|
+
request.resume();
|
|
261
|
+
this.sendError(response, 404, -32601, 'Not found');
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (!this.isAllowedOrigin(request)) {
|
|
265
|
+
request.resume();
|
|
266
|
+
this.sendError(response, 403, -32000, 'Origin is not allowed');
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (request.method === 'OPTIONS') {
|
|
270
|
+
request.resume();
|
|
271
|
+
response.statusCode = 204;
|
|
272
|
+
response.setHeader('Allow', 'GET, POST, DELETE, OPTIONS');
|
|
273
|
+
response.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
|
|
274
|
+
response.setHeader('Access-Control-Allow-Headers', 'Authorization, Content-Type, Mcp-Session-Id, MCP-Protocol-Version, Last-Event-ID');
|
|
275
|
+
response.end();
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (!['GET', 'POST', 'DELETE'].includes(request.method ?? '')) {
|
|
279
|
+
request.resume();
|
|
280
|
+
response.setHeader('Allow', 'GET, POST, DELETE, OPTIONS');
|
|
281
|
+
this.sendError(response, 405, -32601, 'Method not allowed');
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (!this.isAuthorized(request)) {
|
|
285
|
+
request.resume();
|
|
286
|
+
this.sendError(response, 401, -32000, 'Unauthorized', { 'WWW-Authenticate': 'Bearer' });
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
response.setHeader('Cache-Control', 'no-cache, no-transform');
|
|
290
|
+
response.setHeader('X-Accel-Buffering', 'no');
|
|
291
|
+
try {
|
|
292
|
+
if (request.method === 'POST') {
|
|
293
|
+
await this.handlePost(request, response);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
await this.handleSessionRequest(request, response);
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
if (response.headersSent) {
|
|
300
|
+
logger_1.logger.error('MCP HTTP request failed after response headers were sent');
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (error instanceof HttpRequestError) {
|
|
304
|
+
this.sendError(response, error.statusCode, error.errorCode, error.message, error.headers);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
logger_1.logger.error('MCP HTTP request failed');
|
|
308
|
+
this.sendError(response, 500, -32603, 'Internal server error');
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
async handlePost(request, response) {
|
|
312
|
+
let body;
|
|
313
|
+
try {
|
|
314
|
+
body = await this.readJsonBody(request);
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
request.resume();
|
|
318
|
+
throw error;
|
|
319
|
+
}
|
|
320
|
+
if (Array.isArray(body)) {
|
|
321
|
+
this.sendError(response, 400, -32600, 'Batch JSON-RPC requests are not supported');
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const sessionId = singleHeader(request.headers['mcp-session-id']);
|
|
325
|
+
if (sessionId) {
|
|
326
|
+
const session = this.sessions.get(sessionId);
|
|
327
|
+
if (!session) {
|
|
328
|
+
this.sendError(response, 404, -32001, 'Session not found');
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
await this.handleSessionTransport(session, request, response, body);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (!(0, types_js_1.isInitializeRequest)(body)) {
|
|
335
|
+
this.sendError(response, 400, -32000, 'A valid MCP initialize request is required');
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const session = await this.createSession();
|
|
339
|
+
try {
|
|
340
|
+
await this.handleSessionTransport(session, request, response, body);
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
await this.closeSession(session);
|
|
344
|
+
throw error;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async handleSessionRequest(request, response) {
|
|
348
|
+
const sessionId = singleHeader(request.headers['mcp-session-id']);
|
|
349
|
+
if (!sessionId) {
|
|
350
|
+
this.sendError(response, 400, -32000, 'Mcp-Session-Id header is required');
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const session = this.sessions.get(sessionId);
|
|
354
|
+
if (!session) {
|
|
355
|
+
this.sendError(response, 404, -32001, 'Session not found');
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
await this.handleSessionTransport(session, request, response);
|
|
359
|
+
}
|
|
360
|
+
async createSession() {
|
|
361
|
+
if (this.sessions.size + this.pendingSessions >= this.maxSessions) {
|
|
362
|
+
throw new HttpRequestError(429, -32000, 'Maximum MCP session limit reached', { 'Retry-After': '1' });
|
|
363
|
+
}
|
|
364
|
+
this.pendingSessions += 1;
|
|
365
|
+
let reservationReleased = false;
|
|
366
|
+
const releaseReservation = () => {
|
|
367
|
+
if (reservationReleased)
|
|
368
|
+
return;
|
|
369
|
+
reservationReleased = true;
|
|
370
|
+
this.pendingSessions -= 1;
|
|
371
|
+
};
|
|
372
|
+
let server;
|
|
373
|
+
let session;
|
|
374
|
+
try {
|
|
375
|
+
server = this.createMcpServer();
|
|
376
|
+
const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
|
|
377
|
+
sessionIdGenerator: () => (0, node_crypto_1.randomUUID)(),
|
|
378
|
+
enableJsonResponse: this.enableJsonResponse,
|
|
379
|
+
allowedOrigins: this.allowedOrigins.length > 0 ? this.allowedOrigins : undefined,
|
|
380
|
+
enableDnsRebindingProtection: this.allowedOrigins.length > 0,
|
|
381
|
+
onsessioninitialized: sessionId => {
|
|
382
|
+
releaseReservation();
|
|
383
|
+
if (session && this.lifecycle === 'running' && !session.closing) {
|
|
384
|
+
this.sessions.set(sessionId, session);
|
|
385
|
+
session.lastActivityAt = Date.now();
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
session = {
|
|
390
|
+
server,
|
|
391
|
+
transport,
|
|
392
|
+
lastActivityAt: Date.now(),
|
|
393
|
+
activeRequests: 0,
|
|
394
|
+
closing: false,
|
|
395
|
+
closeInitiated: false
|
|
396
|
+
};
|
|
397
|
+
transport.onclose = () => {
|
|
398
|
+
releaseReservation();
|
|
399
|
+
const sessionId = transport.sessionId;
|
|
400
|
+
if (sessionId && this.sessions.get(sessionId) === session) {
|
|
401
|
+
this.sessions.delete(sessionId);
|
|
402
|
+
}
|
|
403
|
+
if (!session || session.closeInitiated)
|
|
404
|
+
return;
|
|
405
|
+
const currentSession = session;
|
|
406
|
+
currentSession.closeInitiated = true;
|
|
407
|
+
currentSession.closing = true;
|
|
408
|
+
const shutdownPromise = Promise.resolve()
|
|
409
|
+
.then(() => currentSession.server.shutdown())
|
|
410
|
+
.catch(error => {
|
|
411
|
+
logger_1.logger.error('MCP session shutdown failed:', error);
|
|
412
|
+
});
|
|
413
|
+
currentSession.closePromise = shutdownPromise;
|
|
414
|
+
};
|
|
415
|
+
await server.connectTransport(transport);
|
|
416
|
+
if (this.lifecycle !== 'running') {
|
|
417
|
+
await this.closeSession(session);
|
|
418
|
+
throw new HttpRequestError(503, -32000, 'MCP server is shutting down');
|
|
419
|
+
}
|
|
420
|
+
return session;
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
if (session)
|
|
424
|
+
await this.closeSession(session);
|
|
425
|
+
releaseReservation();
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
async closeSession(session) {
|
|
430
|
+
if (session.closePromise) {
|
|
431
|
+
await session.closePromise;
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
session.closing = true;
|
|
435
|
+
session.closeInitiated = true;
|
|
436
|
+
const sessionId = session.transport.sessionId;
|
|
437
|
+
if (sessionId && this.sessions.get(sessionId) === session) {
|
|
438
|
+
this.sessions.delete(sessionId);
|
|
439
|
+
}
|
|
440
|
+
const closePromise = (async () => {
|
|
441
|
+
try {
|
|
442
|
+
await session.transport.close();
|
|
443
|
+
}
|
|
444
|
+
finally {
|
|
445
|
+
await session.server.shutdown();
|
|
446
|
+
}
|
|
447
|
+
})();
|
|
448
|
+
session.closePromise = closePromise;
|
|
449
|
+
await closePromise;
|
|
450
|
+
}
|
|
451
|
+
async readJsonBody(request) {
|
|
452
|
+
const contentLength = singleHeader(request.headers['content-length']);
|
|
453
|
+
if (contentLength !== undefined) {
|
|
454
|
+
const declaredLength = Number(contentLength);
|
|
455
|
+
if (!Number.isSafeInteger(declaredLength) || declaredLength < 0) {
|
|
456
|
+
throw new HttpRequestError(400, -32700, 'Invalid Content-Length');
|
|
457
|
+
}
|
|
458
|
+
if (declaredLength > MAX_BODY_BYTES) {
|
|
459
|
+
throw new HttpRequestError(413, -32000, 'Request body is too large');
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
const bodyPromise = this.readBodyContent(request, contentLength);
|
|
463
|
+
let timer;
|
|
464
|
+
try {
|
|
465
|
+
return await Promise.race([
|
|
466
|
+
bodyPromise,
|
|
467
|
+
new Promise((_, reject) => {
|
|
468
|
+
timer = setTimeout(() => {
|
|
469
|
+
request.destroy();
|
|
470
|
+
reject(new HttpRequestError(408, -32000, 'Request body timed out'));
|
|
471
|
+
}, this.requestBodyTimeoutMs);
|
|
472
|
+
timer.unref();
|
|
473
|
+
})
|
|
474
|
+
]);
|
|
475
|
+
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
void bodyPromise.catch(() => undefined);
|
|
478
|
+
throw error;
|
|
479
|
+
}
|
|
480
|
+
finally {
|
|
481
|
+
if (timer)
|
|
482
|
+
clearTimeout(timer);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
async readBodyContent(request, contentLength) {
|
|
486
|
+
const declaredLength = contentLength === undefined ? 0 : Number(contentLength);
|
|
487
|
+
let reservedBytes = 0;
|
|
488
|
+
const reserveBytes = (bytes) => {
|
|
489
|
+
if (bytes <= 0)
|
|
490
|
+
return;
|
|
491
|
+
if (this.activeBodyBytes + bytes > this.maxConcurrentBodyBytes) {
|
|
492
|
+
throw new HttpRequestError(429, -32000, 'Too many concurrent request bodies', { 'Retry-After': '1' });
|
|
493
|
+
}
|
|
494
|
+
this.activeBodyBytes += bytes;
|
|
495
|
+
reservedBytes += bytes;
|
|
496
|
+
};
|
|
497
|
+
try {
|
|
498
|
+
reserveBytes(declaredLength);
|
|
499
|
+
const chunks = [];
|
|
500
|
+
let totalBytes = 0;
|
|
501
|
+
for await (const chunk of request) {
|
|
502
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
503
|
+
totalBytes += buffer.length;
|
|
504
|
+
if (totalBytes > MAX_BODY_BYTES) {
|
|
505
|
+
throw new HttpRequestError(413, -32000, 'Request body is too large');
|
|
506
|
+
}
|
|
507
|
+
reserveBytes(totalBytes - reservedBytes);
|
|
508
|
+
chunks.push(buffer);
|
|
509
|
+
}
|
|
510
|
+
if (chunks.length === 0) {
|
|
511
|
+
throw new HttpRequestError(400, -32700, 'Request body is required');
|
|
512
|
+
}
|
|
513
|
+
try {
|
|
514
|
+
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
515
|
+
}
|
|
516
|
+
catch {
|
|
517
|
+
throw new HttpRequestError(400, -32700, 'Parse error');
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
finally {
|
|
521
|
+
this.activeBodyBytes -= reservedBytes;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
async handleSessionTransport(session, request, response, body) {
|
|
525
|
+
if (session.closing || this.lifecycle !== 'running') {
|
|
526
|
+
throw new HttpRequestError(503, -32000, 'MCP session is closing');
|
|
527
|
+
}
|
|
528
|
+
session.activeRequests += 1;
|
|
529
|
+
session.lastActivityAt = Date.now();
|
|
530
|
+
try {
|
|
531
|
+
await session.transport.handleRequest(request, response, body);
|
|
532
|
+
}
|
|
533
|
+
finally {
|
|
534
|
+
session.activeRequests -= 1;
|
|
535
|
+
session.lastActivityAt = Date.now();
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
startSessionSweep() {
|
|
539
|
+
const intervalMs = Math.min(this.sessionIdleTimeoutMs, 60000);
|
|
540
|
+
this.sessionSweep = setInterval(() => {
|
|
541
|
+
const cutoff = Date.now() - this.sessionIdleTimeoutMs;
|
|
542
|
+
for (const session of this.sessions.values()) {
|
|
543
|
+
if (!session.closing && session.activeRequests === 0 && session.lastActivityAt <= cutoff) {
|
|
544
|
+
void this.closeSession(session).catch(error => {
|
|
545
|
+
logger_1.logger.error('Idle MCP session cleanup failed:', error);
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}, intervalMs);
|
|
550
|
+
this.sessionSweep.unref();
|
|
551
|
+
}
|
|
552
|
+
stopSessionSweep() {
|
|
553
|
+
if (!this.sessionSweep)
|
|
554
|
+
return;
|
|
555
|
+
clearInterval(this.sessionSweep);
|
|
556
|
+
this.sessionSweep = undefined;
|
|
557
|
+
}
|
|
558
|
+
isAuthorized(request) {
|
|
559
|
+
if (!this.authToken)
|
|
560
|
+
return true;
|
|
561
|
+
return singleHeader(request.headers.authorization) === `Bearer ${this.authToken}`;
|
|
562
|
+
}
|
|
563
|
+
isAllowedOrigin(request) {
|
|
564
|
+
const origin = singleHeader(request.headers.origin);
|
|
565
|
+
return !origin || this.allowedOrigins.includes(origin);
|
|
566
|
+
}
|
|
567
|
+
setCorsHeaders(request, response) {
|
|
568
|
+
const origin = singleHeader(request.headers.origin);
|
|
569
|
+
if (origin && this.allowedOrigins.includes(origin)) {
|
|
570
|
+
response.setHeader('Access-Control-Allow-Origin', origin);
|
|
571
|
+
response.setHeader('Vary', 'Origin');
|
|
572
|
+
}
|
|
573
|
+
response.setHeader('Access-Control-Expose-Headers', 'Mcp-Session-Id');
|
|
574
|
+
}
|
|
575
|
+
sendJson(response, statusCode, body) {
|
|
576
|
+
response.statusCode = statusCode;
|
|
577
|
+
response.setHeader('Content-Type', 'application/json');
|
|
578
|
+
response.end(JSON.stringify(body));
|
|
579
|
+
}
|
|
580
|
+
sendError(response, statusCode, errorCode, message, headers = {}) {
|
|
581
|
+
if (response.headersSent)
|
|
582
|
+
return;
|
|
583
|
+
response.statusCode = statusCode;
|
|
584
|
+
response.setHeader('Content-Type', 'application/json');
|
|
585
|
+
for (const [key, value] of Object.entries(headers))
|
|
586
|
+
response.setHeader(key, value);
|
|
587
|
+
response.end(JSON.stringify({
|
|
588
|
+
jsonrpc: '2.0',
|
|
589
|
+
error: { code: errorCode, message },
|
|
590
|
+
id: null
|
|
591
|
+
}));
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
exports.LangflowMCPHttpServer = LangflowMCPHttpServer;
|
|
595
|
+
//# sourceMappingURL=http.js.map
|