@useorgx/openclaw-plugin 0.4.5 → 0.4.8
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 +333 -26
- package/dashboard/dist/assets/B3ziCA02.js +8 -0
- package/dashboard/dist/assets/BNeJ0kpF.js +1 -0
- package/dashboard/dist/assets/BzkiMPmM.js +215 -0
- package/dashboard/dist/assets/CUV9IHHi.js +1 -0
- package/dashboard/dist/assets/CpJsfbXo.js +9 -0
- package/dashboard/dist/assets/Ie7d9Iq2.css +1 -0
- package/dashboard/dist/assets/sAhvFnpk.js +4 -0
- package/dashboard/dist/index.html +5 -5
- package/dist/activity-actor-fields.d.ts +3 -0
- package/dist/activity-actor-fields.js +128 -0
- package/dist/activity-store.d.ts +28 -0
- package/dist/activity-store.js +257 -0
- package/dist/agent-context-store.d.ts +19 -0
- package/dist/agent-context-store.js +60 -3
- package/dist/agent-suite.d.ts +83 -0
- package/dist/agent-suite.js +615 -0
- package/dist/artifacts/register-artifact.d.ts +47 -0
- package/dist/artifacts/register-artifact.js +271 -0
- package/dist/auth-store.js +8 -13
- package/dist/contracts/client.d.ts +23 -1
- package/dist/contracts/client.js +127 -8
- package/dist/contracts/types.d.ts +194 -1
- package/dist/entity-comment-store.d.ts +29 -0
- package/dist/entity-comment-store.js +190 -0
- package/dist/hooks/post-reporting-event.mjs +326 -0
- package/dist/http-handler.d.ts +7 -1
- package/dist/http-handler.js +4500 -534
- package/dist/index.js +1078 -68
- package/dist/local-openclaw.js +8 -0
- package/dist/mcp-client-setup.js +145 -28
- package/dist/mcp-http-handler.d.ts +17 -0
- package/dist/mcp-http-handler.js +144 -3
- package/dist/next-up-queue-store.d.ts +31 -0
- package/dist/next-up-queue-store.js +169 -0
- package/dist/openclaw.plugin.json +1 -1
- package/dist/outbox.d.ts +1 -1
- package/dist/runtime-instance-store.d.ts +1 -1
- package/dist/runtime-instance-store.js +19 -2
- package/dist/skill-pack-state.d.ts +69 -0
- package/dist/skill-pack-state.js +232 -0
- package/dist/worker-supervisor.d.ts +25 -0
- package/dist/worker-supervisor.js +77 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +15 -1
- package/skills/orgx-design-agent/SKILL.md +38 -0
- package/skills/orgx-engineering-agent/SKILL.md +55 -0
- package/skills/orgx-marketing-agent/SKILL.md +40 -0
- package/skills/orgx-operations-agent/SKILL.md +40 -0
- package/skills/orgx-orchestrator-agent/SKILL.md +45 -0
- package/skills/orgx-product-agent/SKILL.md +39 -0
- package/skills/orgx-sales-agent/SKILL.md +40 -0
- package/skills/ship/SKILL.md +63 -0
- package/dashboard/dist/assets/B68j2crt.js +0 -1
- package/dashboard/dist/assets/BZZ-fiJx.js +0 -32
- package/dashboard/dist/assets/BoXlCHKa.js +0 -9
- package/dashboard/dist/assets/Bq9x_Xyh.css +0 -1
- package/dashboard/dist/assets/DBhrRVdp.js +0 -1
- package/dashboard/dist/assets/DD1jv1Hd.js +0 -8
- package/dashboard/dist/assets/DNjbmawF.js +0 -214
package/dist/local-openclaw.js
CHANGED
|
@@ -580,6 +580,10 @@ function turnToActivity(turn, session, cachedSummary, index) {
|
|
|
580
580
|
description: modelAlias,
|
|
581
581
|
agentId: session.agentId,
|
|
582
582
|
agentName: session.agentName,
|
|
583
|
+
requesterAgentId: session.agentId ?? null,
|
|
584
|
+
requesterAgentName: session.agentName ?? null,
|
|
585
|
+
executorAgentId: session.agentId ?? null,
|
|
586
|
+
executorAgentName: session.agentName ?? null,
|
|
583
587
|
runId: session.sessionId ?? session.key,
|
|
584
588
|
initiativeId: session.agentId ? `agent:${session.agentId}` : null,
|
|
585
589
|
timestamp: turn.timestamp,
|
|
@@ -747,6 +751,10 @@ function makeSessionSummaryItem(session) {
|
|
|
747
751
|
description: modelAlias ? `Local session (${modelAlias})` : "Local session",
|
|
748
752
|
agentId: session.agentId,
|
|
749
753
|
agentName: session.agentName,
|
|
754
|
+
requesterAgentId: session.agentId ?? null,
|
|
755
|
+
requesterAgentName: session.agentName ?? null,
|
|
756
|
+
executorAgentId: session.agentId ?? null,
|
|
757
|
+
executorAgentName: session.agentName ?? null,
|
|
750
758
|
runId: session.sessionId ?? session.key,
|
|
751
759
|
initiativeId: session.agentId ? `agent:${session.agentId}` : null,
|
|
752
760
|
timestamp: session.updatedAt ?? new Date().toISOString(),
|
package/dist/mcp-client-setup.js
CHANGED
|
@@ -3,6 +3,11 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { writeFileAtomicSync, writeJsonFileAtomicSync } from "./fs-utils.js";
|
|
6
|
+
const ORGX_LOCAL_MCP_KEY = "orgx-openclaw";
|
|
7
|
+
const ORGX_HOSTED_MCP_URL = "https://mcp.useorgx.com/mcp";
|
|
8
|
+
function escapeRegExp(value) {
|
|
9
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10
|
+
}
|
|
6
11
|
function isRecord(value) {
|
|
7
12
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
8
13
|
}
|
|
@@ -38,53 +43,90 @@ function backupFileSync(path, mode) {
|
|
|
38
43
|
return null;
|
|
39
44
|
}
|
|
40
45
|
}
|
|
46
|
+
function removeLegacyScopedMcpServers(servers) {
|
|
47
|
+
const next = { ...servers };
|
|
48
|
+
let updated = false;
|
|
49
|
+
const scopedPrefix = `${ORGX_LOCAL_MCP_KEY}-`;
|
|
50
|
+
for (const key of Object.keys(next)) {
|
|
51
|
+
if (key === ORGX_LOCAL_MCP_KEY)
|
|
52
|
+
continue;
|
|
53
|
+
if (!key.startsWith(scopedPrefix))
|
|
54
|
+
continue;
|
|
55
|
+
delete next[key];
|
|
56
|
+
updated = true;
|
|
57
|
+
}
|
|
58
|
+
return { updated, next };
|
|
59
|
+
}
|
|
41
60
|
export function patchClaudeMcpConfig(input) {
|
|
42
61
|
const currentServers = isRecord(input.current.mcpServers) ? input.current.mcpServers : {};
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
62
|
+
const existingOrgx = isRecord(currentServers.orgx) ? currentServers.orgx : {};
|
|
63
|
+
const existingOrgxUrl = typeof existingOrgx.url === "string" ? existingOrgx.url : "";
|
|
64
|
+
const existingOrgxType = typeof existingOrgx.type === "string" ? existingOrgx.type : "";
|
|
65
|
+
const existing = isRecord(currentServers[ORGX_LOCAL_MCP_KEY]) ? currentServers[ORGX_LOCAL_MCP_KEY] : {};
|
|
66
|
+
const priorUrl = typeof existing.url === "string" ? existing.url : "";
|
|
67
|
+
const priorType = typeof existing.type === "string" ? existing.type : "";
|
|
68
|
+
// Ensure hosted OrgX is available alongside the local proxy. Avoid overwriting
|
|
69
|
+
// custom `orgx` entries unless it's clearly redundant (pointing at the same
|
|
70
|
+
// local proxy URL we install under `orgx-openclaw`).
|
|
71
|
+
const shouldSetHostedOrgx = !isRecord(currentServers.orgx) ||
|
|
72
|
+
(existingOrgxUrl === input.localMcpUrl && existingOrgxType === "http");
|
|
73
|
+
const nextOrgxEntry = {
|
|
74
|
+
...existingOrgx,
|
|
75
|
+
type: "http",
|
|
76
|
+
url: ORGX_HOSTED_MCP_URL,
|
|
77
|
+
description: typeof existingOrgx.description === "string" && existingOrgx.description.trim().length > 0
|
|
78
|
+
? existingOrgx.description
|
|
79
|
+
: "OrgX cloud MCP (OAuth)",
|
|
80
|
+
};
|
|
81
|
+
const nextEntry = {
|
|
82
|
+
...existing,
|
|
48
83
|
type: "http",
|
|
49
84
|
url: input.localMcpUrl,
|
|
50
|
-
description: typeof
|
|
51
|
-
?
|
|
85
|
+
description: typeof existing.description === "string" && existing.description.trim().length > 0
|
|
86
|
+
? existing.description
|
|
52
87
|
: "OrgX platform via local OpenClaw plugin (no OAuth)",
|
|
53
88
|
};
|
|
54
|
-
const
|
|
89
|
+
const mergedServers = {
|
|
55
90
|
...currentServers,
|
|
56
|
-
orgx:
|
|
91
|
+
...(shouldSetHostedOrgx ? { orgx: nextOrgxEntry } : {}),
|
|
92
|
+
[ORGX_LOCAL_MCP_KEY]: nextEntry,
|
|
57
93
|
};
|
|
94
|
+
const scopedCleanup = removeLegacyScopedMcpServers(mergedServers);
|
|
58
95
|
const next = {
|
|
59
96
|
...input.current,
|
|
60
|
-
mcpServers:
|
|
97
|
+
mcpServers: scopedCleanup.next,
|
|
61
98
|
};
|
|
62
|
-
const
|
|
99
|
+
const updatedLocal = priorUrl !== input.localMcpUrl || priorType !== "http";
|
|
100
|
+
const updatedHosted = shouldSetHostedOrgx &&
|
|
101
|
+
(existingOrgxUrl !== ORGX_HOSTED_MCP_URL || existingOrgxType !== "http");
|
|
102
|
+
const updated = updatedLocal || updatedHosted || scopedCleanup.updated;
|
|
63
103
|
return { updated, next };
|
|
64
104
|
}
|
|
65
105
|
export function patchCursorMcpConfig(input) {
|
|
66
106
|
const currentServers = isRecord(input.current.mcpServers) ? input.current.mcpServers : {};
|
|
67
|
-
const
|
|
68
|
-
const existing = isRecord(currentServers[key]) ? currentServers[key] : {};
|
|
107
|
+
const existing = isRecord(currentServers[ORGX_LOCAL_MCP_KEY]) ? currentServers[ORGX_LOCAL_MCP_KEY] : {};
|
|
69
108
|
const priorUrl = typeof existing.url === "string" ? existing.url : "";
|
|
70
109
|
const nextEntry = {
|
|
71
110
|
...existing,
|
|
72
111
|
url: input.localMcpUrl,
|
|
73
112
|
};
|
|
74
|
-
const
|
|
113
|
+
const mergedServers = {
|
|
75
114
|
...currentServers,
|
|
76
|
-
[
|
|
115
|
+
[ORGX_LOCAL_MCP_KEY]: nextEntry,
|
|
77
116
|
};
|
|
117
|
+
const scopedCleanup = removeLegacyScopedMcpServers(mergedServers);
|
|
78
118
|
const next = {
|
|
79
119
|
...input.current,
|
|
80
|
-
mcpServers:
|
|
120
|
+
mcpServers: scopedCleanup.next,
|
|
81
121
|
};
|
|
82
|
-
const updated = priorUrl !== input.localMcpUrl;
|
|
122
|
+
const updated = priorUrl !== input.localMcpUrl || scopedCleanup.updated;
|
|
83
123
|
return { updated, next };
|
|
84
124
|
}
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
const
|
|
125
|
+
function upsertCodexMcpServerSection(input) {
|
|
126
|
+
const currentText = input.current;
|
|
127
|
+
const lines = currentText.split(/\r?\n/);
|
|
128
|
+
const escapedKey = escapeRegExp(input.key);
|
|
129
|
+
const headerRegex = new RegExp(`^\\[mcp_servers\\.(?:"${escapedKey}"|${escapedKey})\\]\\s*$`);
|
|
88
130
|
let headerIndex = -1;
|
|
89
131
|
for (let i = 0; i < lines.length; i += 1) {
|
|
90
132
|
if (headerRegex.test(lines[i].trim())) {
|
|
@@ -92,15 +134,12 @@ export function patchCodexConfigToml(input) {
|
|
|
92
134
|
break;
|
|
93
135
|
}
|
|
94
136
|
}
|
|
95
|
-
const urlLine = `url = "${input.
|
|
137
|
+
const urlLine = `url = "${input.url}"`;
|
|
96
138
|
if (headerIndex === -1) {
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
"",
|
|
102
|
-
].join("\n");
|
|
103
|
-
const normalized = input.current.endsWith("\n") ? input.current : `${input.current}\n`;
|
|
139
|
+
const needsQuote = /[^A-Za-z0-9_]/.test(input.key);
|
|
140
|
+
const keyLiteral = needsQuote ? `"${input.key}"` : input.key;
|
|
141
|
+
const suffix = ["", `[mcp_servers.${keyLiteral}]`, urlLine, ""].join("\n");
|
|
142
|
+
const normalized = currentText.endsWith("\n") ? currentText : `${currentText}\n`;
|
|
104
143
|
return { updated: true, next: `${normalized}${suffix}` };
|
|
105
144
|
}
|
|
106
145
|
let sectionEnd = lines.length;
|
|
@@ -127,9 +166,87 @@ export function patchCodexConfigToml(input) {
|
|
|
127
166
|
else {
|
|
128
167
|
lines.splice(headerIndex + 1, 0, urlLine);
|
|
129
168
|
updated = true;
|
|
169
|
+
// Recalculate sectionEnd after splice
|
|
170
|
+
sectionEnd = lines.length;
|
|
171
|
+
for (let i = headerIndex + 1; i < lines.length; i += 1) {
|
|
172
|
+
if (lines[i].trim().startsWith("[")) {
|
|
173
|
+
sectionEnd = i;
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Strip stale stdio-transport fields that conflict with url-only entries.
|
|
179
|
+
// Codex rejects `url` when `command`/`args` are present (stdio transport).
|
|
180
|
+
const staleFieldRegex = /^\s*(command|args|startup_timeout_sec)\s*=/;
|
|
181
|
+
for (let i = sectionEnd - 1; i > headerIndex; i -= 1) {
|
|
182
|
+
if (staleFieldRegex.test(lines[i])) {
|
|
183
|
+
lines.splice(i, 1);
|
|
184
|
+
updated = true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return { updated, next: `${lines.join("\n")}\n` };
|
|
188
|
+
}
|
|
189
|
+
function removeCodexLegacyScopedMcpSections(input) {
|
|
190
|
+
const lines = input.current.split(/\r?\n/);
|
|
191
|
+
const scopedPrefix = `${input.baseKey}-`;
|
|
192
|
+
let updated = false;
|
|
193
|
+
let index = 0;
|
|
194
|
+
while (index < lines.length) {
|
|
195
|
+
const trimmed = lines[index].trim();
|
|
196
|
+
const headerMatch = trimmed.match(/^\[mcp_servers\.(?:"([^"]+)"|([A-Za-z0-9_]+))\]\s*$/);
|
|
197
|
+
if (!headerMatch) {
|
|
198
|
+
index += 1;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const key = (headerMatch[1] ?? headerMatch[2] ?? "").trim();
|
|
202
|
+
const isLegacyScoped = key !== input.baseKey &&
|
|
203
|
+
key.startsWith(scopedPrefix);
|
|
204
|
+
if (!isLegacyScoped) {
|
|
205
|
+
index += 1;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
let sectionEnd = lines.length;
|
|
209
|
+
for (let i = index + 1; i < lines.length; i += 1) {
|
|
210
|
+
if (lines[i].trim().startsWith("[")) {
|
|
211
|
+
sectionEnd = i;
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const start = index > 0 && lines[index - 1].trim() === "" ? index - 1 : index;
|
|
216
|
+
lines.splice(start, sectionEnd - start);
|
|
217
|
+
updated = true;
|
|
218
|
+
index = Math.max(0, start);
|
|
130
219
|
}
|
|
131
220
|
return { updated, next: `${lines.join("\n")}\n` };
|
|
132
221
|
}
|
|
222
|
+
export function patchCodexConfigToml(input) {
|
|
223
|
+
let current = input.current;
|
|
224
|
+
let updated = false;
|
|
225
|
+
// Ensure the hosted OrgX entry uses a direct `url` (streamable HTTP) so that
|
|
226
|
+
// `codex mcp login orgx` can perform OAuth. Route through upsertCodexMcpServerSection
|
|
227
|
+
// so stale stdio-transport fields (command/args) are stripped automatically.
|
|
228
|
+
const hosted = upsertCodexMcpServerSection({
|
|
229
|
+
current,
|
|
230
|
+
key: "orgx",
|
|
231
|
+
url: ORGX_HOSTED_MCP_URL,
|
|
232
|
+
});
|
|
233
|
+
updated = updated || hosted.updated;
|
|
234
|
+
current = hosted.next;
|
|
235
|
+
const base = upsertCodexMcpServerSection({
|
|
236
|
+
current,
|
|
237
|
+
key: ORGX_LOCAL_MCP_KEY,
|
|
238
|
+
url: input.localMcpUrl,
|
|
239
|
+
});
|
|
240
|
+
updated = updated || base.updated;
|
|
241
|
+
current = base.next;
|
|
242
|
+
const removedScoped = removeCodexLegacyScopedMcpSections({
|
|
243
|
+
current,
|
|
244
|
+
baseKey: ORGX_LOCAL_MCP_KEY,
|
|
245
|
+
});
|
|
246
|
+
updated = updated || removedScoped.updated;
|
|
247
|
+
current = removedScoped.next;
|
|
248
|
+
return { updated, next: current };
|
|
249
|
+
}
|
|
133
250
|
export async function autoConfigureDetectedMcpClients(input) {
|
|
134
251
|
const logger = input.logger ?? {};
|
|
135
252
|
const home = input.homeDir ?? homedir();
|
|
@@ -30,9 +30,26 @@ export type RegisteredTool = {
|
|
|
30
30
|
parameters: Record<string, unknown>;
|
|
31
31
|
execute: (callId: string, params?: unknown) => Promise<ToolResult>;
|
|
32
32
|
};
|
|
33
|
+
type PromptRole = "system" | "user" | "assistant";
|
|
34
|
+
type PromptMessage = {
|
|
35
|
+
role: PromptRole;
|
|
36
|
+
content: string;
|
|
37
|
+
};
|
|
38
|
+
export type RegisteredPrompt = {
|
|
39
|
+
name: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
arguments?: Array<{
|
|
42
|
+
name: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
required?: boolean;
|
|
45
|
+
}>;
|
|
46
|
+
messages: PromptMessage[];
|
|
47
|
+
};
|
|
33
48
|
export declare function createMcpHttpHandler(input: {
|
|
34
49
|
tools: Map<string, RegisteredTool>;
|
|
50
|
+
prompts?: Map<string, RegisteredPrompt>;
|
|
35
51
|
logger?: Logger;
|
|
36
52
|
serverName: string;
|
|
37
53
|
serverVersion: string;
|
|
38
54
|
}): (req: PluginRequest, res: PluginResponse) => Promise<boolean>;
|
|
55
|
+
export {};
|
package/dist/mcp-http-handler.js
CHANGED
|
@@ -1,5 +1,79 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
const DEFAULT_PROTOCOL_VERSION = "2024-11-05";
|
|
3
|
+
// Domain-scoped MCP servers are meant to be "default safe". The unscoped
|
|
4
|
+
// `/orgx/mcp` endpoint remains available for power users / debugging.
|
|
5
|
+
//
|
|
6
|
+
// NOTE: This scopes only the tools exposed by this plugin (OrgX reporting + mutation).
|
|
7
|
+
// It cannot restrict OpenClaw-native tools (filesystem, shell, etc).
|
|
8
|
+
const ORGX_MCP_ALLOWED_TOOLS_BY_SCOPE = {
|
|
9
|
+
engineering: [
|
|
10
|
+
"orgx_status",
|
|
11
|
+
"orgx_sync",
|
|
12
|
+
"orgx_emit_activity",
|
|
13
|
+
"orgx_report_progress",
|
|
14
|
+
"orgx_register_artifact",
|
|
15
|
+
"orgx_request_decision",
|
|
16
|
+
"orgx_spawn_check",
|
|
17
|
+
],
|
|
18
|
+
product: [
|
|
19
|
+
"orgx_status",
|
|
20
|
+
"orgx_sync",
|
|
21
|
+
"orgx_emit_activity",
|
|
22
|
+
"orgx_report_progress",
|
|
23
|
+
"orgx_register_artifact",
|
|
24
|
+
"orgx_request_decision",
|
|
25
|
+
"orgx_spawn_check",
|
|
26
|
+
],
|
|
27
|
+
design: [
|
|
28
|
+
"orgx_status",
|
|
29
|
+
"orgx_sync",
|
|
30
|
+
"orgx_emit_activity",
|
|
31
|
+
"orgx_report_progress",
|
|
32
|
+
"orgx_register_artifact",
|
|
33
|
+
"orgx_request_decision",
|
|
34
|
+
"orgx_spawn_check",
|
|
35
|
+
],
|
|
36
|
+
marketing: [
|
|
37
|
+
"orgx_status",
|
|
38
|
+
"orgx_sync",
|
|
39
|
+
"orgx_emit_activity",
|
|
40
|
+
"orgx_report_progress",
|
|
41
|
+
"orgx_register_artifact",
|
|
42
|
+
"orgx_request_decision",
|
|
43
|
+
"orgx_spawn_check",
|
|
44
|
+
],
|
|
45
|
+
sales: [
|
|
46
|
+
"orgx_status",
|
|
47
|
+
"orgx_sync",
|
|
48
|
+
"orgx_emit_activity",
|
|
49
|
+
"orgx_report_progress",
|
|
50
|
+
"orgx_register_artifact",
|
|
51
|
+
"orgx_request_decision",
|
|
52
|
+
"orgx_spawn_check",
|
|
53
|
+
],
|
|
54
|
+
operations: [
|
|
55
|
+
"orgx_status",
|
|
56
|
+
"orgx_sync",
|
|
57
|
+
"orgx_emit_activity",
|
|
58
|
+
"orgx_report_progress",
|
|
59
|
+
"orgx_register_artifact",
|
|
60
|
+
"orgx_request_decision",
|
|
61
|
+
"orgx_spawn_check",
|
|
62
|
+
// Operations is allowed to do explicit changesets for remediation/runbooks.
|
|
63
|
+
"orgx_apply_changeset",
|
|
64
|
+
],
|
|
65
|
+
orchestration: [
|
|
66
|
+
"orgx_status",
|
|
67
|
+
"orgx_sync",
|
|
68
|
+
"orgx_emit_activity",
|
|
69
|
+
"orgx_report_progress",
|
|
70
|
+
"orgx_register_artifact",
|
|
71
|
+
"orgx_request_decision",
|
|
72
|
+
"orgx_spawn_check",
|
|
73
|
+
// Orchestrator is the primary mutation surface by design.
|
|
74
|
+
"orgx_apply_changeset",
|
|
75
|
+
],
|
|
76
|
+
};
|
|
3
77
|
function isRecord(value) {
|
|
4
78
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
5
79
|
}
|
|
@@ -38,6 +112,28 @@ function normalizePath(rawUrl) {
|
|
|
38
112
|
const [path] = rawUrl.split("?", 2);
|
|
39
113
|
return path || "/";
|
|
40
114
|
}
|
|
115
|
+
function parseScopeKey(url) {
|
|
116
|
+
// Supported paths:
|
|
117
|
+
// - /orgx/mcp (unscoped)
|
|
118
|
+
// - /orgx/mcp/<scope> (domain-scoped)
|
|
119
|
+
if (!url.startsWith("/orgx/mcp/"))
|
|
120
|
+
return null;
|
|
121
|
+
const rest = url.slice("/orgx/mcp/".length);
|
|
122
|
+
const key = rest.split("/", 1)[0]?.trim() ?? "";
|
|
123
|
+
if (!key)
|
|
124
|
+
return null;
|
|
125
|
+
if (!Object.prototype.hasOwnProperty.call(ORGX_MCP_ALLOWED_TOOLS_BY_SCOPE, key))
|
|
126
|
+
return null;
|
|
127
|
+
return key;
|
|
128
|
+
}
|
|
129
|
+
function resolveToolScope(scopeKey) {
|
|
130
|
+
if (!scopeKey)
|
|
131
|
+
return null;
|
|
132
|
+
return {
|
|
133
|
+
key: scopeKey,
|
|
134
|
+
allowedTools: new Set(ORGX_MCP_ALLOWED_TOOLS_BY_SCOPE[scopeKey]),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
41
137
|
async function readRequestBodyBuffer(req) {
|
|
42
138
|
const body = req.body;
|
|
43
139
|
if (typeof body === "string")
|
|
@@ -131,13 +227,15 @@ async function handleRpcMessage(input) {
|
|
|
131
227
|
if (method === "initialize") {
|
|
132
228
|
const requestedProtocol = typeof params.protocolVersion === "string" ? params.protocolVersion : null;
|
|
133
229
|
const protocolVersion = requestedProtocol?.trim() || DEFAULT_PROTOCOL_VERSION;
|
|
230
|
+
const scopedServerName = input.toolScope ? `${input.serverName}/${input.toolScope.key}` : input.serverName;
|
|
134
231
|
return jsonRpcResult(id, {
|
|
135
232
|
protocolVersion,
|
|
136
233
|
capabilities: {
|
|
137
234
|
tools: {},
|
|
235
|
+
prompts: {},
|
|
138
236
|
},
|
|
139
237
|
serverInfo: {
|
|
140
|
-
name:
|
|
238
|
+
name: scopedServerName,
|
|
141
239
|
version: input.serverVersion,
|
|
142
240
|
},
|
|
143
241
|
});
|
|
@@ -146,6 +244,15 @@ async function handleRpcMessage(input) {
|
|
|
146
244
|
return jsonRpcResult(id, { ok: true });
|
|
147
245
|
}
|
|
148
246
|
if (method === "tools/list") {
|
|
247
|
+
if (input.toolScope) {
|
|
248
|
+
const scopedTools = new Map();
|
|
249
|
+
for (const name of input.toolScope.allowedTools) {
|
|
250
|
+
const tool = input.tools.get(name);
|
|
251
|
+
if (tool)
|
|
252
|
+
scopedTools.set(name, tool);
|
|
253
|
+
}
|
|
254
|
+
return jsonRpcResult(id, { tools: buildToolsList(scopedTools) });
|
|
255
|
+
}
|
|
149
256
|
return jsonRpcResult(id, {
|
|
150
257
|
tools: buildToolsList(input.tools),
|
|
151
258
|
});
|
|
@@ -155,6 +262,9 @@ async function handleRpcMessage(input) {
|
|
|
155
262
|
if (!toolName) {
|
|
156
263
|
return jsonRpcError(id, -32602, "Missing tool name");
|
|
157
264
|
}
|
|
265
|
+
if (input.toolScope && !input.toolScope.allowedTools.has(toolName)) {
|
|
266
|
+
return jsonRpcError(id, -32601, `Tool not available in scope '${input.toolScope.key}': ${toolName}`);
|
|
267
|
+
}
|
|
158
268
|
const tool = input.tools.get(toolName) ?? null;
|
|
159
269
|
if (!tool) {
|
|
160
270
|
return jsonRpcError(id, -32601, `Tool not found: ${toolName}`);
|
|
@@ -188,7 +298,28 @@ async function handleRpcMessage(input) {
|
|
|
188
298
|
return jsonRpcResult(id, { resources: [] });
|
|
189
299
|
}
|
|
190
300
|
if (method === "prompts/list") {
|
|
191
|
-
|
|
301
|
+
const prompts = Array.from(input.prompts.values())
|
|
302
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
303
|
+
.map((prompt) => ({
|
|
304
|
+
name: prompt.name,
|
|
305
|
+
description: prompt.description ?? "",
|
|
306
|
+
arguments: Array.isArray(prompt.arguments) ? prompt.arguments : [],
|
|
307
|
+
}));
|
|
308
|
+
return jsonRpcResult(id, { prompts });
|
|
309
|
+
}
|
|
310
|
+
if (method === "prompts/get") {
|
|
311
|
+
const promptName = typeof params.name === "string" ? params.name.trim() : "";
|
|
312
|
+
if (!promptName) {
|
|
313
|
+
return jsonRpcError(id, -32602, "Missing prompt name");
|
|
314
|
+
}
|
|
315
|
+
const prompt = input.prompts.get(promptName) ?? null;
|
|
316
|
+
if (!prompt) {
|
|
317
|
+
return jsonRpcError(id, -32601, `Prompt not found: ${promptName}`);
|
|
318
|
+
}
|
|
319
|
+
return jsonRpcResult(id, {
|
|
320
|
+
description: prompt.description ?? "",
|
|
321
|
+
messages: prompt.messages,
|
|
322
|
+
});
|
|
192
323
|
}
|
|
193
324
|
if (method.startsWith("notifications/")) {
|
|
194
325
|
return null;
|
|
@@ -197,6 +328,7 @@ async function handleRpcMessage(input) {
|
|
|
197
328
|
}
|
|
198
329
|
export function createMcpHttpHandler(input) {
|
|
199
330
|
const logger = input.logger ?? {};
|
|
331
|
+
const prompts = input.prompts ?? new Map();
|
|
200
332
|
return async function handler(req, res) {
|
|
201
333
|
const method = (req.method ?? "GET").toUpperCase();
|
|
202
334
|
const rawUrl = req.url ?? "/";
|
|
@@ -204,6 +336,12 @@ export function createMcpHttpHandler(input) {
|
|
|
204
336
|
if (!(url === "/orgx/mcp" || url.startsWith("/orgx/mcp/"))) {
|
|
205
337
|
return false;
|
|
206
338
|
}
|
|
339
|
+
const scopeKey = parseScopeKey(url);
|
|
340
|
+
const toolScope = resolveToolScope(scopeKey);
|
|
341
|
+
if (url.startsWith("/orgx/mcp/") && !scopeKey) {
|
|
342
|
+
sendText(res, 404, `Unknown OrgX MCP scope. Supported: ${Object.keys(ORGX_MCP_ALLOWED_TOOLS_BY_SCOPE).join(", ")}\n`);
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
207
345
|
if (method === "OPTIONS") {
|
|
208
346
|
res.writeHead(204, {
|
|
209
347
|
"cache-control": "no-store",
|
|
@@ -212,7 +350,8 @@ export function createMcpHttpHandler(input) {
|
|
|
212
350
|
return true;
|
|
213
351
|
}
|
|
214
352
|
if (method === "GET") {
|
|
215
|
-
|
|
353
|
+
const suffix = toolScope ? ` (scope: ${toolScope.key})` : "";
|
|
354
|
+
sendText(res, 200, `OrgX Local MCP bridge is running${suffix}.\n`);
|
|
216
355
|
return true;
|
|
217
356
|
}
|
|
218
357
|
if (method !== "POST") {
|
|
@@ -234,9 +373,11 @@ export function createMcpHttpHandler(input) {
|
|
|
234
373
|
const response = await handleRpcMessage({
|
|
235
374
|
message,
|
|
236
375
|
tools: input.tools,
|
|
376
|
+
prompts,
|
|
237
377
|
logger,
|
|
238
378
|
serverName: input.serverName,
|
|
239
379
|
serverVersion: input.serverVersion,
|
|
380
|
+
toolScope,
|
|
240
381
|
});
|
|
241
382
|
if (response)
|
|
242
383
|
responses.push(response);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type NextUpPinnedEntry = {
|
|
2
|
+
initiativeId: string;
|
|
3
|
+
workstreamId: string;
|
|
4
|
+
preferredTaskId: string | null;
|
|
5
|
+
preferredMilestoneId: string | null;
|
|
6
|
+
createdAt: string;
|
|
7
|
+
updatedAt: string;
|
|
8
|
+
};
|
|
9
|
+
type PersistedNextUpQueue = {
|
|
10
|
+
version: 1;
|
|
11
|
+
updatedAt: string;
|
|
12
|
+
pins: NextUpPinnedEntry[];
|
|
13
|
+
};
|
|
14
|
+
export declare function readNextUpQueuePins(): PersistedNextUpQueue;
|
|
15
|
+
export declare function upsertNextUpQueuePin(input: {
|
|
16
|
+
initiativeId: string;
|
|
17
|
+
workstreamId: string;
|
|
18
|
+
preferredTaskId?: string | null;
|
|
19
|
+
preferredMilestoneId?: string | null;
|
|
20
|
+
}): PersistedNextUpQueue;
|
|
21
|
+
export declare function removeNextUpQueuePin(input: {
|
|
22
|
+
initiativeId: string;
|
|
23
|
+
workstreamId: string;
|
|
24
|
+
}): PersistedNextUpQueue;
|
|
25
|
+
export declare function setNextUpQueuePinOrder(input: {
|
|
26
|
+
order: Array<{
|
|
27
|
+
initiativeId: string;
|
|
28
|
+
workstreamId: string;
|
|
29
|
+
}>;
|
|
30
|
+
}): PersistedNextUpQueue;
|
|
31
|
+
export {};
|