openclaw-mcp 1.2.1 → 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 -48
- 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
|
@@ -4,11 +4,8 @@
|
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
5
|
|
|
6
6
|
// src/config/constants.ts
|
|
7
|
-
import { createRequire } from "module";
|
|
8
|
-
var require2 = createRequire(import.meta.url);
|
|
9
|
-
var pkg = require2("../../package.json");
|
|
10
7
|
var SERVER_NAME = "openclaw-mcp";
|
|
11
|
-
var SERVER_VERSION =
|
|
8
|
+
var SERVER_VERSION = "1.3.0";
|
|
12
9
|
var DEFAULT_OPENCLAW_URL = "http://127.0.0.1:18789";
|
|
13
10
|
var SERVER_ICON_SVG_BASE64 = "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjgiIGhlaWdodD0iMTI4IiB2aWV3Qm94PSIwIDAgMTI4IDEyOCIgZmlsbD0ibm9uZSI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJiZyIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzFhMWEyZSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzE2MjEzZSIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJjbGF3IiB4MT0iMCUiIHkxPSIwJSIgeDI9IjEwMCUiIHkyPSIxMDAlIj48c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSIjZmYzMzMzIi8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSIjY2MwMDAwIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHJlY3Qgd2lkdGg9IjEyOCIgaGVpZ2h0PSIxMjgiIHJ4PSIyNCIgZmlsbD0idXJsKCNiZykiLz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSg2NCA2NCkiIHN0cm9rZT0idXJsKCNjbGF3KSIgc3Ryb2tlLXdpZHRoPSI3IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGZpbGw9Im5vbmUiPjxwYXRoIGQ9Ik0tMjggLTM4YzAgMCAtMTAgMjAgMCAzMiIvPjxwYXRoIGQ9Ik0tMTIgLTQwYzAgMCAtNiAyMiA0IDM0Ii8+PHBhdGggZD0iTTI4IC0zOGMwIDAgMTAgMjAgMCAzMiIvPjxwYXRoIGQ9Ik0xMiAtNDBjMCAwIDYgMjIgLTQgMzQiLz48Y2lyY2xlIGN4PSIwIiBjeT0iMTAiIHI9IjIwIiBzdHJva2Utd2lkdGg9IjYiLz48cGF0aCBkPSJNLTEwIDR2LTQiIHN0cm9rZS13aWR0aD0iNCIvPjxwYXRoIGQ9Ik0xMCA0di00IiBzdHJva2Utd2lkdGg9IjQiLz48cGF0aCBkPSJNLTggMjBjNCA2IDEyIDYgMTYgMCIgc3Ryb2tlLXdpZHRoPSIzIi8+PC9nPjwvc3ZnPg==";
|
|
14
11
|
|
|
@@ -94,6 +91,45 @@ function parseArguments(version) {
|
|
|
94
91
|
description: "Allowed OAuth redirect URIs (comma-separated)",
|
|
95
92
|
default: process.env.MCP_REDIRECT_URIS || void 0
|
|
96
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
|
+
}
|
|
97
133
|
return {
|
|
98
134
|
openclawUrl: argv["openclaw-url"],
|
|
99
135
|
gatewayToken: argv["gateway-token"],
|
|
@@ -105,7 +141,8 @@ function parseArguments(version) {
|
|
|
105
141
|
clientId: argv["client-id"],
|
|
106
142
|
clientSecret: argv["client-secret"],
|
|
107
143
|
issuerUrl: argv["issuer-url"],
|
|
108
|
-
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
|
|
109
146
|
};
|
|
110
147
|
}
|
|
111
148
|
|
|
@@ -264,6 +301,120 @@ var OpenClawClient = class {
|
|
|
264
301
|
}
|
|
265
302
|
};
|
|
266
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
|
+
|
|
267
418
|
// src/server/tools-registration.ts
|
|
268
419
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
269
420
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -332,12 +483,16 @@ var openclawChatTool = {
|
|
|
332
483
|
session_id: {
|
|
333
484
|
type: "string",
|
|
334
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."
|
|
335
490
|
}
|
|
336
491
|
},
|
|
337
492
|
required: ["message"]
|
|
338
493
|
}
|
|
339
494
|
};
|
|
340
|
-
async function handleOpenclawChat(
|
|
495
|
+
async function handleOpenclawChat(registry2, input) {
|
|
341
496
|
if (!validateInputIsObject(input)) {
|
|
342
497
|
return errorResponse("Invalid input: expected an object");
|
|
343
498
|
}
|
|
@@ -353,8 +508,17 @@ async function handleOpenclawChat(client2, input) {
|
|
|
353
508
|
}
|
|
354
509
|
sessionId = sidResult.value;
|
|
355
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
|
+
}
|
|
356
519
|
try {
|
|
357
|
-
const
|
|
520
|
+
const { client } = registry2.resolve(instanceName);
|
|
521
|
+
const response = await client.chat(msgResult.value, sessionId);
|
|
358
522
|
return successResponse(response.response);
|
|
359
523
|
} catch (error) {
|
|
360
524
|
return errorResponse(error instanceof Error ? error.message : "Failed to chat with OpenClaw");
|
|
@@ -367,16 +531,33 @@ var openclawStatusTool = {
|
|
|
367
531
|
description: "Get OpenClaw gateway status and health information",
|
|
368
532
|
inputSchema: {
|
|
369
533
|
type: "object",
|
|
370
|
-
properties: {
|
|
534
|
+
properties: {
|
|
535
|
+
instance: {
|
|
536
|
+
type: "string",
|
|
537
|
+
description: "Target OpenClaw instance name. Defaults to the default instance."
|
|
538
|
+
}
|
|
539
|
+
}
|
|
371
540
|
}
|
|
372
541
|
};
|
|
373
|
-
async function handleOpenclawStatus(
|
|
542
|
+
async function handleOpenclawStatus(registry2, input) {
|
|
374
543
|
if (!validateInputIsObject(input)) {
|
|
375
544
|
return errorResponse("Invalid input: expected an object");
|
|
376
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
|
+
}
|
|
377
554
|
try {
|
|
378
|
-
const
|
|
379
|
-
|
|
555
|
+
const resolved = registry2.resolve(instanceName);
|
|
556
|
+
const response = await resolved.client.health();
|
|
557
|
+
return jsonResponse({
|
|
558
|
+
...response,
|
|
559
|
+
instance: resolved.name
|
|
560
|
+
});
|
|
380
561
|
} catch (error) {
|
|
381
562
|
return errorResponse(
|
|
382
563
|
error instanceof Error ? error.message : "Failed to get status from OpenClaw"
|
|
@@ -384,6 +565,22 @@ async function handleOpenclawStatus(client2, input) {
|
|
|
384
565
|
}
|
|
385
566
|
}
|
|
386
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
|
+
|
|
387
584
|
// src/mcp/tasks/manager.ts
|
|
388
585
|
var MAX_TASKS = 1e3;
|
|
389
586
|
var CLEANUP_INTERVAL_MS = 10 * 60 * 1e3;
|
|
@@ -424,6 +621,7 @@ var TaskManager = class {
|
|
|
424
621
|
input: options.input,
|
|
425
622
|
createdAt: /* @__PURE__ */ new Date(),
|
|
426
623
|
sessionId: options.sessionId,
|
|
624
|
+
instanceId: options.instanceId,
|
|
427
625
|
priority: options.priority ?? 0
|
|
428
626
|
};
|
|
429
627
|
this.tasks.set(id, task);
|
|
@@ -447,6 +645,9 @@ var TaskManager = class {
|
|
|
447
645
|
if (filter?.sessionId) {
|
|
448
646
|
tasks = tasks.filter((t) => t.sessionId === filter.sessionId);
|
|
449
647
|
}
|
|
648
|
+
if (filter?.instanceId) {
|
|
649
|
+
tasks = tasks.filter((t) => t.instanceId === filter.instanceId);
|
|
650
|
+
}
|
|
450
651
|
return tasks.sort((a, b) => {
|
|
451
652
|
if (b.priority !== a.priority) return b.priority - a.priority;
|
|
452
653
|
return a.createdAt.getTime() - b.createdAt.getTime();
|
|
@@ -554,6 +755,10 @@ var openclawChatAsyncTool = {
|
|
|
554
755
|
priority: {
|
|
555
756
|
type: "number",
|
|
556
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."
|
|
557
762
|
}
|
|
558
763
|
},
|
|
559
764
|
required: ["message"]
|
|
@@ -575,7 +780,7 @@ var openclawTaskStatusTool = {
|
|
|
575
780
|
};
|
|
576
781
|
var openclawTaskListTool = {
|
|
577
782
|
name: "openclaw_task_list",
|
|
578
|
-
description: "List all tasks. Optionally filter by status or
|
|
783
|
+
description: "List all tasks. Optionally filter by status, session, or instance.",
|
|
579
784
|
inputSchema: {
|
|
580
785
|
type: "object",
|
|
581
786
|
properties: {
|
|
@@ -587,6 +792,10 @@ var openclawTaskListTool = {
|
|
|
587
792
|
session_id: {
|
|
588
793
|
type: "string",
|
|
589
794
|
description: "Filter by session ID"
|
|
795
|
+
},
|
|
796
|
+
instance: {
|
|
797
|
+
type: "string",
|
|
798
|
+
description: "Filter by instance name"
|
|
590
799
|
}
|
|
591
800
|
},
|
|
592
801
|
required: []
|
|
@@ -607,12 +816,20 @@ var openclawTaskCancelTool = {
|
|
|
607
816
|
}
|
|
608
817
|
};
|
|
609
818
|
var processorRunning = false;
|
|
610
|
-
var
|
|
611
|
-
async function processTask(task,
|
|
819
|
+
var processorRegistry = null;
|
|
820
|
+
async function processTask(task, registry2) {
|
|
612
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
|
+
}
|
|
613
830
|
try {
|
|
614
831
|
const input = task.input;
|
|
615
|
-
const response = await
|
|
832
|
+
const response = await client.chat(input.message, input.session_id);
|
|
616
833
|
taskManager.updateStatus(task.id, "completed", response.response);
|
|
617
834
|
} catch (error) {
|
|
618
835
|
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -620,26 +837,26 @@ async function processTask(task, client2) {
|
|
|
620
837
|
}
|
|
621
838
|
}
|
|
622
839
|
async function taskProcessor() {
|
|
623
|
-
if (!
|
|
840
|
+
if (!processorRegistry) return;
|
|
624
841
|
while (processorRunning) {
|
|
625
842
|
const task = taskManager.getNextPending();
|
|
626
843
|
if (task) {
|
|
627
|
-
await processTask(task,
|
|
844
|
+
await processTask(task, processorRegistry);
|
|
628
845
|
} else {
|
|
629
846
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
630
847
|
}
|
|
631
848
|
}
|
|
632
849
|
}
|
|
633
|
-
function startTaskProcessor(
|
|
850
|
+
function startTaskProcessor(registry2) {
|
|
634
851
|
if (processorRunning) return;
|
|
635
|
-
|
|
852
|
+
processorRegistry = registry2;
|
|
636
853
|
processorRunning = true;
|
|
637
854
|
taskProcessor().catch(() => {
|
|
638
855
|
processorRunning = false;
|
|
639
856
|
});
|
|
640
857
|
log("Task processor started");
|
|
641
858
|
}
|
|
642
|
-
async function handleOpenclawChatAsync(
|
|
859
|
+
async function handleOpenclawChatAsync(registry2, input) {
|
|
643
860
|
if (!validateInputIsObject(input)) {
|
|
644
861
|
return errorResponse("Invalid input: expected an object");
|
|
645
862
|
}
|
|
@@ -662,18 +879,35 @@ async function handleOpenclawChatAsync(client2, input) {
|
|
|
662
879
|
}
|
|
663
880
|
priority = input.priority;
|
|
664
881
|
}
|
|
665
|
-
|
|
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);
|
|
666
898
|
const task = taskManager.create({
|
|
667
899
|
type: "chat",
|
|
668
900
|
input: { message: msgResult.value, session_id: sessionId },
|
|
669
901
|
sessionId,
|
|
670
|
-
priority
|
|
902
|
+
priority,
|
|
903
|
+
instanceId
|
|
671
904
|
});
|
|
672
905
|
return successResponse(
|
|
673
906
|
JSON.stringify(
|
|
674
907
|
{
|
|
675
908
|
task_id: task.id,
|
|
676
909
|
status: task.status,
|
|
910
|
+
instance: instanceId,
|
|
677
911
|
message: "Task queued. Use openclaw_task_status to check progress."
|
|
678
912
|
},
|
|
679
913
|
null,
|
|
@@ -681,7 +915,7 @@ async function handleOpenclawChatAsync(client2, input) {
|
|
|
681
915
|
)
|
|
682
916
|
);
|
|
683
917
|
}
|
|
684
|
-
async function handleOpenclawTaskStatus(
|
|
918
|
+
async function handleOpenclawTaskStatus(_registry, input) {
|
|
685
919
|
if (!validateInputIsObject(input)) {
|
|
686
920
|
return errorResponse("Invalid input: expected an object");
|
|
687
921
|
}
|
|
@@ -698,6 +932,7 @@ async function handleOpenclawTaskStatus(_client, input) {
|
|
|
698
932
|
task_id: task.id,
|
|
699
933
|
type: task.type,
|
|
700
934
|
status: task.status,
|
|
935
|
+
instance: task.instanceId,
|
|
701
936
|
created_at: task.createdAt.toISOString()
|
|
702
937
|
};
|
|
703
938
|
if (task.startedAt) {
|
|
@@ -721,7 +956,7 @@ var VALID_TASK_STATUSES = [
|
|
|
721
956
|
"failed",
|
|
722
957
|
"cancelled"
|
|
723
958
|
];
|
|
724
|
-
async function handleOpenclawTaskList(
|
|
959
|
+
async function handleOpenclawTaskList(_registry, input) {
|
|
725
960
|
if (!validateInputIsObject(input)) {
|
|
726
961
|
return errorResponse("Invalid input: expected an object");
|
|
727
962
|
}
|
|
@@ -740,12 +975,21 @@ async function handleOpenclawTaskList(_client, input) {
|
|
|
740
975
|
}
|
|
741
976
|
session_id = sidResult.value;
|
|
742
977
|
}
|
|
743
|
-
|
|
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 });
|
|
744
987
|
const stats = taskManager.stats();
|
|
745
988
|
const taskList = tasks.map((t) => ({
|
|
746
989
|
task_id: t.id,
|
|
747
990
|
type: t.type,
|
|
748
991
|
status: t.status,
|
|
992
|
+
instance: t.instanceId,
|
|
749
993
|
priority: t.priority,
|
|
750
994
|
created_at: t.createdAt.toISOString(),
|
|
751
995
|
has_result: t.status === "completed" && !!t.result
|
|
@@ -761,7 +1005,7 @@ async function handleOpenclawTaskList(_client, input) {
|
|
|
761
1005
|
)
|
|
762
1006
|
);
|
|
763
1007
|
}
|
|
764
|
-
async function handleOpenclawTaskCancel(
|
|
1008
|
+
async function handleOpenclawTaskCancel(_registry, input) {
|
|
765
1009
|
if (!validateInputIsObject(input)) {
|
|
766
1010
|
return errorResponse("Invalid input: expected an object");
|
|
767
1011
|
}
|
|
@@ -816,14 +1060,15 @@ function createMcpServer(deps2) {
|
|
|
816
1060
|
return server;
|
|
817
1061
|
}
|
|
818
1062
|
function registerTools(server, deps2) {
|
|
819
|
-
const {
|
|
1063
|
+
const { registry: registry2 } = deps2;
|
|
820
1064
|
const toolHandlers = /* @__PURE__ */ new Map([
|
|
821
|
-
["openclaw_chat", (input) => handleOpenclawChat(
|
|
822
|
-
["openclaw_status", (input) => handleOpenclawStatus(
|
|
823
|
-
["openclaw_chat_async", (input) => handleOpenclawChatAsync(
|
|
824
|
-
["openclaw_task_status", (input) => handleOpenclawTaskStatus(
|
|
825
|
-
["openclaw_task_list", (input) => handleOpenclawTaskList(
|
|
826
|
-
["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)]
|
|
827
1072
|
]);
|
|
828
1073
|
const allTools = [
|
|
829
1074
|
openclawChatTool,
|
|
@@ -831,7 +1076,8 @@ function registerTools(server, deps2) {
|
|
|
831
1076
|
openclawChatAsyncTool,
|
|
832
1077
|
openclawTaskStatusTool,
|
|
833
1078
|
openclawTaskListTool,
|
|
834
|
-
openclawTaskCancelTool
|
|
1079
|
+
openclawTaskCancelTool,
|
|
1080
|
+
openclawInstancesTool
|
|
835
1081
|
];
|
|
836
1082
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
837
1083
|
return { tools: allTools };
|
|
@@ -941,9 +1187,9 @@ var OpenClawAuthProvider = class {
|
|
|
941
1187
|
/**
|
|
942
1188
|
* Auto-approve: generate auth code and redirect immediately.
|
|
943
1189
|
*/
|
|
944
|
-
async authorize(
|
|
1190
|
+
async authorize(client, params, res) {
|
|
945
1191
|
const code = randomUUID();
|
|
946
|
-
this.codes.set(code, { client
|
|
1192
|
+
this.codes.set(code, { client, params, createdAt: Date.now() });
|
|
947
1193
|
const searchParams = new URLSearchParams({ code });
|
|
948
1194
|
if (params.state !== void 0) {
|
|
949
1195
|
searchParams.set("state", params.state);
|
|
@@ -960,13 +1206,13 @@ var OpenClawAuthProvider = class {
|
|
|
960
1206
|
}
|
|
961
1207
|
return codeData.params.codeChallenge;
|
|
962
1208
|
}
|
|
963
|
-
async exchangeAuthorizationCode(
|
|
1209
|
+
async exchangeAuthorizationCode(client, authorizationCode, _codeVerifier, _redirectUri, resource) {
|
|
964
1210
|
const codeData = this.codes.get(authorizationCode);
|
|
965
1211
|
if (!codeData || Date.now() - codeData.createdAt > AUTH_CODE_TTL_MS) {
|
|
966
1212
|
if (codeData) this.codes.delete(authorizationCode);
|
|
967
1213
|
throw new InvalidRequestError("Invalid authorization code");
|
|
968
1214
|
}
|
|
969
|
-
if (codeData.client.client_id !==
|
|
1215
|
+
if (codeData.client.client_id !== client.client_id) {
|
|
970
1216
|
throw new InvalidRequestError("Authorization code was not issued to this client");
|
|
971
1217
|
}
|
|
972
1218
|
this.codes.delete(authorizationCode);
|
|
@@ -975,13 +1221,13 @@ var OpenClawAuthProvider = class {
|
|
|
975
1221
|
const scopes = codeData.params.scopes || [];
|
|
976
1222
|
this.tokens.set(accessToken, {
|
|
977
1223
|
token: accessToken,
|
|
978
|
-
clientId:
|
|
1224
|
+
clientId: client.client_id,
|
|
979
1225
|
scopes,
|
|
980
1226
|
expiresAt: Date.now() + TOKEN_TTL_MS,
|
|
981
1227
|
resource: resource || codeData.params.resource
|
|
982
1228
|
});
|
|
983
1229
|
this.refreshTokens.set(refreshToken, {
|
|
984
|
-
clientId:
|
|
1230
|
+
clientId: client.client_id,
|
|
985
1231
|
scopes,
|
|
986
1232
|
expiresAt: Date.now() + REFRESH_TOKEN_TTL_MS,
|
|
987
1233
|
resource: resource || codeData.params.resource
|
|
@@ -994,13 +1240,13 @@ var OpenClawAuthProvider = class {
|
|
|
994
1240
|
scope: scopes.join(" ")
|
|
995
1241
|
};
|
|
996
1242
|
}
|
|
997
|
-
async exchangeRefreshToken(
|
|
1243
|
+
async exchangeRefreshToken(client, refreshToken, scopes, resource) {
|
|
998
1244
|
const data = this.refreshTokens.get(refreshToken);
|
|
999
1245
|
if (!data || data.expiresAt < Date.now()) {
|
|
1000
1246
|
if (data) this.refreshTokens.delete(refreshToken);
|
|
1001
1247
|
throw new InvalidRequestError("Invalid refresh token");
|
|
1002
1248
|
}
|
|
1003
|
-
if (data.clientId !==
|
|
1249
|
+
if (data.clientId !== client.client_id) {
|
|
1004
1250
|
throw new InvalidRequestError("Refresh token was not issued to this client");
|
|
1005
1251
|
}
|
|
1006
1252
|
this.refreshTokens.delete(refreshToken);
|
|
@@ -1009,13 +1255,13 @@ var OpenClawAuthProvider = class {
|
|
|
1009
1255
|
const tokenScopes = scopes || data.scopes;
|
|
1010
1256
|
this.tokens.set(accessToken, {
|
|
1011
1257
|
token: accessToken,
|
|
1012
|
-
clientId:
|
|
1258
|
+
clientId: client.client_id,
|
|
1013
1259
|
scopes: tokenScopes,
|
|
1014
1260
|
expiresAt: Date.now() + TOKEN_TTL_MS,
|
|
1015
1261
|
resource: resource || data.resource
|
|
1016
1262
|
});
|
|
1017
1263
|
this.refreshTokens.set(newRefreshToken, {
|
|
1018
|
-
clientId:
|
|
1264
|
+
clientId: client.client_id,
|
|
1019
1265
|
scopes: tokenScopes,
|
|
1020
1266
|
expiresAt: Date.now() + REFRESH_TOKEN_TTL_MS,
|
|
1021
1267
|
resource: resource || data.resource
|
|
@@ -1284,18 +1530,20 @@ async function createSSEServer(config, deps2) {
|
|
|
1284
1530
|
|
|
1285
1531
|
// src/index.ts
|
|
1286
1532
|
var args = parseArguments(SERVER_VERSION);
|
|
1287
|
-
var
|
|
1533
|
+
var registry = new InstanceRegistry(args.instances);
|
|
1288
1534
|
var deps = {
|
|
1289
|
-
|
|
1535
|
+
registry,
|
|
1290
1536
|
serverName: SERVER_NAME,
|
|
1291
1537
|
serverVersion: SERVER_VERSION
|
|
1292
1538
|
};
|
|
1293
1539
|
async function main() {
|
|
1294
1540
|
log(`Starting ${SERVER_NAME} v${SERVER_VERSION}`);
|
|
1295
|
-
log(`OpenClaw URL: ${args.openclawUrl}`);
|
|
1296
1541
|
log(`Transport: ${args.transport}`);
|
|
1297
|
-
log(`Gateway token: ${args.gatewayToken ? "configured" : "not set"}`);
|
|
1298
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
|
+
}
|
|
1299
1547
|
if (args.transport === "sse") {
|
|
1300
1548
|
const sseConfig = {
|
|
1301
1549
|
port: args.port,
|
package/docs/CNAME
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
openclaw-mcp.cloud
|
|
Binary file
|