@bivy/bivy 0.5.1-staging.60 → 0.5.1-staging.61
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/bin/bivy.mjs +8 -0
- package/dist/harness/mcp-config-formats.js +27 -0
- package/dist/harness/mcp-config.js +24 -0
- package/dist/harness/mcp-inject.js +78 -2
- package/dist/harness/mcp-serve-cli.js +123 -0
- package/dist/server.js +31 -1
- package/package.json +1 -1
package/bin/bivy.mjs
CHANGED
|
@@ -145,6 +145,7 @@ const attachEntry = path.join(repoRoot, packaged ? "dist/attach.js" : "src/attac
|
|
|
145
145
|
const relayAttachEntry = path.join(repoRoot, packaged ? "dist/relay-attach.js" : "src/relay-attach.ts");
|
|
146
146
|
const execEntry = path.join(repoRoot, packaged ? "dist/exec.js" : "src/exec.ts");
|
|
147
147
|
const mcpProxyEntry = path.join(repoRoot, packaged ? "dist/harness/mcp-proxy-cli.js" : "src/harness/mcp-proxy-cli.ts");
|
|
148
|
+
const mcpServeEntry = path.join(repoRoot, packaged ? "dist/harness/mcp-serve-cli.js" : "src/harness/mcp-serve-cli.ts");
|
|
148
149
|
const qrEntry = path.join(repoRoot, "public", "qr.js");
|
|
149
150
|
const tsxCli = packaged ? "" : path.join(repoRoot, "node_modules", "tsx", "dist", "cli.mjs");
|
|
150
151
|
const nodeBin = process.execPath;
|
|
@@ -4360,6 +4361,13 @@ An agent's own --help passes through, e.g. 'bivy run claude --help'.`);
|
|
|
4360
4361
|
// agent's cwd so relative server commands resolve.
|
|
4361
4362
|
await run(nodeBin, [...nodeScriptArgs(mcpProxyEntry), ...args], { cwd: process.cwd(), env: process.env });
|
|
4362
4363
|
break;
|
|
4364
|
+
case "mcp-serve":
|
|
4365
|
+
// Bivy-owned MCP server (exposes attach_to_chat and future chat tools).
|
|
4366
|
+
// Injected into a non-SDK agent's MCP config so the agent discovers the
|
|
4367
|
+
// tool; like mcp-proxy its stdin/stdout ARE the JSON-RPC stream, so emit
|
|
4368
|
+
// nothing else here and inherit stdio verbatim.
|
|
4369
|
+
await run(nodeBin, [...nodeScriptArgs(mcpServeEntry), ...args], { cwd: process.cwd(), env: process.env });
|
|
4370
|
+
break;
|
|
4363
4371
|
case "help":
|
|
4364
4372
|
case "-h":
|
|
4365
4373
|
case "--help":
|
|
@@ -15,6 +15,33 @@
|
|
|
15
15
|
// exact original bytes on session end regardless. Pure string→string, unit-tested
|
|
16
16
|
// in test/harness-mcp-formats.test.ts.
|
|
17
17
|
import { PROXY_MARKER } from "./mcp-config.js";
|
|
18
|
+
function escapeRegExp(s) {
|
|
19
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
20
|
+
}
|
|
21
|
+
/** True when a TOML config already declares `[mcp_servers.<name>]`. */
|
|
22
|
+
export function hasTomlServer(content, name) {
|
|
23
|
+
return new RegExp(`^\\s*\\[mcp_servers\\.${escapeRegExp(name)}\\]\\s*$`, "m").test(content);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Append a `[mcp_servers.<name>]` table to a Codex TOML config (the reverse of
|
|
27
|
+
* routeThroughProxy's rewrite — this ADDS Bivy's own server rather than wrapping
|
|
28
|
+
* the agent's). Idempotent: returns `inserted: false` untouched when a table of
|
|
29
|
+
* that name already exists. Env is written as an inline table; string values are
|
|
30
|
+
* TOML-escaped via JSON.stringify (safe for the ASCII paths/ids we stamp).
|
|
31
|
+
*/
|
|
32
|
+
export function insertTomlServer(content, name, spec) {
|
|
33
|
+
if (hasTomlServer(content, name))
|
|
34
|
+
return { content, inserted: false };
|
|
35
|
+
const block = [`[mcp_servers.${name}]`, `command = ${JSON.stringify(spec.command)}`, `args = ${tomlStringArray(spec.args ?? [])}`];
|
|
36
|
+
if (spec.env && Object.keys(spec.env).length) {
|
|
37
|
+
const pairs = Object.entries(spec.env).map(([k, v]) => `${k} = ${JSON.stringify(v)}`);
|
|
38
|
+
block.push(`env = { ${pairs.join(", ")} }`);
|
|
39
|
+
}
|
|
40
|
+
// Separate from prior content with a blank line; keep a trailing newline.
|
|
41
|
+
const base = content.length === 0 || content.endsWith("\n") ? content : `${content}\n`;
|
|
42
|
+
const sep = base.length > 0 && !base.endsWith("\n\n") ? "\n" : "";
|
|
43
|
+
return { content: `${base}${sep}${block.join("\n")}\n`, inserted: true };
|
|
44
|
+
}
|
|
18
45
|
function proxyArgs(launcher, server, command, origArgs) {
|
|
19
46
|
return [...(launcher.argsPrefix ?? []), PROXY_MARKER, "--server", server, "--", command, ...origArgs];
|
|
20
47
|
}
|
|
@@ -85,6 +85,30 @@ export function parseProxiedArgs(args) {
|
|
|
85
85
|
const tail = args.slice(sep + 1);
|
|
86
86
|
return { server, command: tail[0], args: tail.slice(1) };
|
|
87
87
|
}
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Bivy-owned tools server — the mirror of the proxy above. routeThroughProxy
|
|
90
|
+
// wraps the agent's OWN servers; this ADDS a `bivy` server (run via `bivy
|
|
91
|
+
// mcp-serve`) so the agent discovers Bivy's chat tools (attach_to_chat, …) in
|
|
92
|
+
// its own tool list. Session id + node URL ride in its env so the tool can post
|
|
93
|
+
// back to the right session.
|
|
94
|
+
/** The server spec that launches `bivy mcp-serve` for a session. */
|
|
95
|
+
export function bivyToolsServerSpec(opts) {
|
|
96
|
+
const env = { BIVY_SESSION_ID: opts.sessionId };
|
|
97
|
+
if (opts.endpoint)
|
|
98
|
+
env.BIVY_MCP_ENDPOINT = opts.endpoint;
|
|
99
|
+
return { command: opts.bivyCommand ?? "bivy", args: ["mcp-serve"], env };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Insert the Bivy tools server under `mcpServers.<name>` (default "bivy").
|
|
103
|
+
* Idempotent: an existing entry of that name is left untouched (returns
|
|
104
|
+
* `added: false`), so a re-inject or a user's own `bivy` server never doubles up.
|
|
105
|
+
*/
|
|
106
|
+
export function withBivyToolsServer(config, spec, name = "bivy") {
|
|
107
|
+
const servers = config.mcpServers ?? {};
|
|
108
|
+
if (servers[name])
|
|
109
|
+
return { config, added: false };
|
|
110
|
+
return { config: { ...config, mcpServers: { ...servers, [name]: spec } }, added: true };
|
|
111
|
+
}
|
|
88
112
|
/** JSON MCP-config file candidates for an agent, most-specific (safest) first. */
|
|
89
113
|
export function agentMcpConfigTargets(agentId, ctx) {
|
|
90
114
|
const ws = (...parts) => nodePath.join(ctx.workspace, ...parts);
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
// FS + network channels. Unit-tested in test/harness-mcp-inject.test.ts.
|
|
15
15
|
import fs from "node:fs";
|
|
16
16
|
import path from "node:path";
|
|
17
|
-
import { agentMcpConfigTargets, routeThroughProxy, } from "./mcp-config.js";
|
|
18
|
-
import { injectTomlMcp, injectYamlMcp } from "./mcp-config-formats.js";
|
|
17
|
+
import { agentMcpConfigTargets, bivyToolsServerSpec, routeThroughProxy, withBivyToolsServer, } from "./mcp-config.js";
|
|
18
|
+
import { injectTomlMcp, injectYamlMcp, insertTomlServer } from "./mcp-config-formats.js";
|
|
19
19
|
/** The proxy launcher Bivy injects — `bivy mcp-proxy …`. */
|
|
20
20
|
export function bivyProxyLauncher(bivyCommand = "bivy") {
|
|
21
21
|
return { command: bivyCommand, argsPrefix: ["mcp-proxy"] };
|
|
@@ -110,6 +110,82 @@ export function injectMcpConfigFile(filePath, launcher) {
|
|
|
110
110
|
},
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Add the `bivy` tools server (attach_to_chat …) to an agent's MCP config so the
|
|
115
|
+
* agent DISCOVERS it as a tool. Unlike the proxy inject (which only rewrites
|
|
116
|
+
* servers a file already has), this CREATES the config when absent so an agent
|
|
117
|
+
* that ships no MCP config still gets the tool. Handles the most-specific JSON
|
|
118
|
+
* config (session-local for claude/gemini/opencode/generic) and Codex's TOML
|
|
119
|
+
* (`~/.codex/config.toml` — Codex has no project-local option). restore() deletes
|
|
120
|
+
* a file it created and rewrites the exact original bytes of one it modified.
|
|
121
|
+
* Idempotent (a `bivy` server already present is a no-op, so concurrent sessions
|
|
122
|
+
* sharing a global config don't double up). Best-effort; never throws. Goose YAML
|
|
123
|
+
* is a follow-up.
|
|
124
|
+
*/
|
|
125
|
+
export function injectBivyToolsForSession(agentId, ctx, bivyCommand = "bivy") {
|
|
126
|
+
const target = agentMcpConfigTargets(agentId, ctx).find((t) => {
|
|
127
|
+
const ext = path.extname(t).toLowerCase();
|
|
128
|
+
return ext === ".json" || ext === ".toml";
|
|
129
|
+
});
|
|
130
|
+
if (!target)
|
|
131
|
+
return { injected: [], restore: () => { } };
|
|
132
|
+
const spec = bivyToolsServerSpec({ sessionId: ctx.sessionId, endpoint: ctx.endpoint, bivyCommand });
|
|
133
|
+
const ext = path.extname(target).toLowerCase();
|
|
134
|
+
const existed = fs.existsSync(target);
|
|
135
|
+
let original;
|
|
136
|
+
if (existed) {
|
|
137
|
+
try {
|
|
138
|
+
original = fs.readFileSync(target, "utf8");
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return { injected: [], restore: () => { } };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
let nextContent;
|
|
145
|
+
if (ext === ".json") {
|
|
146
|
+
let parsed = {};
|
|
147
|
+
if (original !== undefined) {
|
|
148
|
+
try {
|
|
149
|
+
parsed = JSON.parse(original);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// A config we can't parse is not ours to rewrite — leave it be.
|
|
153
|
+
return { injected: [], restore: () => { } };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const { config, added } = withBivyToolsServer(parsed, spec);
|
|
157
|
+
if (!added)
|
|
158
|
+
return { injected: [], restore: () => { } };
|
|
159
|
+
nextContent = `${JSON.stringify(config, null, 2)}\n`;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
const res = insertTomlServer(original ?? "", "bivy", { command: spec.command ?? bivyCommand, args: spec.args, env: spec.env });
|
|
163
|
+
if (!res.inserted)
|
|
164
|
+
return { injected: [], restore: () => { } };
|
|
165
|
+
nextContent = res.content;
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
169
|
+
fs.writeFileSync(target, nextContent);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return { injected: [], restore: () => { } };
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
injected: [target],
|
|
176
|
+
restore: () => {
|
|
177
|
+
try {
|
|
178
|
+
if (existed && original !== undefined)
|
|
179
|
+
fs.writeFileSync(target, original);
|
|
180
|
+
else
|
|
181
|
+
fs.rmSync(target, { force: true });
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// Restore is best-effort.
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
113
189
|
/**
|
|
114
190
|
* Inject the proxy into every MCP config an agent reads for this session.
|
|
115
191
|
* Returns the injected files and a single restore() covering all of them.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// SPDX-License-Identifier: FSL-1.1-ALv2
|
|
2
|
+
// Copyright (c) 2026 Petter André Sjulstad
|
|
3
|
+
// Universal Agent Harness — `bivy mcp-serve` entry point.
|
|
4
|
+
//
|
|
5
|
+
// A Bivy-OWNED stdio MCP server (the mirror of `bivy mcp-proxy`, which wraps the
|
|
6
|
+
// agent's OWN servers). Injected into every non-SDK agent's MCP config at session
|
|
7
|
+
// start (see mcp-inject.ts's injectBivyToolsForSession), it exposes Bivy's chat
|
|
8
|
+
// affordances as first-class tools so ANY agent — codex, gemini, aider, opencode,
|
|
9
|
+
// … — discovers them in its tool list instead of having to be told about a shell
|
|
10
|
+
// command (issue #290). Today it serves one tool, `attach_to_chat`.
|
|
11
|
+
//
|
|
12
|
+
// Claude and Pi already get `attach_to_chat` natively (in-process SDK MCP server /
|
|
13
|
+
// integration ToolProvider); this covers everyone else. The tool just POSTs to the
|
|
14
|
+
// node's existing `POST /api/session/:id/attach` endpoint — the exact plumbing
|
|
15
|
+
// `bivy attach` uses — so it reuses the same workspace confinement, storage, and
|
|
16
|
+
// live broadcast. The session id and node URL arrive via env (BIVY_SESSION_ID /
|
|
17
|
+
// BIVY_MCP_ENDPOINT), stamped into the injected server spec.
|
|
18
|
+
//
|
|
19
|
+
// The attach client is factored out and unit-tested (test/harness-mcp-serve.test.ts)
|
|
20
|
+
// with an injected fetch; the process/transport wiring is thin.
|
|
21
|
+
import path from "node:path";
|
|
22
|
+
import { fileURLToPath } from "node:url";
|
|
23
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
24
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
25
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
26
|
+
const DEFAULT_ENDPOINT = "http://127.0.0.1:4317";
|
|
27
|
+
/** The tools this server advertises, in MCP `tools/list` shape. */
|
|
28
|
+
export const BIVY_MCP_TOOLS = [
|
|
29
|
+
{
|
|
30
|
+
name: "attach_to_chat",
|
|
31
|
+
description: "Send a file or image from the session workspace into the chat the user is reading. The person you're " +
|
|
32
|
+
"talking to is in a chat UI: they cannot see files you only write to disk, and the chat cannot load workspace " +
|
|
33
|
+
"paths or remote image URLs. Use this to show them a report, screenshot, chart, or any file they asked for. An " +
|
|
34
|
+
"image renders inline; any other file shows as a downloadable chip. The path must be inside the session " +
|
|
35
|
+
"workspace. Prefer this over pasting large file contents, describing where a file lives, or markdown image " +
|
|
36
|
+
"syntax (which will not render).",
|
|
37
|
+
inputSchema: {
|
|
38
|
+
type: "object",
|
|
39
|
+
properties: {
|
|
40
|
+
path: { type: "string", description: "Path to the file to send, inside the session workspace (absolute, or relative to it)." },
|
|
41
|
+
caption: { type: "string", description: "Optional short note shown with the attachment." },
|
|
42
|
+
},
|
|
43
|
+
required: ["path"],
|
|
44
|
+
additionalProperties: false,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
/**
|
|
49
|
+
* Perform an `attach_to_chat` call by POSTing to the node's attach endpoint,
|
|
50
|
+
* which confines the path to the workspace, stores the bytes, and broadcasts the
|
|
51
|
+
* chip. Never throws — every failure is returned as `{ isError: true, text }` so
|
|
52
|
+
* the agent gets an actionable message instead of a broken tool.
|
|
53
|
+
*/
|
|
54
|
+
export async function runAttachToChat(endpoint, sessionId, args, fetchImpl, token) {
|
|
55
|
+
if (!sessionId)
|
|
56
|
+
return { isError: true, text: "No active Bivy session to attach to (BIVY_SESSION_ID is not set)." };
|
|
57
|
+
const filePath = typeof args.path === "string" ? args.path.trim() : "";
|
|
58
|
+
if (!filePath)
|
|
59
|
+
return { isError: true, text: "Provide a `path` to a file inside the session workspace." };
|
|
60
|
+
const caption = typeof args.caption === "string" ? args.caption : undefined;
|
|
61
|
+
const base = endpoint.replace(/\/+$/, "");
|
|
62
|
+
const headers = { "content-type": "application/json" };
|
|
63
|
+
if (token)
|
|
64
|
+
headers.authorization = `Bearer ${token}`;
|
|
65
|
+
let res;
|
|
66
|
+
try {
|
|
67
|
+
res = await fetchImpl(`${base}/api/session/${encodeURIComponent(sessionId)}/attach`, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers,
|
|
70
|
+
body: JSON.stringify({ path: filePath, caption }),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
return { isError: true, text: `Could not reach the Bivy node to attach the file: ${error instanceof Error ? error.message : String(error)}` };
|
|
75
|
+
}
|
|
76
|
+
const body = (await res.json().catch(() => ({})));
|
|
77
|
+
if (!res.ok || body?.ok === false) {
|
|
78
|
+
return { isError: true, text: `Attach failed: ${body?.error || `node returned ${res.status}`}` };
|
|
79
|
+
}
|
|
80
|
+
const name = body?.name || filePath;
|
|
81
|
+
return { isError: false, text: `Attached ${name} to the chat as ${body?.kind === "image" ? "an inline image" : "a downloadable file"}. The user can see it now.` };
|
|
82
|
+
}
|
|
83
|
+
/** Build the Bivy MCP `Server` with tools/list + tools/call handlers wired. */
|
|
84
|
+
export function createBivyMcpServer(deps = {}) {
|
|
85
|
+
const endpoint = deps.endpoint ?? process.env.BIVY_MCP_ENDPOINT ?? DEFAULT_ENDPOINT;
|
|
86
|
+
const sessionId = deps.sessionId ?? process.env.BIVY_SESSION_ID ?? process.env.BIVY_MCP_SESSION ?? "";
|
|
87
|
+
const token = deps.token ?? process.env.BIVY_MCP_TOKEN ?? undefined;
|
|
88
|
+
const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
|
|
89
|
+
const server = new Server({ name: "bivy", version: "1.0.0" }, { capabilities: { tools: {} } });
|
|
90
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: BIVY_MCP_TOOLS }));
|
|
91
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
92
|
+
if (req.params.name !== "attach_to_chat") {
|
|
93
|
+
return { isError: true, content: [{ type: "text", text: `Unknown tool: ${req.params.name}` }] };
|
|
94
|
+
}
|
|
95
|
+
const result = await runAttachToChat(endpoint, sessionId, (req.params.arguments ?? {}), fetchImpl, token);
|
|
96
|
+
return { isError: result.isError, content: [{ type: "text", text: result.text }] };
|
|
97
|
+
});
|
|
98
|
+
return server;
|
|
99
|
+
}
|
|
100
|
+
/** Connect the Bivy MCP server to stdio and serve until the stream closes. */
|
|
101
|
+
export async function runMcpServeCli(deps = {}) {
|
|
102
|
+
const server = createBivyMcpServer(deps);
|
|
103
|
+
const transport = new StdioServerTransport();
|
|
104
|
+
await server.connect(transport);
|
|
105
|
+
// Resolves when the transport closes (agent disconnects / process is killed).
|
|
106
|
+
await new Promise((resolve) => transport.onclose = resolve);
|
|
107
|
+
}
|
|
108
|
+
// Run when invoked directly (via `bivy mcp-serve` → tsx this file). Emit nothing
|
|
109
|
+
// to stdout except JSON-RPC — the transport owns stdin/stdout.
|
|
110
|
+
const invokedDirectly = (() => {
|
|
111
|
+
try {
|
|
112
|
+
return Boolean(process.argv[1]) && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
})();
|
|
118
|
+
if (invokedDirectly) {
|
|
119
|
+
void runMcpServeCli().catch((error) => {
|
|
120
|
+
process.stderr.write(`bivy mcp-serve: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
});
|
|
123
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -55,7 +55,7 @@ import { evictToCap, dirSizeBytes } from "./harness/cache-evict.js";
|
|
|
55
55
|
import { checkDiskAdmission } from "./harness/disk-admission.js";
|
|
56
56
|
import { sandboxTier, setConfiguredSandboxTier, normalizeSandboxTier } from "./harness/sandbox.js";
|
|
57
57
|
import { setConfiguredAutoAttachToolImages } from "./harness/tool-image-attachments.js";
|
|
58
|
-
import { injectMcpProxyForSession } from "./harness/mcp-inject.js";
|
|
58
|
+
import { injectMcpProxyForSession, injectBivyToolsForSession } from "./harness/mcp-inject.js";
|
|
59
59
|
import { parseRepo, inferGitHubRepoFromWorkspace, isSharedCloneRoot, resolveGitHubToken, cloneOrUpdateRepo, resolveDefaultBaseRef, resolveBranchBaseRef, fetchOrigin } from "./repo-workspace.js";
|
|
60
60
|
import { configureGitAuth, writeGitCredentialEndpoint } from "./git-auth.js";
|
|
61
61
|
import { GitHubTaskPoller, resolveGitHubTaskConfig, buildTaskPrompt, buildResumePrompt, buildInteractiveResumePrompt, DEFAULT_ISSUE_INSTRUCTIONS, parseBivyDirectives, commitAll, pushBranch, mergeBaseIntoBranch, completeMerge, abortMerge, findOpenPullRequestForBranch, findPullRequestsForBranch, findMergedPullRequestForBranch, issueBranchName, getPullRequest, commentIssue, listOpenLabelledIssues, selectActionableIssues, getIssue, getIssueCommentBody, addLabel, removeLabel, announcePickup, } from "./github-tasks.js";
|
|
@@ -7438,6 +7438,36 @@ async function createSession(workspace = defaultWorkspace, sessionFile, opts = {
|
|
|
7438
7438
|
// Injection is best-effort; never block session creation.
|
|
7439
7439
|
}
|
|
7440
7440
|
}
|
|
7441
|
+
// Bivy-owned tools (attach_to_chat, …): make them discoverable to non-SDK
|
|
7442
|
+
// agents by adding a `bivy` server (run via `bivy mcp-serve`) to the agent's
|
|
7443
|
+
// config. Default-on (not gated on BIVY_MCP_PROXY) — it only ADDS a safe,
|
|
7444
|
+
// session-scoped, restored capability the agent already has via env, so the
|
|
7445
|
+
// chat can receive files. Claude/Pi expose these natively (in-process SDK MCP
|
|
7446
|
+
// server / integration ToolProvider), so the tool-interception runtimes are
|
|
7447
|
+
// skipped to avoid a duplicate registration.
|
|
7448
|
+
if (!rt.capabilities.toolInterception) {
|
|
7449
|
+
try {
|
|
7450
|
+
const res = injectBivyToolsForSession(rt.id, {
|
|
7451
|
+
workspace: sessionWorkspace,
|
|
7452
|
+
home: os.homedir(),
|
|
7453
|
+
sessionId,
|
|
7454
|
+
endpoint: process.env.BIVY_MCP_ENDPOINT,
|
|
7455
|
+
});
|
|
7456
|
+
if (res.injected.length) {
|
|
7457
|
+
const prev = record.mcpRestore;
|
|
7458
|
+
record.mcpRestore = () => { try {
|
|
7459
|
+
res.restore();
|
|
7460
|
+
}
|
|
7461
|
+
finally {
|
|
7462
|
+
prev?.();
|
|
7463
|
+
} };
|
|
7464
|
+
console.log(`MCP tools: added bivy server to ${res.injected.length} config(s) for session ${sessionId}`);
|
|
7465
|
+
}
|
|
7466
|
+
}
|
|
7467
|
+
catch {
|
|
7468
|
+
// Best-effort; never block session creation.
|
|
7469
|
+
}
|
|
7470
|
+
}
|
|
7441
7471
|
rememberSession(record);
|
|
7442
7472
|
rememberWorkspace(sessionWorkspace);
|
|
7443
7473
|
attachSessionListeners(record);
|
package/package.json
CHANGED