Metadata-Version: 2.4
Name: gengomcp
Version: 1.0.0
Summary: MCP server for finding ACL conference papers about NLP from a Qdrant database
License-Expression: MIT
Project-URL: Homepage, https://github.com/sobamchan/gengomcp
Project-URL: Repository, https://github.com/sobamchan/gengomcp
Project-URL: Issues, https://github.com/sobamchan/gengomcp/issues
Keywords: mcp,model-context-protocol,papers,nlp,qdrant,vector-search,rag,retrieval
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Operating System :: OS Independent
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Database
Classifier: Framework :: AsyncIO
Classifier: Typing :: Typed
Requires-Python: >=3.13
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: mcp[cli]>=1.9.0
Requires-Dist: qdrant-client>=1.13.0
Requires-Dist: sentence-transformers>=3.2.0
Requires-Dist: python-dotenv>=1.0.0
Dynamic: license-file

# gengomcp

An [MCP](https://modelcontextprotocol.io/) server (Python, stdio transport) that
lets an agent retrieve ACL conference papers about NLP from a [Qdrant](https://qdrant.tech/) vector
database. It combines **semantic search** (Sentence‑Transformers embeddings)
with **structured filtering** by bibliographic fields like publication year and
venue.

> **Qdrant access is currently limited.** This server queries a shared Qdrant
> collection of ACL NLP papers. If you'd like credentials to use it, please
> reach out to the project maintainer — access may be granted at a limited
> scale. You'll receive a `QDRANT_URL`, `QDRANT_KEY`, and
> `QDRANT_COLLECTION_NAME` to add to your `.env` or MCP client `env` field.

## Quick start

```bash
# 1. install (from the project root)
uv sync            # creates .venv and installs deps (mcp, qdrant-client, sentence-transformers, python-dotenv)

# 2. configure — NEVER commit real keys
cp .env.example .env   # edit .env with your Qdrant credentials

# 3. run
uv run gengomcp             # console script (registered by pyproject.toml)
# or: uv run python server.py
# or: uv run python main.py
```

The server reads its `.env` from the project root (next to `server.py`), so it also
works when launched from another working directory (e.g. by an MCP client or a
sandbox) — see [Wiring it into an MCP client](#wiring-it-into-an-mcp-client).

> The API key is read from the environment and is **never logged or hard-coded**.
> For local runs, copy [`.env.example`](.env.example) → `.env` and fill in your
> Qdrant connection details (`.env` is gitignored). For MCP-client deployment,
> you can inject the same variables via your client's `env` field instead —
> see [Configuring credentials via the MCP client](#configuring-credentials-via-the-mcp-client).

### Wiring it into an MCP client

Any MCP client over **stdio** works. The command below is cwd‑independent (the
absolute server path means the server loads `.env` from its own project root),
so it's safe to launch from any working directory.

Example for Claude Desktop (`claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "gengomcp": {
      "command": "uv",
      "args": ["run", "--project", "/Users/.../gengomcp", "gengomcp"]
    }
  }
}
```

Using the installed `gengomcp` console script (registered by `pyproject.toml`) is
the simplest entry point. For setups without `uv`, point `command` at the script
in the venv directly:

```json
"command": "/Users/.../gengomcp/.venv/bin/gengomcp",
"args": []
```

#### Configuring credentials via the MCP client

Credentials (`QDRANT_URL`, `QDRANT_KEY`, `QDRANT_COLLECTION_NAME`) are read from
the process environment, so you can inject them **directly through your MCP
client's `env` field** — no `.env` file required. This is the recommended way to
wire per-agent secrets:

```json
{
  "mcpServers": {
    "gengomcp": {
      "command": "uv",
      "args": ["run", "--project", "/Users/.../gengomcp", "gengomcp"],
      "env": {
        "QDRANT_URL": "https://<cluster>.cloud.qdrant.io",
        "QDRANT_KEY": "<your-api-key>",
        "QDRANT_COLLECTION_NAME": "papers_test"
      }
    }
  }
}
```

> **Precedence:** environment variables set by the MCP client (`env` field)
> always take priority. A local `.env` file is only used as a fallback
> (loaded with `python-dotenv`, which does **not** override existing env vars).
> This means you can use `.env` while developing locally and switch to the
> client `env` field for production without changing the server.

Required variables (no defaults):

| Variable | Description |
| --- | --- |
| `QDRANT_URL` | Qdrant cluster URL |
| `QDRANT_KEY` | Qdrant API key |
| `QDRANT_COLLECTION_NAME` | Collection to search (e.g. `papers_test`) |

Optional variables (have defaults, see [`.env.example`](.env.example)):
`EMBEDDING_MODEL`, `AUTO_CREATE_INDEXES`, `LOG_LEVEL`.

If a required variable is missing at startup, the server exits with a clear error
explaining how to set it.

#### Poolside (`pool`)

The server is already registered for you. Verify with:

```bash
pool mcp list          # shows: gengomcp
pool mcp get gengomcp  # shows the stored command + args
```

It was added with the cwd‑independent command above, stored under `mcp_servers`
in `~/.config/poolside/settings.yaml` (personal config). The server reads its
own `.env`, so no keys are stored in the poolside config. To remove it later:

```bash
pool mcp remove gengomcp
```

## Tools

| Tool | Purpose |
| --- | --- |
| `search_papers` | **Semantic search for ACL NLP papers.** USE when the user has a topic/question. Embeds `query` and returns the most similar papers, optionally narrowed by structured filters. |
| `get_paper` | USE to inspect a single ACL NLP paper in full detail (abstract, summaries, entities) when you already have its `paper_uuid` from a search result. |
| `list_papers` | USE to **browse/filter ACL NLP papers with no query text** — pure structured filtering + pagination (e.g. "all ACL 2024 papers"). |
| `get_collection_info` | USE first to discover available venues, years, fields of study, and vector names before building filters. |

### `search_papers` parameters

```
query                 str   (required) search text
limit                 int   = 10   (clamped 1..100)
vector_name           str   = "overview"   one of overview/approach/challenge/outcome
year                  int            exact publication year (e.g. 2026)
year_min / year_max   int            year range (inclusive)
year_gt  / year_lt    int            year range (exclusive)
venue                 str            substring match on the booktitle (e.g. "Annual Meeting")
collection_acronym    str            exact venue acronym, e.g. "ACL" / "EMNLP" / "NAACL"
collection_id         str            e.g. "2026.acl"
field_of_study        list[str]      membership on `field_of_studies` (e.g. ["Reasoning"])
author                str            name contained in `author_names`
min_score             float          only return results with similarity >= this value
```

All filters are AND‑combined, so you can layer them, e.g.
`search_papers(query="...", year_min=2020, collection_acronym="ACL")`.

### Example tool calls

```
search_papers(query="stress testing large language models",
              vector_name="overview", year_min=2024, year_max=2026,
              collection_acronym="ACL", limit=5)

get_paper(paper_id="000036a6-e2be-523e-8b8d-0f2cbe2b39e7")

list_papers(collection_acronym="EMNLP", year=2024, limit=20)

list_papers(field_of_study=["Reasoning"], author="Pan", limit=20, offset=<prev_uuid>)
```

## How it works

* **Secrets & config** — credentials are read from environment variables
  (`QDRANT_URL`, `QDRANT_KEY`, `QDRANT_COLLECTION_NAME`). For local runs, copy
  `.env.example` → `.env` and fill them in. For MCP deployment, inject the
  same variables via your client's `env` field instead. `QDRANT_KEY` is used
  directly by the Qdrant client and is never printed or hard-coded.
* **Payload indexes** — Qdrant **requires** a payload index to filter on a
  field. This collection ships with no indexes, so the server creates the needed
  ones **idempotently at startup** (non-destructive — it only adds indexes).
  Disable with `AUTO_CREATE_INDEXES=0` if you manage indexes yourself.
* **Embeddings** — queries are embedded with Sentence‑Transformers using
  `Snowflake/snowflake-arctic-embed-s`, the **only** model that matches this
  collection's 384-dimensional index. The server can truncate+renormalise other
  model outputs to the index dimensionality (matryoshka‑style) as a safety net,
  but models in a different embedding space (e.g. the 768-dim `m-v1.5`) will
  still fail to retrieve — see [The embedding model](#the-embedding-model).
* **Named vectors** — the `overview`/`approach`/`challenge`/`outcome` named
  vectors in the collection are all **384-dimensional**.

### The embedding model

The collection's vectors are **384-dimensional** and were built with the
**Snowflake arctic-embed "s" model** (`Snowflake/snowflake-arctic-embed-s`).
This is the **only** model that produces embeddings in the correct space for
this index — it is the default and **should not be changed**.

The Snowflake family ships in several sizes, but only the `s` (384-dim) variant
matches this collection's index:

| model | dims | works with this index? |
| --- | --- | --- |
| `Snowflake/snowflake-arctic-embed-s`  | **384** | ✅ yes (default) |
| `snowflake-arctic-embed-m-v1.5` | 768 | ❌ no — different embedding space |
| `snowflake-arctic-embed-l-v1.5` | 1024 | ❌ no — different embedding space |

The `m` and `l` variants live in **different embedding spaces** than the
stored 384-dim vectors — even though the server can truncate to match
dimensionality, the resulting embeddings will not align with the index and
retrieval will fail (verified: ~0 cosine similarity against stored vectors).
Keep `EMBEDDING_MODEL` at its default unless you re-index the collection with a
different model.

## Project layout

```
gengomcp/
├── server.py        # the MCP server (tools + Qdrant/Embeddings glue)
├── main.py          # thin launcher
├── pyproject.toml   # deps + `gengomcp` console script
├── uv.lock          # pinned dependency versions
├── LICENSE          # MIT
├── .env             # local secrets  (gitignored — never commit)
├── .env.example     # template (committed)
└── README.md
```

## Development / testing

```bash
uv run python -c "import server; print('ok')"
```
