MCP Server Integration
Overview
mq9 ships with a built-in MCP (Model Context Protocol) Server that lets AI models (Claude, GPT-4o, etc.) operate mq9 mailboxes and the Agent registry through tool calls, without any direct NATS handling.
The MCP Server starts alongside the Admin Server — no additional deployment needed. Any MCP-compatible client can connect and:
- Create mailboxes, send messages, fetch messages, acknowledge consumption
- Register / unregister Agents, discover other Agents
- Query and delete messages
Connecting
Endpoint
The MCP Server is mounted at /mcp on the Admin Server:
http://<admin-server-host>:<port>/mcpClaude Desktop
{
"mcpServers": {
"mq9": {
"url": "http://localhost:9981/mcp"
}
}
}Other MCP Clients
Any client that speaks MCP 2025-03-26 (Claude Code, Cursor, custom SDKs, etc.) can connect directly. No authentication is required.
Tools at a Glance
| Tool | Purpose |
|---|---|
mq9_create_mailbox | Create a mailbox |
mq9_send_message | Send a message to a mailbox |
mq9_fetch_messages | Pull messages (stateful or stateless) |
mq9_ack_message | Acknowledge a message, advance the consumer offset |
mq9_query_mailbox | Inspect mailbox messages without affecting the consumer offset |
mq9_delete_message | Delete a specific message |
mq9_register_agent | Register an Agent in the discovery registry |
mq9_discover_agents | Discover registered Agents |
mq9_unregister_agent | Unregister an Agent |
Tool Reference
mq9_create_mailbox
Create a mailbox. Must be called before sending or receiving messages.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | No | Mailbox name — lowercase letters, digits, and dots only. Auto-generated by the broker if omitted. |
ttl | integer | No | Time-to-live in seconds. 0 or omitted means never expires. |
desc | string | No | Human-readable description (does not affect routing). |
Returns
{ "mail_address": "agent.inbox.abc123", "created": true }Example
Create a mailbox named task.queue, valid for 1 hour
→ mq9_create_mailbox({"name": "task.queue", "ttl": 3600})mq9_send_message
Send a message to a mailbox.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
mail_address | string | Yes | Destination mailbox address. |
payload | string | Yes | Message body (UTF-8 string — plain text or JSON). |
priority | string | No | normal (default) / urgent / critical. Same priority follows FIFO; across priorities: critical > urgent > normal. |
key | string | No | Dedup/compaction key. Only the latest message for a given key is retained; older ones are overwritten. Useful for status-update messages. |
tags | string | No | Comma-separated user tags, e.g. billing,vip. Filterable via mq9_query_mailbox. |
delay | integer | No | Delay delivery by this many seconds. The message is invisible in fetch until the delay expires. Returns msg_id: -1. |
ttl | integer | No | Message-level TTL in seconds. The message expires at send_time + ttl, independent of the mailbox TTL. |
Returns
{ "msg_id": 42, "mail_address": "task.queue" }Delayed messages return
msg_id: -1.
Examples
Urgent task
→ mq9_send_message({
"mail_address": "task.queue",
"payload": "{\"type\": \"analyze\", \"doc_id\": \"abc123\"}",
"priority": "urgent"
})
Status message with key (only latest retained)
→ mq9_send_message({
"mail_address": "task.001.callback",
"payload": "{\"status\": \"running\"}",
"key": "status"
})
Tagged message (filterable by billing)
→ mq9_send_message({
"mail_address": "agent.order.inbox",
"payload": "{\"order_id\": \"o-001\"}",
"tags": "billing,vip"
})
Delayed delivery — 60 seconds
→ mq9_send_message({
"mail_address": "agent.inbox",
"payload": "{\"text\": \"delayed task\"}",
"delay": 60
})
Message-level TTL — 300 seconds
→ mq9_send_message({
"mail_address": "agent.inbox",
"payload": "{\"text\": \"short-lived\"}",
"ttl": 300
})mq9_fetch_messages
Pull messages from a mailbox. Supports stateful consumption (pass group_name — the broker tracks the read offset) and stateless consumption (omit group_name — each call is independent).
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
mail_address | string | Yes | Mailbox address. |
group_name | string | No | Consumer group name. When provided, enables stateful consumption — multiple calls in the same group share an offset. When omitted, consumption is stateless and no offset is recorded. |
reset_to | string | No | Where to start reading. Omitted: resume from the last ACK (stateful) or from the latest message (stateless). |
max_messages | integer | No | Maximum messages to return per call. Default 100. |
max_wait_ms | integer | No | How long the server waits (ms) when the mailbox is empty before returning. Default 500. Set to 0 to return immediately. |
reset_to values
| Value | Description |
|---|---|
| omitted | Stateful: resume from last offset; Stateless: start from latest |
earliest | Force-rewind to the oldest message in the mailbox |
latest | Force-skip history, only receive new messages from now |
time:1746000000 | Start from messages after the given Unix timestamp |
id:42 | Start from the given msg_id (inclusive) |
Returns
{
"messages": [
{ "msg_id": 42, "payload": "...", "priority": "normal", "create_time": 1746000000 }
]
}Examples
Stateful — resume from last checkpoint for group worker-1
→ mq9_fetch_messages({"mail_address": "task.queue", "group_name": "worker-1"})
Stateless — read all history from the beginning
→ mq9_fetch_messages({"mail_address": "task.queue", "reset_to": "earliest"})mq9_ack_message
Acknowledge that a message has been processed. The broker advances the consumer group's offset past msg_id, so the next fetch resumes from there.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
mail_address | string | Yes | Mailbox address. |
group_name | string | Yes | Consumer group name — must match the group used in fetch. |
msg_id | integer | Yes | ID of the last successfully processed message. |
Returns
{ "msg_id": 42, "acked": true }Example
Acknowledge worker-1 has processed up to msg_id=42
→ mq9_ack_message({"mail_address": "task.queue", "group_name": "worker-1", "msg_id": 42})mq9_query_mailbox
Inspect messages in a mailbox without affecting the consumer offset. Use this to peek at contents, filter by tag or time range, or look up a specific key before deciding whether to consume.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
mail_address | string | Yes | Mailbox address. |
key | string | No | Filter by message key (exact match) — returns the latest message for that key. |
tags | array | No | Filter by tags — only messages carrying all specified tags are returned. |
since | integer | No | Only return messages created after this Unix timestamp (seconds). |
limit | integer | No | Maximum number of messages to return. Default 20. |
Returns
{
"messages": [
{ "msg_id": 10, "payload": "...", "priority": "urgent", "create_time": 1746000000 }
]
}Example
Peek at the latest 10 messages tagged billing in task.queue
→ mq9_query_mailbox({"mail_address": "task.queue", "tags": ["billing"], "limit": 10})mq9_delete_message
Delete a specific message from a mailbox.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
mail_address | string | Yes | Mailbox address. |
msg_id | integer | Yes | ID of the message to delete (from a fetch or query response). |
Returns
{ "msg_id": 42, "deleted": true }mq9_register_agent
Register an Agent in the mq9 discovery registry so other Agents can find it. Call once at startup.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique Agent identifier. |
payload | string | Yes | Agent capability description — plain text or an A2A AgentCard JSON string. Used for full-text and semantic vector search. |
Returns
{ "name": "payment-agent", "registered": true }Example
Register a payment Agent
→ mq9_register_agent({
"name": "payment-agent",
"payload": "Agent specialized in payment processing, invoices, and financial transactions"
})mq9_discover_agents
Search the registry for registered Agents. Supports full-text keyword search, semantic vector search, and pagination.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
text | string | No | Full-text keyword search (e.g. "payment invoice"). |
semantic | string | No | Semantic natural-language search using vector similarity (e.g. "process a payment and generate invoice"). Takes priority over text when both are provided. |
limit | integer | No | Maximum results per page. Default 20. |
page | integer | No | Page number, starting from 1. Default 1. |
When text and semantic are both omitted, all registered Agents for the tenant are returned.
Search priority: semantic (vector) > text (full-text) > omitted (list all)
Returns
{
"agents": [
{ "name": "payment-agent", "agent_info": "...", "description": "...", "agent_id": "..." }
]
}Examples
Semantic search — find agents that can handle payments
→ mq9_discover_agents({"semantic": "process a payment and generate invoice"})
Keyword search — find agents mentioning invoice
→ mq9_discover_agents({"text": "invoice"})
Paginated — page 2, 10 per page
→ mq9_discover_agents({"text": "payment", "limit": 10, "page": 2})
List all
→ mq9_discover_agents({})mq9_unregister_agent
Remove an Agent from the registry. Call when the Agent is shutting down.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Name of the Agent to unregister. |
Returns
{ "name": "payment-agent", "unregistered": true }Common Patterns
Pattern 1: Async Agent Collaboration
Two Agents exchange a task and its result through mq9:
Orchestrator Agent:
1. mq9_create_mailbox({"ttl": 300}) → create a temporary reply mailbox
2. mq9_send_message({ → dispatch task to worker
"mail_address": "task.queue",
"payload": '{"doc_id":"abc123","reply_to":"<reply-mailbox>"}',
"priority": "normal"
})
3. mq9_fetch_messages({ → wait for result
"mail_address": "<reply-mailbox>",
"group_name": "orchestrator"
})
4. mq9_ack_message({...}) → confirm processed
Worker Agent:
1. mq9_fetch_messages({"mail_address": "task.queue", "group_name": "workers"})
2. process task...
3. mq9_send_message({"mail_address": "<reply_to>", "payload": '{"status":"done"}'})
4. mq9_ack_message({...})Pattern 2: Agent Discovery and Routing
1. mq9_discover_agents({"semantic": "translate text"}) → find a translation agent
2. parse mail_address from the returned agent_info
3. mq9_send_message({"mail_address": "<translation-agent>", "payload": "..."})Pattern 3: Agent Self-Registration
On startup:
mq9_register_agent({
"name": "translation-agent-v2",
"payload": "Supports EN/ZH/JA/KO translation, specialized in technical docs and legal contracts"
})
On shutdown:
mq9_unregister_agent({"name": "translation-agent-v2"})Error Reference
| Error | Cause |
|---|---|
mailbox xxx does not exist | Mailbox not found — create it first |
mailbox xxx already exists | Duplicate name — CREATE is not idempotent |
message not found | msg_id does not exist |
payload must not be empty | REGISTER called with empty payload |
agent name must not be empty | Agent name is empty |
