feat: MCP server catalog core -- signed, validated, resolvable (#10, #61)
CI / Lint (ruff) (pull_request) Successful in 12s
CI / Tests (py3.12 / windows-latest) (pull_request) Failing after 17s
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 10s
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 14s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 39s

Phase 1 of the MCP server catalog: pure, GUI-free core functions plus the
seed data/catalog.json (20 servers). No GUI wiring in this PR -- bcc.py is
untouched; a follow-up PR adds the picker dialog.

- load_catalog(): strict json.loads ONLY. The lenient repair pipeline
  (repair_json_text / parse_pasted_json*) is never used on catalog bytes,
  by design and by comment, so a signature always authenticates exactly
  what gets parsed.
- validate_catalog(): rejects the whole file (not per-entry) on: bad
  schema/version types, missing tier-appropriate fields (basic needs
  config.command+args, link-only needs docs_url and no config), a
  command allowlist (npx/uvx/docker/node/python/python3 only), -e/--eval/-c
  denial for node/python, --privileged and root/$HOME volume-mount denial
  for docker, non-empty env_required values (hard rejection -- secrets
  never ship in the catalog), secret-looking args (reuses
  _TOKEN_PREFIXES/_is_secret_value rather than reimplementing), non-https
  URL fields, and non-ASCII code points in id/command/args (homoglyph
  defence).
- verify_catalog_signature(): Ed25519 via the cryptography package,
  domain-separated message (the literal prefix "bcc-catalog-v1|" + raw
  bytes), accepts a match against any key in CATALOG_PUBKEYS
  (rotation-ready), never raises.
- resolve_catalog(): picks the highest version among bundled/cached/remote
  candidates that EACH independently pass verify + validate -- the bundled
  catalog gets no implicit trust, closing the hole where an unsigned
  payload merged to main would win on being local. Anti-rollback (never
  regress below the best verified candidate already in hand) and
  anti-freeze (reject a jump of more than 1000 versions) built in.
- catalog_entry_to_paste_json() / config_has_unfilled_placeholders(): small
  pure helpers the future GUI dialog will use to feed a catalog pick into
  the existing paste-import path and to gate Save on unfilled placeholder
  tokens.

data/catalog.json: the provided 20-server seed, with a signed_at field
added at the top level (lives inside the signed payload once real signing
lands in #62). Wired into bcc.spec's PyInstaller datas so it bundles into
the frozen app.

Security requirements from the issue, and where they landed:
- Catalog bytes never touch the lenient JSON repair path -- enforced by
  load_catalog()'s strict json.loads and a comment warning against wiring
  it in later.
- env_required values are a hard rejection when non-empty, not a warning.
- Secret-looking args are rejected at validation time, reusing the
  existing secret-detection helpers instead of duplicating them.
- Non-ASCII id/command/args rejected (typosquat/homoglyph defence).
- URL fields restricted to https://.
- Ed25519 signature verification is domain-separated and never raises.
- The bundled catalog is verified at runtime exactly like remote/cached --
  no implicit trust for being local.
- Anti-rollback and anti-freeze bounds on resolve_catalog's version
  comparison.

Tests: 42 new tests added to tests/test_core.py (full suite: 239 passed,
1 pre-existing unrelated skip). ruff check and ruff format --check both
clean.
This commit is contained in:
Cowork Agent
2026-07-12 17:32:20 -04:00
parent e6b60e94e7
commit 48904c7787
5 changed files with 1254 additions and 1 deletions
+462
View File
@@ -0,0 +1,462 @@
{
"schema": 1,
"version": 1,
"updated": "2026-07-12",
"servers": [
{
"id": "filesystem",
"display": "Filesystem",
"description": "Lets Claude read, write, search, and edit files in directories you explicitly allow.",
"category": "files",
"homepage": "https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem",
"stars": 85995,
"official": true,
"setup": "basic",
"config": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"<ALLOWED_DIR>"
]
},
"placeholders": {
"<ALLOWED_DIR>": "Absolute path to a directory the server may read/write. Add more directories as additional args."
},
"env_required": {},
"docs_url": "https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem",
"notes": "Part of the official modelcontextprotocol/servers reference monorepo (star count is for the whole repo). Clients that support MCP 'roots' can also grant directories dynamically instead of via args."
},
{
"id": "fetch",
"display": "Fetch",
"description": "Fetches a URL and converts the page to clean markdown so Claude can read web content.",
"category": "dev",
"homepage": "https://github.com/modelcontextprotocol/servers/tree/main/src/fetch",
"stars": 85995,
"official": true,
"setup": "basic",
"config": {
"command": "uvx",
"args": [
"mcp-server-fetch"
]
},
"placeholders": {},
"env_required": {},
"docs_url": "https://github.com/modelcontextprotocol/servers/tree/main/src/fetch",
"notes": "Can access local/internal IPs, so treat as a mild security risk on untrusted networks. Add '--ignore-robots-txt' or '--user-agent=...' as extra args if needed."
},
{
"id": "memory",
"display": "Memory",
"description": "Gives Claude a persistent knowledge-graph memory that survives across conversations.",
"category": "ai",
"homepage": "https://github.com/modelcontextprotocol/servers/tree/main/src/memory",
"stars": 85995,
"official": true,
"setup": "basic",
"config": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-memory"
]
},
"placeholders": {},
"env_required": {},
"docs_url": "https://github.com/modelcontextprotocol/servers/tree/main/src/memory",
"notes": "Stores entities/relations as a local JSON knowledge graph (path configurable via MEMORY_FILE_PATH env var)."
},
{
"id": "sequential-thinking",
"display": "Sequential Thinking",
"description": "Gives Claude a structured, step-by-step reasoning tool for breaking down complex problems.",
"category": "ai",
"homepage": "https://github.com/modelcontextprotocol/servers/tree/main/src/sequentialthinking",
"stars": 85995,
"official": true,
"setup": "basic",
"config": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-sequential-thinking"
]
},
"placeholders": {},
"env_required": {},
"docs_url": "https://github.com/modelcontextprotocol/servers/tree/main/src/sequentialthinking",
"notes": "Set DISABLE_THOUGHT_LOGGING=true to stop it from printing each thought step to the console."
},
{
"id": "git",
"display": "Git",
"description": "Lets Claude read history, diff, branch, and search a local git repository.",
"category": "dev",
"homepage": "https://github.com/modelcontextprotocol/servers/tree/main/src/git",
"stars": 85995,
"official": true,
"setup": "basic",
"config": {
"command": "uvx",
"args": [
"mcp-server-git",
"--repository",
"<REPO_PATH>"
]
},
"placeholders": {
"<REPO_PATH>": "Absolute path to the local git repository"
},
"env_required": {},
"docs_url": "https://github.com/modelcontextprotocol/servers/tree/main/src/git",
"notes": "Operates on a local checkout only; it does not talk to GitHub/GitLab APIs (pair with the GitHub or GitLab server for that)."
},
{
"id": "github",
"display": "GitHub",
"description": "Lets Claude read repos/code, and manage issues, pull requests, and Actions on GitHub.",
"category": "code-hosting",
"homepage": "https://github.com/github/github-mcp-server",
"stars": 30202,
"official": true,
"setup": "basic",
"config": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"GITHUB_PERSONAL_ACCESS_TOKEN",
"ghcr.io/github/github-mcp-server"
]
},
"placeholders": {},
"env_required": {
"GITHUB_PERSONAL_ACCESS_TOKEN": ""
},
"docs_url": "https://github.com/github/github-mcp-server/blob/main/docs/installation-guides/README.md",
"notes": "The old '@modelcontextprotocol/server-github' npm package is discontinued (deprecated April 2025). GitHub now ships a Docker-based local server (requires Docker installed/running) plus a hosted remote server at https://api.githubcopilot.com/mcp/ that supports OAuth or PAT auth without Docker."
},
{
"id": "playwright",
"display": "Playwright",
"description": "Lets Claude drive a real browser (click, type, navigate, screenshot) using Playwright's accessibility-tree snapshots.",
"category": "browser",
"homepage": "https://github.com/microsoft/playwright-mcp",
"stars": 34000,
"official": true,
"setup": "basic",
"config": {
"command": "npx",
"args": [
"@playwright/mcp@latest"
]
},
"placeholders": {},
"env_required": {},
"docs_url": "https://github.com/microsoft/playwright-mcp#readme",
"notes": "Maintained by the Playwright team at Microsoft. Add '--isolated' for a throwaway profile, or '--browser firefox|webkit|msedge' to change engine. A persistent browser profile is used by default so logins carry over between sessions."
},
{
"id": "chrome-devtools",
"display": "Chrome DevTools",
"description": "Lets Claude control Chrome and use real DevTools features: performance traces, network inspection, console logs, screenshots.",
"category": "browser",
"homepage": "https://github.com/ChromeDevTools/chrome-devtools-mcp",
"stars": 45000,
"official": true,
"setup": "basic",
"config": {
"command": "npx",
"args": [
"-y",
"chrome-devtools-mcp@latest"
]
},
"placeholders": {},
"env_required": {},
"docs_url": "https://github.com/ChromeDevTools/chrome-devtools-mcp#readme",
"notes": "Maintained by the Google Chrome DevTools team; only officially supports Google Chrome / Chrome for Testing. Exposes the browser's content to the MCP client, so avoid sensitive sites while connected. Add '--slim --headless' for a minimal 3-tool basic-automation mode."
},
{
"id": "postgres",
"display": "Postgres MCP Pro",
"description": "Lets Claude query, inspect schema, and analyze/tune performance of a PostgreSQL database.",
"category": "database",
"homepage": "https://github.com/crystaldba/postgres-mcp",
"stars": 2400,
"official": false,
"setup": "basic",
"config": {
"command": "uvx",
"args": [
"postgres-mcp",
"--access-mode=unrestricted"
]
},
"placeholders": {},
"env_required": {
"DATABASE_URI": ""
},
"docs_url": "https://github.com/crystaldba/postgres-mcp#readme",
"notes": "The official reference Postgres server was archived by the MCP team; this community server (Crystal DBA) is the most capable/most-referenced replacement, adding index tuning and EXPLAIN-plan analysis. Use --access-mode=restricted for read-only/production use. Docker image also available (crystaldba/postgres-mcp)."
},
{
"id": "n8n",
"display": "n8n",
"description": "Build, validate, and deploy n8n workflows with full node documentation for the AI.",
"category": "infra",
"homepage": "https://github.com/czlonkowski/n8n-mcp",
"stars": 22257,
"official": false,
"setup": "basic",
"config": {
"command": "npx",
"args": [
"n8n-mcp"
]
},
"placeholders": {},
"env_required": {
"MCP_MODE": "",
"N8N_API_URL": "",
"N8N_API_KEY": ""
},
"docs_url": "https://github.com/czlonkowski/n8n-mcp",
"notes": "Set MCP_MODE=stdio (required for Claude Desktop, prevents debug logs from breaking the protocol). N8N_API_URL/N8N_API_KEY are optional \u2014 without them you still get full node documentation, validation, and template search; with them you get live workflow create/update/execute against your own n8n instance. A hosted free-tier alternative exists at dashboard.n8n-mcp.com."
},
{
"id": "notion",
"display": "Notion",
"description": "Read, search, and edit Notion pages, databases, and comments from your AI assistant.",
"category": "productivity",
"homepage": "https://github.com/makenotion/notion-mcp-server",
"stars": 4400,
"official": true,
"setup": "basic",
"config": {
"command": "npx",
"args": [
"-y",
"@notionhq/notion-mcp-server"
]
},
"placeholders": {},
"env_required": {
"NOTION_TOKEN": ""
},
"docs_url": "https://developers.notion.com/docs/mcp",
"notes": "Notion is prioritizing its hosted remote MCP (OAuth, https://mcp.notion.com/mcp) and may eventually sunset this local package, but the stdio server still works today and is the simplest way to get a static config with an internal-integration token."
},
{
"id": "obsidian",
"display": "Obsidian",
"description": "Read, search, and edit notes in your Obsidian vault.",
"category": "personal",
"homepage": "https://github.com/MarkusPfundstein/mcp-obsidian",
"stars": 4067,
"official": false,
"setup": "basic",
"config": {
"command": "uvx",
"args": [
"mcp-obsidian"
]
},
"placeholders": {},
"env_required": {
"OBSIDIAN_API_KEY": "",
"OBSIDIAN_HOST": "",
"OBSIDIAN_PORT": ""
},
"docs_url": "https://github.com/MarkusPfundstein/mcp-obsidian",
"notes": "Requires the Obsidian Local REST API community plugin installed and enabled in Obsidian; copy the API key from the plugin settings. OBSIDIAN_HOST defaults to 127.0.0.1 and OBSIDIAN_PORT to 27124 if omitted."
},
{
"id": "brave-search",
"display": "Brave Search",
"description": "Search the web, news, images, and videos using Brave's independent search index.",
"category": "search",
"homepage": "https://github.com/brave/brave-search-mcp-server",
"stars": 1288,
"official": true,
"setup": "basic",
"config": {
"command": "npx",
"args": [
"-y",
"@brave/brave-search-mcp-server",
"--transport",
"stdio"
]
},
"placeholders": {},
"env_required": {
"BRAVE_API_KEY": ""
},
"docs_url": "https://github.com/brave/brave-search-mcp-server",
"notes": "Official Brave server; replaced the old archived modelcontextprotocol/servers brave-search entry (now in modelcontextprotocol/servers-archived). Get an API key from the Brave Search API dashboard."
},
{
"id": "tavily",
"display": "Tavily",
"description": "AI-optimized web search, extract, map, and crawl API built for LLM agents.",
"category": "search",
"homepage": "https://github.com/tavily-ai/tavily-mcp",
"stars": 2206,
"official": true,
"setup": "basic",
"config": {
"command": "npx",
"args": [
"-y",
"tavily-mcp@latest"
]
},
"placeholders": {},
"env_required": {
"TAVILY_API_KEY": ""
},
"docs_url": "https://github.com/tavily-ai/tavily-mcp",
"notes": "Tavily also offers a hosted remote MCP endpoint (mcp.tavily.com) with OAuth as an alternative to running the local npx server."
},
{
"id": "firecrawl",
"display": "Firecrawl",
"description": "Turn any website into clean, LLM-ready data with scraping, crawling, and search tools.",
"category": "search",
"homepage": "https://github.com/firecrawl/firecrawl-mcp-server",
"stars": 6924,
"official": true,
"setup": "basic",
"config": {
"command": "npx",
"args": [
"-y",
"firecrawl-mcp"
]
},
"placeholders": {},
"env_required": {
"FIRECRAWL_API_KEY": ""
},
"docs_url": "https://github.com/firecrawl/firecrawl-mcp-server",
"notes": "Repo moved from mendableai/firecrawl-mcp-server to firecrawl/firecrawl-mcp-server. A keyless free tier works for scrape/search with rate limits; set FIRECRAWL_API_URL instead of the key to point at a self-hosted Firecrawl instance."
},
{
"id": "home-assistant",
"display": "Home Assistant",
"description": "Control smart home devices, query states, and troubleshoot automations in Home Assistant.",
"category": "smart-home",
"homepage": "https://github.com/voska/hass-mcp",
"stars": 308,
"official": false,
"setup": "basic",
"config": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"HA_URL",
"-e",
"HA_TOKEN",
"voska/hass-mcp"
]
},
"placeholders": {},
"env_required": {
"HA_URL": "",
"HA_TOKEN": ""
},
"docs_url": "https://github.com/voska/hass-mcp",
"notes": "HA_URL example: http://homeassistant.local:8123 (use http://host.docker.internal:8123 if HA runs in Docker on the same machine). HA_TOKEN is a Home Assistant long-lived access token from your profile page. A more actively developed alternative is the community 'HA-MCP' integration (homeassistant-ai/ha-mcp, ~3.9k stars), but it installs inside Home Assistant itself via HACS rather than as an external stdio process, so it doesn't fit this catalog's launch-line format."
},
{
"id": "kubernetes",
"display": "Kubernetes",
"description": "Lets Claude inspect and manage Kubernetes/OpenShift resources \u2014 pods, deployments, logs, Helm releases \u2014 using your local kubeconfig.",
"category": "infra",
"homepage": "https://github.com/containers/kubernetes-mcp-server",
"stars": 1626,
"official": false,
"setup": "basic",
"config": {
"command": "npx",
"args": [
"-y",
"kubernetes-mcp-server@latest"
]
},
"placeholders": {},
"env_required": {},
"docs_url": "https://github.com/containers/kubernetes-mcp-server#readme",
"notes": "Not an official Kubernetes SIG project, but a Go-native (no kubectl dependency) implementation maintained under the 'containers' GitHub org (Podman/Red Hat-adjacent) that's widely regarded as the most capable K8s MCP server, supporting Kubernetes and OpenShift. Uses your existing ~/.kube/config automatically; add --read-only to prevent writes."
},
{
"id": "aws-api-mcp-server",
"display": "AWS API MCP Server (AWS Labs)",
"description": "Lets your AI assistant run AWS CLI commands to inspect and manage AWS resources across virtually every AWS service.",
"category": "cloud",
"homepage": "https://github.com/awslabs/mcp",
"stars": 9431,
"official": true,
"setup": "basic",
"config": {
"command": "uvx",
"args": [
"awslabs.aws-api-mcp-server@latest"
]
},
"placeholders": {},
"env_required": {},
"docs_url": "https://awslabs.github.io/mcp/servers/aws-api-mcp-server",
"notes": "AWS credentials are NOT set in this MCP config \u2014 configure them beforehand via `aws configure` (or set AWS_API_MCP_PROFILE_NAME to pick a named profile) so boto3's standard credential chain can find them. Optional env vars: AWS_REGION (default us-east-1), READ_OPERATIONS_ONLY=true to block all mutating AWS calls. AWS notes this server is being superseded by a newer unified AWS MCP server referenced in their agent-toolkit docs."
},
{
"id": "grafana",
"display": "Grafana",
"description": "Query dashboards, datasources, alerts and incidents in Grafana from your AI assistant.",
"category": "observability",
"homepage": "https://github.com/grafana/mcp-grafana",
"stars": 3227,
"official": true,
"setup": "basic",
"config": {
"command": "uvx",
"args": [
"mcp-grafana"
],
"env": {
"GRAFANA_URL": "<GRAFANA_URL>"
}
},
"placeholders": {
"<GRAFANA_URL>": "Your Grafana instance URL, e.g. http://localhost:3000 or https://yourstack.grafana.net"
},
"env_required": {
"GRAFANA_SERVICE_ACCOUNT_TOKEN": ""
},
"docs_url": "https://grafana.com/docs/grafana/latest/developer-resources/mcp/",
"notes": "Requires Grafana 9.0+ for full functionality \u2014 datasource-related tools may not work correctly on older versions."
},
{
"id": "slack",
"display": "Slack",
"description": "Search Slack messages, files, and members, and send messages or manage channels from your AI assistant.",
"category": "communication",
"homepage": "https://slack.com/help/articles/48855576908307-Guide-to-the-Slack-MCP-server",
"stars": null,
"official": true,
"setup": "link-only",
"env_required": {},
"docs_url": "https://docs.slack.dev/changelog/2026/02/17/slack-mcp/",
"notes": "Slack's own MCP server went GA Feb 17, 2026 (streamable HTTP at https://mcp.slack.com/mcp, OAuth). No stdio one-liner is published because it's a hosted, permissioned connector. A well-known community alternative, korotovsky/slack-mcp-server (~1.6k GitHub stars, MIT, not an official Slack product), supports stdio/SSE/HTTP with bot or browser-session tokens and no app-install requirement if a stdio option is preferred."
}
],
"signed_at": "2026-07-12T00:00:00Z"
}