Skip to content

fix: #4 — feat(core): stdio MCP server + CLI subcommands#14

Merged
MiaoDX merged 1 commit into
mainfrom
claude-issue-4
May 2, 2026
Merged

fix: #4 — feat(core): stdio MCP server + CLI subcommands#14
MiaoDX merged 1 commit into
mainfrom
claude-issue-4

Conversation

@MiaoDX

@MiaoDX MiaoDX commented May 2, 2026

Copy link
Copy Markdown
Owner

Closes #4.

Wires the full v0.1 binary surface. One executable now exposes
serve, lookup, lookup-from-prompt, recap, and init, with a
stdio MCP server that publishes four tools.

What's in this PR

  • internal/mcp/server.go — minimal JSON-RPC 2.0 stdio MCP server
    (NDJSON framing). Implements initialize, tools/list, tools/call,
    ping, shutdown, and the notifications/initialized notification.
    Tool-level failures are returned as result-with-isError per MCP
    convention; transport-level failures become JSON-RPC error objects.
    Zero non-stdlib dependencies — the surface is small enough (3 methods,
    4 tools, no resources/prompts) that pulling in an MCP SDK would
    double the project's dependency footprint for very little gain.
  • internal/cli/{cli,lookup,recap,init}.go — subcommand handlers. The
    flag parser tolerates flags placed after positional args
    (lookup "John 3:16" --format=json) per the issue spec. JSON is the
    default lookup output.
  • lookup-from-prompt accepts both Claude (/bible John 3:16) and
    Codex ([[bible:John 3:16]]) marker styles. It reads either a raw
    prompt or a JSON object with a prompt/user_prompt field on stdin
    (the latter is the form Claude Code / Codex hooks send) and emits an
    additionalContext envelope on stdout suitable for both
    UserPromptExpansion and UserPromptSubmit. With no marker it
    exits 0 silently so hooks add nothing to the prompt. Trailing user
    prompt text after the reference is handled by progressively trimming
    tokens from the right until the resolver accepts what remains.
  • recap — Mode B terminal print. --tradition= filters,
    --first-letter masks all-but-first character of each word/Han
    glyph for memory mode, --seed=N makes selection deterministic.
    Recap output goes to stdout only; the contract that it never enters
    a model_call input is enforced by where the agents invoke it
    (Claude Stop hook, Codex cdx shell wrapper) — both land in feat(claude): Claude Code adapter (Mode A + Mode B + skill) #5/feat(codex): Codex adapter (Mode A + Mode B) #6.
  • init --target={claude-code,codex} [--recap=on|off] [--uninstall] [--dry-run] — splices a marker-fenced snippet into
    ~/.claude/settings.json or ~/.codex/config.toml. Idempotent
    (a second run reports "already up to date"); --uninstall strips
    the block and collapses the surrounding blank line; user content
    outside the markers is preserved. The Claude snippet matches
    plan.md §5.1; the Codex snippet matches §5.2.
  • internal/injector/envelope.go — renders the §6.3 reflection
    envelope wrapping verse text in <scripture_card> tags with the
    "do not preach / quote verbatim" framing. DisplayRef formats the
    verse's reference for human display, switching by tradition.
  • internal/packs/lookup.go — shared LookupReference(r resolver.Reference)
    and ReferenceID(r) helpers, used by both internal/cli and
    internal/mcp so the resolver→pack-id mapping has a single
    authoritative implementation. Returns packs.ErrNotBundled when the
    reference resolves to a tradition shipped api-only in this build
    (heart-sutra, quran), distinct from a hard "id miss" error.

Acceptance-criteria mapping

  • scripture-mcp serve launches a stdio MCP server
  • MCP tools: lookup, search, random, list_traditions
    all return verses with checksum_sha256 and source
  • Speaks the snippet from plan.md §5.1 (Claude Code) and §5.2
    (Codex) — init writes those snippets verbatim
  • scripture-mcp lookup "<ref>" --format=json → JSON Verse on
    stdout (default; --format=text for terminal-pretty)
  • scripture-mcp lookup-from-prompt reads stdin, emits
    additionalContext JSON suitable for both Claude
    UserPromptExpansion and Codex UserPromptSubmit
  • scripture-mcp recap [--tradition=<t>] [--terminal] prints
    pretty terminal output, exit 0
  • scripture-mcp recap --first-letter prints first-letter pattern
  • scripture-mcp init --target={claude-code,codex} merges config
    snippet without overwriting; idempotent; supports --uninstall
    and --dry-run
  • p50 latency for MCP lookup < 5 ms — verified by
    TestLookupLatency (200 trials over the in-memory registry)

