{
  "markdown": "# QMD MCP\n\n[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/X1pheR/qmd-mcp/badge)](https://scorecard.dev/viewer/?uri=github.com/X1pheR/qmd-mcp)\n[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/14153/badge)](https://www.bestpractices.dev/projects/14153)\n[![M8ven Live Monitored](https://m8ven.ai/badge/mcp/x1pher-qmd-mcp-jfo7qm)](https://m8ven.ai/mcp/x1pher-qmd-mcp-jfo7qm)\n\nQMD MCP packages [QMD](https://github.com/tobi/qmd) as a long-running Streamable HTTP MCP server. It provides QMD search and document retrieval together with bounded index-maintenance operations, without exposing arbitrary shell execution.\n\nThis is a community-maintained integration. It is not affiliated with, endorsed by, or officially maintained by the upstream QMD project.\n\n## Feedback and contributions\n\nUse GitHub Issues for bug reports and feature requests and pull requests for proposed changes. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the development workflow, test requirements, and coding conventions. Security issues must follow the private process in [`SECURITY.md`](SECURITY.md).\n\nRelease changes are recorded in [`CHANGELOG.md`](CHANGELOG.md).\n\n## Quick start\n\nThe public Docker image is published on GitHub Container Registry (GHCR):\n\n```text\nghcr.io/x1pher/qmd-mcp:v0.1.5\n```\n\nThe package is public, so Docker does not need a GitHub login to pull it.\n\nFor production deployments, use the immutable digest published in the corresponding GitHub Release rather than relying on the version tag alone.\n\nThe image currently supports `linux/amd64`. It intentionally retains only the QMD `linux-x64` native llama runtime to keep the image bounded.\n\n### 1. Create the directories\n\n```bash\nmkdir -p qmd/config qmd/content\ncd qmd\n```\n\nPut the Markdown files you want QMD to index in `content/`.\n\n### 2. Create `config/index.yml`\n\n```yaml\nglobal_context: >-\n  This is a local Markdown knowledge base. Search results are discovery evidence;\n  read the source document before relying on a material claim.\n\ncollections:\n  notes:\n    path: /vault\n    pattern: \"**/*.md\"\n    ignore:\n      - \"archive/**\"\n\n  archive:\n    path: /vault/archive\n    pattern: \"**/*.md\"\n    includeByDefault: false\n\n  append-only-log:\n    path: /vault/logs\n    pattern: \"history.md\"\n    includeByDefault: false\n    embedding: false\n```\n\n`path` values refer to paths inside the container. The Compose example below mounts `./content` at `/vault`.\n\n`embedding: false` is a QMD MCP wrapper extension for collections that should remain lexical-only. The files are still indexed and available to explicit lexical (`lex`) searches, but they are excluded from embedding health and manual `start_embed` jobs. Use it for large append-only logs or other exact-lookup material where repeatedly rebuilding vectors adds cost without useful semantic recall.\n\n### 3. Create `compose.yml`\n\n```yaml\nservices:\n  qmd-mcp:\n    image: ghcr.io/x1pher/qmd-mcp:v0.1.5\n    container_name: qmd-mcp\n    environment:\n      QMD_FORCE_CPU: \"1\"\n      QMD_REFRESH_INTERVAL_MINUTES: \"15\"\n      QMD_REFRESH_INITIAL_DELAY_SECONDS: \"120\"\n    ports:\n      - \"127.0.0.1:8181:8181\"\n    volumes:\n      - ./content:/vault:ro\n      - ./config:/config:ro\n      - qmd-data:/data\n    healthcheck:\n      test:\n        - CMD\n        - node\n        - -e\n        - >-\n          fetch('http://127.0.0.1:8181/health')\n          .then(r=>process.exit(r.ok?0:1))\n          .catch(()=>process.exit(1))\n      interval: 30s\n      timeout: 10s\n      retries: 5\n      start_period: 30s\n    restart: unless-stopped\n\nvolumes:\n  qmd-data:\n```\n\nThe example binds the HTTP port to loopback only. If another container must call QMD MCP directly, attach both containers to a shared Docker network and use the QMD service name instead of exposing it broadly on the host.\n\n`QMD_FORCE_CPU=1` gives a predictable CPU-only deployment. Remove it or set it to `0` if you deliberately want QMD to probe for supported acceleration.\n\n### 4. Start the container\n\n```bash\ndocker compose up -d\n```\n\nCheck the service:\n\n```bash\ncurl --fail http://127.0.0.1:8181/health\n```\n\nThe Streamable HTTP MCP endpoint is:\n\n```text\nhttp://127.0.0.1:8181/mcp\n```\n\n### Docker CLI alternative\n\nYou can run the same release without Compose:\n\n```bash\ndocker volume create qmd-data\n\ndocker run -d \\\n  --name qmd-mcp \\\n  --restart unless-stopped \\\n  -p 127.0.0.1:8181:8181 \\\n  -e QMD_FORCE_CPU=1 \\\n  -e QMD_REFRESH_INTERVAL_MINUTES=15 \\\n  -e QMD_REFRESH_INITIAL_DELAY_SECONDS=120 \\\n  -v \"$PWD/content:/vault:ro\" \\\n  -v \"$PWD/config:/config:ro\" \\\n  -v qmd-data:/data \\\n  ghcr.io/x1pher/qmd-mcp:v0.1.5\n```\n\n## What QMD MCP provides\n\nQMD MCP keeps QMD's read-oriented MCP tools and adds bounded administration operations:\n\n- `health` reports index and runtime state;\n- `start_update` starts a bounded asynchronous filesystem reindex job;\n- `start_embed` starts a bounded asynchronous embedding job;\n- `job_status` reports recent administration jobs;\n- scheduled refresh updates the lexical/index state only; embeddings run explicitly through `start_embed`, while `embedding: false` collections remain lexical-only;\n- routine `query` runs with reranking disabled;\n- `query_reranked` provides a separate CPU-heavy reranked path;\n- query results can include an exact `source_relative_path` for authoritative filesystem handoff when `QMD_SOURCE_RELATIVE_ROOT` is configured and the source path resolves unambiguously;\n- document retrieval returns internal text by default; user-visible MCP resource exposure requires both `exposeToUser=true` and `confirmUserApprovedExposure=true`, and preview/show/open/render/inspect intent is not approval.\n\nOnly one administration job runs at a time. Completed jobs are retained in memory with a bounded history. See [`docs/tools.md`](docs/tools.md) for the complete nine-tool reference, including access level and side effects.\n\n## Runtime paths\n\nThe container uses these stable paths:\n\n| Path | Purpose |\n|---|---|\n| `/config/index.yml` | QMD collection configuration |\n| `/data/index.sqlite` | QMD index database |\n| `/data/home` | Runtime home directory |\n| `/data/cache` | Model and runtime cache |\n\nSource collections should normally be mounted read-only. `/data` must remain writable because it contains the rebuildable index and model/runtime cache.\n\n## Configuration\n\nThe Dockerfile provides working defaults for the normal runtime paths and HTTP listener. Override only the settings your deployment needs.\n\n| Variable | Default | Purpose |\n|---|---:|---|\n| `QMD_HTTP_HOST` | `0.0.0.0` | HTTP listen address inside the container |\n| `QMD_HTTP_PORT` | `8181` | HTTP listen port |\n| `QMD_CONFIG_PATH` | `/config/index.yml` | QMD collection configuration file |\n| `INDEX_PATH` | `/data/index.sqlite` | QMD index database |\n| `QMD_SOURCE_RELATIVE_ROOT` | unset | Optional common source root. When set, query results include exact, collision-safe `source_relative_path` values relative to this root. |\n| `QMD_DEFAULT_COLLECTION` | unset | Default collection for `start_embed`; otherwise the first configured collection is used |\n| `QMD_FORCE_CPU` | `0` | Set to `1` to disable acceleration probing and force CPU use |\n| `QMD_EMBED_PARALLELISM` | unset | Optional QMD embedding parallelism override |\n| `QMD_EMBED_MAX_DOCS_PER_BATCH` | `8` | Default maximum documents per explicit embedding batch; accepted range `1`-`32` |\n| `QMD_EMBED_MAX_BATCH_MB` | `16` | Default maximum explicit embedding batch size in MiB; accepted range `1`-`128` |\n| `QMD_EMBED_MAX_DURATION_MS` | `3600000` | Maximum embedding session length; accepted range `60000`-`7200000` ms |\n| `QMD_REFRESH_INTERVAL_MINUTES` | `15` | Scheduled index-refresh interval; refresh never starts embedding. `0` disables it, maximum `1440` |\n| `QMD_REFRESH_INITIAL_DELAY_SECONDS` | `120` | Delay before the first scheduled refresh; accepted range `0`-`3600` |\n\nInvalid bounded numeric values fail at startup instead of being silently accepted. `QMD_SOURCE_RELATIVE_ROOT` never exposes its absolute path; only a relative source path is returned, and ambiguous normalized-path collisions return `null` rather than guessing.\n\n## Security model\n\n- The container runs as the upstream Node image's unprivileged `node` user.\n- Source collections should normally be mounted read-only.\n- Index and cache state remain separate from source content.\n- Administration is limited to the exposed job operations. The wrapper calls the QMD store API directly; it does not invoke QMD CLI update hooks or expose arbitrary shell execution.\n- MCP request bodies are capped at 1 MiB before JSON parsing.\n- Error messages redact configured index and config paths.\n- MCP transport is not an authentication layer. Keep it on a trusted network boundary or place it behind an authenticated MCP gateway.\n- Production deployments should use an immutable release image digest instead of a branch, `latest`, or another moving tag.\n\nSee [`SECURITY.md`](SECURITY.md) for vulnerability reporting and deployment guidance and [`docs/SECURE-DEVELOPMENT.md`](docs/SECURE-DEVELOPMENT.md) for the secure-design principles, common weakness classes, and review expectations applied to the project.\n\n## Upstream relationship\n\nThis repository is not a fork of the full QMD source tree. It consumes an exact `@tobilu/qmd` package version and applies a small fail-closed compatibility patch set during image build. The build fails if an expected upstream patch target no longer matches exactly.\n\nSee [`UPSTREAM.md`](UPSTREAM.md) for the current upstream version, patch inventory, and update process.\n\n## Validation\n\nThe container build is the primary validation boundary. It installs the locked dependency set, applies every upstream patch, runs the complete unit/property test suite, performs JavaScript syntax checks, and prunes development-only dependencies before the runtime stage. CI also starts the image, initializes the MCP protocol, verifies the exact nine-tool surface, runs a real index update against a temporary Markdown collection, and verifies the resulting document count.\n\nDependency and base-image updates are proposed by Dependabot. A QMD update is accepted only after the image build and functional release acceptance pass against the proposed version.\n\n## Releases\n\nVersions use SemVer tags such as `v0.1.5`. A release must point to an exact CI-green commit. The tag-triggered Release workflow:\n\n1. verifies that the tag matches `package.json`;\n2. builds the `linux/amd64` image;\n3. publishes it to GHCR;\n4. records the immutable image digest;\n5. publishes SBOM/provenance and a GitHub attestation;\n6. creates the corresponding GitHub Release.\n\nNormal CI does not publish images or releases. Release tags are immutable and are never reused for a different commit.\n\n## License\n\nQMD MCP's original wrapper code is MIT licensed. QMD and bundled dependencies retain their own licenses. See [`LICENSE`](LICENSE) and [`UPSTREAM.md`](UPSTREAM.md).\n",
  "bytes": 10903,
  "sha": "34e52f63ded313555feaf0dab5d128e75d082643eeb545777775a8fd410354ad",
  "repo_slug": "x1pher/qmd-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_x1pher_qmd_mcp_11b2fc76/readme"
}