Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The noyalib-mcp Manual

A Model Context Protocol server giving AI agents lossless YAML editing. noyalib-mcp is one of the six crates of the noyalib family and releases in lockstep with the core library at the identical =0.0.X.

This manual is the rendered form of the Markdown in the repository’s docs/ directory; the repository copy is canonical. The family manual for the core library, with the user guide, migrations, and policies, is at https://sebastienrousseau.github.io/noyalib/manual/.

Pages

Install, quick start, and the full surface are in the README. The CHANGELOG is the complete release record.

Agent integration

Wire noyalib-mcp into the AI agent platforms that speak MCP. Each section is self-contained — find your agent, paste the config, restart, and the noyalib_get / noyalib_set tools appear in the agent’s tool palette.

For the full tool surface and JSON Schema, see tools-reference.md.

Install the binary

cargo install noyalib-mcp

The binary lands in ~/.cargo/bin/noyalib-mcp. Throughout this document we assume that path is on PATH; if not, substitute the absolute path in each agent’s config.

Claude Desktop

~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):

{
  "mcpServers": {
    "noyalib": {
      "command": "noyalib-mcp",
      "args": []
    }
  }
}

Restart Claude Desktop. The noyalib_get and noyalib_set tools appear under the tool icon in any conversation.

To restrict the tools to a specific working directory:

{
  "mcpServers": {
    "noyalib": {
      "command": "noyalib-mcp",
      "args": [],
      "env": {
        "NOYALIB_MCP_ROOT": "/Users/me/Projects/configs"
      }
    }
  }
}

(The NOYALIB_MCP_ROOT env var is honoured by the server when present; tool calls that reference paths outside this root return -32000 “outside permitted root”.)

Cursor

~/.cursor/mcp.json:

{
  "mcpServers": {
    "noyalib": {
      "command": "noyalib-mcp"
    }
  }
}

Cursor picks up the change without restart on most workspaces. Open the AI sidebar; the noyalib tools are listed.

Continue.dev

~/.continue/config.json:

{
  "mcpServers": [
    {
      "name": "noyalib",
      "command": "noyalib-mcp"
    }
  ]
}

Restart Continue. Tools appear in the agent’s tool palette.

Generic MCP client

Any client that speaks MCP over stdio can use (the server is dual-era: 2026-07-28 per-request _meta / server/discover, or the 2025-06-18 initialize handshake shown here):

noyalib-mcp

The server reads JSON-RPC requests from stdin and writes responses to stdout, line-delimited. No flags, no environment variables required for the basic case.

Verifying the server is wired up

In any MCP client, ask the agent: “List your available tools.” The agent should mention noyalib_get and noyalib_set (or describe them as “read/write YAML values at paths”).

For a CLI smoke test independent of any agent:

echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{}}}' \
  | noyalib-mcp

Should respond with the server capabilities. Then:

{
  echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{}}}'
  echo '{"jsonrpc":"2.0","method":"notifications/initialized"}'
  echo '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
} | noyalib-mcp

Should list both noyalib_get and noyalib_set with their full input schemas. The full handshake script lives in crates/noyalib-mcp/examples/handshake.sh.

Common agent prompts

Agents wielding the noyalib MCP tools handle prompts like:

“Update the version field in Cargo.toml to 0.0.2.”

Agent: calls noyalib_get with file=Cargo.toml, path=version, sees the current value, then calls noyalib_set with the new value. The diff in git diff is one line.

“Read the database connection string from config/prod.yaml.”

Agent: noyalib_get with path=database.connection_string.

“Increment the replicas count in every *.yaml under k8s/.”

Agent: Glob for the files, then per-file noyalib_get to read the current count, then noyalib_set to write the incremented value. Each file’s comments and indentation survive intact.

Why MCP for YAML editing?

Existing approaches:

  • sed / yq — text-or-AST replacement that often loses comments, normalises indentation, and breaks anchor references
  • Edit tool with full file rewrite — agent regenerates the whole file from memory, which scrambles comments, comments drift, and is hard to review
  • MCP noyalib_set — surgical edit through the lossless CST. One value changes; everything else is byte-identical

