@useorgx/openclaw-plugin 0.4.4 → 0.4.5
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 +64 -2
- package/dashboard/dist/assets/B68j2crt.js +1 -0
- package/dashboard/dist/assets/BZZ-fiJx.js +32 -0
- package/dashboard/dist/assets/{4hvaB0UC.js → BoXlCHKa.js} +1 -1
- package/dashboard/dist/assets/Bq9x_Xyh.css +1 -0
- package/dashboard/dist/assets/DBhrRVdp.js +1 -0
- package/dashboard/dist/assets/DD1jv1Hd.js +8 -0
- package/dashboard/dist/assets/DNjbmawF.js +214 -0
- package/dashboard/dist/index.html +5 -5
- package/dist/http-handler.js +14 -5
- package/dist/index.js +111 -26
- package/dist/mcp-client-setup.d.ts +30 -0
- package/dist/mcp-client-setup.js +215 -0
- package/dist/mcp-http-handler.d.ts +38 -0
- package/dist/mcp-http-handler.js +254 -0
- package/package.json +1 -1
- package/dashboard/dist/assets/B3ziCA02.js +0 -8
- package/dashboard/dist/assets/BgsfM2lz.js +0 -1
- package/dashboard/dist/assets/DCBlK4MX.js +0 -212
- package/dashboard/dist/assets/DEuY_RBN.js +0 -1
- package/dashboard/dist/assets/jyFhCND-.css +0 -1
- package/dashboard/dist/assets/sAhvFnpk.js +0 -4
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
<meta name="googlebot" content="noindex, nofollow, noarchive, nosnippet, noimageindex" />
|
|
10
10
|
<meta name="copyright" content="OrgX. All rights reserved." />
|
|
11
11
|
<title>OrgX Live Dashboard</title>
|
|
12
|
-
<script type="module" crossorigin src="/orgx/live/assets/
|
|
12
|
+
<script type="module" crossorigin src="/orgx/live/assets/DNjbmawF.js"></script>
|
|
13
13
|
<link rel="modulepreload" crossorigin href="/orgx/live/assets/C-KIc3Wc.js">
|
|
14
|
-
<link rel="modulepreload" crossorigin href="/orgx/live/assets/
|
|
15
|
-
<link rel="modulepreload" crossorigin href="/orgx/live/assets/
|
|
16
|
-
<link rel="modulepreload" crossorigin href="/orgx/live/assets/
|
|
17
|
-
<link rel="stylesheet" crossorigin href="/orgx/live/assets/
|
|
14
|
+
<link rel="modulepreload" crossorigin href="/orgx/live/assets/BZZ-fiJx.js">
|
|
15
|
+
<link rel="modulepreload" crossorigin href="/orgx/live/assets/DD1jv1Hd.js">
|
|
16
|
+
<link rel="modulepreload" crossorigin href="/orgx/live/assets/BoXlCHKa.js">
|
|
17
|
+
<link rel="stylesheet" crossorigin href="/orgx/live/assets/Bq9x_Xyh.css">
|
|
18
18
|
</head>
|
|
19
19
|
<body class="bg-[#080808] text-white antialiased">
|
|
20
20
|
<div id="root"></div>
|
package/dist/http-handler.js
CHANGED
|
@@ -2214,13 +2214,22 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2214
2214
|
type: "progress",
|
|
2215
2215
|
timestamp,
|
|
2216
2216
|
payload: {
|
|
2217
|
-
|
|
2217
|
+
// Keep this payload aligned with OrgXClient.emitActivity input
|
|
2218
|
+
// so outbox replay can forward it without shape translation.
|
|
2219
|
+
initiative_id: initiativeId,
|
|
2220
|
+
run_id: input.runId?.trim() || undefined,
|
|
2221
|
+
correlation_id: input.runId
|
|
2222
|
+
? undefined
|
|
2223
|
+
: (input.correlationId?.trim() || `openclaw-${Date.now()}`),
|
|
2224
|
+
source_client: "openclaw",
|
|
2218
2225
|
message,
|
|
2226
|
+
phase: input.phase,
|
|
2227
|
+
progress_pct: typeof input.progressPct === "number" && Number.isFinite(input.progressPct)
|
|
2228
|
+
? Math.max(0, Math.min(100, Math.round(input.progressPct)))
|
|
2229
|
+
: undefined,
|
|
2219
2230
|
level: input.level ?? "info",
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
nextStep: input.nextStep ?? null,
|
|
2223
|
-
metadata: input.metadata ?? null,
|
|
2231
|
+
next_step: input.nextStep ?? undefined,
|
|
2232
|
+
metadata: input.metadata ?? undefined,
|
|
2224
2233
|
},
|
|
2225
2234
|
activityItem,
|
|
2226
2235
|
});
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,9 @@ import { clearPersistedSnapshot, readPersistedSnapshot, writePersistedSnapshot,
|
|
|
22
22
|
import { appendToOutbox, readOutbox, readOutboxSummary, replaceOutbox, } from "./outbox.js";
|
|
23
23
|
import { extractProgressOutboxMessage } from "./reporting/outbox-replay.js";
|
|
24
24
|
import { ensureGatewayWatchdog } from "./gateway-watchdog.js";
|
|
25
|
+
import { createMcpHttpHandler } from "./mcp-http-handler.js";
|
|
26
|
+
import { autoConfigureDetectedMcpClients } from "./mcp-client-setup.js";
|
|
27
|
+
import { readOpenClawGatewayPort, readOpenClawSettingsSnapshot } from "./openclaw-settings.js";
|
|
25
28
|
import { posthogCapture } from "./telemetry/posthog.js";
|
|
26
29
|
export { OrgXClient } from "./api.js";
|
|
27
30
|
const DEFAULT_BASE_URL = "https://www.useorgx.com";
|
|
@@ -364,14 +367,14 @@ export default function register(api) {
|
|
|
364
367
|
const defaultReportingCorrelationId = pickNonEmptyString(process.env.ORGX_CORRELATION_ID) ??
|
|
365
368
|
`openclaw-${config.installationId}`;
|
|
366
369
|
function resolveReportingContext(input) {
|
|
367
|
-
const initiativeId = pickNonEmptyString(input.initiative_id, process.env.ORGX_INITIATIVE_ID);
|
|
370
|
+
const initiativeId = pickNonEmptyString(input.initiative_id, input.initiativeId, process.env.ORGX_INITIATIVE_ID);
|
|
368
371
|
if (!initiativeId || !isUuid(initiativeId)) {
|
|
369
372
|
return {
|
|
370
373
|
ok: false,
|
|
371
374
|
error: "initiative_id is required (set ORGX_INITIATIVE_ID or pass initiative_id).",
|
|
372
375
|
};
|
|
373
376
|
}
|
|
374
|
-
const sourceCandidate = pickNonEmptyString(input.source_client, process.env.ORGX_SOURCE_CLIENT, "openclaw");
|
|
377
|
+
const sourceCandidate = pickNonEmptyString(input.source_client, input.sourceClient, process.env.ORGX_SOURCE_CLIENT, "openclaw");
|
|
375
378
|
const sourceClient = sourceCandidate === "codex" ||
|
|
376
379
|
sourceCandidate === "claude-code" ||
|
|
377
380
|
sourceCandidate === "api" ||
|
|
@@ -382,7 +385,10 @@ export default function register(api) {
|
|
|
382
385
|
const runId = isUuid(runIdCandidate) ? runIdCandidate : undefined;
|
|
383
386
|
const correlationId = runId
|
|
384
387
|
? undefined
|
|
385
|
-
: pickNonEmptyString(input.correlation_id,
|
|
388
|
+
: pickNonEmptyString(input.correlation_id, input.correlationId,
|
|
389
|
+
// Legacy: some buffered payloads only stored a local `runId` which is
|
|
390
|
+
// better treated as a correlation key than a server-backed run_id.
|
|
391
|
+
input.runId, defaultReportingCorrelationId, `openclaw-${Date.now()}`);
|
|
386
392
|
return {
|
|
387
393
|
ok: true,
|
|
388
394
|
value: {
|
|
@@ -622,6 +628,23 @@ export default function register(api) {
|
|
|
622
628
|
installationId: config.installationId,
|
|
623
629
|
workspaceName: input.workspaceName ?? onboardingState.workspaceName,
|
|
624
630
|
});
|
|
631
|
+
if (input.source === "browser_pairing" &&
|
|
632
|
+
process.env.ORGX_DISABLE_MCP_CLIENT_AUTOCONFIG !== "1") {
|
|
633
|
+
try {
|
|
634
|
+
const snapshot = readOpenClawSettingsSnapshot();
|
|
635
|
+
const port = readOpenClawGatewayPort(snapshot.raw);
|
|
636
|
+
const localMcpUrl = `http://127.0.0.1:${port}/orgx/mcp`;
|
|
637
|
+
void autoConfigureDetectedMcpClients({
|
|
638
|
+
localMcpUrl,
|
|
639
|
+
logger: api.log ?? {},
|
|
640
|
+
}).catch(() => {
|
|
641
|
+
// best effort
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
catch {
|
|
645
|
+
// best effort
|
|
646
|
+
}
|
|
647
|
+
}
|
|
625
648
|
}
|
|
626
649
|
// ---------------------------------------------------------------------------
|
|
627
650
|
// 1. Background Sync Service
|
|
@@ -629,7 +652,6 @@ export default function register(api) {
|
|
|
629
652
|
let syncTimer = null;
|
|
630
653
|
let syncInFlight = null;
|
|
631
654
|
let syncServiceRunning = false;
|
|
632
|
-
const outboxQueues = ["progress", "decisions", "artifacts"];
|
|
633
655
|
let outboxReplayState = {
|
|
634
656
|
status: "idle",
|
|
635
657
|
lastReplayAttemptAt: null,
|
|
@@ -809,7 +831,11 @@ export default function register(api) {
|
|
|
809
831
|
throw new Error(context.error);
|
|
810
832
|
}
|
|
811
833
|
const rawPhase = pickStringField(payload, "phase") ?? "implementing";
|
|
812
|
-
const progressPct = typeof payload.progress_pct === "number"
|
|
834
|
+
const progressPct = typeof payload.progress_pct === "number"
|
|
835
|
+
? payload.progress_pct
|
|
836
|
+
: typeof payload.progressPct === "number"
|
|
837
|
+
? payload.progressPct
|
|
838
|
+
: undefined;
|
|
813
839
|
const phase = rawPhase === "intent" ||
|
|
814
840
|
rawPhase === "execution" ||
|
|
815
841
|
rawPhase === "blocked" ||
|
|
@@ -818,7 +844,11 @@ export default function register(api) {
|
|
|
818
844
|
rawPhase === "completed"
|
|
819
845
|
? rawPhase
|
|
820
846
|
: toReportingPhase(rawPhase, progressPct);
|
|
821
|
-
|
|
847
|
+
const metaRaw = payload.metadata;
|
|
848
|
+
const meta = metaRaw && typeof metaRaw === "object" && !Array.isArray(metaRaw)
|
|
849
|
+
? metaRaw
|
|
850
|
+
: {};
|
|
851
|
+
const emitPayload = {
|
|
822
852
|
initiative_id: context.value.initiativeId,
|
|
823
853
|
run_id: context.value.runId,
|
|
824
854
|
correlation_id: context.value.correlationId,
|
|
@@ -827,12 +857,44 @@ export default function register(api) {
|
|
|
827
857
|
phase,
|
|
828
858
|
progress_pct: progressPct,
|
|
829
859
|
level: pickStringField(payload, "level"),
|
|
830
|
-
next_step: pickStringField(payload, "next_step") ??
|
|
860
|
+
next_step: pickStringField(payload, "next_step") ??
|
|
861
|
+
pickStringField(payload, "nextStep") ??
|
|
862
|
+
undefined,
|
|
831
863
|
metadata: {
|
|
864
|
+
...meta,
|
|
832
865
|
source: "orgx_openclaw_outbox_replay",
|
|
833
866
|
outbox_event_id: event.id,
|
|
834
867
|
},
|
|
835
|
-
}
|
|
868
|
+
};
|
|
869
|
+
try {
|
|
870
|
+
await client.emitActivity(emitPayload);
|
|
871
|
+
}
|
|
872
|
+
catch (err) {
|
|
873
|
+
// Some locally-buffered events carry a UUID that *looks* like an OrgX run_id
|
|
874
|
+
// but was only ever used as a local correlation/grouping key. If OrgX
|
|
875
|
+
// doesn't recognize it, retry by treating it as correlation_id so OrgX can
|
|
876
|
+
// create/attach a run deterministically.
|
|
877
|
+
const msg = toErrorMessage(err);
|
|
878
|
+
if (emitPayload.run_id &&
|
|
879
|
+
/^404\\b/.test(msg) &&
|
|
880
|
+
/\\brun\\b/i.test(msg) &&
|
|
881
|
+
/not found/i.test(msg)) {
|
|
882
|
+
await client.emitActivity({
|
|
883
|
+
...emitPayload,
|
|
884
|
+
run_id: undefined,
|
|
885
|
+
// Avoid passing a bare UUID as correlation_id since some server
|
|
886
|
+
// paths may interpret UUIDs as run_id lookups.
|
|
887
|
+
correlation_id: `openclaw:${emitPayload.run_id}`,
|
|
888
|
+
metadata: {
|
|
889
|
+
...emitPayload.metadata,
|
|
890
|
+
replay_run_id_as_correlation: true,
|
|
891
|
+
},
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
throw err;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
836
898
|
return;
|
|
837
899
|
}
|
|
838
900
|
if (event.type === "decision") {
|
|
@@ -918,7 +980,14 @@ export default function register(api) {
|
|
|
918
980
|
};
|
|
919
981
|
let hadReplayFailure = false;
|
|
920
982
|
let lastReplayError = null;
|
|
921
|
-
|
|
983
|
+
// Outbox files are keyed by *session id* (e.g. initiative/run correlation),
|
|
984
|
+
// not by event type.
|
|
985
|
+
const outboxSummary = await readOutboxSummary();
|
|
986
|
+
const queues = Object.entries(outboxSummary.pendingByQueue)
|
|
987
|
+
.filter(([, count]) => typeof count === "number" && count > 0)
|
|
988
|
+
.map(([queueId]) => queueId)
|
|
989
|
+
.sort();
|
|
990
|
+
for (const queue of queues) {
|
|
922
991
|
const pending = await readOutbox(queue);
|
|
923
992
|
if (pending.length === 0) {
|
|
924
993
|
continue;
|
|
@@ -1363,8 +1432,13 @@ export default function register(api) {
|
|
|
1363
1432
|
// ---------------------------------------------------------------------------
|
|
1364
1433
|
// 2. MCP Tools (Model Context Protocol compatible)
|
|
1365
1434
|
// ---------------------------------------------------------------------------
|
|
1435
|
+
const mcpToolRegistry = new Map();
|
|
1436
|
+
const registerMcpTool = (tool, options) => {
|
|
1437
|
+
mcpToolRegistry.set(tool.name, tool);
|
|
1438
|
+
api.registerTool(tool, options);
|
|
1439
|
+
};
|
|
1366
1440
|
// --- orgx_status ---
|
|
1367
|
-
|
|
1441
|
+
registerMcpTool({
|
|
1368
1442
|
name: "orgx_status",
|
|
1369
1443
|
description: "Get current OrgX org status: active initiatives, agent states, pending decisions, active tasks.",
|
|
1370
1444
|
parameters: {
|
|
@@ -1384,7 +1458,7 @@ export default function register(api) {
|
|
|
1384
1458
|
},
|
|
1385
1459
|
}, { optional: true });
|
|
1386
1460
|
// --- orgx_sync ---
|
|
1387
|
-
|
|
1461
|
+
registerMcpTool({
|
|
1388
1462
|
name: "orgx_sync",
|
|
1389
1463
|
description: "Push/pull memory sync with OrgX. Send local memory/daily log; receive initiatives, tasks, decisions, model routing policy.",
|
|
1390
1464
|
parameters: {
|
|
@@ -1414,7 +1488,7 @@ export default function register(api) {
|
|
|
1414
1488
|
},
|
|
1415
1489
|
}, { optional: true });
|
|
1416
1490
|
// --- orgx_delegation_preflight ---
|
|
1417
|
-
|
|
1491
|
+
registerMcpTool({
|
|
1418
1492
|
name: "orgx_delegation_preflight",
|
|
1419
1493
|
description: "Run delegation preflight to score scope quality, estimate ETA/cost, and suggest a split before autonomous execution.",
|
|
1420
1494
|
parameters: {
|
|
@@ -1465,7 +1539,7 @@ export default function register(api) {
|
|
|
1465
1539
|
},
|
|
1466
1540
|
}, { optional: true });
|
|
1467
1541
|
// --- orgx_run_action ---
|
|
1468
|
-
|
|
1542
|
+
registerMcpTool({
|
|
1469
1543
|
name: "orgx_run_action",
|
|
1470
1544
|
description: "Apply a control action to a run: pause, resume, cancel, or rollback (rollback requires checkpointId).",
|
|
1471
1545
|
parameters: {
|
|
@@ -1509,7 +1583,7 @@ export default function register(api) {
|
|
|
1509
1583
|
},
|
|
1510
1584
|
}, { optional: true });
|
|
1511
1585
|
// --- orgx_checkpoints_list ---
|
|
1512
|
-
|
|
1586
|
+
registerMcpTool({
|
|
1513
1587
|
name: "orgx_checkpoints_list",
|
|
1514
1588
|
description: "List checkpoints for a run.",
|
|
1515
1589
|
parameters: {
|
|
@@ -1534,7 +1608,7 @@ export default function register(api) {
|
|
|
1534
1608
|
},
|
|
1535
1609
|
}, { optional: true });
|
|
1536
1610
|
// --- orgx_checkpoint_restore ---
|
|
1537
|
-
|
|
1611
|
+
registerMcpTool({
|
|
1538
1612
|
name: "orgx_checkpoint_restore",
|
|
1539
1613
|
description: "Restore a run to a specific checkpoint.",
|
|
1540
1614
|
parameters: {
|
|
@@ -1573,7 +1647,7 @@ export default function register(api) {
|
|
|
1573
1647
|
},
|
|
1574
1648
|
}, { optional: true });
|
|
1575
1649
|
// --- orgx_spawn_check ---
|
|
1576
|
-
|
|
1650
|
+
registerMcpTool({
|
|
1577
1651
|
name: "orgx_spawn_check",
|
|
1578
1652
|
description: "Check quality gate + get model routing before spawning a sub-agent. Returns allowed/denied, model tier, and check details.",
|
|
1579
1653
|
parameters: {
|
|
@@ -1602,7 +1676,7 @@ export default function register(api) {
|
|
|
1602
1676
|
},
|
|
1603
1677
|
}, { optional: true });
|
|
1604
1678
|
// --- orgx_quality_score ---
|
|
1605
|
-
|
|
1679
|
+
registerMcpTool({
|
|
1606
1680
|
name: "orgx_quality_score",
|
|
1607
1681
|
description: "Record a quality score (1-5) for completed agent work. Used to gate future spawns and track performance.",
|
|
1608
1682
|
parameters: {
|
|
@@ -1640,7 +1714,7 @@ export default function register(api) {
|
|
|
1640
1714
|
},
|
|
1641
1715
|
}, { optional: true });
|
|
1642
1716
|
// --- orgx_create_entity ---
|
|
1643
|
-
|
|
1717
|
+
registerMcpTool({
|
|
1644
1718
|
name: "orgx_create_entity",
|
|
1645
1719
|
description: "Create an OrgX entity (initiative, workstream, task, decision, milestone, etc.).",
|
|
1646
1720
|
parameters: {
|
|
@@ -1725,7 +1799,7 @@ export default function register(api) {
|
|
|
1725
1799
|
},
|
|
1726
1800
|
}, { optional: true });
|
|
1727
1801
|
// --- orgx_update_entity ---
|
|
1728
|
-
|
|
1802
|
+
registerMcpTool({
|
|
1729
1803
|
name: "orgx_update_entity",
|
|
1730
1804
|
description: "Update an existing OrgX entity by type and ID.",
|
|
1731
1805
|
parameters: {
|
|
@@ -1766,7 +1840,7 @@ export default function register(api) {
|
|
|
1766
1840
|
},
|
|
1767
1841
|
}, { optional: true });
|
|
1768
1842
|
// --- orgx_list_entities ---
|
|
1769
|
-
|
|
1843
|
+
registerMcpTool({
|
|
1770
1844
|
name: "orgx_list_entities",
|
|
1771
1845
|
description: "List OrgX entities of a given type with optional status filter.",
|
|
1772
1846
|
parameters: {
|
|
@@ -1908,7 +1982,7 @@ export default function register(api) {
|
|
|
1908
1982
|
}
|
|
1909
1983
|
}
|
|
1910
1984
|
// --- orgx_emit_activity ---
|
|
1911
|
-
|
|
1985
|
+
registerMcpTool({
|
|
1912
1986
|
name: "orgx_emit_activity",
|
|
1913
1987
|
description: "Emit append-only OrgX activity telemetry (launch reporting contract primary write tool).",
|
|
1914
1988
|
parameters: {
|
|
@@ -1968,7 +2042,7 @@ export default function register(api) {
|
|
|
1968
2042
|
},
|
|
1969
2043
|
}, { optional: true });
|
|
1970
2044
|
// --- orgx_apply_changeset ---
|
|
1971
|
-
|
|
2045
|
+
registerMcpTool({
|
|
1972
2046
|
name: "orgx_apply_changeset",
|
|
1973
2047
|
description: "Apply an idempotent transactional OrgX changeset (launch reporting contract primary mutation tool).",
|
|
1974
2048
|
parameters: {
|
|
@@ -2011,7 +2085,7 @@ export default function register(api) {
|
|
|
2011
2085
|
},
|
|
2012
2086
|
}, { optional: true });
|
|
2013
2087
|
// --- orgx_report_progress (alias -> orgx_emit_activity) ---
|
|
2014
|
-
|
|
2088
|
+
registerMcpTool({
|
|
2015
2089
|
name: "orgx_report_progress",
|
|
2016
2090
|
description: "Alias for orgx_emit_activity. Report progress at key milestones so the team can track your work.",
|
|
2017
2091
|
parameters: {
|
|
@@ -2074,7 +2148,7 @@ export default function register(api) {
|
|
|
2074
2148
|
},
|
|
2075
2149
|
}, { optional: true });
|
|
2076
2150
|
// --- orgx_request_decision (alias -> orgx_apply_changeset decision.create) ---
|
|
2077
|
-
|
|
2151
|
+
registerMcpTool({
|
|
2078
2152
|
name: "orgx_request_decision",
|
|
2079
2153
|
description: "Alias for orgx_apply_changeset with decision.create. Request a human decision before proceeding.",
|
|
2080
2154
|
parameters: {
|
|
@@ -2159,7 +2233,7 @@ export default function register(api) {
|
|
|
2159
2233
|
},
|
|
2160
2234
|
}, { optional: true });
|
|
2161
2235
|
// --- orgx_register_artifact ---
|
|
2162
|
-
|
|
2236
|
+
registerMcpTool({
|
|
2163
2237
|
name: "orgx_register_artifact",
|
|
2164
2238
|
description: "Register a work output (PR, document, config change, report, etc.) with OrgX. Makes it visible in the dashboard.",
|
|
2165
2239
|
parameters: {
|
|
@@ -2332,7 +2406,18 @@ export default function register(api) {
|
|
|
2332
2406
|
}, {
|
|
2333
2407
|
getHealth: async (input = {}) => buildHealthReport({ probeRemote: input.probeRemote === true }),
|
|
2334
2408
|
});
|
|
2335
|
-
|
|
2409
|
+
const mcpHttpHandler = createMcpHttpHandler({
|
|
2410
|
+
tools: mcpToolRegistry,
|
|
2411
|
+
logger: api.log ?? {},
|
|
2412
|
+
serverName: "@useorgx/openclaw-plugin",
|
|
2413
|
+
serverVersion: config.pluginVersion,
|
|
2414
|
+
});
|
|
2415
|
+
const compositeHttpHandler = async (req, res) => {
|
|
2416
|
+
if (await mcpHttpHandler(req, res))
|
|
2417
|
+
return true;
|
|
2418
|
+
return await httpHandler(req, res);
|
|
2419
|
+
};
|
|
2420
|
+
api.registerHttpHandler(compositeHttpHandler);
|
|
2336
2421
|
api.log?.info?.("[orgx] Plugin registered", {
|
|
2337
2422
|
baseUrl: config.baseUrl,
|
|
2338
2423
|
hasApiKey: !!config.apiKey,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Logger } from "./mcp-http-handler.js";
|
|
2
|
+
export declare function patchClaudeMcpConfig(input: {
|
|
3
|
+
current: Record<string, unknown>;
|
|
4
|
+
localMcpUrl: string;
|
|
5
|
+
}): {
|
|
6
|
+
updated: boolean;
|
|
7
|
+
next: Record<string, unknown>;
|
|
8
|
+
};
|
|
9
|
+
export declare function patchCursorMcpConfig(input: {
|
|
10
|
+
current: Record<string, unknown>;
|
|
11
|
+
localMcpUrl: string;
|
|
12
|
+
}): {
|
|
13
|
+
updated: boolean;
|
|
14
|
+
next: Record<string, unknown>;
|
|
15
|
+
};
|
|
16
|
+
export declare function patchCodexConfigToml(input: {
|
|
17
|
+
current: string;
|
|
18
|
+
localMcpUrl: string;
|
|
19
|
+
}): {
|
|
20
|
+
updated: boolean;
|
|
21
|
+
next: string;
|
|
22
|
+
};
|
|
23
|
+
export declare function autoConfigureDetectedMcpClients(input: {
|
|
24
|
+
localMcpUrl: string;
|
|
25
|
+
logger?: Logger;
|
|
26
|
+
homeDir?: string;
|
|
27
|
+
}): Promise<{
|
|
28
|
+
updatedPaths: string[];
|
|
29
|
+
skippedPaths: string[];
|
|
30
|
+
}>;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { writeFileAtomicSync, writeJsonFileAtomicSync } from "./fs-utils.js";
|
|
6
|
+
function isRecord(value) {
|
|
7
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
8
|
+
}
|
|
9
|
+
function parseJsonObjectSafe(raw) {
|
|
10
|
+
try {
|
|
11
|
+
const parsed = JSON.parse(raw);
|
|
12
|
+
return isRecord(parsed) ? parsed : null;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function fileModeOrDefault(path, fallback) {
|
|
19
|
+
try {
|
|
20
|
+
const stat = statSync(path);
|
|
21
|
+
return stat.mode & 0o777;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return fallback;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function backupPath(path) {
|
|
28
|
+
return `${path}.bak.${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
29
|
+
}
|
|
30
|
+
function backupFileSync(path, mode) {
|
|
31
|
+
try {
|
|
32
|
+
const content = readFileSync(path);
|
|
33
|
+
const next = backupPath(path);
|
|
34
|
+
writeFileAtomicSync(next, content.toString("utf8"), { mode, encoding: "utf8" });
|
|
35
|
+
return next;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function patchClaudeMcpConfig(input) {
|
|
42
|
+
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,
|
|
48
|
+
type: "http",
|
|
49
|
+
url: input.localMcpUrl,
|
|
50
|
+
description: typeof currentOrgx.description === "string" && currentOrgx.description.trim().length > 0
|
|
51
|
+
? currentOrgx.description
|
|
52
|
+
: "OrgX platform via local OpenClaw plugin (no OAuth)",
|
|
53
|
+
};
|
|
54
|
+
const nextServers = {
|
|
55
|
+
...currentServers,
|
|
56
|
+
orgx: nextOrgx,
|
|
57
|
+
};
|
|
58
|
+
const next = {
|
|
59
|
+
...input.current,
|
|
60
|
+
mcpServers: nextServers,
|
|
61
|
+
};
|
|
62
|
+
const updated = priorUrl !== input.localMcpUrl || priorType !== "http";
|
|
63
|
+
return { updated, next };
|
|
64
|
+
}
|
|
65
|
+
export function patchCursorMcpConfig(input) {
|
|
66
|
+
const currentServers = isRecord(input.current.mcpServers) ? input.current.mcpServers : {};
|
|
67
|
+
const key = "orgx-openclaw";
|
|
68
|
+
const existing = isRecord(currentServers[key]) ? currentServers[key] : {};
|
|
69
|
+
const priorUrl = typeof existing.url === "string" ? existing.url : "";
|
|
70
|
+
const nextEntry = {
|
|
71
|
+
...existing,
|
|
72
|
+
url: input.localMcpUrl,
|
|
73
|
+
};
|
|
74
|
+
const nextServers = {
|
|
75
|
+
...currentServers,
|
|
76
|
+
[key]: nextEntry,
|
|
77
|
+
};
|
|
78
|
+
const next = {
|
|
79
|
+
...input.current,
|
|
80
|
+
mcpServers: nextServers,
|
|
81
|
+
};
|
|
82
|
+
const updated = priorUrl !== input.localMcpUrl;
|
|
83
|
+
return { updated, next };
|
|
84
|
+
}
|
|
85
|
+
export function patchCodexConfigToml(input) {
|
|
86
|
+
const lines = input.current.split(/\r?\n/);
|
|
87
|
+
const headerRegex = /^\[mcp_servers\.(?:orgx|"orgx")\]\s*$/;
|
|
88
|
+
let headerIndex = -1;
|
|
89
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
90
|
+
if (headerRegex.test(lines[i].trim())) {
|
|
91
|
+
headerIndex = i;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const urlLine = `url = "${input.localMcpUrl}"`;
|
|
96
|
+
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`;
|
|
104
|
+
return { updated: true, next: `${normalized}${suffix}` };
|
|
105
|
+
}
|
|
106
|
+
let sectionEnd = lines.length;
|
|
107
|
+
for (let i = headerIndex + 1; i < lines.length; i += 1) {
|
|
108
|
+
if (lines[i].trim().startsWith("[")) {
|
|
109
|
+
sectionEnd = i;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
let updated = false;
|
|
114
|
+
let urlIndex = -1;
|
|
115
|
+
for (let i = headerIndex + 1; i < sectionEnd; i += 1) {
|
|
116
|
+
if (/^\s*url\s*=/.test(lines[i])) {
|
|
117
|
+
urlIndex = i;
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (urlIndex >= 0) {
|
|
122
|
+
if (lines[urlIndex].trim() !== urlLine) {
|
|
123
|
+
lines[urlIndex] = urlLine;
|
|
124
|
+
updated = true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
lines.splice(headerIndex + 1, 0, urlLine);
|
|
129
|
+
updated = true;
|
|
130
|
+
}
|
|
131
|
+
return { updated, next: `${lines.join("\n")}\n` };
|
|
132
|
+
}
|
|
133
|
+
export async function autoConfigureDetectedMcpClients(input) {
|
|
134
|
+
const logger = input.logger ?? {};
|
|
135
|
+
const home = input.homeDir ?? homedir();
|
|
136
|
+
const updatedPaths = [];
|
|
137
|
+
const skippedPaths = [];
|
|
138
|
+
const targets = [
|
|
139
|
+
{ kind: "claude", path: join(home, ".claude", "mcp.json") },
|
|
140
|
+
{ kind: "cursor", path: join(home, ".cursor", "mcp.json") },
|
|
141
|
+
{ kind: "codex", path: join(home, ".codex", "config.toml") },
|
|
142
|
+
];
|
|
143
|
+
for (const target of targets) {
|
|
144
|
+
if (!existsSync(target.path)) {
|
|
145
|
+
skippedPaths.push(target.path);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const mode = fileModeOrDefault(target.path, 0o600);
|
|
149
|
+
try {
|
|
150
|
+
if (target.kind === "codex") {
|
|
151
|
+
const current = readFileSync(target.path, "utf8");
|
|
152
|
+
const patched = patchCodexConfigToml({ current, localMcpUrl: input.localMcpUrl });
|
|
153
|
+
if (!patched.updated) {
|
|
154
|
+
skippedPaths.push(target.path);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const backup = backupFileSync(target.path, mode);
|
|
158
|
+
if (!backup) {
|
|
159
|
+
logger.warn?.("[orgx] MCP client autoconfig: backup failed; skipping", {
|
|
160
|
+
path: target.path,
|
|
161
|
+
kind: target.kind,
|
|
162
|
+
});
|
|
163
|
+
skippedPaths.push(target.path);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
writeFileAtomicSync(target.path, patched.next, { mode, encoding: "utf8" });
|
|
167
|
+
updatedPaths.push(target.path);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const currentText = readFileSync(target.path, "utf8");
|
|
171
|
+
const current = parseJsonObjectSafe(currentText);
|
|
172
|
+
if (!current) {
|
|
173
|
+
logger.warn?.("[orgx] MCP client autoconfig: invalid JSON; skipping", {
|
|
174
|
+
path: target.path,
|
|
175
|
+
kind: target.kind,
|
|
176
|
+
});
|
|
177
|
+
skippedPaths.push(target.path);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const patched = target.kind === "claude"
|
|
181
|
+
? patchClaudeMcpConfig({ current, localMcpUrl: input.localMcpUrl })
|
|
182
|
+
: patchCursorMcpConfig({ current, localMcpUrl: input.localMcpUrl });
|
|
183
|
+
if (!patched.updated) {
|
|
184
|
+
skippedPaths.push(target.path);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const backup = backupFileSync(target.path, mode);
|
|
188
|
+
if (!backup) {
|
|
189
|
+
logger.warn?.("[orgx] MCP client autoconfig: backup failed; skipping", {
|
|
190
|
+
path: target.path,
|
|
191
|
+
kind: target.kind,
|
|
192
|
+
});
|
|
193
|
+
skippedPaths.push(target.path);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
writeJsonFileAtomicSync(target.path, patched.next, mode);
|
|
197
|
+
updatedPaths.push(target.path);
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
logger.warn?.("[orgx] MCP client autoconfig failed; leaving backup in place", {
|
|
201
|
+
path: target.path,
|
|
202
|
+
kind: target.kind,
|
|
203
|
+
error: err instanceof Error ? err.message : String(err),
|
|
204
|
+
});
|
|
205
|
+
skippedPaths.push(target.path);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (updatedPaths.length > 0) {
|
|
209
|
+
logger.info?.("[orgx] MCP client autoconfig applied", {
|
|
210
|
+
localMcpUrl: input.localMcpUrl,
|
|
211
|
+
updated: updatedPaths,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return { updatedPaths, skippedPaths };
|
|
215
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type Logger = {
|
|
2
|
+
info?: (message: string, meta?: Record<string, unknown>) => void;
|
|
3
|
+
warn?: (message: string, meta?: Record<string, unknown>) => void;
|
|
4
|
+
debug?: (message: string, meta?: Record<string, unknown>) => void;
|
|
5
|
+
};
|
|
6
|
+
export interface PluginRequest {
|
|
7
|
+
method?: string;
|
|
8
|
+
url?: string;
|
|
9
|
+
headers: Record<string, string | string[] | undefined>;
|
|
10
|
+
body?: unknown;
|
|
11
|
+
on?: (event: string, listener: (...args: unknown[]) => void) => void;
|
|
12
|
+
once?: (event: string, listener: (...args: unknown[]) => void) => void;
|
|
13
|
+
}
|
|
14
|
+
export interface PluginResponse {
|
|
15
|
+
writeHead(status: number, headers?: Record<string, string>): void;
|
|
16
|
+
end(body?: string | Buffer): void;
|
|
17
|
+
write?(chunk: string | Buffer): boolean | void;
|
|
18
|
+
writableEnded?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export type ToolResult = {
|
|
21
|
+
content: Array<{
|
|
22
|
+
type: "text";
|
|
23
|
+
text: string;
|
|
24
|
+
}>;
|
|
25
|
+
isError?: boolean;
|
|
26
|
+
};
|
|
27
|
+
export type RegisteredTool = {
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
parameters: Record<string, unknown>;
|
|
31
|
+
execute: (callId: string, params?: unknown) => Promise<ToolResult>;
|
|
32
|
+
};
|
|
33
|
+
export declare function createMcpHttpHandler(input: {
|
|
34
|
+
tools: Map<string, RegisteredTool>;
|
|
35
|
+
logger?: Logger;
|
|
36
|
+
serverName: string;
|
|
37
|
+
serverVersion: string;
|
|
38
|
+
}): (req: PluginRequest, res: PluginResponse) => Promise<boolean>;
|