Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
# macOS
.DS_Store
todo.md

# Cache
__pycache__/

# Claude specific exclusions
.claude/
glean_code/__pycache__/
.claude/settings.json

# Miscellaneous git templates and cmds
git/

# Never commit secrets (tokens may match GLEAN_* or glean_tok_* patterns)
Expand Down
39 changes: 39 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,45 @@ For what Glean Code is and how to run it, see the [README](README.md).
commands, status bar, mock/live switching, secure-token storage) into the editor
sidebar. In progress.

## 2026-08-18

### Added

- **`/mcp`** — inspect, configure, and run the bundled MCP server without leaving the REPL.
`/mcp status` reports the installed `mcp` version, whether it can actually run the server,
and any running instance's pid, URL, uptime, and mode. `/mcp config [client]` prints the
paste-ready JSON for Claude Code, Claude Desktop, or Cursor. `/mcp start` runs the server
detached over HTTP and `/mcp stop` terminates it. Documented in
[docs/COMMANDS.md](docs/COMMANDS.md) and [docs/MCP.md](docs/MCP.md).
- **`--name` on `/mcp config`** — the emitted block keys the server as `glean` by default,
the same name Glean's own hosted MCP server would use. `--name glean-cli` keeps both
registered; the default form warns that pasting replaces an existing entry.
- **Transport flags on `glean_mcp.py`** — `--transport stdio|sse|streamable-http`, `--host`,
and `--port`. stdio remains the default and is what MCP clients spawn; the HTTP transports
exist so the server can run detached, since a stdio server started from the REPL would have
no client on the other end of its pipes.
- **`SUPPORT.md`** — best-effort support expectations, triage order, and what makes a bug
report actionable. Surfaced by GitHub in the new-issue chooser.

### Changed

- **The macOS app bundle is now `Glean Code.app`**, not `Glean.app` — the latter is Glean's
own desktop client (`com.glean.desktop`). The installer reads `CFBundleIdentifier` before
writing and refuses a bundle it did not create; `--uninstall` skips one for the same reason.
A pre-existing `Glean.app` that we own is replaced on the next install.
- **CI workflow renamed** from `tests.yml` to `release.yml`, and it now publishes the built
zipapp as a downloadable workflow artifact.

### Fixed

- **`pip install "mcp[cli]"` broke fresh installs.** The MCP SDK's 2.0.0 release renamed
`FastMCP` to `MCPServer` and removed the `mcp.server.fastmcp` module `glean_mcp.py` imports,
so an unpinned install resolved to 2.x and failed on import. Install instructions now pin
`mcp[cli]>=1,<2`, and the import guard distinguishes "not installed" from "installed but
incompatible" instead of advising a reinstall of the version that just broke.
- **`--uninstall` could delete a user's Glean Desktop installation** — it called `rmtree` on
the app path with no ownership check.

## 2026-08-14

### Added
Expand Down
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ A native VS Code extension that brings the full Glean Code REPL — slash comman

