Skip to main content
Glama
README.md
# License MCP Server - The Hard Way

Build an MCP server that answers **"what licenses are my dependencies under, and can I ship this?"**
from a project's lockfiles, mostly offline. You write the SPDX parser, the installed-package readers, the
lockfile parsers, the messy-string normaliser, the compatibility table and the policy engine yourself,
then put MCP in front of them and test the whole thing over real stdio.

At the end you will have built a minimal version of [license-mcp](https://github.com/0xmortuex/license-mcp)
and you will understand the parts most "dependency checker" tutorials skip:

- **where a license answer really comes from** (installed metadata beats lockfile beats network) and why a
  report must say which one it used;
- **what "unknown" means** and why it must never be guessed into "MIT";
- **how to make an ambiguity visible** instead of silently resolving it;
- **how to design a compatibility table you can defend** (ordered rules with reasons, not a mystery matrix);
- **how to design verdicts** (ok / needs review / violation) so that every one has a reason.

## Who this is for

Developers who have built a "hello world" MCP server and want a real one; anyone who has been asked
"are we allowed to ship this?" and wants an agent that can answer with evidence.

**Prerequisites:** Python 3.10+ and `pip install -r requirements.txt`. Nothing else: no network, no QEMU,
no external tools. (Module 03 reads lockfiles that were generated with `uv` and `npm`; they are
committed in `fixtures/`.)

## Modules

| # | Module | What you build |
|---|--------|----------------|
| 01 | [SPDX Grammar](01-spdx-grammar/) | A tokenizer and recursive-descent parser for `MIT OR (Apache-2.0 AND ISC)`, `WITH` exceptions, precedence, and "can this expression be satisfied by my allowed set?" |
| 02 | [Installed Metadata](02-installed-metadata/) | Reading `dist-info/METADATA` and `node_modules/*/package.json`, picking the best license field, and cross-checking against `importlib.metadata` |
| 03 | [Lockfiles](03-lockfiles/) | Parsers for `requirements.txt`, `uv.lock`, `package-lock.json` and `Cargo.lock`: packages, direct vs transitive, embedded license data |
| 04 | [Messy Strings](04-messy-strings/) | Normalising `MIT License`, `Apache 2`, `GPLv2+`, trove classifiers and deprecated ids; flagging ambiguity; refusing to guess |
| 05 | [The Compatibility Table](05-compatibility-table/) | Ordered, cited rules for "may X go into a Y project?", including expression semantics |
| 06 | [The Policy Engine](06-policy-engine/) | Allow / deny / project-license / exceptions, and verdicts that always carry reasons |
| 07 | [MCP Wrapper and Testing](07-mcp-wrapper-and-testing/) | Two tools, a directory sandbox, errors an agent can act on, and a test that launches the server over real stdio |

Each module contains:
- **`README.md`**: the concepts and the design decisions
- **`main.py`**: working, commented code that checks its own results with `assert`
- **`break-it.md`**: challenges that break your code, with hints

## How to use this course

**Build along:** read a module, run its `main.py`, do the break-it challenges. **Reference:** jump to a
module; modules 04-07 load the earlier `main.py` files by path, so keep the directory together.

Run everything the way CI does:

```bash
pip install -r requirements.txt
python verify.py          # all modules
python verify.py 03 05    # just these
```

`verify.py` runs each `main.py` as a child process (with `PYTHONIOENCODING=utf-8`, so Windows consoles
do not crash on non-ASCII) and fails if any assertion fails. CI runs it on Ubuntu and Windows.

## License

MIT