lcode-cli 0.3.1__tar.gz → 0.4.0__tar.gz
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.
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/CHANGELOG.md +27 -1
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/PKG-INFO +5 -2
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/README.md +4 -1
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/SECURITY.md +6 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/docs/configuration.md +4 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/docs/how-it-works.md +1 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/docs/index.md +7 -0
- lcode_cli-0.4.0/docs/mcp.md +216 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/docs/quickstart.md +1 -1
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/docs/troubleshooting.md +6 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/docs/usage.md +3 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/mkdocs.yml +1 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/src/lcode/__init__.py +1 -1
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/src/lcode/agent.py +42 -3
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/src/lcode/bench.py +27 -25
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/src/lcode/cli.py +54 -9
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/src/lcode/config.py +4 -0
- lcode_cli-0.4.0/src/lcode/mcp/__init__.py +5 -0
- lcode_cli-0.4.0/src/lcode/mcp/auth.py +392 -0
- lcode_cli-0.4.0/src/lcode/mcp/catalog.py +109 -0
- lcode_cli-0.4.0/src/lcode/mcp/catalog.toml +198 -0
- lcode_cli-0.4.0/src/lcode/mcp/client.py +165 -0
- lcode_cli-0.4.0/src/lcode/mcp/commands.py +403 -0
- lcode_cli-0.4.0/src/lcode/mcp/config.py +221 -0
- lcode_cli-0.4.0/src/lcode/mcp/http.py +161 -0
- lcode_cli-0.4.0/src/lcode/mcp/manager.py +360 -0
- lcode_cli-0.4.0/src/lcode/mcp/protocol.py +124 -0
- lcode_cli-0.4.0/src/lcode/mcp/stdio.py +153 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/src/lcode/permissions.py +3 -1
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/src/lcode/repl.py +52 -1
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/src/lcode/tools.py +27 -0
- lcode_cli-0.4.0/tests/fake_mcp_server.py +113 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/tests/test_bench.py +15 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/tests/test_cli.py +25 -0
- lcode_cli-0.4.0/tests/test_mcp.py +524 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/.editorconfig +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/.github/CODEOWNERS +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/.github/ISSUE_TEMPLATE/model_request.yml +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/.github/dependabot.yml +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/.github/workflows/ci.yml +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/.github/workflows/docs.yml +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/.github/workflows/release.yml +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/.gitignore +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/.pre-commit-config.yaml +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/AGENTS.md +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/CODE_OF_CONDUCT.md +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/CONTRIBUTING.md +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/LICENSE +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/docs/assets/demo.svg +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/docs/assets/extra.css +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/docs/assets/logo.svg +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/docs/assets/social-preview.png +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/docs/bench.md +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/docs/changelog.md +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/docs/contributing.md +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/docs/installation.md +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/docs/models.md +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/install.sh +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/overrides/main.html +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/pyproject.toml +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/scripts/record_demo.py +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/scripts/social-preview.html +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/src/lcode/__main__.py +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/src/lcode/catalog.py +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/src/lcode/checkpoints.py +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/src/lcode/hardware.py +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/src/lcode/limits.py +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/src/lcode/models.toml +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/src/lcode/ollama.py +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/src/lcode/render.py +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/src/lcode/sessions.py +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/src/lcode/web.py +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/tests/conftest.py +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/tests/test_agent.py +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/tests/test_catalog.py +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/tests/test_checkpoints.py +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/tests/test_config.py +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/tests/test_context_picker.py +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/tests/test_permissions.py +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/tests/test_sessions.py +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/tests/test_tools.py +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/tests/test_web.py +0 -0
- {lcode_cli-0.3.1 → lcode_cli-0.4.0}/uv.lock +0 -0
|
@@ -6,6 +6,31 @@ All notable changes to lcode are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.4.0] - 2026-10-01
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- MCP (Model Context Protocol) servers: the model can use tools from Jira, GitHub, AWS, databases and
|
|
14
|
+
more, with your approval for every call.
|
|
15
|
+
- `lcode mcp add <name>` sets up a ready-made server and tests it: `atlassian`, `aws`,
|
|
16
|
+
`aws-knowledge`, `google-drive`, `grafana`, `gcp`, `github`, `playwright`, `context7`, `sentry`,
|
|
17
|
+
`postgres`, `kubernetes`, `linear` and `notion` (`lcode mcp catalog` lists them).
|
|
18
|
+
- Any other server works too, local (stdio) or remote (Streamable HTTP), configured in
|
|
19
|
+
`~/.config/lcode/mcp.json` in the standard `mcpServers` format, or per project in `.mcp.json`
|
|
20
|
+
(used only after you approve it).
|
|
21
|
+
- Browser sign-in (OAuth 2.1 with PKCE and dynamic client registration) with automatic token
|
|
22
|
+
refresh; `lcode mcp login/logout`.
|
|
23
|
+
- Speaks both the current MCP protocol (2026-07-28) and the earlier `initialize`-based versions.
|
|
24
|
+
- When the tool definitions would take more than 15% of the context window, the model finds tools
|
|
25
|
+
on demand instead (`mcp_tools` setting). `/mcp` shows servers, tools and their context cost;
|
|
26
|
+
`lcode --no-mcp` starts without them.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- `lcode bench`: stopping a run with Ctrl+C no longer leaves the running task's temporary folder
|
|
31
|
+
behind, and the summary marks the model as stopped.
|
|
32
|
+
- `lcode bench` no longer lists the model it's about to benchmark as "already loaded".
|
|
33
|
+
|
|
9
34
|
## [0.3.1] - 2026-09-30
|
|
10
35
|
|
|
11
36
|
### Added
|
|
@@ -140,7 +165,8 @@ First public release.
|
|
|
140
165
|
- `AGENTS.md` project instructions and `/init` to generate them.
|
|
141
166
|
- One-line installer for Linux and macOS.
|
|
142
167
|
|
|
143
|
-
[Unreleased]: https://github.com/nasser1941/lcode/compare/v0.
|
|
168
|
+
[Unreleased]: https://github.com/nasser1941/lcode/compare/v0.4.0...HEAD
|
|
169
|
+
[0.4.0]: https://github.com/nasser1941/lcode/compare/v0.3.1...v0.4.0
|
|
144
170
|
[0.3.1]: https://github.com/nasser1941/lcode/compare/v0.3.0...v0.3.1
|
|
145
171
|
[0.3.0]: https://github.com/nasser1941/lcode/compare/v0.2.2...v0.3.0
|
|
146
172
|
[0.2.2]: https://github.com/nasser1941/lcode/compare/v0.2.1...v0.2.2
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: lcode-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: A local-first terminal coding agent powered by open-weight models on your own GPU or Mac, via Ollama.
|
|
5
5
|
Project-URL: Homepage, https://nasser1941.github.io/lcode/
|
|
6
6
|
Project-URL: Documentation, https://nasser1941.github.io/lcode/
|
|
@@ -81,6 +81,9 @@ through [Ollama](https://ollama.com), and when it needs current information it c
|
|
|
81
81
|
request's edits, new files and shell-command changes, without touching your git history.
|
|
82
82
|
- **Bring your own model.** Eight curated open-weight models, all tested end to end, or any Ollama model
|
|
83
83
|
with tool calling.
|
|
84
|
+
- **MCP servers, ready to go.** Connect Jira and Confluence, GitHub, AWS, Google Drive, Grafana,
|
|
85
|
+
Google Cloud, Sentry, Linear, Notion, Postgres, Kubernetes and more with one command
|
|
86
|
+
(`lcode mcp add atlassian`), or any other MCP server. Browser sign-in (OAuth) is built in.
|
|
84
87
|
- **Web search when needed.** Looks up the latest versions, docs and error messages with Ollama web
|
|
85
88
|
search, Brave, Tavily or your own SearXNG, and reads pages as clean text.
|
|
86
89
|
- **Private.** The model runs on your machine, lcode never uploads your files and has no telemetry.
|
|
@@ -128,6 +131,7 @@ lcode
|
|
|
128
131
|
| `lcode models` / `lcode doctor` | what fits this machine / check the installation |
|
|
129
132
|
| `lcode bench qwen3.6-35b qwen3.5-9b` | compare models on small coding tasks on this machine |
|
|
130
133
|
| `/undo`, `/rewind` | take back the last request's file changes, or go back further |
|
|
134
|
+
| `lcode mcp catalog` / `lcode mcp add github` | ready-made MCP servers / add one |
|
|
131
135
|
| `/rename`, `/resume` | name the current session, resume a saved one |
|
|
132
136
|
| `/model`, `/context`, `/compact`, `/help` | switch model, resize the context window (pick from a list), summarize, list commands |
|
|
133
137
|
|
|
@@ -172,7 +176,6 @@ fills up. Details: [How it works](https://nasser1941.github.io/lcode/how-it-work
|
|
|
172
176
|
|
|
173
177
|
See the pinned [Roadmap issue](https://github.com/nasser1941/lcode/issues/10). Next up:
|
|
174
178
|
|
|
175
|
-
- [MCP tool servers](https://github.com/nasser1941/lcode/issues/6)
|
|
176
179
|
- [Homebrew formula](https://github.com/nasser1941/lcode/issues/8) and an [optional sandbox for shell commands](https://github.com/nasser1941/lcode/issues/9)
|
|
177
180
|
- More models in the catalog, and test reports from your hardware ([report one](https://github.com/nasser1941/lcode/issues/new?template=model_request.yml))
|
|
178
181
|
|
|
@@ -48,6 +48,9 @@ through [Ollama](https://ollama.com), and when it needs current information it c
|
|
|
48
48
|
request's edits, new files and shell-command changes, without touching your git history.
|
|
49
49
|
- **Bring your own model.** Eight curated open-weight models, all tested end to end, or any Ollama model
|
|
50
50
|
with tool calling.
|
|
51
|
+
- **MCP servers, ready to go.** Connect Jira and Confluence, GitHub, AWS, Google Drive, Grafana,
|
|
52
|
+
Google Cloud, Sentry, Linear, Notion, Postgres, Kubernetes and more with one command
|
|
53
|
+
(`lcode mcp add atlassian`), or any other MCP server. Browser sign-in (OAuth) is built in.
|
|
51
54
|
- **Web search when needed.** Looks up the latest versions, docs and error messages with Ollama web
|
|
52
55
|
search, Brave, Tavily or your own SearXNG, and reads pages as clean text.
|
|
53
56
|
- **Private.** The model runs on your machine, lcode never uploads your files and has no telemetry.
|
|
@@ -95,6 +98,7 @@ lcode
|
|
|
95
98
|
| `lcode models` / `lcode doctor` | what fits this machine / check the installation |
|
|
96
99
|
| `lcode bench qwen3.6-35b qwen3.5-9b` | compare models on small coding tasks on this machine |
|
|
97
100
|
| `/undo`, `/rewind` | take back the last request's file changes, or go back further |
|
|
101
|
+
| `lcode mcp catalog` / `lcode mcp add github` | ready-made MCP servers / add one |
|
|
98
102
|
| `/rename`, `/resume` | name the current session, resume a saved one |
|
|
99
103
|
| `/model`, `/context`, `/compact`, `/help` | switch model, resize the context window (pick from a list), summarize, list commands |
|
|
100
104
|
|
|
@@ -139,7 +143,6 @@ fills up. Details: [How it works](https://nasser1941.github.io/lcode/how-it-work
|
|
|
139
143
|
|
|
140
144
|
See the pinned [Roadmap issue](https://github.com/nasser1941/lcode/issues/10). Next up:
|
|
141
145
|
|
|
142
|
-
- [MCP tool servers](https://github.com/nasser1941/lcode/issues/6)
|
|
143
146
|
- [Homebrew formula](https://github.com/nasser1941/lcode/issues/8) and an [optional sandbox for shell commands](https://github.com/nasser1941/lcode/issues/9)
|
|
144
147
|
- More models in the catalog, and test reports from your hardware ([report one](https://github.com/nasser1941/lcode/issues/new?template=model_request.yml))
|
|
145
148
|
|
|
@@ -36,6 +36,12 @@ user, in your working directory**. It is not a sandbox. Keep in mind:
|
|
|
36
36
|
pages are downloaded from their websites. The model writes the queries, so they can contain names or
|
|
37
37
|
snippets from your code. Use `web = ask` to approve each search, or `web = off` / `--no-web` to
|
|
38
38
|
keep everything local.
|
|
39
|
+
- MCP servers you add run as your user (local servers) or act on your accounts (remote servers).
|
|
40
|
+
Every MCP tool call needs your approval unless you're in `yolo` mode or listed the tool under
|
|
41
|
+
`allow`. A project's `.mcp.json` is only used after you approve it, and again after it changes.
|
|
42
|
+
MCP tool results can contain prompt injections, like web pages. Tokens you enter are stored in
|
|
43
|
+
`~/.config/lcode/mcp.json` and sign-in tokens in `~/.local/state/lcode/mcp-auth/`, both readable
|
|
44
|
+
only by you.
|
|
39
45
|
- Sessions (including file contents the model read) are stored in `~/.local/state/lcode/sessions`.
|
|
40
46
|
Checkpoints for `/undo` keep copies of your project's files (except ignored ones) in
|
|
41
47
|
`~/.local/state/lcode/checkpoints` for 14 days; turn them off with `checkpoints = false`.
|
|
@@ -25,6 +25,7 @@ lcode config path # print the file location
|
|
|
25
25
|
| `web` | `on` | Web search and page fetching: `on`, `ask` (before each search/website) or `off` |
|
|
26
26
|
| `search_backend` | `auto` | `auto`, `ollama`, `brave`, `tavily` or `searxng`; `auto` uses the first one configured |
|
|
27
27
|
| `searxng_url` | | Your SearXNG instance, e.g. `http://localhost:8888` |
|
|
28
|
+
| `mcp_tools` | `auto` | How [MCP](mcp.md#context) tool definitions reach the model: `auto`, `direct` or `search` (on demand) |
|
|
28
29
|
| `checkpoints` | `true` | Save a checkpoint before the model changes files, so [`/undo`](usage.md#undo-and-checkpoints) can restore them |
|
|
29
30
|
|
|
30
31
|
Example file:
|
|
@@ -59,6 +60,9 @@ Environment variables override the file, which is useful for one-off runs and CI
|
|
|
59
60
|
| `~/.config/lcode/config.toml` | Settings |
|
|
60
61
|
| `~/.local/state/lcode/sessions/` | Saved conversations (for `lcode -c`) |
|
|
61
62
|
| `~/.local/state/lcode/history` | Prompt history (++up++ in the prompt) |
|
|
63
|
+
| `~/.config/lcode/mcp.json` | [MCP servers](mcp.md) (readable only by you; may contain tokens) |
|
|
64
|
+
| `~/.local/state/lcode/mcp-auth/` | Sign-in tokens for remote MCP servers (readable only by you) |
|
|
65
|
+
| `~/.local/state/lcode/mcp-logs/` | Error output of local MCP servers |
|
|
62
66
|
| `~/.local/state/lcode/checkpoints/` | Checkpoints for `/undo` (copies of your project's files; deleted after 14 days) |
|
|
63
67
|
| `~/.local/state/lcode/limits.json` | Context sizes that ran out of GPU memory on this machine (safe to delete) |
|
|
64
68
|
|
|
@@ -75,6 +75,7 @@ the Ollama server you configure, it only contacts the web when the model searche
|
|
|
75
75
|
| `agent.py` | System prompt, streaming, tool loop, compaction, sessions |
|
|
76
76
|
| `tools.py` | Tool schemas and implementations |
|
|
77
77
|
| `permissions.py` | Approval prompts and the read-only allowlist |
|
|
78
|
+
| `mcp/` | MCP client: stdio and HTTP transports, OAuth sign-in, server settings, the catalog, `lcode mcp` |
|
|
78
79
|
| `bench.py` | `lcode bench`: the benchmark tasks, their checks and the reports |
|
|
79
80
|
| `checkpoints.py` | Snapshots before the model changes files, for `/undo` and `/rewind` |
|
|
80
81
|
| `catalog.py`, `models.toml` | Model catalog and memory estimates |
|
|
@@ -69,6 +69,13 @@ until the task is done, asking before it changes anything.
|
|
|
69
69
|
Every edit is shown as a diff and every non-read-only command needs your approval, unless you
|
|
70
70
|
choose otherwise. `/undo` takes back a request's file changes.
|
|
71
71
|
|
|
72
|
+
- :material-connection:{ .lg .middle } **MCP servers**
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
Jira, GitHub, AWS, Google Drive, Grafana and more, one command each:
|
|
77
|
+
[`lcode mcp add atlassian`](mcp.md). Browser sign-in is built in.
|
|
78
|
+
|
|
72
79
|
- :material-swap-horizontal:{ .lg .middle } **Bring your own model**
|
|
73
80
|
|
|
74
81
|
---
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# MCP servers
|
|
2
|
+
|
|
3
|
+
The [Model Context Protocol](https://modelcontextprotocol.io) (MCP) lets lcode use tools from other
|
|
4
|
+
systems: your issue tracker, GitHub, cloud accounts, databases, dashboards, a browser. Each MCP server
|
|
5
|
+
adds a set of tools that the model can call, just like reading files or running commands, and every
|
|
6
|
+
call is shown to you for approval first.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
lcode mcp catalog # ready-made servers
|
|
10
|
+
lcode mcp add atlassian # add one (signs you in through the browser)
|
|
11
|
+
lcode mcp list # check that your servers work
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Then start `lcode` as usual and ask, for example, *"Summarize the open bugs assigned to me in Jira
|
|
15
|
+
and find the code for the top one."*
|
|
16
|
+
|
|
17
|
+
## Ready-made servers
|
|
18
|
+
|
|
19
|
+
`lcode mcp add <name>` sets these up, asks for anything they need and tests the connection:
|
|
20
|
+
|
|
21
|
+
| Name | What it gives the model | You need |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| `atlassian` | Jira, Confluence, Jira Service Management, Compass, Bitbucket | an Atlassian account (browser sign-in) |
|
|
24
|
+
| `aws` | AWS CLI commands on your account, read-only unless you change it | `uvx` and AWS credentials |
|
|
25
|
+
| `aws-knowledge` | Current AWS documentation, code samples, regional availability | nothing |
|
|
26
|
+
| `google-drive` | Search, read and create files in Google Drive | your own Google OAuth client ([below](#google-drive)) |
|
|
27
|
+
| `grafana` | Dashboards, Prometheus and Loki queries, alerts, incidents | `uvx`, a Grafana service account token |
|
|
28
|
+
| `gcp` | `gcloud` commands on your Google Cloud projects | `npx` and the `gcloud` CLI, signed in |
|
|
29
|
+
| `github` | Repositories, issues, pull requests, Actions | a GitHub token (or the GitHub CLI) |
|
|
30
|
+
| `playwright` | A real (headless) browser: open pages, click, fill forms | `npx` |
|
|
31
|
+
| `context7` | Up-to-date docs and examples for thousands of libraries | nothing (an API key is optional) |
|
|
32
|
+
| `sentry` | Errors, issues, traces and releases | a Sentry account (browser sign-in) |
|
|
33
|
+
| `postgres` | Schemas, read-only queries, query performance | `uvx`, a connection URL |
|
|
34
|
+
| `kubernetes` | Pods, deployments, logs and events, read-only | `npx` and a kubeconfig |
|
|
35
|
+
| `linear` | Issues, projects and cycles | a Linear account (browser sign-in) |
|
|
36
|
+
| `notion` | Pages and databases | a Notion account (browser sign-in) |
|
|
37
|
+
|
|
38
|
+
`npx` comes with [Node.js](https://nodejs.org) (18 or newer) and `uvx` with
|
|
39
|
+
[uv](https://docs.astral.sh/uv/getting-started/installation/). `lcode mcp catalog` shows what's missing
|
|
40
|
+
on your machine.
|
|
41
|
+
|
|
42
|
+
Servers that can change things start read-only where the server supports it (`aws`, `postgres`,
|
|
43
|
+
`kubernetes`); the setup note tells you which setting to change to allow writes.
|
|
44
|
+
|
|
45
|
+
### Signing in
|
|
46
|
+
|
|
47
|
+
Atlassian, Sentry, Linear and Notion use OAuth: lcode opens your browser, you sign in and approve
|
|
48
|
+
access, and the browser hands a code back to lcode on `localhost`. lcode stores the tokens in
|
|
49
|
+
`~/.local/state/lcode/mcp-auth/` (readable only by you) and refreshes them automatically.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
lcode mcp login atlassian # sign in again, e.g. after revoking access
|
|
53
|
+
lcode mcp logout atlassian # forget the tokens
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
In a session, `/mcp login <name>` does the same. On a machine without a browser, lcode prints the
|
|
57
|
+
sign-in URL; open it on any computer that can reach the machine's `localhost` (for example through an
|
|
58
|
+
SSH tunnel), or sign in on your own machine and copy `~/.local/state/lcode/mcp-auth/`.
|
|
59
|
+
|
|
60
|
+
Atlassian organizations can also allow API tokens instead; see
|
|
61
|
+
[Atlassian's guide](https://developer.atlassian.com/cloud/rovo-mcp/guides/configuring-authentication-via-api-token/)
|
|
62
|
+
and add the server [by hand](#adding-your-own-servers) with an `Authorization` header.
|
|
63
|
+
|
|
64
|
+
### GitHub
|
|
65
|
+
|
|
66
|
+
`lcode mcp add github` offers the GitHub CLI's token if you use `gh`; otherwise create a
|
|
67
|
+
[fine-grained token](https://github.com/settings/personal-access-tokens/new) with access to the
|
|
68
|
+
repositories you want. Only the `repos`, `issues`, `pull_requests` and `actions` toolsets are on, to
|
|
69
|
+
keep the tool list short; change `X-MCP-Toolsets` in `mcp.json` for
|
|
70
|
+
[others](https://github.com/github/github-mcp-server#available-toolsets).
|
|
71
|
+
|
|
72
|
+
### Google Drive
|
|
73
|
+
|
|
74
|
+
Google doesn't let apps register themselves, so the Drive server needs an OAuth client from your own
|
|
75
|
+
Google Cloud project:
|
|
76
|
+
|
|
77
|
+
1. In a Google Cloud project, enable the **Google Drive API** and the **Google Drive MCP API**.
|
|
78
|
+
2. Configure the OAuth consent screen (while it's in testing, add yourself as a test user).
|
|
79
|
+
3. Create an **OAuth client ID** of type **Desktop app**, and copy its ID and secret.
|
|
80
|
+
4. Run `lcode mcp add google-drive` and paste them; lcode then signs you in through the browser.
|
|
81
|
+
|
|
82
|
+
See [Google's guide](https://developers.google.com/workspace/guides/configure-mcp-servers) for
|
|
83
|
+
details.
|
|
84
|
+
|
|
85
|
+
## Adding your own servers
|
|
86
|
+
|
|
87
|
+
Any MCP server works. For a remote server, give its URL; for a local one, the command that starts it:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
lcode mcp add tracker --url https://mcp.example.com/mcp
|
|
91
|
+
lcode mcp add tracker --url https://mcp.example.com/mcp --header "Authorization: Bearer \${TRACKER_TOKEN}"
|
|
92
|
+
lcode mcp add notes -- npx -y @modelcontextprotocol/server-filesystem /home/me/notes
|
|
93
|
+
lcode mcp add db --env DATABASE_URL=postgres://localhost/app -- uvx some-postgres-mcp
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Servers are saved in `~/.config/lcode/mcp.json` in the `mcpServers` format that most MCP servers
|
|
97
|
+
document, so you can also paste a server's example config into that file:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"mcpServers": {
|
|
102
|
+
"tracker": {
|
|
103
|
+
"type": "http",
|
|
104
|
+
"url": "https://mcp.example.com/mcp",
|
|
105
|
+
"headers": { "Authorization": "Bearer ${TRACKER_TOKEN}" }
|
|
106
|
+
},
|
|
107
|
+
"notes": {
|
|
108
|
+
"command": "npx",
|
|
109
|
+
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/home/me/notes"],
|
|
110
|
+
"tools": ["read_file", "search_files"],
|
|
111
|
+
"allow": ["read_file"]
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
| Key | Meaning |
|
|
118
|
+
|---|---|
|
|
119
|
+
| `command`, `args`, `env` | A local server, started by lcode and spoken to over stdin/stdout |
|
|
120
|
+
| `type: "http"`, `url`, `headers` | A remote server (Streamable HTTP). Without an `Authorization` header, lcode signs in with OAuth when the server asks for it |
|
|
121
|
+
| `tools` | Only offer these tools to the model (fewer tools save context and help smaller models) |
|
|
122
|
+
| `allow` | Tools that run without asking; `["*"]` for all of the server's tools |
|
|
123
|
+
| `timeout` | Seconds a tool call may take (default 300) |
|
|
124
|
+
| `disabled` | `true` to turn the server off (`lcode mcp disable <name>`) |
|
|
125
|
+
| `oauth` | For servers that need a pre-registered OAuth client: `client_id`, `client_secret`, `scopes`, `authorize_params` |
|
|
126
|
+
|
|
127
|
+
Values can use environment variables: `${NAME}`, or `${NAME:-default}`. Keep tokens in environment
|
|
128
|
+
variables if you prefer not to store them in the file; `mcp.json` is created readable only by you
|
|
129
|
+
either way.
|
|
130
|
+
|
|
131
|
+
Servers that only offer the old HTTP+SSE transport (URLs ending in `/sse`) aren't supported. Most of
|
|
132
|
+
them also have a Streamable HTTP endpoint (often `/mcp`); otherwise bridge one with
|
|
133
|
+
`npx -y mcp-remote <url>` as a local server.
|
|
134
|
+
|
|
135
|
+
### Project servers
|
|
136
|
+
|
|
137
|
+
A repository can share servers with everyone who works on it in a `.mcp.json` at its root, in the same
|
|
138
|
+
format. Because those servers run programs or connect to services on your behalf, lcode asks before
|
|
139
|
+
using them the first time, and again whenever the file changes.
|
|
140
|
+
|
|
141
|
+
## In a session
|
|
142
|
+
|
|
143
|
+
The banner lists your servers. They start in the background, and lcode waits for them (at most a
|
|
144
|
+
couple of minutes, the first time `npx` or `uvx` downloads a server) before your first request.
|
|
145
|
+
|
|
146
|
+
```text
|
|
147
|
+
❯ /mcp
|
|
148
|
+
MCP servers
|
|
149
|
+
┏━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
150
|
+
┃ Server ┃ Status ┃ Tools ┃ Details ┃
|
|
151
|
+
┡━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
|
|
152
|
+
│ aws-knowledge │ ready │ 5 │ https://knowledge-mcp.global.api.aws │
|
|
153
|
+
│ everything │ ready │ 13 │ npx -y @modelcontextprotocol/server-everything │
|
|
154
|
+
│ context7 │ ready │ 2 │ https://mcp.context7.com/mcp │
|
|
155
|
+
└───────────────┴────────┴───────┴────────────────────────────────────────────────┘
|
|
156
|
+
Tool definitions: ~6.2K tokens · all sent with every request
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
A server that can't start shows `failed` with the reason (for example, a missing environment
|
|
160
|
+
variable), and one that needs you to sign in shows `needs sign-in`; the others work normally.
|
|
161
|
+
|
|
162
|
+
| Command | |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `/mcp` | Servers, their status and the context their tools take |
|
|
165
|
+
| `/mcp tools <name>` | A server's tools |
|
|
166
|
+
| `/mcp login <name>` | Sign in (or again) and reconnect |
|
|
167
|
+
| `/mcp restart <name>` | Restart a server, e.g. after changing its settings |
|
|
168
|
+
|
|
169
|
+
Start a session without MCP servers with `lcode --no-mcp`.
|
|
170
|
+
|
|
171
|
+
### Approvals
|
|
172
|
+
|
|
173
|
+
Every MCP tool call shows the server, the tool and its arguments, and waits for your answer, in the
|
|
174
|
+
`ask` and `auto-edit` modes alike: answer ++y++ (once), ++a++ (always, for this tool, until you quit)
|
|
175
|
+
or ++n++ with an optional reason. Only `yolo` mode, or the server's `allow` list, skips the question.
|
|
176
|
+
|
|
177
|
+
### Context
|
|
178
|
+
|
|
179
|
+
Every tool definition is sent to the model with every request. A few servers can add tens of
|
|
180
|
+
thousands of tokens, which leaves less room for your code and makes smaller models pick the wrong
|
|
181
|
+
tool. So when the definitions would take more than 15% of the context window, lcode doesn't send
|
|
182
|
+
them: the model sees the tool names, looks up the ones it needs with `mcp_find_tools` and calls them
|
|
183
|
+
with `mcp_call`. `/mcp` and `/context` show which way is used.
|
|
184
|
+
|
|
185
|
+
| `mcp_tools` setting | |
|
|
186
|
+
|---|---|
|
|
187
|
+
| `auto` (default) | Send the definitions if they fit in 15% of the context, otherwise on demand |
|
|
188
|
+
| `direct` | Always send them |
|
|
189
|
+
| `search` | Always on demand |
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
lcode config set mcp_tools search
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Limiting a server's `tools` in `mcp.json` is the best fix for a crowded tool list. Some servers are
|
|
196
|
+
large: Grafana offers about 80 tools and Playwright about 25, while most have a handful.
|
|
197
|
+
|
|
198
|
+
## Troubleshooting
|
|
199
|
+
|
|
200
|
+
- `lcode mcp list` connects to every server and shows what's wrong.
|
|
201
|
+
- A local server's error output is in `~/.local/state/lcode/mcp-logs/<name>.log`.
|
|
202
|
+
- **A `uvx` server fails with a Python error:** some servers don't work on the newest Python yet;
|
|
203
|
+
the ready-made ones pin Python 3.12 with `--python 3.12`, which you can add to your own. A
|
|
204
|
+
`No module named 'mcp.server.fastmcp'` error means the server needs version 1 of the MCP Python
|
|
205
|
+
SDK: add `"--with", "mcp<2"` before the package name in its `args`.
|
|
206
|
+
- **"needs sign-in":** run `lcode mcp login <name>`. If the sign-in page says the redirect URL isn't
|
|
207
|
+
allowed, the service needs a pre-registered client (see [Google Drive](#google-drive)).
|
|
208
|
+
- **Very slow first start:** `npx -y` and `uvx` download the server the first time.
|
|
209
|
+
|
|
210
|
+
## Protocol support
|
|
211
|
+
|
|
212
|
+
lcode implements MCP tools over stdio and Streamable HTTP, for both the current protocol
|
|
213
|
+
(`2026-07-28`, stateless) and the earlier `initialize`-based versions (`2025-11-25` and before), and
|
|
214
|
+
the MCP authorization spec (OAuth 2.1 with PKCE, protected-resource discovery, dynamic client
|
|
215
|
+
registration and refresh tokens). MCP resources, prompts, sampling and elicitation aren't supported
|
|
216
|
+
yet.
|
|
@@ -62,6 +62,12 @@ of the text-only variant, run `lcode setup <key>` once to create the variant.
|
|
|
62
62
|
- Some sites block automated downloads or need JavaScript; with `OLLAMA_API_KEY` set, lcode retries
|
|
63
63
|
those through Ollama's fetch service.
|
|
64
64
|
|
|
65
|
+
### An MCP server doesn't work
|
|
66
|
+
|
|
67
|
+
Run `lcode mcp list`: it connects to every server and shows why one fails. A local server's error
|
|
68
|
+
output is in `~/.local/state/lcode/mcp-logs/<name>.log`. Remote servers that say *needs sign-in*
|
|
69
|
+
need `lcode mcp login <name>`. More in [MCP servers: Troubleshooting](mcp.md#troubleshooting).
|
|
70
|
+
|
|
65
71
|
### It's slow
|
|
66
72
|
|
|
67
73
|
- The first request loads the model (10–45 s). lcode starts loading in the background as soon as it
|
|
@@ -9,6 +9,7 @@ lcode models # the model catalog and what fits this machine
|
|
|
9
9
|
lcode doctor # check the installation (include this in bug reports)
|
|
10
10
|
lcode config [set|unset] # show or change settings
|
|
11
11
|
lcode bench [models] # score models on small coding tasks on this machine
|
|
12
|
+
lcode mcp [catalog|add|list] # connect MCP servers: Jira, GitHub, AWS, databases… (see MCP servers)
|
|
12
13
|
```
|
|
13
14
|
|
|
14
15
|
| Option | Meaning |
|
|
@@ -23,6 +24,7 @@ lcode bench [models] # score models on small coding tasks on this machin
|
|
|
23
24
|
| `--yolo` | Never ask for permission |
|
|
24
25
|
| `--no-think` | Turn off the model's reasoning: faster, less accurate |
|
|
25
26
|
| `--no-web` | No web search or page fetching in this session |
|
|
27
|
+
| `--no-mcp` | Don't start [MCP servers](mcp.md) in this session |
|
|
26
28
|
| `--show-thinking` | Print the model's reasoning as it streams |
|
|
27
29
|
| `-V, --version` | Print the version |
|
|
28
30
|
|
|
@@ -53,6 +55,7 @@ reasoning is on.
|
|
|
53
55
|
| `/undo` | Undo the file changes of the last request ([details](#undo-and-checkpoints)) |
|
|
54
56
|
| `/rewind [N]` | Go back to before request N: its files, and optionally the conversation |
|
|
55
57
|
| `/checkpoints` | List the requests that changed files, and which files |
|
|
58
|
+
| `/mcp [tools\|login\|restart NAME]` | [MCP servers](mcp.md), their status and tools |
|
|
56
59
|
| `/compact [focus]` | Summarize the conversation to free context |
|
|
57
60
|
| `/context [size]` | Show how full the context window is and change its size: pick from a list with memory estimates, or give a size like `/context 128k` (`/ctx` is a shortcut) |
|
|
58
61
|
| `/model [name]`, `/models` | Switch model, list models |
|
|
@@ -15,12 +15,14 @@ from pathlib import Path
|
|
|
15
15
|
|
|
16
16
|
from rich.console import Console
|
|
17
17
|
from rich.markdown import Markdown
|
|
18
|
+
from rich.markup import escape
|
|
18
19
|
from rich.panel import Panel
|
|
19
20
|
from rich.text import Text
|
|
20
21
|
|
|
21
22
|
from lcode import catalog, limits, sessions, web
|
|
22
23
|
from lcode.checkpoints import Checkpoints
|
|
23
24
|
from lcode.config import format_tokens
|
|
25
|
+
from lcode.mcp import McpManager
|
|
24
26
|
from lcode.ollama import Ollama, OllamaError
|
|
25
27
|
from lcode.permissions import Permissions
|
|
26
28
|
from lcode.render import MarkdownStreamer
|
|
@@ -150,6 +152,8 @@ class Agent:
|
|
|
150
152
|
self.tools = Toolbox(self)
|
|
151
153
|
self.session_id = self.new_session_id()
|
|
152
154
|
self.checkpoints = Checkpoints(self.console, settings.checkpoints)
|
|
155
|
+
self.mcp: McpManager | None = None # set by the CLI when MCP servers are configured
|
|
156
|
+
self._mcp_prompt = "" # the MCP part at the end of the system prompt
|
|
153
157
|
self.session_name = ""
|
|
154
158
|
self.session_title = ""
|
|
155
159
|
self.ctx_used = 0
|
|
@@ -188,9 +192,32 @@ class Agent:
|
|
|
188
192
|
return web.resolve_backend(self.settings.search_backend, self.settings.searxng_url)
|
|
189
193
|
|
|
190
194
|
def tool_schemas(self) -> list[dict]:
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
195
|
+
schemas = list(SCHEMAS)
|
|
196
|
+
if self.settings.web != "off":
|
|
197
|
+
schemas += [*([WEB_SEARCH_SCHEMA] if self.search_backend() else []), WEB_FETCH_SCHEMA]
|
|
198
|
+
if self.mcp:
|
|
199
|
+
schemas += self.mcp.schemas(self.settings.context)
|
|
200
|
+
return schemas
|
|
201
|
+
|
|
202
|
+
def prepare_mcp(self) -> None:
|
|
203
|
+
"""Before a request: wait for MCP servers still starting and describe them in the system prompt."""
|
|
204
|
+
if not self.mcp:
|
|
205
|
+
return
|
|
206
|
+
if self.mcp.pending:
|
|
207
|
+
with self.console.status(f"Starting MCP servers: {', '.join(self.mcp.pending)}", spinner="dots"):
|
|
208
|
+
self.mcp.wait()
|
|
209
|
+
for state in self.mcp.servers.values():
|
|
210
|
+
if state.status in ("failed", "login") and not state.reported:
|
|
211
|
+
state.reported = True
|
|
212
|
+
self.console.print(f"[yellow]MCP server {state.name}: {escape(state.error)}[/] [dim](/mcp)[/]")
|
|
213
|
+
self.mcp.refresh_changed()
|
|
214
|
+
section = self.mcp.prompt_section(self.settings.context)
|
|
215
|
+
if section != self._mcp_prompt:
|
|
216
|
+
content = self.messages[0]["content"]
|
|
217
|
+
if self._mcp_prompt and content.endswith(self._mcp_prompt):
|
|
218
|
+
content = content[: -len(self._mcp_prompt)]
|
|
219
|
+
self.messages[0]["content"] = content + section
|
|
220
|
+
self._mcp_prompt = section
|
|
194
221
|
|
|
195
222
|
def web_prompt(self) -> str:
|
|
196
223
|
if self.settings.web == "off":
|
|
@@ -203,6 +230,7 @@ class Agent:
|
|
|
203
230
|
|
|
204
231
|
def reset(self) -> None:
|
|
205
232
|
self.messages = [{"role": "system", "content": self.system_prompt()}]
|
|
233
|
+
self._mcp_prompt = ""
|
|
206
234
|
self.tools.read_mtimes.clear()
|
|
207
235
|
self.ctx_used = len(self.messages[0]["content"]) // 3
|
|
208
236
|
|
|
@@ -254,6 +282,7 @@ class Agent:
|
|
|
254
282
|
else:
|
|
255
283
|
note = f"The session's directory {saved_cwd} no longer exists; staying in {self.cwd}"
|
|
256
284
|
self.messages = [{"role": "system", "content": self.system_prompt()}, *data.get("messages", [])[1:]]
|
|
285
|
+
self._mcp_prompt = ""
|
|
257
286
|
self.session_id = info.id
|
|
258
287
|
self.checkpoints.load(info.id, data.get("checkpoints") or [])
|
|
259
288
|
self.session_name = data.get("name", "")
|
|
@@ -415,6 +444,15 @@ class Agent:
|
|
|
415
444
|
return f"glob({args.get('pattern', '')})"
|
|
416
445
|
if name == "list_dir":
|
|
417
446
|
return f"list_dir({args.get('path', '.')})"
|
|
447
|
+
if self.mcp and self.mcp.owns(name):
|
|
448
|
+
if name == "mcp_find_tools":
|
|
449
|
+
return f"mcp_find_tools({args.get('query', '')!r})"
|
|
450
|
+
try:
|
|
451
|
+
state, tool, arguments = self.mcp.resolve(name, args)
|
|
452
|
+
except Exception:
|
|
453
|
+
return name
|
|
454
|
+
preview = json.dumps(arguments, ensure_ascii=False)
|
|
455
|
+
return f"{state.name} › {tool['name']}({preview if len(preview) <= 80 else preview[:79] + '…'})"
|
|
418
456
|
return name
|
|
419
457
|
|
|
420
458
|
def run_turn(self, user_text: str) -> None:
|
|
@@ -429,6 +467,7 @@ class Agent:
|
|
|
429
467
|
self.checkpoints.before_change(self.cwd)
|
|
430
468
|
|
|
431
469
|
def _run_turn(self, user_text: str) -> None:
|
|
470
|
+
self.prepare_mcp()
|
|
432
471
|
self.messages.append({"role": "user", "content": self.expand_mentions(user_text)})
|
|
433
472
|
malformed = 0
|
|
434
473
|
for _ in range(MAX_STEPS_PER_TURN):
|
|
@@ -850,31 +850,33 @@ def run_task(
|
|
|
850
850
|
ollama: Ollama, settings: Settings, task: Task, timeout: float, log: Console, keep: bool, usage: dict
|
|
851
851
|
) -> TaskResult:
|
|
852
852
|
folder = Path(tempfile.mkdtemp(prefix=f"lcode-bench-{task.id}-"))
|
|
853
|
-
for rel, content in task.files.items():
|
|
854
|
-
(folder / rel).parent.mkdir(parents=True, exist_ok=True)
|
|
855
|
-
(folder / rel).write_text(content)
|
|
856
|
-
agent = Agent(ollama, settings, folder, console=log)
|
|
857
|
-
error = None
|
|
858
|
-
started = time.monotonic()
|
|
859
|
-
try:
|
|
860
|
-
with time_limit(timeout):
|
|
861
|
-
agent.run_turn(task.prompt)
|
|
862
|
-
except TaskTimeout:
|
|
863
|
-
error = f"timed out after {timeout:.0f}s"
|
|
864
|
-
except OllamaError as e:
|
|
865
|
-
error = str(e).splitlines()[0][:200]
|
|
866
|
-
seconds = time.monotonic() - started
|
|
867
|
-
for key, value in agent.usage.items():
|
|
868
|
-
usage[key] = usage.get(key, 0) + value
|
|
869
|
-
answer = next((m.get("content", "") for m in reversed(agent.messages) if m.get("role") == "assistant"), "")
|
|
870
853
|
try:
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
854
|
+
for rel, content in task.files.items():
|
|
855
|
+
(folder / rel).parent.mkdir(parents=True, exist_ok=True)
|
|
856
|
+
(folder / rel).write_text(content)
|
|
857
|
+
agent = Agent(ollama, settings, folder, console=log)
|
|
858
|
+
error = None
|
|
859
|
+
started = time.monotonic()
|
|
860
|
+
try:
|
|
861
|
+
with time_limit(timeout):
|
|
862
|
+
agent.run_turn(task.prompt)
|
|
863
|
+
except TaskTimeout:
|
|
864
|
+
error = f"timed out after {timeout:.0f}s"
|
|
865
|
+
except OllamaError as e:
|
|
866
|
+
error = str(e).splitlines()[0][:200]
|
|
867
|
+
seconds = time.monotonic() - started
|
|
868
|
+
for key, value in agent.usage.items():
|
|
869
|
+
usage[key] = usage.get(key, 0) + value
|
|
870
|
+
answer = next((m.get("content", "") for m in reversed(agent.messages) if m.get("role") == "assistant"), "")
|
|
871
|
+
try:
|
|
872
|
+
check = task.check(folder, answer)
|
|
873
|
+
except Exception as e: # a check must never crash the benchmark
|
|
874
|
+
check = Check(False, f"check failed: {type(e).__name__}: {e}")
|
|
875
|
+
finally: # also when Ctrl+C stops the task
|
|
876
|
+
if keep:
|
|
877
|
+
(folder / "lcode-bench.log").write_text(log.file.getvalue() if isinstance(log.file, io.StringIO) else "")
|
|
878
|
+
else:
|
|
879
|
+
shutil.rmtree(folder, ignore_errors=True)
|
|
878
880
|
return TaskResult(
|
|
879
881
|
id=task.id,
|
|
880
882
|
passed=check.passed and error is None,
|
|
@@ -1010,7 +1012,7 @@ def summary_rows(runs: list[ModelRun], tasks: list[Task]) -> list[tuple[str, lis
|
|
|
1010
1012
|
return "—" if run.error else value
|
|
1011
1013
|
|
|
1012
1014
|
rows += [
|
|
1013
|
-
("Passed", [cell(r, f"{r.passed}/{len(r.tasks)}") for r in runs]),
|
|
1015
|
+
("Passed", [cell(r, f"{r.passed}/{len(r.tasks)}" + (" (stopped)" if r.interrupted else "")) for r in runs]),
|
|
1014
1016
|
("Time", [cell(r, duration(r.seconds)) for r in runs]),
|
|
1015
1017
|
("Generation", [cell(r, f"{r.generation_tps:.1f} tok/s" if r.generation_tps else "?") for r in runs]),
|
|
1016
1018
|
("Prompt reading", [cell(r, f"{r.prompt_tps:,.0f} tok/s" if r.prompt_tps else "?") for r in runs]),
|