{
  "markdown": "# MCPfinder\n\n> The MCP server that helps AI agents discover, evaluate, and install other MCP servers.\n\nMCPfinder is an AI-first discovery layer over the Official MCP Registry, Glama, and Smithery. Install it once, and your assistant can search for missing capabilities, inspect trust signals, review required secrets, and generate client-specific MCP config snippets.\n\n## Canonical Use\n\n- Canonical transport: `stdio` via `npx -y @mcpfinder/server`\n- Canonical package: [`@mcpfinder/server`](https://www.npmjs.com/package/@mcpfinder/server)\n- MCP Registry entry: [`dev.mcpfinder/server`](https://registry.modelcontextprotocol.io/v0/servers?search=dev.mcpfinder)\n- Public HTTP endpoint: intentionally not advertised as canonical until its tool surface is fully identical to the local server\n\n## Quick Install\n\n### Claude Desktop\n\n```json\n{\n  \"mcpServers\": {\n    \"mcpfinder\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@mcpfinder/server\"]\n    }\n  }\n}\n```\n\n### Cursor\n\n```json\n{\n  \"mcpServers\": {\n    \"mcpfinder\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@mcpfinder/server\"]\n    }\n  }\n}\n```\n\n### Claude Code\n\n```json\n{\n  \"mcpServers\": {\n    \"mcpfinder\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@mcpfinder/server\"]\n    }\n  }\n}\n```\n\nSupported install targets today:\n\n- Claude Desktop\n- Cursor\n- Claude Code\n- Cline / Roo Code\n- Windsurf\n\n### Install via Agent Skill (let your AI do it)\n\nIf your agent supports the [Agent Skills](https://agentskills.io) format\n(Claude Code, GitHub Copilot in VS Code, OpenAI Codex, and others), you can\ndrop a one-line install and let the agent handle the config merge itself.\n\n**Claude Code (global):**\n\n```bash\nmkdir -p ~/.claude/skills/install-mcpfinder && \\\n  curl -sSf -o ~/.claude/skills/install-mcpfinder/SKILL.md \\\n    https://mcpfinder.dev/skill/install-mcpfinder/SKILL.md\n```\n\n**VS Code (project-scoped):**\n\n```bash\nmkdir -p .agents/skills/install-mcpfinder && \\\n  curl -sSf -o .agents/skills/install-mcpfinder/SKILL.md \\\n    https://mcpfinder.dev/skill/install-mcpfinder/SKILL.md\n```\n\nThen tell your agent any of: *\"install MCPfinder\"*, *\"connect my AI to\nPostgres\"*, *\"I need a tool for [anything]\"* — the skill activates, detects\nyour client, merges the config without clobbering, and tells you what to\nrestart.\n\n## For AI Assistants\n\nUse MCPfinder when the user needs a capability you do not already have.\n\n- If the user mentions Slack, Postgres, GitHub, Notion, AWS, Google Drive, filesystems, browsers, APIs, or databases: call `search_mcp_servers`.\n- Before recommending a server: call `get_server_details`.\n- Before telling the user what to paste into config: call `get_install_config`.\n- If the user only knows a domain, not a specific technology: call `browse_categories` (omit `category` to list; pass `category` for top servers).\n\nPreferred workflow:\n\n1. `search_mcp_servers(query=\"postgres\")`\n2. `get_server_details(name=\"...best candidate...\")`\n3. `get_install_config(name=\"...best candidate...\", platform=\"claude-desktop\")`\n4. Tell the user what server you chose, why, which secrets are required, and what restart/reload step is needed.\n\n## Tool Surface\n\n| Tool | Purpose | When to call |\n| --- | --- | --- |\n| `search_mcp_servers` | Search by keyword, technology, or use case | First step when a capability is missing |\n| `get_server_details` | Inspect metadata, trust signals, tools, warnings, env vars | Before recommending or installing |\n| `get_install_config` | Generate a JSON config snippet for a target client | After selecting a server |\n| `browse_categories` | Single-call category browser (omit `category` to list; pass `category` for top servers) | Domain-driven discovery |\n\n## What MCPfinder Returns\n\nMCPfinder is intentionally optimized for agent consumption.\n\n- Human-readable text summaries\n- Structured content for chaining follow-up calls\n- Trust signals: source count, verification, popularity, recency\n- Warning flags: stale projects, missing repository URL, unclear install path, single-source-only\n- Install metadata: config snippet, target file paths, required environment variables, restart instructions\n\n## Ranking and Recommendation\n\nSearch ranking uses:\n\n- text relevance\n- name-match boost — measured against the name with its *hosting prefix* removed\n  (`io.github.<owner>/<repo>` → `<owner>/<repo>`, `ai.smithery/<slug>` →\n  `<slug>`). That leading segment is a reverse-DNS namespace assigned by the\n  Official registry to say where the server's code is hosted — `io.github.*`\n  servers all come from Official, not from some \"GitHub\" registry — and it says\n  nothing about what the server does. 19.6% of the catalog is named\n  `io.github.%`, so scoring it turned the boost into a constant and buried every\n  real match. The owner segment stays, so searching by owner still works. There\n  is no way to search *by* the hosting namespace: `registrySource` filters on\n  the registries an entry was found in (`official`/`glama`/`smithery`), which is\n  a different thing.\n- community usage (`useCount`)\n- official registry presence\n- verification signals\n\nEach result is also annotated with:\n\n- `confidenceScore`\n- `recommendationReason`\n- `warningFlags`\n- `updatedAt`\n- `sourceCount`\n\n## Data Sources\n\nMCPfinder aggregates:\n\n- [Official MCP Registry](https://registry.modelcontextprotocol.io)\n- [Glama](https://glama.ai/mcp/servers)\n- [Smithery](https://smithery.ai)\n\nCounts vary over time and differ depending on whether you count raw upstream records or merged/deduplicated entries. Snapshot metadata is the source of truth for the currently published local bootstrap dataset.\n\n## Snapshots and Freshness\n\nFirst run can bootstrap from a prebuilt SQLite snapshot instead of doing a slow live sync.\nNormal startup therefore does not wait for all live registry budgets. The\nsequential Official → Glama → Smithery cold crawl is a fallback for an empty DB\nonly when snapshot bootstrap is disabled or fails, preserving deterministic\ncross-registry deduplication.\n\nThe download runs in the background: the MCP server answers `initialize`\nimmediately, tool calls arriving before the catalog exists — during the download\nand during the handle switch alike — get a \"still preparing\" notice with progress\n(`status: \"preparing\"`, distinct from a not-found result), and the verified file\nis switched in without a restart. A\nfreshly installed snapshot counts as a fresh sync, so it does not immediately\ntrigger the live crawl it was meant to replace.\n\nEach snapshot is stored as its own immutable file, `data-<sha16>.db`, selected\nby a pointer at `data.db.snapshot.json`. Nothing is replaced in place, so the\nseveral MCP clients that each run their own mcpfinder process against\n`~/.mcpfinder` never pull a database out from under one another; superseded\nfiles are swept only after `MCPFINDER_SNAPSHOT_RETAIN_HOURS` (default 48) of\nbeing un-pointed-to and untouched, and the sweep unlinks the database file\nalone — never its `-wal`/`-shm`, which a peer that still has the file open looks\nup by name. That rule is unconditional: an orphaned journal, one whose database\nis already gone, is left alone too, because nothing distinguishes it from the\njournal of a peer that outlived its own file, and deleting the latter is\ncorruption. What keeps the residue small is that every successful sync ends with\n`PRAGMA wal_checkpoint(TRUNCATE)`; the 40MB `-wal` measured beside a 323MB\ndatabase came from a single-transaction crawl whose journal was never trimmed at\nall. What is left is a bounded leak after processes killed with `SIGKILL` — how\nMCP clients usually stop stdio servers. Two limits follow: a removal is allowed\nto fail — on the platforms and filesystems where an open file's name cannot be\ntaken away, stale snapshots stay until nothing holds them — and a journal can\noutlive the database it belonged to. The install is re-checked daily: one\nmanifest request when nothing changed, plus a request for the DB when the\nmanifest advertises a newer digest — conditional (ETag) on the gzip endpoint,\nunconditional on the brotli one, which is content-addressed by its own digest\nand for which no ETag is ever recorded.\n\n- snapshot manifest: `/api/v1/snapshot/manifest.json`\n- snapshot database (gzip): use `manifest.url` (`data.sqlite.gz?sha=<sha256>`) as the content-addressed primary endpoint\n- snapshot database (brotli): use `manifest.brotli.url` (`data.sqlite.br?sha=<brotli sha256>`)\n- durable current fallback: `/api/v1/snapshot/data.sqlite.gz`, refreshed only after manifest publication\n- scheduled build: [`.github/workflows/snapshot.yml`](.github/workflows/snapshot.yml)\n- staleness monitor: [`.github/workflows/snapshot-staleness.yml`](.github/workflows/snapshot-staleness.yml)\n\n### Two compressions of one database\n\nEvery build publishes the same SQLite file twice — gzip always, brotli when\nthat half of the pipeline succeeds (see the publication section below). Brotli\n(quality 9, 16MB window) is about 21% smaller: measured at 36.8MB against\n46.7MB gzip for the 238MB / 84,647-server database published on 2026-08-26.\nBoth figures scale with the corpus, so treat the manifest's `sizeBytes` and\n`brotli.sizeBytes` as the live numbers rather than these. Compressing the\nsecond artifact costs well under a minute of build time inside a 90-minute job,\nand decompression is a fraction of a second. zstd compresses a further ~2MB but\nneeds Node 22.15+/23.8+. The 22.x floor now clears that bar, but Node 23.0-23.7\ndoes not, so a third artifact would still have to carry a fallback for the gain\nto be safe — not worth it while brotli already does the work.\n\n```jsonc\n{\n  \"publishedAt\": \"…\", \"serverCount\": 84647,\n  // gzip: the snapshot's identity — recorded in the client's pointer and\n  // compared on every freshness check. Unchanged, and always published.\n  \"sha256\": \"<gz digest>\", \"sizeBytes\": 46706108, \"url\": \"data.sqlite.gz?sha=<gz digest>\",\n  // brotli: optional, additive, with its own digest and size. Absent whenever\n  // the artifact could not be built, uploaded or verified.\n  \"brotli\": { \"url\": \"data.sqlite.br?sha=<br digest>\", \"sha256\": \"<br digest>\", \"sizeBytes\": 36760000 }\n}\n```\n\nClients from the next release prefer brotli when the manifest announces it and fall back\nto gzip on *any* brotli-side failure — 404, transport error, corrupt stream,\ndigest mismatch — so a bad brotli object costs bandwidth, never a working\nbootstrap. Both URLs are resolved against the configured snapshot base and one\nthat points outside it is refused rather than fetched: the digest lives in the\nsame manifest as the URL, so it cannot vouch for the origin of the bytes.\nDecompression is size-bounded against the manifest's `rawSizeBytes`, because\nthe bytes reach disk before the digest can be checked. Each artifact is\nverified against its own digest, and the ETag\nrecorded in `data.db.snapshot.json` always describes the object that was\nactually downloaded (a brotli install stores none, since that field is the gz\nobject's validator). Set `MCPFINDER_SNAPSHOT_NO_BROTLI=1` to stay on gzip.\nPublished clients 1.1.0 and 1.2.0 read only `sha256`/`sizeBytes`/`url` and are\nunaffected by the extra fields.\n\nSnapshot publishing is last-known-good: all requested registries must finish\nwith an `ok` sync status, and the merged total plus each per-source count may\nnot fall more than 5% below the currently published manifest. A failed gate\nleaves the published R2 objects untouched. Only a confirmed missing manifest\n(HTTP 404, including the first build) skips the count comparison. Transient,\nmalformed, or structurally incomplete baselines are retried and then fail\nclosed; source health is always required.\n\nPublication uses a content-addressed handoff. The compressed databases are\nuploaded first — `snapshots/<sha256>.sqlite.gz` and\n`snapshots/<brotli sha256>.sqlite.br`, each keyed by its own digest; only then\nis `manifest.json` replaced with a pointer URL such as\n`data.sqlite.gz?sha=<sha256>`. The pointer therefore never announces an object\nthat is not already durable. Cached older\nmanifests continue to resolve to their exact immutable database.\nLegacy manifests without `sha` keep using the existing `data.sqlite.gz` key.\nBefore advancing the manifest, CI downloads both new objects through the public\nWorker endpoint and verifies each one's SHA-256 and the Worker's acknowledged\ncontent address.\n\nOnly the gz half of that is a publication gate. **Brotli is best-effort through\nthe whole pipeline**, on purpose: by the time the brotli steps run, the gz\nobject is already durable in R2 and fit to publish, and a bandwidth\noptimisation must never be able to withhold a working snapshot. A failed brotli\ncompression, upload, or preflight all end the same way — the `brotli` block is\ndropped from `manifest.json` before the pointer is published, and the build\nwarns in the job log and the step summary. The published manifest thus never\nadvertises an artifact that is not in R2, and clients that see no block simply\ndownload gzip. This also means the Worker deploy order is a non-event: until\n`/api/v1/snapshot/data.sqlite.br` is live the preflight 404s on brotli, the\nblock is dropped, and every build still publishes.\n\nThe gzip preflight, by contrast, stays fatal: it prevents publication while an\nolder Worker still ignores the `sha` query or while the new R2 object is not\npublicly readable. The\npreflight is bounded to four attempts with 0.5/1.5/4.5-second backoff and a\nper-attempt timeout covering both response headers and the complete body;\ndeterministic SHA/header/size mismatches fail immediately.\n\nImmutable objects under `snapshots/` expire after 30 days; neither\n`manifest.json` nor legacy `data.sqlite.gz` matches that lifecycle prefix.\nIncomplete multipart uploads retain the existing 7-day abort policy.\nAfter the manifest pointer is published, CI refreshes non-expiring\n`data.sqlite.gz` with the same database and then publishes\n`data.sqlite.gz.sha256` as the final commit marker. There is deliberately **no**\nbrotli twin of that durable pair: it exists to rescue a client whose manifest\ndigest has aged out of the 30-day immutable window, and a brotli client already\nhas that escape hatch — it falls back to the gz artifact, which does have one.\nA second mutable key would add a divergence risk and no availability. If the current immutable\nobject later expires, the Worker serves this durable copy only when the\nrequested SHA, current manifest SHA, and marker SHA all match. A failed DB or\nmarker upload therefore cannot label stale bytes as a new snapshot. Older\ncached SHA requests remain 404 after their 30-day history window.\nThe Worker coalesces concurrent current-proof reads and caches both positive\nand malformed/missing proof results for five minutes; SHA-specific 404s are\nalso publicly cacheable for five minutes.\nActual R2 read failures remain distinct from missing or malformed proof: they\nreturn an uncached 503 and are retried by the next request.\n\nFor an intentional corpus reset, manually dispatch the snapshot workflow with\n`allow_quality_regression` enabled, or run the builder with\n`--allow-quality-regression` (equivalently\n`MCPFINDER_SNAPSHOT_QUALITY_OVERRIDE=1`). The override permits count drops but\nnever permits an errored or incomplete required source.\n\n### Every registry is required\n\nOfficial MCP Registry, Glama, and Smithery are all *required* sources: an\nerrored, missing, skipped, or degraded sync of any one of them blocks snapshot\npublication. **An incomplete snapshot never replaces a complete one.**\n\nThe reason this is affordable is the shape of the publication handoff.\n`manifest.json` is a pointer swapped as the very last step, and the database it\npoints at lives outside the 30-day `snapshots/` expiry prefix, so a build that\nfails the gate simply does not touch it — clients keep bootstrapping from the\nprevious, complete snapshot. A failed build therefore means *staleness*, not\n*unavailability*, and a complete snapshot from yesterday beats a fresh one with\na registry missing. Staleness is visible to everyone in the manifest's\n`publishedAt`; a silently absent third of the corpus is visible to no one.\n\nThe gate is parameterised rather than hardcoded: `scripts/snapshot-quality.mjs`\nstill implements best-effort (`optionalSources`) handling, and it is still\ntested. Nothing is listed there today. If a registry closes permanently, moving\nits name from `requiredSources` to `optionalSources` in\n`scripts/build-snapshot.mjs` demotes it — with the aggregate-count consequences\ndescribed below — instead of the gate having to be rebuilt under pressure.\n`--no-glama` / `--no-smithery` drop a registry from a local build entirely; they\nare for local runs, not for CI.\n\n`counts` in `manifest.json` always carries all three sources, so a per-registry\nregression stays visible to monitoring even though it now also fails the build.\n\nWhen a source *is* demoted to best-effort, its absence legitimately shrinks the\ncorpus and the baseline cannot be corrected for it: `serverCount` in\n`manifest.json` is a *deduplicated* row count while `counts.<source>` are *raw*\nper-registry record counts that overlap across registries (they sum to more than\n`serverCount`). Subtracting one from the other compares incomparable units. So\nwhen a best-effort source is unhealthy the aggregate `serverCount` regression\ncheck is **skipped entirely**, with a warning naming the source:\n\n```\n[build-snapshot] quality warning: serverCount regression check skipped:\nbest-effort source <name> is unavailable and its contribution to the\ndeduplicated baseline cannot be isolated\n```\n\nThe per-source regression checks for the required sources still run (raw against\nraw). With every source required — the current policy — the aggregate check runs\non every build against the undoctored previous `serverCount`, and a drop beyond\nthe 5% threshold blocks publication regardless of which source caused it. The\nmanual `MCPFINDER_SNAPSHOT_QUALITY_OVERRIDE` escape hatch remains the only way\nto publish through a deliberate aggregate reset.\n\nGlama requires `GLAMA_API_KEY` (create one at\n<https://glama.ai/settings/api-keys>) since it closed its public API on\n2026-08-26 — `GET /api/mcp/v1/servers` answers `401 unauthorized` without a key.\nThe key is sent as `Authorization: Bearer <key>` and is never logged, stored in\n`raw_data`, or written to the manifest. Without it the sync is skipped before\nany request, recording `status=skipped` in `sync_log` with\n`Glama API requires GLAMA_API_KEY; skipping Glama sync` — which now fails the\npublication gate, because a snapshot without Glama is not a complete snapshot. A\nrejected key (401/403) is reported as a credential error and is never retried —\nit is not a transient failure. In CI the key comes from the repo secret\n`GLAMA_API_KEY`; an unset secret takes the skip path and no snapshot is\npublished.\n\n> **Licensing:** Glama's API Data License requires *visible attribution to\n> Glama on every page that displays this data*\n> (<https://glama.ai/policies/terms-of-service>). Any surface rendering\n> Glama-sourced servers must carry that attribution.\n\nGlama keeps a 12-minute sync budget for normal local stdio use. The scheduled\nsnapshot job sets `MCPFINDER_GLAMA_SYNC_BUDGET_MINUTES=30` because a full Glama\npagination regularly exceeds the local limit, while still leaving headroom\ninside the job's 90-minute timeout. Custom values must be whole minutes from 1\nthrough 40. HTTP 200 pages with truncated or malformed JSON are retried on the\nsame cursor before the source is marked as errored. The hard registry deadline\ncovers the terminal response body as well as transport and parsing: a page that\nfinishes after the budget is deliberately marked degraded, protecting the\nsnapshot job from silently exceeding its wall-clock budget.\nAn empty Glama page with `hasNextPage=true` is followed when it supplies a new,\nnon-empty cursor. Missing or repeated cursors fail safely as structural errors,\npreventing upstream pagination loops.\n\nSmithery paginates through a fixed seed, and the seeded ordering occasionally\nreturns the same `qualifiedName` on two different pages. That is a structural\nerror — a corpus counted twice is not a corpus — so the crawl restarts from page\none rather than committing what it has. Restarts are capped at three attempts,\nspaced by an exponential backoff, because the fault is transient upstream: the\nbuild of 2026-08-26 20:02 exhausted its two attempts and skipped a publication\ncycle that the 21:43 build then completed with 10,845 servers. Three full passes\ndo not fit the 5-minute local budget, so the snapshot job sets\n`MCPFINDER_SMITHERY_SYNC_BUDGET_MINUTES=12`; custom values must be whole minutes\nfrom 1 through 15.\n\n### A frozen publication announces itself\n\nBecause a failed build is *staleness*, not *unavailability*, nothing breaks when\npublication stops — which is exactly why it has to be announced. A red run is\nvisible only to whoever opens the Actions tab, and `publishedAt` is a pull-based\nsignal nobody polls; that combination once let a stalled publication go\nunnoticed for six days.\n\nTwo independent signals now cover it, and both file into the same GitHub issue,\ndeduplicated by the `snapshot-freeze` label:\n\n1. **The build says it did not finish.** A final\n   `if: (failure() || cancelled()) && steps.manifest-pointer.outcome != 'success'`\n   step in `.github/workflows/snapshot.yml` opens the freeze issue — or comments\n   on the open one — naming the failing step and linking the run. `cancelled()`\n   is there because a job that trips `timeout-minutes`, loses its runner, or is\n   stopped by hand is cancelled rather than failed, and those runs publish\n   nothing. The pointer clause is there because a failure *after* the manifest\n   pointer moved is not a freeze at all: `publishedAt` advanced and clients are\n   already bootstrapping the new snapshot, so a broken durable-fallback upload\n   files no issue — it closes an open one, on the same `publishedAt` criterion\n   the staleness monitor would use two hours later, and reports itself in the\n   run summary and the red run instead. The matching `if: success()` step closes\n   the issue with the new `publishedAt`. The job carries a minimal `permissions`\n   block (`contents: read`, `issues: write`, and `actions: read` so it can read\n   back which step failed).\n2. **The published manifest says nothing moved.**\n   `.github/workflows/snapshot-staleness.yml` runs every two hours, fetches\n   `https://mcpfinder.dev/api/v1/snapshot/manifest.json` the way a client would,\n   and alarms when `publishedAt` is older than **18 hours** — three missed\n   6-hourly builds, so a single failed cycle stays quiet. An unreachable\n   endpoint, unparseable JSON, a `publishedAt` that is not an ISO-8601 instant,\n   or a timestamp in the future are alarm states of their own, never a silent\n   pass. The fetch retries on `[500, 1500, 4500]` ms with a 15s per-attempt\n   timeout, so one 502 or DNS blip does not file an issue the next run closes.\n\nNeither signal subsumes the other: the failure step cannot see a run that never\nstarted or a green run whose bytes never reached R2, and the age monitor cannot\nsay which step broke. The threshold logic lives in\n`scripts/check-snapshot-staleness.mjs` — dependency-free, and unit-tested by\n`scripts/test-snapshot-artifacts.mjs` — rather than inline in YAML.\n`MCPFINDER_SNAPSHOT_BASE_URL` repoints both the monitor and the upload preflight\nat a staging Worker. Both signals go through one shared implementation,\n`scripts/snapshot-freeze-signal.mjs`, which owns the label, the title, the\nduplicate-thread reconciliation, and the throttle that keeps an unchanged alarm\nto one comment per 12 hours instead of one per two-hour pass. \"Unchanged\" is\njudged on the verdict's *cause* — `age-exceeded`, `http-502`,\n`no-published-at`, the failing step names — not on its wording: the wording\ncarries the age, which moves on every pass, while a 502 that becomes a DNS\nfailure is a different incident and comments immediately.\n\nEvery alarm says, in the issue body, that the previous complete snapshot is\nstill being served. A freeze is a data stall; treating it as an outage is how a\nmonitor loses its audience.\n\n**What neither signal covers.** The staleness monitor is itself a GitHub\nscheduled workflow, so repository inactivity takes it down with the build it\nwatches: GitHub disables cron schedules in a repository idle for 60 days, and\nthat one switch silences both. The same applies to an Actions outage, a\nmanually disabled workflow, or an archived repository — a dead monitor produces\nno red X and no issue, which is indistinguishable from a healthy one. Nothing\ninside GitHub can close that gap. The only complete fix is an **external uptime\ncheck** that fetches `/api/v1/snapshot/manifest.json` from outside GitHub and\nalerts on `publishedAt` age. Until one exists, read the two signals above as\ncovering build and publication failures, not the disappearance of the schedule.\n\n## Example Workflow\n\nUser request:\n\n```text\nI need my assistant to read data from PostgreSQL.\n```\n\nAgent workflow:\n\n```text\nsearch_mcp_servers(query=\"postgres\")\nget_server_details(name=\"io.example/postgres\")\nget_install_config(name=\"io.example/postgres\", platform=\"cursor\")\n```\n\nAgent response:\n\n```text\nI found a PostgreSQL MCP server with official registry presence and recent metadata.\nIt requires DATABASE_URL and runs via npx.\nAdd this JSON to ~/.cursor/mcp.json, then reload Cursor.\n```\n\n## Repository Layout\n\n```text\nmcpfinder/\n├── packages/\n│   ├── core/          # sync, SQLite search, trust signals, install-config generation\n│   └── mcp-server/    # stdio MCP server\n├── landing/           # static website and AI-facing public files\n├── api-worker/        # snapshot/support worker for published bootstrap artifacts\n└── scripts/           # snapshot builder and other support scripts\n```\n\n## Development\n\n```bash\npnpm install\npnpm --filter @mcpfinder/core build\npnpm --filter @mcpfinder/server build\nnode packages/mcp-server/dist/index.js\n```\n\n### Dependency policy\n\n`pnpm-workspace.yaml` enforces three supply-chain rules on every resolution:\n`minimumReleaseAge: 10080` (no npm release younger than seven days),\n`blockExoticSubdeps: true` (every transitive dep must resolve from the registry,\nnever a git URL or bare tarball), and `trustPolicy: no-downgrade` (reject a\nversion whose trust evidence is weaker than that of any version published\nbefore it).\n\npnpm 11 also re-applies all three to the existing lockfile under\n`--frozen-lockfile`, so CI enforces them on a lockfile someone else committed —\na violation fails the install outright.\n\nTwo consequences worth knowing before they surprise you:\n\n- **A freshly published version will not resolve for a week.** That is the\n  point. The packages listed under `overrides` are the exception: they are a\n  hand-maintained set of security pins, so they appear in\n  `minimumReleaseAgeExclude` and update the day a patch lands. Remove a package\n  from `overrides` and remove it from that list too.\n- **`trustPolicyExclude` carries exactly one pinned entry**, `undici-types@6.21.0`,\n  with the evidence for why it is benign recorded in the file. Pin any future\n  exemption to an exact version the same way, never to a bare package name.\n\n## Current Limitations\n\n- The local `stdio` server is the canonical interface. Install via `npx -y @mcpfinder/server`.\n- There is no hosted HTTP MCP endpoint currently served at `mcpfinder.dev/mcp`. The `api-worker` package is reserved for snapshot support and will only be promoted to a canonical HTTP transport once it exposes the same tool contract as the stdio server.\n- Tool metadata quality depends on upstream registries; some servers have rich details, others only partial metadata.\n- Tool-level capability extraction is currently strongest for sources that expose tool manifests directly, especially Glama.\n\n## Roadmap\n\nThese items are planned but not yet implemented. Informed largely by feedback\nfrom AI agents consuming the tool surface.\n\n- **Semantic search over tool descriptions.** Today's search ranks by keyword\n  (FTS5) + popularity + source count. It doesn't help when a user describes a\n  capability in prose that doesn't overlap lexically with the server's name or\n  description. Plan: index `toolsExposed[*].description` (where upstream exposes\n  it) into a lightweight embedding column, expose a `semanticQuery` parameter\n  alongside the existing keyword `query`, and rank hybrid.\n- **Hosted HTTP MCP endpoint at `mcpfinder.dev/mcp`.** Today only stdio is\n  canonical. Serverless AI agents (Workers, Lambda, browser) can't spawn a\n  subprocess; giving them an HTTP transport with the same 4-tool contract\n  removes an entire class of blocker. Plan: port the MCP SDK streamable-http\n  transport into `api-worker/`, re-use the same snapshot-backed database via\n  R2 + Durable Objects, gate with a lightweight rate limit.\n- **Capability-count enrichment for non-Glama rows.** `capabilityCount` is\n  currently 0 for most Official/Smithery rows because those upstreams don't\n  publish tool manifests in list responses. Plan: during the snapshot build,\n  probe the downstream server's README or, for npm packages, parse the tarball's\n  `package.json` for an `mcp.tools` hint; surface per-row confidence in the\n  extracted list.\n- **CI automation for npm + Registry publish.** Today the release playbook\n  (`docs/publish-playbook.md`) is manual and consumes a fresh OTP per package.\n  Plan: move to GitHub Actions with NPM automation tokens and a committed\n  `mcp-publisher` login step triggered on `v*` tags.\n\n## Links\n\n- Website: [mcpfinder.dev](https://mcpfinder.dev)\n- GitHub: [mcpfinder/mcpfinder](https://github.com/mcpfinder/mcpfinder)\n- npm: [@mcpfinder/server](https://www.npmjs.com/package/@mcpfinder/server)\n- MCP Registry: [`dev.mcpfinder/server`](https://registry.modelcontextprotocol.io/v0/servers?search=dev.mcpfinder)\n\nBuilt by [Coder AI](https://coderai.dev) under [AGPL-3.0-or-later](LICENSE).\n",
  "bytes": 29908,
  "sha": "7a81724983bc9f9312ff32a887c84977ba470fa6655eb90d26b380891cf4fbc4",
  "repo_slug": "mcpfinder/mcpfinder",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_dev_mcpfinder_server_7dfae3e2/readme"
}