openclaw-mcp 1.2.0 → 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 +1 -0
- package/dist/index.js +296 -45
- package/docs/CNAME +1 -0
- package/docs/assets/og-image.png +0 -0
- package/docs/configuration.md +63 -20
- package/docs/index.html +653 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
[](https://github.com/freema/openclaw-mcp/actions/workflows/ci.yml)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
[](https://github.com/freema/openclaw-mcp/pkgs/container/openclaw-mcp)
|
|
7
|
+
[](https://openclaw-mcp.cloud)
|
|
7
8
|
|
|
8
9
|
<a href="https://glama.ai/mcp/servers/@freema/openclaw-mcp">
|
|
9
10
|
<img width="380" height="200" src="https://glama.ai/mcp/servers/@freema/openclaw-mcp/badge" />
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ 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.3.0";
|
|
9
9
|
var DEFAULT_OPENCLAW_URL = "http://127.0.0.1:18789";
|
|
10
10
|
var SERVER_ICON_SVG_BASE64 = "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjgiIGhlaWdodD0iMTI4IiB2aWV3Qm94PSIwIDAgMTI4IDEyOCIgZmlsbD0ibm9uZSI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJiZyIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzFhMWEyZSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzE2MjEzZSIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJjbGF3IiB4MT0iMCUiIHkxPSIwJSIgeDI9IjEwMCUiIHkyPSIxMDAlIj48c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSIjZmYzMzMzIi8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSIjY2MwMDAwIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHJlY3Qgd2lkdGg9IjEyOCIgaGVpZ2h0PSIxMjgiIHJ4PSIyNCIgZmlsbD0idXJsKCNiZykiLz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSg2NCA2NCkiIHN0cm9rZT0idXJsKCNjbGF3KSIgc3Ryb2tlLXdpZHRoPSI3IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGZpbGw9Im5vbmUiPjxwYXRoIGQ9Ik0tMjggLTM4YzAgMCAtMTAgMjAgMCAzMiIvPjxwYXRoIGQ9Ik0tMTIgLTQwYzAgMCAtNiAyMiA0IDM0Ii8+PHBhdGggZD0iTTI4IC0zOGMwIDAgMTAgMjAgMCAzMiIvPjxwYXRoIGQ9Ik0xMiAtNDBjMCAwIDYgMjIgLTQgMzQiLz48Y2lyY2xlIGN4PSIwIiBjeT0iMTAiIHI9IjIwIiBzdHJva2Utd2lkdGg9IjYiLz48cGF0aCBkPSJNLTEwIDR2LTQiIHN0cm9rZS13aWR0aD0iNCIvPjxwYXRoIGQ9Ik0xMCA0di00IiBzdHJva2Utd2lkdGg9IjQiLz48cGF0aCBkPSJNLTggMjBjNCA2IDEyIDYgMTYgMCIgc3Ryb2tlLXdpZHRoPSIzIi8+PC9nPjwvc3ZnPg==";
|
|
11
11
|
|
|
@@ -91,6 +91,45 @@ function parseArguments(version) {
|
|
|
91
91
|
description: "Allowed OAuth redirect URIs (comma-separated)",
|
|
92
92
|
default: process.env.MCP_REDIRECT_URIS || void 0
|
|
93
93
|
}).help().parseSync();
|
|
94
|
+
let instances;
|
|
95
|
+
const instancesEnv = process.env.OPENCLAW_INSTANCES;
|
|
96
|
+
if (instancesEnv) {
|
|
97
|
+
try {
|
|
98
|
+
const parsed = JSON.parse(instancesEnv);
|
|
99
|
+
if (!Array.isArray(parsed) || parsed.length === 0) {
|
|
100
|
+
throw new Error("OPENCLAW_INSTANCES must be a non-empty JSON array");
|
|
101
|
+
}
|
|
102
|
+
for (const item of parsed) {
|
|
103
|
+
if (!item || typeof item.name !== "string" || !item.name.trim()) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
'Each instance in OPENCLAW_INSTANCES must have a non-empty string "name"'
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
if (typeof item.url !== "string" || !item.url.trim()) {
|
|
109
|
+
throw new Error(`Instance "${item.name}": must have a non-empty string "url"`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
instances = parsed.map((cfg) => ({
|
|
113
|
+
...cfg,
|
|
114
|
+
timeout: cfg.timeout ?? argv.timeout
|
|
115
|
+
}));
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (error instanceof SyntaxError) {
|
|
118
|
+
throw new Error(`OPENCLAW_INSTANCES contains invalid JSON: ${error.message}`);
|
|
119
|
+
}
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
instances = [
|
|
124
|
+
{
|
|
125
|
+
name: "default",
|
|
126
|
+
url: argv["openclaw-url"],
|
|
127
|
+
token: argv["gateway-token"],
|
|
128
|
+
timeout: argv.timeout,
|
|
129
|
+
default: true
|
|
130
|
+
}
|
|
131
|
+
];
|
|
132
|
+
}
|
|
94
133
|
return {
|
|
95
134
|
openclawUrl: argv["openclaw-url"],
|
|
96
135
|
gatewayToken: argv["gateway-token"],
|
|
@@ -102,7 +141,8 @@ function parseArguments(version) {
|
|
|
102
141
|
clientId: argv["client-id"],
|
|
103
142
|
clientSecret: argv["client-secret"],
|
|
104
143
|
issuerUrl: argv["issuer-url"],
|
|
105
|
-
redirectUris: argv["redirect-uris"] ? argv["redirect-uris"].split(",").map((s) => s.trim()).filter(Boolean) : void 0
|
|
144
|
+
redirectUris: argv["redirect-uris"] ? argv["redirect-uris"].split(",").map((s) => s.trim()).filter(Boolean) : void 0,
|
|
145
|
+
instances
|
|
106
146
|
};
|
|
107
147
|
}
|
|
108
148
|
|
|
@@ -261,6 +301,120 @@ var OpenClawClient = class {
|
|
|
261
301
|
}
|
|
262
302
|
};
|
|
263
303
|
|
|
304
|
+
// src/openclaw/registry.ts
|
|
305
|
+
var INSTANCE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
|
|
306
|
+
var InstanceRegistry = class {
|
|
307
|
+
instances = /* @__PURE__ */ new Map();
|
|
308
|
+
defaultName;
|
|
309
|
+
constructor(configs) {
|
|
310
|
+
if (configs.length === 0) {
|
|
311
|
+
throw new Error("At least one OpenClaw instance must be configured");
|
|
312
|
+
}
|
|
313
|
+
const names = /* @__PURE__ */ new Set();
|
|
314
|
+
let explicitDefault;
|
|
315
|
+
for (const config of configs) {
|
|
316
|
+
if (!INSTANCE_NAME_RE.test(config.name)) {
|
|
317
|
+
throw new Error(
|
|
318
|
+
`Invalid instance name "${config.name}": must be 1-64 chars, alphanumeric/dashes/underscores, start with alphanumeric`
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
const parsed = new URL(config.url);
|
|
323
|
+
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
324
|
+
throw new Error(
|
|
325
|
+
`Instance "${config.name}": URL must use http or https (got ${parsed.protocol})`
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
} catch (error) {
|
|
329
|
+
if (error instanceof TypeError) {
|
|
330
|
+
throw new Error(`Instance "${config.name}": invalid URL "${config.url}"`);
|
|
331
|
+
}
|
|
332
|
+
throw error;
|
|
333
|
+
}
|
|
334
|
+
if (names.has(config.name)) {
|
|
335
|
+
throw new Error(`Duplicate instance name: "${config.name}"`);
|
|
336
|
+
}
|
|
337
|
+
names.add(config.name);
|
|
338
|
+
if (config.default) {
|
|
339
|
+
if (explicitDefault) {
|
|
340
|
+
throw new Error(
|
|
341
|
+
`Multiple default instances: "${explicitDefault}" and "${config.name}". Only one default is allowed.`
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
explicitDefault = config.name;
|
|
345
|
+
}
|
|
346
|
+
const client = new OpenClawClient(config.url, config.token, config.timeout);
|
|
347
|
+
this.instances.set(config.name, { config, client });
|
|
348
|
+
}
|
|
349
|
+
this.defaultName = explicitDefault ?? configs[0].name;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Get client by instance name. Returns undefined if not found.
|
|
353
|
+
*/
|
|
354
|
+
get(name) {
|
|
355
|
+
return this.instances.get(name)?.client;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Get the default client.
|
|
359
|
+
*/
|
|
360
|
+
getDefault() {
|
|
361
|
+
const entry = this.instances.get(this.defaultName);
|
|
362
|
+
if (!entry) {
|
|
363
|
+
throw new Error(`Default instance "${this.defaultName}" not found`);
|
|
364
|
+
}
|
|
365
|
+
return entry.client;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Get the default instance name.
|
|
369
|
+
*/
|
|
370
|
+
getDefaultName() {
|
|
371
|
+
return this.defaultName;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Resolve an optional instance name to a concrete client.
|
|
375
|
+
* Falls back to default when name is undefined.
|
|
376
|
+
*/
|
|
377
|
+
resolve(name) {
|
|
378
|
+
if (!name) {
|
|
379
|
+
return { name: this.defaultName, client: this.getDefault() };
|
|
380
|
+
}
|
|
381
|
+
const client = this.get(name);
|
|
382
|
+
if (!client) {
|
|
383
|
+
const available = this.listNames().join(", ");
|
|
384
|
+
throw new Error(`Unknown instance "${name}". Available: ${available}`);
|
|
385
|
+
}
|
|
386
|
+
return { name, client };
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* List instance names.
|
|
390
|
+
*/
|
|
391
|
+
listNames() {
|
|
392
|
+
return Array.from(this.instances.keys());
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* List instances with safe metadata (never exposes tokens).
|
|
396
|
+
*/
|
|
397
|
+
list() {
|
|
398
|
+
return Array.from(this.instances.entries()).map(([name, { config }]) => ({
|
|
399
|
+
name,
|
|
400
|
+
url: config.url,
|
|
401
|
+
isDefault: name === this.defaultName
|
|
402
|
+
}));
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Number of registered instances.
|
|
406
|
+
*/
|
|
407
|
+
get size() {
|
|
408
|
+
return this.instances.size;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Check if this is a single-instance (backward-compat) setup.
|
|
412
|
+
*/
|
|
413
|
+
get isSingleInstance() {
|
|
414
|
+
return this.instances.size === 1;
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
|
|
264
418
|
// src/server/tools-registration.ts
|
|
265
419
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
266
420
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -329,12 +483,16 @@ var openclawChatTool = {
|
|
|
329
483
|
session_id: {
|
|
330
484
|
type: "string",
|
|
331
485
|
description: "Optional session ID for conversation context"
|
|
486
|
+
},
|
|
487
|
+
instance: {
|
|
488
|
+
type: "string",
|
|
489
|
+
description: "Target OpenClaw instance name. Use openclaw_instances to list available instances. Defaults to the default instance."
|
|
332
490
|
}
|
|
333
491
|
},
|
|
334
492
|
required: ["message"]
|
|
335
493
|
}
|
|
336
494
|
};
|
|
337
|
-
async function handleOpenclawChat(
|
|
495
|
+
async function handleOpenclawChat(registry2, input) {
|
|
338
496
|
if (!validateInputIsObject(input)) {
|
|
339
497
|
return errorResponse("Invalid input: expected an object");
|
|
340
498
|
}
|
|
@@ -350,8 +508,17 @@ async function handleOpenclawChat(client2, input) {
|
|
|
350
508
|
}
|
|
351
509
|
sessionId = sidResult.value;
|
|
352
510
|
}
|
|
511
|
+
let instanceName;
|
|
512
|
+
if (input.instance !== void 0) {
|
|
513
|
+
const instResult = validateId(input.instance, "instance");
|
|
514
|
+
if (instResult.valid === false) {
|
|
515
|
+
return errorResponse(instResult.error);
|
|
516
|
+
}
|
|
517
|
+
instanceName = instResult.value;
|
|
518
|
+
}
|
|
353
519
|
try {
|
|
354
|
-
const
|
|
520
|
+
const { client } = registry2.resolve(instanceName);
|
|
521
|
+
const response = await client.chat(msgResult.value, sessionId);
|
|
355
522
|
return successResponse(response.response);
|
|
356
523
|
} catch (error) {
|
|
357
524
|
return errorResponse(error instanceof Error ? error.message : "Failed to chat with OpenClaw");
|
|
@@ -364,16 +531,33 @@ var openclawStatusTool = {
|
|
|
364
531
|
description: "Get OpenClaw gateway status and health information",
|
|
365
532
|
inputSchema: {
|
|
366
533
|
type: "object",
|
|
367
|
-
properties: {
|
|
534
|
+
properties: {
|
|
535
|
+
instance: {
|
|
536
|
+
type: "string",
|
|
537
|
+
description: "Target OpenClaw instance name. Defaults to the default instance."
|
|
538
|
+
}
|
|
539
|
+
}
|
|
368
540
|
}
|
|
369
541
|
};
|
|
370
|
-
async function handleOpenclawStatus(
|
|
542
|
+
async function handleOpenclawStatus(registry2, input) {
|
|
371
543
|
if (!validateInputIsObject(input)) {
|
|
372
544
|
return errorResponse("Invalid input: expected an object");
|
|
373
545
|
}
|
|
546
|
+
let instanceName;
|
|
547
|
+
if (input.instance !== void 0) {
|
|
548
|
+
const instResult = validateId(input.instance, "instance");
|
|
549
|
+
if (instResult.valid === false) {
|
|
550
|
+
return errorResponse(instResult.error);
|
|
551
|
+
}
|
|
552
|
+
instanceName = instResult.value;
|
|
553
|
+
}
|
|
374
554
|
try {
|
|
375
|
-
const
|
|
376
|
-
|
|
555
|
+
const resolved = registry2.resolve(instanceName);
|
|
556
|
+
const response = await resolved.client.health();
|
|
557
|
+
return jsonResponse({
|
|
558
|
+
...response,
|
|
559
|
+
instance: resolved.name
|
|
560
|
+
});
|
|
377
561
|
} catch (error) {
|
|
378
562
|
return errorResponse(
|
|
379
563
|
error instanceof Error ? error.message : "Failed to get status from OpenClaw"
|
|
@@ -381,6 +565,22 @@ async function handleOpenclawStatus(client2, input) {
|
|
|
381
565
|
}
|
|
382
566
|
}
|
|
383
567
|
|
|
568
|
+
// src/mcp/tools/instances.ts
|
|
569
|
+
var openclawInstancesTool = {
|
|
570
|
+
name: "openclaw_instances",
|
|
571
|
+
description: "List all configured OpenClaw instances. Shows instance names, URLs, and which is the default. Use instance names in other tools to target a specific OpenClaw gateway.",
|
|
572
|
+
inputSchema: {
|
|
573
|
+
type: "object",
|
|
574
|
+
properties: {}
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
async function handleOpenclawInstances(registry2, _input) {
|
|
578
|
+
return jsonResponse({
|
|
579
|
+
instances: registry2.list(),
|
|
580
|
+
total: registry2.size
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
|
|
384
584
|
// src/mcp/tasks/manager.ts
|
|
385
585
|
var MAX_TASKS = 1e3;
|
|
386
586
|
var CLEANUP_INTERVAL_MS = 10 * 60 * 1e3;
|
|
@@ -421,6 +621,7 @@ var TaskManager = class {
|
|
|
421
621
|
input: options.input,
|
|
422
622
|
createdAt: /* @__PURE__ */ new Date(),
|
|
423
623
|
sessionId: options.sessionId,
|
|
624
|
+
instanceId: options.instanceId,
|
|
424
625
|
priority: options.priority ?? 0
|
|
425
626
|
};
|
|
426
627
|
this.tasks.set(id, task);
|
|
@@ -444,6 +645,9 @@ var TaskManager = class {
|
|
|
444
645
|
if (filter?.sessionId) {
|
|
445
646
|
tasks = tasks.filter((t) => t.sessionId === filter.sessionId);
|
|
446
647
|
}
|
|
648
|
+
if (filter?.instanceId) {
|
|
649
|
+
tasks = tasks.filter((t) => t.instanceId === filter.instanceId);
|
|
650
|
+
}
|
|
447
651
|
return tasks.sort((a, b) => {
|
|
448
652
|
if (b.priority !== a.priority) return b.priority - a.priority;
|
|
449
653
|
return a.createdAt.getTime() - b.createdAt.getTime();
|
|
@@ -551,6 +755,10 @@ var openclawChatAsyncTool = {
|
|
|
551
755
|
priority: {
|
|
552
756
|
type: "number",
|
|
553
757
|
description: "Task priority (higher = processed first). Default: 0"
|
|
758
|
+
},
|
|
759
|
+
instance: {
|
|
760
|
+
type: "string",
|
|
761
|
+
description: "Target OpenClaw instance name. Defaults to the default instance."
|
|
554
762
|
}
|
|
555
763
|
},
|
|
556
764
|
required: ["message"]
|
|
@@ -572,7 +780,7 @@ var openclawTaskStatusTool = {
|
|
|
572
780
|
};
|
|
573
781
|
var openclawTaskListTool = {
|
|
574
782
|
name: "openclaw_task_list",
|
|
575
|
-
description: "List all tasks. Optionally filter by status or
|
|
783
|
+
description: "List all tasks. Optionally filter by status, session, or instance.",
|
|
576
784
|
inputSchema: {
|
|
577
785
|
type: "object",
|
|
578
786
|
properties: {
|
|
@@ -584,6 +792,10 @@ var openclawTaskListTool = {
|
|
|
584
792
|
session_id: {
|
|
585
793
|
type: "string",
|
|
586
794
|
description: "Filter by session ID"
|
|
795
|
+
},
|
|
796
|
+
instance: {
|
|
797
|
+
type: "string",
|
|
798
|
+
description: "Filter by instance name"
|
|
587
799
|
}
|
|
588
800
|
},
|
|
589
801
|
required: []
|
|
@@ -604,12 +816,20 @@ var openclawTaskCancelTool = {
|
|
|
604
816
|
}
|
|
605
817
|
};
|
|
606
818
|
var processorRunning = false;
|
|
607
|
-
var
|
|
608
|
-
async function processTask(task,
|
|
819
|
+
var processorRegistry = null;
|
|
820
|
+
async function processTask(task, registry2) {
|
|
609
821
|
taskManager.updateStatus(task.id, "running");
|
|
822
|
+
let client;
|
|
823
|
+
try {
|
|
824
|
+
client = registry2.resolve(task.instanceId).client;
|
|
825
|
+
} catch (error) {
|
|
826
|
+
const errorMsg = error instanceof Error ? error.message : "Instance not available";
|
|
827
|
+
taskManager.updateStatus(task.id, "failed", void 0, errorMsg);
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
610
830
|
try {
|
|
611
831
|
const input = task.input;
|
|
612
|
-
const response = await
|
|
832
|
+
const response = await client.chat(input.message, input.session_id);
|
|
613
833
|
taskManager.updateStatus(task.id, "completed", response.response);
|
|
614
834
|
} catch (error) {
|
|
615
835
|
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -617,26 +837,26 @@ async function processTask(task, client2) {
|
|
|
617
837
|
}
|
|
618
838
|
}
|
|
619
839
|
async function taskProcessor() {
|
|
620
|
-
if (!
|
|
840
|
+
if (!processorRegistry) return;
|
|
621
841
|
while (processorRunning) {
|
|
622
842
|
const task = taskManager.getNextPending();
|
|
623
843
|
if (task) {
|
|
624
|
-
await processTask(task,
|
|
844
|
+
await processTask(task, processorRegistry);
|
|
625
845
|
} else {
|
|
626
846
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
627
847
|
}
|
|
628
848
|
}
|
|
629
849
|
}
|
|
630
|
-
function startTaskProcessor(
|
|
850
|
+
function startTaskProcessor(registry2) {
|
|
631
851
|
if (processorRunning) return;
|
|
632
|
-
|
|
852
|
+
processorRegistry = registry2;
|
|
633
853
|
processorRunning = true;
|
|
634
854
|
taskProcessor().catch(() => {
|
|
635
855
|
processorRunning = false;
|
|
636
856
|
});
|
|
637
857
|
log("Task processor started");
|
|
638
858
|
}
|
|
639
|
-
async function handleOpenclawChatAsync(
|
|
859
|
+
async function handleOpenclawChatAsync(registry2, input) {
|
|
640
860
|
if (!validateInputIsObject(input)) {
|
|
641
861
|
return errorResponse("Invalid input: expected an object");
|
|
642
862
|
}
|
|
@@ -659,18 +879,35 @@ async function handleOpenclawChatAsync(client2, input) {
|
|
|
659
879
|
}
|
|
660
880
|
priority = input.priority;
|
|
661
881
|
}
|
|
662
|
-
|
|
882
|
+
let instanceId;
|
|
883
|
+
try {
|
|
884
|
+
let instanceName;
|
|
885
|
+
if (input.instance !== void 0) {
|
|
886
|
+
const instResult = validateId(input.instance, "instance");
|
|
887
|
+
if (instResult.valid === false) {
|
|
888
|
+
return errorResponse(instResult.error);
|
|
889
|
+
}
|
|
890
|
+
instanceName = instResult.value;
|
|
891
|
+
}
|
|
892
|
+
const resolved = registry2.resolve(instanceName);
|
|
893
|
+
instanceId = resolved.name;
|
|
894
|
+
} catch (error) {
|
|
895
|
+
return errorResponse(error instanceof Error ? error.message : "Invalid instance");
|
|
896
|
+
}
|
|
897
|
+
startTaskProcessor(registry2);
|
|
663
898
|
const task = taskManager.create({
|
|
664
899
|
type: "chat",
|
|
665
900
|
input: { message: msgResult.value, session_id: sessionId },
|
|
666
901
|
sessionId,
|
|
667
|
-
priority
|
|
902
|
+
priority,
|
|
903
|
+
instanceId
|
|
668
904
|
});
|
|
669
905
|
return successResponse(
|
|
670
906
|
JSON.stringify(
|
|
671
907
|
{
|
|
672
908
|
task_id: task.id,
|
|
673
909
|
status: task.status,
|
|
910
|
+
instance: instanceId,
|
|
674
911
|
message: "Task queued. Use openclaw_task_status to check progress."
|
|
675
912
|
},
|
|
676
913
|
null,
|
|
@@ -678,7 +915,7 @@ async function handleOpenclawChatAsync(client2, input) {
|
|
|
678
915
|
)
|
|
679
916
|
);
|
|
680
917
|
}
|
|
681
|
-
async function handleOpenclawTaskStatus(
|
|
918
|
+
async function handleOpenclawTaskStatus(_registry, input) {
|
|
682
919
|
if (!validateInputIsObject(input)) {
|
|
683
920
|
return errorResponse("Invalid input: expected an object");
|
|
684
921
|
}
|
|
@@ -695,6 +932,7 @@ async function handleOpenclawTaskStatus(_client, input) {
|
|
|
695
932
|
task_id: task.id,
|
|
696
933
|
type: task.type,
|
|
697
934
|
status: task.status,
|
|
935
|
+
instance: task.instanceId,
|
|
698
936
|
created_at: task.createdAt.toISOString()
|
|
699
937
|
};
|
|
700
938
|
if (task.startedAt) {
|
|
@@ -718,7 +956,7 @@ var VALID_TASK_STATUSES = [
|
|
|
718
956
|
"failed",
|
|
719
957
|
"cancelled"
|
|
720
958
|
];
|
|
721
|
-
async function handleOpenclawTaskList(
|
|
959
|
+
async function handleOpenclawTaskList(_registry, input) {
|
|
722
960
|
if (!validateInputIsObject(input)) {
|
|
723
961
|
return errorResponse("Invalid input: expected an object");
|
|
724
962
|
}
|
|
@@ -737,12 +975,21 @@ async function handleOpenclawTaskList(_client, input) {
|
|
|
737
975
|
}
|
|
738
976
|
session_id = sidResult.value;
|
|
739
977
|
}
|
|
740
|
-
|
|
978
|
+
let instanceFilter;
|
|
979
|
+
if (input.instance !== void 0) {
|
|
980
|
+
const instResult = validateId(input.instance, "instance");
|
|
981
|
+
if (instResult.valid === false) {
|
|
982
|
+
return errorResponse(instResult.error);
|
|
983
|
+
}
|
|
984
|
+
instanceFilter = instResult.value;
|
|
985
|
+
}
|
|
986
|
+
const tasks = taskManager.list({ status, sessionId: session_id, instanceId: instanceFilter });
|
|
741
987
|
const stats = taskManager.stats();
|
|
742
988
|
const taskList = tasks.map((t) => ({
|
|
743
989
|
task_id: t.id,
|
|
744
990
|
type: t.type,
|
|
745
991
|
status: t.status,
|
|
992
|
+
instance: t.instanceId,
|
|
746
993
|
priority: t.priority,
|
|
747
994
|
created_at: t.createdAt.toISOString(),
|
|
748
995
|
has_result: t.status === "completed" && !!t.result
|
|
@@ -758,7 +1005,7 @@ async function handleOpenclawTaskList(_client, input) {
|
|
|
758
1005
|
)
|
|
759
1006
|
);
|
|
760
1007
|
}
|
|
761
|
-
async function handleOpenclawTaskCancel(
|
|
1008
|
+
async function handleOpenclawTaskCancel(_registry, input) {
|
|
762
1009
|
if (!validateInputIsObject(input)) {
|
|
763
1010
|
return errorResponse("Invalid input: expected an object");
|
|
764
1011
|
}
|
|
@@ -813,14 +1060,15 @@ function createMcpServer(deps2) {
|
|
|
813
1060
|
return server;
|
|
814
1061
|
}
|
|
815
1062
|
function registerTools(server, deps2) {
|
|
816
|
-
const {
|
|
1063
|
+
const { registry: registry2 } = deps2;
|
|
817
1064
|
const toolHandlers = /* @__PURE__ */ new Map([
|
|
818
|
-
["openclaw_chat", (input) => handleOpenclawChat(
|
|
819
|
-
["openclaw_status", (input) => handleOpenclawStatus(
|
|
820
|
-
["openclaw_chat_async", (input) => handleOpenclawChatAsync(
|
|
821
|
-
["openclaw_task_status", (input) => handleOpenclawTaskStatus(
|
|
822
|
-
["openclaw_task_list", (input) => handleOpenclawTaskList(
|
|
823
|
-
["openclaw_task_cancel", (input) => handleOpenclawTaskCancel(
|
|
1065
|
+
["openclaw_chat", (input) => handleOpenclawChat(registry2, input)],
|
|
1066
|
+
["openclaw_status", (input) => handleOpenclawStatus(registry2, input)],
|
|
1067
|
+
["openclaw_chat_async", (input) => handleOpenclawChatAsync(registry2, input)],
|
|
1068
|
+
["openclaw_task_status", (input) => handleOpenclawTaskStatus(registry2, input)],
|
|
1069
|
+
["openclaw_task_list", (input) => handleOpenclawTaskList(registry2, input)],
|
|
1070
|
+
["openclaw_task_cancel", (input) => handleOpenclawTaskCancel(registry2, input)],
|
|
1071
|
+
["openclaw_instances", (input) => handleOpenclawInstances(registry2, input)]
|
|
824
1072
|
]);
|
|
825
1073
|
const allTools = [
|
|
826
1074
|
openclawChatTool,
|
|
@@ -828,7 +1076,8 @@ function registerTools(server, deps2) {
|
|
|
828
1076
|
openclawChatAsyncTool,
|
|
829
1077
|
openclawTaskStatusTool,
|
|
830
1078
|
openclawTaskListTool,
|
|
831
|
-
openclawTaskCancelTool
|
|
1079
|
+
openclawTaskCancelTool,
|
|
1080
|
+
openclawInstancesTool
|
|
832
1081
|
];
|
|
833
1082
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
834
1083
|
return { tools: allTools };
|
|
@@ -938,9 +1187,9 @@ var OpenClawAuthProvider = class {
|
|
|
938
1187
|
/**
|
|
939
1188
|
* Auto-approve: generate auth code and redirect immediately.
|
|
940
1189
|
*/
|
|
941
|
-
async authorize(
|
|
1190
|
+
async authorize(client, params, res) {
|
|
942
1191
|
const code = randomUUID();
|
|
943
|
-
this.codes.set(code, { client
|
|
1192
|
+
this.codes.set(code, { client, params, createdAt: Date.now() });
|
|
944
1193
|
const searchParams = new URLSearchParams({ code });
|
|
945
1194
|
if (params.state !== void 0) {
|
|
946
1195
|
searchParams.set("state", params.state);
|
|
@@ -957,13 +1206,13 @@ var OpenClawAuthProvider = class {
|
|
|
957
1206
|
}
|
|
958
1207
|
return codeData.params.codeChallenge;
|
|
959
1208
|
}
|
|
960
|
-
async exchangeAuthorizationCode(
|
|
1209
|
+
async exchangeAuthorizationCode(client, authorizationCode, _codeVerifier, _redirectUri, resource) {
|
|
961
1210
|
const codeData = this.codes.get(authorizationCode);
|
|
962
1211
|
if (!codeData || Date.now() - codeData.createdAt > AUTH_CODE_TTL_MS) {
|
|
963
1212
|
if (codeData) this.codes.delete(authorizationCode);
|
|
964
1213
|
throw new InvalidRequestError("Invalid authorization code");
|
|
965
1214
|
}
|
|
966
|
-
if (codeData.client.client_id !==
|
|
1215
|
+
if (codeData.client.client_id !== client.client_id) {
|
|
967
1216
|
throw new InvalidRequestError("Authorization code was not issued to this client");
|
|
968
1217
|
}
|
|
969
1218
|
this.codes.delete(authorizationCode);
|
|
@@ -972,13 +1221,13 @@ var OpenClawAuthProvider = class {
|
|
|
972
1221
|
const scopes = codeData.params.scopes || [];
|
|
973
1222
|
this.tokens.set(accessToken, {
|
|
974
1223
|
token: accessToken,
|
|
975
|
-
clientId:
|
|
1224
|
+
clientId: client.client_id,
|
|
976
1225
|
scopes,
|
|
977
1226
|
expiresAt: Date.now() + TOKEN_TTL_MS,
|
|
978
1227
|
resource: resource || codeData.params.resource
|
|
979
1228
|
});
|
|
980
1229
|
this.refreshTokens.set(refreshToken, {
|
|
981
|
-
clientId:
|
|
1230
|
+
clientId: client.client_id,
|
|
982
1231
|
scopes,
|
|
983
1232
|
expiresAt: Date.now() + REFRESH_TOKEN_TTL_MS,
|
|
984
1233
|
resource: resource || codeData.params.resource
|
|
@@ -991,13 +1240,13 @@ var OpenClawAuthProvider = class {
|
|
|
991
1240
|
scope: scopes.join(" ")
|
|
992
1241
|
};
|
|
993
1242
|
}
|
|
994
|
-
async exchangeRefreshToken(
|
|
1243
|
+
async exchangeRefreshToken(client, refreshToken, scopes, resource) {
|
|
995
1244
|
const data = this.refreshTokens.get(refreshToken);
|
|
996
1245
|
if (!data || data.expiresAt < Date.now()) {
|
|
997
1246
|
if (data) this.refreshTokens.delete(refreshToken);
|
|
998
1247
|
throw new InvalidRequestError("Invalid refresh token");
|
|
999
1248
|
}
|
|
1000
|
-
if (data.clientId !==
|
|
1249
|
+
if (data.clientId !== client.client_id) {
|
|
1001
1250
|
throw new InvalidRequestError("Refresh token was not issued to this client");
|
|
1002
1251
|
}
|
|
1003
1252
|
this.refreshTokens.delete(refreshToken);
|
|
@@ -1006,13 +1255,13 @@ var OpenClawAuthProvider = class {
|
|
|
1006
1255
|
const tokenScopes = scopes || data.scopes;
|
|
1007
1256
|
this.tokens.set(accessToken, {
|
|
1008
1257
|
token: accessToken,
|
|
1009
|
-
clientId:
|
|
1258
|
+
clientId: client.client_id,
|
|
1010
1259
|
scopes: tokenScopes,
|
|
1011
1260
|
expiresAt: Date.now() + TOKEN_TTL_MS,
|
|
1012
1261
|
resource: resource || data.resource
|
|
1013
1262
|
});
|
|
1014
1263
|
this.refreshTokens.set(newRefreshToken, {
|
|
1015
|
-
clientId:
|
|
1264
|
+
clientId: client.client_id,
|
|
1016
1265
|
scopes: tokenScopes,
|
|
1017
1266
|
expiresAt: Date.now() + REFRESH_TOKEN_TTL_MS,
|
|
1018
1267
|
resource: resource || data.resource
|
|
@@ -1281,18 +1530,20 @@ async function createSSEServer(config, deps2) {
|
|
|
1281
1530
|
|
|
1282
1531
|
// src/index.ts
|
|
1283
1532
|
var args = parseArguments(SERVER_VERSION);
|
|
1284
|
-
var
|
|
1533
|
+
var registry = new InstanceRegistry(args.instances);
|
|
1285
1534
|
var deps = {
|
|
1286
|
-
|
|
1535
|
+
registry,
|
|
1287
1536
|
serverName: SERVER_NAME,
|
|
1288
1537
|
serverVersion: SERVER_VERSION
|
|
1289
1538
|
};
|
|
1290
1539
|
async function main() {
|
|
1291
1540
|
log(`Starting ${SERVER_NAME} v${SERVER_VERSION}`);
|
|
1292
|
-
log(`OpenClaw URL: ${args.openclawUrl}`);
|
|
1293
1541
|
log(`Transport: ${args.transport}`);
|
|
1294
|
-
log(`Gateway token: ${args.gatewayToken ? "configured" : "not set"}`);
|
|
1295
1542
|
log(`Request timeout: ${args.timeout}ms`);
|
|
1543
|
+
for (const instance of registry.list()) {
|
|
1544
|
+
const defaultLabel = instance.isDefault ? " (default)" : "";
|
|
1545
|
+
log(`Instance "${instance.name}": ${instance.url}${defaultLabel}`);
|
|
1546
|
+
}
|
|
1296
1547
|
if (args.transport === "sse") {
|
|
1297
1548
|
const sseConfig = {
|
|
1298
1549
|
port: args.port,
|
package/docs/CNAME
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
openclaw-mcp.cloud
|
|
Binary file
|