@aiwerk/mcp-bridge 1.2.3 → 1.3.0

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 CHANGED
@@ -115,10 +115,38 @@ Config: `~/.mcp-bridge/config.json` | Secrets: `~/.mcp-bridge/.env`
115
115
  },
116
116
  "toolPrefix": true,
117
117
  "connectionTimeoutMs": 5000,
118
- "requestTimeoutMs": 60000
118
+ "requestTimeoutMs": 60000,
119
+ "schemaCompression": {
120
+ "enabled": true,
121
+ "maxDescriptionLength": 80
122
+ }
119
123
  }
120
124
  ```
121
125
 
126
+ ### Schema Compression
127
+
128
+ In router mode, tool descriptions from upstream servers can be verbose (100-300+ chars each). Schema compression truncates them to save tokens:
129
+
130
+ - **Enabled by default** — descriptions capped at 80 characters
131
+ - Cuts at sentence boundary when possible, otherwise word boundary
132
+ - Use `action=schema` to retrieve the full uncompressed schema for any tool on demand
133
+
134
+ ```json
135
+ "schemaCompression": {
136
+ "enabled": true,
137
+ "maxDescriptionLength": 80
138
+ }
139
+ ```
140
+
141
+ **Token savings example:** 30 Todoist tools: ~2800 tokens uncompressed -> ~1200 compressed (~57% reduction).
142
+
143
+ To get full details for a specific tool:
144
+ ```
145
+ mcp(server="todoist", action="schema", tool="find-tasks")
146
+ ```
147
+
148
+ Set `"enabled": false` to disable compression and return full descriptions.
149
+
122
150
  ### Modes
123
151
 
124
152
  | Mode | Tools exposed | Best for |
@@ -186,6 +214,9 @@ Built-in catalog with pre-configured servers:
186
214
  | wise | stdio | International payments |
187
215
  | tavily | stdio | AI-optimized web search |
188
216
  | apify | streamable-http | Web scraping and automation |
217
+ | atlassian | stdio | Confluence and Jira |
218
+ | chrome-devtools | stdio | Chrome browser automation |
219
+ | hostinger | sse | Web hosting management |
189
220
 
190
221
  ```bash
191
222
  mcp-bridge install todoist # Interactive setup with API key prompt
