Metadata-Version: 2.4
Name: glossary-mcp
Version: 0.2.0
Summary: General-purpose MCP server to read, search, and safely edit a conlangkit-format glossary
Project-URL: Homepage, https://github.com/dhh1128/glossary-mcp
Project-URL: Repository, https://github.com/dhh1128/glossary-mcp
Project-URL: Issues, https://github.com/dhh1128/glossary-mcp/issues
Author-email: Daniel Hardman <daniel.hardman@gmail.com>
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: claude,conlangkit,glossary,mcp,terminology
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.11
Requires-Dist: conlangkit>=0.1
Requires-Dist: mcp<3,>=2.0
Requires-Dist: pydantic>=2.0
Description-Content-Type: text/markdown

# glossary-mcp

[![CI](https://github.com/dhh1128/glossary-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/dhh1128/glossary-mcp/actions/workflows/ci.yml)

A small, **general-purpose** MCP server that lets an AI **read, search, and safely
edit a glossary file** in the [conlangkit](https://github.com/dhh1128/conlangkit)
glossary format — for *any* glossary, not tied to any one project.

The glossary is a Markdown pipe-table with four columns:

```
lemma | tags | definition | notes
----- | ---- | ---------- | -----
core  | tier | the constitutive tier-1 material of a party | never shared
```

Writes go through conlangkit, which owns the file: it keeps entries sorted (by byte
order) and re-emits the glossary on save. On an already-sorted file that yields a
**minimal diff** — only the touched row moves. The file is re-read from disk before
each write (so a long-lived server picks up external hand-edits) and the write is
atomic (temp file + `os.replace`).

## Prerequisites

- Python 3.11+
- [uv](https://docs.astral.sh/uv/)
- A glossary directory or file, pointed to by `GLOSSARY_PATH`

[conlangkit](https://github.com/dhh1128/conlangkit) (the glossary format library)
is a normal PyPI dependency, pulled in by `uv sync`.

### MCP SDK compatibility

| glossary-mcp | `mcp` SDK |
|---|---|
| `0.2.0`+ | `2.x` |
| `0.1.1` | `1.x` (pinned `<2`) |
| `0.1.0` | **broken — do not use** |

`0.1.0` declared only `mcp>=1.0`, so once `mcp` 2.0 was released a fresh resolve
pulled it in — and 2.0 removed `mcp.server.fastmcp`. The server then died at
import with `ModuleNotFoundError`, which a client sees only as a server offering
**no tools at all** (a crashed stdio server cannot report why). If you are pinned
to `0.1.0`, upgrade — restarting will not help. `0.1.1` pinned `mcp<2` as a
stopgap; `0.2.0` ports to the 2.x API proper.

## Installation

```bash
uv sync   # creates .venv, installs glossary-mcp (editable) + conlangkit + dev deps
```

## Running tests

```bash
uv run pytest   # runs the suite with branch coverage (gate: 100%)
```

The smoke tests run without `GLOSSARY_PATH`. Integration tests run against a
fixture glossary.

## Running the server

```bash
export GLOSSARY_PATH=/path/to/glossary        # a directory containing glossary.md …
export GLOSSARY_PATH=/path/to/glossary.md     # … or a direct path to the file
uv run glossary-mcp
```

`GLOSSARY_PATH` may be a **directory** (the server looks for `glossary.md` inside
it) or a **file**. The server uses stdio transport. For a local checkout, set
`command` to a resolvable path, e.g. the project's
`/path/to/glossary-mcp/.venv/bin/glossary-mcp`:

```json
{
  "mcpServers": {
    "glossary": {
      "command": "glossary-mcp",
      "env": {
        "GLOSSARY_PATH": "/path/to/glossary"
      }
    }
  }
}
```

For consuming the **published** package from *another* repo without a local
checkout or absolute paths, use `uvx` — see the next section.

## Consuming from another repo (portable `.mcp.json`)

`glossary-mcp` is published to [PyPI](https://pypi.org/project/glossary-mcp/),
so any other repo can wire it into its MCP configuration with **no local checkout,
no venv, and no absolute paths**. `uvx` fetches and caches the package (and its
`conlangkit` / `mcp` dependencies) on demand:

```json
{
  "mcpServers": {
    "glossary": {
      "type": "stdio",
      "command": "uvx",
      "args": ["glossary-mcp==0.2.0"],
      "env": {
        "GLOSSARY_PATH": "${CLAUDE_PROJECT_DIR:-.}/../glossary"
      }
    }
  }
}
```

- **`command: "uvx"`, `args: ["glossary-mcp"]`** — the console-script name
  equals the package name, so **no `--from` flag is needed**. `uvx` resolves the
  package from PyPI into an ephemeral, cached environment on first launch.
- **`GLOSSARY_PATH`** is the only required environment variable. It may point at a
  directory (the server looks for `glossary.md` inside) or directly at a file. The
  `${CLAUDE_PROJECT_DIR:-.}/../glossary` value assumes the standard sibling layout
  where the consuming repo and its `glossary` repo are checked out side by side.
  `${CLAUDE_PROJECT_DIR}` resolves the project root and requires **Claude Code ≥
  v2.1.195**; the `:-.` fallback keeps it working (relative to the current
  directory) on older versions and other MCP clients.
- **Pin the version while pre-1.0.** Because the tool surface may still change
  before 1.0, pin so a new release can't reach every consuming repo at once and
  break them in lockstep: `"args": ["glossary-mcp==0.2.0"]`. Bump
  deliberately (repo by repo, or via your propagation tooling). Float the version
  (bare `"glossary-mcp"`, always latest) only once the surface stabilizes
  at ≥ 1.0.

## Tools

| Tool | Description |
|---|---|
| `lookup_term` | Exact lemma lookup → tags, definition, notes |
| `search_by_definition` | Find entries whose definition matches a query |
| `search_notes` | Free-text search over the notes column |
| `glossary_stats` | Entry count, tag histogram, unique-tag count |
| `list_terms` | All lemmas, optionally filtered by tag |
| `add_term` | Append a new entry (refuses a duplicate lemma) |
| `update_term` | Edit an existing entry's tags/definition/notes by lemma |

### A note on slashes in definitions

In the conlangkit format, `/` separates alternative senses in the definition column.
A definition that contains a *literal* slash (e.g. `allow/ask/deny`) is stored
escaped on disk as `allow\/ask\/deny` — which renders as a plain `/` on GitHub
(CommonMark/GFM). **You never type the escape**: pass ordinary prose (with plain
slashes) to `add_term`/`update_term`, and read ordinary prose back — the server
escapes and unescapes transparently.

### Case sensitivity

Lemma lookup, insertion, and sorting are **case-sensitive** (byte order) — conlangkit
targets writing systems where case-folding is not meaningful, so `core` and `Core`
are distinct entries. Use each term's canonical case: lowercase for ordinary terms,
uppercase for acronyms (`AID`, `GCD`). This keeps you from creating near-duplicates.

## Methodology

Non-trivial work in this repo runs under the `cc` craft methodology
(`~/code/me/cc`); read `constitution.md` first. Follow strict TDD (see
`AGENTS.md`).
