Skip to main content

mcp-ops-kit

The domain-free half of this repo: what an MCP server needs to let agents act on a booking system safely, plus the trajectory evals that gate it in CI. It was extracted from two working domains, not designed up front. The synthetic clinic (mcp_ops_server) came first. Then a synthetic salon (salon_ops) was built by copying the clinic's code. The kit is what the two copies turned out to share (Part 8, see FAILURES.md).

pip install mcp-ops-kit            # add [live] for the OpenAI agent used in live eval runs

(Once the first release is tagged; until then, pip install -e . from this repo.)

The source lives in this repo, next to the two domains built on it. Releases are published to PyPI from a kit-vX.Y.Z tag on main by trusted publishing, with attestations, after CI has built the wheel and used it from a fresh venv (release-kit.yml).

What the kit owns, and what a domain supplies

Kit module Gives you Your domain supplies
auth Hashed bearer tokens in a versioned file: each names its audience and expiry and can be revoked; the file is re-read on change, so revocation needs no restart. A token CLI (issue, revoke, list, migrate), scopes and a subject binding, all checked in server code (Guard) An AuthConfig: scope names, the subject claim (patient_id, customer_id), its ID pattern, the default audience
oauth OAuth 2.1 resource-server tokens: JWT access tokens (RFC 9068, RS256/ES256) checked against your authorization server's JWKS (file or URL), with a jti revocation file Your issuer and JWKS location (settings)
audit One encrypted, HMAC-chained record per tool call (who, token, tool, scope, subject ID, outcome, time), never the arguments; verify and show CLIs Nothing
crypto The data key (new-key), per-purpose subkeys, AES-256-GCM, atomic writes The key, from your secret store
limits A per-client token-bucket rate limit, applied before argument validation Nothing
server The audit-and-rate-limit-first OpsServer, which also rebuilds every result's text from the validated output, so an agent sees only declared fields. A health tool, /mcp with bearer auth, /healthz, env settings, a fail-closed serve (no token source, audit log or data key: no start) Your tools (register(server, guard)), your settings fields, your env prefix
sqlite One deadline per call: lock wait, busy wait and execution, with a late COMMIT rolled back. With a data key, the file on disk is encrypted (the database lives in memory; each committed write re-encrypts it atomically) Your schema, seed and queries (subclass SqliteStore)
errors StoreError and its kinds. Tools turn them into messages an agent can act on (store_errors) Your own rule errors, as subclasses
evals The harness (fresh data and a real server per task), the trajectory grader, the replay and OpenAI agents, the report and the CI gate An EvalDomain, golden tasks and negative controls
smoke /healthz, 401 without a token, then an MCP session Your tool round trip, with each write read back

Build a domain on it: the salon as the worked example

  1. Data and rules. Write a deterministic synthetic seed (salon_ops/seed.py) and a store that subclasses SqliteStore (salon_ops/store.py). Enforce every rule in the store, and make the critical one a database constraint too. The salon keeps one booked visit per opening with a unique index.
  2. Auth. One AuthConfig (salon_ops/auth.py): three scopes, tokens bound to customer_id by the pattern CUS-[0-9]{4}, and the audience salon-ops. The token CLI is kit.main(AUTH).
  3. Tools. Thin wrappers (salon_ops/tools.py). Each calls guard.check(scope, subject_id) and then the store, inside store_errors(), and returns a TypedDict so it gets an output schema.
  4. Server. kit.build_server(name, version, AUTH, register, audit=...), with kit.build_verifier(settings, AUTH) and kit.open_audit(settings) in your build_http_app, then kit.build_http_app, and serve in __main__ (salon_ops/server.py, salon_ops/__main__.py). A database file goes through require_key_for_file, so it is never plaintext.
  5. Evals. An EvalDomain (salon_ops/evals/run.py) says who the principals are, which argument names the subject (SubjectRule), the default clock, and how to get fresh data per task. Then write golden tasks and negative controls for every grader check (salon_ops/evals/golden/make_tasks.py) before you trust the gate.
  6. Smoke test. Your exercise(session) for kit.smoke.main (salon_ops/smoke.py).

