feat(ai): TextStack MCP server — stdio transport + search_books (AI-047)#342
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
AI-047 —
TextStack.Ai.Mcpproject + 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
mcpinspector can connect. Completesinitialize+tools/list+tools/callwith 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)
ModelContextProtocolSDK 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.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→ publicGET /search?q=(no per-user auth, so AI-047 doesn't block on AI-050's OAuth). RuntimeMcpToolCatalogserved via the SDK's low-levelListTools/CallToolhandlers, so AI-048 just appends descriptors.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 MCPIsErrortool result (data for the model, not a protocol fault); 15s client timeout (was 100s default); real client cancellation still propagates, distinguished from timeout viawhen (!ct.IsCancellationRequested).Tests — 36 (full unit suite 443)
Catalog mapping (real
SearchResultDtofield names),GETurl + 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; noIToolleaked. 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 passdotnet format --verify-no-changes→ cleanNo new tables. No Docker
mcp-serverservice yet (AI-049/051).GET /mcp/manifest+ landing page = AI-052.🤖 Generated with Claude Code