@@ -26,6 +26,12 @@ export type RouterDispatchResponse = {
26
26
  action: "call";
27
27
  tool: string;
28
28
  result: any;
29
+ } | {
30
+ server: string;
31
+ action: "schema";
32
+ tool: string;
33
+ schema: any;
34
+ description: string;
29
35
  } | {
30
36
  action: "status";
31
37
  servers: RouterServerStatus[];
@@ -2,6 +2,7 @@ import { SseTransport } from "./transport-sse.js";
2
2
  import { StdioTransport } from "./transport-stdio.js";
3
3
  import { StreamableHttpTransport } from "./transport-streamable-http.js";
4
4
  import { fetchToolsList, initializeProtocol, PACKAGE_VERSION } from "./protocol.js";
5
+ import { compressDescription } from "./schema-compression.js";
5
6
  const DEFAULT_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
6
7
  const DEFAULT_MAX_CONCURRENT = 5;
7
8
  export class McpRouter {
@@ -59,10 +60,28 @@ export class McpRouter {
59
60
  return this.error("connection_failed", `Failed to connect to ${server}: ${error instanceof Error ? error.message : String(error)}`);
60
61
  }
61
62
  }
63
+ if (normalizedAction === "schema") {
64
+ if (!tool) {
65
+ return this.error("invalid_params", "tool is required for action=schema");
66
+ }
67
+ try {
68
+ await this.getToolList(server);
69
+ }
70
+ catch (error) {
71
+ return this.error("connection_failed", `Failed to connect to ${server}: ${error instanceof Error ? error.message : String(error)}`);
72
+ }
73
+ const state = this.states.get(server);
74
+ const fullTool = state.fullToolsMap?.get(tool);
75
+ if (!fullTool) {
76
+ return this.error("unknown_tool", `Tool '${tool}' not found on server '${server}'`, state.toolNames);
77
+ }
78
+ return { server, action: "schema", tool, schema: fullTool.inputSchema, description: fullTool.description };
79
+ }
62
80
  if (normalizedAction === "refresh") {
63
81
  try {
64
82
  const state = await this.ensureConnected(server);
65
83
  state.toolsCache = undefined;
84
+ state.fullToolsMap = undefined;
66
85
  state.toolNames = [];
67
86
  const tools = await this.getToolList(server);
68
87
  return { server, action: "refresh", refreshed: true, tools };
@@ -72,7 +91,7 @@ export class McpRouter {
72
91
  }
73
92
  }
74
93
  if (normalizedAction !== "call") {
75
- return this.error("invalid_params", `action must be one of: list, call, refresh`);
94
+ return this.error("invalid_params", `action must be one of: list, call, refresh, schema`);
76
95
  }
77
96
  if (!tool) {
78
97
  return this.error("invalid_params", "tool is required for action=call");
@@ -116,9 +135,15 @@ export class McpRouter {
116
135
  }
117
136
  const tools = await fetchToolsList(state.transport);
118
137
  state.toolNames = tools.map((tool) => tool.name);
138
+ // Store full tool metadata for action=schema
139
+ state.fullToolsMap = new Map(tools.map((tool) => [tool.name, { description: tool.description || "", inputSchema: tool.inputSchema }]));
140
+ const compressionEnabled = this.clientConfig.schemaCompression?.enabled ?? true;
141
+ const maxLen = this.clientConfig.schemaCompression?.maxDescriptionLength ?? 80;
119
142
  state.toolsCache = tools.map((tool) => ({
120
143
  name: tool.name,
121
- description: tool.description || "",
144
+ description: compressionEnabled
145
+ ? compressDescription(tool.description || "", maxLen)
146
+ : (tool.description || ""),
122
147
  requiredParams: this.extractRequiredParams(tool)
123
148
  }));
124
149
  this.markUsed(server);
@@ -213,6 +238,7 @@ export class McpRouter {
213
238
  }
214
239
  state.initialized = false;
215
240
  state.toolsCache = undefined;
241
+ state.fullToolsMap = undefined;
216
242
  state.toolNames = [];
217
243
  }
218
244
  markUsed(server) {
@@ -240,6 +266,7 @@ export class McpRouter {
240
266
  return;
241
267
  state.initialized = false;
242
268
  state.toolsCache = undefined;
269
+ state.fullToolsMap = undefined;
243
270
  state.toolNames = [];
244
271
  };
245
272
  if (serverConfig.transport === "sse") {
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Compress tool descriptions to reduce token usage in router tool listings.
3
+ */
4
+ export declare function compressDescription(desc: string, maxLen?: number): string;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Compress tool descriptions to reduce token usage in router tool listings.
3
+ */
4
+ export function compressDescription(desc, maxLen = 80) {
5
+ if (desc.length <= maxLen) {
6
+ return desc;
7
+ }
8
+ // Try to cut at sentence boundary (". " before maxLen)
9
+ const searchArea = desc.slice(0, maxLen);
10
+ const sentenceEnd = searchArea.lastIndexOf(". ");
11
+ if (sentenceEnd > 0) {
12
+ return desc.slice(0, sentenceEnd + 1) + "\u2026";
13
+ }
14
+ // Fall back to word boundary
15
+ const lastSpace = searchArea.lastIndexOf(" ");
16
+ if (lastSpace > 0) {
17
+ return desc.slice(0, lastSpace) + "\u2026";
18
+ }
19
+ // No word boundary found, hard truncate
20
+ return desc.slice(0, maxLen) + "\u2026";
21
+ }
@@ -24,6 +24,10 @@ export interface McpClientConfig {
24
24
  requestTimeoutMs?: number;
25
25
  routerIdleTimeoutMs?: number;
26
26
  routerMaxConcurrent?: number;
27
+ schemaCompression?: {
28
+ enabled?: boolean;
29
+ maxDescriptionLength?: number;
30
+ };
27
31
  }
28
32
  export interface McpTool {
29
33
  name: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiwerk/mcp-bridge",
3
- "version": "1.2.3",
3
+ "version": "1.3.0",
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",