| Area | Commands |
| --- | --- |
| Shell | `/help` `/status` `/doctor` `/auth` `/login` `/logout` `/open` `/ask` `/config` `/mode` `/history` `/clear` `/exit` |
| Shell | `/help` `/status` `/doctor` `/auth` `/login` `/logout` `/open` `/ask` `/config` `/mode` `/mcp` `/history` `/clear` `/exit` |
| Chat and search | `/chat` `/search` `/autocomplete` `/recommendations` `/feedback` `/datasources.list` |
| Indexing — read & debug | `/datasources.status` `/datasources.config` `/documents.status` `/documents.count` `/users.count` `/documents.access` `/debug.document` `/debug.documents` `/debug.user` `/indexing.rotate-token` |
| Indexing — single write | `/index.document` `/index.permissions` `/index.user` `/index.group` `/index.membership` and their `/index.delete-*` partners |
Expand Down Expand Up @@ -242,6 +242,10 @@ Requires Python 3.10+ and the **v1 line** of the `mcp` package — install it as
server imports, so an unpinned install breaks it ([details](docs/MCP.md#mcp-sdk-v2)). The
REPL itself remains Python 3.9+ and stdlib-only.

`/mcp` drives it from inside the REPL — `/mcp status` for version and health, `/mcp config
<client>` for the paste-ready JSON, and `/mcp start` to run one detached over HTTP when you
want a server that isn't owned by a client.

Setup for all three clients, the tool table, and the mock-mode rationale: **[docs/MCP.md](docs/MCP.md)**.

## Project layout
Expand All @@ -257,6 +261,7 @@ glean-code-cli/
client.py Glean REST wrapper + mock responses
config.py config file load and save
help_docs.py per-command documentation
mcp_control.py /mcp — MCP server diagnostics and process control
mock_corpus.py the fake corpus every mock endpoint reads from
_indexing_walk.py --path file walking for indexing commands
completion.py readline tab completion
Expand All @@ -265,7 +270,7 @@ glean-code-cli/
auth_commands.py /auth command handlers
auth/ OAuth 2.1 + PKCE: oauth, pkce, callback_server,
token_store, manager
tests/ 16 test modules, stdlib unittest only
tests/ 17 test modules, stdlib unittest only
docs/ full reference set — see below
```

Expand All @@ -290,7 +295,7 @@ files, and they outrank the `Glean Code.app` launcher in `Cmd+Space`:
export PYTHONPYCACHEPREFIX="$HOME/.cache/python"
```

740 tests covering the client and every mock response, commands and dispatch, config, UI, auth, completion, help docs, the mock corpus, indexing-walk, scaffold, the installer, and the MCP server. Development notes: [docs/TESTING.md](docs/TESTING.md).
776 tests covering the client and every mock response, commands and dispatch, config, UI, auth, completion, help docs, the mock corpus, indexing-walk, scaffold, the installer, and the MCP server. Development notes: [docs/TESTING.md](docs/TESTING.md).

## Documentation

Expand Down
44 changes: 44 additions & 0 deletions docs/COMMANDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,50 @@ Quickly switch the API mode without editing config.

---

#### /mcp

Inspect, configure, and run the bundled MCP server without leaving the REPL.

```text
/mcp <status|config|start|stop> [client] [--url] [--stdio] [--name <key>] [--port <n>] [--host <addr>] [--transport <t>] [--mock]
```

| Subcommand | Description |
| --- | --- |
| `status` | Installed `mcp` version and whether it can run the server, plus the running server's pid, URL, uptime, and mode. The default when no subcommand is given. |
| `config` | Print the JSON block to paste into a client. Optional client name: `claude-code`, `claude-desktop`, `cursor`. |
| `start` | Run the server detached over HTTP and record where it landed. |
| `stop` | Terminate the server `start` launched. |

| Flag | Description |
| --- | --- |
| `--url` | `config`: emit the URL form pointing at the running server. |
| `--stdio` | `config`: force the command form even while a server is running. |
| `--name` | `config`: key under `mcpServers`. Default `glean`. |
| `--port` | `start`: bind port. Default `8787`. |
| `--host` | `start`: bind address. Default `127.0.0.1`. |
| `--transport` | `start`: `streamable-http` (default) or `sse`. |
| `--mock` | `start`: serve the built-in corpus. Implied when the REPL is in mock mode. |

```text
/mcp status
/mcp config claude-code
/mcp config --name glean-cli
/mcp start --port 9000
/mcp config --url
/mcp stop
```

**Output** — `status` prints a key/value table; `config` prints JSON; `start` and `stop` confirm with the pid and URL.

**`start` cannot use stdio, by design.** stdio is the transport an MCP client spawns for itself over a pipe pair — started from the REPL it would have no client on the other end. `/mcp start` therefore runs `streamable-http` (or `sse`), which a client attaches to by URL. For the ordinary setup, use `/mcp config` and let the client spawn the server. See [docs/MCP.md](MCP.md#starting-a-server-from-the-repl).

**Name collisions.** The emitted block keys the server as `glean` under `mcpServers`. Glean's own hosted MCP server would naturally be registered under that name too, and pasting over it silently swaps the toolset with no error. Use `--name glean-cli` to run both side by side.

**Endpoint** — `(local — spawns glean_mcp.py)`

---

#### /ask

Translate a natural-language request into a sequence of Glean Code slash commands using Glean Assistant as the planner. Read [docs/NATURAL_LANGUAGE.md](NATURAL_LANGUAGE.md) for the full design.
Expand Down
72 changes: 72 additions & 0 deletions docs/MCP.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,78 @@ flip an agent onto fake data the day a token expires.

Requires Python 3.10+. The REPL itself remains Python 3.9+ and stdlib-only.

## Starting a server from the REPL

The setups above are the normal ones: the client owns the server's lifecycle, spawning
`glean_mcp.py` over **stdio** whenever it needs it. Nothing to start by hand.

When you do want a server running independently — to point several clients at one process, to
watch its log while you work, or to check the thing comes up at all — `/mcp` handles it from
inside `glean`:

```text
/mcp status # mcp version, compatibility, and any running server
/mcp config claude-code # the JSON block to paste, per client
/mcp start --port 9000 # run it detached over HTTP
/mcp config --url # the JSON block pointing at that running server
/mcp stop
```

```text
── mcp ─────────────────────────────────────────────────────────────
mcp package 1.29.0 (v1 line, compatible)
server script glean_mcp.py
server running pid 73343, up 4m
endpoint http://127.0.0.1:8791/mcp
would serve mock [MOCK MODE banner active]
tools search, chat, list_agents, run_agent
log ~/.gleancode/mcp.log
────────────────────────────────────────────────────────────────────
```

**`/mcp start` never uses stdio.** stdio is a pipe pair between a client and the server it
spawned; started from the REPL there would be no client on the other end, and the REPL already
owns its own stdin and stdout. `start` therefore runs `streamable-http` (default) or `sse`,
which a client attaches to by URL — `/mcp config --url` prints that form. This only helps if
your client supports URL-based servers; for a client that spawns commands, use the stdio form.

Details worth knowing:

- **Binds `127.0.0.1` by default.** A live server holds whatever credentials your config has.
`--host` can widen that; think before it does.
- **The server outlives the REPL.** It is started in its own session, so `/exit` leaves it
running. State lives in `~/.gleancode/mcp.json`, which is how a later `glean` session still
finds it, and stale entries are cleared automatically when the process is gone.
- **Mock mode is inherited.** Start it while the REPL is in mock mode (or pass `--mock`) and
the server serves the built-in corpus with the `[MOCK MODE]` banner on every response.
- **Logs go to `~/.gleancode/mcp.log`.** A server that dies on startup — a port already in
use, most often — is reported immediately, with the log path for the detail.

### Sitting alongside Glean's own MCP server

Glean ships its own hosted MCP server. It is a different thing from this one: it talks to your
tenant directly, while `glean_mcp.py` wraps the Client REST API and adds mock mode. `/mcp` only
manages this repo's server — it never detects, starts, or talks to Glean's.

They collide in exactly one place. Both would naturally be registered under the key `glean`:

```json
{ "mcpServers": { "glean": { ... } } }
```

Pasting one over the other **silently replaces it** — no error, you simply end up with a
different set of tools than you expected. Use a distinct key to keep both:

```text
/mcp config --name glean-cli
```

```json
{ "mcpServers": { "glean-cli": { "command": "python3", "args": ["…/glean_mcp.py"] } } }
```

`/mcp config` warns about this whenever it emits the default name.

## MCP SDK v2

`glean_mcp.py` targets the **v1 line** of the `mcp` SDK. Install it with an upper bound:
Expand Down
14 changes: 13 additions & 1 deletion docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Notes on the test suite added during development of glean-code-cli. See [Running tests](../README.md#running-tests) for the user-facing instructions on how to run the tests.

All 740 tests pass. Here's what was added across the development passes:
All 776 tests pass. Here's what was added across the development passes:

`tests/test_commands_extended.py` (155 new tests) — covers all previously untested commands:

Expand Down Expand Up @@ -61,3 +61,15 @@ All 740 tests pass. Here's what was added across the development passes:
- `_build_client` — forces live mode whatever `mode` the config file carries (including `auto`), and switches to mock only when `GLEAN_MOCK` is set; truthy spellings (`1`, `true`, `yes`, `on`) accepted, everything else ignored
- Labelling — all four tools (`search`, `chat`, `list_agents`, `run_agent`) prefix their response with the `[MOCK MODE]` banner when serving fake data, including empty-result responses, and never in live mode
- Tool descriptions — every tool docstring names `GLEAN_MOCK`, so the warning reaches the agent before it calls anything

`tests/test_mcp_control.py` (36 new tests) — covers `/mcp` server control:

- Package diagnostics — reports the installed `mcp` version and whether it can actually run the server (v1 provides `mcp.server.fastmcp`; v2 does not)
- State file — roundtrip, unreadable-file tolerance, and clearing a stale entry when the recorded pid is dead or has been reused by another process
- `start` refusals — stdio (which needs a client on the other end), unknown transports, missing package, an incompatible v2 install named by version, and a second start while one is running
- `stop` — signals and clears state; a no-op when nothing is running
- Client config — the stdio command form and the URL form, both JSON-serialisable
- Defaults — loopback-only bind, non-stdio default transport, and tool names checked against `glean_mcp.py` itself
- Command dispatch — bare `/mcp` shows status, unknown subcommands and clients error, `--url` without a server errors, a non-numeric `--port` errors

Every test redirects the state and log paths at a temp directory, so `~/.gleancode/` is never touched.
Loading
Loading