hyprpilot-nvim-mcp 1.1.2__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.
@@ -0,0 +1,65 @@
1
+ # Compiled Lua sources
2
+ luac.out
3
+
4
+ # luarocks build files
5
+ *.src.rock
6
+ *.zip
7
+ *.tar.gz
8
+
9
+ # Object files
10
+ *.o
11
+ *.os
12
+ *.ko
13
+ *.obj
14
+ *.elf
15
+
16
+ # Precompiled Headers
17
+ *.gch
18
+ *.pch
19
+
20
+ # Libraries
21
+ *.lib
22
+ *.a
23
+ *.la
24
+ *.lo
25
+ *.def
26
+ *.exp
27
+
28
+ # Shared objects (inc. Windows DLLs)
29
+ *.dll
30
+ *.so
31
+ *.so.*
32
+ *.dylib
33
+
34
+ # Executables
35
+ *.exe
36
+ *.out
37
+ *.app
38
+ *.i*86
39
+ *.x86_64
40
+ *.hex
41
+ /.task
42
+
43
+ # Python (pkg/)
44
+ __pycache__/
45
+ *.py[cod]
46
+ *$py.class
47
+ build/
48
+ dist/
49
+ wheels/
50
+ *.egg-info/
51
+ *.egg
52
+ .venv/
53
+ venv/
54
+ env/
55
+ .mypy_cache/
56
+ .ruff_cache/
57
+ .pytest_cache/
58
+ .coverage
59
+ htmlcov/
60
+
61
+ # Editors
62
+ .idea/
63
+ .vscode/
64
+ *.swp
65
+ /docs/plans
@@ -0,0 +1,349 @@
1
+ # CLAUDE.md (`pkg/` workspace)
2
+
3
+ > Sub-CLAUDE.md for the Python package inside the `hyprpilot.nvim`
4
+ > mono-repo. The root [`../CLAUDE.md`](../CLAUDE.md) covers shared
5
+ > conventions (commits, branching, mono-repo layout, release-please).
6
+ > This file covers Python-specific stack, tooling, and gotchas.
7
+
8
+ ## Overview
9
+
10
+ `hyprpilot-nvim-mcp` is a `uvx`-runnable MCP server that bridges Neovim
11
+ editor state into the [`hyprpilot`](https://github.com/hyprpilot/hyprpilot)
12
+ agent's tool surface. It attaches to a running Neovim via the
13
+ `$NVIM_LISTEN_ADDRESS` socket using `pynvim` and acts as a **pure
14
+ dispatcher**: at boot it queries the Lua side
15
+ (`require("hyprpilot.mcp").list()`) for the captain-registered tool
16
+ catalogue and re-exposes each tool to the agent via FastMCP. The Python
17
+ package ships **zero default tools**; the captain registers what they
18
+ need on the Lua side. Two management tools (`reload_dynamic_tools`,
19
+ `healthcheck`) are the only built-ins.
20
+
21
+ ## Stack & Structure
22
+
23
+ - **Language:** Python 3.14+ (`requires-python = ">=3.14"` in
24
+ `pyproject.toml`; ruff `target-version = "py314"`).
25
+ - **CLI:** [`click`](https://click.palletsprojects.com/) with a
26
+ class-based command tree. The `Server` class hosts state + behaviour;
27
+ `Server.cli` is the `@click.group` exposed to `[project.scripts]`.
28
+ click owns env-var parsing for every option (`envvar=`, `click.BOOL`
29
+ coercion). Do not write a separate `Config.from_env()` layer.
30
+ - **Logging:** [`rich`](https://rich.readthedocs.io/) `RichHandler`
31
+ bound to a stderr `Console`. The wrapper module
32
+ (`hyprpilot_nvim_mcp/log.py`) exports `configure(level)` + `get(name)`;
33
+ every module imports `from hyprpilot_nvim_mcp import log` and uses
34
+ the standard logging level methods.
35
+ - **Package manager:** [`uv`](https://docs.astral.sh/uv/) (Astral). This
36
+ package is a member of the repo-root **uv workspace**; `uv.lock` and
37
+ `.venv/` live at the repo root, not in `pkg/`. Run `uv sync` from the
38
+ root (or anywhere — uv finds the workspace).
39
+ - **MCP framework:** [`fastmcp`](https://gofastmcp.com/) — but we don't
40
+ use the decorator path for dynamic tools. We construct
41
+ `fastmcp.tools.FunctionTool(name=..., description=..., parameters=schema, fn=dispatcher)`
42
+ directly so the **Lua schema passes through verbatim** as the agent's
43
+ tool view — single source of truth on the Lua side. Decorator-based
44
+ registration is fine for the management tools (`healthcheck`,
45
+ `reload_dynamic_tools`) where we own both ends.
46
+ - **Neovim client:** [`pynvim`](https://github.com/neovim/pynvim) — msgpack-RPC
47
+ over the listen socket. Sync API.
48
+ - **Lint / format:** `ruff` (single binary, replaces black + isort + flake8).
49
+ - **Type-check:** `mypy --strict` (configured via `pyproject.toml`).
50
+ - **Test:** `pytest` + `pytest-asyncio` (headless `nvim --embed --clean`
51
+ fixture in `conftest.py`).
52
+ - **Toolchain pin:** `mise.toml` pins `python = "3.14"`, `uv = "latest"`,
53
+ `task = "3"`.
54
+ - **Build backend:** `hatchling` (pure-Python wheel; flat package layout).
55
+ - **Layout:**
56
+ `hyprpilot_nvim_mcp/{__init__,cli,log,nvim,server,dispatcher}.py`
57
+ + `hyprpilot_nvim_mcp/tools/{healthcheck,reload}.py`. `cli.py` hosts
58
+ the `Server` class (click group + runtime state); `dispatcher.py`
59
+ builds the FastMCP `FunctionTool` per Lua-registered tool;
60
+ `tools/<name>.py` exposes a single `register(mcp, nvim, ...)` entry
61
+ point that the CLI wires.
62
+
63
+ ## Conventions
64
+
65
+ - **Conventional Commits** — `feat:`, `fix:`, `chore:`, `docs:`,
66
+ `refactor:`, `test:`. Scope when meaningful (`feat(tools): ...`).
67
+ - **Branching** — `feature/*` for new work, `fix/*` or `hotfix/*` for
68
+ fixes.
69
+ - **stdout is sacred** — MCP wire bytes go on stdout. Logs go to stderr
70
+ via the `log` module. **Never** `print()` from a tool; never log to
71
+ stdout. One stray `print` corrupts the daemon's parser.
72
+ - **`mypy --strict` from day one** — every public function annotated,
73
+ every helper too. No `# type: ignore` without a comment explaining why.
74
+ - **One file per management tool** — `tools/<name>.py` exports a
75
+ `register(mcp, nvim, ...)` function and nothing else. Dynamic
76
+ Lua-side tools live in `dispatcher.py` (single closure factory) — no
77
+ per-tool Python file for those.
78
+ - **No tool catalogue in Python** — the captain's tools live on the Lua
79
+ side; Python just dispatches. If you find yourself adding a Python
80
+ tool that talks to nvim editor state (LSP, treesitter, buffers, etc.),
81
+ stop and add it to `lua/hyprpilot/mcp.lua` examples instead. Python
82
+ built-ins are reserved for things only the bridge can do
83
+ (`healthcheck`, `reload_dynamic_tools`).
84
+ - **Errors are values** — wrap every `nvim.*` call to translate pynvim
85
+ exceptions into typed `MCPToolError`. The daemon never sees a Python
86
+ traceback; the captain reads clean messages in the chat surface.
87
+ - **Reconnect, don't crash** — nvim quitting/restarting is normal;
88
+ the bridge survives it. Exponential back-off (1s → 30s cap).
89
+ - **`pyproject.toml` is the single source of truth** for deps, scripts,
90
+ ruff config, mypy config. No `setup.py`, no `setup.cfg`, no
91
+ `requirements.txt`.
92
+ - **Inline single-use values** — same rule as the root CLAUDE.md.
93
+ Never name a local, constant, or intermediate dict that has only one
94
+ consumer. Inline directly into the call site.
95
+ - **Config knobs ship with their behaviour** — never declare a CLI
96
+ option, env var, or config dataclass field whose handler doesn't
97
+ exist yet. Add it in the same PR that wires it.
98
+ - **Validation logs and skips, not throws** for captain-facing input
99
+ (CLI args, env vars, MCP tool registration). Tool handler errors
100
+ still translate to `MCPToolError` per the "Errors are values" rule
101
+ above; that's a different surface.
102
+
103
+ ## Decision Log
104
+
105
+ - **Framework: FastMCP**
106
+ - Chose: decorator-based tool registration with auto-derived schemas.
107
+ - Why: zero-ceremony registration, schema comes from type hints +
108
+ docstrings (one source of truth). Upstreamed into the official MCP
109
+ Python SDK.
110
+ - Rejected: hand-rolled JSON-RPC server — boilerplate without payoff.
111
+
112
+ - **Neovim client: pynvim**
113
+ - Chose: official client, msgpack-RPC over the listen socket.
114
+ - Why: idiomatic Python API for buffers/windows/LSP, supports
115
+ `exec_lua` for arbitrary Lua callouts.
116
+ - Rejected: writing our own msgpack-RPC client — reinventing what
117
+ `pynvim` already battle-tests.
118
+
119
+ - **Build backend: hatchling**
120
+ - Chose: hatchling with src layout.
121
+ - Why: `uv` defaults to it for new packages, `pyproject.toml`-only
122
+ config, no quirks.
123
+ - Rejected: setuptools — unnecessary legacy surface for a fresh
124
+ project.
125
+
126
+ - **Sync vs async tools**
127
+ - Chose: synchronous tool handlers.
128
+ - Why: `pynvim`'s msgpack-RPC is sync; FastMCP allows async tools
129
+ but the underlying transport gains nothing from it.
130
+
131
+ - **Pure dispatcher, zero default tools (shipped in #14)**
132
+ - Chose: Python ships only `healthcheck` + `reload_dynamic_tools`.
133
+ Everything else comes from `require("hyprpilot.mcp").list()` on the
134
+ Lua side and is registered via FastMCP `FunctionTool(parameters=schema)`
135
+ so the Lua schema is the agent's view verbatim.
136
+ - Why: single source of truth for the tool catalogue (Lua), and the
137
+ captain registers what they need — no curated Python defaults to
138
+ fight with. Bridge stays small (~150 LOC of dispatcher + nvim wrapper).
139
+ - Rejected: shipping built-in `buffer`/`lsp`/`treesitter`/`exec_lua`
140
+ tools on the Python side. Captain explicitly pulled this:
141
+ "we will register it from explicitly the plugin side of the neovim".
142
+
143
+ - **CLI: class-based click `Server`**
144
+ - Chose: a single `Server` class hosts log config, parsed options,
145
+ and a `serve()` method. `Server.cli` is the `@click.group` exposed
146
+ via `[project.scripts]`. Subcommands receive the instance via
147
+ `click.pass_obj`.
148
+ - Why: keeps option parsing, env-var resolution, and runtime wiring
149
+ in one cohesive unit; matches the captain's pattern across other
150
+ Python projects.
151
+ - Rejected: function-based click commands with separate `Config`
152
+ dataclass — adds an env-parsing layer click already provides.
153
+
154
+ ## Approaches Tried
155
+
156
+ - **Built-in `exec_lua` tool (#14)** — gated behind an env var, with a
157
+ separate `tools/exec_lua.py` file. Captain pulled it entirely:
158
+ "we will register it from explicitly the plugin side of the neovim".
159
+ If the captain wants an arbitrary-Lua escape hatch, they register it
160
+ via `require("hyprpilot.mcp").register({...})`. The Python side does
161
+ not own that surface.
162
+ - **Forward-looking config knobs** — same lesson as the root CLAUDE.md.
163
+ Don't declare a CLI option / env var whose handler doesn't exist yet.
164
+ - **Decorator registration for dynamic tools** — `@mcp.tool` derives
165
+ the schema from Python type hints + docstring. For Lua-side tools
166
+ whose schema lives on the Lua side, that's the wrong direction:
167
+ use `FunctionTool(parameters=schema)` so the Lua schema is verbatim.
168
+
169
+ ## Tools & MCP Usage
170
+
171
+ - **`uv`** — package + venv manager. `uv sync` installs deps from
172
+ `pyproject.toml` + `uv.lock`. `uvx <pkg>` is the no-install spawn
173
+ idiom (PyPI fetch + cache).
174
+ - **`ruff`** — single-binary lint + format. `task lint` runs both
175
+ `ruff format --check` and `ruff check`.
176
+ - **`mypy --strict`** — full type-check of `hyprpilot_nvim_mcp/` and
177
+ `tests/`. `task lint` runs it after ruff.
178
+ - **`pytest`** — test runner. `tests/conftest.py` spawns headless
179
+ `nvim --embed --clean` per fixture; tests assert against the in-process
180
+ pynvim handle.
181
+ - **`task`** — entry points. `task install`, `task lint`, `task test`,
182
+ `task build`.
183
+ - **`mise`** — pins `python`, `uv`, and `task` versions. CI installs
184
+ via `jdx/mise-action@v4`.
185
+ - **GitHub Actions** — `.github/workflows/ci.yml` (ruff + mypy + pytest)
186
+ and `.github/workflows/release-please.yml` (release-please opens PRs
187
+ on `main` from conventional commits, tags + creates GitHub Release on
188
+ merge).
189
+
190
+ ## Publishing to PyPI
191
+
192
+ The `build-pypi` + `publish-pypi` jobs in
193
+ `.github/workflows/release-please.yml` publish `hyprpilot-nvim-mcp`
194
+ to PyPI as a downstream chain of the `release-please` job, gated
195
+ on `release_created == 'true'`. Auth is **PyPI Trusted Publishing
196
+ (OIDC)** — no API token is stored in GitHub. PyPI verifies the
197
+ workflow's identity via OIDC and issues a short-lived publish token
198
+ at runtime.
199
+
200
+ The publish jobs live in `release-please.yml` (not a separate
201
+ `publish-pypi.yml` reusable workflow) because PyPI's trusted-
202
+ publisher matches the OIDC token's `job_workflow_ref` claim
203
+ against the registered workflow file. With `workflow_call`, that
204
+ claim points at the CALLING workflow regardless of where the
205
+ job's steps are defined — so a reusable workflow path requires
206
+ PyPI to register the CALLER's filename, which is confusing and
207
+ error-prone. Inlining keeps OIDC identity = trusted-publisher
208
+ filename = `release-please.yml`.
209
+
210
+ ### One-time setup (captain only)
211
+
212
+ > **Migrating from a `publish-pypi.yml`-named publisher?** If you
213
+ > registered the trusted publisher with `Workflow name:
214
+ > publish-pypi.yml` (the pre-PR-#67 setup), update it to
215
+ > `release-please.yml`. PyPI: project settings → "Trusted
216
+ > publishers" → the existing entry → edit the workflow name. No
217
+ > other field changes.
218
+
219
+ **1. Create the PyPI project** (skip if already published).
220
+ The first publish has to seed the project; PyPI doesn't accept a
221
+ trusted publisher for a project that doesn't exist yet. Two paths:
222
+
223
+ - **Recommended — pending publisher.** PyPI lets you register a
224
+ trusted publisher BEFORE the project exists, via
225
+ [pypi.org/manage/account/publishing](https://pypi.org/manage/account/publishing/)
226
+ → "Add a new pending publisher". Use the values in step 2 below.
227
+ The first tag-push then publishes via OIDC and the pending
228
+ publisher is auto-promoted.
229
+ - **Alternative — manual seed.** Build locally
230
+ (`cd pkg && uv build`) and `uv run twine upload dist/*` once with
231
+ an API token, then add the trusted publisher per step 2.
232
+
233
+ **2. Add the trusted publisher.**
234
+ On [pypi.org/manage/project/hyprpilot-nvim-mcp/settings/publishing/](https://pypi.org/manage/project/hyprpilot-nvim-mcp/settings/publishing/):
235
+
236
+ | Field | Value |
237
+ |---|---|
238
+ | PyPI Project Name | `hyprpilot-nvim-mcp` |
239
+ | Owner | `hyprpilot` |
240
+ | Repository name | `hyprpilot.nvim` |
241
+ | Workflow name | `release-please.yml` |
242
+ | Environment name | `pypi` |
243
+
244
+ The environment binding scopes the OIDC grant to a single
245
+ GitHub Environment, so a workflow running outside `pypi` (a
246
+ forked PR, a misconfigured workflow) can't grab the publish
247
+ token even if it can read the repo.
248
+
249
+ **3. Create the GitHub Environment.**
250
+ On [github.com/hyprpilot/hyprpilot.nvim/settings/environments](https://github.com/hyprpilot/hyprpilot.nvim/settings/environments)
251
+ → "New environment" → name it `pypi`.
252
+
253
+ Optional but recommended:
254
+ - **Deployment branch rule** → "Selected branches" → add `main`.
255
+ Protects against publishes from feature branches.
256
+ - **Required reviewers** → list the captain. The publish job
257
+ will pause until approved on each tag.
258
+
259
+ **4. Pin the publish action's SHA.**
260
+ The workflow ships with `pypa/gh-action-pypi-publish@release/v1`
261
+ (a moving tag) for the first checkin. Run:
262
+
263
+ ```bash
264
+ gh api repos/pypa/gh-action-pypi-publish/git/refs/tags/release/v1 \
265
+ --jq '.object.sha'
266
+ ```
267
+
268
+ …and replace the `release/v1` ref with the resolved SHA, with a
269
+ `# release/v1` trailing comment so Renovate / Dependabot can
270
+ track it (matches the `release-please-action@<sha> # v5.0.0`
271
+ pattern in the same file). Skip this step in dev; ship it
272
+ before the first tagged release.
273
+
274
+ ### How it runs
275
+
276
+ 1. Captain merges the release-please PR on `main`. release-please
277
+ cuts a `vX.Y.Z` tag and a GitHub Release.
278
+ 2. The same workflow's `build-pypi` job (gated on
279
+ `release_created == 'true'`) produces the wheel + sdist via
280
+ `task build` (uv backend); `publish-pypi` downloads the
281
+ artifact and calls `pypa/gh-action-pypi-publish` which
282
+ OIDC-authenticates against PyPI's trusted-publisher entry.
283
+ Both run in the same workflow run as `release-please` — no
284
+ tag-push trigger involved (and none would fire anyway, because
285
+ `secrets.GITHUB_TOKEN`-pushed tags don't trigger workflows by
286
+ GitHub's anti-loop design).
287
+ 3. PyPI verifies the OIDC claims (repo, workflow, environment) match
288
+ the registered publisher and accepts the upload. Captain sees
289
+ the new version on
290
+ [pypi.org/project/hyprpilot-nvim-mcp/](https://pypi.org/project/hyprpilot-nvim-mcp/)
291
+ within a minute.
292
+ 4. `skip-existing: true` makes a tag re-push (e.g. release-please
293
+ re-cutting the same version) idempotent — the publish step
294
+ no-ops instead of failing.
295
+
296
+ ### Why trusted publishing over an API token
297
+
298
+ - **No long-lived secret in the repo.** API tokens get leaked,
299
+ rotated, forgotten. OIDC tokens are minted per-job, valid for
300
+ ~15 minutes.
301
+ - **PyPI scopes the grant.** The trusted-publisher entry binds to
302
+ an exact (repo, workflow, environment) tuple. A different
303
+ workflow in the same repo cannot publish, even with admin
304
+ access.
305
+ - **No setup on the captain's local machine.** `pip config` /
306
+ `.pypirc` aren't needed for CI publishing.
307
+
308
+ ### Local publishing (if you ever need to)
309
+
310
+ For one-off pushes off the CI path (manual seed in step 1 above,
311
+ hotfix when GitHub Actions is down):
312
+
313
+ ```bash
314
+ cd pkg
315
+ uv build # → dist/*.whl + *.tar.gz
316
+ uv run twine upload dist/* # prompts for an API token
317
+ ```
318
+
319
+ Generate the token at
320
+ [pypi.org/manage/account/token](https://pypi.org/manage/account/token/);
321
+ scope it to the `hyprpilot-nvim-mcp` project. Stash it in your
322
+ password manager — never commit it, never paste it into a CI
323
+ secret (the workflow uses OIDC; an API token there would be
324
+ strictly worse).
325
+
326
+ ## Gotchas
327
+
328
+ - **`NVIM_LISTEN_ADDRESS` must be a Unix socket path** that the running
329
+ Neovim was started with (`nvim --listen /tmp/nvim.sock`). The bridge
330
+ fails fast with a typed error if the socket is missing.
331
+ - **Logging to stdout corrupts the MCP wire** — see Conventions above.
332
+ All logs go to stderr; the `log` module's helpers enforce this.
333
+ - **`pynvim` is thread-unsafe** — the bridge wraps every `nvim.*` access
334
+ in a lock; FastMCP's tool dispatch can run on multiple threads under
335
+ load.
336
+ - **`pynvim` lacks `py.typed`** — mypy can't see its types. We carry
337
+ one targeted `[[tool.mypy.overrides]]` block in `pyproject.toml` to
338
+ silence missing-imports for `pynvim`. Don't widen this to other deps
339
+ without a stated reason.
340
+ - **`FunctionTool` parameters take JSON Schema verbatim** — pass the
341
+ Lua-side `schema` table straight through; do not Pythonize it. FastMCP
342
+ forwards the JSON Schema to the agent. Rebuilding it from Python
343
+ type hints would defeat the purpose of dynamic discovery.
344
+ - **`uv.lock` lives at the repo root**, not in `pkg/`. The workspace
345
+ shares one lockfile and one `.venv/`. Update via `uv sync` or
346
+ `uv lock` from any directory; never edit by hand.
347
+ - **One bridge per nvim** — N Neovim instances mean N entries in
348
+ `mcps.json`, each with its own `NVIM_LISTEN_ADDRESS`. No multi-attach
349
+ in v0.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cenk Kilic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: hyprpilot-nvim-mcp
3
+ Version: 1.1.2
4
+ Summary: MCP server bridging Neovim editor state into the hyprpilot agent's tool surface.
5
+ Project-URL: Homepage, https://github.com/hyprpilot/hyprpilot-nvim-mcp
6
+ Project-URL: Source, https://github.com/hyprpilot/hyprpilot-nvim-mcp
7
+ Author: Cenk Kilic
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Requires-Python: >=3.14
11
+ Requires-Dist: click>=8.1
12
+ Requires-Dist: fastmcp>=2.0
13
+ Requires-Dist: pynvim>=0.5
14
+ Requires-Dist: rich>=13.7
15
+ Description-Content-Type: text/markdown
16
+
17
+ # hyprpilot-nvim-mcp
18
+
19
+ `uvx`-runnable MCP server that bridges Neovim editor state into the
20
+ [`hyprpilot`](https://github.com/hyprpilot/hyprpilot) agent's tool surface.
21
+
22
+ > [!IMPORTANT]
23
+ > This package is in early bootstrap. Only the CLI entry point and a
24
+ > stub `ping` tool are wired today. Buffer / LSP / treesitter / dynamic
25
+ > tool registration land in follow-up PRs (see
26
+ > [`docs/plans/2026-05-09-nvim-mcp-handoff.md`](docs/plans/2026-05-09-nvim-mcp-handoff.md)).
27
+
28
+ ## Install in `mcps.json`
29
+
30
+ ```json
31
+ "neovim": {
32
+ "command": "uvx",
33
+ "args": ["hyprpilot-nvim-mcp"],
34
+ "env": { "NVIM_LISTEN_ADDRESS": "${NVIM_LISTEN_ADDRESS}" }
35
+ }
36
+ ```
37
+
38
+ `uvx` fetches the package from PyPI on first run and caches it.
39
+
40
+ ## Run from source
41
+
42
+ ```sh
43
+ uv sync
44
+ uv run hyprpilot-nvim-mcp
45
+ ```
46
+
47
+ ## Environment
48
+
49
+ | Variable | Default | Description |
50
+ | --- | --- | --- |
51
+ | `NVIM_LISTEN_ADDRESS` | _required_ | Path to the running Neovim's listen socket. |
52
+ | `HYPRPILOT_NVIM_MCP_LOG_LEVEL` | `INFO` | Stderr log level. |
53
+ | `HYPRPILOT_NVIM_MCP_ENABLE_EXEC_LUA` | `0` | Enables the `exec_lua` tool (RCE surface — leave off unless you know why). |
54
+
55
+ ## Development
56
+
57
+ ```sh
58
+ task install # uv sync --all-groups
59
+ task lint # ruff format --check + ruff check + mypy
60
+ task test # pytest
61
+ task build # uv build
62
+ ```
63
+
64
+ ## License
65
+
66
+ [MIT](LICENSE)
@@ -0,0 +1,50 @@
1
+ # hyprpilot-nvim-mcp
2
+
3
+ `uvx`-runnable MCP server that bridges Neovim editor state into the
4
+ [`hyprpilot`](https://github.com/hyprpilot/hyprpilot) agent's tool surface.
5
+
6
+ > [!IMPORTANT]
7
+ > This package is in early bootstrap. Only the CLI entry point and a
8
+ > stub `ping` tool are wired today. Buffer / LSP / treesitter / dynamic
9
+ > tool registration land in follow-up PRs (see
10
+ > [`docs/plans/2026-05-09-nvim-mcp-handoff.md`](docs/plans/2026-05-09-nvim-mcp-handoff.md)).
11
+
12
+ ## Install in `mcps.json`
13
+
14
+ ```json
15
+ "neovim": {
16
+ "command": "uvx",
17
+ "args": ["hyprpilot-nvim-mcp"],
18
+ "env": { "NVIM_LISTEN_ADDRESS": "${NVIM_LISTEN_ADDRESS}" }
19
+ }
20
+ ```
21
+
22
+ `uvx` fetches the package from PyPI on first run and caches it.
23
+
24
+ ## Run from source
25
+
26
+ ```sh
27
+ uv sync
28
+ uv run hyprpilot-nvim-mcp
29
+ ```
30
+
31
+ ## Environment
32
+
33
+ | Variable | Default | Description |
34
+ | --- | --- | --- |
35
+ | `NVIM_LISTEN_ADDRESS` | _required_ | Path to the running Neovim's listen socket. |
36
+ | `HYPRPILOT_NVIM_MCP_LOG_LEVEL` | `INFO` | Stderr log level. |
37
+ | `HYPRPILOT_NVIM_MCP_ENABLE_EXEC_LUA` | `0` | Enables the `exec_lua` tool (RCE surface — leave off unless you know why). |
38
+
39
+ ## Development
40
+
41
+ ```sh
42
+ task install # uv sync --all-groups
43
+ task lint # ruff format --check + ruff check + mypy
44
+ task test # pytest
45
+ task build # uv build
46
+ ```
47
+
48
+ ## License
49
+
50
+ [MIT](LICENSE)
@@ -0,0 +1,39 @@
1
+ ---
2
+ # https://taskfile.dev
3
+
4
+ version: "3"
5
+
6
+ tasks:
7
+ install:
8
+ desc: Sync the uv environment.
9
+ cmds:
10
+ - uv sync --all-groups
11
+
12
+ format:
13
+ desc: Format the source tree with ruff.
14
+ cmds:
15
+ - uv run ruff format .
16
+ - uv run ruff check --fix .
17
+
18
+ lint:
19
+ desc: Lint and type-check the source tree.
20
+ cmds:
21
+ - uv run ruff format --check .
22
+ - uv run ruff check .
23
+ - uv run mypy
24
+
25
+ test:
26
+ desc: Run the test suite.
27
+ cmds:
28
+ - uv run pytest
29
+
30
+ build:
31
+ desc: Build wheel and sdist into `pkg/dist/`.
32
+ cmds:
33
+ # `--out-dir dist` (relative to cwd, i.e. `pkg/dist`) overrides
34
+ # uv's default of writing workspace-member artifacts into the
35
+ # workspace ROOT's `dist/`. Local `task build` then drops
36
+ # output inside `pkg/`, and the `publish-pypi.yml` workflow's
37
+ # `upload-artifact path: pkg/dist/` finds them where it
38
+ # expects.
39
+ - uv build --out-dir dist
@@ -0,0 +1,10 @@
1
+ """hyprpilot-nvim-mcp — MCP bridge from Neovim editor state into the hyprpilot agent."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("hyprpilot-nvim-mcp")
7
+ except PackageNotFoundError:
8
+ __version__ = "0.1.0"
9
+
10
+ __all__ = ["__version__"]