@aiwerk/mcp-bridge 1.1.0 → 1.1.1
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/config.js +7 -1
- package/dist/src/mcp-router.d.ts +5 -5
- package/dist/src/protocol.js +1 -1
- package/dist/src/smart-filter.js +6 -4
- package/dist/src/standalone-server.js +6 -3
- package/dist/src/transport-base.d.ts +5 -5
- package/dist/src/transport-sse.js +11 -19
- package/dist/src/transport-streamable-http.js +15 -1
- package/dist/src/types.d.ts +5 -0
- package/dist/src/update-checker.js +2 -2
- package/package.json +1 -1
package/dist/src/config.js
CHANGED
|
@@ -100,7 +100,13 @@ export function loadConfig(options = {}) {
|
|
|
100
100
|
}
|
|
101
101
|
/** Get the default config directory path. */
|
|
102
102
|
export function getConfigDir(configPath) {
|
|
103
|
-
|
|
103
|
+
if (!configPath)
|
|
104
|
+
return DEFAULT_CONFIG_DIR;
|
|
105
|
+
// If path ends with separator or has no extension, treat as directory
|
|
106
|
+
if (configPath.endsWith("/") || configPath.endsWith("\\") || !configPath.includes(".")) {
|
|
107
|
+
return configPath;
|
|
108
|
+
}
|
|
109
|
+
return join(configPath, "..");
|
|
104
110
|
}
|
|
105
111
|
/** Initialize the config directory with template files. */
|
|
106
112
|
export function initConfigDir(logger) {
|
package/dist/src/mcp-router.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { McpClientConfig, McpServerConfig, McpTransport } from "./types.js";
|
|
1
|
+
import { McpClientConfig, McpServerConfig, McpTransport, Logger } from "./types.js";
|
|
2
2
|
type RouterErrorCode = "unknown_server" | "unknown_tool" | "connection_failed" | "mcp_error" | "invalid_params";
|
|
3
3
|
export interface RouterToolHint {
|
|
4
4
|
name: string;
|
|
@@ -36,9 +36,9 @@ export type RouterDispatchResponse = {
|
|
|
36
36
|
code?: number;
|
|
37
37
|
};
|
|
38
38
|
export interface RouterTransportRefs {
|
|
39
|
-
sse: new (config: McpServerConfig, clientConfig: McpClientConfig, logger:
|
|
40
|
-
stdio: new (config: McpServerConfig, clientConfig: McpClientConfig, logger:
|
|
41
|
-
streamableHttp: new (config: McpServerConfig, clientConfig: McpClientConfig, logger:
|
|
39
|
+
sse: new (config: McpServerConfig, clientConfig: McpClientConfig, logger: Logger, onReconnected?: () => Promise<void>) => McpTransport;
|
|
40
|
+
stdio: new (config: McpServerConfig, clientConfig: McpClientConfig, logger: Logger, onReconnected?: () => Promise<void>) => McpTransport;
|
|
41
|
+
streamableHttp: new (config: McpServerConfig, clientConfig: McpClientConfig, logger: Logger, onReconnected?: () => Promise<void>) => McpTransport;
|
|
42
42
|
}
|
|
43
43
|
export declare class McpRouter {
|
|
44
44
|
private readonly servers;
|
|
@@ -48,7 +48,7 @@ export declare class McpRouter {
|
|
|
48
48
|
private readonly idleTimeoutMs;
|
|
49
49
|
private readonly maxConcurrent;
|
|
50
50
|
private readonly states;
|
|
51
|
-
constructor(servers: Record<string, McpServerConfig>, clientConfig: McpClientConfig, logger:
|
|
51
|
+
constructor(servers: Record<string, McpServerConfig>, clientConfig: McpClientConfig, logger: Logger, transportRefs?: Partial<RouterTransportRefs>);
|
|
52
52
|
static generateDescription(servers: Record<string, McpServerConfig>): string;
|
|
53
53
|
dispatch(server?: string, action?: string, tool?: string, params?: any): Promise<RouterDispatchResponse>;
|
|
54
54
|
getToolList(server: string): Promise<RouterToolHint[]>;
|
package/dist/src/protocol.js
CHANGED
|
@@ -5,7 +5,7 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
5
5
|
const __dirname = dirname(__filename);
|
|
6
6
|
export const PACKAGE_VERSION = (() => {
|
|
7
7
|
try {
|
|
8
|
-
return JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8")).version;
|
|
8
|
+
return JSON.parse(readFileSync(join(__dirname, "..", "..", "package.json"), "utf-8")).version;
|
|
9
9
|
}
|
|
10
10
|
catch {
|
|
11
11
|
return "0.0.0";
|
package/dist/src/smart-filter.js
CHANGED
|
@@ -178,7 +178,9 @@ export class SmartFilter {
|
|
|
178
178
|
const allServerWords = [...descriptionWords, ...keywordWords];
|
|
179
179
|
// Calculate overlaps
|
|
180
180
|
const descMatches = this.countOverlap(queryWords, descriptionWords);
|
|
181
|
-
|
|
181
|
+
// Count keyword matches that are NOT already counted in description
|
|
182
|
+
const keywordOnlyWords = keywordWords.filter(kw => !descriptionWords.includes(kw));
|
|
183
|
+
const keywordOnlyMatches = this.countOverlap(queryWords, keywordOnlyWords);
|
|
182
184
|
// Add basic synonym matching for common terms
|
|
183
185
|
let semanticMatches = 0;
|
|
184
186
|
for (const queryWord of queryWords) {
|
|
@@ -194,7 +196,7 @@ export class SmartFilter {
|
|
|
194
196
|
}
|
|
195
197
|
}
|
|
196
198
|
// Weighted scoring: description 1.0x, keywords 0.7x, semantic 0.5x, partial matches 0.3x
|
|
197
|
-
const score = (descMatches * 1.0 +
|
|
199
|
+
const score = (descMatches * 1.0 + keywordOnlyMatches * 0.7 + semanticMatches * 0.5 + partialMatches) / queryWords.length;
|
|
198
200
|
return score;
|
|
199
201
|
}
|
|
200
202
|
getSemanticScore(queryWord, serverWords) {
|
|
@@ -391,8 +393,8 @@ export const DEFAULTS = {
|
|
|
391
393
|
topServers: 5,
|
|
392
394
|
hardCap: 8,
|
|
393
395
|
topTools: 10,
|
|
394
|
-
serverThreshold: 0.
|
|
395
|
-
toolThreshold: 0.
|
|
396
|
+
serverThreshold: 0.01,
|
|
397
|
+
toolThreshold: 0.05,
|
|
396
398
|
fallback: "keyword",
|
|
397
399
|
alwaysInclude: [],
|
|
398
400
|
timeoutMs: 500,
|
|
@@ -246,9 +246,11 @@ export class StandaloneServer {
|
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
248
|
/** Connect to all backend servers and discover their tools (direct mode). */
|
|
249
|
-
async discoverDirectTools() {
|
|
250
|
-
if (this.directTools.length > 0)
|
|
249
|
+
async discoverDirectTools(force = false) {
|
|
250
|
+
if (this.directTools.length > 0 && !force)
|
|
251
251
|
return; // Already discovered
|
|
252
|
+
if (force)
|
|
253
|
+
this.directTools = [];
|
|
252
254
|
const globalNames = new Set();
|
|
253
255
|
for (const [serverName, serverConfig] of Object.entries(this.config.servers)) {
|
|
254
256
|
try {
|
|
@@ -279,7 +281,8 @@ export class StandaloneServer {
|
|
|
279
281
|
}
|
|
280
282
|
createTransport(serverName, serverConfig) {
|
|
281
283
|
const onReconnected = async () => {
|
|
282
|
-
this.logger.info(`[mcp-bridge] ${serverName} reconnected`);
|
|
284
|
+
this.logger.info(`[mcp-bridge] ${serverName} reconnected, refreshing tools`);
|
|
285
|
+
await this.discoverDirectTools(true);
|
|
283
286
|
};
|
|
284
287
|
switch (serverConfig.transport) {
|
|
285
288
|
case "sse":
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { McpTransport, McpRequest, McpResponse, McpServerConfig } from "./types.js";
|
|
1
|
+
import { McpTransport, McpRequest, McpResponse, McpServerConfig, McpClientConfig, Logger } from "./types.js";
|
|
2
2
|
export type PendingRequest = {
|
|
3
3
|
resolve: Function;
|
|
4
4
|
reject: Function;
|
|
@@ -14,14 +14,14 @@ export type PendingRequest = {
|
|
|
14
14
|
*/
|
|
15
15
|
export declare abstract class BaseTransport implements McpTransport {
|
|
16
16
|
protected config: McpServerConfig;
|
|
17
|
-
protected clientConfig:
|
|
17
|
+
protected clientConfig: McpClientConfig;
|
|
18
18
|
protected connected: boolean;
|
|
19
19
|
protected pendingRequests: Map<number, PendingRequest>;
|
|
20
|
-
protected logger:
|
|
20
|
+
protected logger: Logger;
|
|
21
21
|
protected reconnectTimer: NodeJS.Timeout | null;
|
|
22
22
|
protected onReconnected?: () => Promise<void>;
|
|
23
23
|
protected backoffDelay: number;
|
|
24
|
-
constructor(config: McpServerConfig, clientConfig:
|
|
24
|
+
constructor(config: McpServerConfig, clientConfig: McpClientConfig, logger: Logger, onReconnected?: () => Promise<void>);
|
|
25
25
|
abstract connect(): Promise<void>;
|
|
26
26
|
abstract disconnect(): Promise<void>;
|
|
27
27
|
abstract sendRequest(request: McpRequest): Promise<McpResponse>;
|
|
@@ -73,4 +73,4 @@ export declare function resolveArgs(args: string[], extraEnv?: Record<string, st
|
|
|
73
73
|
/**
|
|
74
74
|
* Warn if a URL uses non-TLS HTTP to a remote (non-localhost) host.
|
|
75
75
|
*/
|
|
76
|
-
export declare function warnIfNonTlsRemoteUrl(rawUrl: string, logger:
|
|
76
|
+
export declare function warnIfNonTlsRemoteUrl(rawUrl: string, logger: Logger): void;
|
|
@@ -52,7 +52,7 @@ export class SseTransport extends BaseTransport {
|
|
|
52
52
|
const reader = response.body.getReader();
|
|
53
53
|
const decoder = new TextDecoder();
|
|
54
54
|
let buffer = "";
|
|
55
|
-
|
|
55
|
+
const state = { event: "", dataBuffer: this.currentDataBuffer };
|
|
56
56
|
while (true) {
|
|
57
57
|
const { done, value } = await reader.read();
|
|
58
58
|
if (done)
|
|
@@ -61,17 +61,7 @@ export class SseTransport extends BaseTransport {
|
|
|
61
61
|
const lines = buffer.split('\n');
|
|
62
62
|
buffer = lines.pop() || "";
|
|
63
63
|
for (const line of lines) {
|
|
64
|
-
|
|
65
|
-
if (trimmed.startsWith("event: ")) {
|
|
66
|
-
currentEvent = trimmed.substring(7).trim();
|
|
67
|
-
}
|
|
68
|
-
else if (trimmed === "") {
|
|
69
|
-
this.processEventLine(line, currentEvent);
|
|
70
|
-
currentEvent = "";
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
this.processEventLine(line, currentEvent);
|
|
74
|
-
}
|
|
64
|
+
this.processEventLine(line, state);
|
|
75
65
|
}
|
|
76
66
|
}
|
|
77
67
|
}
|
|
@@ -82,20 +72,22 @@ export class SseTransport extends BaseTransport {
|
|
|
82
72
|
this.scheduleReconnect();
|
|
83
73
|
}
|
|
84
74
|
}
|
|
85
|
-
processEventLine(line,
|
|
75
|
+
processEventLine(line, state) {
|
|
86
76
|
const trimmed = line.trim();
|
|
87
|
-
if (trimmed.startsWith("event: "))
|
|
77
|
+
if (trimmed.startsWith("event: ")) {
|
|
78
|
+
state.event = trimmed.substring(7).trim();
|
|
88
79
|
return;
|
|
80
|
+
}
|
|
89
81
|
if (trimmed.startsWith("data: ")) {
|
|
90
|
-
|
|
82
|
+
state.dataBuffer.push(trimmed.substring(6));
|
|
91
83
|
return;
|
|
92
84
|
}
|
|
93
85
|
if (trimmed === "") {
|
|
94
|
-
if (
|
|
86
|
+
if (state.dataBuffer.length === 0)
|
|
95
87
|
return;
|
|
96
|
-
const data =
|
|
97
|
-
|
|
98
|
-
if (
|
|
88
|
+
const data = state.dataBuffer.join("\n");
|
|
89
|
+
state.dataBuffer.length = 0;
|
|
90
|
+
if (state.event === "endpoint") {
|
|
99
91
|
if (data.startsWith("/")) {
|
|
100
92
|
const base = new URL(this.config.url);
|
|
101
93
|
this.endpointUrl = `${base.origin}${data}`;
|
|
@@ -61,7 +61,21 @@ export class StreamableHttpTransport extends BaseTransport {
|
|
|
61
61
|
.filter((line) => line.startsWith('data:'))
|
|
62
62
|
.map((line) => line.substring(5).trim());
|
|
63
63
|
if (dataLines.length > 0) {
|
|
64
|
-
|
|
64
|
+
for (const dl of dataLines) {
|
|
65
|
+
try {
|
|
66
|
+
const parsed = JSON.parse(dl);
|
|
67
|
+
if (parsed.id !== undefined) {
|
|
68
|
+
jsonResponse = parsed;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
this.handleMessage(parsed);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch { /* skip malformed lines */ }
|
|
75
|
+
}
|
|
76
|
+
if (!jsonResponse) {
|
|
77
|
+
jsonResponse = JSON.parse(dataLines[dataLines.length - 1]);
|
|
78
|
+
}
|
|
65
79
|
}
|
|
66
80
|
else {
|
|
67
81
|
throw new Error("No data lines in SSE response");
|
package/dist/src/types.d.ts
CHANGED
|
@@ -30,12 +30,17 @@ export interface McpTool {
|
|
|
30
30
|
description: string;
|
|
31
31
|
inputSchema: any;
|
|
32
32
|
}
|
|
33
|
+
/** MCP JSON-RPC request. id is required for requests (omit only for notifications). */
|
|
33
34
|
export interface McpRequest {
|
|
34
35
|
jsonrpc: "2.0";
|
|
35
36
|
id?: number;
|
|
36
37
|
method: string;
|
|
37
38
|
params?: any;
|
|
38
39
|
}
|
|
40
|
+
/** MCP request that requires a response (id is mandatory). */
|
|
41
|
+
export interface McpCallRequest extends McpRequest {
|
|
42
|
+
id: number;
|
|
43
|
+
}
|
|
39
44
|
export declare function nextRequestId(): number;
|
|
40
45
|
export interface McpResponse {
|
|
41
46
|
jsonrpc: "2.0";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execSync, exec as execCb } from "child_process";
|
|
1
|
+
import { execSync, exec as execCb, 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;
|
|
@@ -87,7 +87,7 @@ export async function runUpdate(logger) {
|
|
|
87
87
|
function npmViewVersion(_logger) {
|
|
88
88
|
return new Promise((resolve, reject) => {
|
|
89
89
|
const timeout = setTimeout(() => reject(new Error("npm view timed out")), 10_000);
|
|
90
|
-
|
|
90
|
+
execFile("npm", ["view", PACKAGE_NAME, "version"], { encoding: "utf-8" }, (err, stdout) => {
|
|
91
91
|
clearTimeout(timeout);
|
|
92
92
|
if (err)
|
|
93
93
|
return reject(err);
|