Tests

  • internal/mcp/server_test.goinitialize, notifications-have-no-
    reply, tools/list exposes all four, tools/call for each tool
    including the missing-verse → tool-error path, parse-error framing,
    unknown-method → method-not-found, and the p50 latency check.
  • internal/cli/lookup_test.go — JSON default, JSON/text formats,
    ambiguous → exit 3, unrecognized → exit 1, sutra → "not bundled"
    message, plus marker-scan tests for slash and inline forms (with
    trailing prompt text), JSON input form, and silent-on-no-marker.
  • internal/cli/recap_test.go — deterministic with seed, tradition
    filter, first-letter mask (ASCII + Han), unknown-tradition exit.
  • internal/cli/init_test.go — file creation, recap-on/off variant,
    idempotent rerun, user-content preservation, uninstall, codex TOML
    output, missing-target → exit 2, dry-run.
  • internal/injector/envelope_test.go — envelope wrapper structure
    and per-tradition DisplayRef cases.
  • internal/packs/lookup_test.goReferenceID mapping incl.
    multi-word and numbered books, error cases, LookupReference
    happy/api-only/miss paths.

Verification

  • make all (lint + verify-packs + test + build) clean
  • Smoke test: ./bin/scripture-mcp lookup "John 3:16" --format=json
    resolves, returns canonical verse with checksum
    8473c0b1c7664945528317faf77351258eb79f8b11ba821ef76d7e916cde711a
  • Smoke test: piping three JSON-RPC requests
    (initialize / tools/list / tools/call lookup John 3:16) into
    scripture-mcp serve returns protocolVersion 2024-11-05, the
    full tool list, and the verse with matching checksum

Generated by Claude Code

Wires the full v0.1 binary surface from issue #4. One executable now
exposes serve / lookup / lookup-from-prompt / recap / init, plus a
stdio MCP server with four tools (lookup, search, random,
list_traditions).

- internal/mcp/server.go — minimal JSON-RPC 2.0 stdio server
  (NDJSON framing). Tool errors are returned as result-with-isError
  per MCP convention; transport-level failures use JSON-RPC error
  objects. Zero non-stdlib dependencies.
- internal/cli/{lookup,recap,init}.go — subcommand handlers.
  lookup-from-prompt accepts both Claude (`/bible John 3:16`) and
  Codex (`[[bible:John 3:16]]`) marker styles and emits an
  additionalContext envelope suitable for both UserPromptExpansion
  and UserPromptSubmit hooks. init splices marker-fenced snippets
  into ~/.claude/settings.json and ~/.codex/config.toml; rerunning
  is a no-op once installed, --uninstall strips the block, and
  user content outside the markers is preserved.
- internal/injector/envelope.go — renders the §6.3 reflection
  envelope wrapping the verse text in <scripture_card> tags.
- internal/packs/lookup.go — shared `LookupReference` and
  `ReferenceID` so both CLI and MCP map resolver.Reference → pack
  id from a single source.

Tests cover the four MCP methods (initialize, notifications,
tools/list, tools/call), all CLI subcommands, marker scanning for
both formats with trailing prompt text, init's
install/uninstall/idempotent/dry-run paths, and a p50 lookup
latency check (<5ms budget per the issue).

The empty internal/{cli,injector,mcp}/doc.go placeholders from #1
are removed; each package now carries its docstring on the file
that defines its API.
@MiaoDX MiaoDX marked this pull request as ready for review May 2, 2026 01:14
@MiaoDX MiaoDX merged commit aa097d6 into main May 2, 2026
1 check passed
MiaoDX added a commit that referenced this pull request May 2, 2026
…#18)

Closes #7.

- install.sh detects OS (darwin/linux) and arch (arm64/x86_64),
  downloads the matching scripture-mcp release tarball, and installs
  the binary to ~/.local/bin (overridable with --prefix). Re-running
  overwrites the binary in place — that's the upgrade path.
- After install, it detects which coding agents are on $PATH (claude,
  codex), prompts (via /dev/tty so the prompt still works under
  curl|bash), and calls 'scripture-mcp init --target=<agent>' for
  each. init is already idempotent on configs (PR #14's marker-fenced
  splice).
- --uninstall reverses both halves: 'init --uninstall' for each
  detected agent, then removes the binary.
- --no-wire installs the binary only; --yes auto-confirms; --version
  pins a release tag; --from-archive bypasses the network and unpacks
  a local tarball (used by the test harness, also handy for air-
  gapped installs).

Tests live in internal/installer/install_test.go: they build the real
scripture-mcp, pack it into a tarball, then drive install.sh through
fresh-install, idempotent re-run, and uninstall against a sandboxed
HOME and a stubbed PATH. Covered: --help surface, unknown-flag exit,
no-agents case, --no-wire, missing archive.

README's Install section is rewritten around install.sh (was
'planned'); the brew line is removed per the issue's note.

Co-authored-by: Claude <noreply@anthropic.com>
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.

feat(core): stdio MCP server + CLI subcommands

2 participants