This is the same property that makes noyafmt --fix safe: the green tree preserves trivia by construction.

Security considerations

  • Server has full filesystem access — it can read and write any path the user running the binary can. For untrusted environments, use NOYALIB_MCP_ROOT to confine to a directory.
  • No authentication on the JSON-RPC channel — MCP runs over stdio; the security boundary is the OS process. Don’t expose the binary over a network without an authenticating proxy.
  • Path traversal — the server resolves paths as-given. If client-side path sanitisation matters, sanitise before calling.

For the full security policy see the workspace SECURITY.md.

Troubleshooting

Tools don’t appear in the agent

  1. Verify the binary works: noyalib-mcp --version
  2. Verify the agent’s config syntax (most agents log MCP server startup; check the agent’s logs)
  3. Restart the agent — most clients only re-read the MCP config on launch

Edit fails with “path not found”

noyalib_set requires the parent path to exist. To set server.host when server doesn’t exist, first set server to {}, then set server.host. Or pre-populate the file with the parent structure.

Agent says “no such tool”

Some agents serve a curated tool list and require explicit allow-listing. Check the agent’s tool-permissions UI; toggle noyalib_get and noyalib_set on.

Working directory differs from expectation

The server inherits the working directory of its parent process (the agent). Relative paths in tool arguments resolve from there. Use absolute paths when in doubt.

MCP tools reference

The complete list of tools noyalib-mcp advertises via tools/list, with their JSON Schema input shape and example invocations. This file is the user-facing reference; the authoritative source is crates/noyalib-mcp/src/tools.rs::descriptors().

The server is dual-era: it speaks MCP 2026-07-28 (stateless per-request _meta, server/discover) and answers the legacy 2025-06-18 initialize handshake. Tool calls go through tools/call; see the MCP spec for the JSON-RPC envelope.

Tool list

The server advertises 2 tools. Every tool delegates to noyalib::cst::Document so edits round-trip with comments, indentation, and sibling entries preserved byte-for-byte.

noyalib_get

Read the YAML value at a dotted/indexed path in a file. Returns the source slice exactly — no re-quoting, no canonicalisation. Preserves comments and formatting for any later noyalib_set round-trip.

Input schema:

{
  "type": "object",
  "properties": {
    "file": {
      "type": "string",
      "description": "Path to the YAML file on disk."
    },
    "path": {
      "type": "string",
      "description": "Dotted/indexed path into the YAML, e.g. `server.host` or `items[0].name`."
    }
  },
  "required": ["file", "path"]
}

Example invocation:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "noyalib_get",
    "arguments": {
      "file": "/etc/myapp/config.yaml",
      "path": "server.host"
    }
  }
}

Example response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      { "type": "text", "text": "api.example.com" }
    ]
  }
}

Error codes:

CodeMeaning
-32000I/O error reading the file
-32001Parse error in the YAML
-32002Path not found in the document
-32602Missing or non-string argument

noyalib_set

Set the YAML value at a dotted/indexed path in a file. Only the touched span is rewritten — every comment, blank line, and sibling entry is preserved byte-for-byte. Useful for Renovate-style version bumps and config patches by AI agents.

Input schema:

{
  "type": "object",
  "properties": {
    "file": {
      "type": "string",
      "description": "Path to the YAML file on disk."
    },
    "path": {
      "type": "string",
      "description": "Dotted/indexed path into the YAML."
    },
    "value": {
      "type": "string",
      "description": "Replacement value as a YAML fragment (e.g. `0.0.2`, `\"hello\"`, `[1, 2, 3]`). Must parse in the target position; the document is left unchanged on parse error."
    }
  },
  "required": ["file", "path", "value"]
}

Example invocation:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "noyalib_set",
    "arguments": {
      "file": "/etc/myapp/config.yaml",
      "path": "server.port",
      "value": "9090"
    }
  }
}

Example response:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [
      { "type": "text", "text": "set server.port = 9090 in /etc/myapp/config.yaml (lossless: comments and formatting preserved)" }
    ]
  }
}

Error codes:

CodeMeaning
-32000I/O error reading or writing the file
-32001Parse error in the source YAML
-32003The new value cannot be applied at the path (parse error in the fragment, or path doesn’t exist)
-32602Missing or non-string argument

Lossless edit guarantee

Both tools route through noyalib::cst::Document, so the on-disk file after a noyalib_set:

  • Has the same comments at the same positions
  • Has the same blank lines and indentation
  • Has the same anchor / alias relationships
  • Has the same key order
  • Has the new value at the requested path

The only bytes that change are the bytes for the value itself. Empirically: git diff after a single noyalib_set shows a 1-line change. This is the contract.

Path syntax

The path argument follows the same syntax as noyalib::Value::get_path and noyalib::cst::Document::get:

PathSelects
nameTop-level key name
server.hosthost inside the mapping at server
items[0]First element of the sequence at items
items[0].namename inside the first element
services.api.replicasDeeply nested

Path-not-found semantics

noyalib_get returns error -32002 if the path does not exist. noyalib_set will create missing intermediate keys at the same indent level when possible; if the parent path does not exist or points to an incompatible shape (e.g. server.host[0] when server.host is a string), it returns -32003.

Concurrency

The server processes one request at a time per stdio session. Concurrent edits to the same file from multiple sessions are not serialised by the server — clients should coordinate externally (e.g. via flock) if multiple agents may edit the same file.

Capabilities advertised at initialize

{
  "protocolVersion": "2025-06-18",
  "serverInfo": {
    "name": "noyalib-mcp",
    "version": "0.0.1"
  },
  "capabilities": {
    "tools": {}
  }
}

The server does not advertise prompts or resources capabilities — it’s tool-only.

MCP delivery comparison

Delivery surfaceLossless YAML editsRemote transportTyped tool results
noyalib-mcpYesStreamable HTTP and SSEYes
noya-cliYesNoNot applicable
Native noyalibYesNoRust types

noyalib-mcp is the protocol adapter when an MCP-aware agent must reach the same parser and lossless editor used by the native tools. It is not a general MCP gateway or an authentication service. Compare MCP servers by tool contract, transport, trust boundary, protocol revision, and deployment model rather than by tool count alone.

Benchmarks

The mcp_tools Criterion harness measures representative tool operations.

cargo bench --bench mcp_tools

Transport tests cover protocol correctness separately from the microbenchmark. Record the CPU, operating system, Rust version, commit, input size, transport, and command with any result. CI does not use shared-runner latency as a pass/fail threshold.

Architecture

noyalib-mcp is a Model Context Protocol server that gives an AI agent lossless YAML editing. It speaks MCP over stdio, streamable HTTP or the older HTTP+SSE transport, and delegates every YAML operation to noyalib::cst::Document, so an edit preserves comments, indentation, and sibling entries byte for byte.

The protocol itself is rmcp, the official Rust SDK: the framing, the handshake, the sessions and the protocol revisions are its, and this crate supplies the tools and the text a model reads. See ADR 0001 for why.

Layers

  • src/main.rs is three lines: it hands the library’s handler to transport::run.
  • src/transport.rs is the transport dispatch: the command line (--transport, --host, --port, --version, --help), the stdio session, the streamable HTTP listener at /mcp, and a bridge for the 2024-11-05 HTTP+SSE transport at /sse and /messages/ (in src/transport/sse.rs) that the SDK no longer ships a server for. It depends only on the SDK and on a ServerHandler passed in — nothing in it knows what the tools are — so it is the same module in every Rust server of the suite.
  • src/lib.rs is the handler: YamlServer holds the tool and prompt routers, describes the server for initialize and server/discover, serves the resources, and turns a call for a tool that does not exist into a result the model can read instead of the SDK’s -32602.
  • src/tools.rs is the YAML work. get, set, set_multidoc, parse, edit and validate are plain functions that know nothing about MCP; the #[tool_router] block below them is what tools/list advertises and tools/call reaches. Each tool returns its answer twice — as text for the model, and as structuredContent against the outputSchema it declares.
  • src/resources.rs backs resources/list, resources/templates/list, and resources/read with read-only reference material an agent can pin without a tool call: the failure taxonomy, the tool reference, and per-tool descriptors. None of it touches the filesystem or accepts caller paths.
  • src/prompts.rs backs prompts/list and prompts/get with a pure text template that teaches the host model the recommended noyalib_get / noyalib_set workflow.

