@aiwerk/mcp-bridge 1.1.6 → 1.1.8
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/dist/src/index.d.ts +1 -1
- package/dist/src/standalone-server.d.ts +2 -0
- package/dist/src/standalone-server.js +26 -0
- package/dist/src/transport-base.d.ts +2 -2
- package/dist/src/transport-base.js +12 -9
- package/dist/src/transport-streamable-http.js +26 -9
- package/dist/src/types.d.ts +12 -0
- package/dist/src/update-checker.js +6 -5
- package/package.json +1 -1
package/dist/src/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export type { RouterToolHint, RouterServerStatus, RouterDispatchResponse, Router
|
|
|
7
7
|
export { convertJsonSchemaToTypeBox, createToolParameters, setTypeBoxLoader, setSchemaLogger } from "./schema-convert.js";
|
|
8
8
|
export { initializeProtocol, fetchToolsList, PACKAGE_VERSION } from "./protocol.js";
|
|
9
9
|
export { loadConfig, parseEnvFile, initConfigDir, getConfigDir } from "./config.js";
|
|
10
|
-
export type { Logger, McpServerConfig, McpClientConfig, McpTool, McpRequest, McpCallRequest, McpResponse, McpTransport, McpServerConnection, BridgeConfig, } from "./types.js";
|
|
10
|
+
export type { Logger, McpServerConfig, McpClientConfig, McpTool, McpRequest, McpCallRequest, McpResponse, JsonRpcMessage, McpTransport, McpServerConnection, BridgeConfig, } from "./types.js";
|
|
11
11
|
export { nextRequestId } from "./types.js";
|
|
12
12
|
export { pickRegisteredToolName } from "./tool-naming.js";
|
|
13
13
|
export { StandaloneServer } from "./standalone-server.js";
|
|
@@ -22,8 +22,10 @@ export declare class StandaloneServer {
|
|
|
22
22
|
private handleInitialize;
|
|
23
23
|
private handleToolsList;
|
|
24
24
|
private handleToolsCall;
|
|
25
|
+
private discoveryPromise?;
|
|
25
26
|
/** Connect to all backend servers and discover their tools (direct mode). */
|
|
26
27
|
private discoverDirectTools;
|
|
28
|
+
private _doDiscovery;
|
|
27
29
|
private createTransport;
|
|
28
30
|
/** Graceful shutdown: disconnect all backend servers. */
|
|
29
31
|
shutdown(): Promise<void>;
|
|
@@ -89,9 +89,17 @@ export class StandaloneServer {
|
|
|
89
89
|
switch (request.method) {
|
|
90
90
|
case "initialize":
|
|
91
91
|
return this.handleInitialize(id);
|
|
92
|
+
case "notifications/initialized":
|
|
93
|
+
return { jsonrpc: "2.0", id, result: {} };
|
|
92
94
|
case "tools/list":
|
|
95
|
+
if (!this.initialized) {
|
|
96
|
+
return { jsonrpc: "2.0", id, error: { code: -32002, message: "Server not initialized. Call 'initialize' first." } };
|
|
97
|
+
}
|
|
93
98
|
return this.handleToolsList(id);
|
|
94
99
|
case "tools/call":
|
|
100
|
+
if (!this.initialized) {
|
|
101
|
+
return { jsonrpc: "2.0", id, error: { code: -32002, message: "Server not initialized. Call 'initialize' first." } };
|
|
102
|
+
}
|
|
95
103
|
return this.handleToolsCall(id, request.params);
|
|
96
104
|
case "ping":
|
|
97
105
|
return { jsonrpc: "2.0", id, result: {} };
|
|
@@ -245,10 +253,28 @@ export class StandaloneServer {
|
|
|
245
253
|
};
|
|
246
254
|
}
|
|
247
255
|
}
|
|
256
|
+
discoveryPromise;
|
|
248
257
|
/** Connect to all backend servers and discover their tools (direct mode). */
|
|
249
258
|
async discoverDirectTools(force = false) {
|
|
250
259
|
if (this.directTools.length > 0 && !force)
|
|
251
260
|
return; // Already discovered
|
|
261
|
+
if (this.discoveryPromise && !force) {
|
|
262
|
+
await this.discoveryPromise;
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const promise = this._doDiscovery(force);
|
|
266
|
+
this.discoveryPromise = promise;
|
|
267
|
+
try {
|
|
268
|
+
await promise;
|
|
269
|
+
}
|
|
270
|
+
finally {
|
|
271
|
+
// Only clear if we're still the active promise (a force call may have replaced us)
|
|
272
|
+
if (this.discoveryPromise === promise) {
|
|
273
|
+
this.discoveryPromise = undefined;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
async _doDiscovery(force) {
|
|
252
278
|
if (force) {
|
|
253
279
|
this.directTools = [];
|
|
254
280
|
for (const [, conn] of this.directConnections) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { McpTransport, McpRequest, McpResponse, McpServerConfig, McpClientConfig, Logger } from "./types.js";
|
|
1
|
+
import { McpTransport, McpRequest, McpResponse, McpServerConfig, McpClientConfig, Logger, JsonRpcMessage } from "./types.js";
|
|
2
2
|
export type PendingRequest = {
|
|
3
3
|
resolve: (value: McpResponse) => void;
|
|
4
4
|
reject: (reason: Error) => void;
|
|
@@ -35,7 +35,7 @@ export declare abstract class BaseTransport implements McpTransport {
|
|
|
35
35
|
* - Other notifications -> debug log
|
|
36
36
|
* - Responses with id -> resolve/reject matching pending request
|
|
37
37
|
*/
|
|
38
|
-
protected handleMessage(message:
|
|
38
|
+
protected handleMessage(message: JsonRpcMessage): void;
|
|
39
39
|
/** Reject and clear all pending requests with the given reason. */
|
|
40
40
|
protected rejectAllPending(reason: string): void;
|
|
41
41
|
/**
|
|
@@ -44,15 +44,18 @@ export class BaseTransport {
|
|
|
44
44
|
this.logger.debug(`[mcp-bridge] Unhandled ${this.transportName} notification: ${message.method}`);
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
|
-
if (hasId
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
47
|
+
if (hasId) {
|
|
48
|
+
const id = message.id;
|
|
49
|
+
if (this.pendingRequests.has(id)) {
|
|
50
|
+
const pending = this.pendingRequests.get(id);
|
|
51
|
+
clearTimeout(pending.timeout);
|
|
52
|
+
this.pendingRequests.delete(id);
|
|
53
|
+
if (message.error) {
|
|
54
|
+
pending.reject(new Error(message.error.message || "MCP error"));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
pending.resolve({ jsonrpc: "2.0", id, result: message.result });
|
|
58
|
+
}
|
|
56
59
|
}
|
|
57
60
|
}
|
|
58
61
|
}
|
|
@@ -57,17 +57,34 @@ export class StreamableHttpTransport extends BaseTransport {
|
|
|
57
57
|
let jsonResponse;
|
|
58
58
|
if (contentType.includes("text/event-stream")) {
|
|
59
59
|
const text = await response.text();
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
const lines = text.split('\n');
|
|
61
|
+
// SSE event boundary parsing: collect data lines, dispatch on empty line
|
|
62
|
+
let dataBuffer = [];
|
|
63
|
+
const dispatch = () => {
|
|
64
|
+
if (dataBuffer.length === 0)
|
|
65
|
+
return;
|
|
66
|
+
const data = dataBuffer.join("\n");
|
|
67
|
+
dataBuffer = [];
|
|
67
68
|
try {
|
|
68
|
-
this.handleMessage(JSON.parse(
|
|
69
|
+
this.handleMessage(JSON.parse(data));
|
|
70
|
+
}
|
|
71
|
+
catch { /* skip malformed events */ }
|
|
72
|
+
};
|
|
73
|
+
let hasData = false;
|
|
74
|
+
for (const line of lines) {
|
|
75
|
+
const trimmed = line.trim();
|
|
76
|
+
if (trimmed.startsWith("data:")) {
|
|
77
|
+
dataBuffer.push(trimmed.substring(5).trimStart());
|
|
78
|
+
hasData = true;
|
|
69
79
|
}
|
|
70
|
-
|
|
80
|
+
else if (trimmed === "" && dataBuffer.length > 0) {
|
|
81
|
+
dispatch();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Dispatch any trailing data (server may omit final empty line)
|
|
85
|
+
dispatch();
|
|
86
|
+
if (!hasData) {
|
|
87
|
+
throw new Error("No data lines in SSE response");
|
|
71
88
|
}
|
|
72
89
|
}
|
|
73
90
|
else {
|
package/dist/src/types.d.ts
CHANGED
|
@@ -30,6 +30,18 @@ export interface McpTool {
|
|
|
30
30
|
description: string;
|
|
31
31
|
inputSchema: any;
|
|
32
32
|
}
|
|
33
|
+
/** Incoming JSON-RPC message (response or notification). */
|
|
34
|
+
export interface JsonRpcMessage {
|
|
35
|
+
jsonrpc: "2.0";
|
|
36
|
+
id?: number | null;
|
|
37
|
+
method?: string;
|
|
38
|
+
result?: any;
|
|
39
|
+
error?: {
|
|
40
|
+
code: number;
|
|
41
|
+
message: string;
|
|
42
|
+
data?: unknown;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
33
45
|
/** MCP JSON-RPC request. id is required for requests (omit only for notifications). */
|
|
34
46
|
export interface McpRequest {
|
|
35
47
|
jsonrpc: "2.0";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFileSync, execFile } from "child_process";
|
|
2
2
|
import { PACKAGE_VERSION } from "./protocol.js";
|
|
3
3
|
const PACKAGE_NAME = "@aiwerk/mcp-bridge";
|
|
4
4
|
let cachedUpdateInfo = null;
|
|
@@ -66,7 +66,8 @@ export async function runUpdate(logger) {
|
|
|
66
66
|
}
|
|
67
67
|
logger.info(`[mcp-bridge] Running update: ${info.updateCommand}`);
|
|
68
68
|
try {
|
|
69
|
-
const
|
|
69
|
+
const parts = info.updateCommand.split(/\s+/);
|
|
70
|
+
const output = await execFileAsync(parts[0], parts.slice(1), 60_000);
|
|
70
71
|
// Invalidate cache so next check re-fetches
|
|
71
72
|
cachedUpdateInfo = null;
|
|
72
73
|
noticeDelivered = false;
|
|
@@ -100,16 +101,16 @@ function npmViewVersion(_logger) {
|
|
|
100
101
|
}
|
|
101
102
|
function npmViewVersionSync(_logger) {
|
|
102
103
|
try {
|
|
103
|
-
return
|
|
104
|
+
return execFileSync("npm", ["view", PACKAGE_NAME, "version"], { encoding: "utf-8", timeout: 10_000 }).trim();
|
|
104
105
|
}
|
|
105
106
|
catch {
|
|
106
107
|
return "unknown";
|
|
107
108
|
}
|
|
108
109
|
}
|
|
109
|
-
function
|
|
110
|
+
function execFileAsync(file, args, timeoutMs) {
|
|
110
111
|
return new Promise((resolve, reject) => {
|
|
111
112
|
const timeout = setTimeout(() => reject(new Error(`Command timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
112
|
-
|
|
113
|
+
execFile(file, args, { encoding: "utf-8", timeout: timeoutMs }, (err, stdout, stderr) => {
|
|
113
114
|
clearTimeout(timeout);
|
|
114
115
|
if (err)
|
|
115
116
|
return reject(new Error(`${err.message}\n${stderr ?? ""}`));
|