borgmcp 3.0.2 → 3.2.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 +5 -1
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +5 -1
- package/dist/cli-help.js.map +1 -1
- package/dist/config-utils.d.ts +13 -0
- package/dist/config-utils.d.ts.map +1 -1
- package/dist/config-utils.js +131 -9
- package/dist/config-utils.js.map +1 -1
- package/dist/launch-all-cmd.js +1 -1
- package/dist/launch-all-cmd.js.map +1 -1
- package/dist/remote-client.d.ts.map +1 -1
- package/dist/remote-client.js +27 -10
- package/dist/remote-client.js.map +1 -1
- package/dist/server-handshake.d.ts +1 -1
- package/dist/tool-manifest.js +3 -3
- package/dist/tool-manifest.js.map +1 -1
- package/dist/update-cmd.d.ts +1 -0
- package/dist/update-cmd.d.ts.map +1 -1
- package/dist/update-cmd.js +13 -0
- package/dist/update-cmd.js.map +1 -1
- package/docs/EXTRACTION_PROVENANCE.md +16 -30
- package/docs/LOCAL_SERVER.md +6 -2
- package/docs/RELEASING.md +3 -3
- package/package.json +2 -2
- package/src/cli-help.ts +5 -1
- package/src/config-utils.ts +148 -9
- package/src/launch-all-cmd.ts +1 -1
- package/src/remote-client.ts +46 -10
- package/src/server-handshake.ts +1 -1
- package/src/tool-manifest.ts +3 -3
- package/src/update-cmd.ts +17 -1
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Extraction Provenance
|
|
2
2
|
|
|
3
|
+
**This file is a frozen historical record.** It documents the one-time
|
|
4
|
+
extraction of this repository from the monorepo and the state of the client
|
|
5
|
+
line as of that extraction and its immediate releases. Nothing appends to it,
|
|
6
|
+
no release process touches it, and no statement in it describes the current
|
|
7
|
+
version — the authoritative version is `package.json` and the npm registry.
|
|
8
|
+
Facts below were true when recorded and are not maintained.
|
|
9
|
+
|
|
3
10
|
The initial standalone source was extracted from `Byte-Ventures/borg-mcp` commit `17ff8ce14e12122a8cc9089f6b94174c02fa2a04` on branch `main`.
|
|
4
11
|
|
|
5
12
|
The extraction copied the monorepo's `client/src/` production boundary and top-level `client/__tests__/*.test.ts` unit tests. It did not import monorepo Git history, worker or website source, deployment configuration, local state, credentials files, build output, or the live integration suite.
|
|
@@ -17,42 +24,20 @@ The extraction copied the monorepo's `client/src/` production boundary and top-l
|
|
|
17
24
|
- Kept self-hosted `--host --enroll` preview-only while implementing the
|
|
18
25
|
client-generated PENDING credential/retry tuple and capability-gated,
|
|
19
26
|
repository-idempotent cube creation required for local dogfood.
|
|
20
|
-
|
|
21
|
-
release identity is `3.0.2`.** Extraction and versioning do not authorize
|
|
22
|
-
publication.
|
|
23
|
-
|
|
24
|
-
Spent versions — each burned an immutable tag and published nothing:
|
|
25
|
-
|
|
26
|
-
| Version | Failed at | Recovered by |
|
|
27
|
-
| --- | --- | --- |
|
|
28
|
-
| `2.0.0` | lightweight release tag, before packaging | `2.0.1` |
|
|
29
|
-
| `2.0.7` | before package creation or npm publication | — |
|
|
30
|
-
| `2.10.1` | before artifact creation or publication | — |
|
|
31
|
-
| `2.12.0` | before artifact creation or publication | `2.12.1` |
|
|
32
|
-
| `3.0.1` | publish job never scheduled; run cancelled after verify and approval | `3.0.2` |
|
|
33
|
-
|
|
34
|
-
Every other version in the `2.x` and `3.x` lines published and was
|
|
35
|
-
registry-verified.
|
|
36
|
-
Record spent versions here when they occur; do not append a clause to this
|
|
37
|
-
entry. To confirm the table is still complete, list the repository's version
|
|
38
|
-
tags and `npm view borgmcp versions` — a spent version is a tag whose version
|
|
39
|
-
never reached the registry, and the two sets should differ by exactly these
|
|
40
|
-
rows.
|
|
41
|
-
|
|
42
|
-
## Review Holds
|
|
27
|
+
## Review Holds Resolved at Extraction (historical)
|
|
43
28
|
|
|
44
29
|
Google OAuth / Cloud sign-in has been fully removed from this local-only client.
|
|
45
30
|
`src/auth.ts`, `src/device-auth.ts`, and all installed-application OAuth client
|
|
46
31
|
material are deleted, along with the hosted API default and the Cloud
|
|
47
|
-
subscription/billing/dashboard tools. `scripts/verify-public-source.mjs`
|
|
32
|
+
subscription/billing/dashboard tools. `scripts/verify-public-source.mjs`
|
|
48
33
|
forbids ANY Google OAuth client ID or `GOCSPX` value anywhere in the source or
|
|
49
34
|
packed artifact (zero tolerance), and a no-cloud egress guard asserts the packed
|
|
50
35
|
artifact reaches no hosted authority.
|
|
51
36
|
|
|
52
|
-
|
|
37
|
+
At extraction, local enrollment used the reviewed client-generated credential/retry
|
|
53
38
|
contract, with a pre-request `PENDING` record in the local 0600-permission seat
|
|
54
|
-
store, exact-tuple ambiguous retry, and verified activation. The
|
|
55
|
-
|
|
39
|
+
store, exact-tuple ambiguous retry, and verified activation. The client candidate
|
|
40
|
+
at that time resolved to audited registry `borgmcp-shared` version `0.9.0` with integrity
|
|
56
41
|
`sha512-bfZPP9JGgBQrCFoZetabqKHc8HLaUqHVR3GJLb/1F1oon7z/B4el4aeBHVvXlxN9+2G7kU/ymPZ/K25nVQapmQ==`.
|
|
57
42
|
The coupled `borgmcp-server@0.9.0` release was published on 2026-08-02 for the
|
|
58
43
|
client `borgmcp@2.10.0` line. Its annotated tag object
|
|
@@ -62,6 +47,7 @@ client `borgmcp@2.10.0` line. Its annotated tag object
|
|
|
62
47
|
in the `borgmcp-server-0.9.0-release` registry decision. That server and its
|
|
63
48
|
client line pin historical `borgmcp-shared` version `0.8.1` and remain
|
|
64
49
|
immutable. The immutable `v2.0.7` attempt failed before publication and remains
|
|
65
|
-
preserved.
|
|
66
|
-
|
|
67
|
-
protected-publication gates.
|
|
50
|
+
preserved. Each release's identity and publication gate are governed by its reviewed
|
|
51
|
+
source at the annotated release tag and the exact-artifact and
|
|
52
|
+
protected-publication gates; the authoritative version is `package.json` and
|
|
53
|
+
the registry, not this document.
|
package/docs/LOCAL_SERVER.md
CHANGED
|
@@ -204,7 +204,11 @@ stopped server remains stopped; matching controller and prepared runtime are
|
|
|
204
204
|
reported as `prepared; still stopped` without inventing a live protocol check.
|
|
205
205
|
An absent server is skipped rather than installed. Partial completion prints the
|
|
206
206
|
safe retry command `borg update --yes`. Borg never starts a stopped server,
|
|
207
|
-
daemonizes, or restarts agent processes.
|
|
207
|
+
daemonizes, or restarts agent processes. After the package pair verifies, the
|
|
208
|
+
command replaces stale absolute `borgmcp` package launch paths in Claude Code,
|
|
209
|
+
Codex, and OpenCode MCP registrations with the version-stable `borg-mcp`
|
|
210
|
+
command. It preserves each registration's other settings. It does not create
|
|
211
|
+
absent registrations or modify a `borg` entry that points to another command.
|
|
208
212
|
|
|
209
213
|
`borg server update` remains the server-runtime-only command. It verifies and
|
|
210
214
|
activates the server artifact but deliberately does not rewrite the global
|
|
@@ -258,6 +262,6 @@ The default discovery endpoint is `https://127.0.0.1:7091`. Explicit `--host` va
|
|
|
258
262
|
|
|
259
263
|
## Release status
|
|
260
264
|
|
|
261
|
-
This client candidate pins published `borgmcp-shared@0.
|
|
265
|
+
This client candidate pins published `borgmcp-shared@0.10.1`. Its release remains
|
|
262
266
|
coupled to a server artifact rebuilt against the same shared version, with both
|
|
263
267
|
artifacts required to pass the complete process-level local dogfood gate.
|
package/docs/RELEASING.md
CHANGED
|
@@ -19,11 +19,11 @@ Before creating the release tag, independently verify all of these conditions:
|
|
|
19
19
|
- the extraction review confirms no private backend secrets, deployment
|
|
20
20
|
configuration, customer data, local state, or duplicated shared contracts
|
|
21
21
|
entered the public package;
|
|
22
|
-
- the exact audited registry dependency `borgmcp-shared@0.
|
|
22
|
+
- the exact audited registry dependency `borgmcp-shared@0.10.1` remains locked to
|
|
23
23
|
its canonical tarball and integrity
|
|
24
|
-
`sha512-
|
|
24
|
+
`sha512-IAESc6+VpUGECNabFkO60agvga0B8Fx0hk2RMUkSDCnlIjMvDodMFKlqw+TOzdXtjf0Uk9LJi9ZCU4HfRYRRhw==`;
|
|
25
25
|
- the coupled client/server release is published only after the server artifact
|
|
26
|
-
is rebuilt against `borgmcp-shared@0.
|
|
26
|
+
is rebuilt against `borgmcp-shared@0.10.1` and both pass the complete local
|
|
27
27
|
dogfood gate;
|
|
28
28
|
- the selected stable client version is unused and the exact release commit is
|
|
29
29
|
on protected `main`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "borgmcp",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Coordinate AI coding agents in shared cubes. Works with Claude Code, Codex, and OpenCode.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
74
|
-
"borgmcp-shared": "0.
|
|
74
|
+
"borgmcp-shared": "0.10.1",
|
|
75
75
|
"chalk": "^5.3.0",
|
|
76
76
|
"prompts": "^2.4.2",
|
|
77
77
|
"which": "^4.0.0"
|
package/src/cli-help.ts
CHANGED
|
@@ -129,7 +129,11 @@ export function updateHelpText(version: string): string {
|
|
|
129
129
|
`provenance fail closed with manual-update guidance.\n\n` +
|
|
130
130
|
`If no local server is installed, the server phase is skipped. A failure after the client\n` +
|
|
131
131
|
`succeeds is reported as partial completion with the safe retry command. Borg never starts a\n` +
|
|
132
|
-
`stopped server
|
|
132
|
+
`stopped server. After package verification, Borg replaces stale borgmcp package launch paths\n` +
|
|
133
|
+
`in Claude Code, Codex, and OpenCode registrations while preserving their other settings. It\n` +
|
|
134
|
+
`leaves absent registrations and borg entries that point to another command unchanged. Borg\n` +
|
|
135
|
+
`never restarts agent processes;\n` +
|
|
136
|
+
`restart active agent sessions yourself.\n`
|
|
133
137
|
);
|
|
134
138
|
}
|
|
135
139
|
|
package/src/config-utils.ts
CHANGED
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
withAgentRuntimeEnv,
|
|
16
16
|
} from './agent-runtime.js';
|
|
17
17
|
import {
|
|
18
|
-
resolveMcpBinaryPath,
|
|
19
18
|
resolveRegenPath,
|
|
20
19
|
resolveClearRewakePath,
|
|
21
20
|
resolveLogAuditPath,
|
|
@@ -36,7 +35,7 @@ const HOOK_COMMAND = shellEscape(resolveRegenPath());
|
|
|
36
35
|
const CLEAR_REWAKE_HOOK_COMMAND = shellEscape(resolveClearRewakePath());
|
|
37
36
|
const AUDIT_HOOK_COMMAND = shellEscape(resolveLogAuditPath());
|
|
38
37
|
const FOREIGN_PATH_REMINDER_HOOK_COMMAND = shellEscape(resolveForeignPathReminderPath());
|
|
39
|
-
const
|
|
38
|
+
const MCP_COMMAND = 'borg-mcp';
|
|
40
39
|
|
|
41
40
|
/**
|
|
42
41
|
* Claude Code CLI config path. The CLI reads `mcpServers.<name>` from
|
|
@@ -678,6 +677,150 @@ export function isCodexMcpServerConfigured(
|
|
|
678
677
|
}
|
|
679
678
|
}
|
|
680
679
|
|
|
680
|
+
function configuredMcpCommand(value: unknown): string | null {
|
|
681
|
+
if (typeof value === 'string') return value;
|
|
682
|
+
if (Array.isArray(value) && value.length === 1 && typeof value[0] === 'string') {
|
|
683
|
+
return value[0];
|
|
684
|
+
}
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function isStaleBorgMcpCommand(value: unknown): boolean {
|
|
689
|
+
const command = configuredMcpCommand(value);
|
|
690
|
+
if (!command || command === MCP_COMMAND || !path.isAbsolute(command)) return false;
|
|
691
|
+
const normalized = command.replace(/\\/g, '/');
|
|
692
|
+
return /\/(?:node_modules\/borgmcp|borg-mcp-client)\/dist\/index\.js$/.test(normalized);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function readClaudeMcpCommand(configPath: string): unknown {
|
|
696
|
+
try {
|
|
697
|
+
return readJsonFile(configPath)?.mcpServers?.[MCP_SERVER_NAME]?.command;
|
|
698
|
+
} catch {
|
|
699
|
+
return undefined;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function readCodexMcpCommand(configPath: string): unknown {
|
|
704
|
+
try {
|
|
705
|
+
const text = fs.readFileSync(configPath, 'utf-8');
|
|
706
|
+
if (!isCodexMcpServerConfigured(configPath)) return undefined;
|
|
707
|
+
const header = /^\s*\[mcp_servers\.borg\]\s*$/m.exec(text);
|
|
708
|
+
if (!header) return undefined;
|
|
709
|
+
const tail = text.slice(header.index + header[0].length);
|
|
710
|
+
const nextHeader = /^\s*\[/m.exec(tail);
|
|
711
|
+
const section = nextHeader ? tail.slice(0, nextHeader.index) : tail;
|
|
712
|
+
return section?.match(/^\s*command\s*=\s*"([^"]+)"\s*$/m)?.[1];
|
|
713
|
+
} catch {
|
|
714
|
+
return undefined;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function readOpenCodeMcpCommand(configPath: string): unknown {
|
|
719
|
+
try {
|
|
720
|
+
const borgServer = readJsonFile(configPath)?.mcp?.[MCP_SERVER_NAME];
|
|
721
|
+
if (!borgServer || borgServer.type !== 'local') return undefined;
|
|
722
|
+
const environment = borgServer.environment ?? borgServer.env;
|
|
723
|
+
if (environment?.BORG_AGENT_KIND !== 'opencode' && environment?.BORG_OPENCODE !== '1') {
|
|
724
|
+
return undefined;
|
|
725
|
+
}
|
|
726
|
+
return borgServer.command;
|
|
727
|
+
} catch {
|
|
728
|
+
return undefined;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
export interface RefreshManagedAgentMcpConfigOptions {
|
|
733
|
+
claudeConfigPath?: string;
|
|
734
|
+
codexConfigPath?: string;
|
|
735
|
+
openCodeConfigPath?: string;
|
|
736
|
+
refreshClaude?: () => void;
|
|
737
|
+
refreshCodex?: () => void;
|
|
738
|
+
refreshOpenCode?: () => void;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function refreshClaudeMcpCommand(configPath: string): void {
|
|
742
|
+
const config = readJsonFile(configPath);
|
|
743
|
+
config.mcpServers[MCP_SERVER_NAME].command = MCP_COMMAND;
|
|
744
|
+
writeJsonFile(configPath, config);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function refreshCodexMcpCommand(configPath: string): void {
|
|
748
|
+
const text = fs.readFileSync(configPath, 'utf-8');
|
|
749
|
+
const header = /^\s*\[mcp_servers\.borg\]\s*$/m.exec(text);
|
|
750
|
+
if (!header) return;
|
|
751
|
+
const sectionStart = header.index + header[0].length;
|
|
752
|
+
const tail = text.slice(sectionStart);
|
|
753
|
+
const nextHeader = /^\s*\[/m.exec(tail);
|
|
754
|
+
const sectionEnd = nextHeader ? sectionStart + nextHeader.index : text.length;
|
|
755
|
+
const section = text.slice(sectionStart, sectionEnd);
|
|
756
|
+
const updatedSection = section.replace(
|
|
757
|
+
/^(\s*command\s*=\s*)"[^"]+"(\s*)$/m,
|
|
758
|
+
`$1"${MCP_COMMAND}"$2`,
|
|
759
|
+
);
|
|
760
|
+
fs.writeFileSync(
|
|
761
|
+
configPath,
|
|
762
|
+
text.slice(0, sectionStart) + updatedSection + text.slice(sectionEnd),
|
|
763
|
+
'utf-8',
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function refreshOpenCodeMcpCommand(configPath: string): void {
|
|
768
|
+
const config = readJsonFile(configPath);
|
|
769
|
+
const borgServer = config.mcp[MCP_SERVER_NAME];
|
|
770
|
+
borgServer.command = Array.isArray(borgServer.command) ? [MCP_COMMAND] : MCP_COMMAND;
|
|
771
|
+
writeJsonFile(configPath, config);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Replace only the command in stale Borg registrations. Other entry fields are
|
|
776
|
+
* preserved, and a `borg` entry that points at another command is not changed.
|
|
777
|
+
*/
|
|
778
|
+
export function refreshManagedAgentMcpConfigs(
|
|
779
|
+
options: RefreshManagedAgentMcpConfigOptions = {},
|
|
780
|
+
): Array<'claude' | 'codex' | 'opencode'> {
|
|
781
|
+
const refreshed: Array<'claude' | 'codex' | 'opencode'> = [];
|
|
782
|
+
const failures: string[] = [];
|
|
783
|
+
const claudeConfigPath = options.claudeConfigPath ?? CLAUDE_CONFIG_PATH;
|
|
784
|
+
const codexConfigPath = options.codexConfigPath ?? CODEX_CONFIG_PATH;
|
|
785
|
+
const openCodeConfigPath = options.openCodeConfigPath ?? OPENCODE_CONFIG_PATH;
|
|
786
|
+
const agents = [
|
|
787
|
+
{
|
|
788
|
+
kind: 'claude' as const,
|
|
789
|
+
label: 'Claude Code',
|
|
790
|
+
command: readClaudeMcpCommand(claudeConfigPath),
|
|
791
|
+
refresh: options.refreshClaude ?? (() => refreshClaudeMcpCommand(claudeConfigPath)),
|
|
792
|
+
},
|
|
793
|
+
{
|
|
794
|
+
kind: 'codex' as const,
|
|
795
|
+
label: 'Codex',
|
|
796
|
+
command: readCodexMcpCommand(codexConfigPath),
|
|
797
|
+
refresh: options.refreshCodex ?? (() => refreshCodexMcpCommand(codexConfigPath)),
|
|
798
|
+
},
|
|
799
|
+
{
|
|
800
|
+
kind: 'opencode' as const,
|
|
801
|
+
label: 'OpenCode',
|
|
802
|
+
command: readOpenCodeMcpCommand(openCodeConfigPath),
|
|
803
|
+
refresh: options.refreshOpenCode ?? (() => refreshOpenCodeMcpCommand(openCodeConfigPath)),
|
|
804
|
+
},
|
|
805
|
+
];
|
|
806
|
+
|
|
807
|
+
for (const agent of agents) {
|
|
808
|
+
if (!isStaleBorgMcpCommand(agent.command)) continue;
|
|
809
|
+
try {
|
|
810
|
+
agent.refresh();
|
|
811
|
+
refreshed.push(agent.kind);
|
|
812
|
+
} catch (error) {
|
|
813
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
814
|
+
failures.push(`${agent.label}: ${message}`);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
if (failures.length > 0) {
|
|
819
|
+
throw new Error(`Could not refresh agent MCP configs: ${failures.join('; ')}`);
|
|
820
|
+
}
|
|
821
|
+
return refreshed;
|
|
822
|
+
}
|
|
823
|
+
|
|
681
824
|
/**
|
|
682
825
|
* Get absolute path to borg index.js
|
|
683
826
|
* Returns the actual index.js file, not the npm symlink
|
|
@@ -703,9 +846,7 @@ export function addMcpServer(): void {
|
|
|
703
846
|
// Ignore - server might not exist yet
|
|
704
847
|
}
|
|
705
848
|
|
|
706
|
-
|
|
707
|
-
// registered server always matches the running client version.
|
|
708
|
-
const command = `claude mcp add --scope user borg ${shellQuote(MCP_BINARY)}`;
|
|
849
|
+
const command = `claude mcp add --scope user borg ${shellQuote(MCP_COMMAND)}`;
|
|
709
850
|
|
|
710
851
|
execSync(command, {
|
|
711
852
|
stdio: 'inherit', // Show output to user
|
|
@@ -736,7 +877,6 @@ export function addCodexMcpServer(): void {
|
|
|
736
877
|
// Identity is durable configuration; remote wake is a per-launch
|
|
737
878
|
// transport capability. Do not persist a transport marker here: a future
|
|
738
879
|
// Codex child may launch without a live --remote socket.
|
|
739
|
-
// gh#client#18: use absolute path to THIS installation's binary.
|
|
740
880
|
const apiUrlEnvArg = apiUrl ? ` --env BORG_API_URL=${shellQuote(apiUrl)}` : '';
|
|
741
881
|
const stateRoot = process.env[BORG_STATE_ROOT_ENV];
|
|
742
882
|
const stateRootEnvArg = stateRoot
|
|
@@ -746,7 +886,7 @@ export function addCodexMcpServer(): void {
|
|
|
746
886
|
apiUrlEnvArg +
|
|
747
887
|
stateRootEnvArg +
|
|
748
888
|
` --env ${BORG_AGENT_KIND_ENV}=codex` +
|
|
749
|
-
` -- ${shellQuote(
|
|
889
|
+
` -- ${shellQuote(MCP_COMMAND)}`, {
|
|
750
890
|
stdio: 'inherit',
|
|
751
891
|
env: codexConfigEnv,
|
|
752
892
|
});
|
|
@@ -965,9 +1105,8 @@ export function addOpenCodeMcpServer(): void {
|
|
|
965
1105
|
const stateRootEnvArg = stateRoot
|
|
966
1106
|
? ` --env ${BORG_STATE_ROOT_ENV}=${shellQuote(stateRoot)}`
|
|
967
1107
|
: '';
|
|
968
|
-
// gh#client#18: use absolute path to THIS installation's binary.
|
|
969
1108
|
execSync(
|
|
970
|
-
`opencode mcp add borg --env BORG_SESSION=1 --env BORG_AGENT_KIND=opencode --env BORG_OPENCODE=1${apiUrlEnvArg}${stateRootEnvArg} -- ${shellQuote(
|
|
1109
|
+
`opencode mcp add borg --env BORG_SESSION=1 --env BORG_AGENT_KIND=opencode --env BORG_OPENCODE=1${apiUrlEnvArg}${stateRootEnvArg} -- ${shellQuote(MCP_COMMAND)}`,
|
|
971
1110
|
{ stdio: 'inherit', env: borgAgentConfigEnv(process.env) }
|
|
972
1111
|
);
|
|
973
1112
|
} catch (error: any) {
|
package/src/launch-all-cmd.ts
CHANGED
|
@@ -66,7 +66,7 @@ async function resolveTargetCube(
|
|
|
66
66
|
return {
|
|
67
67
|
error:
|
|
68
68
|
`'${args.cubeName}' is ambiguous — ${matches.length} saved connections on this machine share that name:\n${list}\n` +
|
|
69
|
-
'cd into the intended project and re-run without
|
|
69
|
+
'cd into the intended project and re-run as `borg launch-all` without the positional cube name (resolves the active cube), ' +
|
|
70
70
|
'or clear the stale connection(s) by running `borg reset-local-connection` from the worktree that holds each.',
|
|
71
71
|
};
|
|
72
72
|
}
|
package/src/remote-client.ts
CHANGED
|
@@ -17,17 +17,23 @@ import { randomUUID } from 'node:crypto';
|
|
|
17
17
|
import {
|
|
18
18
|
createProtocolEnvelope,
|
|
19
19
|
decodeDeleteCubeResponse,
|
|
20
|
+
decodeDeleteRoleRequest,
|
|
21
|
+
decodeDeleteRoleResult,
|
|
20
22
|
decodeDroneRuntimeMetadataState,
|
|
21
23
|
decodeEvictDroneResult,
|
|
22
24
|
decodeProtocolEnvelope,
|
|
23
25
|
decodeProtocolErrorEnvelope,
|
|
24
26
|
decodeReassignDroneResult,
|
|
27
|
+
decodeRoleRationaleRequest,
|
|
28
|
+
decodeRoleRationaleResult,
|
|
25
29
|
decodeUpdateDroneRuntimeMetadataResponse,
|
|
26
30
|
ErrorCode,
|
|
27
31
|
ProtocolContractError,
|
|
28
32
|
type AgentKind,
|
|
33
|
+
type DeleteRoleResult,
|
|
29
34
|
type EvictDroneResult,
|
|
30
35
|
type ReassignDroneResult,
|
|
36
|
+
type RoleRationaleResult,
|
|
31
37
|
} from 'borgmcp-shared/protocol';
|
|
32
38
|
import { consolePrefix } from './console-prefix.js';
|
|
33
39
|
import { debugLog } from './debug.js';
|
|
@@ -301,7 +307,10 @@ async function localServerRequest<T>(
|
|
|
301
307
|
path: string,
|
|
302
308
|
method: 'GET' | 'POST' | 'PUT' | 'PATCH',
|
|
303
309
|
payload?: Record<string, unknown>,
|
|
304
|
-
options: {
|
|
310
|
+
options: {
|
|
311
|
+
retryMode?: AuthedFetchRetryMode;
|
|
312
|
+
decodePayload?: (value: unknown) => T;
|
|
313
|
+
} = {},
|
|
305
314
|
): Promise<T | null> {
|
|
306
315
|
return decodeLocalProtocolResponse<T>((signal) => authedFetch(path, {
|
|
307
316
|
method,
|
|
@@ -318,7 +327,7 @@ async function localServerRequest<T>(
|
|
|
318
327
|
body: JSON.stringify(createProtocolEnvelope(randomUUID(), payload)),
|
|
319
328
|
}),
|
|
320
329
|
retryMode: options.retryMode,
|
|
321
|
-
}), true);
|
|
330
|
+
}), true, options.decodePayload);
|
|
322
331
|
}
|
|
323
332
|
|
|
324
333
|
export interface LocalManageOperation {
|
|
@@ -1253,12 +1262,21 @@ export async function roleRationale(
|
|
|
1253
1262
|
section: string,
|
|
1254
1263
|
serverTrustIdentity?: string,
|
|
1255
1264
|
): Promise<{ role: string; section: string; body: string }> {
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1265
|
+
const local = await localAuthorityContext(sessionToken, apiUrl, serverTrustIdentity);
|
|
1266
|
+
const request = decodeRoleRationaleRequest({ role, section });
|
|
1267
|
+
const result = await localServerRequest<RoleRationaleResult>(
|
|
1268
|
+
local,
|
|
1269
|
+
`/api/cubes/${local.cubeId}/role-rationale`,
|
|
1270
|
+
'POST',
|
|
1271
|
+
{ ...request },
|
|
1272
|
+
{ decodePayload: decodeRoleRationaleResult },
|
|
1273
|
+
);
|
|
1274
|
+
if (!result) throw new Error('Local Borg server returned an empty role rationale response');
|
|
1275
|
+
return {
|
|
1276
|
+
role: result.role_name,
|
|
1277
|
+
section: result.section.heading,
|
|
1278
|
+
body: result.section.body,
|
|
1279
|
+
};
|
|
1262
1280
|
}
|
|
1263
1281
|
|
|
1264
1282
|
/**
|
|
@@ -1662,8 +1680,26 @@ export async function patchRoleSection(
|
|
|
1662
1680
|
* (reassign or evict those drones first).
|
|
1663
1681
|
*/
|
|
1664
1682
|
export async function deleteRole(roleId: string): Promise<void> {
|
|
1665
|
-
|
|
1666
|
-
|
|
1683
|
+
assertUuidShape(roleId, 'role_id');
|
|
1684
|
+
const active = await getActiveCube();
|
|
1685
|
+
if (!active?.serverTrustIdentity) throw new Error('Selected Borg server authority state is missing or unreadable');
|
|
1686
|
+
assertUuidShape(active.cubeId, 'cube_id');
|
|
1687
|
+
const result = await localManageRequest<DeleteRoleResult>(
|
|
1688
|
+
active,
|
|
1689
|
+
`/api/cubes/${active.cubeId}/roles/${roleId}`,
|
|
1690
|
+
'DELETE',
|
|
1691
|
+
{
|
|
1692
|
+
operation: `delete role ${manageCopyValue(roleId)} from cube ${manageCopyValue(active.name)}`,
|
|
1693
|
+
cubeName: active.name,
|
|
1694
|
+
noMutation: 'No role was deleted.',
|
|
1695
|
+
},
|
|
1696
|
+
decodeDeleteRoleRequest({}),
|
|
1697
|
+
decodeDeleteRoleResult,
|
|
1698
|
+
);
|
|
1699
|
+
if (!result) throw new Error('Local Borg server returned an empty role deletion response');
|
|
1700
|
+
if (result.role_id !== roleId) {
|
|
1701
|
+
throw new Error('Local Borg server returned a deletion response for an unexpected role');
|
|
1702
|
+
}
|
|
1667
1703
|
}
|
|
1668
1704
|
|
|
1669
1705
|
/**
|
package/src/server-handshake.ts
CHANGED
|
@@ -214,7 +214,7 @@ export interface ServerAttachResult {
|
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
/**
|
|
217
|
-
* Attach an enrolled client principal to one granted cube/role over protocol
|
|
217
|
+
* Attach an enrolled client principal to one granted cube/role over protocol v8.
|
|
218
218
|
* The client CSPRNG-generates the session bearer and persists it PENDING in the
|
|
219
219
|
* OS keychain (keyed by the stable per-seat identity) BEFORE this request, so an
|
|
220
220
|
* interrupted/lost response is recovered by re-sending the exact same bearer —
|
package/src/tool-manifest.ts
CHANGED
|
@@ -154,8 +154,8 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
|
|
|
154
154
|
{
|
|
155
155
|
name: 'borg_role-rationale',
|
|
156
156
|
description:
|
|
157
|
-
"Fetch
|
|
158
|
-
"Pass a role name/
|
|
157
|
+
"Fetch exactly one named section from a role's detailed playbook using the current drone session. " +
|
|
158
|
+
"Pass a role name/UUID and a plain-label section key. Role names and section keys match case-insensitively; UUIDs match exactly. Malformed selectors, ambiguous role names, and unknown roles/sections refuse. Returns the server's canonical role name, role_id, section heading, and the section body in full, refusing rather than truncating when it exceeds the server's role-text size limit, so a drone can read one section on demand instead of carrying the whole playbook in every borg_regen.",
|
|
159
159
|
inputSchema: {
|
|
160
160
|
type: 'object',
|
|
161
161
|
properties: {
|
|
@@ -481,7 +481,7 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
|
|
|
481
481
|
},
|
|
482
482
|
{
|
|
483
483
|
name: 'borg_delete-role',
|
|
484
|
-
description: 'Delete a role.
|
|
484
|
+
description: 'Delete a role using the selected local client\'s cube-management grant. Unknown or inaccessible roles refuse. Also refuses for the default, mandatory, or human-seat role; a role referenced by message-taxonomy routing; or a role assigned to an active drone. Reassign active drones with borg_reassign-drone or remove them with borg_evict-drone first. Evicted drones that held the deleted role are reassigned to the cube\'s default role; their activity-log attribution is unaffected.',
|
|
485
485
|
inputSchema: {
|
|
486
486
|
type: 'object',
|
|
487
487
|
properties: {
|
package/src/update-cmd.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { updateHelpText } from './cli-help.js';
|
|
|
8
8
|
import { preflightBorgServerTag } from './server-handshake.js';
|
|
9
9
|
import { loadBorgServerTrust } from './server-trust.js';
|
|
10
10
|
import { shellEscape } from './shell-escape.js';
|
|
11
|
+
import { refreshManagedAgentMcpConfigs } from './config-utils.js';
|
|
11
12
|
|
|
12
13
|
const CLIENT_PACKAGE = 'borgmcp';
|
|
13
14
|
const SERVER_PACKAGE = 'borgmcp-server';
|
|
@@ -64,6 +65,7 @@ export interface UpdateDeps {
|
|
|
64
65
|
reenter(binPath: string, args: readonly string[]): Promise<number>;
|
|
65
66
|
serverJson(binPath: string, command: 'update' | 'status'): Promise<unknown>;
|
|
66
67
|
verifyRunningProtocol(origin: string): Promise<void>;
|
|
68
|
+
refreshAgentMcpConfigs(): Promise<Array<'claude' | 'codex' | 'opencode'>>;
|
|
67
69
|
confirm(message: string): Promise<'yes' | 'no' | 'eof' | 'interrupted'>;
|
|
68
70
|
isTTY(): boolean;
|
|
69
71
|
stdout(text: string): void;
|
|
@@ -113,7 +115,8 @@ type ServerUpdateFailureStage =
|
|
|
113
115
|
| 'final server state verification'
|
|
114
116
|
| 'final package verification'
|
|
115
117
|
| 'managed service continuity check'
|
|
116
|
-
| 'running server protocol verification'
|
|
118
|
+
| 'running server protocol verification'
|
|
119
|
+
| 'agent MCP config refresh';
|
|
117
120
|
|
|
118
121
|
interface NpmContext {
|
|
119
122
|
commandPath: string;
|
|
@@ -695,6 +698,15 @@ export async function runUpdate(options: UpdateOptions, deps: UpdateDeps): Promi
|
|
|
695
698
|
}
|
|
696
699
|
|
|
697
700
|
if (!serverWasPresent) {
|
|
701
|
+
try {
|
|
702
|
+
await deps.refreshAgentMcpConfigs();
|
|
703
|
+
} catch (error) {
|
|
704
|
+
deps.stderr(
|
|
705
|
+
`Client updated, but agent MCP config refresh failed: ${errorMessage(error, 'unknown failure')}.\n` +
|
|
706
|
+
`Run borg setup to repair Borg-written agent registrations. Configurations that use another command are not changed.\n`,
|
|
707
|
+
);
|
|
708
|
+
return 1;
|
|
709
|
+
}
|
|
698
710
|
deps.stdout(
|
|
699
711
|
`Updated ${CLIENT_PACKAGE}@${pair.client.version}. Local server: skipped (not installed).\n` +
|
|
700
712
|
`Restart active agent sessions to load the updated client.\n`,
|
|
@@ -809,6 +821,9 @@ export async function runUpdate(options: UpdateOptions, deps: UpdateDeps): Promi
|
|
|
809
821
|
retryCommand = 'borg server status';
|
|
810
822
|
await deps.verifyRunningProtocol(status.endpoint!);
|
|
811
823
|
}
|
|
824
|
+
failureStage = 'agent MCP config refresh';
|
|
825
|
+
retryCommand = 'borg update --yes';
|
|
826
|
+
await deps.refreshAgentMcpConfigs();
|
|
812
827
|
deps.stdout(
|
|
813
828
|
state === 'stopped'
|
|
814
829
|
? (
|
|
@@ -1210,6 +1225,7 @@ export function buildDefaultUpdateDeps(): UpdateDeps {
|
|
|
1210
1225
|
const trust = await loadBorgServerTrust(origin);
|
|
1211
1226
|
await preflightBorgServerTag(origin, trust.fetchImpl);
|
|
1212
1227
|
},
|
|
1228
|
+
refreshAgentMcpConfigs: async () => refreshManagedAgentMcpConfigs(),
|
|
1213
1229
|
confirm: defaultConfirm,
|
|
1214
1230
|
isTTY: () => process.stdin.isTTY === true && process.stdout.isTTY === true,
|
|
1215
1231
|
stdout: (text) => process.stdout.write(text),
|