Two kinds of failure

A tool that ran and could not do the job — a file that cannot be read, YAML that does not parse, a path that does not exist, a fragment that cannot be applied — is a successful JSON-RPC response carrying isError: true and text the model can act on. A tool the server does not have is reported the same way, naming the ones that exist: the stateless HTTP revision carries the SDK’s -32602 as an HTTP 400, which the client reports as a transport fault and the model never reads.

A request the protocol rejects — an unknown method, a resource that does not exist, a routing header that disagrees with the body — is a JSON-RPC error, which the client handles.

Trust boundary

Everything the agent sends arrives through the SDK and reaches the six functions as deserialised arguments. Those functions are the surface this crate owns, and the stateless three are fuzzed directly (fuzz_targets/tools.rs); CI replays the seed corpus on every push.

The HTTP listeners exist only when --transport asks for them, bind the loopback interface unless --host says otherwise, and do not authenticate: a routable deployment belongs behind a gateway the operator trusts.

Distribution

server.json and glama.json are the registry manifests; both carry the version and the ghcr.io image tag, and the release workflow’s Validate job refuses a tag they disagree with. pkg/npm-wrapper ships the same binary through npm. The container entrypoint is stdio, which is what docker run -i gives an agent; the HTTP transports are reached from it by passing --transport.

Lockstep

The crate pins noyalib at the identical =0.0.X and releases with it (core ADR-0005).

Engineering policies

Version and dependency policy

noyalib-mcp releases in strict lockstep with noyalib. Version 0.0.58 pins the core at exactly =0.0.58. Release work uses feat/v0.0.58; each subsequent iteration increments exactly 0.0.1.

Minimum Rust version

The minimum supported Rust version is 1.89.0, the floor shared across the noyalib family; the MCP SDK stack alone requires 1.88.0. The manifest declares it and CI verifies it. A future increase is a breaking-axis change and requires a changelog entry.

Protocol and transport stability

Tool names, input and output schemas, prompts, resources, transport routes, header behaviour, and advertised protocol revisions are public compatibility surfaces. Bind network transports to loopback by default. Authentication and authorization belong at the deployment boundary.

During 0.0.x, the patch component is the breaking axis. Family-wide policies live in the core POLICIES.md.

Repository standard compliance grade

Assessment date: 2026-09-20. Source rubric: /Users/seb/Code/REPO-STANDARD.md. This is a conservative static assessment of checked-in evidence, not a substitute for successful CI runs.

Result

15/24 signals (62%). README template: pass. Strict candidate tier: L1.

CategorySignalsSummary
Identity and README3/3Canonical structure and MCP-specific policy present
Documentation2/3Manual, architecture, and ADR evidence present
Build and install UX1/3Native build present; generated completions/manpage signals are absent
Releases and binaries2/3Automated releases present; complete L3 evidence remains open
Packaging2/3Container distribution present; Repology/reproducibility are incomplete
CI quality gates2/3Matrix and coverage signals present
Supply chain2/3Foundation controls present; advisory-audit detection remains open
Community1/3Foundation governance present; L2 template and docs-lint signals are open

The cumulative tier remains L1. Priority work is generated CLI assets, distribution tracking and reproducibility, explicit advisory-audit CI, and audit-visible issue/PR templates and markdown lint enforcement.

Detailed README reference

Noyalib logo

noyalib-mcp

Model Context Protocol server exposing noyalib's lossless YAML editing to AI agents (Claude Desktop, Claude Code, Cursor, Zed, Continue.dev, …).

Build Crates.io Docs.rs lib.rs OpenSSF Scorecard OpenSSF Best Practices


Contents


Install

cargo install noyalib-mcp

For environments without a Rust toolchain (the typical AI-agent deployment shape):

# npm wrapper: downloads the matching binary on first run, checks it
# against the SHA-256 in the package, caches it under
# ~/.cache/noyalib-mcp/<version>/. It refuses to run until releases
# publish per-platform archives and their digests.
npx @sebastienrousseau/noyalib-mcp

# Container — multi-arch (linux/amd64, linux/arm64).
docker run --rm -i ghcr.io/sebastienrousseau/noyalib-mcp:latest

Split from the monorepo since v0.0.13. Prior versions shipped from sebastienrousseau/noyalib/crates/noyalib-mcp/ under the workspace-lockstep release cadence. From v0.0.13 onward noyalib-mcp lives here as its own crate, still released in strict lockstep with the parent noyalib at the same version. See ADR-0005 for the rationale and rollback recipe.

Both consume the same signed binary attached to every GitHub Release. See Verification for the verify commands.


Requirements

  • Rust 1.89.0 or newer to build from source: rust-version in the manifest, enforced by the msrv-core CI job on every push.
  • Any tier-1 platform. CI runs the tests on Linux, macOS, and Windows with the stable, beta, and nightly toolchains; stable is the gate, beta and nightly are early warning.
  • The matching core. This crate pins noyalib at the identical =0.0.X and releases in lockstep with it; Cargo resolves that pin for you.
  • An MCP client speaking JSON-RPC 2.0 over stdio (2025-06-18 or 2026-07-28 protocol eras); the README’s Connect section lists tested hosts.

Quick Start

The server speaks JSON-RPC 2.0 over stdio with newline-delimited frames, per the MCP specification. A typical agent launches the binary as a child process, sends initialize, then dispatches tool calls:

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","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

claude mcp add noyalib $(which noyalib-mcp)

Cursor

~/.cursor/mcp.json:

{
  "mcpServers": {
    "noyalib": {
      "command": "noyalib-mcp"
    }
  }
}

Zed

~/.config/zed/settings.json:

{
  "context_servers": {
    "noyalib": {
      "command": { "path": "noyalib-mcp" }
    }
  }
}

Continue.dev

~/.continue/config.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

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 — Takes { file: string, path: string }; returns the raw source fragment at the dotted/indexed path (e.g. server.host, items[0].name). No re-quoting; no canonicalisation.
  • noyalib_set — Takes { file: string, path: string, value: string }; returns 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.
  • noyalib_parse — Takes { yaml: string }; returns the JSON data model of the text (tags stripped, a stream as an array). Stateless: nothing on disk is touched.
  • noyalib_edit — Takes { yaml: string, path: string, value: string }; returns the whole text with that one value replaced losslessly. Stateless.
  • noyalib_validate — Takes { yaml: string, schema?: string }; returns valid with either the parse error (line and column) or every JSON Schema violation with its path. Stateless.

Each tool’s full input schema lives in the response to tools/list. The server also handles the standard initialize / initialized / notifications/cancelled lifecycle.

Command-line workflows are available through the noya-cli binaries (noyafmt, noyavalidate). Lower-level embedding uses the noyalib library API.


Examples

Agent-driving demos under examples/:

