{
  "markdown": "# Alexandria\n\n[![npm version](https://img.shields.io/npm/v/@the-40-thieves/alexandria-mcp.svg)](https://www.npmjs.com/package/@the-40-thieves/alexandria-mcp)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](cursor://anysphere.cursor-deeplink/mcp/install?name=alexandria&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB0aGUtNDAtdGhpZXZlcy9hbGV4YW5kcmlhLW1jcCJdfQ==)\n[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=alexandria&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40the-40-thieves%2Falexandria-mcp%22%5D%7D)\n[Install in Goose](goose://extension?cmd=npx&arg=-y&arg=%40the-40-thieves%2Falexandria-mcp&id=alexandria&name=Alexandria&description=Query%2C%20read%2C%20and%20ingest%20texts%20from%20152%20public%20digital%20libraries)\n\nA Model Context Protocol (MCP) server for querying, reading, and ingesting texts from 152 public digital libraries. Works with any MCP-compatible client (Claude Desktop, Cursor, VS Code Copilot, etc.).\n\n## Tools\n\n| Tool | Description |\n|---|---|\n| `library_list_sources` | List all 152 sources with descriptions and full-text capabilities |\n| `library_ask(query, max_sources?, results_per_source?)` | **Natural language search** — routes your query to the best sources, searches in parallel, returns unified deduplicated results |\n| `library_search(query, source, limit?)` | Search a specific source by title, author, or keywords |\n| `library_read(id, source)` | Fetch full text or metadata for an item (200k char limit) |\n| `library_index(id, source)` | Dry run: chunk and score text quality without writing anything |\n| `library_ingest(id, source)` | Chunk → embed → store in your vector database. Idempotent. |\n| `library_recommend(id, limit?)` | Get similar papers via Semantic Scholar's recommendation engine (up to 500) |\n| `library_answer(query, max_sources?, results_per_source?, read_top?)` | Ask a question and get a synthesized answer with inline `[n]` citations, fused across sources with reciprocal rank fusion; `warnings[]` flags an uncited or all-dropped answer |\n| `library_research(query, depth?, breadth?, max_minutes?)` | Recursive multi-round research: generates queries, answers each, extracts learnings, and writes a final cited report over every source found |\n| `library_health_check(source?, cluster?)` | Report per-source health (`ok`, `degraded`, `down`, `key_missing`, `unknown`), merging this process's live error rate/latency with the last off-process probe run |\n| `library_citations(id, source, direction, limit?, format?)` | List the works an item cites (`direction: \"references\"`) or the works that cite it (`direction: \"citations\"`), via OpenAlex's citation graph with OpenCitations as a fallback; `format: \"bibtex\" \\| \"ris\" \\| \"apa\"` also returns a `formatted` bibliography string |\n\n`library_ask` is the primary entry point. `library_search` is for targeted queries against a known source. `library_index` / `library_ingest` are for building a vector knowledge base from retrieved texts. `library_answer` and `library_research` synthesize a cited answer or report instead of returning raw results. `library_health_check` tells you whether a source is worth calling before you call it. `library_citations` walks the citation graph around an item and can export it as a bibliography.\n\n`library_ask`, `library_search`, `library_answer`, `library_research`, `library_health_check`, and `library_citations` take a `response_format: \"concise\" | \"detailed\"` parameter (default `concise`); concise trims results and citations to the high-signal fields (title, source, id, year, hasFullText, url; answer/report + citations; name, cluster, status), detailed returns the full payload, including routing reasons, relevance scores, per-stage diagnostics, and per-source error rate/latency/quota usage. In `detailed` mode, `library_search` also attaches a `resource_link` content item for each full-text result, pointing at that item's `library://doc/{source}/{id}` resource (see below): a client with resource support can read the full text directly instead of a second `library_read` call.\n\n## Prompts\n\nThree ready-made research workflows, surfaced by MCP clients as slash commands (Claude Code's `/alexandria:<name>`, VS Code's `/alexandria.prompt`). Each returns a single message naming the tools to call, in order. It does not call any tool itself.\n\n| Prompt | Description |\n|---|---|\n| `literature_review(topic, depth?)` | Survey a topic across sources and produce a cited report |\n| `fact_check_claim(claim)` | Check one claim against the library and report whether it is supported |\n| `verify_bibliography(references)` | Check that a list of references (one per line) resolves to real, findable items |\n\n## Resources\n\n`library://doc/{source}/{id}` reads the same text `library_read(id, source)` returns (including the open-access fallback below), addressed by the `source`/`id` pair `library_search` or `library_ask` returned. Clients that support MCP resources (Claude Code's `@srv:uri`, VS Code's Add Context) can pull an item's full text directly.\n\n<!-- sources:start -->\n## Sources (152)\n\n152 sources across 19 clusters (36 hidden pending a key or config not present in this deployment). Full per-source detail, including auth env vars and last-verified dates, is generated in [docs/sources.md](docs/sources.md).\n\n| Cluster | Sources | Hidden |\n|---|---|---|\n| academic | 22 | 3 |\n| ai_research | 3 | 0 |\n| archives | 4 | 2 |\n| culture | 8 | 3 |\n| developer | 17 | 5 |\n| economics | 11 | 3 |\n| geopolitical | 3 | 3 |\n| government | 7 | 3 |\n| law | 4 | 1 |\n| literature | 16 | 2 |\n| markets | 2 | 1 |\n| news_global | 5 | 2 |\n| news_regional | 15 | 0 |\n| real_estate | 2 | 2 |\n| science | 8 | 2 |\n| security | 14 | 0 |\n| standards | 3 | 0 |\n| video | 1 | 1 |\n| web | 7 | 3 |\n| **Total** | 152 | 36 |\n\n<!-- sources:end -->\n\n## Credentials\n\nMost tools query external library APIs directly and need no credentials at all. The two optional dependencies are scoped to specific tools:\n\n### OpenAI, optional ([platform.openai.com](https://platform.openai.com/api-keys))\n\nRequired by two tools only:\n\n- **`library_ask`**: uses `gpt-4o-mini` to route your natural language query to the right sources and generate optimized per-source search terms. Without this key, use `library_search` to query sources directly.\n- **`library_ingest`**: uses `text-embedding-3-small` to embed chunked text before writing to the vector store.\n\n`library_list_sources`, `library_search`, `library_read`, `library_index`, and `library_recommend` all work without an OpenAI key.\n\n#### Pointing roles at a gateway instead of OpenAI directly\n\nEvery LLM/embedding call (routing in `library_ask`, embeddings in `library_ingest`) goes through a small per-role provider table (`src/utils/providers.ts`, THE-318) instead of talking to OpenAI's SDK directly. There are five roles: `router`, `synth`, `research`, `embeddings`, `rerank`. Each is resolved from env in this order:\n\n1. `ALEXANDRIA_<ROLE>_BASE_URL`, `ALEXANDRIA_<ROLE>_API_KEY`, `ALEXANDRIA_<ROLE>_MODEL` (per-role overrides; `<ROLE>` is the role name upper-cased, e.g. `ALEXANDRIA_ROUTER_BASE_URL`)\n2. `ALEXANDRIA_BASE_URL`, `ALEXANDRIA_API_KEY` (shared defaults across every role)\n3. `OPENAI_API_KEY`, with `baseURL` defaulted to `https://api.openai.com/v1`\n\nWith only `OPENAI_API_KEY` set, every role resolves through step 3, which is exactly today's behavior (`gpt-4o-mini` for `router`/`synth` against `api.openai.com`, `text-embedding-3-small` for `embeddings`).\n\nTo route every role through a LiteLLM gateway instead:\n\n```env\nALEXANDRIA_BASE_URL=http://100.78.123.100:4001/v1\nALEXANDRIA_API_KEY=sk-litellm-...\n```\n\nTo route through Cloudflare AI Gateway (its OpenAI-compatible endpoint):\n\n```env\nALEXANDRIA_BASE_URL=https://gateway.ai.cloudflare.com/v1/<account_id>/<gateway_id>/openai\nALEXANDRIA_API_KEY=<your OpenAI key, forwarded through the gateway>\n```\n\nOr point just one role at a gateway while the rest stay on OpenAI directly, e.g. `ALEXANDRIA_ROUTER_BASE_URL` + `ALEXANDRIA_ROUTER_API_KEY` for routing only.\n\nSee [docs/cloudflare.md](docs/cloudflare.md) for the fuller Cloudflare integration guide: AI Gateway's newer unified endpoint, Workers AI for the `embeddings`/`rerank` roles, Tunnel + Access for a private `/mcp`, WAF rate limiting for a public one, R2 as an optional cache store, the Browser Run fetch tier below, and why Alexandria stays a hybrid (Node core, Cloudflare services) rather than a full Workers port.\n\nWhen `ALEXANDRIA_<ROLE>_BASE_URL`/`ALEXANDRIA_BASE_URL` is set *and* `OPENAI_API_KEY` is also present, `OPENAI_API_KEY` is wired up as a one-shot fallback: a network error or 5xx from the gateway falls through to a direct OpenAI call once before the request fails. `chatJSON` (used by routing) always validates the model's response against a zod schema and retries once, on the same backend, with the validation error appended to the prompt, which helps when a gateway is proxying a smaller or local model that doesn't reliably follow the JSON contract on the first try. It requests `response_format: json_object` only against `api.openai.com`, or when `ALEXANDRIA_<ROLE>_JSON_MODE=1` confirms the gateway/model supports it; otherwise it asks for JSON in the prompt instead.\n\n### Supabase — optional ([supabase.com](https://supabase.com/dashboard))\n\nRequired by one tool only:\n\n- **`library_ingest`** — writes chunked, embedded text into a pgvector table for semantic search. Without this, retrieved texts stay in-context and are not persisted anywhere.\n\nEverything else — searching, reading, browsing, getting recommendations — queries external sources in real time and needs no database.\n\n### Source-specific keys\n\nSome sources require their own API key. These are free registrations. Sources without a key listed here work without any credentials.\n\n| Env Var | Source(s) | Get It |\n|---|---|---|\n| `CORE_API_KEY` | `core` | [core.ac.uk/services/api](https://core.ac.uk/services/api) |\n| `COURTLISTENER_API_KEY` | `courtlistener` | [courtlistener.com/profile/tokens](https://www.courtlistener.com/profile/tokens/) |\n| `GOVINFO_API_KEY` | `govinfo`; also accepted by `congress` and `regulations` as a fallback for `DATA_GOV_API_KEY` | [api.data.gov/signup](https://api.data.gov/signup/). Does not cover `smithsonian`, which needs its own `SMITHSONIAN_API_KEY` from the same signup page |\n| `GOOGLE_BOOKS_API_KEY` | `googlebooks` | [Google Cloud Console](https://console.cloud.google.com/) → APIs & Services → Books API |\n| `BHL_API_KEY` | `bhl` | [biodiversitylibrary.org/getapikey](https://www.biodiversitylibrary.org/getapikey.aspx) |\n| `DIGITALNZ_API_KEY` | `digitalnz` | [digitalnz.org/developers](https://digitalnz.org/developers) |\n| `DPLA_API_KEY` | `dpla` | [pro.dp.la/developers/api-codex](https://pro.dp.la/developers/api-codex) |\n| `EUROPEANA_API_KEY` | `europeana` | [apis.europeana.eu](https://apis.europeana.eu/en/) — test key immediate, personal ~1 week |\n| `GITHUB_TOKEN` | required by `githubsearch` and `githubmcp`; optional for `ghsa` and `openiti` | [github.com/settings/tokens](https://github.com/settings/tokens), public repo read scope. `githubsearch` and `githubmcp` are hidden without it; `ghsa` falls back to 60s pacing and `openiti` to an unauthenticated search path |\n| `NASA_ADS_API_KEY` | `nasaads` | [ui.adsabs.harvard.edu/user/settings/token](https://ui.adsabs.harvard.edu/user/settings/token) |\n| `SPRINGER_OA_API_KEY` + `SPRINGER_META_API_KEY` | `springer` | [dev.springernature.com](https://dev.springernature.com/) — same registration, two keys |\n| `ZENODO_API_KEY` | `zenodo` | [zenodo.org/account/settings/applications/tokens/new](https://zenodo.org/account/settings/applications/tokens/new/) — optional, increases rate limits |\n| `SMITHSONIAN_API_KEY` | `smithsonian` | [api.data.gov/signup](https://api.data.gov/signup/). Its own key, separate from `GOVINFO_API_KEY` |\n| `SEMANTIC_SCHOLAR_API_KEY` | `semanticscholar` | [semanticscholar.org/product/api](https://www.semanticscholar.org/product/api) — optional, increases rate limits |\n| `TROVE_API_KEY` | `trove` | [trove.nla.gov.au/about/create-something/using-api](https://trove.nla.gov.au/about/create-something/using-api) — ~1 week approval |\n| `YOUTUBE_API_KEY` | `youtube` | [console.cloud.google.com](https://console.cloud.google.com/) — enable YouTube Data API v3; search only, transcripts need no key |\n\n## Privacy Policy\n\nAlexandria has no telemetry of its own: it runs on infrastructure you\ncontrol, and nothing about your queries, results, or credentials is sent to\nthe Alexandria project.\n\n- Queries go to the upstream public library sources the agent selects for a\n  given request, using each source's own public API under that source's own\n  terms.\n- `library_ask`, `library_answer`, `library_research`, and `library_ingest`,\n  when an OpenAI (or OpenAI-compatible gateway) key is configured, also send\n  your query text and retrieved excerpts to the LLM and embeddings provider\n  the operator set up. See [docs/cloudflare.md](docs/cloudflare.md) for the\n  Cloudflare AI Gateway routing path and\n  [docs/fetch-tier-runtime.md](docs/fetch-tier-runtime.md) for how outbound\n  fetches to library sources are guarded.\n- Nothing is stored by the project itself. The operator's own `data/`\n  directory holds local caches (state DB, per-process read cache) on the\n  machine running the server, and nowhere else.\n\nFull text: [PRIVACY.md](PRIVACY.md).\n\n## Setup\n\n```bash\ngit clone https://github.com/The-40-Thieves/alexandria-mcp\ncd alexandria-mcp\nnpm install\nnpm run build\n```\n\nCopy `.env.example` to `.env`. Minimum configuration to run with no credentials (search and read only):\n\n```env\nTRANSPORT=stdio\n```\n\nTo enable `library_ask`:\n\n```env\nTRANSPORT=stdio\nOPENAI_API_KEY=sk-...\n```\n\nTo enable `library_ingest`:\n\n```env\nTRANSPORT=stdio\nOPENAI_API_KEY=sk-...\nSUPABASE_URL=https://your-project.supabase.co\nSUPABASE_SERVICE_ROLE_KEY=eyJ...\n```\n\n## Supabase Schema\n\nRequired only if using `library_ingest`:\n\n```sql\ncreate table if not exists knowledge_chunks (\n  id bigserial primary key,\n  content text not null,\n  embedding vector(1536),\n  mcp_name text,\n  metadata jsonb,\n  created_at timestamptz default now()\n);\n\ncreate table if not exists source_docs (\n  id bigserial primary key,\n  source_url text not null,\n  mcp_name text not null,\n  title text,\n  source text,\n  chunk_count int,\n  indexed_at timestamptz,\n  unique (source_url, mcp_name)\n);\n\ncreate index if not exists knowledge_chunks_embedding_hnsw_idx\n  on knowledge_chunks using hnsw (embedding vector_cosine_ops);\n```\n\nIf you set this schema up before Task 12, you had an `ivfflat` index instead\n(`knowledge_chunks_embedding_idx`) - `docs/sql/match_chunks.sql` drops it and\ncreates the `hnsw` one shown above, since HNSW builds incrementally and needs\nno `lists` tuning constant as the table grows. If corpus-as-cache hits seem to\nbe missing results a plain `<=>` scan would have found, raise the query-time\n`hnsw.ef_search` session setting (default 40) at the cost of a slower query -\nsee the commented recommendation in `docs/sql/match_chunks.sql`.\n\n### Corpus as cache\n\n`library_answer` can also read straight from `knowledge_chunks` - previously\ningested text, already embedded - as one more ranked list next to the live\nper-source search, skipping the network entirely for a hit it already has\nthe full text for. This only ever serves chunks from a source whose registry\nfreshness is `static` or `daily` (never `realtime`), and only above\n`ALEXANDRIA_CORPUS_MIN_SIM` cosine similarity (default `0.92`).\n\nIt needs one more piece of schema beyond the table above: run\n`docs/sql/match_chunks.sql` in the Supabase SQL editor. It defines\n`match_knowledge_chunks()`, the nearest-neighbor search function\n`SupabaseVectorStoreProvider.query()` calls via `.rpc()`, and the `hnsw`\nindex above. This file was written against the current pgvector/supabase-js\ndocs but has not been run against a live database - verify it against your\nown project before relying on it.\n\n## Claude Code (npx)\n\n```bash\nclaude mcp add --env OPENAI_API_KEY=sk-... alexandria -- npx -y @the-40-thieves/alexandria-mcp\n```\n\nSearch and read work with no environment variables at all; the `--env` flag\nabove is only needed to enable `library_ask`, `library_answer`,\n`library_research`, and `library_ingest`. See [Credentials](#credentials)\nfor the full list of optional keys.\n\n## Claude Desktop (stdio)\n\nMinimum config (search and read only), using the published package via `npx`:\n\n```json\n{\n  \"mcpServers\": {\n    \"library\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@the-40-thieves/alexandria-mcp\"],\n      \"env\": {\n        \"TRANSPORT\": \"stdio\"\n      }\n    }\n  }\n}\n```\n\nWith `library_ask` and `library_ingest` enabled:\n\n```json\n{\n  \"mcpServers\": {\n    \"library\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@the-40-thieves/alexandria-mcp\"],\n      \"env\": {\n        \"TRANSPORT\": \"stdio\",\n        \"OPENAI_API_KEY\": \"sk-...\",\n        \"SUPABASE_URL\": \"https://your-project.supabase.co\",\n        \"SUPABASE_SERVICE_ROLE_KEY\": \"eyJ...\"\n      }\n    }\n  }\n}\n```\n\nFrom a local checkout instead of the published package, replace `command`/\n`args` with `\"command\": \"node\", \"args\": [\"/path/to/alexandria-mcp/dist/index.js\"]`.\n\n## Railway (HTTP)\n\nHTTP mode is off by default. Set `TRANSPORT=http` to serve Streamable HTTP on\n`/mcp` instead of speaking stdio, and `PORT` to choose the listen port\n(default `3000`; Railway sets `PORT` for you). Every request gets its own MCP\nserver and transport, so the endpoint is stateless and safe to run behind a\nload balancer.\n\n| Env Var | Values | Default |\n|---|---|---|\n| `TRANSPORT` | `stdio` or `http` | `stdio` |\n| `PORT` | any port number, HTTP mode only | `3000` |\n\n`/mcp` (only) is guarded: DNS-rebinding-safe Host/Origin validation\n(`ALEXANDRIA_ALLOWED_ORIGINS`, a comma-separated hostname list - loopback\nis always allowed regardless) and a per-client-IP rate limit\n(`ALEXANDRIA_HTTP_RATE_LIMIT`, default 60/minute, `429` with a JSON-RPC\nerror body once exceeded). See `docs/fetch-tier-runtime.md` for the\ndetails and `src/httpGuards.ts` for the implementation.\n\n**Set `ALEXANDRIA_ALLOWED_ORIGINS` on any deployment reachable by a\nhostname other than loopback.** With it unset, `Host`-header validation is\n**off** and the server logs one warning at startup saying so: a deployment's\n`Host` header is its own hostname, which cannot be in an allowlist that does\nnot exist, so enforcing the check without one would `403` every request.\nSetting it is what turns DNS-rebinding protection on. The `Origin` check is\nunconditional either way, so a browser request carrying an `Origin` outside\nthe list is always rejected.\n\n`POST /mcp` requires `content-type: application/json` (anything else is\n`415`) and a body no larger than 100 KiB (`413`, connection closed).\n\nBehind a reverse proxy or PaaS edge (Cloudflare Tunnel, Railway's own edge,\n...) the rate limiter's per-client key defaults to `req.socket.remoteAddress`,\nwhich is the proxy's address for every caller, not the caller's - set\n`ALEXANDRIA_TRUSTED_PROXY=1` to key on `CF-Connecting-IP` (falling back to\nthe *rightmost* `X-Forwarded-For` entry, the one appended by the last hop;\nthe leftmost entry is whatever the original caller sent) instead. Only set this once `/mcp` is\nreachable exclusively through a proxy you trust to set those headers\nhonestly - see `docs/cloudflare.md`'s Tunnel and Access section.\n\nServes both eras of the MCP protocol on the same `/mcp` endpoint:\n`createMcpHandler(factory, { legacy: 'stateless' })` (`@modelcontextprotocol/server`,\nadapted to `node:http` by `toNodeHandler()` from `@modelcontextprotocol/node`)\nanswers a 2026-07-28 `server/discover` probe or per-request envelope on the\nmodern path, and falls back to the same stateless idiom the pre-2026 SDK used\nfor a 2025-era `initialize` handshake. One `createServer()` factory backs\nboth. stdio uses the connection-pinned `serveStdio(factory)` from\n`@modelcontextprotocol/server/stdio`, which selects the era from the\nconnection's opening exchange. Note: the 2025-era fallback path answers over\n`text/event-stream` (SSE) rather than a bare JSON body, since the SDK exposes\nno equivalent to v1's `enableJsonResponse` for that path. Any MCP client\nbuilt on a Streamable HTTP transport (the SDK's own `StreamableHTTPClientTransport`\nincluded) already parses either format transparently.\n\nSet those (plus any source keys) in the Railway dashboard and deploy:\n\n```bash\nrailway up\n```\n\nLocally the same thing is:\n\n```bash\nTRANSPORT=http PORT=3000 npm start\n```\n\nRegister in Claude Desktop:\n\n```json\n{\n  \"mcpServers\": {\n    \"library\": {\n      \"url\": \"https://your-service.up.railway.app/mcp\"\n    }\n  }\n}\n```\n\nHealth check: `GET /health` returns `{ status: \"ok\", version: \"11.0.0\", sources: { total: 152, visible: 116, hidden: 36, calls: 0, errors: 0 }, byKind: { rest: 118, hub: 0, rss: 22, mcp: 6, scrape: 6 }, quota: { day: \"2026-09-02\", reserved: 0, sources: 0, backend: \"state\" }, cache: { entries: 0 }, tools: 11 }`.\n\nMetrics: `GET /metrics` returns per-source counters (calls, errors, timeouts, cacheHits, quotaRejections, latencyMsTotal) and per-tool counters (invocations, llmCalls) as JSON, e.g. `{ \"sources\": { \"arxiv\": { \"calls\": 12, \"errors\": 0, \"timeouts\": 0, \"cacheHits\": 3, \"quotaRejections\": 0, \"latencyMsTotal\": 4210 } }, \"tools\": { \"library_ask\": { \"invocations\": 5, \"llmCalls\": 5 } } }`. Only sources/tools actually called since the process started appear.\n\n## Install in other clients\n\nAll of these run the published package via `npx`; search and read work with\nno environment variables, and `--env`/env block additions enable\n`library_ask`, `library_answer`, `library_research`, and `library_ingest` the\nsame way the Claude Code and Claude Desktop sections above do.\n\n**GitHub Copilot** - `.vscode/mcp.json`:\n\n```json\n{\n  \"servers\": {\n    \"alexandria\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@the-40-thieves/alexandria-mcp\"]\n    }\n  }\n}\n```\n\n**Windsurf** - `mcp_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"alexandria\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@the-40-thieves/alexandria-mcp\"]\n    }\n  }\n}\n```\n\n**Codex CLI**:\n\n```bash\ncodex mcp add alexandria -- npx -y @the-40-thieves/alexandria-mcp\n```\n\n**OpenCode** - `opencode.json`:\n\n```json\n{\n  \"mcp\": {\n    \"alexandria\": {\n      \"type\": \"local\",\n      \"command\": [\"npx\", \"-y\", \"@the-40-thieves/alexandria-mcp\"]\n    }\n  }\n}\n```\n\n**Amazon Q** - `~/.aws/amazonq/mcp.json` (global) or `q mcp add`:\n\n```json\n{\n  \"mcpServers\": {\n    \"alexandria\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@the-40-thieves/alexandria-mcp\"]\n    }\n  }\n}\n```\n\n**Kiro** - `.kiro/settings/mcp.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"alexandria\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@the-40-thieves/alexandria-mcp\"]\n    }\n  }\n}\n```\n\n**Gemini CLI**:\n\n```bash\ngemini extensions install https://github.com/The-40-Thieves/alexandria-mcp\n```\n\n**Continue** - add to the `mcpServers` array in your Continue config:\n\n```json\n{\n  \"name\": \"alexandria\",\n  \"command\": \"npx\",\n  \"args\": [\"-y\", \"@the-40-thieves/alexandria-mcp\"]\n}\n```\n\n## Adding Custom Providers\n\nThe pipeline is provider-agnostic. To add a new embedding model or vector store:\n\n1. Implement `EmbeddingProvider` or `VectorStoreProvider` from `src/types.ts`\n2. Add your implementation to `src/pipeline/providers/`\n3. Register it in `src/pipeline/providers/index.ts`\n4. Set `EMBEDDING_PROVIDER` or `VECTOR_STORE_PROVIDER` in your env\n\n```typescript\n// Example: Ollama embedding provider\nimport type { EmbeddingProvider } from '../../types.js';\n\nexport class OllamaEmbeddingProvider implements EmbeddingProvider {\n  readonly dimensions = 768;\n\n  async embed(texts: string[]): Promise<number[][]> {\n    // your implementation\n  }\n}\n```\n",
  "bytes": 23977,
  "sha": "cbef2473a88eab29b6dd286599c49862966eb84429dd1dc8cd1d46b12b4f490e",
  "repo_slug": "the-40-thieves/alexandria-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_the_40_thieves_alexandria_mcp_3bc36050/readme"
}