Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -223,3 +223,6 @@ js/**/dist/
# The web client js/agent-ui builds into the Python package. Generated, and
# put into the wheel by hatch_build.py rather than tracked here.
src/mcp_agent_api/ui/

# Local design notes, never published
docs/proposals/
37 changes: 20 additions & 17 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,21 @@ Every PR title must be a valid

The `pr-title` check fails the PR if the type is not one of these:

| Type | Use for |
| --- | --- |
| `feat` | a new capability |
| `fix` | a bug fix |
| `perf` | a performance improvement |
| `refactor` | an internal change with no behaviour change |
| `docs` | documentation only |
| `test` | tests only |
| `build` | the build system or dependencies |
| `ci` | CI configuration |
| `chore` | anything else |
| Type | Use for | Releases |
| --- | --- | --- |
| `feat` | a new capability | yes |
| `fix` | a bug fix | yes |
| `perf` | a performance improvement | yes |
| `refactor` | an internal change with no behaviour change | no |
| `docs` | documentation only | **yes** |
| `test` | tests only | no |
| `build` | the build system or dependencies | no |
| `ci` | CI configuration | no |
| `chore` | anything else | no |

release-please counts `docs` as user-facing, so a docs commit cuts a release
on its own. A `build` commit, which is what every dependency bump is, waits
for the next release that something else cuts.

Mark a **breaking change** with a `!`:

Expand Down Expand Up @@ -58,8 +62,7 @@ shift every bump down one place:
| a breaking change (`!`) | minor |
| anything else that releases | patch |

So a `feat` is a **patch** here, not a minor. The two most recent releases show
it: 0.9.1 was a single `feat`, and 0.9.0 was a breaking change.
So a `feat` is a **patch** here, not a minor.

This matters to consumers, who bound the dependency at the next minor. A
change that breaks them has to carry the `!`, or release-please ships it inside
Expand All @@ -68,10 +71,10 @@ a range they already allow.
## The release flow

1. Merge PRs with conventional titles.
2. release-please keeps a rolling release PR. It bumps `pyproject.toml` and
`js/mcp-view/package.json`, which the linked-versions plugin keeps equal,
and updates `CHANGELOG.md`. **That PR is editable**, so curate the changelog
before merging if a release warrants it.
2. release-please keeps a rolling release PR. It bumps `pyproject.toml`,
`js/mcp-view/package.json` and its lock, which the linked-versions plugin
keeps equal, and updates `CHANGELOG.md`. **That PR is editable**, so curate
the changelog before merging if a release warrants it.
3. Merging the release PR tags `vX.Y.Z` and cuts a GitHub Release. That
publishes the wheel to PyPI and the bridge to npm.

Expand Down
35 changes: 18 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,17 @@ routes, and an eighth, `GET /threads`, when you pass `owner`:
| `GET /threads/{id}` | the thread's transcript |
| `GET /threads/{id}/idle` | answers once no run holds the thread |
| `GET /threads/{id}/turns` | its turns, with the state each ended holding |
| `GET /threads/{id}/state` | a session-state payload in full; `?turn=N` for the value as it stood then |
| `GET /threads/{id}/state/{key}` | one session-state value in full; `?turn=N` for the value as it stood then |
| `GET /views/...` | a `ui://` view bundle |
| `GET /connections` | what the agent connected to, and which credential headers it wants |

The four read routes serve what the stream deliberately leaves out.
The read routes serve what the stream deliberately leaves out.
`GET /connections` is what a client needs before there is a conversation at
all.

Every route answers `503` while the agent is not built: whenever the provider
raises, as it does while a lifespan is still connecting to MCP servers.

**`mcp_agent_api.app`** closes the stack for a deployment with no application
of its own. `create_app(build=…)` wraps those routes in a lifespan, a
checkpointer, CORS and two health probes. A module-level `app` means
Expand All @@ -134,18 +137,18 @@ checkpointer, CORS and two health probes. A module-level `app` means
### The toolset plugin contract

Writing a toolset with a coding agent? `uv run mcp-toolset skill --install`
puts the authoring skill in the repo. It covers this section as a procedure,
and adds a mapping table for porting an existing codebase into tools. It ships
in the wheel, so it matches the pinned version.
puts the authoring skill in the repo and points `AGENTS.md` at it. It covers
this section as a procedure, and adds a mapping table for porting an existing
codebase into tools. It ships in the wheel, so it matches the pinned version.

`mcp_runtime` finds a toolset by convention. Given the name `my-toolset` it
imports `my_toolset.tools` and reads three module-level exports:

- **`TOOLS`** (required) — a non-empty list of LangChain tools that return a
- **`TOOLS`** (required): a non-empty list of LangChain tools that return a
`ToolResult`.
- **`VIEWS`** (optional) — `{tool_name: view_id}`, with a built bundle at
- **`VIEWS`** (optional): `{tool_name: view_id}`, with a built bundle at
`<package>/views/<view_id>.html`.
- **`CREDENTIAL_HEADERS`** (optional) — header names the tools read off the
- **`CREDENTIAL_HEADERS`** (optional): header names the tools read off the
transport, used to derive the model-facing auth hint.

**A data key is a public name.** Every field of a `ToolResult` except
Expand Down Expand Up @@ -177,12 +180,12 @@ Treat `ToolResult`, `NotAuthored` and the `ui/*` wire protocol as public API.

Further reading:

- [docs/SESSION-STATE.md](./docs/SESSION-STATE.md) — the state contract as
sequence diagrams, including the trust assumption it rests on.
- [examples/session-state/](./examples/session-state/) — a runnable version of
- [docs/SESSION-STATE.md](./docs/SESSION-STATE.md): the state contract, with
diagrams and four worked scenarios, and the trust assumption it rests on.
- [examples/session-state/](./examples/session-state/): a runnable version of
the whole thing, a third-party server included. Run
`uv run python examples/session-state/demo.py`; no API key needed.
- [examples/agui-events/](./examples/agui-events/) — the same machinery over
- [examples/agui-events/](./examples/agui-events/): the same machinery over
HTTP, driven by the client the wheel ships. Tokens stream, tool calls and
receipts arrive in order, and a state panel fetches values rather than
receiving them on the wire.
Expand Down Expand Up @@ -248,18 +251,16 @@ As a consumer with uv, this is an ordinary dependency with no source override:
dependencies = ["mcp-toolsets-runtime[api]"]
```

Imports are unchanged from the old workspace packages, so
`from mcp_runtime.server import build_server` still works. `uv.lock` pins
whatever resolved, and upgrading is
`uv.lock` pins whatever resolved, and upgrading is
`uv lock --upgrade-package mcp-toolsets-runtime`.

The package is pre-1.0, where a minor release may break. Bound it at the next
minor in your own `pyproject.toml` if you would rather take those deliberately.

**[docs/CONSUMING.md](./docs/CONSUMING.md)** covers the rest: the plugin
contract, serving toolsets, wiring up UI views and the npm bridge, wiring
session state into your own agent, serving that agent over HTTP, migrating
off the in-repo workspace, and upgrading across a breaking release.
session state into your own agent, serving that agent over HTTP, and
upgrading across a breaking release.

## Develop

Expand Down
Loading
Loading