ScriptWhat it shows
handshake.shinitialize → tools/list smoke test. Confirms the binary speaks the protocol and announces the expected tools.
format-call.shtools/call format on a poorly-spaced document. Demonstrates that comments + indentation pass through the CST formatter unchanged.
set-then-get.shRound-trip the mutation surface: set rewrites server.port, get reads it back. Surgical edit; surrounding bytes untouched.
chmod +x crates/noyalib-mcp/examples/*.sh
crates/noyalib-mcp/examples/handshake.sh | jq -c .

POSIX-shell only — no jq, no node dependencies. Pipe through jq -c . if you want pretty-printed JSON responses.


Verification

GitHub Releases ship the crate archive and a CycloneDX SBOM, each with a sigstore bundle and checksums; the GHCR image is built from the tagged source. Pre-built binaries are not attached to releases yet. To verify a release artefact:

COSIGN_EXPERIMENTAL=1 cosign verify-blob \
  --certificate-identity-regexp '^https://github\.com/sebastienrousseau/noyalib-mcp/\.github/workflows/release\.yml@refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
  --bundle <artefact>.bundle \
  <artefact>

The npm wrapper additionally carries an npm provenance attestation:

npm view noyalib-mcp provenance

Full cookbook: 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.89.0 stable, the floor shared across the noyalib family and the maintainer’s other Rust crates (noyalib ADR-0014). The MCP SDK alone needs 1.88.0. The MCP wire surface itself is text-only JSON-RPC and pulls no nightly-only dependencies. CI verifies the floor on every PR via the msrv-core workflow job. The bump policy lives in docs/POLICIES.md.

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

The four entry points, identical across every repo in the family:


Sibling MCP servers by the same author — open-source, Apache-2.0 licensed, targeting banking and financial-services AI agents. noyalib-mcp complements them by giving agents lossless YAML editing for structured configuration files:

ServerPurpose
pain001-mcpGenerate & validate ISO 20022 pain.001 payment initiation files (Customer Credit Transfer)
bankstatementparser-mcpParse bank statements (BAI2, MT940/MT942, CAMT.053, OFX, CSV) into structured transactions
camt053-mcpParse & reconcile ISO 20022 camt.053 bank-to-customer statements — CBPR+/HVPS+ ready
acmt001-mcpGenerate & validate ISO 20022 acmt.001 account management messages

MCP Registry

mcp-name: io.github.sebastienrousseau/noyalib-mcp


Conformance

Every push runs the official yaml-test-suite through this server’s tools, from the same vendored suite and the same core commit as the noyalib core: 195 of 195 addressable cases (the other 211 have no top-level key for noyalib_get to read; noyalib_parse sees all of them). A two-document configuration that uses most of YAML at once (tests/fixtures/ultra-complex/) parses to exactly its expected JSON through noyalib_parse. Details and the family table: noyalib.com/conformance.

License

Dual-licensed under Apache 2.0 or MIT, at your option.

Architecture decision records

One file per decision that shaped this server: context, options, decision, consequences. Change a decision by adding a new record that supersedes the old one. Decisions that shape the whole family — the lockstep release model, the workspace split — live in the core repository’s docs/adr/.

0001. Serve stdio, streamable HTTP and SSE from one command line

  • Status: Accepted
  • Date: 2026-09-19
  • Deciders: maintainer

Context

Until v0.0.45 the server was a hand-written JSON-RPC loop over stdio, speaking two revisions it implemented itself: the 2025-06-18 initialize handshake and a dual-era reading of 2026-07-28 (per-request _meta, server/discover). A client spawned the process; nothing listened. That fits a developer’s laptop. It does not fit a shared deployment, a gateway that fans one server out to many agents, or an auditor that speaks HTTP.

Keeping up with the specification by hand is a protocol project, not a YAML one — and the hand-written loop had already drifted: it answered initialize with 2025-06-18 while the current handshake revision is 2025-11-25, it had no session layer, and it could not have served the mirrored routing headers (SEP-2243) that 2026-07-28 requires over HTTP. The official Rust SDK, rmcp, implements all of it, is maintained by the people who write the specification, and is what the sibling Rust servers of the suite are moving to.

Options considered

  1. Stay on stdio and the hand-written loop, and leave remote use to a wrapper process.
  2. Extend the hand-written loop with HTTP, sessions and both current revisions.
  3. Adopt rmcp for the protocol, keep the six tools as plain functions, and put the transport dispatch in one module copied verbatim into every Rust server of the suite.

Decision

Option 3. noyalib-mcp runs stdio; noyalib-mcp --transport streamable-http listens on --host/--port at /mcp and speaks both current protocol revisions on that one endpoint, streaming responses as server-sent events and offering the server-to-client stream on GET; noyalib-mcp --transport sse serves the older HTTP+SSE transport at /sse and /messages/. The SDK has no server side for that last transport, so src/transport.rs carries a small bridge: one event stream per session, a POST endpoint that feeds it, and the SDK’s service running over an in-memory pair of channels.

The module binds loopback unless told otherwise and adds no authentication of its own: a routable deployment sits behind a gateway the operator trusts. It depends only on the SDK and on a ServerHandler passed in, so the same file serves oxml-mcp and rlg-mcp unchanged. It is the same code here, re-wrapped by this repository’s rustfmt settings — oxml-mcp pins max_width = 80 and this crate pins rustfmt’s defaults, so a byte-identical copy would fail cargo fmt --check.

The tools keep their names, argument names and descriptions. Each now also declares an outputSchema and returns its answer as structured content beside the text, carries behaviour annotations, and gives every argument an example so a client that has no document of its own can still generate a well-formed call. A tool that ran and failed is an isError result the model can read; so is an unknown tool, because the stateless revision carries the SDK’s -32602 as an HTTP 400 that no model ever sees. A request the protocol rejects is still a JSON-RPC error.

Consequences

main() delegates to transport::run, which is the same file in every server, so the suite is started, documented and tested the same way. The server is verified over streamable HTTP with an independent MCP auditor in both protocol eras and over SSE with the Python SDK’s client before release.

The crate is no longer close to dependency-free: the SDK brings tokio, axum, schemars and uuid, and the MSRV moves to 1.88. The security posture changes shape rather than degrading: the tools still touch only the files a caller names, and the listeners exist only when asked for on the command line.

The failure taxonomy moves from JSON-RPC codes to result text. A client that switched on -32000/-32001/-32002/-32003 now reads isError and the message, which is what a model was always shown. That is a wire-visible change inside a 0.0.x line, taken deliberately: the codes were this server’s invention, the results are the specification’s.

A stdio client must now perform the handshake — or, in the stateless revision, name its protocol version in _meta — before its first request. A bare tools/call as the first line, which the old loop tolerated, is refused.

noyalib-mcp v0.0.17 Release Notes

Lockstep release with noyalib v0.0.17 (ADR-0005: publishes =0.0.17 pinned to the core).

What changed

  • No crate-content change beyond the lockstep bump.
  • noyalib pin =0.0.16 -> =0.0.17; crate version bumped in lockstep.

Repository hardening (CI/docs only)

Coverage, MSRV, CodeQL, and OpenSSF Scorecard gates plus upstream cargo-vet audit imports were added across the satellites in this cycle; branch protection now makes commit signing unskippable. No user-facing change.

Upgrading

Requires Rust 1.86.0+.

noyalib-mcp v0.0.16 Release Notes

Lockstep release with noyalib v0.0.16 (ADR-0005 strict-lockstep: the MCP server always publishes =X.Y.Z pinned to the core). No wire-format or tool-inventory change to the MCP surface.

What changed

Security

  • crossbeam-epoch bumped to 0.9.20 (RUSTSEC-2026-0204). Closes an invalid-pointer-dereference advisory in the fmt::Pointer impl for Atomic/Shared, previously locked at a vulnerable version through a transitive dependency.

Dependencies

  • noyalib pin =0.0.15 → =0.0.16 and the crate version bumped in lockstep.
  • MSRV raised 1.85.0 → 1.86.0, matching the single lockstep floor the core adopted in v0.0.16. The crate builds cleanly on 1.86.

Docs / registry manifests

  • README Tools section reformatted as a bullet list.
  • server.json and glama.json (MCP-registry manifests) aligned to 0.0.16 — version field and the ghcr.io/...:0.0.16 image tags. The release Validate job enforces that these match the tag.

Engineering / CI (no user-facing change)

  • Signed-history enforcement. Commits above the last release were re-signed and the branch-protection ruleset split so signatures are required with no bypass, while signed release pushes still work.
  • Supply-chain hardening. Imported the upstream audit sets the core uses (mozilla, google, et al.), so most dependency bumps no longer churn cargo-vet; a dependabot-vet workflow auto-refreshes the remaining exemptions.
  • New CI gates brought to parity with the core: a coverage gate (measured 95.7 % regions / 98.3 % functions / 94.4 % lines, enforced against the 93/96/94 floor), an MSRV gate verifying the 1.86 build, CodeQL, and OpenSSF Scorecard.

What did not change

  • The MCP tool inventory, JSON-RPC wire format, and stdio transport.
  • #![forbid(unsafe_code)] — intact.
  • Public API of the noyalib_mcp library.

Upgrading

Drop-in for most users:

noyalib-mcp = "0.0.16"

You must be on Rust 1.86.0+ (or stay on v0.0.15). The container image is ghcr.io/sebastienrousseau/noyalib-mcp:0.0.16.