Skip to content

Best-of-breed MCP server, install widgets, mcp_swap.py#5

Merged
tony merged 16 commits into
masterfrom
mcp-1
May 18, 2026
Merged

Best-of-breed MCP server, install widgets, mcp_swap.py#5
tony merged 16 commits into
masterfrom
mcp-1

Conversation

@tony
Copy link
Copy Markdown
Owner

@tony tony commented May 18, 2026

Summary

  • Add eight new high-level MCP tools wrapping the catalog, discovery, diagnostic, and search domains: list_stores, get_store_descriptor, inspect_record_sample, list_sources, filter_sources, summarize_discovery, recent_sessions, validate_query.
  • Add three new resources (agentgrep://catalog, agentgrep://store-roles, agentgrep://store-formats) so MCP clients have a self-describing data dictionary for the on-disk layout.
  • Wire FastMCP's TimingMiddleware + ResponseLimitingMiddleware (512 KB cap) + ErrorHandlingMiddleware, plus an AgentgrepAuditMiddleware that logs every tool call with agentgrep_* structured extra keys and redacts terms / pattern / sample_text to {len, sha256_prefix}.
  • Refactor the former 625-line src/agentgrep/mcp.py into a mcp/ subpackage with per-domain tool modules; the agentgrep-mcp = "agentgrep.mcp:main" entry point is preserved through __init__.py re-exports.
  • Promote Library and MCP to top-level sidebar sections; docs/packages/ is gone and /packages/agentgrep/* URLs redirect to /library/*.
  • Add tabbed install widgets on the MCP and Library landing pages — five clients × three methods × scope × cooldown for the server, four methods × install + runnable Python quickstart for the library. Widget framework + the MCP install widget itself are lifted from libtmux-mcp.
  • Lift scripts/mcp_swap.py so contributors can swap installed Claude / Codex / Cursor / Gemini configs between pinned releases and the local checkout, with timestamped backups and LIFO revert.

Changes by area

MCP server (src/agentgrep/mcp/)

mcp/__init__.py + mcp/server.py + mcp/_library.py + mcp/models.py + mcp/instructions.py + mcp/middleware.py + mcp/prompts.py + mcp/resources.py: split of the former flat module. _library.py carries the protocol-typed view of the parent agentgrep package and the dynamic importlib.import_module("agentgrep") cast, plus the shared AgentName / AgentSelector / SearchTypeName aliases and the READONLY_TAGS / RESOURCE_ANNOTATIONS constants.

mcp/tools/{search,discovery,catalog,diagnostic}_tools.py: per-domain tool modules, each exposing a register(mcp) function the tools/__init__.py:register_tools dispatcher calls. Mirrors libtmux-mcp's pane/window/session split.

mcp/middleware.py: AgentgrepAuditMiddleware emits one record per tool call with agentgrep_tool, agentgrep_outcome, agentgrep_duration_ms, agentgrep_error_type (on failure), agentgrep_client_id, agentgrep_request_id, and a redacted agentgrep_args_summary. Sensitive scalar payloads become {len, sha256_prefix}; sensitive list payloads digest each element.

mcp/instructions.py: _build_instructions() returns segments joined with double newlines — _INSTR_HEADER / _INSTR_SCOPE / _INSTR_SEARCH_VS_DISCOVERY / _INSTR_DEFAULTS / _INSTR_RESOURCES / _INSTR_PRIVACY.

New tools

Tool Domain Purpose
list_stores catalog Enumerate the StoreCatalog with optional agent + role filters
get_store_descriptor catalog Look up one store descriptor by id
inspect_record_sample catalog Fetch first-N parsed records from one adapter+path
list_sources discovery Structured listing with path_kind_filter / source_kind_filter
filter_sources discovery Discovered sources matching a required substring pattern
summarize_discovery discovery Aggregate counts by agent / format / path-kind
recent_sessions search Sources modified in the last N hours, newest-first
validate_query diagnostic Dry-run a regex/literal query against sample text

New resources

URI Returns
agentgrep://catalog Full StoreCatalog payload (role, format, upstream ref, schema notes per entry)
agentgrep://store-roles StoreRole enum members with one-line descriptions
agentgrep://store-formats StoreFormat enum members with one-line descriptions

Docs (docs/)

docs/_ext/widgets/ (autodiscovery framework) + docs/_widgets/{mcp-install,library-install}/{widget.html,widget.js,widget.css} (assets). The framework's _prehydrate.py emits an inline <head> script that mirrors saved selections from localStorage onto <html data-*> attributes before first paint, eliminating FOUC across gp-sphinx SPA navigation.

docs/library/{index,tutorial,how-to,reference,examples}.md: moved verbatim from docs/packages/agentgrep/ via git mv. docs/packages/ is deleted; docs/redirects.txt covers the six old URL paths in dirhtml form (no .html suffix).

docs/index.md: top-level toctrees re-ordered with explicit captions in the order Get started → Library → MCP → Reference → Project.

docs/_ext/agentgrep_fastmcp.py: docs-only signature shims for the eight new tools, with Pydantic Field annotations and examples=[...] for each parameter. __fastmcp__ metadata attached for sphinx-autodoc-fastmcp.

docs/mcp/{tools,resources}.md: each new tool gets a {fastmcp-tool} <name> + {fastmcp-tool-input} <name> pair under an H2 that doesn't collide with the tool slug; each new resource gets a {fastmcp-resource} <name> block with prose.

docs/conf.py: extra_extensions registers docs._ext.widgets; fastmcp_model_classes lists the fifteen new request/response models; fastmcp_section_badge_map gains Catalog and Diagnostic.

Dev workflow

scripts/mcp_swap.py: PEP 723 standalone (only third-party dep is tomlkit) lifted from libtmux-mcp. Defaults derive the server slug from pyproject.toml (-mcp suffix stripped — agentgrep passes through unchanged) and the entry command from the first [project.scripts] key. State lives at $XDG_STATE_HOME/agentgrep-dev/swap/state.json; backups sit beside each config file with a .bak.mcp-swap-<timestamp> suffix.

justfile: new mcp-swap group with mcp-detect / mcp-status / mcp-use-local / mcp-revert recipes.

Top-level

README.md: new top-level README for GitHub and PyPI. Single-client install snippet (Claude Code) plus the library quickstart, with the install widget on agentgrep.org cited as the canonical multi-client picker.

CHANGES: unreleased entry covering the deliverables in user vocabulary, with ### What's new and ### Development subheadings per the changelog conventions.

pyproject.toml: tomlkit>=0.13 added to the dev group so tests/test_mcp_swap.py can import outside the PEP 723 path; no runtime dep change.

Design decisions

Subpackage over flat module: a 625-line file was fine for two tools but would have pushed past 1500 with eight more tools, their request/response models, and middleware. Per-domain modules under tools/ keep each file focused and match the libtmux-mcp pattern this repo's MCP work is modelled on.

Audit via logging extra=, not f-string message: per AGENTS.md, structured fields belong in extra so JSON formatters can index them (agentgrep_tool="search") rather than being trapped inside a message template. The message stays a short stable string ("tool call completed"), and aggregators can group by template signature without each invocation becoming a unique signature.

No Safety / Retry middleware: every tool is read-only, so libtmux-mcp's safety-tier gating doesn't apply. RetryMiddleware exists upstream to recover from libtmux socket transients; agentgrep's blocking backends (subprocess rg/jq, SQLite opened read-only) don't fail with analogous transient errors, so retrying would only mask real bugs. TimingMiddleware, ResponseLimitingMiddleware, and ErrorHandlingMiddleware stay because they're tool-shape-independent hardening.

Combine widget-framework + MCP-install widget in one commit: _prehydrate.py imports from mcp_install.py at module-import time, so registering the framework's Sphinx extension alone would break the build until the install widget lands. Bundling them is the smallest atomic unit that keeps every gate green.

agentgrep-mcp invocation via --from agentgrep: the MCP server ships as a [project.scripts] console script inside the agentgrep PyPI distribution, not as a separate package. uvx --from agentgrep agentgrep-mcp and pipx run --spec agentgrep agentgrep-mcp install the package then invoke the matching script name.

Library widget skips the scope/cooldown axes: library users are pulling the package into their own project; they don't have an mcpServers.<slug> registration target and their cooldown policy is whatever uv / pipx / pip is already configured with. One axis (method) keeps the picker focused on the actual decision.

Page-level MyST anchors kept as package-agentgrep-*: per-page anchors in docs/library/{tutorial,how-to,reference,examples}.md are self-referential — nothing else in the docs cross-references them — so renaming to library-* would be cosmetic churn and add rebase risk to any in-flight work that references them.

Verification

The widget framework should have no stale libtmux references after the lift + retrofit:

$ rg -n 'libtmux' docs/_ext/widgets/ docs/_widgets/

The capabilities resource should advertise exactly the tools and resources the server registers:

$ uv run pytest tests/test_agentgrep_mcp.py::test_mcp_lists_tools_resources_prompts_and_templates tests/test_agentgrep_mcp.py::test_mcp_capabilities_advertises_new_resources -v

The pre-commit gate (gating every commit on this branch):

$ rm -rf docs/_build
$ uv run ruff check . --fix --show-fixes
$ uv run ruff format .
$ uv run ty check
$ uv run py.test --reruns 0 -vvv
$ just build-docs

Test plan

  • test_mcp_lists_tools_resources_prompts_and_templates — lockstep on the full tool surface (search, find, list_sources, filter_sources, summarize_discovery, list_stores, get_store_descriptor, inspect_record_sample, validate_query, recent_sessions)
  • test_mcp_instructions_carry_every_segment_header — guards against accidental _INSTR_* segment deletion
  • test_audit_middleware_emits_extras + test_audit_middleware_redacts_pattern — structured audit log shape + sensitive-payload redaction
  • test_response_limit_middleware_is_wiredResponseLimitingMiddleware and AgentgrepAuditMiddleware are both present on the built server
  • test_mcp_list_stores_returns_catalog_entries + test_mcp_list_stores_filters_by_agent + test_mcp_get_store_descriptor_known_and_unknown + test_mcp_inspect_record_sample_unknown_path + test_mcp_inspect_record_sample_returns_codex_history — catalog tools happy path and error paths
  • test_mcp_list_sources_with_filters + test_mcp_filter_sources_requires_pattern + test_mcp_summarize_discovery_totals_match_list_sources — discovery tools
  • test_mcp_validate_query_invalid_regex + test_mcp_validate_query_substring_match — diagnostic regex parsing and literal-substring matching
  • test_mcp_recent_sessions_filters_by_mtime — mtime cutoff arithmetic on a backdated fixture
  • test_mcp_catalog_resource_returns_full_catalog + test_mcp_store_roles_resource + test_mcp_store_formats_resource + test_mcp_capabilities_advertises_new_resources — three new resources land + capabilities advertises them
  • tests/test_widgets.py::test_widgets_render_in_built_docs + tests/test_widgets.py::test_widget_assets_copied — both widgets emit their class hooks and ship CSS/JS to _static/widgets/<name>/
  • tests/test_mcp_swap.py — full swap/revert lifecycle across Claude (user + project scope), Codex, Cursor, Gemini; LIFO revert ordering; env-preservation on replacements; dry-run output stability
  • Manual smoke: just mcp-detect && just mcp-status && just mcp-use-local --entry agentgrep-mcp against a real ~/.claude.json / ~/.codex/config.toml / ~/.cursor/mcp.json / ~/.gemini/settings.json round-trip

tony added 13 commits May 17, 2026 21:13
why: A single 626-line mcp.py is fine for two tools but blocks
the planned domain coverage expansion. Per-domain tool modules
with a register(mcp) dispatcher mirrors the libtmux-mcp pattern
and keeps each module focused.
what:
- Replace src/agentgrep/mcp.py with the src/agentgrep/mcp/
  subpackage.
- Models, protocols, instructions, resources, prompts, and tools
  each get their own module.
- tools/__init__.py exposes register_tools(mcp); server.py
  imports and calls it during build_mcp_server().
- Entry point agentgrep-mcp = agentgrep.mcp:main keeps working
  via the mcp/__init__.py re-export.
why: A single string blob makes it hard to extend instructions
without churn. Composed _INSTR_* segments let downstream readers
(clients, dashboards) scan section headers and let us add
agent-context segments later without breaking the base set.
what:
- Split _build_instructions() into HEADER / SCOPE /
  SEARCH_VS_DISCOVERY / DEFAULTS / RESOURCES / PRIVACY segments.
- Add a segment-presence test in tests/test_agentgrep_mcp.py.
why: The MCP server previously had no observability and no
response cap. A large search dump from a power user could OOM
a client; an exception inside a tool surfaced as a raw Python
traceback instead of an MCP error code. Adding the standard
FastMCP middleware quartet plus an agentgrep-flavored audit log
brings the server in line with libtmux-mcp's hardening level.
what:
- Add AgentgrepAuditMiddleware that logs agentgrep_tool,
  agentgrep_outcome, agentgrep_duration_ms, and
  agentgrep_args_summary; redacts terms and pattern to
  {len, sha256_prefix}.
- Wire FastMCP's TimingMiddleware, ResponseLimitingMiddleware
  (512KB cap), and ErrorHandlingMiddleware(transform_errors=True).
- Tests cover audit extras, pattern redaction, and middleware
  wiring assertion.
why: The MCP server only exposed search and find — leaving every
other library capability invisible to MCP clients. Full domain
coverage means clients can introspect the store catalog,
validate regex before running it, summarize what's discoverable,
and inspect adapter samples without dropping back to the CLI.
what:
- list_stores / get_store_descriptor wrap the StoreCatalog
  (catalog_tools).
- list_sources / filter_sources / summarize_discovery layer
  structured filters on top of discover_sources
  (discovery_tools).
- validate_query exposes matches_text for cheap pre-flight
  regex checks (diagnostic_tools).
- recent_sessions filters discovered sources by mtime
  (search_tools).
- inspect_record_sample returns first-N records from a named
  adapter + path for schema validation (catalog_tools).
- Each tool runs through asyncio.to_thread to keep the event
  loop unblocked.
- Eleven new test cases plus the lockstep tool-count assertion
  bumped from 2 to 10.
why: Clients that want to reason about the on-disk layout without
spawning a search shouldn't have to scrape the docs site.
Surfacing the StoreCatalog plus its supporting enums as resources
gives agents a self-describing data dictionary.
what:
- agentgrep://catalog returns the full StoreCatalog model.
- agentgrep://store-roles and agentgrep://store-formats enumerate
  enum members with one-line descriptions.
- CapabilitiesModel.resources is updated; the lockstep test
  covers the new URIs.
why: sphinx_autodoc_fastmcp introspects the docs-only shim, not
the live server, so new tools stayed invisible to the docs site
until we mirror their signatures and metadata here.
what:
- Add docs-only shims for the eight new tools (list_stores,
  get_store_descriptor, list_sources, filter_sources,
  summarize_discovery, validate_query, recent_sessions,
  inspect_record_sample).
- Each shim carries the Pydantic Field annotations the live tool
  uses, plus examples.
- Update fastmcp_model_classes in docs/conf.py with the new
  request and response models, and add Catalog/Diagnostic to the
  section badge map.
- Add the new directives to docs/mcp/tools.md, picking H2 titles
  that don't collide with the tool slug anchors.
- Document the three new resources in docs/mcp/resources.md.
…ions

why: The Packages > agentgrep nesting was premature scaffolding
for a future package split that never materialized. Readers care
about the library and the MCP server as products, not as members
of a packages collection. Promoting both to top-level sidebar
sections puts them at the same visual weight as Get started and
Reference.
what:
- git mv docs/packages/agentgrep/*.md to docs/library/*.md.
- Delete docs/packages/ entirely.
- Rebuild the top-level toctrees in docs/index.md with explicit
  captions in this order: Get started, Library, MCP, Reference,
  Project.
- Repoint the landing-page Library card and quickstart 'Next
  steps' link to library/*.
- docs/redirects.txt redirects /packages/agentgrep/* to
  /library/* (dirhtml form, no .html suffix).
…btmux-mcp

why: We want a polished, tabbed install widget on the MCP landing
page rather than scattered install snippets. Building the widget
infrastructure from scratch duplicates work already proven in
libtmux-mcp — its autodiscovery + Jinja + prehydrate + asset-copy
pipeline is exactly what we need, and lifting it verbatim keeps
both projects on the same widget contract.

The framework alone won't load (the prehydrate hook imports from
mcp_install at module-import time), so the widget and framework
land in the same commit. Asset retrofits cover the storage-key
namespace, the CSS class prefix lm-/ag-, and the per-CLI install
commands which target ``agentgrep-mcp`` inside the ``agentgrep``
PyPI package via ``--from`` (uvx) / ``--spec`` (pipx).
what:
- Copy docs/_ext/widgets/{__init__,_base,_directive,_assets,
  _discovery,_prehydrate}.py verbatim from libtmux-mcp.
- Copy docs/_ext/widgets/mcp_install.py and retrofit
  install commands, JSON/TOML bodies, server slug from
  tmux/libtmux-mcp to agentgrep.
- Copy docs/_widgets/mcp-install/{widget.html,widget.js,
  widget.css}; rename lm- prefix to ag- and storage namespace
  to agentgrep.mcp-install.*.
- Register docs._ext.widgets in conf.py extra_extensions.
- Embed {mcp-install} at the top of docs/mcp/index.md.
why: The library landing page should present install + import +
first-search as a single tabbed onboarding panel, not as three
scattered code blocks across a tutorial page. Mirrors the MCP
install widget so readers see consistent affordances across the
docs site.
what:
- Add docs/_ext/widgets/library_install.py with four methods
  (uvx run / pipx run / uv add / pip install) and an embedded
  Python quickstart that drives run_search_query end-to-end.
- Add docs/_widgets/library-install/{widget.html,widget.js,
  widget.css}. Storage namespace: agentgrep.library-install.method.
- Extend _prehydrate to inject a small <script> + the
  inject_library_install_prehydrate hook so the saved method
  paints without FOUC; wire both hooks in widgets/__init__.py.
- Embed {library-install} at the top of docs/library/index.md.
- tests/test_widgets.py smoke-tests both widgets render and ship
  their CSS/JS assets to _static/widgets/<name>/.
why: Iterating on the MCP server required hand-editing four CLI
config files (Claude, Codex, Cursor, Gemini) to point at the
local checkout, then reverting them before testing release
behavior. The script lifted from libtmux-mcp automates both
directions with timestamped backups and LIFO undo, eliminating
the friction that's been blocking rapid MCP iteration.
what:
- Copy scripts/mcp_swap.py from libtmux-mcp (PEP 723 standalone,
  requires tomlkit).
- Retrofit STATE_DIR namespace from libtmux-mcp-dev to
  agentgrep-dev and update docstrings to clarify the slug-strip
  rule passes the ``agentgrep`` name through unchanged.
- Tighten an except clause from the Python 3.14-only relaxed
  syntax (``except E1, E2:``) to the parenthesised form so the
  script runs under the >=3.10 declared in its PEP 723 header.
- Add justfile recipes: mcp-detect / mcp-status / mcp-use-local
  / mcp-revert.
why: The lifted script is dev-critical — a regression that
corrupts a contributor's Claude / Codex / Cursor / Gemini config
is a worst-case bug. The libtmux-mcp test suite already
exercises every command, scope, and revert path; lifting it
gives us the same coverage on day one.
what:
- Copy tests/test_mcp_swap.py from libtmux-mcp and retrofit the
  fake-repo pyproject to use ``agentgrep-mcp`` so the slug-strip
  test exercises the ``agentgrep-mcp`` -> ``agentgrep`` mapping
  agentgrep contributors will actually see.
- Add ``tomlkit`` to the dev dependency group so the test file
  imports outside the PEP 723 standalone path.
- Bump the script's PEP 723 ``requires-python`` from 3.10 to
  3.14 to match the project's declared floor and silence
  ruff's preference for the bare ``except E1, E2:`` form.
- Add an explicit ``import tomlkit.items`` so ty doesn't warn
  about the implicit submodule reference in the isinstance check.
why: GitHub and PyPI both render the top-level README; the
project shipped without one. A short README with the
one-paragraph pitch, install snippet, and links to the docs site
closes the gap without duplicating the install widget content.
what:
- Add README.md with the project pitch, a single-client install
  snippet, a library quickstart, and links to docs, source, and
  issues.
why: The unreleased entry was a placeholder. Documenting the new
tools, widgets, sidebar reorg, and dev tooling in user
vocabulary lets future readers (and future-me preparing a
release) reconstruct what shipped without paging through
commits.
what:
- Append #### What's new sections covering the eight new MCP
  tools, the three new resources, the FastMCP middleware
  hardening, the MCP install widget, the library install +
  quickstart widget, and the sidebar reorg.
- Append #### Development sections for the lifted mcp_swap.py,
  the mcp/ subpackage refactor, and the new top-level README.
why: The MCP install widget already ships on the MCP landing
page but the Sphinx homepage shows only the warning and card
grid — readers have to click through before they see an
install affordance. libtmux-mcp embeds the same widget on
its homepage with :variant: compact between the warning and
the grid; mirroring that shape gives agentgrep readers a
one-step install path before they pick a docs section.
what:
- Insert {mcp-install} :variant: compact between the
  pre-alpha warning and the grid in docs/index.md.
tony added 2 commits May 17, 2026 22:16
why: Method tabs and cooldown controls on the install widget did
nothing when clicked. Root cause: widget.js never mutates the
[hidden] attribute on panels — it relies entirely on the CSS
rules emitted by _prehydrate.py to drive panel visibility from
<html data-mcp-install-*> attributes. Those rules still keyed on
.lm-mcp-install__panel selectors from the original libtmux-mcp
lift, which never match agentgrep's .ag-mcp-install__panel HTML,
so flipping the html attribute did nothing visually.
what:
- Rename lm-mcp-install -> ag-mcp-install across
  _prehydrate.py and the cooldown-days slot spans in _base.py.
- After this fix the prehydrate <style> block in the rendered
  HTML uses ag-mcp-install__panel everywhere, and tab switching
  actually drives panel display.
why: libtmux-mcp ships three small static assets that polish
sphinx-design card titles, style prompt-style admonitions, and
preserve inline markdown when sphinx-copybutton copies a prompt
block. agentgrep was missing all three; lifting them brings the
two projects to parity and quietly fixes the Furo border quirk
on any future card title that contains a <code> chip.
what:
- Copy docs/_static/css/project-cards.css verbatim — extends
  Furo's <code> chip border rule to .sd-card-title contexts.
- Copy docs/_static/css/project-admonitions.css verbatim —
  styles .admonition.prompt, .admonition.agent-thought, and
  the system-prompt / server-prompt code panels.
- Copy docs/_static/js/prompt-copy.js verbatim — intercepts
  sphinx-copybutton clicks on .admonition.prompt blocks and
  reconstructs inline backtick markdown for clipboard write.
- Register all three from conf.py setup() with the same
  add_js_file(..., loading_method="defer") and add_css_file
  call shape libtmux-mcp uses.
@tony tony merged commit d275aa9 into master May 18, 2026
3 checks passed
@tony tony deleted the mcp-1 branch May 18, 2026 03:19
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