Skip to content

feat(ai): TextStack MCP server — stdio transport + search_books (AI-047)#342

Merged
mrviduus merged 1 commit into
mainfrom
ai-047-mcp-stdio
Jun 16, 2026
Merged

feat(ai): TextStack MCP server — stdio transport + search_books (AI-047)#342
mrviduus merged 1 commit into
mainfrom
ai-047-mcp-stdio

Conversation

@mrviduus

Copy link
Copy Markdown
Owner

AI-047 — TextStack.Ai.Mcp project + stdio transport (Phase 8, slice 1/7)

A new executable that speaks Model Context Protocol over stdio (JSON-RPC 2.0) so Claude Desktop / Cursor / the mcp inspector can connect. Completes initialize + tools/list + tools/call with one tool — search_books — end-to-end. Foundation for the 7-tool surface (AI-048), SSE (AI-049), OAuth (AI-050), npm (AI-051), landing (AI-052), integration tests (AI-053).

Two foundational decisions (shape all of Phase 8)

  1. Official ModelContextProtocol SDK 1.4.0 (stable), not hand-rolled. MCP is a wire protocol (framing, lifecycle, capability negotiation); the hand-rolled story already lives where it's pedagogically load-bearing (AgentLoop, CrewOrchestrator, ToolDispatcher). The SDK tracks spec churn (a stated Phase-8 risk); the code we own — the runtime tool catalog + the HTTP bridge — is the defensible part.
  2. HTTP bridge, NOT in-process. The server is stateless (no DB/EF/OpenAI/config) — each MCP tool call → an HTTP call to the existing API. So auth / spoiler-gate / site-resolution / rate-limiting are enforced once, server-side (critical for remote SSE + OAuth in AI-049/050), it deploys anywhere (Docker service + npm wrapper later), and it matches the cost projection ("MCP calls hit existing endpoints"). Tool schemas are the shared source of truth across the three consumers (function-calling, in-app agents, MCP); invocation is HTTP.

What's in this slice

  • backend/src/Ai/TextStack.Ai.Mcp/ — Exe, references only the SDK + Hosting + Http (no Application/Infrastructure). Generic Host + AddMcpServer().WithStdioServerTransport().
  • search_books → public GET /search?q= (no per-user auth, so AI-047 doesn't block on AI-050's OAuth). Runtime McpToolCatalog served via the SDK's low-level ListTools/CallTool handlers, so AI-048 just appends descriptors.
  • Config via env: TEXTSTACK_API_URL (default prod), TEXTSTACK_SITE_HOST (Host header → site resolution), TEXTSTACK_MCP_TIMEOUT_SECONDS (15), TEXTSTACK_MCP_TOKEN (reserved for AI-050).

Correctness — stdout = JSON-RPC ONLY

The #1 rule: any stray stdout write corrupts framing silently. ClearProviders() + AddConsole(LogToStandardErrorThreshold=Trace) forces all logs to stderr; a throwing handler serializes a framed JSON-RPC error, never leaks to stdout (verified by audit). HTTP-bridge hardened (QA): transport/parse/timeout failures → a clean MCP IsError tool result (data for the model, not a protocol fault); 15s client timeout (was 100s default); real client cancellation still propagates, distinguished from timeout via when (!ct.IsCancellationRequested).

Tests — 36 (full unit suite 443)

Catalog mapping (real SearchResultDto field names), GET url + Host header + querystring escaping, arg-validation edge cases (missing/wrong-type/out-of-range/extra-prop/boundaries), error/timeout/cancellation paths, unknown-tool → clean error. StudyBuddy tool set-equality green; no ITool leaked. Over-the-wire stdio smoke deferred to AI-053 integration (handler-level coverage is the CI-stable layer).

Verify

  • dotnet build textstack.sln → 0 warnings/errors (new Exe project builds)
  • dotnet test tests/TextStack.UnitTests → 443 pass
  • dotnet format --verify-no-changes → clean

No new tables. No Docker mcp-server service yet (AI-049/051). GET /mcp/manifest + landing page = AI-052.

🤖 Generated with Claude Code

First Phase 8 slice: a new executable TextStack.Ai.Mcp that speaks Model
Context Protocol over stdio (JSON-RPC 2.0) so Claude Desktop / Cursor /
the mcp inspector can connect. Completes initialize + tools/list +
tools/call with one tool, search_books, end-to-end.

Two foundational decisions that shape all of Phase 8:
- Official ModelContextProtocol SDK 1.4.0 (stable), not hand-rolled — MCP
  is a wire protocol; the hand-rolled story lives in AgentLoop/
  CrewOrchestrator. SDK tracks spec churn; we own the tool catalog +
  HTTP bridge, the defensible part.
- HTTP bridge, NOT in-process — the server is stateless (no DB/EF/OpenAI),
  each MCP tool call → an HTTP call to the existing API, so auth /
  spoiler-gate / site-resolution / rate-limiting are enforced once
  server-side (matters for remote SSE + OAuth in AI-049/050). Tool
  schemas are the shared source of truth; invocation is HTTP.

search_books → public GET /search?q= (no per-user auth, so AI-047 doesn't
block on AI-050's OAuth). Runtime McpToolCatalog served via the SDK's
low-level ListTools/CallTool handlers so AI-048 just appends descriptors.

stdout is JSON-RPC ONLY — ClearProviders + AddConsole(stderr threshold);
a throwing handler serializes a framed JSON-RPC error, never leaks.
HTTP-bridge hardened (QA): transport/parse/timeout → clean MCP IsError
tool result (not a protocol fault), 15s client timeout, real client
cancellation still propagates (distinguished from timeout).

36 tests (catalog mapping, GET url + Host header + escaping, arg
validation edge cases, error/timeout/cancellation paths). Over-the-wire
stdio smoke deferred to AI-053 integration. No new tables, no Docker
service yet (AI-049/051).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mrviduus mrviduus merged commit dea73c0 into main Jun 16, 2026
5 checks passed
@mrviduus mrviduus deleted the ai-047-mcp-stdio branch June 16, 2026 14:47
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