pipecat-mcp-transport
An MCP transport for pipecat. One MCP client, for
example Claude Code, holds one text conversation with one pipecat bot. The server gives 2 tools,
start and chat, over streamable HTTP.
Tested with pipecat-ai 1.10.0 and mcp 2.2.0 on Python 3.12, 3.13 and 3.14.
Install
pip install pipecat-mcp-transport
Use
McpBotServer takes your bot function and serves it. Your bot takes its transport from the
runner arguments. That branch is the only edit an existing bot needs.
from pipecat_mcp_transport import McpBotServer, McpRunnerArguments
async def bot(runner_args) -> None:
if isinstance(runner_args, McpRunnerArguments):
transport = runner_args.transport
else:
transport = await create_transport(runner_args, {"webrtc": voice_params})
...
if __name__ == "__main__":
McpBotServer(bot).run()
examples/bot.py runs one stt llm tts bot on http://127.0.0.1:7870/mcp. It runs on the stock
webrtc transport too, from the same file. It reads OPENAI_API_KEY, and OPENAI_BASE_URL for
an OpenAI-shaped server of your own.
Add the server to an MCP client as a streamable HTTP server on that url. For Claude Code:
claude mcp add --transport http pipecat http://127.0.0.1:7870/mcp
Tools
| tool | arguments | gives |
|---|---|---|
start |
none | the handle of one new conversation |
chat |
handle, line |
the whole reply of the bot to that line |
start runs your bot function in a task and gives a UUIDv4 handle. One handle is one session:
one pipeline, one context, one history. The handle is a bearer secret, so the server binds
localhost by default.
chat appends line to the context of the handle and runs the model. It gives the reply as one
text block when the turn ends. An empty line starts no turn and gives the reply of the turn in
flight. A turn that one call read is gone, so the next empty line gives an empty block.
Turn end
The reply is each text frame with skip_tts set that reaches the output transport. That is the
LLMTextFrame pieces of the model, or the AggregatedTextFrame sentences of an
LLMTextProcessor in front of the tts. The output joins them as the pipecat aggregators do,
and answers the call on LLMFullResponseEndFrame.
A response that starts a round of function calls carries no reply of its own. The turn counts
each FunctionCallsStartedFrame and ends on the response that starts no round, so a turn of 1
round and a turn of 2 rounds both give the whole reply. That frame is a system frame, so it
reaches the output before the end frame of its response. FunctionCallInProgressFrame comes
from the task of each call and reaches the output after that end frame, so the turn reads no
round from it.
The input transport pushes LLMConfigureOutputFrame(skip_tts=True) once, when the pipeline
starts. The llm service keeps that setting, so every turn of the session pays 0 tts requests and
no TTSTextFrame reaches the caller. A text bot and a voice bot end their turn on the same
frame.
Interruption
A chat call on a handle with a turn in flight interrupts the bot and drains the pipeline before it appends the new line. One lock per handle keeps the calls of that handle in order, so 2 concurrent calls run one after the other and neither reply carries the text of the other.
Progress and cancel
The call sends one progress notification per reply piece. A caller with an idle window resets it on each notification.
On streamable HTTP a caller cancels by closing the response stream. The tool handler ends, the
session stays and the turn runs on. A later chat call with an empty line gives the whole reply.
Session lifetime
The sweep of the server owns the lifetime. It drops a handle that takes no chat call for 300
seconds, ends its transport and cancels its bot task. McpBotServer(bot, handle_seconds=600)
moves that number.
When a bot returns or raises, the server drops its handle, so its slot is free for the next
start call. A call on a dropped handle gives one tool error for both causes. It names the
field, the lifetime and the next call, start.
The server gives the bot pipeline_idle_timeout_secs=None, so the idle timeout of the worker
runs on no session of this transport and your bot needs no idle frame set.
Security
The server binds 127.0.0.1 by default. On any bind, it checks the Host and Origin headers
of each request. A request with a foreign host gets 421, and a request with a foreign origin gets
403.
With no transport_security, the server takes the loopback setting of the MCP SDK: the hosts
127.0.0.1, localhost and [::1] on any port, and their http origins. A server on
host="0.0.0.0" keeps that setting, so a caller that dials another name gets 421.
If a caller dials another name, pass a TransportSecuritySettings of the MCP SDK that names it:
from mcp.server.transport_security import TransportSecuritySettings
security = TransportSecuritySettings(
allowed_hosts=["bot.example.com"],
allowed_origins=["https://app.example.com"],
)
McpBotServer(bot, host="0.0.0.0", transport_security=security).run()
A host entry that ends in :* takes any port. enable_dns_rebinding_protection=False turns
both checks off. The handle is a bearer secret, so put an authenticating proxy in front of an
open bind.
McpBotServer(bot, sessions=64) moves the cap on open sessions. The default is 32, and a
start call over the cap gives a tool error that names it.
Cost
Seconds from one chat call to its reply, median of 5 runs on one x86-64 workstation, Python 3.14.7. The model is a local stub that streams one word every 0.03 seconds and answers 7 words, so 0.21 seconds of each row is model pace.
| shape | median | min | max |
|---|---|---|---|
| 1 turn | 0.292 s | 0.289 s | 0.792 s |
| 5 turns on one handle | 1.446 s | 1.442 s | 1.448 s |
| 1 turn with 1 tool call | 0.385 s | 0.383 s | 0.385 s |
The first run of a shape waits for its pipeline to start, which the max of the 1 turn row holds.
python examples/bench.py --url <openai url> --out rows.jsonl --sha <commit> writes one row.
Limits
- The reply lands as one text block after the turn. No token streams to the caller.
- Markup tags the bot writes reach the caller in the text block unless the bot strips them.
- An interrupted turn gives its text so far with no marker. A caller reads it as whole.
- The greeting a bot speaks on client ready reaches no caller. The first turn starts on the first chat call.
- A chat call carries no audio block and no image block. The bot pays stt on nothing.
- A function handler that does not run the model again leaves the call waiting. The caller cancels, and the next call with an empty line gives the reply so far.
- A response that calls only the cancel tool of an async tool starts no round, because pipecat
sends no
FunctionCallsStartedFramefor that tool. The turn ends on that response, and the reply after the cancel reaches no caller. - A new line replaces a done turn that no call read. The reply of that turn reaches no caller.
- The collector awaits each progress notification, so a slow caller slows its own session.
- A session lives in one process. A second replica needs sticky routing on the handle.
- Claude Code caps a tool result at 25 000 tokens and ends a call after 5 minutes with no output. A long reply needs a shorter system prompt.
- The client tool of a bot has no caller on MCP, and pipecat marks no function call as one. Register no client tool.
Develop
- The package ships a
py.typedmarker. make lintchecks the lock, the format, the lint rules, and the types with pyright.make testruns the tests.make test-lowestruns them on the lowest allowed pipecat-ai, mcp, starlette and uvicorn. CI runs both.make auditchecksuv.lockfor known vulnerabilities.- A
vtag publishes the package to PyPI.
License
BSD 2-Clause. Softcery builds and maintains the package.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pipecat_mcp_transport-0.1.0.tar.gz.
File metadata
- Download URL: pipecat_mcp_transport-0.1.0.tar.gz
- Upload date:
- Size: 10.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e8f349b53762531ec0f9122811afd49b9e8b4159a32be719a9df49fa341d9330
|
|
| MD5 |
3319b00053149b49f42ad76c81d73832
|
|
| BLAKE2b-256 |
8136cb65196e781968757e639c2b896fed4167bad04c553f7027b2119a93eb92
|
Provenance
The following attestation bundles were made for pipecat_mcp_transport-0.1.0.tar.gz:
Publisher:
release.yml on softcery/pipecat-mcp-transport
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pipecat_mcp_transport-0.1.0.tar.gz -
Subject digest:
e8f349b53762531ec0f9122811afd49b9e8b4159a32be719a9df49fa341d9330 - Sigstore transparency entry: 2847408460
- Sigstore integration time:
-
Permalink:
softcery/pipecat-mcp-transport@7b2b64c9831aa2c4229a6e6be12afcf04408168c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/softcery
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7b2b64c9831aa2c4229a6e6be12afcf04408168c -
Trigger Event:
push
-
Statement type:
File details
Details for the file pipecat_mcp_transport-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pipecat_mcp_transport-0.1.0-py3-none-any.whl
- Upload date:
- Size: 12.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0924a4f27d10e61fa5be9d1afa28d1fd2f859b3babb02d6ab570e0defdf104a0
|
|
| MD5 |
3c9fa04a53928769ae2b2df42bd69135
|
|
| BLAKE2b-256 |
cb2681919d3a9c032d917b434e42d45e7da0c783690b2f0303e766591459d4df
|
Provenance
The following attestation bundles were made for pipecat_mcp_transport-0.1.0-py3-none-any.whl:
Publisher:
release.yml on softcery/pipecat-mcp-transport
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pipecat_mcp_transport-0.1.0-py3-none-any.whl -
Subject digest:
0924a4f27d10e61fa5be9d1afa28d1fd2f859b3babb02d6ab570e0defdf104a0 - Sigstore transparency entry: 2847408533
- Sigstore integration time:
-
Permalink:
softcery/pipecat-mcp-transport@7b2b64c9831aa2c4229a6e6be12afcf04408168c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/softcery
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7b2b64c9831aa2c4229a6e6be12afcf04408168c -
Trigger Event:
push
-
Statement type: