@aiwerk/mcp-bridge 2.1.0 → 2.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/README.md +66 -1
- package/dist/bin/mcp-bridge.js +0 -0
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +1 -1
- package/dist/src/update-checker.d.ts +11 -0
- package/dist/src/update-checker.js +52 -4
- package/package.json +10 -2
- package/servers/imap-email/recipe.json +45 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @aiwerk/mcp-bridge
|
|
2
2
|
|
|
3
|
-
[](https://github.com/AIWerk/mcp-bridge/actions/workflows/ci.yml)
|
|
4
4
|
[](https://www.npmjs.com/package/@aiwerk/mcp-bridge)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
|
|
@@ -17,6 +17,12 @@ Most AI agents connect to MCP servers one-by-one. With 10+ servers, that's 10+ c
|
|
|
17
17
|
- **Intent routing**: say what you need in plain language, the bridge finds the right tool
|
|
18
18
|
- **Schema compression**: tool descriptions compressed ~57%, full schema on demand
|
|
19
19
|
- **Security layer**: trust levels, tool deny/allow lists, result size limits
|
|
20
|
+
- **HTTP auth**: bearer token, custom headers, and **OAuth2 Client Credentials** with automatic token management
|
|
21
|
+
- **Result caching**: LRU cache with per-tool TTL overrides
|
|
22
|
+
- **Batch calls**: parallel multi-tool execution via `action=batch`
|
|
23
|
+
- **Multi-server resolution**: automatic tool disambiguation when multiple servers provide the same tool
|
|
24
|
+
- **Configurable retries**: exponential backoff for transient errors
|
|
25
|
+
- **Graceful shutdown**: clean process termination and connection cleanup
|
|
20
26
|
- **Direct mode**: all tools registered individually with automatic prefixing
|
|
21
27
|
- **3 transports**: stdio, SSE, streamable-http
|
|
22
28
|
- **Built-in catalog**: 14 pre-configured servers, install with one command
|
|
@@ -363,6 +369,35 @@ When `action="call"` is used without `server=`, mcp-bridge can resolve collision
|
|
|
363
369
|
| `sse` | `url`, `headers` | Remote SSE servers |
|
|
364
370
|
| `streamable-http` | `url`, `headers` | Modern HTTP-based servers |
|
|
365
371
|
|
|
372
|
+
### Authentication
|
|
373
|
+
|
|
374
|
+
SSE and streamable-HTTP transports support three auth methods:
|
|
375
|
+
|
|
376
|
+
**Bearer token:**
|
|
377
|
+
```json
|
|
378
|
+
{ "auth": { "type": "bearer", "token": "${MY_API_TOKEN}" } }
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
**Custom headers:**
|
|
382
|
+
```json
|
|
383
|
+
{ "auth": { "type": "header", "headers": { "X-API-Key": "${MY_KEY}" } } }
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
**OAuth2 Client Credentials** (automatic token management):
|
|
387
|
+
```json
|
|
388
|
+
{
|
|
389
|
+
"auth": {
|
|
390
|
+
"type": "oauth2",
|
|
391
|
+
"clientId": "${CLIENT_ID}",
|
|
392
|
+
"clientSecret": "${CLIENT_SECRET}",
|
|
393
|
+
"tokenUrl": "https://provider.com/oauth/token",
|
|
394
|
+
"scopes": ["read", "write"]
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
OAuth2 features: automatic token acquisition, caching with expiry-aware refresh, single-attempt 401 retry, env var substitution in credentials.
|
|
400
|
+
|
|
366
401
|
### Environment variables
|
|
367
402
|
|
|
368
403
|
Secrets go in `~/.mcp-bridge/.env` (chmod 600 on init):
|
|
@@ -459,6 +494,36 @@ const result = await router.dispatch("todoist", "call", "find-tasks", { query: "
|
|
|
459
494
|
└──────────────────────────────────────────────┘
|
|
460
495
|
```
|
|
461
496
|
|
|
497
|
+
## Security Limitations
|
|
498
|
+
|
|
499
|
+
The built-in security layer (trust levels, tool filters, result sanitization) provides **baseline protection** for common threats:
|
|
500
|
+
|
|
501
|
+
- Prompt injection patterns (known strings)
|
|
502
|
+
- Oversized responses
|
|
503
|
+
- Unauthorized tool access
|
|
504
|
+
|
|
505
|
+
**What it does NOT cover:**
|
|
506
|
+
- Unicode obfuscation / homoglyph attacks
|
|
507
|
+
- Sophisticated multi-step injection chains
|
|
508
|
+
- Content-level PII detection
|
|
509
|
+
|
|
510
|
+
For production deployments with high security requirements, consider adding an external content filtering layer (e.g., guardrails, PII redaction service) between the bridge and your application.
|
|
511
|
+
|
|
512
|
+
## Roadmap
|
|
513
|
+
|
|
514
|
+
| Status | Feature | Version |
|
|
515
|
+
|--------|---------|---------|
|
|
516
|
+
| ✅ | Smart Router v2 (intent, cache, batch, resolution) | 1.9.0 |
|
|
517
|
+
| ✅ | HTTP auth (bearer, headers) | 2.0.0 |
|
|
518
|
+
| ✅ | Configurable retries + graceful shutdown | 2.0.0 |
|
|
519
|
+
| ✅ | OAuth2 Client Credentials | 2.1.0 |
|
|
520
|
+
| 🔜 | Hosted bridge (bridge.aiwerk.ch) | planned |
|
|
521
|
+
| 🔜 | Remote catalog integration | planned |
|
|
522
|
+
| 🔜 | OpenTelemetry / Prometheus metrics | planned |
|
|
523
|
+
| 🔜 | PII redaction | planned |
|
|
524
|
+
|
|
525
|
+
See [docs/hosted-bridge-spec.md](docs/hosted-bridge-spec.md) for the hosted bridge architecture.
|
|
526
|
+
|
|
462
527
|
## Related
|
|
463
528
|
|
|
464
529
|
- **[@aiwerk/openclaw-mcp-bridge](https://github.com/AIWerk/openclaw-mcp-bridge)** — OpenClaw plugin wrapper (uses this package as core)
|
package/dist/bin/mcp-bridge.js
CHANGED
|
File without changes
|
package/dist/src/index.d.ts
CHANGED
|
@@ -17,6 +17,6 @@ export type { Logger, McpServerConfig, McpClientConfig, HttpAuthConfig, RetryCon
|
|
|
17
17
|
export { nextRequestId } from "./types.js";
|
|
18
18
|
export { pickRegisteredToolName } from "./tool-naming.js";
|
|
19
19
|
export { StandaloneServer } from "./standalone-server.js";
|
|
20
|
-
export { checkForUpdate, getUpdateNotice, runUpdate, resetNoticeFlag } from "./update-checker.js";
|
|
21
|
-
export type { UpdateInfo } from "./update-checker.js";
|
|
20
|
+
export { checkForUpdate, checkPluginUpdate, getUpdateNotice, runUpdate, resetNoticeFlag } from "./update-checker.js";
|
|
21
|
+
export type { UpdateInfo, PluginUpdateInfo } from "./update-checker.js";
|
|
22
22
|
export { filterServers, buildFilteredDescription } from "./smart-filter.js";
|
package/dist/src/index.js
CHANGED
|
@@ -22,6 +22,6 @@ export { pickRegisteredToolName } from "./tool-naming.js";
|
|
|
22
22
|
// Standalone server
|
|
23
23
|
export { StandaloneServer } from "./standalone-server.js";
|
|
24
24
|
// Update checker
|
|
25
|
-
export { checkForUpdate, getUpdateNotice, runUpdate, resetNoticeFlag } from "./update-checker.js";
|
|
25
|
+
export { checkForUpdate, checkPluginUpdate, getUpdateNotice, runUpdate, resetNoticeFlag } from "./update-checker.js";
|
|
26
26
|
// Smart filter
|
|
27
27
|
export { filterServers, buildFilteredDescription } from "./smart-filter.js";
|
|
@@ -6,11 +6,22 @@ export interface UpdateInfo {
|
|
|
6
6
|
updateCommand: string;
|
|
7
7
|
updateCommandParts: string[];
|
|
8
8
|
}
|
|
9
|
+
export interface PluginUpdateInfo {
|
|
10
|
+
pluginName: string;
|
|
11
|
+
currentVersion: string;
|
|
12
|
+
latestVersion: string;
|
|
13
|
+
updateAvailable: boolean;
|
|
14
|
+
}
|
|
9
15
|
/**
|
|
10
16
|
* Check npm registry for a newer version. Non-blocking, best-effort.
|
|
11
17
|
* Caches result for the lifetime of the process.
|
|
12
18
|
*/
|
|
13
19
|
export declare function checkForUpdate(logger: Logger): Promise<UpdateInfo>;
|
|
20
|
+
/**
|
|
21
|
+
* Check if a wrapper plugin (e.g. openclaw-mcp-bridge) has an update.
|
|
22
|
+
* The caller passes the installed plugin version; we check npm for the latest.
|
|
23
|
+
*/
|
|
24
|
+
export declare function checkPluginUpdate(pluginName: string, installedVersion: string, logger: Logger): Promise<PluginUpdateInfo>;
|
|
14
25
|
/**
|
|
15
26
|
* Build the notice string to inject into the first tool response.
|
|
16
27
|
* Returns empty string if no update or already delivered.
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { execFileSync, execFile } from "child_process";
|
|
2
2
|
import { PACKAGE_VERSION } from "./protocol.js";
|
|
3
3
|
const PACKAGE_NAME = "@aiwerk/mcp-bridge";
|
|
4
|
+
const PLUGIN_PACKAGE_NAME = "@aiwerk/openclaw-mcp-bridge";
|
|
4
5
|
let cachedUpdateInfo = null;
|
|
6
|
+
let cachedPluginUpdateInfo = null;
|
|
5
7
|
let noticeDelivered = false;
|
|
6
8
|
/**
|
|
7
9
|
* Check npm registry for a newer version. Non-blocking, best-effort.
|
|
@@ -42,16 +44,59 @@ export async function checkForUpdate(logger) {
|
|
|
42
44
|
}
|
|
43
45
|
return cachedUpdateInfo;
|
|
44
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Check if a wrapper plugin (e.g. openclaw-mcp-bridge) has an update.
|
|
49
|
+
* The caller passes the installed plugin version; we check npm for the latest.
|
|
50
|
+
*/
|
|
51
|
+
export async function checkPluginUpdate(pluginName, installedVersion, logger) {
|
|
52
|
+
if (cachedPluginUpdateInfo)
|
|
53
|
+
return cachedPluginUpdateInfo;
|
|
54
|
+
try {
|
|
55
|
+
const latest = await npmViewVersionOf(pluginName, logger);
|
|
56
|
+
const updateAvailable = latest !== installedVersion && isNewer(latest, installedVersion);
|
|
57
|
+
cachedPluginUpdateInfo = {
|
|
58
|
+
pluginName,
|
|
59
|
+
currentVersion: installedVersion,
|
|
60
|
+
latestVersion: latest,
|
|
61
|
+
updateAvailable,
|
|
62
|
+
};
|
|
63
|
+
if (updateAvailable) {
|
|
64
|
+
logger.info(`[mcp-bridge] Plugin update available: ${pluginName} ${installedVersion} → ${latest}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
logger.warn(`[mcp-bridge] Plugin version check failed: ${err instanceof Error ? err.message : err}`);
|
|
69
|
+
cachedPluginUpdateInfo = {
|
|
70
|
+
pluginName,
|
|
71
|
+
currentVersion: installedVersion,
|
|
72
|
+
latestVersion: installedVersion,
|
|
73
|
+
updateAvailable: false,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return cachedPluginUpdateInfo;
|
|
77
|
+
}
|
|
45
78
|
/**
|
|
46
79
|
* Build the notice string to inject into the first tool response.
|
|
47
80
|
* Returns empty string if no update or already delivered.
|
|
48
81
|
*/
|
|
49
82
|
export function getUpdateNotice() {
|
|
50
|
-
|
|
83
|
+
const coreUpdate = cachedUpdateInfo?.updateAvailable;
|
|
84
|
+
const pluginUpdate = cachedPluginUpdateInfo?.updateAvailable;
|
|
85
|
+
if (noticeDelivered || (!coreUpdate && !pluginUpdate))
|
|
51
86
|
return "";
|
|
52
87
|
noticeDelivered = true;
|
|
53
|
-
|
|
54
|
-
|
|
88
|
+
const lines = ["\n\n---"];
|
|
89
|
+
if (coreUpdate) {
|
|
90
|
+
lines.push(`Core update: ${cachedUpdateInfo.currentVersion} → ${cachedUpdateInfo.latestVersion}`);
|
|
91
|
+
}
|
|
92
|
+
if (pluginUpdate) {
|
|
93
|
+
lines.push(`Plugin update: ${cachedPluginUpdateInfo.pluginName} ${cachedPluginUpdateInfo.currentVersion} → ${cachedPluginUpdateInfo.latestVersion}`);
|
|
94
|
+
}
|
|
95
|
+
const installCmd = cachedPluginUpdateInfo?.pluginName
|
|
96
|
+
? `openclaw plugins install ${cachedPluginUpdateInfo.pluginName}`
|
|
97
|
+
: cachedUpdateInfo.updateCommand;
|
|
98
|
+
lines.push(`Run: ${installCmd}`);
|
|
99
|
+
return lines.join("\n");
|
|
55
100
|
}
|
|
56
101
|
/**
|
|
57
102
|
* Reset the notice flag (for testing).
|
|
@@ -89,8 +134,11 @@ export async function runUpdate(logger) {
|
|
|
89
134
|
}
|
|
90
135
|
// --- helpers ---
|
|
91
136
|
function npmViewVersion(_logger) {
|
|
137
|
+
return npmViewVersionOf(PACKAGE_NAME, _logger);
|
|
138
|
+
}
|
|
139
|
+
function npmViewVersionOf(packageName, _logger) {
|
|
92
140
|
return new Promise((resolve, reject) => {
|
|
93
|
-
execFile("npm", ["view",
|
|
141
|
+
execFile("npm", ["view", packageName, "version"], { encoding: "utf-8", timeout: 10_000 }, (err, stdout) => {
|
|
94
142
|
if (err)
|
|
95
143
|
return reject(err);
|
|
96
144
|
const ver = (stdout ?? "").trim();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiwerk/mcp-bridge",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Standalone MCP server that multiplexes multiple MCP servers into one interface",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/src/index.js",
|
|
@@ -45,13 +45,21 @@
|
|
|
45
45
|
"test": "node --import tsx --test tests/*.test.ts",
|
|
46
46
|
"typecheck": "tsc --noEmit",
|
|
47
47
|
"prepublishOnly": "npm run build",
|
|
48
|
-
"validate-recipe": "npx tsx bin/validate-recipe.ts"
|
|
48
|
+
"validate-recipe": "npx tsx bin/validate-recipe.ts",
|
|
49
|
+
"lint": "eslint src/",
|
|
50
|
+
"format": "prettier --write src/",
|
|
51
|
+
"format:check": "prettier --check src/"
|
|
49
52
|
},
|
|
50
53
|
"dependencies": {
|
|
51
54
|
"@sinclair/typebox": "^0.34.0"
|
|
52
55
|
},
|
|
53
56
|
"devDependencies": {
|
|
57
|
+
"@eslint/js": "^10.0.1",
|
|
54
58
|
"@types/node": "^22.0.0",
|
|
59
|
+
"@typescript-eslint/eslint-plugin": "^8.57.0",
|
|
60
|
+
"@typescript-eslint/parser": "^8.57.0",
|
|
61
|
+
"eslint": "^10.0.3",
|
|
62
|
+
"prettier": "^3.8.1",
|
|
55
63
|
"tsx": "^4.0.0",
|
|
56
64
|
"typescript": "^5.7.0"
|
|
57
65
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "imap-email",
|
|
3
|
+
"schemaVersion": 2,
|
|
4
|
+
"name": "IMAP Email",
|
|
5
|
+
"description": "Email tools for any IMAP/SMTP provider - list, read, search, send, manage emails",
|
|
6
|
+
"recipeVersion": "1.0.0",
|
|
7
|
+
"metadata": {
|
|
8
|
+
"category": "communication",
|
|
9
|
+
"tags": ["email", "imap", "smtp", "inbox"],
|
|
10
|
+
"country": "global",
|
|
11
|
+
"language": "en",
|
|
12
|
+
"homepage": "https://github.com/AIWerk/mcp-server-imap",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"pricing": "free",
|
|
15
|
+
"maturity": "beta"
|
|
16
|
+
},
|
|
17
|
+
"transports": [
|
|
18
|
+
{
|
|
19
|
+
"type": "stdio",
|
|
20
|
+
"command": "npx",
|
|
21
|
+
"args": ["-y", "@aiwerk/mcp-server-imap"],
|
|
22
|
+
"env": {
|
|
23
|
+
"IMAP_HOST": "${IMAP_HOST}",
|
|
24
|
+
"IMAP_PORT": "${IMAP_PORT}",
|
|
25
|
+
"IMAP_USER": "${IMAP_USER}",
|
|
26
|
+
"IMAP_PASS": "${IMAP_PASS}",
|
|
27
|
+
"SMTP_HOST": "${SMTP_HOST}",
|
|
28
|
+
"SMTP_PORT": "${SMTP_PORT}",
|
|
29
|
+
"SMTP_SEND_ENABLED": "${SMTP_SEND_ENABLED}"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"auth": {
|
|
34
|
+
"required": true,
|
|
35
|
+
"type": "api-key",
|
|
36
|
+
"envVars": ["IMAP_HOST", "IMAP_USER", "IMAP_PASS"],
|
|
37
|
+
"instructions": "Set your IMAP server hostname, username (email), and password. For Gmail use an App Password. Set SMTP_SEND_ENABLED=true to enable email sending.",
|
|
38
|
+
"bootstrap": "env-only"
|
|
39
|
+
},
|
|
40
|
+
"capabilities": {
|
|
41
|
+
"toolCount": 10,
|
|
42
|
+
"toolNames": ["email_list", "email_read", "email_search", "email_folders", "email_move", "email_flag", "email_delete", "email_send", "email_reply", "email_attachment"],
|
|
43
|
+
"sideEffects": "external-write"
|
|
44
|
+
}
|
|
45
|
+
}
|