Skip to content

feat: ship Gusto SDK docs as a local MCP server (npx-installable)#2288

Draft
jeffredodd wants to merge 5 commits into
mainfrom
feat/docusaurus-mcp-server
Draft

feat: ship Gusto SDK docs as a local MCP server (npx-installable)#2288
jeffredodd wants to merge 5 commits into
mainfrom
feat/docusaurus-mcp-server

Conversation

@jeffredodd

@jeffredodd jeffredodd commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

TL;DR

Adds an MCP (Model Context Protocol) server that exposes our SDK docs to AI coding tools like Claude Code and Cursor. Partners install it once with:

claude mcp add gusto-sdk-docs -- npx -y @gusto/embedded-sdk-docs-mcp

…and from then on the agent can search and fetch the actual SDK docs while writing integration code. No copy/paste, no "open the docs tab," answers cite the version of the SDK they have installed.

Draft because we still need to decide on a final package name, npm scope, and how to wire the second package into publish.yaml. The code is working and smoke-tested end to end.


What is MCP and why are we doing this?

MCP is an open protocol (Anthropic, late 2024) that lets AI agents call external tools — search a database, fetch a doc, run a command — without bespoke integration per agent. The agent connects to an MCP "server," asks what tools it has, and calls them with structured arguments. Claude Code, Cursor, VS Code Copilot, Windsurf, etc. all speak it.

For us, the win is partner ergonomics. Today when a partner integrating the SDK asks their AI "how do I theme the SDK?", the agent either guesses from training data (often wrong, always stale) or asks them to paste docs in. With an MCP server they install once, the agent gets ground-truth answers from sdk.gusto.com content — at the version of the SDK they're actually using.

The two tools this server exposes:

Tool Returns
docs_search Top-N matching pages with title, URL, snippet, score
docs_fetch Full markdown content for a page URL

That's enough for an agent to do "find the right page, then read it" — which covers the vast majority of "how do I do X with the SDK?" questions.


Architecture: two packages, one source of truth

┌──────────────────────────────┐       build       ┌──────────────────────────────┐
│ docs-site/                   │  ───────────────▶ │ docs-site/build/mcp/         │
│   docusaurus.config.ts       │                   │   docs.json                  │
│   + docusaurus-plugin-       │   docs source     │   search-index.json          │
│     mcp-server               │   ───▶ artifacts  │   manifest.json              │
└──────────────────────────────┘                   └──────────────────────────────┘
                                                                  │
                                                                  │ copy at publish time
                                                                  ▼
                                                   ┌──────────────────────────────┐
                                                   │ docs-mcp/                    │
                                                   │   src/cli.ts                 │
                                                   │   artifacts/  (bundled)      │
                                                   │   → npm: @gusto/embedded-    │
                                                   │     sdk-docs-mcp             │
                                                   └──────────────────────────────┘
                                                                  │
                                                                  │ npx
                                                                  ▼
                                                            Claude / Cursor
                                                            (stdio MCP)

docs-site/ — build half

docusaurus-plugin-mcp-server hooks into docusaurus build and writes three JSON files into build/mcp/:

  • docs.json (~3 MB) — every page's content as markdown
  • search-index.json (~19 MB) — pre-built FlexSearch index
  • manifest.json — server metadata

Verified locally:

[MCP] Found 122 routes to process
[MCP] Successfully processed 122 documents
[MCP] Running indexer: flexsearch
[FlexSearch] Indexed 122 documents

docs-mcp/ — runtime half

A new sibling package (@gusto/embedded-sdk-docs-mcp) that:

  1. Bundles the artifacts (artifacts/ is populated by the prepublish step from docs-site/build/mcp/)
  2. Ships a stdio CLI (bin: gusto-sdk-docs-mcp)
  3. Reuses docusaurus-plugin-mcp-server's runtime (McpDocsServer + the docs_search / docs_fetch tool implementations) and connects the underlying McpServer to StdioServerTransport instead of the plugin's built-in HTTP adapters

Why split it across two packages?

Build-time and runtime have completely different dependency graphs:

  • Build needs Docusaurus + MDX + Webpack + the theme — ~100 MB of tooling.
  • Runtime needs FlexSearch + the MCP SDK — ~1 MB of code.

If we shipped the build deps to every partner running npx, we'd waste ~100 MB of bandwidth per install for code that never runs. Splitting means partners download only what's needed at runtime, plus the JSON artifacts.

Bonus: a docs-only fix doesn't have to bump the SDK, and an SDK release auto-refreshes the MCP package because the build runs from the same source tree.

Versioning

@gusto/embedded-sdk-docs-mcp is versioned in lock-step with @gusto/embedded-react-sdk. A partner on SDK 0.47.x runs npx @gusto/embedded-sdk-docs-mcp@0.47 and gets the docs that match. Older minors stay available on npm just by being old published versions.

Why not just host an HTTP MCP endpoint at sdk.gusto.com/mcp?

We could — it's a real follow-up, not "instead of." It needs a serverless function (Cloudflare Worker, Vercel Edge, etc.) wrapping the artifacts, because the docs site currently deploys to gh-pages, which is static-only. The npx path:

  • Requires zero hosting changes
  • Works offline once installed
  • Pins to the exact SDK version the partner has

So we ship npx first, hosted endpoint later.

Do we need a PR on Gusto/embedded-sdk-docs?

