Make postulo-mcp official: what that means, and what is missing before it can be #1

Open
opened 2026-09-12 09:28:22 +00:00 by tiagoagueda · 2 comments
Owner

Where this actually stands

Not at the beginning. One commit — Serve one Postulo instance to an agent, over the Model
Context Protocol
— already contains the whole thing the README describes:

  • Eleven read tools, five write tools behind --write, three resources
    (postulo://applications/7 and friends) and two prompts.
  • A Postulo client over httpx, address and token from the environment so neither reaches
    a process list.
  • 14 tests, all passing, run against an in-memory Postulo so nothing leaves the machine.
  • CI on every push: ruff check and pytest, on python:3.14-slim-bookworm.
  • mcp pinned at 2.1.1 in uv.lock.

So this issue is not "implement it". It is "finish making it a thing somebody else can
install and trust".

First: "official plugin" is two different things, and only one of them fits

postulo-mcp is deliberately not a Postulo plugin. Its own pyproject.toml says so:

This runs beside the agent, not inside Postulo: the official MCP SDK, and httpx to reach
the instance's API. Postulo itself is not a dependency at all.

And Postulo's docs/PLAN.md makes it a stated position rather than an accident:

AI assistance — Never inside Postulo. The application is complete and useful without an
API key, and none is ever asked for. Since 0.2.0 the other direction exists instead:
postulo-mcp serves an agent the person already runs through the ordinary API (#19).

Postulo's plugin catalogue — plugins/catalogue.py, an Ed25519-signed index with a checksum
per wheel — distributes plugins that are installed into a Postulo instance, and
provenance.official means precisely "its file matches what the official repository
signed"
. Publishing postulo-mcp through that would mean installing an MCP server inside
Postulo, which is the thing PLAN.md says never happens.

So "official" here should mean: maintained, versioned, released and documented by the
project — not listed in the plugin catalogue.
The rest of this issue assumes that. If the
intention is genuinely to run MCP inside the instance, that is a different issue and it
starts by reversing a line in PLAN.md, deliberately and in writing.

What is missing

1. It has never been released

version = "0.1.0", one commit, no tag, no CHANGELOG.md, and no release workflow — CI runs
tests and stops. The README tells people to install it:

uv tool install git+https://source.tiagoagueda.com/postulo/postulo-mcp.git

which installs whatever main happens to be that day. That is fine for trying it and wrong
for something called official: there is no version anybody can name, pin, or report a bug
against.

  • A CHANGELOG.md, kept as Postulo's is.
  • A release workflow mirroring Postulo's: refuse a tag that disagrees with the code or the
    changelog, build the sdist and wheel, create the Forgejo release with the changelog section
    as its notes. Postulo's release.yml and scripts/release_tools.py are the pattern.
  • Decide where the wheel goes. A git URL is not an installation story for a tool people
    are meant to trust. PyPI, or the Forgejo package registry, or both — pick one and put it in
    the README in place of the git URL.

2. The README makes promises nothing tests

These are the promises that make pointing an agent at somebody's entire job search
acceptable, and every one of them currently holds only because the code happens to say so:

Nothing destructive, ever. There is no delete tool, anywhere, and there will not be one.

Files are never returned as bytes: a CV travels as text.

Every write lands on the application's timeline with the token's name against it.

Write, only with --write and a token carrying the write scope.

Each wants a test that fails if somebody later makes it untrue:

  • No registered tool issues a DELETE, and none is named or described as removing anything.
    A test over the registered tool set rather than over a list somebody remembers to update.
  • No tool returns bytes or a base64 payload.
  • Without --write, the write tools are not registered at all — not merely refused.
  • With --write but a read-only token, a write fails with something a model can act on
    rather than a traceback.

A promise in a README with no test is a promise until the next person edits the file.

3. Tools that are missing, and one of them matters

Against the API the instance actually exposes:

API Exposed to the agent
applications, companies, listings, reminders, interviews, cvs, letters, insights, search yes
/captures, /captures/preview no
/me no
documents router no

Captures are the gap worth closing. Postulo's whole front door is "here is a posting,
read it"
, and an agent that has found a job advert cannot hand it over. /captures/preview
is particularly well suited: it reads a page, stores nothing, notifies nobody, and answers
with what would be captured — so an agent can offer it to the person before anything is
written. That makes it arguably a read tool, not a write one, which is worth deciding
explicitly rather than by accident.

/me is small and useful: an agent's first call can say which scopes the token has, so a
refusal later is explainable rather than mysterious.

4. Documentation

pyproject.toml points at The capture API on the wiki — Postulo's API page, not an MCP
page. There is no wiki page for postulo-mcp itself. For something official there should be
one, and it should carry the paragraph the README already gets right:

A read token exposes to the model you chose: your applications and their timelines, the
companies and people you recorded, your CVs and letters as text, your reminders, interviews
and figures. That is your whole job search.

Suggested order

  1. The safety tests (§2). They cost least, and everything after this is riskier without them.
  2. /me, then captures (§3), each its own commit.
  3. Changelog and release workflow (§1), then decide where the wheel goes.
  4. Wiki page, and swap the git URL in the README for the real one (§4).
  5. Tag v0.1.0.

Nothing here needs a decision from Postulo core except the one at the top: that official
means released and documented, not listed in the plugin catalogue.

## Where this actually stands Not at the beginning. One commit — *Serve one Postulo instance to an agent, over the Model Context Protocol* — already contains the whole thing the README describes: - Eleven read tools, five write tools behind `--write`, three resources (`postulo://applications/7` and friends) and two prompts. - A `Postulo` client over `httpx`, address and token from the environment so neither reaches a process list. - **14 tests, all passing**, run against an in-memory Postulo so nothing leaves the machine. - CI on every push: `ruff check` and `pytest`, on `python:3.14-slim-bookworm`. - `mcp` pinned at 2.1.1 in `uv.lock`. So this issue is not "implement it". It is "finish making it a thing somebody else can install and trust". ## First: "official plugin" is two different things, and only one of them fits **postulo-mcp is deliberately not a Postulo plugin.** Its own `pyproject.toml` says so: > This runs beside the agent, not inside Postulo: the official MCP SDK, and httpx to reach > the instance's API. **Postulo itself is not a dependency at all.** And Postulo's `docs/PLAN.md` makes it a stated position rather than an accident: > AI assistance — **Never inside Postulo.** The application is complete and useful without an > API key, and none is ever asked for. Since 0.2.0 the other direction exists instead: > postulo-mcp serves an agent the person already runs through the ordinary API (#19). Postulo's plugin catalogue — `plugins/catalogue.py`, an Ed25519-signed index with a checksum per wheel — distributes plugins that are **installed into a Postulo instance**, and `provenance.official` means precisely *"its file matches what the official repository signed"*. Publishing postulo-mcp through that would mean installing an MCP server inside Postulo, which is the thing PLAN.md says never happens. **So "official" here should mean: maintained, versioned, released and documented by the project — not listed in the plugin catalogue.** The rest of this issue assumes that. If the intention is genuinely to run MCP *inside* the instance, that is a different issue and it starts by reversing a line in `PLAN.md`, deliberately and in writing. ## What is missing ### 1. It has never been released `version = "0.1.0"`, one commit, no tag, no `CHANGELOG.md`, and no release workflow — CI runs tests and stops. The README tells people to install it: uv tool install git+https://source.tiagoagueda.com/postulo/postulo-mcp.git which installs whatever `main` happens to be that day. That is fine for trying it and wrong for something called official: there is no version anybody can name, pin, or report a bug against. - A `CHANGELOG.md`, kept as Postulo's is. - A release workflow mirroring Postulo's: refuse a tag that disagrees with the code or the changelog, build the sdist and wheel, create the Forgejo release with the changelog section as its notes. Postulo's `release.yml` and `scripts/release_tools.py` are the pattern. - **Decide where the wheel goes.** A git URL is not an installation story for a tool people are meant to trust. PyPI, or the Forgejo package registry, or both — pick one and put it in the README in place of the git URL. ### 2. The README makes promises nothing tests These are the promises that make pointing an agent at somebody's entire job search acceptable, and every one of them currently holds only because the code happens to say so: > **Nothing destructive, ever.** There is no delete tool, anywhere, and there will not be one. > Files are never returned as bytes: a CV travels as text. > Every write lands on the application's timeline with the token's name against it. > **Write**, only with `--write` *and* a token carrying the `write` scope. Each wants a test that fails if somebody later makes it untrue: - No registered tool issues a `DELETE`, and none is named or described as removing anything. A test over the *registered tool set* rather than over a list somebody remembers to update. - No tool returns bytes or a base64 payload. - Without `--write`, the write tools are **not registered at all** — not merely refused. - With `--write` but a read-only token, a write fails with something a model can act on rather than a traceback. A promise in a README with no test is a promise until the next person edits the file. ### 3. Tools that are missing, and one of them matters Against the API the instance actually exposes: | API | Exposed to the agent | | --- | --- | | applications, companies, listings, reminders, interviews, cvs, letters, insights, search | yes | | **`/captures`, `/captures/preview`** | **no** | | `/me` | no | | `documents` router | no | **Captures are the gap worth closing.** Postulo's whole front door is *"here is a posting, read it"*, and an agent that has found a job advert cannot hand it over. `/captures/preview` is particularly well suited: it reads a page, stores nothing, notifies nobody, and answers with what would be captured — so an agent can offer it to the person before anything is written. That makes it arguably a **read** tool, not a write one, which is worth deciding explicitly rather than by accident. `/me` is small and useful: an agent's first call can say which scopes the token has, so a refusal later is explainable rather than mysterious. ### 4. Documentation `pyproject.toml` points at *The capture API* on the wiki — Postulo's API page, not an MCP page. There is no wiki page for postulo-mcp itself. For something official there should be one, and it should carry the paragraph the README already gets right: > A `read` token exposes to the model you chose: your applications and their timelines, the > companies and people you recorded, your CVs and letters as text, your reminders, interviews > and figures. **That is your whole job search.** ## Suggested order 1. The safety tests (§2). They cost least, and everything after this is riskier without them. 2. `/me`, then captures (§3), each its own commit. 3. Changelog and release workflow (§1), then decide where the wheel goes. 4. Wiki page, and swap the git URL in the README for the real one (§4). 5. Tag `v0.1.0`. Nothing here needs a decision from Postulo core except the one at the top: that **official means released and documented, not listed in the plugin catalogue.**
Author
Owner

The version to tag is not v0.1.0.

An official plugin's version tracks the core's, so the first release of this repository is
v0.3.0 — the one that pairs with Postulo 0.3.0 — and its requires_postulo is
~=0.3.0. Step 5 of the suggested order should read that way.

The rule, the reasoning and what has to change in core are in postulo/postulo#186. Two parts
of it matter here:

  • The pairing is on major.minor, with the patch free. postulo-mcp 0.3.4 is the fifth fix
    to the 0.3 plugin and implies no core release, so this repository can still fix its own
    bugs between core releases.
  • Core parses requires_postulo and never checks it, so declaring it is not yet
    protection. That is being fixed in core; until it is, the README should say plainly which
    Postulo this works with rather than relying on the installer to refuse.
**The version to tag is not `v0.1.0`.** An official plugin's version tracks the core's, so the first release of this repository is **`v0.3.0`** — the one that pairs with Postulo 0.3.0 — and its `requires_postulo` is `~=0.3.0`. Step 5 of the suggested order should read that way. The rule, the reasoning and what has to change in core are in postulo/postulo#186. Two parts of it matter here: - The pairing is on **major.minor**, with the patch free. postulo-mcp 0.3.4 is the fifth fix to the 0.3 plugin and implies no core release, so this repository can still fix its own bugs between core releases. - Core parses `requires_postulo` and **never checks it**, so declaring it is not yet protection. That is being fixed in core; until it is, the README should say plainly which Postulo this works with rather than relying on the installer to refuse.
Author
Owner

Decided: English only, and no catalogue

postulo-mcp carries no translation catalogue. It is the one official plugin exempt from
the rule every other one follows (postulo/postulo#187), and the exemption is deliberate
rather than deferred work.

Why. Almost everything this repository puts into words is read by a model, not by a
person: sixteen tool descriptions, three resource descriptions, two prompts, and the
server's instructions to the model. Translating those does not help anybody and can hurt —
an agent reasoning over a French tool description while the person it serves writes English
is worse off than one reading the English, and the failure is silent. A catalogue here would
be work with a negative return.

This is not an argument that translation does not matter. It is that these particular
strings have a machine for an audience, and the rest of Postulo's strings do not.

Write it down in the repository. A locale/ directory that does not exist looks like an
oversight, and the next person to tidy the official plugins into consistency will add one.
A short section in the README — or a comment where the tool descriptions are declared —
saying that the descriptions are model-facing and stay in English is what stops that.

What would reopen it

Strings a person reads. Today that is the README and whatever the command line prints
when something goes wrong — a bad token, an unreachable instance, a missing scope. There are
few of them and they are seen at the moment somebody is already confused, which is the worst
moment to be reading a second language. If that set grows into anything resembling a setup
flow, this decision is worth revisiting for those strings only; the tool descriptions
stay English regardless.

## Decided: English only, and no catalogue **postulo-mcp carries no translation catalogue.** It is the one official plugin exempt from the rule every other one follows (postulo/postulo#187), and the exemption is deliberate rather than deferred work. **Why.** Almost everything this repository puts into words is read by a *model*, not by a person: sixteen tool descriptions, three resource descriptions, two prompts, and the server's instructions to the model. Translating those does not help anybody and can hurt — an agent reasoning over a French tool description while the person it serves writes English is worse off than one reading the English, and the failure is silent. A catalogue here would be work with a negative return. This is not an argument that translation does not matter. It is that these particular strings have a machine for an audience, and the rest of Postulo's strings do not. **Write it down in the repository.** A `locale/` directory that does not exist looks like an oversight, and the next person to tidy the official plugins into consistency will add one. A short section in the README — or a comment where the tool descriptions are declared — saying that the descriptions are model-facing and stay in English is what stops that. ### What would reopen it Strings a **person** reads. Today that is the README and whatever the command line prints when something goes wrong — a bad token, an unreachable instance, a missing scope. There are few of them and they are seen at the moment somebody is already confused, which is the worst moment to be reading a second language. If that set grows into anything resembling a setup flow, this decision is worth revisiting for **those strings only**; the tool descriptions stay English regardless.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Postulo/postulo-mcp#1
No description provided.