Skip to content

Expose the tool layer over MCP - #8

Merged
xfznprojects merged 2 commits into
mainfrom
mcp-server
Sep 17, 2026
Merged

xfznprojects merged 2 commits into
mainfrom
mcp-server

Conversation

@xfznprojects

Copy link
Copy Markdown
Owner

SessionIQ now speaks the Model Context Protocol, so any MCP client — Claude Desktop, Cursor, or an agent of your own — can query a local library.

What it exposes

The same seven tools the in-app assistant calls: list_projects, search_library, filter_assets, compute_stat, asset_details, similar_tracks and next_up.

Each one delegates to the same LibraryToolbox, so a question answered through MCP and the same question asked inside the app are computed by identical code. That logic is what evals/ already measures — the tool layer scores 7/7 — so this adds a new surface without adding a second implementation to maintain.

Design decisions

The SDK is an optional extra (pip install -e ".[mcp]"), imported lazily, so the base install is unaffected. The entry point reports a missing extra in plain language rather than letting a protocol error surface.

stdio transport. MCP servers for local tools normally speak stdio, which fits a local-first app exactly: no port, no network exposure, the client launches it.

Retrieval runs in memory, not through ChromaDB. This is a read-only query surface that should start instantly and must not need an embedding model. The lexical-plus-metadata blend scored 0.733 hit@1 against 0.767 with vectors — not worth a model download here. Documented in the module.

Read-only, and reads at startup. It answers questions and changes nothing. It sees the library as of when the client launched it, which is stated in the README so nobody is surprised that a file added mid-session is missing.

A bug this surfaced in the shared path

LibraryToolbox.execute() truncated over-long results by chopping the serialised string and appending '…"}', which produced invalid JSON. The model coped with it; an MCP client would simply fail to parse it, and a large filter_assets over a real library hits this.

Payloads are now trimmed structurally — whole list items first, then oversized strings — so the JSON stays valid, and a payload that still cannot fit reports that rather than emitting something malformed. This fixes the in-app path too.

Verification

  • 187 tests pass (up from 169), ruff clean
  • End-to-end with a real MCP client: I spawned the server as a subprocess and drove it over an actual stdio connection. It initialised, reported its instructions, listed all seven tools with correct required parameters, and returned the right answers — compute_stat(rms_db, max) → loud.wav @ -9.0 dB, filter_assets(status=Ready) → 1 match, list_projects → both projects.
  • 13 MCP tests cover loading, wiring, the exposed surface and delegation; they skip cleanly when the optional SDK is absent, and CI installs it so the surface stays covered
  • No stray stdout output, which would corrupt the protocol — a real failure mode for stdio servers

Note

The SDK is at version 2.x, where FastMCP was renamed to MCPServer and moved to mcp.server.mcpserver. This is built against the current API rather than the superseded one.

SessionIQ now speaks the Model Context Protocol, so any MCP client — Claude
Desktop, Cursor, an agent of your own — can query a local library. It exposes
the same seven tools the in-app assistant calls and delegates to the same
LibraryToolbox, so a question answered here and the same question asked in the
app are computed identically. That logic is already covered by evals/.

The SDK is an optional extra. It is imported lazily so the base install is
unaffected, and the entry point reports a missing extra rather than raising a
protocol error. Retrieval runs in memory rather than through ChromaDB: this is
a read-only query surface that should start instantly and must not need an
embedding model, and the lexical-plus-metadata blend scored 0.733 hit@1 against
0.767 with vectors.

Building this surfaced a bug in the shared tool path. execute() truncated
over-long results by chopping the serialised string and appending a marker,
which produced invalid JSON. The model coped with it; an MCP client would
simply fail to parse. Payloads are now trimmed structurally — whole list items
first, then oversized strings — so the JSON stays valid, and a payload that
still cannot fit reports that instead of emitting something malformed.
Thirteen tests cover library loading, toolbox wiring, the exposed tool set, and
that each MCP tool delegates rather than reimplementing. They skip cleanly when
the optional SDK is absent, and CI installs it so the surface stays covered.

Five more cover the trimming, including the case that used to emit invalid
JSON: a filter over sixty assets, parsed through the same helper the app uses.
@xfznprojects
xfznprojects merged commit 2293bed into main Sep 17, 2026
5 checks passed
@xfznprojects
xfznprojects deleted the mcp-server branch September 17, 2026 21:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant