openclaw-mcp 1.6.0 → 1.7.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 +22 -2
- package/dist/index.js +83 -52
- package/docs/configuration.md +12 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -161,9 +161,15 @@ See [Installation Guide](docs/installation.md) for details.
|
|
|
161
161
|
|------|-------------|
|
|
162
162
|
| `openclaw_chat_async` | Queue a message, get task_id immediately |
|
|
163
163
|
| `openclaw_task_status` | Check task progress and get results |
|
|
164
|
-
| `openclaw_task_list` | List
|
|
164
|
+
| `openclaw_task_list` | List your tasks with filtering |
|
|
165
165
|
| `openclaw_task_cancel` | Cancel a pending task |
|
|
166
166
|
|
|
167
|
+
Tasks are scoped to the MCP connection that created them. In HTTP mode, where
|
|
168
|
+
one process serves many clients, a client can only see and cancel its own
|
|
169
|
+
tasks — another client's `task_id` reads as "not found" even if it is known.
|
|
170
|
+
Reconnecting starts a fresh scope, so poll a task on the connection that
|
|
171
|
+
queued it.
|
|
172
|
+
|
|
167
173
|
## Multi-Instance Mode
|
|
168
174
|
|
|
169
175
|
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).
|
|
@@ -261,7 +267,8 @@ AUTH_ENABLED=true MCP_CLIENT_ID=openclaw MCP_CLIENT_SECRET=$MCP_CLIENT_SECRET \
|
|
|
261
267
|
openclaw-mcp --transport http
|
|
262
268
|
```
|
|
263
269
|
|
|
264
|
-
|
|
270
|
+
CORS is disabled unless you opt in. Set `CORS_ORIGINS` only when a browser
|
|
271
|
+
client needs to reach the server directly:
|
|
265
272
|
|
|
266
273
|
```bash
|
|
267
274
|
CORS_ORIGINS=https://claude.ai,https://your-app.com
|
|
@@ -269,6 +276,19 @@ CORS_ORIGINS=https://claude.ai,https://your-app.com
|
|
|
269
276
|
|
|
270
277
|
See [Configuration](docs/configuration.md) for all security options.
|
|
271
278
|
|
|
279
|
+
### Upgrading to 1.7.0
|
|
280
|
+
|
|
281
|
+
Two defaults changed for security. Both only affect HTTP mode; stdio is
|
|
282
|
+
unchanged.
|
|
283
|
+
|
|
284
|
+
- **CORS is now off by default.** Previously an unset `CORS_ORIGINS` sent
|
|
285
|
+
`Access-Control-Allow-Origin: *`. If a browser client depends on that, set
|
|
286
|
+
the origins explicitly (`CORS_ORIGINS=https://your-app.com`), or
|
|
287
|
+
`CORS_ORIGINS=*` to restore the old behaviour.
|
|
288
|
+
- **Async tasks are scoped to the connection that created them.** A client
|
|
289
|
+
that used to poll a `task_id` queued by a different connection will now get
|
|
290
|
+
"not found".
|
|
291
|
+
|
|
272
292
|
## Migrating from SSE to HTTP transport
|
|
273
293
|
|
|
274
294
|
Starting with v1.5.0, the primary transport is **Streamable HTTP** (`--transport http`). The legacy SSE transport (`--transport sse`) is deprecated but still works for backward compatibility.
|
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.7.0";
|
|
9
9
|
var DEFAULT_OPENCLAW_URL = "http://127.0.0.1:18789";
|
|
10
10
|
var DEFAULT_MODEL = "openclaw";
|
|
11
11
|
var SERVER_ICON_SVG_BASE64 = "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjgiIGhlaWdodD0iMTI4IiB2aWV3Qm94PSIwIDAgMTI4IDEyOCIgZmlsbD0ibm9uZSI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJiZyIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzFhMWEyZSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzE2MjEzZSIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJjbGF3IiB4MT0iMCUiIHkxPSIwJSIgeDI9IjEwMCUiIHkyPSIxMDAlIj48c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSIjZmYzMzMzIi8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSIjY2MwMDAwIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHJlY3Qgd2lkdGg9IjEyOCIgaGVpZ2h0PSIxMjgiIHJ4PSIyNCIgZmlsbD0idXJsKCNiZykiLz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSg2NCA2NCkiIHN0cm9rZT0idXJsKCNjbGF3KSIgc3Ryb2tlLXdpZHRoPSI3IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGZpbGw9Im5vbmUiPjxwYXRoIGQ9Ik0tMjggLTM4YzAgMCAtMTAgMjAgMCAzMiIvPjxwYXRoIGQ9Ik0tMTIgLTQwYzAgMCAtNiAyMiA0IDM0Ii8+PHBhdGggZD0iTTI4IC0zOGMwIDAgMTAgMjAgMCAzMiIvPjxwYXRoIGQ9Ik0xMiAtNDBjMCAwIDYgMjIgLTQgMzQiLz48Y2lyY2xlIGN4PSIwIiBjeT0iMTAiIHI9IjIwIiBzdHJva2Utd2lkdGg9IjYiLz48cGF0aCBkPSJNLTEwIDR2LTQiIHN0cm9rZS13aWR0aD0iNCIvPjxwYXRoIGQ9Ik0xMCA0di00IiBzdHJva2Utd2lkdGg9IjQiLz48cGF0aCBkPSJNLTggMjBjNCA2IDEyIDYgMTYgMCIgc3Ryb2tlLXdpZHRoPSIzIi8+PC9nPjwvc3ZnPg==";
|
|
@@ -475,6 +475,7 @@ var InstanceRegistry = class {
|
|
|
475
475
|
};
|
|
476
476
|
|
|
477
477
|
// src/server/tools-registration.ts
|
|
478
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
478
479
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
479
480
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
480
481
|
|
|
@@ -641,12 +642,12 @@ async function handleOpenclawInstances(registry2, _input) {
|
|
|
641
642
|
}
|
|
642
643
|
|
|
643
644
|
// src/mcp/tasks/manager.ts
|
|
645
|
+
import { randomUUID } from "crypto";
|
|
644
646
|
var MAX_TASKS = 1e3;
|
|
645
647
|
var CLEANUP_INTERVAL_MS = 10 * 60 * 1e3;
|
|
646
648
|
var CLEANUP_MAX_AGE_MS = 60 * 60 * 1e3;
|
|
647
649
|
var TaskManager = class {
|
|
648
650
|
tasks = /* @__PURE__ */ new Map();
|
|
649
|
-
taskCounter = 0;
|
|
650
651
|
cleanupInterval;
|
|
651
652
|
constructor() {
|
|
652
653
|
this.cleanupInterval = setInterval(() => this.cleanup(CLEANUP_MAX_AGE_MS), CLEANUP_INTERVAL_MS);
|
|
@@ -655,16 +656,17 @@ var TaskManager = class {
|
|
|
655
656
|
}
|
|
656
657
|
}
|
|
657
658
|
/**
|
|
658
|
-
* Generate
|
|
659
|
+
* Generate an unguessable task ID.
|
|
660
|
+
*
|
|
661
|
+
* Deliberately not sequential: a predictable counter would let a caller
|
|
662
|
+
* enumerate IDs and probe for tasks it does not own. Ownership checks are
|
|
663
|
+
* the real defence, this just removes the oracle.
|
|
659
664
|
*/
|
|
660
665
|
generateId() {
|
|
661
|
-
|
|
662
|
-
const timestamp = Date.now().toString(36);
|
|
663
|
-
const counter = this.taskCounter.toString(36).padStart(4, "0");
|
|
664
|
-
return `task_${timestamp}_${counter}`;
|
|
666
|
+
return `task_${randomUUID()}`;
|
|
665
667
|
}
|
|
666
668
|
/**
|
|
667
|
-
* Create a new task
|
|
669
|
+
* Create a new task owned by `options.ownerId`.
|
|
668
670
|
*/
|
|
669
671
|
create(options) {
|
|
670
672
|
if (this.tasks.size >= MAX_TASKS) {
|
|
@@ -679,6 +681,7 @@ var TaskManager = class {
|
|
|
679
681
|
status: "pending",
|
|
680
682
|
input: options.input,
|
|
681
683
|
createdAt: /* @__PURE__ */ new Date(),
|
|
684
|
+
ownerId: options.ownerId,
|
|
682
685
|
sessionId: options.sessionId,
|
|
683
686
|
instanceId: options.instanceId,
|
|
684
687
|
priority: options.priority ?? 0
|
|
@@ -688,16 +691,21 @@ var TaskManager = class {
|
|
|
688
691
|
return task;
|
|
689
692
|
}
|
|
690
693
|
/**
|
|
691
|
-
* Get task by ID
|
|
694
|
+
* Get a task by ID, but only if `ownerId` owns it.
|
|
695
|
+
*
|
|
696
|
+
* Returns undefined both for unknown IDs and for tasks owned by someone
|
|
697
|
+
* else, so callers cannot distinguish the two.
|
|
692
698
|
*/
|
|
693
|
-
get(id) {
|
|
694
|
-
|
|
699
|
+
get(id, ownerId) {
|
|
700
|
+
const task = this.tasks.get(id);
|
|
701
|
+
if (!task || task.ownerId !== ownerId) return void 0;
|
|
702
|
+
return task;
|
|
695
703
|
}
|
|
696
704
|
/**
|
|
697
|
-
* List
|
|
705
|
+
* List the tasks owned by `ownerId`, optionally filtered further.
|
|
698
706
|
*/
|
|
699
|
-
list(filter) {
|
|
700
|
-
let tasks = Array.from(this.tasks.values());
|
|
707
|
+
list(ownerId, filter) {
|
|
708
|
+
let tasks = Array.from(this.tasks.values()).filter((t) => t.ownerId === ownerId);
|
|
701
709
|
if (filter?.status) {
|
|
702
710
|
tasks = tasks.filter((t) => t.status === filter.status);
|
|
703
711
|
}
|
|
@@ -713,7 +721,10 @@ var TaskManager = class {
|
|
|
713
721
|
});
|
|
714
722
|
}
|
|
715
723
|
/**
|
|
716
|
-
* Update task status
|
|
724
|
+
* Update task status.
|
|
725
|
+
*
|
|
726
|
+
* Internal worker path only — never reachable from a tool handler, so it
|
|
727
|
+
* takes no ownerId.
|
|
717
728
|
*/
|
|
718
729
|
updateStatus(id, status, result, error) {
|
|
719
730
|
const task = this.tasks.get(id);
|
|
@@ -731,10 +742,10 @@ var TaskManager = class {
|
|
|
731
742
|
return true;
|
|
732
743
|
}
|
|
733
744
|
/**
|
|
734
|
-
* Cancel a pending task
|
|
745
|
+
* Cancel a pending task owned by `ownerId`.
|
|
735
746
|
*/
|
|
736
|
-
cancel(id) {
|
|
737
|
-
const task = this.
|
|
747
|
+
cancel(id, ownerId) {
|
|
748
|
+
const task = this.get(id, ownerId);
|
|
738
749
|
if (!task) return false;
|
|
739
750
|
if (task.status !== "pending") {
|
|
740
751
|
return false;
|
|
@@ -745,16 +756,22 @@ var TaskManager = class {
|
|
|
745
756
|
return true;
|
|
746
757
|
}
|
|
747
758
|
/**
|
|
748
|
-
* Delete a task (cleanup)
|
|
759
|
+
* Delete a task (cleanup). Internal only.
|
|
749
760
|
*/
|
|
750
761
|
delete(id) {
|
|
751
762
|
return this.tasks.delete(id);
|
|
752
763
|
}
|
|
753
764
|
/**
|
|
754
|
-
* Get next pending task (for
|
|
765
|
+
* Get next pending task across all owners (for the background worker).
|
|
766
|
+
*
|
|
767
|
+
* Internal only — the worker has to see every queue, which is exactly why
|
|
768
|
+
* tool handlers must go through the owner-scoped methods instead.
|
|
755
769
|
*/
|
|
756
770
|
getNextPending() {
|
|
757
|
-
const pending = this.
|
|
771
|
+
const pending = Array.from(this.tasks.values()).filter((t) => t.status === "pending").sort((a, b) => {
|
|
772
|
+
if (b.priority !== a.priority) return b.priority - a.priority;
|
|
773
|
+
return a.createdAt.getTime() - b.createdAt.getTime();
|
|
774
|
+
});
|
|
758
775
|
return pending[0];
|
|
759
776
|
}
|
|
760
777
|
/**
|
|
@@ -775,9 +792,12 @@ var TaskManager = class {
|
|
|
775
792
|
return cleaned;
|
|
776
793
|
}
|
|
777
794
|
/**
|
|
778
|
-
* Get statistics
|
|
795
|
+
* Get statistics for the tasks owned by `ownerId`.
|
|
796
|
+
*
|
|
797
|
+
* Scoped like everything else: a global total would leak how busy other
|
|
798
|
+
* connections are.
|
|
779
799
|
*/
|
|
780
|
-
stats() {
|
|
800
|
+
stats(ownerId) {
|
|
781
801
|
const byStatus = {
|
|
782
802
|
pending: 0,
|
|
783
803
|
running: 0,
|
|
@@ -785,13 +805,13 @@ var TaskManager = class {
|
|
|
785
805
|
failed: 0,
|
|
786
806
|
cancelled: 0
|
|
787
807
|
};
|
|
808
|
+
let total = 0;
|
|
788
809
|
for (const task of this.tasks.values()) {
|
|
810
|
+
if (task.ownerId !== ownerId) continue;
|
|
789
811
|
byStatus[task.status]++;
|
|
812
|
+
total++;
|
|
790
813
|
}
|
|
791
|
-
return {
|
|
792
|
-
total: this.tasks.size,
|
|
793
|
-
byStatus
|
|
794
|
-
};
|
|
814
|
+
return { total, byStatus };
|
|
795
815
|
}
|
|
796
816
|
};
|
|
797
817
|
var taskManager = new TaskManager();
|
|
@@ -915,7 +935,7 @@ function startTaskProcessor(registry2) {
|
|
|
915
935
|
});
|
|
916
936
|
log("Task processor started");
|
|
917
937
|
}
|
|
918
|
-
async function handleOpenclawChatAsync(registry2, input) {
|
|
938
|
+
async function handleOpenclawChatAsync(registry2, input, ownerId) {
|
|
919
939
|
if (!validateInputIsObject(input)) {
|
|
920
940
|
return errorResponse("Invalid input: expected an object");
|
|
921
941
|
}
|
|
@@ -957,6 +977,7 @@ async function handleOpenclawChatAsync(registry2, input) {
|
|
|
957
977
|
const task = taskManager.create({
|
|
958
978
|
type: "chat",
|
|
959
979
|
input: { message: msgResult.value, session_id: sessionId },
|
|
980
|
+
ownerId,
|
|
960
981
|
sessionId,
|
|
961
982
|
priority,
|
|
962
983
|
instanceId
|
|
@@ -974,7 +995,7 @@ async function handleOpenclawChatAsync(registry2, input) {
|
|
|
974
995
|
)
|
|
975
996
|
);
|
|
976
997
|
}
|
|
977
|
-
async function handleOpenclawTaskStatus(_registry, input) {
|
|
998
|
+
async function handleOpenclawTaskStatus(_registry, input, ownerId) {
|
|
978
999
|
if (!validateInputIsObject(input)) {
|
|
979
1000
|
return errorResponse("Invalid input: expected an object");
|
|
980
1001
|
}
|
|
@@ -983,7 +1004,7 @@ async function handleOpenclawTaskStatus(_registry, input) {
|
|
|
983
1004
|
return errorResponse(tidResult.error);
|
|
984
1005
|
}
|
|
985
1006
|
const task_id = tidResult.value;
|
|
986
|
-
const task = taskManager.get(task_id);
|
|
1007
|
+
const task = taskManager.get(task_id, ownerId);
|
|
987
1008
|
if (!task) {
|
|
988
1009
|
return errorResponse(`Task not found: ${task_id}`);
|
|
989
1010
|
}
|
|
@@ -1015,7 +1036,7 @@ var VALID_TASK_STATUSES = [
|
|
|
1015
1036
|
"failed",
|
|
1016
1037
|
"cancelled"
|
|
1017
1038
|
];
|
|
1018
|
-
async function handleOpenclawTaskList(_registry, input) {
|
|
1039
|
+
async function handleOpenclawTaskList(_registry, input, ownerId) {
|
|
1019
1040
|
if (!validateInputIsObject(input)) {
|
|
1020
1041
|
return errorResponse("Invalid input: expected an object");
|
|
1021
1042
|
}
|
|
@@ -1042,8 +1063,12 @@ async function handleOpenclawTaskList(_registry, input) {
|
|
|
1042
1063
|
}
|
|
1043
1064
|
instanceFilter = instResult.value;
|
|
1044
1065
|
}
|
|
1045
|
-
const tasks = taskManager.list(
|
|
1046
|
-
|
|
1066
|
+
const tasks = taskManager.list(ownerId, {
|
|
1067
|
+
status,
|
|
1068
|
+
sessionId: session_id,
|
|
1069
|
+
instanceId: instanceFilter
|
|
1070
|
+
});
|
|
1071
|
+
const stats = taskManager.stats(ownerId);
|
|
1047
1072
|
const taskList = tasks.map((t) => ({
|
|
1048
1073
|
task_id: t.id,
|
|
1049
1074
|
type: t.type,
|
|
@@ -1064,7 +1089,7 @@ async function handleOpenclawTaskList(_registry, input) {
|
|
|
1064
1089
|
)
|
|
1065
1090
|
);
|
|
1066
1091
|
}
|
|
1067
|
-
async function handleOpenclawTaskCancel(_registry, input) {
|
|
1092
|
+
async function handleOpenclawTaskCancel(_registry, input, ownerId) {
|
|
1068
1093
|
if (!validateInputIsObject(input)) {
|
|
1069
1094
|
return errorResponse("Invalid input: expected an object");
|
|
1070
1095
|
}
|
|
@@ -1073,7 +1098,7 @@ async function handleOpenclawTaskCancel(_registry, input) {
|
|
|
1073
1098
|
return errorResponse(tidResult.error);
|
|
1074
1099
|
}
|
|
1075
1100
|
const task_id = tidResult.value;
|
|
1076
|
-
const task = taskManager.get(task_id);
|
|
1101
|
+
const task = taskManager.get(task_id, ownerId);
|
|
1077
1102
|
if (!task) {
|
|
1078
1103
|
return errorResponse(`Task not found: ${task_id}`);
|
|
1079
1104
|
}
|
|
@@ -1082,7 +1107,7 @@ async function handleOpenclawTaskCancel(_registry, input) {
|
|
|
1082
1107
|
`Cannot cancel task with status: ${task.status}. Only pending tasks can be cancelled.`
|
|
1083
1108
|
);
|
|
1084
1109
|
}
|
|
1085
|
-
const cancelled = taskManager.cancel(task_id);
|
|
1110
|
+
const cancelled = taskManager.cancel(task_id, ownerId);
|
|
1086
1111
|
if (!cancelled) {
|
|
1087
1112
|
return errorResponse("Failed to cancel task");
|
|
1088
1113
|
}
|
|
@@ -1120,13 +1145,14 @@ function createMcpServer(deps2) {
|
|
|
1120
1145
|
}
|
|
1121
1146
|
function registerTools(server, deps2) {
|
|
1122
1147
|
const { registry: registry2 } = deps2;
|
|
1148
|
+
const ownerId = randomUUID2();
|
|
1123
1149
|
const toolHandlers = /* @__PURE__ */ new Map([
|
|
1124
1150
|
["openclaw_chat", (input) => handleOpenclawChat(registry2, input)],
|
|
1125
1151
|
["openclaw_status", (input) => handleOpenclawStatus(registry2, input)],
|
|
1126
|
-
["openclaw_chat_async", (input) => handleOpenclawChatAsync(registry2, input)],
|
|
1127
|
-
["openclaw_task_status", (input) => handleOpenclawTaskStatus(registry2, input)],
|
|
1128
|
-
["openclaw_task_list", (input) => handleOpenclawTaskList(registry2, input)],
|
|
1129
|
-
["openclaw_task_cancel", (input) => handleOpenclawTaskCancel(registry2, input)],
|
|
1152
|
+
["openclaw_chat_async", (input) => handleOpenclawChatAsync(registry2, input, ownerId)],
|
|
1153
|
+
["openclaw_task_status", (input) => handleOpenclawTaskStatus(registry2, input, ownerId)],
|
|
1154
|
+
["openclaw_task_list", (input) => handleOpenclawTaskList(registry2, input, ownerId)],
|
|
1155
|
+
["openclaw_task_cancel", (input) => handleOpenclawTaskCancel(registry2, input, ownerId)],
|
|
1130
1156
|
["openclaw_instances", (input) => handleOpenclawInstances(registry2, input)]
|
|
1131
1157
|
]);
|
|
1132
1158
|
const allTools = [
|
|
@@ -1158,7 +1184,7 @@ function registerTools(server, deps2) {
|
|
|
1158
1184
|
}
|
|
1159
1185
|
|
|
1160
1186
|
// src/server/http.ts
|
|
1161
|
-
import { randomUUID as
|
|
1187
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
1162
1188
|
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
1163
1189
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
1164
1190
|
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
|
|
@@ -1166,7 +1192,7 @@ import { mcpAuthRouter } from "@modelcontextprotocol/sdk/server/auth/router.js";
|
|
|
1166
1192
|
import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
|
|
1167
1193
|
|
|
1168
1194
|
// src/auth/provider.ts
|
|
1169
|
-
import { randomUUID } from "crypto";
|
|
1195
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
1170
1196
|
import {
|
|
1171
1197
|
InvalidRequestError,
|
|
1172
1198
|
InvalidTokenError
|
|
@@ -1265,7 +1291,7 @@ var OpenClawAuthProvider = class {
|
|
|
1265
1291
|
* Auto-approve: generate auth code and redirect immediately.
|
|
1266
1292
|
*/
|
|
1267
1293
|
async authorize(client, params, res) {
|
|
1268
|
-
const code =
|
|
1294
|
+
const code = randomUUID3();
|
|
1269
1295
|
this.codes.set(code, { client, params, createdAt: Date.now() });
|
|
1270
1296
|
const searchParams = new URLSearchParams({ code });
|
|
1271
1297
|
if (params.state !== void 0) {
|
|
@@ -1293,8 +1319,8 @@ var OpenClawAuthProvider = class {
|
|
|
1293
1319
|
throw new InvalidRequestError("Authorization code was not issued to this client");
|
|
1294
1320
|
}
|
|
1295
1321
|
this.codes.delete(authorizationCode);
|
|
1296
|
-
const accessToken =
|
|
1297
|
-
const refreshToken =
|
|
1322
|
+
const accessToken = randomUUID3();
|
|
1323
|
+
const refreshToken = randomUUID3();
|
|
1298
1324
|
const scopes = codeData.params.scopes || [];
|
|
1299
1325
|
this.tokens.set(accessToken, {
|
|
1300
1326
|
token: accessToken,
|
|
@@ -1327,8 +1353,8 @@ var OpenClawAuthProvider = class {
|
|
|
1327
1353
|
throw new InvalidRequestError("Refresh token was not issued to this client");
|
|
1328
1354
|
}
|
|
1329
1355
|
this.refreshTokens.delete(refreshToken);
|
|
1330
|
-
const accessToken =
|
|
1331
|
-
const newRefreshToken =
|
|
1356
|
+
const accessToken = randomUUID3();
|
|
1357
|
+
const newRefreshToken = randomUUID3();
|
|
1332
1358
|
const tokenScopes = scopes || data.scopes;
|
|
1333
1359
|
this.tokens.set(accessToken, {
|
|
1334
1360
|
token: accessToken,
|
|
@@ -1395,12 +1421,12 @@ function parseTrustProxy(value) {
|
|
|
1395
1421
|
}
|
|
1396
1422
|
function loadCorsConfig() {
|
|
1397
1423
|
const corsOrigins = process.env.CORS_ORIGINS;
|
|
1398
|
-
if (!corsOrigins || corsOrigins === "
|
|
1399
|
-
return { origins: ["*"], enabled: true };
|
|
1400
|
-
}
|
|
1401
|
-
if (corsOrigins.toLowerCase() === "none" || corsOrigins === "") {
|
|
1424
|
+
if (!corsOrigins || corsOrigins.toLowerCase() === "none") {
|
|
1402
1425
|
return { origins: [], enabled: false };
|
|
1403
1426
|
}
|
|
1427
|
+
if (corsOrigins === "*") {
|
|
1428
|
+
return { origins: ["*"], enabled: true };
|
|
1429
|
+
}
|
|
1404
1430
|
return {
|
|
1405
1431
|
origins: corsOrigins.split(",").map((s) => s.trim()).filter(Boolean),
|
|
1406
1432
|
enabled: true
|
|
@@ -1551,7 +1577,7 @@ async function createHttpServer(config, deps2) {
|
|
|
1551
1577
|
return;
|
|
1552
1578
|
}
|
|
1553
1579
|
const transport = new StreamableHTTPServerTransport({
|
|
1554
|
-
sessionIdGenerator: () =>
|
|
1580
|
+
sessionIdGenerator: () => randomUUID4(),
|
|
1555
1581
|
onsessioninitialized: (newSessionId) => {
|
|
1556
1582
|
streamableSessions.set(newSessionId, { transport, server });
|
|
1557
1583
|
log(`Streamable session initialized: ${newSessionId}`);
|
|
@@ -1586,6 +1612,11 @@ async function createHttpServer(config, deps2) {
|
|
|
1586
1612
|
log(`HTTP server listening on ${config.host}:${config.port}`);
|
|
1587
1613
|
log(`Auth enabled: ${authEnabled}`);
|
|
1588
1614
|
log(`CORS origins: ${corsConfig.enabled ? corsConfig.origins.join(", ") : "disabled"}`);
|
|
1615
|
+
if (corsConfig.origins.includes("*")) {
|
|
1616
|
+
logError(
|
|
1617
|
+
"WARNING: CORS_ORIGINS=* allows any web page to call this server from a browser. List explicit origins instead."
|
|
1618
|
+
);
|
|
1619
|
+
}
|
|
1589
1620
|
if (authEnabled) {
|
|
1590
1621
|
log("OAuth 2.1 authentication is REQUIRED for all connections");
|
|
1591
1622
|
log("Endpoints:");
|
package/docs/configuration.md
CHANGED
|
@@ -123,17 +123,24 @@ When `TRUST_PROXY` is unset and a reverse proxy injects an `X-Forwarded-For` hea
|
|
|
123
123
|
|
|
124
124
|
### CORS Configuration
|
|
125
125
|
|
|
126
|
-
| Variable | Description | Default
|
|
127
|
-
| -------------- | --------------------------------- |
|
|
128
|
-
| `CORS_ORIGINS` | Allowed origins (comma-separated) |
|
|
126
|
+
| Variable | Description | Default |
|
|
127
|
+
| -------------- | --------------------------------- | ------------------ |
|
|
128
|
+
| `CORS_ORIGINS` | Allowed origins (comma-separated) | _(CORS disabled)_ |
|
|
129
|
+
|
|
130
|
+
CORS is **off unless you opt in**. With it off the server sends no
|
|
131
|
+
`Access-Control-Allow-Origin` header, so browsers refuse cross-origin calls to
|
|
132
|
+
it. Non-browser clients — Claude.ai, Claude Desktop, and anything speaking
|
|
133
|
+
stdio — are unaffected; set this only when a web page has to reach the server
|
|
134
|
+
directly.
|
|
129
135
|
|
|
130
136
|
**CORS_ORIGINS examples:**
|
|
131
137
|
|
|
132
|
-
-
|
|
133
|
-
- `none` — Disable CORS entirely
|
|
138
|
+
- _(unset)_ or `none` — CORS disabled (default)
|
|
134
139
|
- `https://claude.ai` — Single origin
|
|
135
140
|
- `https://claude.ai,https://your-app.com` — Multiple origins
|
|
136
141
|
- `*.example.com` — Wildcard subdomain
|
|
142
|
+
- `*` — Allow all origins. Lets any web page drive this server through a
|
|
143
|
+
visitor's browser; do not use in production.
|
|
137
144
|
|
|
138
145
|
### Authentication (OAuth 2.1)
|
|
139
146
|
|
package/package.json
CHANGED