@aiwerk/mcp-bridge 1.0.0 → 1.0.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/dist/bin/mcp-bridge.d.ts +2 -0
- package/dist/bin/mcp-bridge.js +320 -0
- package/dist/src/config.d.ts +19 -0
- package/dist/src/config.js +145 -0
- package/{src/index.ts → dist/src/index.d.ts} +1 -30
- package/dist/src/index.js +21 -0
- package/dist/src/mcp-router.d.ts +65 -0
- package/dist/src/mcp-router.js +271 -0
- package/dist/src/protocol.d.ts +4 -0
- package/dist/src/protocol.js +58 -0
- package/dist/src/schema-convert.d.ts +11 -0
- package/dist/src/schema-convert.js +150 -0
- package/dist/src/standalone-server.d.ts +30 -0
- package/dist/src/standalone-server.js +312 -0
- package/dist/src/tool-naming.d.ts +3 -0
- package/dist/src/tool-naming.js +38 -0
- package/dist/src/transport-base.d.ts +76 -0
- package/dist/src/transport-base.js +163 -0
- package/dist/src/transport-sse.d.ts +16 -0
- package/dist/src/transport-sse.js +207 -0
- package/dist/src/transport-stdio.d.ts +20 -0
- package/dist/src/transport-stdio.js +281 -0
- package/dist/src/transport-streamable-http.d.ts +11 -0
- package/dist/src/transport-streamable-http.js +164 -0
- package/dist/src/types.d.ts +72 -0
- package/dist/src/types.js +4 -0
- package/dist/src/update-checker.d.ts +25 -0
- package/dist/src/update-checker.js +132 -0
- package/package.json +19 -4
- package/scripts/install-server.ps1 +25 -58
- package/scripts/install-server.sh +37 -90
- package/servers/apify/README.md +6 -6
- package/servers/github/README.md +6 -6
- package/servers/google-maps/README.md +6 -6
- package/servers/hetzner/README.md +6 -6
- package/servers/hostinger/README.md +6 -6
- package/servers/linear/README.md +6 -6
- package/servers/miro/README.md +6 -6
- package/servers/notion/README.md +6 -6
- package/servers/stripe/README.md +6 -6
- package/servers/tavily/README.md +6 -6
- package/servers/todoist/README.md +6 -6
- package/servers/wise/README.md +6 -6
- package/bin/mcp-bridge.js +0 -9
- package/bin/mcp-bridge.ts +0 -335
- package/src/config.ts +0 -168
- package/src/mcp-router.ts +0 -366
- package/src/protocol.ts +0 -69
- package/src/schema-convert.ts +0 -178
- package/src/standalone-server.ts +0 -385
- package/src/tool-naming.ts +0 -51
- package/src/transport-base.ts +0 -199
- package/src/transport-sse.ts +0 -230
- package/src/transport-stdio.ts +0 -312
- package/src/transport-streamable-http.ts +0 -188
- package/src/types.ts +0 -88
- package/src/update-checker.ts +0 -155
- package/tests/collision.test.ts +0 -60
- package/tests/env-resolve.test.ts +0 -68
- package/tests/mcp-router.test.ts +0 -301
- package/tests/schema-convert.test.ts +0 -70
- package/tests/transport-base.test.ts +0 -214
- package/tsconfig.json +0 -15
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export interface Logger {
|
|
2
|
+
info: (...args: unknown[]) => void;
|
|
3
|
+
warn: (...args: unknown[]) => void;
|
|
4
|
+
error: (...args: unknown[]) => void;
|
|
5
|
+
debug: (...args: unknown[]) => void;
|
|
6
|
+
}
|
|
7
|
+
export interface McpServerConfig {
|
|
8
|
+
transport: "sse" | "stdio" | "streamable-http";
|
|
9
|
+
/** Human-readable description for router tool description generation */
|
|
10
|
+
description?: string;
|
|
11
|
+
url?: string;
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
command?: string;
|
|
14
|
+
args?: string[];
|
|
15
|
+
env?: Record<string, string>;
|
|
16
|
+
framing?: "auto" | "lsp" | "newline";
|
|
17
|
+
}
|
|
18
|
+
export interface McpClientConfig {
|
|
19
|
+
servers: Record<string, McpServerConfig>;
|
|
20
|
+
mode?: "direct" | "router";
|
|
21
|
+
toolPrefix?: boolean | "auto";
|
|
22
|
+
reconnectIntervalMs?: number;
|
|
23
|
+
connectionTimeoutMs?: number;
|
|
24
|
+
requestTimeoutMs?: number;
|
|
25
|
+
routerIdleTimeoutMs?: number;
|
|
26
|
+
routerMaxConcurrent?: number;
|
|
27
|
+
}
|
|
28
|
+
export interface McpTool {
|
|
29
|
+
name: string;
|
|
30
|
+
description: string;
|
|
31
|
+
inputSchema: any;
|
|
32
|
+
}
|
|
33
|
+
export interface McpRequest {
|
|
34
|
+
jsonrpc: "2.0";
|
|
35
|
+
id?: number;
|
|
36
|
+
method: string;
|
|
37
|
+
params?: any;
|
|
38
|
+
}
|
|
39
|
+
export declare function nextRequestId(): number;
|
|
40
|
+
export interface McpResponse {
|
|
41
|
+
jsonrpc: "2.0";
|
|
42
|
+
id: number;
|
|
43
|
+
result?: any;
|
|
44
|
+
error?: {
|
|
45
|
+
code: number;
|
|
46
|
+
message: string;
|
|
47
|
+
data?: any;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export interface McpTransport {
|
|
51
|
+
connect(): Promise<void>;
|
|
52
|
+
disconnect(): Promise<void>;
|
|
53
|
+
sendRequest(request: McpRequest): Promise<McpResponse>;
|
|
54
|
+
sendNotification(notification: any): Promise<void>;
|
|
55
|
+
isConnected(): boolean;
|
|
56
|
+
}
|
|
57
|
+
export interface McpServerConnection {
|
|
58
|
+
name: string;
|
|
59
|
+
transport: McpTransport;
|
|
60
|
+
tools: McpTool[];
|
|
61
|
+
isInitialized: boolean;
|
|
62
|
+
registeredToolNames: string[];
|
|
63
|
+
}
|
|
64
|
+
/** Bridge-level config loaded from ~/.mcp-bridge/config.json */
|
|
65
|
+
export interface BridgeConfig extends McpClientConfig {
|
|
66
|
+
http?: {
|
|
67
|
+
auth?: {
|
|
68
|
+
type: "bearer";
|
|
69
|
+
token: string;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Logger } from "./types.js";
|
|
2
|
+
export interface UpdateInfo {
|
|
3
|
+
currentVersion: string;
|
|
4
|
+
latestVersion: string;
|
|
5
|
+
updateAvailable: boolean;
|
|
6
|
+
updateCommand: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Check npm registry for a newer version. Non-blocking, best-effort.
|
|
10
|
+
* Caches result for the lifetime of the process.
|
|
11
|
+
*/
|
|
12
|
+
export declare function checkForUpdate(logger: Logger): Promise<UpdateInfo>;
|
|
13
|
+
/**
|
|
14
|
+
* Build the notice string to inject into the first tool response.
|
|
15
|
+
* Returns empty string if no update or already delivered.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getUpdateNotice(): string;
|
|
18
|
+
/**
|
|
19
|
+
* Reset the notice flag (for testing).
|
|
20
|
+
*/
|
|
21
|
+
export declare function resetNoticeFlag(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Execute the actual npm update. Returns a result message.
|
|
24
|
+
*/
|
|
25
|
+
export declare function runUpdate(logger: Logger): Promise<string>;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { execSync, exec as execCb } from "child_process";
|
|
2
|
+
import { PACKAGE_VERSION } from "./protocol.js";
|
|
3
|
+
const PACKAGE_NAME = "@aiwerk/mcp-bridge";
|
|
4
|
+
let cachedUpdateInfo = null;
|
|
5
|
+
let noticeDelivered = false;
|
|
6
|
+
/**
|
|
7
|
+
* Check npm registry for a newer version. Non-blocking, best-effort.
|
|
8
|
+
* Caches result for the lifetime of the process.
|
|
9
|
+
*/
|
|
10
|
+
export async function checkForUpdate(logger) {
|
|
11
|
+
if (cachedUpdateInfo)
|
|
12
|
+
return cachedUpdateInfo;
|
|
13
|
+
const current = PACKAGE_VERSION;
|
|
14
|
+
const updateCmd = `npm update -g ${PACKAGE_NAME}`;
|
|
15
|
+
try {
|
|
16
|
+
const latest = await npmViewVersion(logger);
|
|
17
|
+
const updateAvailable = latest !== current && isNewer(latest, current);
|
|
18
|
+
cachedUpdateInfo = {
|
|
19
|
+
currentVersion: current,
|
|
20
|
+
latestVersion: latest,
|
|
21
|
+
updateAvailable,
|
|
22
|
+
updateCommand: updateCmd,
|
|
23
|
+
};
|
|
24
|
+
if (updateAvailable) {
|
|
25
|
+
logger.info(`[mcp-bridge] Update available: ${current} → ${latest}`);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
logger.info(`[mcp-bridge] Version ${current} is up to date`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
logger.warn(`[mcp-bridge] Version check failed: ${err instanceof Error ? err.message : err}`);
|
|
33
|
+
cachedUpdateInfo = {
|
|
34
|
+
currentVersion: current,
|
|
35
|
+
latestVersion: current,
|
|
36
|
+
updateAvailable: false,
|
|
37
|
+
updateCommand: updateCmd,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return cachedUpdateInfo;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build the notice string to inject into the first tool response.
|
|
44
|
+
* Returns empty string if no update or already delivered.
|
|
45
|
+
*/
|
|
46
|
+
export function getUpdateNotice() {
|
|
47
|
+
if (noticeDelivered || !cachedUpdateInfo?.updateAvailable)
|
|
48
|
+
return "";
|
|
49
|
+
noticeDelivered = true;
|
|
50
|
+
return (`\n\n---\nUpdate available: ${cachedUpdateInfo.currentVersion} → ${cachedUpdateInfo.latestVersion}\n` +
|
|
51
|
+
`Run: ${cachedUpdateInfo.updateCommand}`);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Reset the notice flag (for testing).
|
|
55
|
+
*/
|
|
56
|
+
export function resetNoticeFlag() {
|
|
57
|
+
noticeDelivered = false;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Execute the actual npm update. Returns a result message.
|
|
61
|
+
*/
|
|
62
|
+
export async function runUpdate(logger) {
|
|
63
|
+
const info = cachedUpdateInfo ?? await checkForUpdate(logger);
|
|
64
|
+
if (!info.updateAvailable) {
|
|
65
|
+
return `MCP Bridge is already up to date (v${info.currentVersion}).`;
|
|
66
|
+
}
|
|
67
|
+
logger.info(`[mcp-bridge] Running update: ${info.updateCommand}`);
|
|
68
|
+
try {
|
|
69
|
+
const output = await execAsync(info.updateCommand, 60_000);
|
|
70
|
+
// Invalidate cache so next check re-fetches
|
|
71
|
+
cachedUpdateInfo = null;
|
|
72
|
+
noticeDelivered = false;
|
|
73
|
+
// Verify new version
|
|
74
|
+
const newVersion = npmViewVersionSync(logger);
|
|
75
|
+
return (`MCP Bridge updated: ${info.currentVersion} → ${newVersion}\n` +
|
|
76
|
+
`A restart is needed to load the new version.\n\n` +
|
|
77
|
+
`npm output:\n${output.trim()}`);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
81
|
+
logger.error(`[mcp-bridge] Update failed: ${msg}`);
|
|
82
|
+
return (`Update failed. You can try manually:\n` +
|
|
83
|
+
`${info.updateCommand}\n\nError: ${msg}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// --- helpers ---
|
|
87
|
+
function npmViewVersion(_logger) {
|
|
88
|
+
return new Promise((resolve, reject) => {
|
|
89
|
+
const timeout = setTimeout(() => reject(new Error("npm view timed out")), 10_000);
|
|
90
|
+
execCb(`npm view ${PACKAGE_NAME} version`, { encoding: "utf-8" }, (err, stdout) => {
|
|
91
|
+
clearTimeout(timeout);
|
|
92
|
+
if (err)
|
|
93
|
+
return reject(err);
|
|
94
|
+
const ver = (stdout ?? "").trim();
|
|
95
|
+
if (!ver)
|
|
96
|
+
return reject(new Error("empty version from npm"));
|
|
97
|
+
resolve(ver);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function npmViewVersionSync(_logger) {
|
|
102
|
+
try {
|
|
103
|
+
return execSync(`npm view ${PACKAGE_NAME} version`, { encoding: "utf-8", timeout: 10_000 }).trim();
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return "unknown";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function execAsync(cmd, timeoutMs) {
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
const timeout = setTimeout(() => reject(new Error(`Command timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
112
|
+
execCb(cmd, { encoding: "utf-8", timeout: timeoutMs }, (err, stdout, stderr) => {
|
|
113
|
+
clearTimeout(timeout);
|
|
114
|
+
if (err)
|
|
115
|
+
return reject(new Error(`${err.message}\n${stderr ?? ""}`));
|
|
116
|
+
resolve(stdout ?? "");
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function isNewer(latest, current) {
|
|
121
|
+
const l = latest.split(".").map(Number);
|
|
122
|
+
const c = current.split(".").map(Number);
|
|
123
|
+
for (let i = 0; i < Math.max(l.length, c.length); i++) {
|
|
124
|
+
const lv = l[i] ?? 0;
|
|
125
|
+
const cv = c[i] ?? 0;
|
|
126
|
+
if (lv > cv)
|
|
127
|
+
return true;
|
|
128
|
+
if (lv < cv)
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
132
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiwerk/mcp-bridge",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Standalone MCP server that multiplexes multiple MCP servers into one interface",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./src/index.
|
|
6
|
+
"main": "./dist/src/index.js",
|
|
7
|
+
"types": "./dist/src/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/src/index.d.ts",
|
|
11
|
+
"import": "./dist/src/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
7
14
|
"bin": {
|
|
8
|
-
"mcp-bridge": "./bin/mcp-bridge.js"
|
|
15
|
+
"mcp-bridge": "./dist/bin/mcp-bridge.js"
|
|
9
16
|
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist/",
|
|
19
|
+
"servers/",
|
|
20
|
+
"scripts/",
|
|
21
|
+
"LICENSE",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
10
24
|
"license": "MIT",
|
|
11
25
|
"author": "AIWerk <kontakt@aiwerk.ch>",
|
|
12
26
|
"homepage": "https://github.com/AIWerk/mcp-bridge#readme",
|
|
@@ -29,7 +43,8 @@
|
|
|
29
43
|
"scripts": {
|
|
30
44
|
"build": "tsc",
|
|
31
45
|
"test": "node --import tsx --test tests/*.test.ts",
|
|
32
|
-
"typecheck": "tsc --noEmit"
|
|
46
|
+
"typecheck": "tsc --noEmit",
|
|
47
|
+
"prepublishOnly": "npm run build"
|
|
33
48
|
},
|
|
34
49
|
"dependencies": {
|
|
35
50
|
"@sinclair/typebox": "^0.34.0"
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
$ErrorActionPreference = "Stop"
|
|
2
2
|
|
|
3
3
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
4
|
-
$
|
|
5
|
-
$EnvFile = Join-Path $
|
|
6
|
-
$
|
|
4
|
+
$McpBridgeDir = Join-Path $env:USERPROFILE ".mcp-bridge"
|
|
5
|
+
$EnvFile = Join-Path $McpBridgeDir ".env"
|
|
6
|
+
$McpBridgeJson = Join-Path $McpBridgeDir "config.json"
|
|
7
7
|
|
|
8
8
|
if ($args.Count -eq 0) {
|
|
9
9
|
Write-Host "Usage: install-server.ps1 <server-name> [--dry-run] [--remove]"
|
|
@@ -133,26 +133,26 @@ if ($Remove) {
|
|
|
133
133
|
Write-Host "Removing $ServerTitle MCP Server"
|
|
134
134
|
Write-Host "========================================"
|
|
135
135
|
|
|
136
|
-
if (-not (Test-Path $
|
|
137
|
-
Write-Host "❌ Config not found: $
|
|
136
|
+
if (-not (Test-Path $McpBridgeJson)) {
|
|
137
|
+
Write-Host "❌ Config not found: $McpBridgeJson" -ForegroundColor Red
|
|
138
138
|
exit 1
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
$cfg = Get-Content $
|
|
142
|
-
$servers = $cfg.
|
|
141
|
+
$cfg = Get-Content $McpBridgeJson -Raw | ConvertFrom-Json
|
|
142
|
+
$servers = $cfg.servers
|
|
143
143
|
if (-not ($servers.PSObject.Properties.Name -contains $ServerName)) {
|
|
144
144
|
Write-Host "ℹ️ Server '$ServerName' not found in config. Nothing to remove." -ForegroundColor Yellow
|
|
145
145
|
exit 0
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
# Backup
|
|
149
|
-
$backupFile = "$
|
|
150
|
-
Copy-Item $
|
|
149
|
+
$backupFile = "$McpBridgeJson.bak-$(Get-Date -Format 'yyyyMMddHHmmss')"
|
|
150
|
+
Copy-Item $McpBridgeJson $backupFile
|
|
151
151
|
Write-Host "Backup: $backupFile"
|
|
152
152
|
|
|
153
153
|
# Remove server entry
|
|
154
154
|
$servers.PSObject.Properties.Remove($ServerName)
|
|
155
|
-
$cfg | ConvertTo-Json -Depth 10 | Set-Content $
|
|
155
|
+
$cfg | ConvertTo-Json -Depth 10 | Set-Content $McpBridgeJson -Encoding UTF8
|
|
156
156
|
Write-Host "✅ Removed $ServerName from config" -ForegroundColor Green
|
|
157
157
|
Write-Host "ℹ️ Server recipe kept in servers\$ServerName\ (reinstall anytime)" -ForegroundColor Cyan
|
|
158
158
|
|
|
@@ -168,17 +168,7 @@ if ($Remove) {
|
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
if ([string]::IsNullOrEmpty($restart) -or $restart -match '^[Yy]$') {
|
|
173
|
-
try {
|
|
174
|
-
Restart-Service openclaw-gateway -ErrorAction Stop
|
|
175
|
-
Write-Host "✅ Gateway restarted. $ServerTitle removed." -ForegroundColor Green
|
|
176
|
-
} catch {
|
|
177
|
-
Write-Host "⚠️ Auto-restart failed. Run: Restart-Service openclaw-gateway" -ForegroundColor Yellow
|
|
178
|
-
}
|
|
179
|
-
} else {
|
|
180
|
-
Write-Host "⏭️ Run manually: Restart-Service openclaw-gateway"
|
|
181
|
-
}
|
|
171
|
+
Write-Host "✅ $ServerTitle removed. Restart mcp-bridge to apply." -ForegroundColor Green
|
|
182
172
|
exit 0
|
|
183
173
|
}
|
|
184
174
|
|
|
@@ -218,7 +208,7 @@ while ([string]::IsNullOrWhiteSpace($Token)) {
|
|
|
218
208
|
}
|
|
219
209
|
|
|
220
210
|
# 4. Write to .env
|
|
221
|
-
New-Item -ItemType Directory -Force -Path $
|
|
211
|
+
New-Item -ItemType Directory -Force -Path $McpBridgeDir | Out-Null
|
|
222
212
|
if (-not (Test-Path $EnvFile)) { New-Item -ItemType File -Force -Path $EnvFile | Out-Null }
|
|
223
213
|
|
|
224
214
|
$envExists = Select-String -Path $EnvFile -Pattern "^$([regex]::Escape($EnvVarName))=" -Quiet
|
|
@@ -237,14 +227,14 @@ if ($envExists) {
|
|
|
237
227
|
Write-Host "Saved $EnvVarName to $EnvFile"
|
|
238
228
|
}
|
|
239
229
|
|
|
240
|
-
# 5. Backup and merge
|
|
241
|
-
if (-not (Test-Path $
|
|
230
|
+
# 5. Backup and merge config.json
|
|
231
|
+
if (-not (Test-Path $McpBridgeJson)) { Set-Content -Path $McpBridgeJson -Value "{}" -Encoding UTF8 }
|
|
242
232
|
|
|
243
233
|
$timestamp = Get-Date -Format "yyyyMMddHHmmss"
|
|
244
|
-
Copy-Item -Path $
|
|
245
|
-
Write-Host "Backup: $
|
|
234
|
+
Copy-Item -Path $McpBridgeJson -Destination "$McpBridgeJson.bak-$timestamp" -Force
|
|
235
|
+
Write-Host "Backup: $McpBridgeJson.bak-$timestamp"
|
|
246
236
|
|
|
247
|
-
$cfgRaw = Get-Content -Path $
|
|
237
|
+
$cfgRaw = Get-Content -Path $McpBridgeJson -Raw
|
|
248
238
|
if ([string]::IsNullOrWhiteSpace($cfgRaw)) { $cfgRaw = "{}" }
|
|
249
239
|
$cfg = $cfgRaw | ConvertFrom-Json
|
|
250
240
|
$serverConfig = Get-Content -Path $ServerConfigFile -Raw | ConvertFrom-Json
|
|
@@ -258,43 +248,20 @@ if ($pathOverride -and $serverConfig.args -and $serverConfig.args.Count -gt 0) {
|
|
|
258
248
|
}
|
|
259
249
|
}
|
|
260
250
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
if ($
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
if (-not ($entries.PSObject.Properties.Name -contains "openclaw-mcp-bridge")) {
|
|
267
|
-
$entries | Add-Member -NotePropertyName "openclaw-mcp-bridge" -NotePropertyValue ([PSCustomObject]@{})
|
|
268
|
-
}
|
|
269
|
-
$mcpClient = $entries."openclaw-mcp-bridge"
|
|
270
|
-
if (-not ($mcpClient.PSObject.Properties.Name -contains "enabled")) {
|
|
271
|
-
$mcpClient | Add-Member -NotePropertyName "enabled" -NotePropertyValue $true
|
|
272
|
-
}
|
|
273
|
-
$mcpConfig = Ensure-Property -Object $mcpClient -Name "config" -DefaultValue ([PSCustomObject]@{})
|
|
274
|
-
if (-not ($mcpConfig.PSObject.Properties.Name -contains "toolPrefix")) { $mcpConfig | Add-Member -NotePropertyName "toolPrefix" -NotePropertyValue $true }
|
|
275
|
-
if (-not ($mcpConfig.PSObject.Properties.Name -contains "reconnectIntervalMs")) { $mcpConfig | Add-Member -NotePropertyName "reconnectIntervalMs" -NotePropertyValue 30000 }
|
|
276
|
-
if (-not ($mcpConfig.PSObject.Properties.Name -contains "connectionTimeoutMs")) { $mcpConfig | Add-Member -NotePropertyName "connectionTimeoutMs" -NotePropertyValue 10000 }
|
|
277
|
-
if (-not ($mcpConfig.PSObject.Properties.Name -contains "requestTimeoutMs")) { $mcpConfig | Add-Member -NotePropertyName "requestTimeoutMs" -NotePropertyValue 60000 }
|
|
278
|
-
$servers = Ensure-Property -Object $mcpConfig -Name "servers" -DefaultValue ([PSCustomObject]@{})
|
|
251
|
+
if (-not ($cfg.PSObject.Properties.Name -contains "toolPrefix")) { $cfg | Add-Member -NotePropertyName "toolPrefix" -NotePropertyValue $true }
|
|
252
|
+
if (-not ($cfg.PSObject.Properties.Name -contains "reconnectIntervalMs")) { $cfg | Add-Member -NotePropertyName "reconnectIntervalMs" -NotePropertyValue 30000 }
|
|
253
|
+
if (-not ($cfg.PSObject.Properties.Name -contains "connectionTimeoutMs")) { $cfg | Add-Member -NotePropertyName "connectionTimeoutMs" -NotePropertyValue 10000 }
|
|
254
|
+
if (-not ($cfg.PSObject.Properties.Name -contains "requestTimeoutMs")) { $cfg | Add-Member -NotePropertyName "requestTimeoutMs" -NotePropertyValue 60000 }
|
|
255
|
+
$servers = Ensure-Property -Object $cfg -Name "servers" -DefaultValue ([PSCustomObject]@{})
|
|
279
256
|
|
|
280
257
|
if ($servers.PSObject.Properties.Name -contains $ServerName) {
|
|
281
258
|
$servers.PSObject.Properties.Remove($ServerName)
|
|
282
259
|
}
|
|
283
260
|
$servers | Add-Member -NotePropertyName $ServerName -NotePropertyValue $serverConfig
|
|
284
261
|
|
|
285
|
-
$cfg | ConvertTo-Json -Depth 30 | Set-Content -Path $
|
|
262
|
+
$cfg | ConvertTo-Json -Depth 30 | Set-Content -Path $McpBridgeJson -Encoding UTF8
|
|
286
263
|
Write-Host "Configuration merged for: $ServerName"
|
|
287
264
|
|
|
288
|
-
# 6. Gateway restart
|
|
289
265
|
Write-Host ""
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
try {
|
|
293
|
-
openclaw gateway restart 2>$null
|
|
294
|
-
Write-Host "Gateway restarting... Check 'openclaw gateway status' in a moment."
|
|
295
|
-
} catch {
|
|
296
|
-
Write-Host "Could not restart automatically. Run: openclaw gateway restart"
|
|
297
|
-
}
|
|
298
|
-
} else {
|
|
299
|
-
Write-Host "Run manually: openclaw gateway restart"
|
|
300
|
-
}
|
|
266
|
+
Write-Host "$ServerTitle MCP Server installed."
|
|
267
|
+
Write-Host "Restart mcp-bridge to pick up the new server configuration."
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
set -e
|
|
3
3
|
|
|
4
4
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
ENV_FILE="${
|
|
5
|
+
MCP_BRIDGE_DIR="${HOME}/.mcp-bridge"
|
|
6
|
+
MCP_BRIDGE_JSON="${MCP_BRIDGE_DIR}/config.json"
|
|
7
|
+
ENV_FILE="${MCP_BRIDGE_DIR}/.env"
|
|
8
8
|
|
|
9
9
|
usage() {
|
|
10
10
|
echo "Usage: $0 <server-name> [--dry-run] [--remove]"
|
|
@@ -128,17 +128,17 @@ if [[ "$REMOVE" == "true" ]]; then
|
|
|
128
128
|
echo "Removing ${SERVER_TITLE} MCP Server"
|
|
129
129
|
echo "========================================"
|
|
130
130
|
|
|
131
|
-
if [[ ! -f "$
|
|
132
|
-
echo "❌ Config not found: $
|
|
131
|
+
if [[ ! -f "$MCP_BRIDGE_JSON" ]]; then
|
|
132
|
+
echo "❌ Config not found: $MCP_BRIDGE_JSON"
|
|
133
133
|
exit 1
|
|
134
134
|
fi
|
|
135
135
|
|
|
136
136
|
# Check if server exists in config
|
|
137
137
|
HAS_SERVER=$(python3 -c "
|
|
138
138
|
import json
|
|
139
|
-
with open('$
|
|
139
|
+
with open('$MCP_BRIDGE_JSON') as f:
|
|
140
140
|
cfg = json.load(f)
|
|
141
|
-
servers = cfg.get('
|
|
141
|
+
servers = cfg.get('servers',{})
|
|
142
142
|
print('yes' if '$SERVER_NAME' in servers else 'no')
|
|
143
143
|
" 2>/dev/null)
|
|
144
144
|
|
|
@@ -148,18 +148,18 @@ print('yes' if '$SERVER_NAME' in servers else 'no')
|
|
|
148
148
|
fi
|
|
149
149
|
|
|
150
150
|
# Backup
|
|
151
|
-
BACKUP_FILE="${
|
|
152
|
-
cp "$
|
|
151
|
+
BACKUP_FILE="${MCP_BRIDGE_JSON}.bak-$(date +%Y%m%d%H%M%S)"
|
|
152
|
+
cp "$MCP_BRIDGE_JSON" "$BACKUP_FILE"
|
|
153
153
|
echo "Backup: ${BACKUP_FILE}"
|
|
154
154
|
|
|
155
155
|
# Remove server entry from config (keep servers/<name>/ directory)
|
|
156
156
|
python3 -c "
|
|
157
157
|
import json
|
|
158
|
-
with open('$
|
|
158
|
+
with open('$MCP_BRIDGE_JSON') as f:
|
|
159
159
|
cfg = json.load(f)
|
|
160
|
-
servers = cfg
|
|
160
|
+
servers = cfg.get('servers', {})
|
|
161
161
|
del servers['$SERVER_NAME']
|
|
162
|
-
with open('$
|
|
162
|
+
with open('$MCP_BRIDGE_JSON', 'w') as f:
|
|
163
163
|
json.dump(cfg, f, indent=2)
|
|
164
164
|
f.write('\n')
|
|
165
165
|
print('✅ Removed $SERVER_NAME from config')
|
|
@@ -182,21 +182,21 @@ print('ℹ️ Server recipe kept in servers/$SERVER_NAME/ (reinstall anytime)')
|
|
|
182
182
|
read -r -p "Restart gateway now? [Y/n]: " RESTART </dev/tty
|
|
183
183
|
fi
|
|
184
184
|
if [[ -z "$RESTART" || "$RESTART" =~ ^[Yy]$ ]]; then
|
|
185
|
-
systemctl --user restart
|
|
186
|
-
echo "⚠️ Auto-restart failed. Run: systemctl --user restart
|
|
185
|
+
systemctl --user restart mcp-bridge 2>/dev/null || {
|
|
186
|
+
echo "⚠️ Auto-restart failed. Run: systemctl --user restart mcp-bridge"
|
|
187
187
|
exit 0
|
|
188
188
|
}
|
|
189
189
|
sleep 3
|
|
190
|
-
if systemctl --user is-active --quiet
|
|
191
|
-
echo "✅
|
|
190
|
+
if systemctl --user is-active --quiet mcp-bridge 2>/dev/null; then
|
|
191
|
+
echo "✅ Service restarted. ${SERVER_TITLE} removed."
|
|
192
192
|
else
|
|
193
|
-
echo "❌
|
|
194
|
-
cp "$BACKUP_FILE" "$
|
|
195
|
-
systemctl --user restart
|
|
193
|
+
echo "❌ Service failed to start! Restoring backup..."
|
|
194
|
+
cp "$BACKUP_FILE" "$MCP_BRIDGE_JSON"
|
|
195
|
+
systemctl --user restart mcp-bridge 2>/dev/null
|
|
196
196
|
echo "Restored from backup."
|
|
197
197
|
fi
|
|
198
198
|
else
|
|
199
|
-
echo "⏭️ Run manually: systemctl --user restart
|
|
199
|
+
echo "⏭️ Run manually: systemctl --user restart mcp-bridge"
|
|
200
200
|
fi
|
|
201
201
|
exit 0
|
|
202
202
|
fi
|
|
@@ -235,7 +235,7 @@ if [[ -f "$ENV_VARS_FILE" ]] && [[ -s "$ENV_VARS_FILE" ]]; then
|
|
|
235
235
|
done
|
|
236
236
|
|
|
237
237
|
# Write to .env
|
|
238
|
-
mkdir -p "$
|
|
238
|
+
mkdir -p "$MCP_BRIDGE_DIR"
|
|
239
239
|
touch "$ENV_FILE"
|
|
240
240
|
chmod 600 "$ENV_FILE"
|
|
241
241
|
|
|
@@ -255,22 +255,22 @@ if [[ -f "$ENV_VARS_FILE" ]] && [[ -s "$ENV_VARS_FILE" ]]; then
|
|
|
255
255
|
fi
|
|
256
256
|
fi
|
|
257
257
|
|
|
258
|
-
# 4. Backup and merge
|
|
259
|
-
mkdir -p "$(dirname "$
|
|
260
|
-
[[ ! -f "$
|
|
258
|
+
# 4. Backup and merge config.json
|
|
259
|
+
mkdir -p "$(dirname "$MCP_BRIDGE_JSON")"
|
|
260
|
+
[[ ! -f "$MCP_BRIDGE_JSON" ]] && echo "{}" > "$MCP_BRIDGE_JSON"
|
|
261
261
|
|
|
262
|
-
BACKUP_FILE="${
|
|
263
|
-
cp "$
|
|
262
|
+
BACKUP_FILE="${MCP_BRIDGE_JSON}.bak-$(date +%Y%m%d%H%M%S)"
|
|
263
|
+
cp "$MCP_BRIDGE_JSON" "$BACKUP_FILE"
|
|
264
264
|
echo "Backup: ${BACKUP_FILE}"
|
|
265
265
|
|
|
266
266
|
PATH_OVERRIDE="$(resolve_path_override)"
|
|
267
267
|
|
|
268
|
-
python3 - "$
|
|
268
|
+
python3 - "$MCP_BRIDGE_JSON" "$SERVER_CONFIG_FILE" "$SERVER_NAME" "$PATH_OVERRIDE" <<'PY'
|
|
269
269
|
import json, sys
|
|
270
270
|
|
|
271
|
-
|
|
271
|
+
config_path, server_cfg_path, server_name, path_override = sys.argv[1:5]
|
|
272
272
|
|
|
273
|
-
with open(
|
|
273
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
274
274
|
raw = f.read().strip()
|
|
275
275
|
cfg = json.loads(raw) if raw else {}
|
|
276
276
|
|
|
@@ -284,22 +284,14 @@ if path_override:
|
|
|
284
284
|
if isinstance(value, str) and value.startswith("path/to/"):
|
|
285
285
|
args[idx] = path_override
|
|
286
286
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
mcp_client = entries.setdefault("openclaw-mcp-bridge", {})
|
|
293
|
-
mcp_client.setdefault("enabled", True)
|
|
294
|
-
mcp_cfg = mcp_client.setdefault("config", {})
|
|
295
|
-
mcp_cfg.setdefault("toolPrefix", True)
|
|
296
|
-
mcp_cfg.setdefault("reconnectIntervalMs", 30000)
|
|
297
|
-
mcp_cfg.setdefault("connectionTimeoutMs", 10000)
|
|
298
|
-
mcp_cfg.setdefault("requestTimeoutMs", 60000)
|
|
299
|
-
servers = mcp_cfg.setdefault("servers", {})
|
|
287
|
+
cfg.setdefault("toolPrefix", True)
|
|
288
|
+
cfg.setdefault("reconnectIntervalMs", 30000)
|
|
289
|
+
cfg.setdefault("connectionTimeoutMs", 10000)
|
|
290
|
+
cfg.setdefault("requestTimeoutMs", 60000)
|
|
291
|
+
servers = cfg.setdefault("servers", {})
|
|
300
292
|
servers[server_name] = server_cfg
|
|
301
293
|
|
|
302
|
-
with open(
|
|
294
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
|
303
295
|
json.dump(cfg, f, indent=2)
|
|
304
296
|
f.write("\n")
|
|
305
297
|
|
|
@@ -308,50 +300,5 @@ PY
|
|
|
308
300
|
|
|
309
301
|
# 5. Gateway restart
|
|
310
302
|
echo ""
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
systemctl --user restart openclaw-gateway 2>/dev/null || {
|
|
314
|
-
echo "⚠️ Auto-restart failed. Run: systemctl --user restart openclaw-gateway"
|
|
315
|
-
exit 0
|
|
316
|
-
}
|
|
317
|
-
echo "Waiting for gateway startup..."
|
|
318
|
-
CONFIRMED=false
|
|
319
|
-
ROUTER_MODE=false
|
|
320
|
-
for i in 1 2 3 4 5 6; do
|
|
321
|
-
sleep 5
|
|
322
|
-
if ! systemctl --user is-active --quiet openclaw-gateway 2>/dev/null; then
|
|
323
|
-
echo "❌ Gateway failed to start!"
|
|
324
|
-
journalctl --user -u openclaw-gateway --since "1 min ago" --no-pager 2>/dev/null | grep -iE "error|fail|missing" | head -5
|
|
325
|
-
echo "Full logs: journalctl --user -u openclaw-gateway --since '1 min ago' --no-pager"
|
|
326
|
-
exit 1
|
|
327
|
-
fi
|
|
328
|
-
# Router mode: servers connect lazily, just check plugin loaded
|
|
329
|
-
if journalctl --user -u openclaw-gateway --since "1 min ago" --no-pager 2>/dev/null | grep -qi "Plugin activated with.*servers configured"; then
|
|
330
|
-
CONFIRMED=true
|
|
331
|
-
ROUTER_MODE=true
|
|
332
|
-
break
|
|
333
|
-
fi
|
|
334
|
-
# Direct mode: server connects at boot
|
|
335
|
-
if journalctl --user -u openclaw-gateway --since "1 min ago" --no-pager 2>/dev/null | grep -qi "Server ${SERVER_NAME} initialized"; then
|
|
336
|
-
CONFIRMED=true
|
|
337
|
-
break
|
|
338
|
-
fi
|
|
339
|
-
# Check if server explicitly failed
|
|
340
|
-
if journalctl --user -u openclaw-gateway --since "1 min ago" --no-pager 2>/dev/null | grep -qi "Startup failed: ${SERVER_NAME}"; then
|
|
341
|
-
echo "❌ ${SERVER_TITLE} MCP Server failed to start!"
|
|
342
|
-
journalctl --user -u openclaw-gateway --since "1 min ago" --no-pager 2>/dev/null | grep -i "$SERVER_NAME" | tail -5
|
|
343
|
-
exit 1
|
|
344
|
-
fi
|
|
345
|
-
done
|
|
346
|
-
if $CONFIRMED; then
|
|
347
|
-
if $ROUTER_MODE; then
|
|
348
|
-
echo "✅ ${SERVER_TITLE} configured! (Router mode — server connects on first use)"
|
|
349
|
-
else
|
|
350
|
-
echo "✅ ${SERVER_TITLE} MCP Server installed and running!"
|
|
351
|
-
fi
|
|
352
|
-
else
|
|
353
|
-
echo "⚠️ Gateway running but plugin not confirmed after 30s. Check: journalctl --user -u openclaw-gateway -f"
|
|
354
|
-
fi
|
|
355
|
-
else
|
|
356
|
-
echo "⏭️ Run manually: systemctl --user restart openclaw-gateway"
|
|
357
|
-
fi
|
|
303
|
+
echo "✅ ${SERVER_TITLE} MCP Server installed."
|
|
304
|
+
echo "Restart mcp-bridge to pick up the new server configuration."
|
package/servers/apify/README.md
CHANGED
|
@@ -10,21 +10,21 @@ Hosted Apify MCP endpoint for actors, scraping, and docs search.
|
|
|
10
10
|
|
|
11
11
|
### Linux / macOS
|
|
12
12
|
```bash
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
# Using mcp-bridge CLI:
|
|
14
|
+
mcp-bridge install apify
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
### Windows (PowerShell)
|
|
18
18
|
```powershell
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
# Using mcp-bridge CLI:
|
|
20
|
+
mcp-bridge install apify
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
### Manual Setup
|
|
24
24
|
1. Get your token: https://console.apify.com/settings/integrations
|
|
25
25
|
2. Add to .env: `APIFY_TOKEN=your_token`
|
|
26
|
-
3. Add config to
|
|
27
|
-
4. Restart
|
|
26
|
+
3. Add config to ~/.mcp-bridge/config.json (see config.json)
|
|
27
|
+
4. Restart mcp-bridge
|
|
28
28
|
|
|
29
29
|
## What you get
|
|
30
30
|
- Actor discovery and execution
|