MCP Tools¶
The tools registered on the Patcher MCP server. Each tool’s function signature defines the JSON schema clients see; the docstring is what the client’s LLM reads to decide when to call it.
For end-user setup snippets see Working with Agents. For prompt examples that exercise these tools, see Working with Agents.
MCP tools exposed to clients.
Each @mcp.tool decorator registers a callable that an MCP client (Claude
or otherwise) can invoke. The function signature becomes the tool’s JSON
schema, so parameters must be typed and the docstring is what the client’s
LLM reads to decide when to call it: be specific about what the tool returns
and what inputs it expects.
Tools acquire their own DB sessions via get_session_maker() rather
than through FastAPI’s Depends injection, which is REST-specific. App
records are projected through patcher_api.schemas.app.App so the
shape an MCP client sees is identical to GET /apps/{slug}.
- async get_catalog_summary() dict¶
Return top-line statistics about the Patcher catalog: the total number of apps and per-source coverage counts (how many apps have data from each upstream source: Installomator, Homebrew Cask, Jamf App Installer, AutoPkg). Useful to orient on what data is available before drilling into specific apps with
search_appsorget_app.Returned dict has keys
total_apps(int) andsources(a dict mapping each source name to the count of apps carrying that source’s data).- Return type:
- async search_apps(query: str, limit: int = 20) list[dict]¶
Search the catalog for apps matching
query.Performs a case-insensitive substring match against each app’s slug, name, vendor, and bundle_id. Useful for fuzzy lookups when the caller knows part of an app’s identity but not its exact slug (e.g. “firefox” hits
firefox,firefoxesr,firefoxpkg). Results are ordered by slug for deterministic paging across repeated queries.queryis required but may be empty, in which case everything up tolimitis returned.limitdefaults to 20 and is hard-capped at 100 to keep responses small enough for an LLM context window.Each result is the full app record (same shape as
get_app); useget_appdirectly when you already know the exact slug.
- async get_app(slug: str) dict¶
Fetch a single app record by its slug.
Returns the full app projection: identity (slug, name, vendor, bundle_id), versioning (current_version, latest_release_date), download metadata (download_url, install_method, sha256), and provenance (sources). Identical to
GET /apps/{slug}on the REST API.slugis the URL-friendly app identifier (e.g. “firefox”, “1password8”); usesearch_appsfirst if you don’t know the exact slug. RaisesValueErrorif no app with that slug exists in the catalog.
- async list_drift(vendor: str | None = None, source: str | None = None, limit: int = 25, offset: int = 0) dict¶
Find apps where Patcher’s upstream sources disagree on the latest version.
Drift answers a question the stitched catalog is uniquely positioned to answer: do upstream sources agree on what “latest” means? When they don’t, one source is usually silently stuck (vendor moved their release artifact, the label still finds the old location, and the source keeps reporting the old version indefinitely). Only sources that expose a stable per-app version string participate, currently Installomator and Homebrew Cask.
vendoris an optional case-insensitive vendor filter (e.g. “Mozilla”); None disables.sourceoptionally narrows results to drift entries where the named source participated in the disagreement; None disables.limitdefaults to 25 and is hard-capped at 100;offsetdefaults to 0.Returned dict has
total_scanned(apps inspected, those with at least two versioned sources),total_with_drift(subset where sources disagreed, pre-pagination), andentries(the paged list ofpatcher_api.schemas.drift.DriftEntrydicts).
- async list_categories() dict¶
Return the catalog’s distinct categorical values.
Useful for an MCP client that wants to describe the catalog’s shape (which install methods are represented, which sources have data, which vendors are present) without iterating every app.
install_methodsis the staticInstallMethodenum (the universe of values Patcher recognizes, not just those currently in use);sourcesandvendorsreflect what’s actually in the catalog right now.Returned dict has keys
install_methods(list[str]),sources(list[str], sorted), andvendors(list[str], sorted).- Return type:
- async generate_installomator_label(slug: str) dict¶
Generate an Installomator-shaped label for the app identified by
slug.Projects the app’s Homebrew Cask, Installomator, and Jamf App Installer source payloads into the Installomator label format that consumers can drop into their Installomator deployments. Mirrors the REST endpoint
POST /apps/{slug}/generate-labelexactly.slugis the URL-friendly app identifier (e.g. “firefox”); usesearch_appsfirst if you don’t know the exact slug. RaisesValueErrorif no app with that slug exists, or if the app has no source detail attached (rare, usually a leftover seed record without upstream coverage).Returned dict has
label_name(str, the app’s slug),content(dict of Installomator variable name to value, with unresolved fields omitted),sources_used(list[str] naming which upstream sources contributed), andwarnings(list[str] explaining any fields that couldn’t be resolved, most commonlyexpectedTeamIDfor Cask-only apps).
- async get_app_sources(slug: str) dict¶
Return the raw per-source payloads for the app identified by
slug.Use this when the caller needs to inspect what each upstream source said about an app (the raw Installomator label dict, the full Homebrew Cask JSON, the Jamf App Installer catalog row), as opposed to the stitched canonical projection that
get_appreturns. Mirrors the REST endpointGET /apps/{slug}/sourcesexactly.slugis the URL-friendly app identifier. RaisesValueErrorif no app with that slug exists. If the app exists but has no source detail row attached, returns a dict with every source key set toNonerather than raising.Returned dict has keys
installomator,homebrew_cask,autopkg,mas, andjamf_app_installer; each value is either the source’s native payload (dict) orNonewhen that source has no data for the app.
- async list_recent_changes(limit: int = 25) list[dict]¶
Return the most recently added apps in the catalog, newest first.
Ordering uses
App.iddescending as a proxy for recency: rows are autoincrement-assigned at ingest time, so a higher id means a later insertion. This is honest about what the catalog can currently express; once theAppmodel grows amodified_attimestamp column, this tool should switch to that column and accept asince_isofilter parameter for true change-feed semantics.limitdefaults to 25 and is hard-capped at 100 to keep responses small enough for an LLM context window.Each result is the full app record (same shape as
get_app).