openclaw-mcp 1.3.0 → 1.4.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 +77 -0
- package/dist/index.js +120 -14
- package/docs/configuration.md +65 -4
- package/docs/deployment.md +18 -0
- package/docs/index.html +122 -2
- package/docs/installation.md +2 -0
- package/docs/logging.md +13 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -47,6 +47,7 @@ services:
|
|
|
47
47
|
environment:
|
|
48
48
|
- OPENCLAW_URL=http://host.docker.internal:18789
|
|
49
49
|
- OPENCLAW_GATEWAY_TOKEN=${OPENCLAW_GATEWAY_TOKEN}
|
|
50
|
+
- OPENCLAW_MODEL=openclaw
|
|
50
51
|
- AUTH_ENABLED=true
|
|
51
52
|
- MCP_CLIENT_ID=openclaw
|
|
52
53
|
- MCP_CLIENT_SECRET=${MCP_CLIENT_SECRET}
|
|
@@ -88,6 +89,7 @@ Add to your Claude Desktop config:
|
|
|
88
89
|
"env": {
|
|
89
90
|
"OPENCLAW_URL": "http://127.0.0.1:18789",
|
|
90
91
|
"OPENCLAW_GATEWAY_TOKEN": "your-gateway-token",
|
|
92
|
+
"OPENCLAW_MODEL": "openclaw",
|
|
91
93
|
"OPENCLAW_TIMEOUT_MS": "300000"
|
|
92
94
|
}
|
|
93
95
|
}
|
|
@@ -141,6 +143,7 @@ See [Installation Guide](docs/installation.md) for details.
|
|
|
141
143
|
|------|-------------|
|
|
142
144
|
| `openclaw_chat` | Send messages to OpenClaw and get responses |
|
|
143
145
|
| `openclaw_status` | Check OpenClaw gateway health |
|
|
146
|
+
| `openclaw_instances` | List all configured OpenClaw instances |
|
|
144
147
|
|
|
145
148
|
### Async Tools (for long-running operations)
|
|
146
149
|
|
|
@@ -151,6 +154,80 @@ See [Installation Guide](docs/installation.md) for details.
|
|
|
151
154
|
| `openclaw_task_list` | List all tasks with filtering |
|
|
152
155
|
| `openclaw_task_cancel` | Cancel a pending task |
|
|
153
156
|
|
|
157
|
+
## Multi-Instance Mode
|
|
158
|
+
|
|
159
|
+
Orchestrate multiple OpenClaw gateways from a single MCP server. One bridge, many claws — route requests to prod, staging, dev, or whatever you name them (lobster-supreme and the-claw-abides are perfectly valid names).
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
┌──────────────────────────────────────────────────────────────────────┐
|
|
163
|
+
│ Claude.ai / Claude Desktop │
|
|
164
|
+
│ (MCP Client) │
|
|
165
|
+
└──────────────────────┬───────────────────────────────────────────────┘
|
|
166
|
+
│
|
|
167
|
+
▼
|
|
168
|
+
┌──────────────────────────────────────────────────────────────────────┐
|
|
169
|
+
│ OpenClaw MCP Bridge Server │
|
|
170
|
+
│ │
|
|
171
|
+
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
172
|
+
│ │ Instance │ │ Instance │ │ Instance │ │
|
|
173
|
+
│ │ Registry │ │ Resolver │ │ Validator │ │
|
|
174
|
+
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
|
175
|
+
│ │ │ │ │
|
|
176
|
+
│ ┌──────┴─────────────────┴──────────────────┴───────┐ │
|
|
177
|
+
│ │ Per-Instance OpenClaw Clients │ │
|
|
178
|
+
│ │ (separate auth, timeout, URL per instance) │ │
|
|
179
|
+
│ └────────┬──────────────┬──────────────┬────────────┘ │
|
|
180
|
+
└───────────┼──────────────┼──────────────┼────────────────────────────┘
|
|
181
|
+
│ │ │
|
|
182
|
+
▼ ▼ ▼
|
|
183
|
+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
184
|
+
│ 🦞 prod │ │ 🦞 staging │ │ 🦞 dev │
|
|
185
|
+
│ (default) │ │ │ │ │
|
|
186
|
+
│ :18789 │ │ :18789 │ │ :18789 │
|
|
187
|
+
│ OpenClaw GW │ │ OpenClaw GW │ │ OpenClaw GW │
|
|
188
|
+
└──────────────┘ └──────────────┘ └──────────────┘
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Setup
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
OPENCLAW_INSTANCES='[
|
|
195
|
+
{"name": "prod", "url": "http://prod:18789", "token": "tok1", "default": true},
|
|
196
|
+
{"name": "staging", "url": "http://staging:18789", "token": "tok2"},
|
|
197
|
+
{"name": "dev", "url": "http://dev:18789", "token": "tok3"}
|
|
198
|
+
]'
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Usage
|
|
202
|
+
|
|
203
|
+
All tools accept an optional `instance` parameter to target a specific gateway:
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
# Chat with staging instance
|
|
207
|
+
openclaw_chat message="Deploy status?" instance="staging"
|
|
208
|
+
|
|
209
|
+
# Check health of prod
|
|
210
|
+
openclaw_status instance="prod"
|
|
211
|
+
|
|
212
|
+
# List all configured instances
|
|
213
|
+
openclaw_instances
|
|
214
|
+
|
|
215
|
+
# Async task targeting dev
|
|
216
|
+
openclaw_chat_async message="Run tests" instance="dev"
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
When `instance` is omitted, the default instance is used. Each instance has its own auth token, timeout, and URL — fully isolated.
|
|
220
|
+
|
|
221
|
+
### Key Features
|
|
222
|
+
|
|
223
|
+
- **Zero-migration upgrade** — existing single-instance deployments work without any config change
|
|
224
|
+
- **Per-instance isolation** — separate auth tokens, timeouts, and URLs
|
|
225
|
+
- **Dynamic routing** — Claude picks the right instance per request
|
|
226
|
+
- **Task tracking** — async tasks remember which instance they target
|
|
227
|
+
- **Security** — tokens are never exposed via `openclaw_instances`
|
|
228
|
+
|
|
229
|
+
See [Configuration — Multi-Instance Mode](docs/configuration.md#multi-instance-mode) for the full reference.
|
|
230
|
+
|
|
154
231
|
## Documentation
|
|
155
232
|
|
|
156
233
|
- [Installation](docs/installation.md) — Setup for Claude Desktop & Claude.ai
|
package/dist/index.js
CHANGED
|
@@ -5,11 +5,13 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
5
5
|
|
|
6
6
|
// src/config/constants.ts
|
|
7
7
|
var SERVER_NAME = "openclaw-mcp";
|
|
8
|
-
var SERVER_VERSION = "1.
|
|
8
|
+
var SERVER_VERSION = "1.4.0";
|
|
9
9
|
var DEFAULT_OPENCLAW_URL = "http://127.0.0.1:18789";
|
|
10
|
+
var DEFAULT_MODEL = "openclaw";
|
|
10
11
|
var SERVER_ICON_SVG_BASE64 = "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjgiIGhlaWdodD0iMTI4IiB2aWV3Qm94PSIwIDAgMTI4IDEyOCIgZmlsbD0ibm9uZSI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJiZyIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzFhMWEyZSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzE2MjEzZSIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJjbGF3IiB4MT0iMCUiIHkxPSIwJSIgeDI9IjEwMCUiIHkyPSIxMDAlIj48c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSIjZmYzMzMzIi8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSIjY2MwMDAwIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHJlY3Qgd2lkdGg9IjEyOCIgaGVpZ2h0PSIxMjgiIHJ4PSIyNCIgZmlsbD0idXJsKCNiZykiLz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSg2NCA2NCkiIHN0cm9rZT0idXJsKCNjbGF3KSIgc3Ryb2tlLXdpZHRoPSI3IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGZpbGw9Im5vbmUiPjxwYXRoIGQ9Ik0tMjggLTM4YzAgMCAtMTAgMjAgMCAzMiIvPjxwYXRoIGQ9Ik0tMTIgLTQwYzAgMCAtNiAyMiA0IDM0Ii8+PHBhdGggZD0iTTI4IC0zOGMwIDAgMTAgMjAgMCAzMiIvPjxwYXRoIGQ9Ik0xMiAtNDBjMCAwIDYgMjIgLTQgMzQiLz48Y2lyY2xlIGN4PSIwIiBjeT0iMTAiIHI9IjIwIiBzdHJva2Utd2lkdGg9IjYiLz48cGF0aCBkPSJNLTEwIDR2LTQiIHN0cm9rZS13aWR0aD0iNCIvPjxwYXRoIGQ9Ik0xMCA0di00IiBzdHJva2Utd2lkdGg9IjQiLz48cGF0aCBkPSJNLTggMjBjNCA2IDEyIDYgMTYgMCIgc3Ryb2tlLXdpZHRoPSIzIi8+PC9nPjwvc3ZnPg==";
|
|
11
12
|
|
|
12
13
|
// src/utils/logger.ts
|
|
14
|
+
var debugEnabled = false;
|
|
13
15
|
var SENSITIVE_PATTERNS = [
|
|
14
16
|
/Bearer\s+[A-Za-z0-9\-._~+/]+=*/gi,
|
|
15
17
|
/api[_-]?key["\s:=]+[A-Za-z0-9\-._~+/]{8,}/gi,
|
|
@@ -27,6 +29,19 @@ function sanitizeLogMessage(message) {
|
|
|
27
29
|
function log(message) {
|
|
28
30
|
console.error(`[openclaw-mcp] ${sanitizeLogMessage(message)}`);
|
|
29
31
|
}
|
|
32
|
+
function setDebugEnabled(enabled) {
|
|
33
|
+
debugEnabled = enabled;
|
|
34
|
+
}
|
|
35
|
+
function isDebugEnabled() {
|
|
36
|
+
return debugEnabled;
|
|
37
|
+
}
|
|
38
|
+
function logDebug(messageOrFactory) {
|
|
39
|
+
if (!debugEnabled) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const message = typeof messageOrFactory === "function" ? messageOrFactory() : messageOrFactory;
|
|
43
|
+
console.error(`[openclaw-mcp] DEBUG: ${sanitizeLogMessage(message)}`);
|
|
44
|
+
}
|
|
30
45
|
function logError(message, error) {
|
|
31
46
|
console.error(`[openclaw-mcp] ERROR: ${sanitizeLogMessage(message)}`);
|
|
32
47
|
if (error) {
|
|
@@ -51,6 +66,11 @@ function parseArguments(version) {
|
|
|
51
66
|
type: "string",
|
|
52
67
|
description: "Bearer token for OpenClaw gateway authentication",
|
|
53
68
|
default: process.env.OPENCLAW_GATEWAY_TOKEN || void 0
|
|
69
|
+
}).option("model", {
|
|
70
|
+
alias: "m",
|
|
71
|
+
type: "string",
|
|
72
|
+
description: "Model name for chat completions",
|
|
73
|
+
default: process.env.OPENCLAW_MODEL || DEFAULT_MODEL
|
|
54
74
|
}).option("transport", {
|
|
55
75
|
alias: "t",
|
|
56
76
|
type: "string",
|
|
@@ -70,6 +90,10 @@ function parseArguments(version) {
|
|
|
70
90
|
type: "number",
|
|
71
91
|
description: "Request timeout in milliseconds",
|
|
72
92
|
default: parseInt(process.env.OPENCLAW_TIMEOUT_MS || "120000", 10)
|
|
93
|
+
}).option("debug", {
|
|
94
|
+
type: "boolean",
|
|
95
|
+
description: "Enable debug logging",
|
|
96
|
+
default: process.env.DEBUG === "true" || process.env.NODE_ENV === "development"
|
|
73
97
|
}).option("auth", {
|
|
74
98
|
type: "boolean",
|
|
75
99
|
description: "Enable OAuth authentication (SSE mode)",
|
|
@@ -90,6 +114,10 @@ function parseArguments(version) {
|
|
|
90
114
|
type: "string",
|
|
91
115
|
description: "Allowed OAuth redirect URIs (comma-separated)",
|
|
92
116
|
default: process.env.MCP_REDIRECT_URIS || void 0
|
|
117
|
+
}).option("allow-dcr", {
|
|
118
|
+
type: "boolean",
|
|
119
|
+
description: "Allow OAuth Dynamic Client Registration (Cursor/Windsurf compatibility, dev-only)",
|
|
120
|
+
default: process.env.MCP_DANGEROUSLY_ALLOW_DCR === "true"
|
|
93
121
|
}).help().parseSync();
|
|
94
122
|
let instances;
|
|
95
123
|
const instancesEnv = process.env.OPENCLAW_INSTANCES;
|
|
@@ -133,15 +161,18 @@ function parseArguments(version) {
|
|
|
133
161
|
return {
|
|
134
162
|
openclawUrl: argv["openclaw-url"],
|
|
135
163
|
gatewayToken: argv["gateway-token"],
|
|
164
|
+
model: argv.model,
|
|
136
165
|
transport: argv.transport,
|
|
137
166
|
port: argv.port,
|
|
138
167
|
host: argv.host,
|
|
139
168
|
timeout: argv.timeout,
|
|
169
|
+
debug: argv.debug,
|
|
140
170
|
authEnabled: argv.auth,
|
|
141
171
|
clientId: argv["client-id"],
|
|
142
172
|
clientSecret: argv["client-secret"],
|
|
143
173
|
issuerUrl: argv["issuer-url"],
|
|
144
174
|
redirectUris: argv["redirect-uris"] ? argv["redirect-uris"].split(",").map((s) => s.trim()).filter(Boolean) : void 0,
|
|
175
|
+
allowDcr: argv["allow-dcr"],
|
|
145
176
|
instances
|
|
146
177
|
};
|
|
147
178
|
}
|
|
@@ -171,14 +202,17 @@ var OpenClawApiError = class extends OpenClawError {
|
|
|
171
202
|
// src/openclaw/client.ts
|
|
172
203
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
173
204
|
var MAX_RESPONSE_SIZE_BYTES = 10 * 1024 * 1024;
|
|
205
|
+
var MAX_DEBUG_BODY_LENGTH = 4096;
|
|
174
206
|
var OpenClawClient = class {
|
|
175
207
|
baseUrl;
|
|
176
208
|
gatewayToken;
|
|
177
209
|
timeoutMs;
|
|
178
|
-
|
|
210
|
+
model;
|
|
211
|
+
constructor(baseUrl, gatewayToken, timeoutMs = DEFAULT_TIMEOUT_MS, model = "openclaw") {
|
|
179
212
|
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
180
213
|
this.gatewayToken = gatewayToken;
|
|
181
214
|
this.timeoutMs = timeoutMs;
|
|
215
|
+
this.model = model;
|
|
182
216
|
}
|
|
183
217
|
buildHeaders() {
|
|
184
218
|
const headers = {
|
|
@@ -189,8 +223,16 @@ var OpenClawClient = class {
|
|
|
189
223
|
}
|
|
190
224
|
return headers;
|
|
191
225
|
}
|
|
226
|
+
truncateForLog(value) {
|
|
227
|
+
if (value.length <= MAX_DEBUG_BODY_LENGTH) return value;
|
|
228
|
+
return value.slice(0, MAX_DEBUG_BODY_LENGTH) + `... (truncated, ${value.length} chars total)`;
|
|
229
|
+
}
|
|
192
230
|
async request(path, options = {}) {
|
|
193
231
|
const url = `${this.baseUrl}${path}`;
|
|
232
|
+
logDebug(() => `Request: ${options.method ?? "GET"} ${url}`);
|
|
233
|
+
if (options.body) {
|
|
234
|
+
logDebug(() => `Request body: ${this.truncateForLog(options.body)}`);
|
|
235
|
+
}
|
|
194
236
|
const controller = new AbortController();
|
|
195
237
|
const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
196
238
|
try {
|
|
@@ -203,11 +245,23 @@ var OpenClawClient = class {
|
|
|
203
245
|
}
|
|
204
246
|
});
|
|
205
247
|
if (!response.ok) {
|
|
248
|
+
if (isDebugEnabled()) {
|
|
249
|
+
const contentLength2 = response.headers.get("content-length");
|
|
250
|
+
if (!contentLength2 || parseInt(contentLength2, 10) <= MAX_RESPONSE_SIZE_BYTES) {
|
|
251
|
+
const errorBody = await response.text();
|
|
252
|
+
if (errorBody.length <= MAX_RESPONSE_SIZE_BYTES) {
|
|
253
|
+
logDebug(
|
|
254
|
+
() => `Response error (${response.status}): ${this.truncateForLog(errorBody)}`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
206
259
|
throw new OpenClawApiError(
|
|
207
260
|
`API request failed: ${response.status} ${response.statusText}`,
|
|
208
261
|
response.status
|
|
209
262
|
);
|
|
210
263
|
}
|
|
264
|
+
logDebug(() => `Response: ${response.status} ${response.statusText}`);
|
|
211
265
|
const contentLength = response.headers.get("content-length");
|
|
212
266
|
if (contentLength && parseInt(contentLength, 10) > MAX_RESPONSE_SIZE_BYTES) {
|
|
213
267
|
throw new OpenClawApiError("Response exceeds maximum allowed size (10MB)", 413);
|
|
@@ -276,7 +330,7 @@ var OpenClawClient = class {
|
|
|
276
330
|
*/
|
|
277
331
|
async chat(message, sessionId) {
|
|
278
332
|
const body = {
|
|
279
|
-
model:
|
|
333
|
+
model: this.model,
|
|
280
334
|
messages: [{ role: "user", content: message }],
|
|
281
335
|
max_tokens: 4096
|
|
282
336
|
};
|
|
@@ -306,7 +360,7 @@ var INSTANCE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
|
|
|
306
360
|
var InstanceRegistry = class {
|
|
307
361
|
instances = /* @__PURE__ */ new Map();
|
|
308
362
|
defaultName;
|
|
309
|
-
constructor(configs) {
|
|
363
|
+
constructor(configs, model) {
|
|
310
364
|
if (configs.length === 0) {
|
|
311
365
|
throw new Error("At least one OpenClaw instance must be configured");
|
|
312
366
|
}
|
|
@@ -343,7 +397,7 @@ var InstanceRegistry = class {
|
|
|
343
397
|
}
|
|
344
398
|
explicitDefault = config.name;
|
|
345
399
|
}
|
|
346
|
-
const client = new OpenClawClient(config.url, config.token, config.timeout);
|
|
400
|
+
const client = new OpenClawClient(config.url, config.token, config.timeout, model);
|
|
347
401
|
this.instances.set(config.name, { config, client });
|
|
348
402
|
}
|
|
349
403
|
this.defaultName = explicitDefault ?? configs[0].name;
|
|
@@ -1123,8 +1177,15 @@ var ALLOW_ANY_REDIRECT = new Proxy([], {
|
|
|
1123
1177
|
return Reflect.get(target, prop);
|
|
1124
1178
|
}
|
|
1125
1179
|
});
|
|
1180
|
+
var MAX_DYNAMIC_CLIENTS = 100;
|
|
1126
1181
|
var OpenClawClientsStore = class {
|
|
1127
1182
|
client;
|
|
1183
|
+
dynamicClients = /* @__PURE__ */ new Map();
|
|
1184
|
+
// Assigned conditionally in the constructor. The MCP SDK probes
|
|
1185
|
+
// `clientsStore.registerClient` at router-setup time and only advertises
|
|
1186
|
+
// `/register` when the property is defined, so we must NOT define a no-op
|
|
1187
|
+
// method on the prototype — it has to be instance-level and gated on config.
|
|
1188
|
+
registerClient;
|
|
1128
1189
|
constructor(config) {
|
|
1129
1190
|
if (config.clientId && config.clientSecret) {
|
|
1130
1191
|
const redirectUris = config.redirectUris && config.redirectUris.length > 0 ? config.redirectUris : ALLOW_ANY_REDIRECT;
|
|
@@ -1139,16 +1200,25 @@ var OpenClawClientsStore = class {
|
|
|
1139
1200
|
client_id_issued_at: Math.floor(Date.now() / 1e3)
|
|
1140
1201
|
};
|
|
1141
1202
|
}
|
|
1203
|
+
if (config.allowDynamicRegistration) {
|
|
1204
|
+
this.registerClient = (client) => {
|
|
1205
|
+
if (this.dynamicClients.size >= MAX_DYNAMIC_CLIENTS) {
|
|
1206
|
+
const oldestKey = this.dynamicClients.keys().next().value;
|
|
1207
|
+
if (oldestKey !== void 0) {
|
|
1208
|
+
this.dynamicClients.delete(oldestKey);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
this.dynamicClients.set(client.client_id, client);
|
|
1212
|
+
return client;
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1142
1215
|
}
|
|
1143
1216
|
async getClient(clientId) {
|
|
1144
1217
|
if (this.client && this.client.client_id === clientId) {
|
|
1145
1218
|
return this.client;
|
|
1146
1219
|
}
|
|
1147
|
-
return
|
|
1220
|
+
return this.dynamicClients.get(clientId);
|
|
1148
1221
|
}
|
|
1149
|
-
// No registerClient — dynamic client registration is intentionally disabled.
|
|
1150
|
-
// Only the pre-configured client from MCP_CLIENT_ID + MCP_CLIENT_SECRET can authenticate.
|
|
1151
|
-
// This prevents anyone who knows the server URL from self-registering and bypassing auth.
|
|
1152
1222
|
};
|
|
1153
1223
|
var OpenClawAuthProvider = class {
|
|
1154
1224
|
clientsStore;
|
|
@@ -1287,9 +1357,15 @@ var OpenClawAuthProvider = class {
|
|
|
1287
1357
|
resource: tokenData.resource
|
|
1288
1358
|
};
|
|
1289
1359
|
}
|
|
1290
|
-
async revokeToken(
|
|
1291
|
-
this.tokens.
|
|
1292
|
-
|
|
1360
|
+
async revokeToken(client, request) {
|
|
1361
|
+
const tokenData = this.tokens.get(request.token);
|
|
1362
|
+
if (tokenData && tokenData.clientId === client.client_id) {
|
|
1363
|
+
this.tokens.delete(request.token);
|
|
1364
|
+
}
|
|
1365
|
+
const refreshData = this.refreshTokens.get(request.token);
|
|
1366
|
+
if (refreshData && refreshData.clientId === client.client_id) {
|
|
1367
|
+
this.refreshTokens.delete(request.token);
|
|
1368
|
+
}
|
|
1293
1369
|
}
|
|
1294
1370
|
};
|
|
1295
1371
|
|
|
@@ -1530,7 +1606,14 @@ async function createSSEServer(config, deps2) {
|
|
|
1530
1606
|
|
|
1531
1607
|
// src/index.ts
|
|
1532
1608
|
var args = parseArguments(SERVER_VERSION);
|
|
1533
|
-
|
|
1609
|
+
setDebugEnabled(args.debug);
|
|
1610
|
+
var trimmedModel = args.model.trim();
|
|
1611
|
+
if (!trimmedModel) {
|
|
1612
|
+
logError('OPENCLAW_MODEL / --model must be a non-empty string. Default is "openclaw".');
|
|
1613
|
+
process.exit(1);
|
|
1614
|
+
}
|
|
1615
|
+
args.model = trimmedModel;
|
|
1616
|
+
var registry = new InstanceRegistry(args.instances, args.model);
|
|
1534
1617
|
var deps = {
|
|
1535
1618
|
registry,
|
|
1536
1619
|
serverName: SERVER_NAME,
|
|
@@ -1538,8 +1621,12 @@ var deps = {
|
|
|
1538
1621
|
};
|
|
1539
1622
|
async function main() {
|
|
1540
1623
|
log(`Starting ${SERVER_NAME} v${SERVER_VERSION}`);
|
|
1624
|
+
log(`Model: ${args.model}`);
|
|
1541
1625
|
log(`Transport: ${args.transport}`);
|
|
1542
1626
|
log(`Request timeout: ${args.timeout}ms`);
|
|
1627
|
+
if (args.debug) {
|
|
1628
|
+
log("Debug logging: enabled");
|
|
1629
|
+
}
|
|
1543
1630
|
for (const instance of registry.list()) {
|
|
1544
1631
|
const defaultLabel = instance.isDefault ? " (default)" : "";
|
|
1545
1632
|
log(`Instance "${instance.name}": ${instance.url}${defaultLabel}`);
|
|
@@ -1567,7 +1654,8 @@ async function main() {
|
|
|
1567
1654
|
sseConfig.authConfig = {
|
|
1568
1655
|
clientId: args.clientId,
|
|
1569
1656
|
clientSecret: args.clientSecret,
|
|
1570
|
-
redirectUris: args.redirectUris
|
|
1657
|
+
redirectUris: args.redirectUris,
|
|
1658
|
+
allowDynamicRegistration: args.allowDcr
|
|
1571
1659
|
};
|
|
1572
1660
|
log(`OAuth client ID: ${args.clientId}`);
|
|
1573
1661
|
if (!args.redirectUris || args.redirectUris.length === 0) {
|
|
@@ -1575,6 +1663,24 @@ async function main() {
|
|
|
1575
1663
|
"WARNING: MCP_REDIRECT_URIS not set \u2014 any redirect_uri will be accepted. Set MCP_REDIRECT_URIS for production."
|
|
1576
1664
|
);
|
|
1577
1665
|
}
|
|
1666
|
+
if (args.allowDcr) {
|
|
1667
|
+
const isLoopback = args.host === "127.0.0.1" || args.host === "localhost" || args.host === "::1";
|
|
1668
|
+
const publicOptIn = process.env.MCP_DANGEROUSLY_ALLOW_DCR_PUBLIC === "true";
|
|
1669
|
+
if (!isLoopback && !publicOptIn) {
|
|
1670
|
+
logError(
|
|
1671
|
+
`MCP_DANGEROUSLY_ALLOW_DCR=true is set but HOST="${args.host}" is not loopback. Dynamic Client Registration combined with a publicly reachable bind allows anyone on the network to obtain a token. Bind to 127.0.0.1, or set MCP_DANGEROUSLY_ALLOW_DCR_PUBLIC=true to override. Refusing to start.`
|
|
1672
|
+
);
|
|
1673
|
+
process.exit(1);
|
|
1674
|
+
}
|
|
1675
|
+
log(
|
|
1676
|
+
"WARNING: MCP_DANGEROUSLY_ALLOW_DCR is enabled \u2014 OAuth Dynamic Client Registration is open. Any client that can reach this server may self-register and obtain tokens. Use for local development only."
|
|
1677
|
+
);
|
|
1678
|
+
if (publicOptIn) {
|
|
1679
|
+
log(
|
|
1680
|
+
"WARNING: MCP_DANGEROUSLY_ALLOW_DCR_PUBLIC=true \u2014 DCR is exposed on a non-loopback bind. You have explicitly accepted the risk."
|
|
1681
|
+
);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1578
1684
|
} else if (args.authEnabled && !args.clientId) {
|
|
1579
1685
|
logError("AUTH_ENABLED=true but MCP_CLIENT_ID is not set. Refusing to start without auth.");
|
|
1580
1686
|
process.exit(1);
|
package/docs/configuration.md
CHANGED
|
@@ -11,11 +11,27 @@ All configuration can be done via environment variables. Copy `.env.example` to
|
|
|
11
11
|
| `OPENCLAW_URL` | OpenClaw gateway URL | `http://127.0.0.1:18789` |
|
|
12
12
|
| `OPENCLAW_GATEWAY_TOKEN` | Bearer token for gateway authentication | (none) |
|
|
13
13
|
| `OPENCLAW_TIMEOUT_MS` | Request timeout in milliseconds | `120000` (2 min) |
|
|
14
|
+
| `OPENCLAW_MODEL` | Model name for chat completions | `openclaw` |
|
|
14
15
|
|
|
15
16
|
### Multi-Instance Mode
|
|
16
17
|
|
|
17
18
|
Orchestrate multiple OpenClaw gateways from a single MCP server. Set `OPENCLAW_INSTANCES` as a JSON array — when present, it takes precedence over `OPENCLAW_URL` / `OPENCLAW_GATEWAY_TOKEN`.
|
|
18
19
|
|
|
20
|
+
```
|
|
21
|
+
┌─────────────────┐
|
|
22
|
+
│ Claude Client │
|
|
23
|
+
└────────┬────────┘
|
|
24
|
+
│
|
|
25
|
+
┌─────────────▼─────────────┐
|
|
26
|
+
│ OpenClaw MCP Bridge │
|
|
27
|
+
│ │
|
|
28
|
+
│ instance="prod" ──────────────► OpenClaw GW (prod)
|
|
29
|
+
│ instance="staging" ────────────► OpenClaw GW (staging)
|
|
30
|
+
│ instance="dev" ───────────────► OpenClaw GW (dev)
|
|
31
|
+
│ (no instance) ──► default ──► OpenClaw GW (prod)
|
|
32
|
+
└────────────────────────────┘
|
|
33
|
+
```
|
|
34
|
+
|
|
19
35
|
| Variable | Description | Default |
|
|
20
36
|
| -------------------- | ------------------------------ | ----------------------------- |
|
|
21
37
|
| `OPENCLAW_INSTANCES` | JSON array of instance configs | (none — single-instance mode) |
|
|
@@ -45,11 +61,44 @@ Each instance object supports:
|
|
|
45
61
|
All gateway-facing tools (`openclaw_chat`, `openclaw_status`, `openclaw_chat_async`) accept an optional `instance` parameter. When omitted, the default instance is used.
|
|
46
62
|
|
|
47
63
|
```
|
|
64
|
+
# Target a specific instance
|
|
48
65
|
openclaw_chat message="Hello" instance="staging"
|
|
49
|
-
|
|
66
|
+
|
|
67
|
+
# Check health of a specific gateway
|
|
68
|
+
openclaw_status instance="prod"
|
|
69
|
+
|
|
70
|
+
# List all available instances (names, URLs, default — tokens are never exposed)
|
|
71
|
+
openclaw_instances
|
|
72
|
+
|
|
73
|
+
# Async tasks also support instance targeting
|
|
74
|
+
openclaw_chat_async message="Run migration" instance="dev"
|
|
75
|
+
|
|
76
|
+
# Filter task list by instance
|
|
77
|
+
openclaw_task_list instance="staging"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**How instance resolution works:**
|
|
81
|
+
|
|
82
|
+
1. If `instance` parameter is provided → use that instance
|
|
83
|
+
2. If `instance` is omitted → use the instance marked as `default`
|
|
84
|
+
3. If no instance is marked as default → the first instance in the array is used
|
|
85
|
+
|
|
86
|
+
Each instance gets its own isolated HTTP client with independent auth token, timeout, and base URL. Async tasks store the target instance ID so results are always routed correctly.
|
|
87
|
+
|
|
88
|
+
**Docker Compose with multi-instance:**
|
|
89
|
+
|
|
90
|
+
```yaml
|
|
91
|
+
services:
|
|
92
|
+
mcp-bridge:
|
|
93
|
+
image: ghcr.io/freema/openclaw-mcp:latest
|
|
94
|
+
environment:
|
|
95
|
+
- OPENCLAW_INSTANCES=[{"name":"prod","url":"http://prod-gw:18789","token":"tok1","default":true},{"name":"staging","url":"http://staging-gw:18789","token":"tok2"}]
|
|
96
|
+
- AUTH_ENABLED=true
|
|
97
|
+
- MCP_CLIENT_ID=openclaw
|
|
98
|
+
- MCP_CLIENT_SECRET=${MCP_CLIENT_SECRET}
|
|
50
99
|
```
|
|
51
100
|
|
|
52
|
-
**Backward compatibility:** When `OPENCLAW_INSTANCES` is not set, the server creates a single `"default"` instance from `OPENCLAW_URL` + `OPENCLAW_GATEWAY_TOKEN`. Existing deployments work without any configuration change.
|
|
101
|
+
**Backward compatibility:** When `OPENCLAW_INSTANCES` is not set, the server creates a single `"default"` instance from `OPENCLAW_URL` + `OPENCLAW_GATEWAY_TOKEN`. Existing deployments work without any configuration change — zero migration required.
|
|
53
102
|
|
|
54
103
|
### Server Settings (SSE transport)
|
|
55
104
|
|
|
@@ -84,10 +133,12 @@ The server uses the MCP SDK's built-in OAuth 2.1 server with authorization code
|
|
|
84
133
|
| `MCP_CLIENT_SECRET` | OAuth client secret | When auth enabled |
|
|
85
134
|
| `MCP_ISSUER_URL` | OAuth issuer URL override (e.g., `https://mcp.example.com`) | When behind HTTPS proxy |
|
|
86
135
|
| `MCP_REDIRECT_URIS` | Allowed redirect URIs (comma-separated) | Recommended for production |
|
|
136
|
+
| `MCP_DANGEROUSLY_ALLOW_DCR` | Enable Dynamic Client Registration (`true`/`false`) | Dev only (see below) |
|
|
137
|
+
| `MCP_DANGEROUSLY_ALLOW_DCR_PUBLIC` | Escape hatch to allow DCR on non-loopback binds | Never, unless you mean it |
|
|
87
138
|
|
|
88
139
|
**Client ID validation rules:**
|
|
89
140
|
|
|
90
|
-
- 3
|
|
141
|
+
- 3-64 characters
|
|
91
142
|
- Alphanumeric, dashes, underscores only
|
|
92
143
|
- Must start with a letter or digit
|
|
93
144
|
|
|
@@ -104,4 +155,14 @@ When auth is enabled, the server exposes these OAuth 2.1 endpoints:
|
|
|
104
155
|
- `POST /token` — Token exchange (requires client_secret)
|
|
105
156
|
- `POST /revoke` — Token revocation
|
|
106
157
|
|
|
107
|
-
Dynamic client registration is **disabled** — only the pre-configured client (from `MCP_CLIENT_ID` + `MCP_CLIENT_SECRET`) can authenticate. This prevents anyone who knows the server URL from self-registering and bypassing auth.
|
|
158
|
+
Dynamic client registration is **disabled by default** — only the pre-configured client (from `MCP_CLIENT_ID` + `MCP_CLIENT_SECRET`) can authenticate. This prevents anyone who knows the server URL from self-registering and bypassing auth.
|
|
159
|
+
|
|
160
|
+
#### Cursor / Windsurf compatibility (dev only)
|
|
161
|
+
|
|
162
|
+
Cursor and Windsurf only support MCP servers that expose OAuth 2.0 Dynamic Client Registration (RFC 7591). To let them connect, set `MCP_DANGEROUSLY_ALLOW_DCR=true`. The server will then advertise a `/register` endpoint and accept ad-hoc client registrations (kept in an in-memory FIFO store, capped at 100 entries).
|
|
163
|
+
|
|
164
|
+
`MCP_CLIENT_ID` and `MCP_CLIENT_SECRET` are still required — DCR augments the pre-configured client, it does not replace it. If you are only running Cursor locally you can use any valid values for them; they simply remain unused.
|
|
165
|
+
|
|
166
|
+
**This is dev-only.** With DCR enabled alongside the server's auto-approve authorization flow, any client that can reach the server can register itself and obtain a token. To prevent accidental exposure the server refuses to start when `MCP_DANGEROUSLY_ALLOW_DCR=true` and `HOST` is not loopback (`127.0.0.1`, `localhost`, or `::1`). If you genuinely need DCR on a non-loopback bind (e.g., inside a trusted private network), also set `MCP_DANGEROUSLY_ALLOW_DCR_PUBLIC=true`.
|
|
167
|
+
|
|
168
|
+
For production with Claude.ai, keep DCR disabled and use the pre-configured `MCP_CLIENT_ID` / `MCP_CLIENT_SECRET`.
|
package/docs/deployment.md
CHANGED
|
@@ -17,6 +17,8 @@ services:
|
|
|
17
17
|
environment:
|
|
18
18
|
- OPENCLAW_URL=${OPENCLAW_URL:-http://host.docker.internal:18789}
|
|
19
19
|
- OPENCLAW_GATEWAY_TOKEN=${OPENCLAW_GATEWAY_TOKEN:-}
|
|
20
|
+
- OPENCLAW_MODEL=${OPENCLAW_MODEL:-openclaw}
|
|
21
|
+
- DEBUG=${DEBUG:-false}
|
|
20
22
|
- AUTH_ENABLED=${AUTH_ENABLED:-true}
|
|
21
23
|
- MCP_CLIENT_ID=${MCP_CLIENT_ID:-openclaw}
|
|
22
24
|
- MCP_CLIENT_SECRET=${MCP_CLIENT_SECRET:-}
|
|
@@ -169,8 +171,24 @@ The MCP bridge communicates with the OpenClaw gateway via its OpenAI-compatible
|
|
|
169
171
|
|
|
170
172
|
Without this, the MCP bridge will receive `405 Method Not Allowed` from the gateway.
|
|
171
173
|
|
|
174
|
+
## Bridge / Gateway Compatibility
|
|
175
|
+
|
|
176
|
+
| MCP Bridge | Gateway | Result |
|
|
177
|
+
|------------|---------|--------|
|
|
178
|
+
| ≤ 1.2.2 | ≥ 2026.3.24 | `400 Bad Request` — bridge sends `model: "claude-opus-4-5"`, gateway rejects it |
|
|
179
|
+
| ≥ 1.3.0 | ≥ 2026.3.24 | Works — bridge defaults to `model: "openclaw"` |
|
|
180
|
+
| ≥ 1.3.0 | older | Works — set `OPENCLAW_MODEL` to whatever the older gateway expects |
|
|
181
|
+
|
|
182
|
+
If you're running a non-standard gateway setup with custom agent routing, set `OPENCLAW_MODEL=openclaw/<agentId>` to match your configuration.
|
|
183
|
+
|
|
172
184
|
## Troubleshooting
|
|
173
185
|
|
|
186
|
+
### `400 Bad Request` from gateway on `openclaw_chat`
|
|
187
|
+
|
|
188
|
+
Gateway versions 2026.3.24+ require `model: "openclaw"` (or `"openclaw/<agentId>"`). The MCP bridge defaults to `"openclaw"` since v1.3.0. If you're using an older bridge version, upgrade or set `OPENCLAW_MODEL=openclaw`. If you need custom model routing, set `OPENCLAW_MODEL` to the value your gateway expects.
|
|
189
|
+
|
|
190
|
+
To diagnose, enable debug logging (`DEBUG=true`) which logs the outgoing request body and gateway error responses.
|
|
191
|
+
|
|
174
192
|
### `405 Method Not Allowed` from gateway
|
|
175
193
|
|
|
176
194
|
The OpenClaw gateway's HTTP chat completions endpoint is disabled by default. Enable it in `openclaw.json` — see [Gateway Prerequisites](#openclaw-gateway-prerequisites) above.
|
package/docs/index.html
CHANGED
|
@@ -129,6 +129,7 @@ pre, code { font-family: 'JetBrains Mono', monospace; }
|
|
|
129
129
|
display: none; gap: 2rem; font-size: 0.875rem;
|
|
130
130
|
text-transform: uppercase; letter-spacing: 0.05em; font-weight: 500; color: var(--text);
|
|
131
131
|
}
|
|
132
|
+
.hamburger { display: none; }
|
|
132
133
|
.nav-links a:hover { color: var(--accent); transition: color 0.2s; }
|
|
133
134
|
.nav-gh { display: flex; align-items: center; gap: 0; font-size: 0.75rem; font-weight: 700; }
|
|
134
135
|
.nav-gh-btn {
|
|
@@ -288,8 +289,106 @@ pre, code { font-family: 'JetBrains Mono', monospace; }
|
|
|
288
289
|
text-align: center; font-size: 10px; color: rgba(160,160,160,0.5);
|
|
289
290
|
}
|
|
290
291
|
|
|
291
|
-
/* ===
|
|
292
|
-
|
|
292
|
+
/* === Mobile Hamburger Menu === */
|
|
293
|
+
.hamburger {
|
|
294
|
+
display: flex; flex-direction: column; gap: 5px; padding: 0.5rem;
|
|
295
|
+
cursor: pointer; z-index: 60; background: none; border: none;
|
|
296
|
+
}
|
|
297
|
+
.hamburger span {
|
|
298
|
+
display: block; width: 22px; height: 2px; background: #fff;
|
|
299
|
+
transition: transform 0.3s, opacity 0.3s;
|
|
300
|
+
}
|
|
301
|
+
.hamburger.active span:nth-child(1) { transform: rotate(45deg) translate(5px, 5px); }
|
|
302
|
+
.hamburger.active span:nth-child(2) { opacity: 0; }
|
|
303
|
+
.hamburger.active span:nth-child(3) { transform: rotate(-45deg) translate(5px, -5px); }
|
|
304
|
+
|
|
305
|
+
.mobile-menu {
|
|
306
|
+
display: none; position: fixed; inset: 0; z-index: 55;
|
|
307
|
+
background: rgba(8,8,8,0.97); backdrop-filter: blur(16px);
|
|
308
|
+
-webkit-backdrop-filter: blur(16px);
|
|
309
|
+
flex-direction: column; align-items: center; justify-content: center; gap: 2rem;
|
|
310
|
+
font-size: 1.25rem; text-transform: uppercase; letter-spacing: 0.05em; font-weight: 700;
|
|
311
|
+
}
|
|
312
|
+
.mobile-menu.active { display: flex; }
|
|
313
|
+
.mobile-menu a { color: var(--text); transition: color 0.2s; padding: 0.5rem 1rem; }
|
|
314
|
+
.mobile-menu a:hover, .mobile-menu a:active { color: var(--accent); }
|
|
315
|
+
|
|
316
|
+
/* === Mobile-first fixes === */
|
|
317
|
+
@media (max-width: 639px) {
|
|
318
|
+
.hamburger { display: flex; }
|
|
319
|
+
.nav-gh { display: none; }
|
|
320
|
+
|
|
321
|
+
.hero { padding: 7rem 0 3rem; }
|
|
322
|
+
.hero h1 { font-size: 2.25rem; }
|
|
323
|
+
.hero-sub { font-size: 1rem; margin-top: 1.25rem; }
|
|
324
|
+
.hero-buttons { margin-top: 2rem; gap: 0.75rem; }
|
|
325
|
+
.btn-primary, .btn-secondary { padding: 0.875rem 1.5rem; font-size: 1rem; }
|
|
326
|
+
.stats-bar { margin-top: 2.5rem; gap: 1rem; font-size: 0.75rem; }
|
|
327
|
+
|
|
328
|
+
.how-section, .features-section, .tools-section, .quickstart-section, .cta-section {
|
|
329
|
+
padding: 4rem 0;
|
|
330
|
+
}
|
|
331
|
+
.how-section h2, .features-section h2, .tools-section h2,
|
|
332
|
+
.quickstart-section h2, .cta-section h2 {
|
|
333
|
+
font-size: 1.75rem; margin-bottom: 2rem;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.how-step { padding: 1.5rem; }
|
|
337
|
+
.how-step-num { font-size: 1.5rem; }
|
|
338
|
+
.how-step h3 { font-size: 1.1rem; }
|
|
339
|
+
|
|
340
|
+
.features-grid { gap: 1rem; }
|
|
341
|
+
.feature-card { padding: 1.5rem; }
|
|
342
|
+
.feature-card h3 { font-size: 1.1rem; }
|
|
343
|
+
|
|
344
|
+
.tools-table { font-size: 0.75rem; }
|
|
345
|
+
.tools-table th, .tools-table td { padding: 0.75rem 0.5rem; }
|
|
346
|
+
.tools-col-header h3 { font-size: 1.25rem; }
|
|
347
|
+
|
|
348
|
+
.tab-btn { padding: 0.75rem 1rem; font-size: 0.65rem; }
|
|
349
|
+
.tab-content { padding: 1rem; }
|
|
350
|
+
.tab-content pre code { font-size: 0.7rem; word-break: break-all; white-space: pre-wrap; }
|
|
351
|
+
|
|
352
|
+
.cta-btn-primary, .cta-btn-secondary {
|
|
353
|
+
padding: 1rem 2rem; font-size: 1rem; width: 100%; text-align: center;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.footer { padding: 2.5rem 0; }
|
|
357
|
+
.footer-links { gap: 1.25rem; }
|
|
358
|
+
.footer-bottom { margin-top: 2rem; }
|
|
359
|
+
|
|
360
|
+
.plugin-cards { gap: 0.75rem !important; }
|
|
361
|
+
|
|
362
|
+
/* Prevent horizontal overflow */
|
|
363
|
+
.terminal-body { font-size: 0.75rem; padding: 1rem; }
|
|
364
|
+
.container { padding: 0 1rem; }
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/* === Tablet (640px - 1023px) === */
|
|
368
|
+
@media (min-width: 640px) and (max-width: 1023px) {
|
|
369
|
+
.hamburger { display: flex; }
|
|
370
|
+
.nav-links { display: none; }
|
|
371
|
+
.hero h1 { font-size: 4rem; }
|
|
372
|
+
.hero-sub { font-size: 1.25rem; }
|
|
373
|
+
.hero-buttons { flex-direction: row; }
|
|
374
|
+
.btn-primary, .btn-secondary { width: auto; }
|
|
375
|
+
.how-grid { grid-template-columns: repeat(2, 1fr); }
|
|
376
|
+
.how-step:nth-child(2) { border-right: none; }
|
|
377
|
+
.features-grid { grid-template-columns: repeat(2, 1fr); }
|
|
378
|
+
.tools-grid { grid-template-columns: repeat(2, 1fr); }
|
|
379
|
+
.cta-section h2 { font-size: 2.5rem; }
|
|
380
|
+
.cta-buttons { flex-direction: row; }
|
|
381
|
+
.footer-inner { flex-direction: row; justify-content: space-between; align-items: center; }
|
|
382
|
+
.plugin-cards { grid-template-columns: repeat(2, 1fr) !important; }
|
|
383
|
+
|
|
384
|
+
.how-section, .features-section, .tools-section, .quickstart-section, .cta-section {
|
|
385
|
+
padding: 5rem 0;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/* === Desktop (1024px+) === */
|
|
390
|
+
@media (min-width: 1024px) {
|
|
391
|
+
.hamburger { display: none; }
|
|
293
392
|
.nav-links { display: flex; }
|
|
294
393
|
.hero h1 { font-size: 6rem; }
|
|
295
394
|
.hero-sub { font-size: 1.5rem; }
|
|
@@ -322,6 +421,9 @@ pre, code { font-family: 'JetBrains Mono', monospace; }
|
|
|
322
421
|
<a href="#quickstart">Quick start</a>
|
|
323
422
|
<a href="#plugin">Plugin</a>
|
|
324
423
|
</div>
|
|
424
|
+
<button class="hamburger" aria-label="Menu" onclick="document.querySelector('.mobile-menu').classList.toggle('active');this.classList.toggle('active')">
|
|
425
|
+
<span></span><span></span><span></span>
|
|
426
|
+
</button>
|
|
325
427
|
<div class="nav-gh">
|
|
326
428
|
<a class="nav-gh-btn" href="https://github.com/freema/openclaw-mcp" target="_blank" rel="noopener">
|
|
327
429
|
<svg viewBox="0 0 16 16" width="16" height="16" fill="white"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>
|
|
@@ -339,6 +441,16 @@ pre, code { font-family: 'JetBrains Mono', monospace; }
|
|
|
339
441
|
</div>
|
|
340
442
|
</nav>
|
|
341
443
|
|
|
444
|
+
<!-- Mobile Menu Overlay -->
|
|
445
|
+
<div class="mobile-menu" id="mobile-menu">
|
|
446
|
+
<a href="#how" onclick="closeMobileMenu()">How it works</a>
|
|
447
|
+
<a href="#features" onclick="closeMobileMenu()">Features</a>
|
|
448
|
+
<a href="#tools" onclick="closeMobileMenu()">Tools</a>
|
|
449
|
+
<a href="#quickstart" onclick="closeMobileMenu()">Quick start</a>
|
|
450
|
+
<a href="#plugin" onclick="closeMobileMenu()">Plugin</a>
|
|
451
|
+
<a href="https://github.com/freema/openclaw-mcp" target="_blank" rel="noopener" style="color:var(--accent)">GitHub</a>
|
|
452
|
+
</div>
|
|
453
|
+
|
|
342
454
|
<!-- Hero -->
|
|
343
455
|
<header class="hero">
|
|
344
456
|
<div class="container">
|
|
@@ -610,6 +722,14 @@ npx openclaw-mcp --transport sse --port 3000</code></pre>
|
|
|
610
722
|
</div>
|
|
611
723
|
</footer>
|
|
612
724
|
|
|
725
|
+
<!-- Mobile Menu Script -->
|
|
726
|
+
<script>
|
|
727
|
+
function closeMobileMenu() {
|
|
728
|
+
document.querySelector('.mobile-menu').classList.remove('active');
|
|
729
|
+
document.querySelector('.hamburger').classList.remove('active');
|
|
730
|
+
}
|
|
731
|
+
</script>
|
|
732
|
+
|
|
613
733
|
<!-- Tab Switching Script -->
|
|
614
734
|
<script>
|
|
615
735
|
(function() {
|
package/docs/installation.md
CHANGED
|
@@ -91,9 +91,11 @@ openclaw-mcp --help
|
|
|
91
91
|
Options:
|
|
92
92
|
--openclaw-url, -u OpenClaw gateway URL [default: "http://127.0.0.1:18789"]
|
|
93
93
|
--gateway-token Bearer token for gateway [default: none]
|
|
94
|
+
--model, -m Model name for chat [default: "openclaw"]
|
|
94
95
|
--transport, -t Transport mode [choices: "stdio", "sse"] [default: "stdio"]
|
|
95
96
|
--port, -p Port for SSE server [default: 3000]
|
|
96
97
|
--host Host for SSE server [default: "0.0.0.0"]
|
|
98
|
+
--debug Enable debug logging [default: false]
|
|
97
99
|
--auth Enable OAuth [default: false]
|
|
98
100
|
--client-id MCP OAuth client ID [env: MCP_CLIENT_ID]
|
|
99
101
|
--client-secret MCP OAuth client secret [env: MCP_CLIENT_SECRET]
|
package/docs/logging.md
CHANGED
|
@@ -36,13 +36,24 @@ The MCP server logs operational events to **stderr** using the `[openclaw-mcp]`
|
|
|
36
36
|
- Invalid client configuration (missing secrets, bad client ID format)
|
|
37
37
|
- Session errors
|
|
38
38
|
|
|
39
|
-
### What Is NOT Logged
|
|
39
|
+
### What Is NOT Logged (Info/Error levels)
|
|
40
40
|
|
|
41
41
|
- **Message content** — user messages and OpenClaw responses are never logged
|
|
42
42
|
- **Authentication tokens** — Bearer tokens, client secrets, gateway tokens
|
|
43
43
|
- **Request/response bodies** — only error messages, not full payloads
|
|
44
44
|
- **User-identifiable information** — no IPs, user agents, or personal data
|
|
45
45
|
|
|
46
|
+
### Debug Level (`DEBUG=true`)
|
|
47
|
+
|
|
48
|
+
> **Warning:** Debug mode is a diagnostic tool. It logs request and response payloads which may contain user message content. Do not enable in production under normal operation — use it only for active troubleshooting, then disable it.
|
|
49
|
+
|
|
50
|
+
When debug logging is enabled, the following **are** logged for troubleshooting:
|
|
51
|
+
|
|
52
|
+
- **Request bodies** — outgoing payloads sent to the gateway (truncated to 4096 chars)
|
|
53
|
+
- **Error response bodies** — full error responses from the gateway (truncated to 4096 chars)
|
|
54
|
+
|
|
55
|
+
Credentials are still redacted by the sanitization layer. Headers (including Authorization) are never logged, even in debug mode.
|
|
56
|
+
|
|
46
57
|
## Sensitive Data Redaction
|
|
47
58
|
|
|
48
59
|
The logger automatically redacts patterns that look like credentials:
|
|
@@ -99,4 +110,4 @@ DEBUG=true # Explicit debug flag
|
|
|
99
110
|
NODE_ENV=development # Development mode
|
|
100
111
|
```
|
|
101
112
|
|
|
102
|
-
Debug logs include
|
|
113
|
+
Debug logs include request/response bodies for troubleshooting (truncated to 4096 chars). Credentials are still redacted, and headers (including Authorization) are never logged.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Model Context Protocol (MCP) server for OpenClaw AI assistant integration",
|
|
5
5
|
"author": "Tomáš Grasl <https://www.tomasgrasl.cz/>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"dev": "tsx watch src/index.ts",
|
|
15
15
|
"build": "tsup",
|
|
16
16
|
"start": "node dist/index.js",
|
|
17
|
-
"clean": "
|
|
17
|
+
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
18
18
|
"typecheck": "tsc --noEmit",
|
|
19
19
|
"lint": "eslint src --ext .ts",
|
|
20
20
|
"lint:fix": "eslint src --ext .ts --fix",
|