@matterailab/orbcode 0.2.1 → 0.2.3
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 +405 -18
- package/dist/api/models.js +19 -3
- package/dist/commands/mcp.js +266 -0
- package/dist/config/settings.js +27 -0
- package/dist/core/agent.js +28 -7
- package/dist/headless.js +18 -0
- package/dist/index.js +9 -0
- package/dist/mcp/auth.js +289 -0
- package/dist/mcp/client.js +132 -0
- package/dist/mcp/config.js +270 -0
- package/dist/mcp/manager.js +277 -0
- package/dist/mcp/types.js +8 -0
- package/dist/memory/loader.js +167 -0
- package/dist/memory/types.js +1 -0
- package/dist/prompts/system.js +26 -7
- package/dist/skills/loader.js +132 -0
- package/dist/skills/types.js +1 -0
- package/dist/tools/executors/skills.js +28 -0
- package/dist/tools/index.js +22 -3
- package/dist/tools/schemas/index.js +6 -3
- package/dist/tools/schemas/use_skill.js +1 -1
- package/dist/ui/App.js +219 -53
- package/dist/ui/components/McpApprovalPrompt.js +61 -0
- package/dist/ui/components/McpAuthScreen.js +55 -0
- package/dist/ui/components/McpPicker.js +262 -0
- package/dist/ui/components/ModelPicker.js +2 -2
- package/dist/ui/components/StatusBar.js +28 -9
- package/dist/utils/clipboard.js +45 -0
- package/package.json +2 -1
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { addMcpServer, configPathForScope, loadMcpConfig, removeMcpServerAnyScope, } from "../mcp/config.js";
|
|
2
|
+
/**
|
|
3
|
+
* `orbcode mcp` subcommand — manage MCP servers from the command line, like
|
|
4
|
+
* Claude Code's `claude mcp add/remove/list`.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* orbcode mcp add <name> <command> [args...] stdio (default)
|
|
8
|
+
* orbcode mcp add --transport http <name> <url> http
|
|
9
|
+
* orbcode mcp add --transport sse <name> <url> sse
|
|
10
|
+
* orbcode mcp add -s <scope> -e KEY=value ... <name> ... with scope/env
|
|
11
|
+
* orbcode mcp add --header KEY=value ... <name> <url> http/sse headers
|
|
12
|
+
* orbcode mcp remove <name> remove from any scope
|
|
13
|
+
* orbcode mcp list list all servers
|
|
14
|
+
*
|
|
15
|
+
* Flags:
|
|
16
|
+
* -s, --scope <user|project|local> config scope (default: project)
|
|
17
|
+
* -t, --transport <stdio|http|sse> transport type (default: stdio)
|
|
18
|
+
* -e, --env KEY=value environment variable (repeatable, stdio)
|
|
19
|
+
* --header KEY=value HTTP header (repeatable, http/sse)
|
|
20
|
+
* --oauth enable OAuth for http/sse
|
|
21
|
+
* --oauth-scope <scope> OAuth scope (implies --oauth)
|
|
22
|
+
*/
|
|
23
|
+
const VALID_SCOPES = ["user", "project", "local"];
|
|
24
|
+
const VALID_TRANSPORTS = ["stdio", "http", "sse"];
|
|
25
|
+
/** Parse a KEY=value pair from a flag argument. */
|
|
26
|
+
function parseKeyValue(arg) {
|
|
27
|
+
const idx = arg.indexOf("=");
|
|
28
|
+
if (idx === -1)
|
|
29
|
+
throw new Error(`Expected KEY=value, got "${arg}"`);
|
|
30
|
+
return [arg.slice(0, idx), arg.slice(idx + 1)];
|
|
31
|
+
}
|
|
32
|
+
/** Print usage and exit. */
|
|
33
|
+
function printMcpHelp() {
|
|
34
|
+
console.log(`orbcode mcp — manage MCP servers
|
|
35
|
+
|
|
36
|
+
Usage:
|
|
37
|
+
orbcode mcp add [options] <name> <command> [args...] add a stdio server
|
|
38
|
+
orbcode mcp add [options] --transport http <name> <url> add an http server
|
|
39
|
+
orbcode mcp add [options] --transport sse <name> <url> add an sse server
|
|
40
|
+
orbcode mcp remove <name> remove a server
|
|
41
|
+
orbcode mcp list list all servers
|
|
42
|
+
|
|
43
|
+
Options:
|
|
44
|
+
-s, --scope <user|project|local> config scope (default: project)
|
|
45
|
+
-t, --transport <stdio|http|sse> transport type (default: stdio)
|
|
46
|
+
-e, --env KEY=value environment variable (repeatable, stdio)
|
|
47
|
+
--header KEY=value HTTP header (repeatable, http/sse)
|
|
48
|
+
--oauth enable OAuth for http/sse
|
|
49
|
+
--oauth-scope <scope> OAuth scope (implies --oauth)
|
|
50
|
+
|
|
51
|
+
Scopes:
|
|
52
|
+
project .mcp.json in the current directory (shared, checked into git)
|
|
53
|
+
user ~/.orbcode/settings.json (applies to all projects on this machine)
|
|
54
|
+
local .orbcode/settings.json (per-project, per-machine, not checked in)`);
|
|
55
|
+
}
|
|
56
|
+
/** Handle `orbcode mcp ...`. Returns the process exit code. */
|
|
57
|
+
export async function runMcpCommand(args) {
|
|
58
|
+
const [subcommand, ...rest] = args;
|
|
59
|
+
switch (subcommand) {
|
|
60
|
+
case "add":
|
|
61
|
+
return runAdd(rest);
|
|
62
|
+
case "remove":
|
|
63
|
+
case "rm":
|
|
64
|
+
return runRemove(rest);
|
|
65
|
+
case "list":
|
|
66
|
+
case "ls":
|
|
67
|
+
return runList(rest);
|
|
68
|
+
case "help":
|
|
69
|
+
case "--help":
|
|
70
|
+
case "-h":
|
|
71
|
+
printMcpHelp();
|
|
72
|
+
return 0;
|
|
73
|
+
default:
|
|
74
|
+
console.error(`Unknown mcp subcommand: "${subcommand ?? ""}". Try: orbcode mcp help`);
|
|
75
|
+
return 1;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** Parse the flags shared by add (scope, transport, env, headers, oauth). */
|
|
79
|
+
function parseAddFlags(args) {
|
|
80
|
+
let scope = "project";
|
|
81
|
+
let transport = "stdio";
|
|
82
|
+
const env = {};
|
|
83
|
+
const headers = {};
|
|
84
|
+
let oauth = false;
|
|
85
|
+
const positional = [];
|
|
86
|
+
// Flags must come before the server name. Once we hit the first positional
|
|
87
|
+
// (the server name), everything after it — including args that look like
|
|
88
|
+
// flags (-y, -f, --foo) — becomes the server's command/args. This matches
|
|
89
|
+
// Claude Code and lets stdio servers take their own flags.
|
|
90
|
+
let sawPositional = false;
|
|
91
|
+
for (let i = 0; i < args.length; i++) {
|
|
92
|
+
const arg = args[i];
|
|
93
|
+
if (sawPositional) {
|
|
94
|
+
// A "--" immediately after the server name is the separator between
|
|
95
|
+
// orbcode's flags and the server's command (e.g. `add name -- npx`),
|
|
96
|
+
// matching Claude Code's `claude mcp add <name> -- <command>`. Consume
|
|
97
|
+
// it so it isn't mistaken for the command. A later "--" is kept as a
|
|
98
|
+
// literal arg for the server command.
|
|
99
|
+
if (arg === "--" && positional.length === 1)
|
|
100
|
+
continue;
|
|
101
|
+
positional.push(arg);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (arg === "-s" || arg === "--scope") {
|
|
105
|
+
const value = args[++i];
|
|
106
|
+
if (!value || !VALID_SCOPES.includes(value)) {
|
|
107
|
+
throw new Error(`Invalid scope "${value ?? ""}". Valid: ${VALID_SCOPES.join(", ")}`);
|
|
108
|
+
}
|
|
109
|
+
scope = value;
|
|
110
|
+
}
|
|
111
|
+
else if (arg === "-t" || arg === "--transport") {
|
|
112
|
+
const value = args[++i];
|
|
113
|
+
if (!value || !VALID_TRANSPORTS.includes(value)) {
|
|
114
|
+
throw new Error(`Invalid transport "${value ?? ""}". Valid: ${VALID_TRANSPORTS.join(", ")}`);
|
|
115
|
+
}
|
|
116
|
+
transport = value;
|
|
117
|
+
}
|
|
118
|
+
else if (arg === "-e" || arg === "--env") {
|
|
119
|
+
const value = args[++i];
|
|
120
|
+
if (!value)
|
|
121
|
+
throw new Error("Missing value for --env");
|
|
122
|
+
const [k, v] = parseKeyValue(value);
|
|
123
|
+
env[k] = v;
|
|
124
|
+
}
|
|
125
|
+
else if (arg === "--header") {
|
|
126
|
+
const value = args[++i];
|
|
127
|
+
if (!value)
|
|
128
|
+
throw new Error("Missing value for --header");
|
|
129
|
+
const [k, v] = parseKeyValue(value);
|
|
130
|
+
headers[k] = v;
|
|
131
|
+
}
|
|
132
|
+
else if (arg === "--oauth") {
|
|
133
|
+
oauth = true;
|
|
134
|
+
}
|
|
135
|
+
else if (arg === "--oauth-scope") {
|
|
136
|
+
const value = args[++i];
|
|
137
|
+
if (!value)
|
|
138
|
+
throw new Error("Missing value for --oauth-scope");
|
|
139
|
+
oauth = { scope: value };
|
|
140
|
+
}
|
|
141
|
+
else if (arg === "--") {
|
|
142
|
+
// Explicit separator: everything after is positional.
|
|
143
|
+
sawPositional = true;
|
|
144
|
+
}
|
|
145
|
+
else if (arg.startsWith("-") && arg.length > 1) {
|
|
146
|
+
throw new Error(`Unknown flag: ${arg}. Put flags before the server name, or use -- to separate.`);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
positional.push(arg);
|
|
150
|
+
sawPositional = true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return { scope, transport, env, headers, oauth, positional };
|
|
154
|
+
}
|
|
155
|
+
/** `orbcode mcp add` — build a server config from flags and write it. */
|
|
156
|
+
function runAdd(args) {
|
|
157
|
+
let parsed;
|
|
158
|
+
try {
|
|
159
|
+
parsed = parseAddFlags(args);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
console.error(error.message);
|
|
163
|
+
return 1;
|
|
164
|
+
}
|
|
165
|
+
const { scope, transport, env, headers, oauth, positional } = parsed;
|
|
166
|
+
if (positional.length === 0) {
|
|
167
|
+
console.error("Missing server name. Usage: orbcode mcp add <name> <command> [args...]");
|
|
168
|
+
return 1;
|
|
169
|
+
}
|
|
170
|
+
const [name, ...rest] = positional;
|
|
171
|
+
let config;
|
|
172
|
+
if (transport === "http" || transport === "sse") {
|
|
173
|
+
if (rest.length === 0) {
|
|
174
|
+
console.error(`Missing URL for ${transport} transport. Usage: orbcode mcp add -t ${transport} <name> <url>`);
|
|
175
|
+
return 1;
|
|
176
|
+
}
|
|
177
|
+
const url = rest[0];
|
|
178
|
+
const cfg = { type: transport, url };
|
|
179
|
+
if (Object.keys(headers).length > 0)
|
|
180
|
+
cfg.headers = headers;
|
|
181
|
+
if (oauth !== false)
|
|
182
|
+
cfg.oauth = oauth;
|
|
183
|
+
config = cfg;
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
// stdio
|
|
187
|
+
if (rest.length === 0) {
|
|
188
|
+
console.error("Missing command for stdio transport. Usage: orbcode mcp add <name> <command> [args...]");
|
|
189
|
+
return 1;
|
|
190
|
+
}
|
|
191
|
+
const [command, ...cmdArgs] = rest;
|
|
192
|
+
const cfg = { type: "stdio", command: command };
|
|
193
|
+
if (cmdArgs.length > 0)
|
|
194
|
+
cfg.args = cmdArgs;
|
|
195
|
+
if (Object.keys(env).length > 0)
|
|
196
|
+
cfg.env = env;
|
|
197
|
+
config = cfg;
|
|
198
|
+
}
|
|
199
|
+
let detail;
|
|
200
|
+
if (transport === "http" || transport === "sse") {
|
|
201
|
+
const cfg = config;
|
|
202
|
+
detail = `${transport} ${cfg.url}`;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
const cfg = config;
|
|
206
|
+
detail = `stdio ${cfg.command}${cfg.args?.length ? " " + cfg.args.join(" ") : ""}`;
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
addMcpServer(process.cwd(), name, config, scope);
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
console.error(error.message);
|
|
213
|
+
return 1;
|
|
214
|
+
}
|
|
215
|
+
const filePath = configPathForScope(process.cwd(), scope);
|
|
216
|
+
console.log(`Added ${transport === "http" || transport === "sse" ? transport.toUpperCase() : "stdio"} MCP server ${name}`);
|
|
217
|
+
if (transport === "http" || transport === "sse") {
|
|
218
|
+
console.log(` URL: ${config.url}`);
|
|
219
|
+
}
|
|
220
|
+
console.log(` Scope: ${scope}`);
|
|
221
|
+
console.log(` File modified: ${filePath}`);
|
|
222
|
+
return 0;
|
|
223
|
+
}
|
|
224
|
+
/** `orbcode mcp remove <name>` — remove from whichever scope it's in. */
|
|
225
|
+
function runRemove(args) {
|
|
226
|
+
if (args.length === 0) {
|
|
227
|
+
console.error("Missing server name. Usage: orbcode mcp remove <name>");
|
|
228
|
+
return 1;
|
|
229
|
+
}
|
|
230
|
+
const name = args[0];
|
|
231
|
+
const scope = removeMcpServerAnyScope(process.cwd(), name);
|
|
232
|
+
if (!scope) {
|
|
233
|
+
console.error(`MCP server "${name}" not found in any scope.`);
|
|
234
|
+
return 1;
|
|
235
|
+
}
|
|
236
|
+
const filePath = configPathForScope(process.cwd(), scope);
|
|
237
|
+
console.log(`Removed MCP server "${name}" from ${scope} scope.`);
|
|
238
|
+
console.log(` File modified: ${filePath}`);
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
241
|
+
/** `orbcode mcp list` — print all configured servers with their scope + status. */
|
|
242
|
+
function runList(_args) {
|
|
243
|
+
const { servers } = loadMcpConfig(process.cwd());
|
|
244
|
+
const names = Object.keys(servers).sort();
|
|
245
|
+
if (names.length === 0) {
|
|
246
|
+
console.log("No MCP servers configured.");
|
|
247
|
+
console.log("Add one with: orbcode mcp add <name> <command> [args...]");
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
250
|
+
for (const name of names) {
|
|
251
|
+
const cfg = servers[name];
|
|
252
|
+
const scope = cfg.scope;
|
|
253
|
+
let detail;
|
|
254
|
+
if (cfg.type === "http" || cfg.type === "sse") {
|
|
255
|
+
detail = `${cfg.type} ${cfg.url}`;
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
// stdio (type undefined or "stdio")
|
|
259
|
+
const cmd = cfg.command;
|
|
260
|
+
const args = cfg.args ?? [];
|
|
261
|
+
detail = `stdio ${cmd}${args.length ? " " + args.join(" ") : ""}`;
|
|
262
|
+
}
|
|
263
|
+
console.log(` ${name.padEnd(20)} ${scope.padEnd(8)} ${detail}`);
|
|
264
|
+
}
|
|
265
|
+
return 0;
|
|
266
|
+
}
|
package/dist/config/settings.js
CHANGED
|
@@ -18,6 +18,8 @@ const SETTINGS_KEYS = [
|
|
|
18
18
|
"baseUrl",
|
|
19
19
|
"apiKey",
|
|
20
20
|
"env",
|
|
21
|
+
"enabledMcpServers",
|
|
22
|
+
"disabledMcpServers",
|
|
21
23
|
];
|
|
22
24
|
export function getConfigDir() {
|
|
23
25
|
return process.env.MATTERAI_CONFIG_DIR || path.join(os.homedir(), ".orbcode");
|
|
@@ -150,6 +152,12 @@ export function loadSettings() {
|
|
|
150
152
|
if (Array.isArray(fileSettings.customModels)) {
|
|
151
153
|
customModels.push(...fileSettings.customModels);
|
|
152
154
|
}
|
|
155
|
+
// MCP servers: merge across scopes (local overrides project overrides
|
|
156
|
+
// user on name collisions). The MCP config layer also reads .mcp.json;
|
|
157
|
+
// settings.json mcpServers are the per-user/per-machine override path.
|
|
158
|
+
if (fileSettings.mcpServers && typeof fileSettings.mcpServers === "object") {
|
|
159
|
+
settings.mcpServers = { ...(settings.mcpServers ?? {}), ...fileSettings.mcpServers };
|
|
160
|
+
}
|
|
153
161
|
}
|
|
154
162
|
if (customModels.length > 0) {
|
|
155
163
|
settings.customModels = customModels;
|
|
@@ -210,3 +218,22 @@ export function saveSettings(settings) {
|
|
|
210
218
|
};
|
|
211
219
|
fs.writeFileSync(getConfigPath(), JSON.stringify(toPersist, null, "\t") + "\n", { mode: 0o600 });
|
|
212
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* Persist the enabled/disabled MCP server lists to the local project
|
|
223
|
+
* settings.json (`.orbcode/settings.json`). These are per-project approval
|
|
224
|
+
* decisions, so they live in the local scope, not in config.json.
|
|
225
|
+
*/
|
|
226
|
+
export function saveMcpApproval(cwd, enabled, disabled) {
|
|
227
|
+
const settingsPath = path.join(cwd, ".orbcode", "settings.json");
|
|
228
|
+
let existing = {};
|
|
229
|
+
try {
|
|
230
|
+
existing = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
// file may not exist yet
|
|
234
|
+
}
|
|
235
|
+
existing.enabledMcpServers = [...new Set(enabled)];
|
|
236
|
+
existing.disabledMcpServers = [...new Set(disabled)];
|
|
237
|
+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
238
|
+
fs.writeFileSync(settingsPath, JSON.stringify(existing, null, "\t") + "\n", { mode: 0o600 });
|
|
239
|
+
}
|
package/dist/core/agent.js
CHANGED
|
@@ -9,6 +9,8 @@ import { walkFiles } from "../tools/executors/listFiles.js";
|
|
|
9
9
|
import { previewFileChange } from "../tools/executors/files.js";
|
|
10
10
|
import { getSessionFilePath, saveSession } from "./sessions.js";
|
|
11
11
|
import { HookRunner } from "./hooks.js";
|
|
12
|
+
import { loadMemoryFiles } from "../memory/loader.js";
|
|
13
|
+
import { loadSkills } from "../skills/loader.js";
|
|
12
14
|
const MAX_STEPS_PER_TURN = 50;
|
|
13
15
|
const RESULT_PREVIEW_LINES = 6;
|
|
14
16
|
function detectRepo(cwd) {
|
|
@@ -77,6 +79,8 @@ export class Agent {
|
|
|
77
79
|
title = "";
|
|
78
80
|
createdAt = new Date().toISOString();
|
|
79
81
|
hooks;
|
|
82
|
+
/** MCP server manager (may be undefined when MCP is disabled). */
|
|
83
|
+
mcp;
|
|
80
84
|
/** SessionStart fires once, lazily, before the first turn of this instance. */
|
|
81
85
|
sessionStarted = false;
|
|
82
86
|
/** carries SessionStart additionalContext into the first user message */
|
|
@@ -107,7 +111,13 @@ export class Agent {
|
|
|
107
111
|
this.firstMessageSent = this.messages.length > 0;
|
|
108
112
|
}
|
|
109
113
|
this.sessionApproveEdits = options.autoApproveEdits;
|
|
110
|
-
this.
|
|
114
|
+
this.mcp = options.mcp;
|
|
115
|
+
// Build the system prompt with AGENTS.md memory files and the skills
|
|
116
|
+
// catalog injected, so the model sees project/user instructions and
|
|
117
|
+
// knows which skills it can invoke.
|
|
118
|
+
const memoryFiles = loadMemoryFiles(options.cwd);
|
|
119
|
+
const skills = loadSkills(options.cwd);
|
|
120
|
+
this.systemPrompt = buildSystemPrompt(options.cwd, { memoryFiles, skills });
|
|
111
121
|
this.client = createLLMClient({
|
|
112
122
|
token: options.token,
|
|
113
123
|
modelId: options.modelId,
|
|
@@ -353,15 +363,26 @@ User time zone: ${timeZone}, UTC${timeZoneOffsetStr}`;
|
|
|
353
363
|
async endSession(reason) {
|
|
354
364
|
// Let in-flight Notification hooks settle before the final SessionEnd.
|
|
355
365
|
await this.awaitBackground(3000);
|
|
356
|
-
if (
|
|
357
|
-
|
|
366
|
+
if (this.hooks.hasHooks("SessionEnd")) {
|
|
367
|
+
try {
|
|
368
|
+
await this.hooks.run("SessionEnd", { reason });
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
// a SessionEnd hook must never prevent the app from exiting
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
// Tear down MCP server connections so child processes / sockets don't leak.
|
|
358
375
|
try {
|
|
359
|
-
await this.
|
|
376
|
+
await this.mcp?.stop();
|
|
360
377
|
}
|
|
361
378
|
catch {
|
|
362
|
-
//
|
|
379
|
+
// best-effort
|
|
363
380
|
}
|
|
364
381
|
}
|
|
382
|
+
/** The MCP manager (for the TUI's /mcp command and approval flow). */
|
|
383
|
+
get mcpManager() {
|
|
384
|
+
return this.mcp;
|
|
385
|
+
}
|
|
365
386
|
/** Summarize the conversation so far and replace history with the summary. */
|
|
366
387
|
async compact() {
|
|
367
388
|
const { onEvent } = this.options.callbacks;
|
|
@@ -452,7 +473,7 @@ User time zone: ${timeZone}, UTC${timeZoneOffsetStr}`;
|
|
|
452
473
|
let reasoningDetails;
|
|
453
474
|
const toolCallsByIndex = new Map();
|
|
454
475
|
let nextSyntheticIndex = 10000;
|
|
455
|
-
const stream = this.client.createMessage(this.systemPrompt, this.outgoingMessages(), getActiveTools(), signal);
|
|
476
|
+
const stream = this.client.createMessage(this.systemPrompt, this.outgoingMessages(), getActiveTools(this.mcp), signal);
|
|
456
477
|
for await (const chunk of stream) {
|
|
457
478
|
if (signal.aborted)
|
|
458
479
|
throw new DOMException("aborted", "AbortError");
|
|
@@ -663,7 +684,7 @@ User time zone: ${timeZone}, UTC${timeZoneOffsetStr}`;
|
|
|
663
684
|
this.sessionApproveCommands = true;
|
|
664
685
|
}
|
|
665
686
|
}
|
|
666
|
-
const result = await executeTool(toolCall.name, args, this.toolContext());
|
|
687
|
+
const result = await executeTool(toolCall.name, args, this.toolContext(), this.mcp);
|
|
667
688
|
// PostToolUse can feed extra context (or a block reason) back to the
|
|
668
689
|
// model. PreToolUse additionalContext is delivered here too.
|
|
669
690
|
let resultText = result.text;
|
package/dist/headless.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getModel, isValidAxonModel, usesAiSdk } from "./api/models.js";
|
|
2
2
|
import { getAuthToken, getPendingProjectHooks, loadSettings } from "./config/settings.js";
|
|
3
3
|
import { Agent } from "./core/agent.js";
|
|
4
|
+
import { McpManager } from "./mcp/manager.js";
|
|
4
5
|
/** Non-interactive `orbcode -p "prompt"` mode: prints the final response to stdout. */
|
|
5
6
|
export async function runHeadless(prompt, yolo) {
|
|
6
7
|
const settings = loadSettings();
|
|
@@ -28,6 +29,21 @@ export async function runHeadless(prompt, yolo) {
|
|
|
28
29
|
process.stderr.write(`note: ${pendingHooks.commands.length} project hook(s) in .orbcode/settings.json are untrusted and were skipped. ` +
|
|
29
30
|
`Trust them in an interactive session, or set MATTERAI_TRUST_PROJECT_HOOKS=1.\n`);
|
|
30
31
|
}
|
|
32
|
+
// Start MCP servers. In headless mode there's no interactive approval, so
|
|
33
|
+
// project-scope servers are only connected if they were previously approved
|
|
34
|
+
// (persisted in .orbcode/settings.json). Unapproved project servers are
|
|
35
|
+
// skipped with a note, matching the project-hooks behavior.
|
|
36
|
+
const mcp = new McpManager(process.cwd(), settings.disabledMcpServers ?? [], settings.enabledMcpServers ?? []);
|
|
37
|
+
const mcpSnapshot = await mcp.start();
|
|
38
|
+
const pendingMcp = mcp.getPendingApproval();
|
|
39
|
+
if (pendingMcp.length > 0) {
|
|
40
|
+
process.stderr.write(`note: ${pendingMcp.length} project MCP server(s) in .mcp.json are unapproved and were skipped. ` +
|
|
41
|
+
`Approve them in an interactive session with /mcp.\n`);
|
|
42
|
+
}
|
|
43
|
+
const connectedMcp = mcpSnapshot.servers.filter((s) => s.status === "connected").length;
|
|
44
|
+
if (connectedMcp > 0) {
|
|
45
|
+
process.stderr.write(`MCP: ${connectedMcp}/${mcpSnapshot.servers.length} server(s) connected.\n`);
|
|
46
|
+
}
|
|
31
47
|
let exitCode = 0;
|
|
32
48
|
// Only the final content is printed: either the attempt_completion result
|
|
33
49
|
// or, failing that, the last assistant text. Intermediate text and tool
|
|
@@ -46,6 +62,7 @@ export async function runHeadless(prompt, yolo) {
|
|
|
46
62
|
autoApproveEdits: yolo,
|
|
47
63
|
autoApproveSafeCommands: yolo,
|
|
48
64
|
hooks: settings.hooks,
|
|
65
|
+
mcp,
|
|
49
66
|
callbacks: {
|
|
50
67
|
onEvent: (event) => {
|
|
51
68
|
switch (event.type) {
|
|
@@ -84,6 +101,7 @@ export async function runHeadless(prompt, yolo) {
|
|
|
84
101
|
});
|
|
85
102
|
await agent.runTurn(prompt);
|
|
86
103
|
await agent.endSession("other");
|
|
104
|
+
await mcp.stop().catch(() => { });
|
|
87
105
|
const finalContent = completionResult || lastText || textBuffer;
|
|
88
106
|
if (finalContent) {
|
|
89
107
|
process.stdout.write(finalContent.trimEnd() + "\n");
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { render } from "ink";
|
|
|
3
3
|
import { PRODUCT_NAME, VERSION } from "./branding.js";
|
|
4
4
|
import { App } from "./ui/App.js";
|
|
5
5
|
import { runHeadless } from "./headless.js";
|
|
6
|
+
import { runMcpCommand } from "./commands/mcp.js";
|
|
6
7
|
import { loadSessionById } from "./core/sessions.js";
|
|
7
8
|
import { clearUpdateCache, compareVersions, fetchLatestNpmVersion, getGlobalInstallRoot, getUpdateInfo, isGlobalInstall, runNpmUpdate, } from "./utils/updateCheck.js";
|
|
8
9
|
const PACKAGE_NAME = "@matterailab/orbcode";
|
|
@@ -15,6 +16,9 @@ Usage:
|
|
|
15
16
|
orbcode login sign in to MatterAI
|
|
16
17
|
orbcode update install the latest version from npm
|
|
17
18
|
orbcode update --force force a global install even if this CLI doesn't look global
|
|
19
|
+
orbcode mcp add ... add an MCP server (see: orbcode mcp help)
|
|
20
|
+
orbcode mcp remove ... remove an MCP server
|
|
21
|
+
orbcode mcp list list configured MCP servers
|
|
18
22
|
orbcode -p "<prompt>" run a single prompt non-interactively (prints only the final response)
|
|
19
23
|
orbcode -p "…" --yolo non-interactive with auto-approved edits/commands
|
|
20
24
|
orbcode --model <id> use a specific model for this run
|
|
@@ -86,6 +90,11 @@ async function main() {
|
|
|
86
90
|
const code = await runUpdate(force);
|
|
87
91
|
process.exit(code);
|
|
88
92
|
}
|
|
93
|
+
// `orbcode mcp ...` — manage MCP servers from the command line (add/remove/list).
|
|
94
|
+
if (args[0] === "mcp") {
|
|
95
|
+
const code = await runMcpCommand(args.slice(1));
|
|
96
|
+
process.exit(code);
|
|
97
|
+
}
|
|
89
98
|
const model = takeFlagValue(args, "model") ?? takeFlagValue(args, "m");
|
|
90
99
|
if (model) {
|
|
91
100
|
// loadSettings() treats MATTERAI_MODEL as the highest-precedence override,
|