lcode-cli 0.10.0__tar.gz → 0.11.1__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.10.0 → lcode_cli-0.11.1}/CHANGELOG.md +27 -1
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/PKG-INFO +8 -4
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/README.md +5 -3
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/docs/how-it-works.md +1 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/docs/mcp.md +50 -1
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/docs/quickstart.md +1 -1
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/pyproject.toml +5 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/__init__.py +1 -1
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/mcp/catalog.py +4 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/mcp/catalog.toml +58 -1
- lcode_cli-0.11.1/src/lcode/mcp/server.py +152 -0
- lcode_cli-0.11.1/src/lcode/servers/__init__.py +1 -0
- lcode_cli-0.11.1/src/lcode/servers/encord.py +288 -0
- lcode_cli-0.11.1/src/lcode/servers/valohai.py +329 -0
- lcode_cli-0.11.1/tests/test_servers.py +379 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/uv.lock +452 -2
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/.editorconfig +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/.github/CODEOWNERS +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/.github/ISSUE_TEMPLATE/model_request.yml +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/.github/dependabot.yml +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/.github/workflows/ci.yml +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/.github/workflows/docs.yml +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/.github/workflows/release.yml +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/.gitignore +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/.pre-commit-config.yaml +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/AGENTS.md +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/CODE_OF_CONDUCT.md +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/CONTRIBUTING.md +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/LICENSE +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/SECURITY.md +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/docs/agents.md +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/docs/assets/demo.svg +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/docs/assets/extra.css +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/docs/assets/logo.svg +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/docs/assets/social-preview.png +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/docs/bench.md +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/docs/changelog.md +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/docs/commands.md +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/docs/configuration.md +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/docs/contributing.md +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/docs/index.md +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/docs/installation.md +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/docs/memory.md +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/docs/models.md +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/docs/sandbox.md +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/docs/troubleshooting.md +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/docs/usage.md +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/examples/README.md +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/examples/commands/review.md +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/examples/skills/commit-message/SKILL.md +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/examples/skills/commit-message/scripts/staged.sh +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/install.sh +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/mkdocs.yml +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/overrides/main.html +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/scripts/record_demo.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/scripts/social-preview.html +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/__main__.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/agent.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/bench.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/catalog.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/checkpoints.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/cli.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/config.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/extensions.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/frontmatter.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/hardware.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/limits.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/mcp/__init__.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/mcp/auth.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/mcp/client.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/mcp/commands.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/mcp/config.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/mcp/http.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/mcp/manager.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/mcp/protocol.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/mcp/stdio.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/memory.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/models.toml +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/ollama.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/permissions.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/planning.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/render.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/repl.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/sandbox.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/sessions.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/subagents.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/tools.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/vision.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/src/lcode/web.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/tests/conftest.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/tests/fake_mcp_server.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/tests/test_agent.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/tests/test_bench.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/tests/test_catalog.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/tests/test_checkpoints.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/tests/test_cli.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/tests/test_config.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/tests/test_context_picker.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/tests/test_extensions.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/tests/test_mcp.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/tests/test_memory.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/tests/test_permissions.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/tests/test_plan_mode.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/tests/test_sandbox.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/tests/test_sessions.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/tests/test_subagents.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/tests/test_tools.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/tests/test_vision.py +0 -0
- {lcode_cli-0.10.0 → lcode_cli-0.11.1}/tests/test_web.py +0 -0
|
@@ -6,6 +6,30 @@ All notable changes to lcode are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.11.1] - 2026-10-05
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- `lcode mcp add encord` failed with "needs the Encord SDK" when lcode itself was installed with
|
|
14
|
+
`uv tool install`: `uvx` reused that installation, which doesn't have the SDK. The Encord and
|
|
15
|
+
Valohai presets now run `uvx --isolated`. If you added either server with 0.11.0, add it again
|
|
16
|
+
(`lcode mcp add encord`) or put `--isolated` first in its `args` in `mcp.json`.
|
|
17
|
+
|
|
18
|
+
## [0.11.0] - 2026-10-05
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- Ready-made MCP servers for data and ML platforms:
|
|
23
|
+
- `lcode mcp add metabase` connects to the MCP server built into Metabase 60 and later, with browser
|
|
24
|
+
sign-in, so the model sees your data with your Metabase permissions.
|
|
25
|
+
- `lcode mcp add encord` and `lcode mcp add valohai`: those platforms have no MCP server of their own,
|
|
26
|
+
so lcode now ships `lcode-mcp-encord` (projects, datasets, ontologies, labeling progress, tasks and
|
|
27
|
+
labels, built on the Encord SDK) and `lcode-mcp-valohai` (executions with their logs, metrics and
|
|
28
|
+
outputs, pipelines). Both are read-only unless started with `--allow-writes`, which adds
|
|
29
|
+
assigning tasks and setting priorities (Encord) or starting and stopping executions (Valohai), and
|
|
30
|
+
both work with other MCP clients too. See [MCP servers](https://nasser1941.github.io/lcode/mcp/#encord).
|
|
31
|
+
- Catalog questions that ask for an address now accept it without `https://`.
|
|
32
|
+
|
|
9
33
|
## [0.10.0] - 2026-10-05
|
|
10
34
|
|
|
11
35
|
### Added
|
|
@@ -287,7 +311,9 @@ First public release.
|
|
|
287
311
|
- `AGENTS.md` project instructions and `/init` to generate them.
|
|
288
312
|
- One-line installer for Linux and macOS.
|
|
289
313
|
|
|
290
|
-
[Unreleased]: https://github.com/nasser1941/lcode/compare/v0.
|
|
314
|
+
[Unreleased]: https://github.com/nasser1941/lcode/compare/v0.11.1...HEAD
|
|
315
|
+
[0.11.1]: https://github.com/nasser1941/lcode/compare/v0.11.0...v0.11.1
|
|
316
|
+
[0.11.0]: https://github.com/nasser1941/lcode/compare/v0.10.0...v0.11.0
|
|
291
317
|
[0.10.0]: https://github.com/nasser1941/lcode/compare/v0.9.0...v0.10.0
|
|
292
318
|
[0.9.0]: https://github.com/nasser1941/lcode/compare/v0.8.0...v0.9.0
|
|
293
319
|
[0.8.0]: https://github.com/nasser1941/lcode/compare/v0.7.0...v0.8.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: lcode-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.1
|
|
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/
|
|
@@ -30,6 +30,8 @@ Requires-Dist: prompt-toolkit>=3.0.43
|
|
|
30
30
|
Requires-Dist: requests>=2.31
|
|
31
31
|
Requires-Dist: rich>=13.7
|
|
32
32
|
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
33
|
+
Provides-Extra: encord
|
|
34
|
+
Requires-Dist: encord>=0.1.200; extra == 'encord'
|
|
33
35
|
Description-Content-Type: text/markdown
|
|
34
36
|
|
|
35
37
|
<p align="center">
|
|
@@ -95,7 +97,7 @@ through [Ollama](https://ollama.com), and when it needs current information it c
|
|
|
95
97
|
- **Bring your own model.** Eight curated open-weight models, all tested end to end, or any Ollama model
|
|
96
98
|
with tool calling.
|
|
97
99
|
- **MCP servers, ready to go.** Connect Jira and Confluence, GitHub, AWS, Google Drive, Grafana,
|
|
98
|
-
Google Cloud, Sentry, Linear, Notion, Postgres, Kubernetes and more with one command
|
|
100
|
+
Google Cloud, Sentry, Linear, Notion, Postgres, Kubernetes, Metabase, Encord, Valohai and more with one command
|
|
99
101
|
(`lcode mcp add atlassian`), image generation with ComfyUI, or any other MCP server. Browser sign-in (OAuth) is built in.
|
|
100
102
|
- **Sees images.** Attach a screenshot or mockup with `@path` and lcode looks at it, using the
|
|
101
103
|
vision part of your model. With ComfyUI (`lcode mcp add comfyui`) it can also generate and edit
|
|
@@ -199,8 +201,10 @@ fills up. Details: [How it works](https://nasser1941.github.io/lcode/how-it-work
|
|
|
199
201
|
|
|
200
202
|
See the pinned [Roadmap issue](https://github.com/nasser1941/lcode/issues/10). Next up:
|
|
201
203
|
|
|
202
|
-
- [
|
|
203
|
-
-
|
|
204
|
+
- [Smarter context for small windows](https://github.com/nasser1941/lcode/issues/46)
|
|
205
|
+
- [Code intelligence with language servers](https://github.com/nasser1941/lcode/issues/44)
|
|
206
|
+
- [A repository map and semantic search with local embeddings](https://github.com/nasser1941/lcode/issues/45)
|
|
207
|
+
- [Hooks and permission rules](https://github.com/nasser1941/lcode/issues/43)
|
|
204
208
|
- More models in the catalog, and test reports from your hardware ([report one](https://github.com/nasser1941/lcode/issues/new?template=model_request.yml))
|
|
205
209
|
|
|
206
210
|
## Contributing
|
|
@@ -61,7 +61,7 @@ through [Ollama](https://ollama.com), and when it needs current information it c
|
|
|
61
61
|
- **Bring your own model.** Eight curated open-weight models, all tested end to end, or any Ollama model
|
|
62
62
|
with tool calling.
|
|
63
63
|
- **MCP servers, ready to go.** Connect Jira and Confluence, GitHub, AWS, Google Drive, Grafana,
|
|
64
|
-
Google Cloud, Sentry, Linear, Notion, Postgres, Kubernetes and more with one command
|
|
64
|
+
Google Cloud, Sentry, Linear, Notion, Postgres, Kubernetes, Metabase, Encord, Valohai and more with one command
|
|
65
65
|
(`lcode mcp add atlassian`), image generation with ComfyUI, or any other MCP server. Browser sign-in (OAuth) is built in.
|
|
66
66
|
- **Sees images.** Attach a screenshot or mockup with `@path` and lcode looks at it, using the
|
|
67
67
|
vision part of your model. With ComfyUI (`lcode mcp add comfyui`) it can also generate and edit
|
|
@@ -165,8 +165,10 @@ fills up. Details: [How it works](https://nasser1941.github.io/lcode/how-it-work
|
|
|
165
165
|
|
|
166
166
|
See the pinned [Roadmap issue](https://github.com/nasser1941/lcode/issues/10). Next up:
|
|
167
167
|
|
|
168
|
-
- [
|
|
169
|
-
-
|
|
168
|
+
- [Smarter context for small windows](https://github.com/nasser1941/lcode/issues/46)
|
|
169
|
+
- [Code intelligence with language servers](https://github.com/nasser1941/lcode/issues/44)
|
|
170
|
+
- [A repository map and semantic search with local embeddings](https://github.com/nasser1941/lcode/issues/45)
|
|
171
|
+
- [Hooks and permission rules](https://github.com/nasser1941/lcode/issues/43)
|
|
170
172
|
- More models in the catalog, and test reports from your hardware ([report one](https://github.com/nasser1941/lcode/issues/new?template=model_request.yml))
|
|
171
173
|
|
|
172
174
|
## Contributing
|
|
@@ -85,6 +85,7 @@ the Ollama server you configure, it only contacts the web when the model searche
|
|
|
85
85
|
| `agent.py` | System prompt, streaming, tool loop, compaction, sessions |
|
|
86
86
|
| `tools.py` | Tool schemas and implementations |
|
|
87
87
|
| `permissions.py` | Approval prompts and the read-only allowlist |
|
|
88
|
+
| `servers/` | The MCP servers lcode ships for services without one: Encord, Valohai |
|
|
88
89
|
| `mcp/` | MCP client: stdio and HTTP transports, OAuth sign-in, server settings, the catalog, `lcode mcp` |
|
|
89
90
|
| `bench.py` | `lcode bench`: the benchmark tasks, their checks and the reports |
|
|
90
91
|
| `vision.py` | Looking at images with a model that can see |
|
|
@@ -35,13 +35,16 @@ and find the code for the top one."*
|
|
|
35
35
|
| `kubernetes` | Pods, deployments, logs and events, read-only | `npx` and a kubeconfig |
|
|
36
36
|
| `linear` | Issues, projects and cycles | a Linear account (browser sign-in) |
|
|
37
37
|
| `notion` | Pages and databases | a Notion account (browser sign-in) |
|
|
38
|
+
| `metabase` | Explore your data and ask questions through Metabase's semantic layer | Metabase 60 or later with MCP on ([below](#metabase)) |
|
|
39
|
+
| `encord` | Projects, datasets, ontologies, labeling progress, tasks and labels, read-only | `uvx`, an SSH key registered in Encord ([below](#encord)) |
|
|
40
|
+
| `valohai` | ML executions with their logs, metrics and outputs, and pipelines, read-only | `uvx`, a Valohai API token ([below](#valohai)) |
|
|
38
41
|
|
|
39
42
|
`npx` comes with [Node.js](https://nodejs.org) (18 or newer) and `uvx` with
|
|
40
43
|
[uv](https://docs.astral.sh/uv/getting-started/installation/). `lcode mcp catalog` shows what's missing
|
|
41
44
|
on your machine.
|
|
42
45
|
|
|
43
46
|
Servers that can change things start read-only where the server supports it (`aws`, `postgres`,
|
|
44
|
-
`kubernetes`); the setup note tells you which setting to change to allow writes.
|
|
47
|
+
`kubernetes`, `encord`, `valohai`); the setup note tells you which setting to change to allow writes.
|
|
45
48
|
|
|
46
49
|
### Signing in
|
|
47
50
|
|
|
@@ -134,6 +137,52 @@ size, prompt), which can take several minutes of trial and error. Once an image
|
|
|
134
137
|
lcode to save that workflow in your project (for example `assets/icon.workflow.json`) and reuse it:
|
|
135
138
|
later images are a single `run_workflow` call.
|
|
136
139
|
|
|
140
|
+
### Metabase
|
|
141
|
+
|
|
142
|
+
Metabase has its own MCP server inside every instance (Metabase 60 and later), at
|
|
143
|
+
`/api/metabase-mcp`. An admin switches it on under **Admin > AI > MCP**. Then:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
lcode mcp add metabase # asks for your Metabase address, then signs in in the browser
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
You sign in with your Metabase account, and the model sees exactly the data your Metabase
|
|
150
|
+
permissions allow. See [Metabase's MCP docs](https://www.metabase.com/docs/latest/ai/mcp).
|
|
151
|
+
|
|
152
|
+
### Encord
|
|
153
|
+
|
|
154
|
+
Encord has no MCP server of its own, so lcode ships one: `lcode-mcp-encord`, built on the Encord SDK
|
|
155
|
+
and run with `uvx --isolated --from "lcode-cli[encord]"`. It signs in with an SSH key that you register in
|
|
156
|
+
Encord under **Settings > Public keys**; `lcode mcp add encord` asks for the key file's path.
|
|
157
|
+
|
|
158
|
+
| Tool | |
|
|
159
|
+
|---|---|
|
|
160
|
+
| `list_projects`, `list_datasets` | What you can see |
|
|
161
|
+
| `get_project`, `get_ontology` | A project's datasets, ontology classes and workflow stages |
|
|
162
|
+
| `workflow_progress` | How many tasks are in each workflow stage, by status |
|
|
163
|
+
| `list_tasks` | Data units with their stage, status and last edit, filtered by stage or title |
|
|
164
|
+
| `get_labels` | The labels on one data unit: counts per class and the label JSON |
|
|
165
|
+
| `assign_task`, `set_priority` | Only with `--allow-writes` in the server's `args` in `mcp.json` |
|
|
166
|
+
|
|
167
|
+
### Valohai
|
|
168
|
+
|
|
169
|
+
Valohai has no MCP server of its own either, so lcode ships `lcode-mcp-valohai`, which talks to
|
|
170
|
+
Valohai's REST API. It needs an API token (**My Profile > Authentication > Manage tokens**), and the
|
|
171
|
+
address of a self-hosted installation if you use one; `lcode mcp add valohai` asks for both.
|
|
172
|
+
|
|
173
|
+
| Tool | |
|
|
174
|
+
|---|---|
|
|
175
|
+
| `list_projects`, `list_pipelines` | Projects and their pipelines |
|
|
176
|
+
| `list_executions`, `get_execution` | Executions with their status, parameters, metrics, outputs and errors |
|
|
177
|
+
| `get_execution_logs` | The last lines of an execution's log, optionally only stderr |
|
|
178
|
+
| `list_outputs` | The files an execution produced, with `datum://` URIs to use as inputs |
|
|
179
|
+
| `compare_executions` | Metrics and differing parameters of several executions side by side |
|
|
180
|
+
| `start_execution`, `stop_execution` | Only with `--allow-writes` in the server's `args` in `mcp.json` |
|
|
181
|
+
|
|
182
|
+
Both servers are ordinary MCP servers: other MCP clients can use them too, for example
|
|
183
|
+
`uvx --isolated --from lcode-cli lcode-mcp-valohai` with `VALOHAI_TOKEN` set. Keep `--isolated`: without it,
|
|
184
|
+
`uvx` reuses an lcode you installed with `uv tool install`, which may be older or lack the Encord SDK.
|
|
185
|
+
|
|
137
186
|
## Adding your own servers
|
|
138
187
|
|
|
139
188
|
Any MCP server works. For a remote server, give its URL; for a local one, the command that starts it:
|
|
@@ -35,6 +35,9 @@ dependencies = [
|
|
|
35
35
|
"tomli>=2.0; python_version < '3.11'",
|
|
36
36
|
]
|
|
37
37
|
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
encord = ["encord>=0.1.200"] # for lcode-mcp-encord
|
|
40
|
+
|
|
38
41
|
[project.urls]
|
|
39
42
|
Homepage = "https://nasser1941.github.io/lcode/"
|
|
40
43
|
Documentation = "https://nasser1941.github.io/lcode/"
|
|
@@ -44,6 +47,8 @@ Changelog = "https://github.com/nasser1941/lcode/blob/main/CHANGELOG.md"
|
|
|
44
47
|
|
|
45
48
|
[project.scripts]
|
|
46
49
|
lcode = "lcode.cli:main"
|
|
50
|
+
lcode-mcp-valohai = "lcode.servers.valohai:main"
|
|
51
|
+
lcode-mcp-encord = "lcode.servers.encord:main"
|
|
47
52
|
|
|
48
53
|
[dependency-groups]
|
|
49
54
|
dev = ["pytest>=8", "ruff>=0.6"]
|
|
@@ -99,6 +99,10 @@ def build(preset: Preset, answers: dict[str, str | None]) -> dict:
|
|
|
99
99
|
server = copy.deepcopy(preset.server)
|
|
100
100
|
for spec in preset.inputs:
|
|
101
101
|
value = answers.get(spec.var)
|
|
102
|
+
if value and spec.var.endswith(("_URL", "_HOST")):
|
|
103
|
+
value = value.rstrip("/") # the URL may have a path appended, e.g. ${METABASE_URL}/api/metabase-mcp
|
|
104
|
+
if "://" not in value:
|
|
105
|
+
value = "https://" + value # people type metabase.example.com
|
|
102
106
|
if value:
|
|
103
107
|
server = _replace(server, spec.var, value)
|
|
104
108
|
elif spec.optional and not os.environ.get(spec.var):
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# requires commands that must be installed.
|
|
8
8
|
# login sign in through the browser (OAuth) after adding.
|
|
9
9
|
#
|
|
10
|
-
# Commands and URLs were checked against each project's documentation in September 2026.
|
|
10
|
+
# Commands and URLs were checked against each project's documentation in September and October 2026.
|
|
11
11
|
|
|
12
12
|
[atlassian]
|
|
13
13
|
name = "Atlassian"
|
|
@@ -224,3 +224,60 @@ homepage = "https://developers.notion.com/docs/mcp"
|
|
|
224
224
|
login = true
|
|
225
225
|
setup = "Sign in with your Notion account in the browser."
|
|
226
226
|
server = { type = "http", url = "https://mcp.notion.com/mcp" }
|
|
227
|
+
|
|
228
|
+
[metabase]
|
|
229
|
+
name = "Metabase"
|
|
230
|
+
description = "Explore your data and ask questions through Metabase's semantic layer, with your Metabase permissions"
|
|
231
|
+
homepage = "https://www.metabase.com/docs/latest/ai/mcp"
|
|
232
|
+
login = true
|
|
233
|
+
setup = """Needs Metabase 60 or later with its MCP server switched on: an admin turns it on under Admin > AI > MCP.
|
|
234
|
+
Sign in with your Metabase account in the browser."""
|
|
235
|
+
server = { type = "http", url = "${METABASE_URL}/api/metabase-mcp" }
|
|
236
|
+
|
|
237
|
+
[[metabase.inputs]]
|
|
238
|
+
var = "METABASE_URL"
|
|
239
|
+
prompt = "Metabase address, e.g. https://metabase.example.com"
|
|
240
|
+
|
|
241
|
+
[encord]
|
|
242
|
+
name = "Encord"
|
|
243
|
+
description = "Projects, datasets, ontologies, labeling progress, tasks and labels (read-only)"
|
|
244
|
+
homepage = "https://nasser1941.github.io/lcode/mcp/#encord"
|
|
245
|
+
requires = ["uvx"]
|
|
246
|
+
setup = """Encord has no MCP server of its own, so this one comes with lcode (it uses the Encord SDK). It signs in with an SSH
|
|
247
|
+
key registered in Encord: Settings > Public keys. Read-only: add --allow-writes to its args in mcp.json to let the
|
|
248
|
+
model assign tasks and set priorities; lcode still asks before every call."""
|
|
249
|
+
|
|
250
|
+
[encord.server]
|
|
251
|
+
command = "uvx"
|
|
252
|
+
# --isolated: otherwise uvx reuses an installed lcode-cli tool, which lacks the encord extra.
|
|
253
|
+
args = ["--isolated", "--python", "3.12", "--from", "lcode-cli[encord]", "lcode-mcp-encord"]
|
|
254
|
+
env = { ENCORD_SSH_KEY_FILE = "${ENCORD_SSH_KEY_FILE}" }
|
|
255
|
+
|
|
256
|
+
[[encord.inputs]]
|
|
257
|
+
var = "ENCORD_SSH_KEY_FILE"
|
|
258
|
+
prompt = "Path to the SSH private key registered in Encord"
|
|
259
|
+
default = "~/.ssh/id_ed25519"
|
|
260
|
+
|
|
261
|
+
[valohai]
|
|
262
|
+
name = "Valohai"
|
|
263
|
+
description = "ML executions with their logs, metrics and outputs, and pipelines (read-only)"
|
|
264
|
+
homepage = "https://nasser1941.github.io/lcode/mcp/#valohai"
|
|
265
|
+
requires = ["uvx"]
|
|
266
|
+
setup = """Valohai has no MCP server of its own, so this one comes with lcode. It needs an API token: My Profile >
|
|
267
|
+
Authentication > Manage tokens. Read-only: add --allow-writes to its args in mcp.json to let the model start and stop
|
|
268
|
+
executions; lcode still asks before every call."""
|
|
269
|
+
|
|
270
|
+
[valohai.server]
|
|
271
|
+
command = "uvx"
|
|
272
|
+
args = ["--isolated", "--python", "3.12", "--from", "lcode-cli", "lcode-mcp-valohai"]
|
|
273
|
+
env = { VALOHAI_TOKEN = "${VALOHAI_TOKEN}", VALOHAI_HOST = "${VALOHAI_HOST}" }
|
|
274
|
+
|
|
275
|
+
[[valohai.inputs]]
|
|
276
|
+
var = "VALOHAI_TOKEN"
|
|
277
|
+
prompt = "Valohai API token"
|
|
278
|
+
secret = true
|
|
279
|
+
|
|
280
|
+
[[valohai.inputs]]
|
|
281
|
+
var = "VALOHAI_HOST"
|
|
282
|
+
prompt = "Valohai address (Enter for app.valohai.com; for a self-hosted installation, its URL)"
|
|
283
|
+
optional = true
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""A small MCP server over stdio, for the servers lcode ships itself (lcode.servers).
|
|
2
|
+
|
|
3
|
+
It speaks both eras of MCP, like lcode's client: the legacy `initialize` handshake and the modern
|
|
4
|
+
stateless `server/discover`. Tools are plain functions that return text; raising ToolFailure turns
|
|
5
|
+
into an error result the model can read. Tools that change something are only listed when the
|
|
6
|
+
server is started with --allow-writes.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import sys
|
|
13
|
+
import traceback
|
|
14
|
+
from collections.abc import Callable
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from typing import IO
|
|
17
|
+
|
|
18
|
+
from lcode.mcp.protocol import LEGACY_VERSIONS, METHOD_NOT_FOUND, MODERN_VERSIONS
|
|
19
|
+
|
|
20
|
+
INVALID_PARAMS = -32602
|
|
21
|
+
MAX_RESULT = 60_000 # characters per tool result; long listings are cut with a note
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ToolFailure(Exception):
|
|
25
|
+
"""A tool couldn't do its job; the message goes to the model."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class Tool:
|
|
30
|
+
name: str
|
|
31
|
+
description: str
|
|
32
|
+
properties: dict
|
|
33
|
+
required: list[str]
|
|
34
|
+
fn: Callable[..., str]
|
|
35
|
+
writes: bool = False
|
|
36
|
+
|
|
37
|
+
def definition(self) -> dict:
|
|
38
|
+
return {
|
|
39
|
+
"name": self.name,
|
|
40
|
+
"description": self.description,
|
|
41
|
+
"inputSchema": {"type": "object", "properties": self.properties, "required": self.required},
|
|
42
|
+
"annotations": {"readOnlyHint": not self.writes, "destructiveHint": self.writes},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class Server:
|
|
48
|
+
name: str
|
|
49
|
+
version: str
|
|
50
|
+
instructions: str = ""
|
|
51
|
+
allow_writes: bool = False
|
|
52
|
+
tools: dict[str, Tool] = field(default_factory=dict)
|
|
53
|
+
|
|
54
|
+
def tool(self, name: str, description: str, properties: dict | None = None, required=(), writes: bool = False):
|
|
55
|
+
"""Register a function as a tool (a decorator)."""
|
|
56
|
+
|
|
57
|
+
def register(fn: Callable[..., str]) -> Callable[..., str]:
|
|
58
|
+
self.tools[name] = Tool(name, description, properties or {}, list(required), fn, writes)
|
|
59
|
+
return fn
|
|
60
|
+
|
|
61
|
+
return register
|
|
62
|
+
|
|
63
|
+
def listed(self) -> list[Tool]:
|
|
64
|
+
return [t for t in self.tools.values() if self.allow_writes or not t.writes]
|
|
65
|
+
|
|
66
|
+
# -- JSON-RPC
|
|
67
|
+
def handle(self, message: dict) -> dict | None:
|
|
68
|
+
"""The response to one message (None for notifications)."""
|
|
69
|
+
method, params, ident = message.get("method"), message.get("params") or {}, message.get("id")
|
|
70
|
+
if ident is None:
|
|
71
|
+
return None # a notification, such as notifications/initialized
|
|
72
|
+
try:
|
|
73
|
+
result = self.dispatch(str(method), params if isinstance(params, dict) else {})
|
|
74
|
+
except _RpcError as e:
|
|
75
|
+
return {"jsonrpc": "2.0", "id": ident, "error": {"code": e.code, "message": str(e)}}
|
|
76
|
+
return {"jsonrpc": "2.0", "id": ident, "result": result}
|
|
77
|
+
|
|
78
|
+
def dispatch(self, method: str, params: dict) -> dict:
|
|
79
|
+
info = {"name": self.name, "version": self.version}
|
|
80
|
+
if method == "initialize":
|
|
81
|
+
asked = str(params.get("protocolVersion") or "")
|
|
82
|
+
return {
|
|
83
|
+
"protocolVersion": asked if asked in LEGACY_VERSIONS else LEGACY_VERSIONS[0],
|
|
84
|
+
"capabilities": {"tools": {}},
|
|
85
|
+
"serverInfo": info,
|
|
86
|
+
"instructions": self.instructions,
|
|
87
|
+
}
|
|
88
|
+
if method == "server/discover":
|
|
89
|
+
return {
|
|
90
|
+
"supportedVersions": list(MODERN_VERSIONS),
|
|
91
|
+
"capabilities": {"tools": {}},
|
|
92
|
+
"instructions": self.instructions,
|
|
93
|
+
"_meta": {"io.modelcontextprotocol/serverInfo": info},
|
|
94
|
+
}
|
|
95
|
+
if method == "ping":
|
|
96
|
+
return {}
|
|
97
|
+
if method == "tools/list":
|
|
98
|
+
return {"tools": [t.definition() for t in self.listed()]}
|
|
99
|
+
if method == "tools/call":
|
|
100
|
+
return self.call(str(params.get("name", "")), params.get("arguments") or {})
|
|
101
|
+
raise _RpcError(METHOD_NOT_FOUND, f"unknown method {method}")
|
|
102
|
+
|
|
103
|
+
def call(self, name: str, arguments: dict) -> dict:
|
|
104
|
+
tool = self.tools.get(name)
|
|
105
|
+
if tool is None or tool not in self.listed():
|
|
106
|
+
hint = " (start the server with --allow-writes to enable it)" if tool else ""
|
|
107
|
+
return _text(f"Unknown tool {name}{hint}", error=True)
|
|
108
|
+
if not isinstance(arguments, dict):
|
|
109
|
+
raise _RpcError(INVALID_PARAMS, "arguments must be an object")
|
|
110
|
+
unknown = set(arguments) - set(tool.properties)
|
|
111
|
+
missing = [r for r in tool.required if arguments.get(r) in (None, "")]
|
|
112
|
+
if unknown or missing:
|
|
113
|
+
problems = [f"unknown argument(s): {', '.join(sorted(unknown))}"] if unknown else []
|
|
114
|
+
problems += [f"missing: {', '.join(missing)}"] if missing else []
|
|
115
|
+
return _text(f"Bad arguments for {name}: {'; '.join(problems)}", error=True)
|
|
116
|
+
try:
|
|
117
|
+
text = tool.fn(**arguments)
|
|
118
|
+
except ToolFailure as e:
|
|
119
|
+
return _text(str(e), error=True)
|
|
120
|
+
except Exception as e: # report, don't crash the server
|
|
121
|
+
traceback.print_exc(file=sys.stderr)
|
|
122
|
+
return _text(f"{type(e).__name__}: {e}", error=True)
|
|
123
|
+
if len(text) > MAX_RESULT:
|
|
124
|
+
text = text[:MAX_RESULT] + "\n… (cut off: ask for less, e.g. with a smaller limit or a filter)"
|
|
125
|
+
return _text(text)
|
|
126
|
+
|
|
127
|
+
def serve(self, stdin: IO[str] | None = None, stdout: IO[str] | None = None) -> None:
|
|
128
|
+
"""Answer newline-delimited JSON-RPC on stdin/stdout until stdin closes."""
|
|
129
|
+
stdin, stdout = stdin or sys.stdin, stdout or sys.stdout
|
|
130
|
+
for line in stdin:
|
|
131
|
+
line = line.strip()
|
|
132
|
+
if not line:
|
|
133
|
+
continue
|
|
134
|
+
try:
|
|
135
|
+
message = json.loads(line)
|
|
136
|
+
except json.JSONDecodeError:
|
|
137
|
+
response = {"jsonrpc": "2.0", "id": None, "error": {"code": -32700, "message": "parse error"}}
|
|
138
|
+
else:
|
|
139
|
+
response = self.handle(message) if isinstance(message, dict) else None
|
|
140
|
+
if response is not None:
|
|
141
|
+
stdout.write(json.dumps(response) + "\n")
|
|
142
|
+
stdout.flush()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class _RpcError(Exception):
|
|
146
|
+
def __init__(self, code: int, message: str):
|
|
147
|
+
super().__init__(message)
|
|
148
|
+
self.code = code
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _text(text: str, error: bool = False) -> dict:
|
|
152
|
+
return {"content": [{"type": "text", "text": text}], "isError": error}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""MCP servers that lcode ships for services that have none of their own."""
|