@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.
Files changed (60) hide show
  1. package/README.md +333 -26
  2. package/dashboard/dist/assets/B3ziCA02.js +8 -0
  3. package/dashboard/dist/assets/BNeJ0kpF.js +1 -0
  4. package/dashboard/dist/assets/BzkiMPmM.js +215 -0
  5. package/dashboard/dist/assets/CUV9IHHi.js +1 -0
  6. package/dashboard/dist/assets/CpJsfbXo.js +9 -0
  7. package/dashboard/dist/assets/Ie7d9Iq2.css +1 -0
  8. package/dashboard/dist/assets/sAhvFnpk.js +4 -0
  9. package/dashboard/dist/index.html +5 -5
  10. package/dist/activity-actor-fields.d.ts +3 -0
  11. package/dist/activity-actor-fields.js +128 -0
  12. package/dist/activity-store.d.ts +28 -0
  13. package/dist/activity-store.js +257 -0
  14. package/dist/agent-context-store.d.ts +19 -0
  15. package/dist/agent-context-store.js +60 -3
  16. package/dist/agent-suite.d.ts +83 -0
  17. package/dist/agent-suite.js +615 -0
  18. package/dist/artifacts/register-artifact.d.ts +47 -0
  19. package/dist/artifacts/register-artifact.js +271 -0
  20. package/dist/auth-store.js +8 -13
  21. package/dist/contracts/client.d.ts +23 -1
  22. package/dist/contracts/client.js +127 -8
  23. package/dist/contracts/types.d.ts +194 -1
  24. package/dist/entity-comment-store.d.ts +29 -0
  25. package/dist/entity-comment-store.js +190 -0
  26. package/dist/hooks/post-reporting-event.mjs +326 -0
  27. package/dist/http-handler.d.ts +7 -1
  28. package/dist/http-handler.js +4500 -534
  29. package/dist/index.js +1078 -68
  30. package/dist/local-openclaw.js +8 -0
  31. package/dist/mcp-client-setup.js +145 -28
  32. package/dist/mcp-http-handler.d.ts +17 -0
  33. package/dist/mcp-http-handler.js +144 -3
  34. package/dist/next-up-queue-store.d.ts +31 -0
  35. package/dist/next-up-queue-store.js +169 -0
  36. package/dist/openclaw.plugin.json +1 -1
  37. package/dist/outbox.d.ts +1 -1
  38. package/dist/runtime-instance-store.d.ts +1 -1
  39. package/dist/runtime-instance-store.js +19 -2
  40. package/dist/skill-pack-state.d.ts +69 -0
  41. package/dist/skill-pack-state.js +232 -0
  42. package/dist/worker-supervisor.d.ts +25 -0
  43. package/dist/worker-supervisor.js +77 -0
  44. package/openclaw.plugin.json +1 -1
  45. package/package.json +15 -1
  46. package/skills/orgx-design-agent/SKILL.md +38 -0
  47. package/skills/orgx-engineering-agent/SKILL.md +55 -0
  48. package/skills/orgx-marketing-agent/SKILL.md +40 -0
  49. package/skills/orgx-operations-agent/SKILL.md +40 -0
  50. package/skills/orgx-orchestrator-agent/SKILL.md +45 -0
  51. package/skills/orgx-product-agent/SKILL.md +39 -0
  52. package/skills/orgx-sales-agent/SKILL.md +40 -0
  53. package/skills/ship/SKILL.md +63 -0
  54. package/dashboard/dist/assets/B68j2crt.js +0 -1
  55. package/dashboard/dist/assets/BZZ-fiJx.js +0 -32
  56. package/dashboard/dist/assets/BoXlCHKa.js +0 -9
  57. package/dashboard/dist/assets/Bq9x_Xyh.css +0 -1
  58. package/dashboard/dist/assets/DBhrRVdp.js +0 -1
  59. package/dashboard/dist/assets/DD1jv1Hd.js +0 -8
  60. package/dashboard/dist/assets/DNjbmawF.js +0 -214
@@ -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(),
@@ -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 currentOrgx = isRecord(currentServers.orgx) ? currentServers.orgx : {};
44
- const priorUrl = typeof currentOrgx.url === "string" ? currentOrgx.url : "";
45
- const priorType = typeof currentOrgx.type === "string" ? currentOrgx.type : "";
46
- const nextOrgx = {
47
- ...currentOrgx,
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 currentOrgx.description === "string" && currentOrgx.description.trim().length > 0
51
- ? currentOrgx.description
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 nextServers = {
89
+ const mergedServers = {
55
90
  ...currentServers,
56
- orgx: nextOrgx,
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: nextServers,
97
+ mcpServers: scopedCleanup.next,
61
98
  };
62
- const updated = priorUrl !== input.localMcpUrl || priorType !== "http";
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 key = "orgx-openclaw";
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 nextServers = {
113
+ const mergedServers = {
75
114
  ...currentServers,
76
- [key]: nextEntry,
115
+ [ORGX_LOCAL_MCP_KEY]: nextEntry,
77
116
  };
117
+ const scopedCleanup = removeLegacyScopedMcpServers(mergedServers);
78
118
  const next = {
79
119
  ...input.current,
80
- mcpServers: nextServers,
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
- export function patchCodexConfigToml(input) {
86
- const lines = input.current.split(/\r?\n/);
87
- const headerRegex = /^\[mcp_servers\.(?:orgx|"orgx")\]\s*$/;
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.localMcpUrl}"`;
137
+ const urlLine = `url = "${input.url}"`;
96
138
  if (headerIndex === -1) {
97
- const suffix = [
98
- "",
99
- "[mcp_servers.orgx]",
100
- urlLine,
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 {};
@@ -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: input.serverName,
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
- return jsonRpcResult(id, { prompts: [] });
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
- sendText(res, 200, "OrgX Local MCP bridge is running.\n");
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 {};