
pbx-mcp: an MCP server for Asterisk and FreeSWITCH, read-only by default
I look after open source telephony for a living, and the thing that eats the most time isn't fixing the PBX. It's answering "is the trunk down" and "who is on a call right now" for people who will never touch a CLI.
So I wrote an MCP server for it. It talks to Asterisk over the Manager Interface and to FreeSWITCH over the Event Socket, and hands the live state back as tools.
There wasn't one already. There is a project with asterisk in the name on npm, but it belongs to a code security company that happens to share the name and has nothing to do with the PBX. For FreeSWITCH there was nothing at all.
What it can read:
- active channels with caller ID, state, bridge and how long they have been up
- PJSIP endpoints and device state, falling back to chan_sip peers on older boxes
- Sofia profiles and gateways, so you can see whether a trunk is actually registered upstream
- dialplan context dumps
- raw CLI and API commands, filtered
The part that took the longest was the safety model, because pointing a model at a live switch is a bad idea if you do it lazily.
Write tools like originate and hangup aren't blocked at runtime, they are never registered with the model at all unless you set PBX_MCP_ALLOW_WRITE=true. A model can't call a tool it can't see. On top of that there are allow lists for which CLI and API commands can run, shell metacharacters get rejected rather than escaped, and AMI header injection is guarded, since AMI is newline delimited and a caller ID field with a CRLF in it is otherwise a free command. Output is clamped at 20k characters too, because show channels on a busy box will happily eat your whole context window.
Two ways to run it. Either npx:
npx -y pbx-mcp
or the container, which is handy if you would rather not put Node on the PBX host:
docker run -i --rm -e ASTERISK_AMI_HOST=10.0.0.5 -e ASTERISK_AMI_USERNAME=mcp -e ASTERISK_AMI_PASSWORD=secret ghcr.io/ictinnovations/pbx-mcp
It speaks stdio, so there is no port to expose.
MIT, TypeScript, and the only runtime deps are the MCP SDK and Zod. The AMI and ESL clients are hand rolled and also published separately if you want the protocol layer without the MCP part.
GitHub: https://github.com/ictinnovations/pbx-mcp
npm: https://www.npmjs.com/package/pbx-mcp
Docker Hub: https://hub.docker.com/r/ictinnovations/pbx-mcp
GHCR: ghcr.io/ictinnovations/pbx-mcp
MCP Registry: io.github.ictinnovations/pbx-mcp
For anyone who has shipped a read-only server: has the read-only default held up for you, or do people just flip the write flag on day one and defeat the point?