The salon example has the following parts:

  • 6 tools;
  • 3 rules the clinic does not have: a stylist must offer the service, a 24-hour late-cancel window, and at most 3 upcoming visits per customer;
  • 18 golden tasks and 14 negative controls, gated in CI next to the clinic's.

Guarantees the kit enforces, with tests

  • The kit never imports a domain. tests/test_kit.py walks every import in mcp_ops_kit/.
  • A bound token never loads unbound. Unknown keys are refused, so a clinic token bound to a patient does not load in the salon as an unbound token with its patient_id silently dropped. Before the kit, the clinic's own loader ignored unknown keys in exactly this way. A token whose scopes another domain does not declare is refused too.
  • A token is for one server. Every token names its audience (static tokens in the file, JWTs in aud). A tokens file holding another audience's token does not load, and the verifier checks the audience of each token again. This closes the Part 8 limit, where an unbound token loaded in any domain declaring the same scope names (tests/test_kit.py).
  • Tokens expire and can be revoked on a running server. Static tokens by revoke, JWTs by jti; both files are re-read on change. If either file becomes invalid, every token it governs is refused until it is fixed, so a revocation is never skipped (tests/test_tokens.py, tests/test_oauth.py).
  • Every tool call is audited, or no call runs. A record that cannot be written stops the server from acting (the call already made still returns its real result). An edited, deleted or reordered record fails verify at its line (tests/test_audit.py).
  • No plaintext database on disk. A server refuses a database file without a key, and a plaintext SQLite file with one (tests/test_encryption.py).
  • An agent sees only declared fields (tests/test_minimisation.py), each pinned with a reason.
  • The state check sees every change. The record diff grades the booking lifecycle (booked, cancelled, booked-then-cancelled), and reports anything else (an edited, removed or odd-status record) as unexpected. That fails the task unless the task expects it.
  • A refusal can be pinned to its error. A step pattern may carry "error": {"$icontains": ...}, so a refusal task fails when the agent hits some other error.
  • A scope a tool names but the domain never declared raises an error. It is not quietly treated as "forbidden".
  • Everything else is held to both domains' full suites and eval gates: the clinic's (on SQLite and on FHIR) and the salon's.

Not in the kit (yet)

  • The FHIR backend is clinic-specific by nature, and stays in mcp_ops_server. Encryption at rest for FHIR data is the FHIR server's storage, not this code.
  • Key rotation: there is no re-encrypt command yet. A new data key means a fresh audit log and database file (keep the old key to read the old ones).
  • Token introspection (RFC 7662) and a shared rate limit across replicas.

Metadata

Release files for mcp-ops-kit 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mcp-ops-kit 0.1.0
File Size Uploaded Provenance
mcp_ops_kit-0.1.0.tar.gz 48.9 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for mcp-ops-kit 0.1.0
File Interpreter ABI Platform
mcp_ops_kit-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 107.2 kB

Release files / mcp_ops_kit-0.1.0.tar.gz

Download URL mcp_ops_kit-0.1.0.tar.gz
Size 48.9 kB
Tags Source
SHA-256 checksum
How to use checksums
5f10d1f91617379120fdd884672d47cc24aa476189046be99677ac09819c3b44
BLAKE2b-256 checksum
How to use checksums
605acd92c4f1293f56bc1e09f8c9e2cade39301abef5fcf7867415acd6ba6ca7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Release files / mcp_ops_kit-0.1.0-py3-none-any.whl

Download URL mcp_ops_kit-0.1.0-py3-none-any.whl
Size 58.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cdddbf473ecaaee5244980d896d2466bc758fd66c195b319d6e08924504ac905
BLAKE2b-256 checksum
How to use checksums
8999f9b3c8cd24109aa33e690e7fb8143cf4cf37f7631a3b6ccd3942e02ee9ac
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Security for mcp-ops-kit 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for mcp_ops_kit-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page