No. That repo is a sync target — it hosts the static site. Its Buildkite pipeline will incidentally build the MCP artifacts as part of docusaurus build and they'll end up at sdk.gusto.com/mcp/*.json as static files (which a future hosted endpoint could consume), but no code changes are needed there for this PR to land.


Smoke test

Sending a real MCP initialize → tools/list → tools/call sequence over stdio:

$ printf '...initialize...\n...initialized...\n...docs_search query=theming...\n' | node dist/cli.js

Found 3 result(s):

1. **Theme Variables**
   URL: https://sdk.gusto.com/docs/reference/theme-variables
   ...

2. **Theming**
   URL: https://sdk.gusto.com/docs/theming
   Theming provides a simple and powerful way to customize the visual appearance ...

Tools/list returns docs_search and docs_fetch with proper input schemas. Real query returns real results from the actual built artifacts.

Step-by-step repro in docs-mcp/README.md.


Open questions before merge

  • Final package name. @gusto/embedded-sdk-docs-mcp? @gusto/embedded-react-sdk-mcp? @gusto/sdk-docs-mcp?
  • Wire publish.yaml to publish both packages on each SDK release. (Currently just publishes @gusto/embedded-react-sdk. Adding a second publish step is mechanical but I'd love a sanity check on the version-bump flow first.)
  • Bundle size. The published tarball is ~22 MB (mostly the pre-built search index). We could lazy-build the index from docs.json on first run (smaller download, slower first start). Worth doing now or later?
  • Private API reach. The CLI calls McpDocsServer.createMcpServer(), which is typed private in the plugin's .d.ts but is the documented internal integration point its HTTP adapters use. Cast through unknown as { createMcpServer(): McpServer }. Acceptable, or should we upstream an exposeMcpServer PR to the plugin?
  • Future: hosted sdk.gusto.com/mcp endpoint via a serverless function — separate PR.

How to try it locally

cd docs-mcp
npm install
npm run build   # runs docs-site build, copies artifacts, compiles CLI

# Wire into Claude Code locally
claude mcp add gusto-sdk-docs-dev -- node $(pwd)/dist/cli.js

Then ask the agent something like "Use gusto-sdk-docs-dev to find how the SDK handles theming." It will call docs_search, then docs_fetch, and answer from real docs.

🤖 Generated with Claude Code

jeffredodd and others added 2 commits June 26, 2026 10:28
Adds docusaurus-plugin-mcp-server, which emits build/mcp/ artifacts
(docs.json, search-index.json, manifest.json) during `docusaurus build`.
An adapter at the hosting layer wraps those artifacts into a live /mcp
endpoint so AI agents (Claude Code, Cursor, etc.) can search and fetch
the SDK docs over JSON-RPC.

Wiring the runtime adapter into the downstream hosting
(Gusto/embedded-sdk-docs gh-pages → a serverless/edge function) is a
follow-up — this PR just lands the artifact pipeline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ships the docs-site/build/mcp/ artifacts (docs.json, search-index.json,
manifest.json) bundled with a thin stdio MCP CLI so partners can install
the server with one command:

  claude mcp add gusto-sdk-docs -- npx -y @gusto/embedded-sdk-docs-mcp

The CLI reuses docusaurus-plugin-mcp-server's runtime (McpDocsServer +
its docs_search / docs_fetch tools) and connects the underlying McpServer
to StdioServerTransport instead of the plugin's HTTP adapters, since
stdio is the conventional transport for npx-launched MCP servers.

Two-package split:
- docs-site/   owns the docs and runs the build that emits artifacts
- docs-mcp/    is a runtime-only package that bundles those artifacts
                and serves them; ~22 MB published, ~1 MB of code
                (mostly FlexSearch + MCP SDK)

Build deps stay in docs-site so partners never download Docusaurus +
MDX + Webpack just to run a local search server.

prepublishOnly runs the docs-site build (if needed), copies the
artifacts into docs-mcp/artifacts/, and compiles the CLI to dist/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jeffredodd jeffredodd changed the title feat(docs-site): expose docs as an MCP server via build-time plugin feat: ship Gusto SDK docs as a local MCP server (npx-installable) Jun 26, 2026
jeffredodd and others added 3 commits June 26, 2026 11:44
Surfaces @gusto/embedded-sdk-docs-mcp in Getting Started so partners
discover the install during onboarding, not after they've already
fought their AI to get an answer. Covers Claude Code, Cursor/VS Code,
the two tools the server exposes, SDK-version pinning, and the
local-only privacy story.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Foregrounds the protocol name (MCP) rather than the broader "AI"
framing — partners scanning the sidebar know exactly what kind of
integration this is.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- eslint.config.ts: add docs-mcp/**/* to the ignore list, matching how
  docs-site/ and sdk-app/public/ are handled (standalone subpackage
  with its own tsconfig + ESM-flavored .js imports the root rules
  don't agree with)
- .prettierignore: skip docs-mcp/artifacts/ and docs-mcp/dist/, which
  contain large build outputs (the search index choked Prettier's
  estree printer with a stack overflow)
- Re-format docs-mcp/README.md and docs/getting-started/mcp-assistant.md
  to satisfy the root Prettier config (table padding, em-dash quoting)

Co-Authored-By: Claude Opus 4.7 (1M context) <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.

1 participant