<p align="center">
<img src="https://cloudcdn.pro/noyalib/v1/logos/noyalib.svg" alt="Noyalib logo" width="128" />
</p>
<h1 align="center">noyalib-mcp</h1>
<p align="center">
<strong>Model Context Protocol server exposing noyalib's
lossless YAML editing to AI agents (Claude Desktop, Claude
Code, Cursor, Zed, Continue.dev, …).</strong>
</p>
<p align="center">
<a href="https://github.com/sebastienrousseau/noyalib/actions"><img src="https://img.shields.io/github/actions/workflow/status/sebastienrousseau/noyalib/ci.yml?style=for-the-badge&logo=github" alt="Build" /></a>
<a href="https://crates.io/crates/noyalib-mcp"><img src="https://img.shields.io/crates/v/noyalib-mcp.svg?style=for-the-badge&color=fc8d62&logo=rust" alt="Crates.io" /></a>
<a href="https://docs.rs/noyalib-mcp"><img src="https://img.shields.io/badge/docs.rs-noyalib--mcp-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" alt="Docs.rs" /></a>
<a href="https://lib.rs/crates/noyalib-mcp"><img src="https://img.shields.io/badge/lib.rs-noyalib-orange.svg?style=for-the-badge" alt="lib.rs" /></a>
<a href="https://api.securityscorecards.dev/projects/github.com/sebastienrousseau/noyalib"><img src="https://api.securityscorecards.dev/projects/github.com/sebastienrousseau/noyalib/badge" alt="OpenSSF Scorecard" /></a>
</p>
---
## Contents
- [Install](#install) — Cargo, npx, Docker
- [Quick Start](#quick-start) — JSON-RPC handshake
- [Why this approach?](#why-this-approach) — design rationale
- [Connect](#connect) — per-client configuration
- [Tools exposed](#tools-exposed) — MCP tool reference
- [Examples](#examples) — runnable scripts
- [Verification](#verification) — cosign + npm provenance
- [When not to use noyalib-mcp](#when-not-to-use-noyalib-mcp)
- [Documentation](#documentation)
- [License](#license)
---
## Install
```bash
cargo install noyalib-mcp
```
For environments without a Rust toolchain (the typical AI-agent
deployment shape):
```bash
# npm wrapper — auto-downloads the matching binary on first run,
# caches under ~/.cache/noyalib-mcp/<version>/.
npx noyalib-mcp
# Container — multi-arch (linux/amd64, linux/arm64).
docker run --rm -i ghcr.io/sebastienrousseau/noyalib-mcp:latest
```
Both consume the same signed binary attached to every GitHub
Release. See [Verification](#verification) for the verify
commands.
---
## Quick Start
The server speaks JSON-RPC 2.0 over stdio with newline-delimited
frames, per the
[MCP specification](https://modelcontextprotocol.io). A typical
agent launches the binary as a child process, sends
`initialize`, then dispatches tool calls:
```json
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"agent","version":"0.0.1"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
{"jsonrpc":"2.0","id":3,"method":"tools/call",
"params":{"name":"format","arguments":{"yaml":"a:1\nb:2\n"}}}
```
---
## Why this approach?
AI agents that edit YAML configuration today regex-replace and
corrupt comments, indentation, and document structure. The same
agent fixing a port number in a Kubernetes manifest can shift
every comment by a line, reorder sibling keys, or strip
trailing whitespace that a downstream linter cared about.
noyalib's CST does the edits losslessly — a `set("server.port",
"9090")` rewrites only the byte span of the `8080` scalar; the
surrounding comments and indentation pass through untouched.
This server is the protocol shim that lets MCP-aware clients
drive that engine safely:
- **Lossless mutation.** `tools/call set` returns a document
byte-identical to the input outside the touched span.
- **Surgical reads.** `tools/call get` walks the dotted path
and returns just the value, not the whole tree.
- **Schema validation.** `tools/call validate --schema` runs
the same JSON Schema 2020-12 engine `noyavalidate` ships.
- **Stdio transport.** Standard MCP. Works with every
spec-compliant client.
---
## Connect
### Claude Desktop / Claude Code
```bash
claude mcp add noyalib $(which noyalib-mcp)
```
### Cursor
`~/.cursor/mcp.json`:
```json
{
"mcpServers": {
"noyalib": {
"command": "noyalib-mcp"
}
}
}
```
### Zed
`~/.config/zed/settings.json`:
```json
{
"context_servers": {
"noyalib": {
"command": { "path": "noyalib-mcp" }
}
}
}
```
### Continue.dev
`~/.continue/config.json`:
```json
{
"experimental": {
"modelContextProtocolServers": [
{ "transport": { "type": "stdio", "command": "noyalib-mcp" } }
]
}
}
```
### Any other MCP-aware client
Point at the binary; the transport is stdio with newline-
delimited JSON-RPC 2.0.
---
## Tools exposed
The v0.0.1 server registers two file-oriented tools — both
operate on a YAML file at `file: <path>`, not on inline source
strings, so an agent's edits land on disk losslessly:
| `noyalib_get` | `{ file: string, path: string }` | The raw source fragment at the dotted/indexed path (e.g. `server.host`, `items[0].name`). No re-quoting; no canonicalisation. |
| `noyalib_set` | `{ file: string, path: string, value: string }` | The file rewritten via the lossless CST so only the touched span changes; comments, blank lines, and sibling formatting survive byte-for-byte. The `value` is a YAML fragment (`0.0.2`, `"hello"`, `[1, 2, 3]`); a parse failure leaves the file unchanged. |
Each tool's full input schema lives in the response to
`tools/list`. The server also handles the standard
`initialize` / `initialized` / `notifications/cancelled`
lifecycle.
Format / parse / validate are not exposed as MCP tools today —
they're available via the [`noya-cli`](../noya-cli/README.md)
binaries (`noyafmt`, `noyavalidate`) and the
[`noyalib`](../noyalib/README.md) library API. Promotion to
first-class MCP tools is on the v0.0.2+ roadmap.
---
## Examples
Agent-driving demos under
[`crates/noyalib-mcp/examples/`](examples/):
| [`handshake.sh`](examples/handshake.sh) | `initialize` → `tools/list` smoke test. Confirms the binary speaks the protocol and announces the expected tools. |
| [`format-call.sh`](examples/format-call.sh) | `tools/call format` on a poorly-spaced document. Demonstrates that comments + indentation pass through the CST formatter unchanged. |
| [`set-then-get.sh`](examples/set-then-get.sh) | Round-trip the mutation surface: `set` rewrites `server.port`, `get` reads it back. Surgical edit; surrounding bytes untouched. |
```bash
chmod +x crates/noyalib-mcp/examples/*.sh
POSIX-shell only — no `jq`, no `node` dependencies. Pipe
through `jq -c .` if you want pretty-printed JSON responses.
---
## Verification
The npm wrapper and the GHCR image both consume the signed
binary attached to every GitHub Release. To verify the
underlying binary before trusting it:
```bash
COSIGN_EXPERIMENTAL=1 cosign verify-blob \
--certificate-identity-regexp 'https://github.com/sebastienrousseau/noyalib/' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
--certificate <artefact>.pem \
--signature <artefact>.sig \
<artefact>
```
The npm wrapper additionally carries an
[npm provenance attestation](https://docs.npmjs.com/generating-provenance-statements):
```bash
npm view noyalib-mcp provenance
```
Full cookbook: [`pkg/VERIFY.md`](https://github.com/sebastienrousseau/noyalib/blob/main/pkg/VERIFY.md).
---
## When not to use noyalib-mcp
- **You don't trust your AI agent with filesystem access at
all.** noyalib-mcp doesn't read or write files itself —
every operation takes the YAML document as a string argument
and returns the result as a string. The agent decides what
to do with the result. If the agent has filesystem access,
it can persist the response wherever it wants.
- **You need a sandboxed schema registry.** noyalib-mcp accepts
schemas as inline strings in `tools/call validate`; it does
not fetch schemas from URLs. If your workflow needs
network-resolved schemas, the agent is responsible for
fetching the schema first and passing the bytes.
---
## Compatibility
**MSRV: Rust 1.75.0** stable — same floor as the core
`noyalib` library. The MCP wire surface is text-only JSON-RPC
and pulls no nightly-only deps. CI verifies the floor on every
PR via the `Per-crate MSRV` workflow job. The bump policy
lives in
[`doc/POLICIES.md`](https://github.com/sebastienrousseau/noyalib/blob/main/doc/POLICIES.md#1-msrv-minimum-supported-rust-version).
**Tier-1 platforms** (CI-verified each PR): `aarch64-apple-darwin`,
`x86_64-unknown-linux-gnu`, `x86_64-pc-windows-msvc`. The
binary writes via atomic file replacement on every platform —
on Windows via `MoveFileExW(MOVEFILE_REPLACE_EXISTING |
MOVEFILE_WRITE_THROUGH)` semantics.
---
## Documentation
- **Engineering policies** (MSRV, SemVer, security, performance, concurrency, platform support, feature flags):
[`doc/POLICIES.md`](https://github.com/sebastienrousseau/noyalib/blob/main/doc/POLICIES.md)
- **Security policy**:
[`SECURITY.md`](https://github.com/sebastienrousseau/noyalib/blob/main/SECURITY.md)
- **API reference**: <https://docs.rs/noyalib-mcp>
- **Tools reference (input schemas + error codes)**:
[`doc/tools-reference.md`](https://github.com/sebastienrousseau/noyalib/blob/main/crates/noyalib-mcp/doc/tools-reference.md)
- **Agent integration (Claude Desktop, Cursor, Continue.dev)**:
[`doc/agent-integration.md`](https://github.com/sebastienrousseau/noyalib/blob/main/crates/noyalib-mcp/doc/agent-integration.md)
- **MCP specification**: <https://modelcontextprotocol.io>
- **Workspace README**:
<https://github.com/sebastienrousseau/noyalib#readme>
---
## License
Dual-licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0)
or [MIT](https://opensource.org/licenses/MIT), at your option.