@matterailab/orbcode 0.2.2 → 0.2.4

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 CHANGED
@@ -36,6 +36,9 @@ activity rows, edit/command approvals, and todo tracking.
36
36
  - [Headless mode](#headless-mode)
37
37
  - [Configuration](#configuration)
38
38
  - [Hooks](#hooks)
39
+ - [MCP servers](#mcp-servers)
40
+ - [Skills](#skills)
41
+ - [AGENTS.md memory](#agentsmd-memory)
39
42
  - [Architecture](#architecture)
40
43
  - [Tools](#tools)
41
44
  - [Agent loop](#agent-loop)
@@ -293,8 +296,9 @@ MatterAI gateway untouched.
293
296
  | `/compact` | summarize the conversation and replace history with the summary |
294
297
  | `/tasks` | print the current task list |
295
298
  | `/status` | version, model, account, gateway, context usage, cost, approval modes |
296
- | `/cost` | show session cost and fetch account balance |
299
+ | `/usage` | fetch plan usage |
297
300
  | `/init` | analyze the codebase and create/improve `AGENTS.md` |
301
+ | `/mcp` | manage MCP servers — enable, disable, reconnect, view status & tool counts |
298
302
  | `/login` | start the browser sign-in flow |
299
303
  | `/logout` | remove the saved token |
300
304
  | `/version` | print the CLI version |
@@ -486,6 +490,316 @@ untrusted.
486
490
  **→ Full reference with worked recipes for every event:
487
491
  [docs/HOOKS.md](https://github.com/MatterAIOrg/OrbCode/blob/main/docs/HOOKS.md).**
488
492
 
493
+ ## MCP servers
494
+
495
+ OrbCode connects to external tools via the **Model Context Protocol** (MCP). MCP
496
+ servers expose tools that appear alongside the native tools as
497
+ `mcp__<server>__<tool>` and can be called by the model like any other tool.
498
+
499
+ ### Configuration
500
+
501
+ MCP servers are configured in three scopes (highest precedence last):
502
+
503
+ 1. **User scope** — `mcpServers` in `~/.orbcode/settings.json`. Applies to every
504
+ project on this machine.
505
+ 2. **Project scope** — `.mcp.json` in the project root (and parent directories,
506
+ closer-to-cwd wins). This is the check-into-git, shared format, compatible
507
+ with Claude Code's `.mcp.json`.
508
+ 3. **Local scope** — `mcpServers` in `.orbcode/settings.json`. Per-project,
509
+ per-machine overrides (not checked in).
510
+
511
+ ### Adding servers from the command line
512
+
513
+ The `orbcode mcp` subcommand manages servers without editing JSON by hand —
514
+ like Claude Code's `claude mcp add/remove/list`:
515
+
516
+ ```bash
517
+ # stdio (default transport): name + command + args
518
+ orbcode mcp add filesystem npx -y @modelcontextprotocol/server-filesystem /Users/me/projects
519
+
520
+ # http transport
521
+ orbcode mcp add --transport http linear-server https://mcp.linear.app/mcp
522
+
523
+ # sse transport with a header
524
+ orbcode mcp add -t sse --header "Authorization=Bearer ${TOKEN}" my-sse https://example.com/sse
525
+
526
+ # http with OAuth (auth from /mcp after adding)
527
+ orbcode mcp add --transport http --oauth notion https://mcp.notion.com/mcp
528
+ orbcode mcp add -t http --oauth --oauth-scope "read:issues" linear https://mcp.linear.app/mcp
529
+
530
+ # stdio with env vars, written to user scope (~/.orbcode/settings.json)
531
+ orbcode mcp add -s user -e API_KEY=secret -e DEBUG=true my-server node server.js
532
+
533
+ # list all configured servers (name, scope, transport detail)
534
+ orbcode mcp list
535
+
536
+ # remove a server (finds it in whichever scope it lives)
537
+ orbcode mcp remove filesystem
538
+ ```
539
+
540
+ Flags go before the server name; everything after the name is the command +
541
+ args (so stdio servers can take their own flags like `-y`). Use `--` to force
542
+ the split if needed. `-s`/`--scope` selects `project` (default, writes
543
+ `.mcp.json`), `user` (writes `~/.orbcode/settings.json`), or `local` (writes
544
+ `.orbcode/settings.json`). Run `orbcode mcp help` for the full reference.
545
+
546
+ `add` and `remove` print the file they modified:
547
+
548
+ ```
549
+ $ orbcode mcp add --transport http linear-server https://mcp.linear.app/mcp
550
+ Added HTTP MCP server linear-server
551
+ URL: https://mcp.linear.app/mcp
552
+ Scope: project
553
+ File modified: /Users/me/my-project/.mcp.json
554
+ ```
555
+
556
+ OAuth servers show `needs-auth` after adding — they don't auto-open a browser.
557
+ Open `/mcp` in the TUI, select the server, and press `a` (or `1`) to
558
+ authenticate. OrbCode shows an auth screen:
559
+
560
+ ```
561
+ Authenticating with linear-server…
562
+
563
+ ✽ A browser window will open for authentication
564
+
565
+ If your browser doesn't open automatically, copy this URL manually (c to copy)
566
+ https://mcp.linear.app/authorize?response_type=code&client_id=…
567
+
568
+ If the redirect page shows a connection error, paste the URL from your browser's address bar:
569
+ URL> █
570
+
571
+ Return here after authenticating in your browser. Press Esc to go back.
572
+ ```
573
+
574
+ The browser opens automatically to the server's OAuth authorize page (RFC 9728
575
+ discovery → PKCE → redirect to a loopback callback → token exchange). Press `c`
576
+ to copy the URL to the clipboard if the browser doesn't open. If the redirect
577
+ fails (e.g. wrong port), press enter and paste the redirect URL from the
578
+ browser's address bar — OrbCode extracts the code from it. Either path
579
+ (callback or paste) completes the flow. Tokens persist under
580
+ `~/.orbcode/mcp-auth/<server>.json` (mode 0600) for future sessions; refresh
581
+ happens automatically when they expire. For M2M grants (`client_credentials`,
582
+ `private_key_jwt`), there's no browser — the token exchange is direct.
583
+
584
+ `.mcp.json` format (project scope):
585
+
586
+ ```json
587
+ {
588
+ "mcpServers": {
589
+ "filesystem": {
590
+ "command": "npx",
591
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"]
592
+ },
593
+ "github": {
594
+ "type": "http",
595
+ "url": "https://api.githubcopilot.com/mcp/",
596
+ "headers": { "Authorization": "Bearer ${GITHUB_TOKEN}" }
597
+ }
598
+ }
599
+ }
600
+ ```
601
+
602
+ `settings.json` `mcpServers` (user/local scope) uses the same per-server shape.
603
+ Environment variables (`${VAR}`) are expanded from `process.env`.
604
+
605
+ Three server types are supported:
606
+
607
+ - **stdio** (default, omit `type`): OrbCode spawns `command` with `args` and
608
+ talks over stdin/stdout. Optional `env` and `cwd`.
609
+ - **http**: Streamable HTTP transport. `url` + optional `headers` + optional
610
+ `oauth` (see [Authentication](#mcp-authentication)).
611
+ - **sse**: Server-Sent Events (legacy remote). `url` + optional `headers` +
612
+ optional `oauth`.
613
+
614
+ ### MCP authentication
615
+
616
+ Remote servers (http/sse) often require auth. OrbCode supports three ways:
617
+
618
+ **1. Static headers** (API keys, personal access tokens). Use `headers` with
619
+ `${ENV_VAR}` expansion — the token is read from the environment, never written
620
+ to disk:
621
+
622
+ ```json
623
+ {
624
+ "github": {
625
+ "type": "http",
626
+ "url": "https://api.githubcopilot.com/mcp/",
627
+ "headers": { "Authorization": "Bearer ${GITHUB_TOKEN}" }
628
+ }
629
+ }
630
+ ```
631
+
632
+ **2. OAuth 2.0 flows** (for servers that require user login: GitHub, Google
633
+ Drive, Slack, Notion, …). Set `oauth: true` (or `{ "scope": "..." }`) on an
634
+ http/sse server. OrbCode runs the full authorization-code flow via the SDK:
635
+ RFC 9728 protected-resource discovery, RFC 8414 authorization-server metadata,
636
+ PKCE, dynamic client registration, and token refresh. A one-shot loopback HTTP
637
+ server receives the browser redirect. Tokens, client info, code verifiers, and
638
+ discovery state are persisted per-server under `~/.orbcode/mcp-auth/<server>.json`
639
+ (mode 0600) so re-auth is only needed when a token expires or is revoked.
640
+
641
+ ```json
642
+ {
643
+ "notion": {
644
+ "type": "http",
645
+ "url": "https://mcp.notion.com/mcp",
646
+ "oauth": true
647
+ },
648
+ "google-drive": {
649
+ "type": "http",
650
+ "url": "https://mcp.google.com/drive",
651
+ "oauth": { "scope": "https://www.googleapis.com/auth/drive.readonly" }
652
+ }
653
+ }
654
+ ```
655
+
656
+ **3. Machine-to-machine OAuth** (no browser). For service-to-service auth,
657
+ use `client_credentials` or `private_key_jwt` grants — these use the SDK's
658
+ built-in `ClientCredentialsProvider` / `PrivateKeyJwtProvider`:
659
+
660
+ ```json
661
+ {
662
+ "internal-api": {
663
+ "type": "http",
664
+ "url": "https://internal.example.com/mcp",
665
+ "oauth": {
666
+ "grantType": "client_credentials",
667
+ "clientId": "orbcode-client",
668
+ "clientSecret": "${INTERNAL_CLIENT_SECRET}",
669
+ "scope": "mcp:tools"
670
+ }
671
+ },
672
+ "jwt-api": {
673
+ "type": "http",
674
+ "url": "https://jwt.example.com/mcp",
675
+ "oauth": {
676
+ "grantType": "private_key_jwt",
677
+ "clientId": "orbcode-client",
678
+ "privateKey": "${JWT_PRIVATE_KEY_PEM}",
679
+ "algorithm": "RS256"
680
+ }
681
+ }
682
+ }
683
+ ```
684
+
685
+ When a server needs auth, its status shows `needs-auth` in `/mcp`; press **a**
686
+ to re-authenticate (clears stored tokens and re-runs the flow). Secrets in
687
+ `oauth` blocks support `${ENV_VAR}` expansion like `headers`, so client
688
+ secrets and private keys can be sourced from the environment rather than
689
+ committed to `.mcp.json`.
690
+
691
+ ### Enabling & disabling servers
692
+
693
+ - **User/local-scope servers** connect automatically on startup (you wrote them,
694
+ so they're trusted).
695
+ - **Project-scope servers** (from `.mcp.json`) require a one-time approval: on
696
+ first launch in a project, OrbCode shows a checklist of detected servers.
697
+ Select the ones you trust; the decision is persisted to
698
+ `.orbcode/settings.json` (`enabledMcpServers` / `disabledMcpServers`) so they
699
+ auto-connect on future sessions.
700
+
701
+ The `/mcp` command opens an interactive manager at any time:
702
+
703
+ - ↑/↓ to select a server. The selected server shows a detail panel with
704
+ **Status**, **Auth** (authenticated / not authenticated / static headers),
705
+ **URL** (or stdio command), **Config location** (the file path), and a
706
+ numbered action list.
707
+ - **enter** or **2** toggles enable/disable. **r** or **3** reconnects. **a**
708
+ or **1** authenticates a `needs-auth` OAuth server (opens a browser for the
709
+ authorization-code flow; M2M grants exchange directly). **esc** closes.
710
+ - Each server shows a live status icon: `✓ connected`, `△ needs-auth`,
711
+ `✗ failed`, `○ disabled`, `⋯ connecting`, plus a tool count when connected.
712
+ - Enable/disable choices are persisted per-project. OAuth tokens are persisted
713
+ per-server under `~/.orbcode/mcp-auth/`.
714
+
715
+ ### Headless mode
716
+
717
+ In `orbcode -p`, there's no interactive approval, so project-scope servers are
718
+ only connected if they were previously approved (via an interactive `/mcp`
719
+ session). Unapproved project servers are skipped with a stderr note. User/local
720
+ servers connect as usual.
721
+
722
+ ## Skills
723
+
724
+ Skills are reusable instruction sets the model can load on demand. A skill is a
725
+ directory containing a `SKILL.md` file with optional YAML frontmatter and a
726
+ markdown body of specialized instructions.
727
+
728
+ ### Creating a skill
729
+
730
+ Place a directory under `~/.orbcode/skills/` (user, applies everywhere) or
731
+ `.orbcode/skills/` (project, checked into the repo):
732
+
733
+ ```
734
+ ~/.orbcode/skills/
735
+ my-skill/
736
+ SKILL.md
737
+ ```
738
+
739
+ `SKILL.md` format:
740
+
741
+ ```markdown
742
+ ---
743
+ description: Write concise, idiomatic Go code following project conventions
744
+ when_to_use: the task involves writing or reviewing Go code
745
+ ---
746
+
747
+ # Go style skill
748
+
749
+ When writing Go in this project:
750
+ - Use `errors.Join` for multi-error aggregation
751
+ - Prefer table-driven tests
752
+ - ...
753
+ ```
754
+
755
+ - `description` (frontmatter or first paragraph): shown in the skill catalog.
756
+ - `when_to_use` (frontmatter): a hint telling the model when to invoke this skill.
757
+ - `${SKILL_DIR}` in the body is replaced with the skill's absolute directory
758
+ path, so you can reference bundled scripts.
759
+
760
+ ### How skills are used
761
+
762
+ The skill catalog (names + descriptions + when-to-use) is injected into the
763
+ system prompt. When a task matches a skill's `when_to_use` condition, the model
764
+ calls the `use_skill` tool with the skill's name, which loads the full
765
+ instructions into context. Project skills override user skills on name
766
+ collisions (closer-to-cwd wins).
767
+
768
+ ## AGENTS.md memory
769
+
770
+ AGENTS.md files provide project- and user-level instructions that are injected
771
+ into every system prompt — build commands, code style, architecture notes,
772
+ conventions. This is OrbCode's equivalent of Claude Code's `CLAUDE.md`, using
773
+ the open `AGENTS.md` filename so it works across tools.
774
+
775
+ ### Discovery
776
+
777
+ Files are loaded in this order (lowest precedence first; higher-precedence files
778
+ appear later and get more weight):
779
+
780
+ 1. **User memory**: `~/.orbcode/AGENTS.md` — personal global instructions.
781
+ 2. **Project memory**: `AGENTS.md` and `.orbcode/AGENTS.md` in the cwd and every
782
+ parent directory (closer-to-cwd wins). Checked into the repo, shared with the
783
+ team.
784
+ 3. **Local memory**: `AGENTS.local.md` in the cwd and parents — private
785
+ per-machine overrides (gitignore this).
786
+
787
+ ### @include directives
788
+
789
+ An AGENTS.md file can include other files with `@path` references:
790
+
791
+ ```markdown
792
+ # Project guide
793
+
794
+ See the detailed style guide: @./docs/style.md
795
+ And the global one: @~/orbcode-global.md
796
+ ```
797
+
798
+ `@path` (relative to the including file), `@./path`, `@~/path`, and `@/abs/path`
799
+ are all supported. Includes are resolved recursively (up to 5 levels, with cycle
800
+ detection). Use `/init` to have OrbCode generate a starter `AGENTS.md` by
801
+ analyzing the codebase.
802
+
489
803
  ## Architecture
490
804
 
491
805
  ```
@@ -502,23 +816,50 @@ src/
502
816
  stream.ts chunk model: text / reasoning / native_tool_calls / usage
503
817
  headers.ts X-AxonCode-Version, X-AxonCode-TaskId, X-AXON-REPO, …
504
818
  prompts/system.ts system prompt: agent roleDefinition + tool guide (ported
505
- verbatim from the extension) + CLI system-info section
819
+ verbatim from the extension) + CLI system-info section +
820
+ AGENTS.md memory + skills catalog injection
506
821
  tools/
507
822
  schemas/ native-tools JSON schemas, copied verbatim from the extension
508
- executors/ CLI implementations (fs, child_process, search, web)
509
- index.ts dispatch, approval classification, call summaries
823
+ executors/ CLI implementations (fs, child_process, search, web, skills)
824
+ index.ts dispatch, approval classification, call summaries, MCP routing
510
825
  core/
511
- agent.ts the agent loop (see below)
826
+ agent.ts the agent loop (see below); owns the McpManager
512
827
  events.ts AgentEvent model consumed by the UI
513
828
  hooks.ts lifecycle hooks engine, Claude-Code compatible (see Hooks)
829
+ mcp/
830
+ types.ts MCP server config + connection state types
831
+ config.ts .mcp.json + settings.json mcpServers loader (3-scope merge)
832
+ client.ts SDK transport (stdio/http/sse) + tool enum + tool call
833
+ manager.ts McpManager: connection lifecycle, enable/disable, tool routing
834
+ skills/
835
+ types.ts Skill + frontmatter types
836
+ loader.ts ~/.orbcode/skills + .orbcode/skills discovery (SKILL.md format)
837
+ memory/
838
+ types.ts MemoryFile type
839
+ loader.ts AGENTS.md discovery (user/project/local) + @include resolution
514
840
  ui/
515
841
  App.tsx main Ink app: static finalized rows + dynamic streaming area
516
842
  LoginView.tsx device-flow login screen with paste fallback
517
843
  components/ Header, InputBox, rows, ApprovalPrompt, FollowupPrompt,
518
- Spinner, StatusBar
844
+ HookTrustPrompt, McpApprovalPrompt, McpPicker, ModelPicker,
845
+ SessionPicker, Spinner, StatusBar
519
846
  markdown.ts markdown → ANSI renderer
520
847
  ```
521
848
 
849
+ **MCP integration**: the `McpManager` (owned by the agent) loads the merged
850
+ config on `start()`, connects to approved servers in parallel, and exposes
851
+ their tools as `mcp__<server>__<tool>` definitions alongside the native tools.
852
+ The agent routes any `mcp__*` tool call to the manager, which dispatches it to
853
+ the right SDK client. Project-scope servers (from `.mcp.json`) require a
854
+ one-time approval; the TUI shows a checklist at startup and `/mcp` manages them
855
+ at runtime. Connections are torn down on session end.
856
+
857
+ **Skills & memory**: on agent construction, `loadMemoryFiles()` walks the
858
+ AGENTS.md discovery chain (user → project → local, with `@include` resolution)
859
+ and `loadSkills()` walks the skills directories. Both are injected into the
860
+ system prompt so the model sees project instructions and the skill catalog on
861
+ every turn. The `use_skill` tool loads a skill's full body on demand.
862
+
522
863
  **Streaming** faithfully ports the extension's handler quirks: cumulative
523
864
  content dedup (some backends re-send full content), `<think>` blocks routed to
524
865
  reasoning, both `reasoning` and `reasoning_content` delta fields, tool-call
@@ -535,7 +876,7 @@ extension). It shows in the status bar, is written into the session file (so
535
876
  `/resume` lists real titles), and becomes the terminal window title:
536
877
  `<title> (orbcode)`.
537
878
 
538
- **Usage data**: `/status` and `/cost` fetch `/axoncode/profile` and show the
879
+ **Usage data**: `/status` and `/usage` fetch `/axoncode/profile` and show the
539
880
  plan, usage percentage (used/remaining), remaining reviews, and the credits
540
881
  reset date — the same data as the extension's profile view.
541
882
 
@@ -554,11 +895,13 @@ Active in the CLI (schemas byte-identical to the extension's `native-tools`):
554
895
  | `execute_command` | user's shell, 120s timeout, 30k output cap, optional cwd |
555
896
  | `web_search` / `web_fetch` | proxied through the MatterAI backend with your token |
556
897
  | `update_todo_list` | drives the TUI todo panel |
898
+ | `use_skill` | loads a skill's full instructions from ~/.orbcode/skills or .orbcode/skills |
557
899
  | `ask_followup_question` | interactive menu in the TUI |
558
900
  | `attempt_completion` | ends the turn with a completion card |
901
+ | `mcp__<server>__<tool>` | any tool exposed by a connected MCP server (see [MCP servers](#mcp-servers)) |
559
902
 
560
903
  Present in `tools/schemas/` but **inactive** (need IDE services): `codebase_search`,
561
- `lsp`, `list_code_definition_names`, `use_skill`, `check_past_chat_memories`,
904
+ `lsp`, `list_code_definition_names`, `check_past_chat_memories`,
562
905
  `browser_action`, `generate_image`, `new_task`, `switch_mode`,
563
906
  `fetch_instructions`, `run_slash_command`.
564
907
 
@@ -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
+ }