feat(mcp): read-only MCP server baked into the CLI (things mcp)#98
Open
ryanlewis wants to merge 3 commits into
Open
feat(mcp): read-only MCP server baked into the CLI (things mcp)#98ryanlewis wants to merge 3 commits into
things mcp)#98ryanlewis wants to merge 3 commits into
Conversation
Add a `things mcp` subcommand that runs a Model Context Protocol server over stdio, exposing the read-only CLI surface as six typed tools (things_list/show/search/projects/areas/tags). Aimed at MCP hosts that can't shell out (Claude Desktop) and a typed alternative for those that can (Cursor, Claude Code). - internal/mcpserver: server + handlers on the official modelcontextprotocol/go-sdk v1.6.1. Each tool opens the DB per call (read-only), runs the existing db query, and returns the same JSON the CLI emits with --json (no ANSI). show reports ambiguous titles with their UUIDs; the server fails fast at startup if the DB is unreachable. - cmd/things: wire the `mcp` command (fail-fast probe + signal handling). - dbtest: add NewFileSQL for cross-process (binary-spawning) tests. - tests: in-memory transport round-trip unit tests plus a build-tagged stdio integration test that spawns the binary; `make test-integration`. - docs: README MCP section with Claude Desktop / Cursor / generic config. Refs #82
Address findings from an extra-high-recall review pass: - internal/db: list collectors (collectTasks, ListAreas, ListTags, ListProjects) initialized empty slices instead of nil, so a zero-row result renders as JSON `[]` rather than `null` for both --json and the MCP tools — honoring the "returns a JSON array" contract. Keeps MCP output identical to the CLI's --json (both now `[]`). - internal/mcpserver: align applyDateFilters error messages to the CLI's `--on/--from/--to` form so the date-error surface is self-consistent (things.ParseListDate already emits `--`-prefixed errors) and 1:1 with the CLI. - test: pin the empty-result `[]` contract via the MCP search tool. Refs #82
691bea6 to
f9bd6e1
Compare
Replace the terse config snippet with a step-by-step Claude Desktop guide (find the binary path via `which things`, Settings → Developer → Edit Config, ⌘Q to reload, verify via the tools menu) plus a troubleshooting note. Fixes a misleading example: `"command": "things"` (bare) fails in Claude Desktop because GUI apps don't inherit the shell PATH — the docs now use the absolute path. Adds brief Cursor / Claude Code / generic host notes and the --db override. Refs #82
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.
Summary
Adds a
things mcpsubcommand that runs a read-only Model Context Protocol server over stdio, exposing the CLI's read surface as six typed MCP tools. Aimed at hosts that can't shell out (Claude Desktop) and a typed alternative for those that can (Cursor, Claude Code). Implements #82.Tools (results are the same JSON the CLI emits with
--json):things_listthings <view>view,project,area,tag,on,from,tothings_showthings showtask(UUID or title)things_searchthings searchquerythings_projectsthings projectsarea,completedthings_areasthings areasthings_tagsthings tagsDesign
internal/mcpserver; a thinthings mcpKong command starts it.internal/mcpserveris the ready seam for a standalonecmd/things-mcplater if ever wanted, at ~10 lines.internal/dbqueries and render viaoutput.Print(buf, v, asJSON=true)— the ANSI-free JSON path — so output is byte-identical to--json.db.Open→ query →Close), mirroring CLI semantics and sidestepping WAL/snapshot concerns vs. Things3's writes. ABackendinterface (satisfied by*db.DB) makes handlers unit-testable.internal/skill/SKILL.mdis intentionally unchanged —things mcpis a long-running server, not a one-shot an agent drives via Bash, so it doesn't belong in the Bash skill (the issue calls for the two to coexist independently).Decisions (issue's open questions)
github.com/modelcontextprotocol/go-sdkv1.6.1 — v1.x stability/compat guarantee, reflection-derived typed schemas, pure Go, the ecosystem's convergence point (vs. pre-1.0mark3labs/mcp-go).things_-prefixed — collision-proof and self-documenting; settles naming before tag.Tests
internal/mcpserver/server_test.go— drives the real server over the SDK's in-memory transport: tool registration, views, project/area/tag filters,on/from/to(incl. inverted range + deadlines view), unknown-view/date-combo errors,showby UUID/title/ambiguous/not-found, search, projects/areas/tags, an ANSI-free assertion, an open-failure guard, and the empty-result[]contract.cmd/things/mcp_integration_test.go(//go:build integration) — builds the binary and runs a full stdio round-trip (initialize→tools/list→tools/call) against a seeded temp DB; hermetic, no real Things3.make test-integration.A bundled extra-high-recall multi-agent review pass also ran; its confirmed findings (empty list →
nullinstead of[]; date-filter error-message drift) are fixed in691bea6.Verification
make build·make test(-race, all packages) ·make lint(clean) ·make test-integration— all green. Manual:things mcpregisters; fail-fast prints a clear error and exits 1.Closes #82