EuropassMCP
# EuropassMCP
An MCP server that lets an AI agent assemble a Europass CV and export it as
strictly schema-compliant Europass XML, or as a PDF carrying that XML inside it.
The Europass CV is the European Union's standardised curriculum vitae format.
Its machine-readable form is an XML document conforming to the Europass CV XML
Schema Definition version 3.0.3. This server gives an agent a handful of small,
section-scoped tools for filling one in over the course of a conversation, and
refuses to export anything the schema would reject, so "almost valid" is not a
state a finished document can reach.
> ### Unofficial
>
> This project is **not affiliated with, endorsed by, or produced by** the
> European Union, the European Commission or Cedefop. It ships no EU emblem and
> no Europass wordmark; the PDF it produces is an independent look-alike of the
> Europass layout, not the official document, and says so on the page. Europass
> is a trademark of the European Union.
>
> The one European Union asset this repository redistributes is the XML schema
> itself, under the EUPL-1.1 grant its authors published it with. See
> [Licensing](#licensing).
## Requirements
- Python 3.11 or newer.
- An MCP client that can launch a server over stdio.
- For PDF export only, four system libraries that pip cannot install. See
[With PDF export](#with-pdf-export).
## Installation
### One line
```sh
curl -fsSL https://raw.githubusercontent.com/Tsadoq/EuropassMCP/main/install.sh | bash
```
That installs `uv` if it is missing, installs the server as a `uv` tool, and
registers it with your client: through `claude mcp add` when the Claude
command-line interface is on your PATH, and otherwise by merging a single
`mcpServers` entry into `claude_desktop_config.json`. The merge rewrites that one
entry and leaves every other server in the file alone, so running the command
twice still leaves exactly one `europassmcp`.
Flags reach the script through the pipe by way of `bash -s --`:
```sh
curl -fsSL https://raw.githubusercontent.com/Tsadoq/EuropassMCP/main/install.sh | bash -s -- --with-pdf
```
`--with-pdf` adds the PDF extra and then points you at
[With PDF export](#with-pdf-export) for the system libraries it cannot install
for you. `--config-only` skips straight to registering the client, for an
install you already have.
Piping a URL into a shell runs whatever that URL serves. If that is not a trade
you want, the script is a file like any other:
```sh
curl -fsSL https://raw.githubusercontent.com/Tsadoq/EuropassMCP/main/install.sh -o install.sh
less install.sh
bash install.sh
```
### By hand
> The commands in this section name `europassmcp` on PyPI, which is not there
> yet. See [Until the PyPI release](#until-the-pypi-release) for the forms that
> resolve today.
The server speaks MCP over stdio, so in normal use a client launches it rather
than you running it by hand. What the script does on your behalf is install the
server and write that launch command into the client's configuration:
```sh
uv tool install europassmcp # or: uv tool install 'europassmcp[pdf]'
```
followed by the stanza under [Client configuration](#client-configuration).
Installing up front is optional, though. The commands below launch the server
through `uvx`, which fetches it on demand; either one is what goes into that
stanza, and running one in a terminal starts a server that waits on stdin.
#### XML only
```sh
uvx europassmcp
```
Nothing outside Python is needed. Every tool works except `export_pdf`, which
refuses with a message naming the command that would fix it.
#### With PDF export
PDF export goes through WeasyPrint, which loads Pango, PangoFT2, HarfBuzz and
fontconfig through the C ABI **at import time**. pip cannot install any of them,
so `europassmcp[pdf]` installs cleanly on a machine that has none of them and
then refuses every PDF.
Install them with the system package manager before you install the extra. If
you do not, `export_pdf` answers with the same refusal it gives an install that
never had the extra at all: the underlying
`OSError: cannot load library 'libpango-1.0-0'` is caught and never reaches you.
So on an install that does already have `europassmcp[pdf]`, that refusal means
these libraries are the part that is missing.
On Debian and Ubuntu:
```sh
sudo apt install libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz0b libfontconfig1
```
On macOS, with Homebrew:
```sh
brew install pango
```
Homebrew pulls glib, harfbuzz and fontconfig in as dependencies of pango.
WeasyPrint's [installation
guide](https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#installation)
is the authority for other platforms and for troubleshooting a load that fails
anyway.
Optionally add `libharfbuzz-subset0` (Debian and Ubuntu; Homebrew's harfbuzz
already includes it). Without it PDFs still render, but every font is embedded
whole instead of subsetted, which makes the file considerably larger.
Then:
```sh
uvx --from 'europassmcp[pdf]' europassmcp
```
### Until the PyPI release
The repository is public, so the one-line installer downloads and runs. What is
still missing is the PyPI release, which is what every command above that names
`europassmcp` on its own needs: `uv tool install europassmcp`, `uvx europassmcp`,
and the `[pdf]` form of each fail to resolve until it exists.
A direct reference to this repository is the same two paths by another route,
and both work today:
```sh
uvx --from 'europassmcp @ git+https://github.com/Tsadoq/EuropassMCP' europassmcp
uvx --from 'europassmcp[pdf] @ git+https://github.com/Tsadoq/EuropassMCP' europassmcp
```
Either one is also what belongs in the `args` of the
[client stanza](#client-configuration), after `--from`.
The installer needs no such adjustment. It asks the index whether the package is
published and installs from this repository for as long as the answer is no, so
it installs the release the day there is one without being edited, and the
stanza it writes names whichever source it installed from.
Against a clone, `uvx --from . europassmcp` and `uvx --from '.[pdf]' europassmcp`
are those two paths once more, and `bash install.sh --config-only` still
registers the client.
This section describes a state that ends with the first release, and
[docs/releasing.md](docs/releasing.md) is where the step that deletes it lives.
## Client configuration
Most MCP clients take the same `mcpServers` stanza; in Claude Desktop it lives in
`claude_desktop_config.json`.
```json
{
"mcpServers": {
"europassmcp": {
"command": "uvx",
"args": ["europassmcp"]
}
}
}
```
With PDF export, the extra moves into the arguments:
```json
{
"mcpServers": {
"europassmcp": {
"command": "uvx",
"args": ["--from", "europassmcp[pdf]", "europassmcp"]
}
}
}
```
Until `europassmcp` reaches PyPI, those `args` have to name this repository
instead; see [Until the PyPI release](#until-the-pypi-release).
Compiling the schema takes roughly three seconds, once, while the server starts.
A client that times out a server's first response quickly may need a longer
startup allowance.
## What the agent gets
Drafts live in memory for the length of the session, keyed by a name the agent
chooses, and are built up section by section. Nothing is written to disk.
| Tool | What it does |
| --- | --- |
| `create_cv` | Starts a new empty draft under a name of the agent's choosing. |
| `set_personal_info` | Records who the CV is about. The one section a valid CV cannot omit. |
| `add_work_experience` | Appends one job, in the order it should be printed. |
| `add_education` | Appends one qualification or training period. |
| `add_language_skill` | Appends one language with its CEFR levels, mother tongues included. |
| `add_digital_skill` | Appends one digital or computer skill. |
| `lookup_country` | Finds Europass country codes from a country name in any of the languages the schema documents. |
| `lookup_language` | Finds Europass language codes the same way. |
| `search_occupation` | Finds ISCO-88 occupation codes from a job description. |
| `validate_cv` | Reports everything the schema objects to, with the nearest valid values for a refused code. |
| `export_xml` | Returns the finished CV as Europass XML. Refuses a draft that would not validate. |
| `export_pdf` | Lays the CV out as a PDF with the XML attached, and returns a link to it. |
Two of these carry most of the design. The lookup tools exist because the
vendored code lists hold 1350 members, including 564 occupations and 197 country
codes, which is far more than belongs in a tool description or in a model's
context; the agent searches instead of being handed a list. And `validate_cv`
answers a refused value with the valid values spelled most like it, so a rejected
`ITA` comes back with `IT`, which is the same search from the other direction.
The PDF comes back as a resource link rather than as inlined bytes, because a
document of several hundred kilobytes would otherwise be replayed into the
model's context on every later turn. The source XML travels inside the PDF as a
file attachment, so the document a person receives is still the document a
machine can read back.
## The vendored schema
The twelve XSD files under `src/europassmcp/schema/` are vendored rather than
fetched, because there is nowhere left to fetch them from: every URL under
`europass.cedefop.europa.eu/xml/` has answered with a redirect to a marketing
page since roughly 2021. They were recovered from the Internet Archive.
[`docs/schema-provenance.md`](docs/schema-provenance.md) records where each file
came from, the one modification made to them, and how to repeat the recovery.
## Licensing
This distribution aggregates two separately licensed bodies of work, kept in
separate directories so that neither licence has to be read as covering the
other.
- **First-party code**, which is everything except `src/europassmcp/schema/`, is
under the MIT Licence in [`LICENSE`](LICENSE).
- **The vendored Europass XML Schema Definition**, in `src/europassmcp/schema/`,
is Copyright European Union 2002-2010, published by Cedefop under the European
Union Public Licence version 1.1 (EUPL-1.1). The MIT grant does not extend to
it.
[`NOTICE`](NOTICE) states the EUPL-1.1 grant in full and travels inside the wheel
and the sdist, which is what keeps the schema redistributable. Redistributing
these files, modified or not, means carrying it with them.
## Development
```sh
uv sync
uv run pytest
uv run pre-commit install
```
`uv run pre-commit run --all-files` runs the same ruff, ruff-format and mypy
checks CI does. The generated binding under `src/europassmcp/_generated/` is
xsdata output and is regenerated, never edited by hand; CI regenerates it and
fails on any diff. `scripts/fetch_schema.py` re-runs the schema recovery.
TDQS
Scored across 12 tools
Every tool targets a clear and separate concern: document creation, a specific CV section, validation/export, or a single code-list lookup. Even the lookup tools are unambiguously separated by resource type, and the add_* tools are distinguished by the section they populate.
All tool names follow a predictable lower_snake_case verb_noun pattern: create_, set_, add_, validate_, export_, lookup_, and search_. The add_* family clearly maps to CV sections, and the lookup_* tools consistently represent code-list lookups.
Twelve tools is well-scoped for a CV-building server: one for document creation, four for content sections, one validator, two exporters, and three code-list lookups. There are no redundant or near-duplicate tools inflating the set.
The core Europass sections and the validation/export pipeline are covered, but the entry lifecycle is incomplete: added work, education, language, and digital skill entries cannot be edited or removed, and there is no clean tool for reading back the current CV state. This creates avoidable dead ends when an agent makes a mistake.