> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Serve Agents

> Publish your PraisonAI agents to Claude, Cursor, and any MCP client in one line

Publish one or more agents to any MCP client as `ask_{name}` tools with a single `serve_agents([...])` call.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent
from praisonai_mcp import serve_agents

support = Agent(name="Support", instructions="Help users with support tickets.")
billing = Agent(name="Billing", instructions="Answer billing questions.")

serve_agents([support, billing], port=7777)
```

> [!NOTE]
>
> **Same thing via `launch()`.** `Agent.launch(protocol="mcp")` and `PraisonAIAgents.launch(protocol="mcp")` both delegate to `serve_agents(...)`, so the tools, endpoint, and session model are identical:
>
> ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
> PraisonAIAgents(agents=[support, billing]).launch(protocol="mcp", port=7777)
> # is equivalent to
> serve_agents([support, billing], port=7777)
>
> # and
> agent.launch(protocol="mcp", port=7777)
> # is equivalent to
> serve_agents([agent], port=7777)
> ```
>
> Pick whichever idiom fits your code — see [Agents MCP](https://praison.ai/docs/deploy/servers/agents-mcp.md).

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    Client[🖥️ MCP Client<br/>Claude / Cursor] --> Endpoint[🌐 HTTP-stream<br/>Endpoint]
    Endpoint --> Serve[⚙️ serve_agents]
    Serve --> AskS[🔧 ask_support]
    Serve --> AskB[🔧 ask_billing]
    AskS --> Agents[🤖 Your Agents]
    AskB --> Agents
    Agents --> Store[💾 Session Store]
    Store --> Response[✅ Response]

    classDef client fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef agent fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef result fill:#10B981,stroke:#7C90A0,color:#fff

    class Client,Response client
    class Endpoint,Serve,AskS,AskB process
    class Agents,Store agent
    class Response result
```

## Quick Start

**1. Install**

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
pip install praisonai-mcp
```

`pip install praisonai-mcp` alone is enough for the default `http-stream` transport. Add `praisonai-mcp[all]` only if you need extra transports.

**2. Serve One Agent**

Pass a single agent — `serve_agents` wraps it in a list for you.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent
from praisonai_mcp import serve_agents

support = Agent(name="Support", instructions="Help users with support tickets.")

serve_agents(support, port=7777)
```

**3. Serve Multiple Agents**

Pass a list to publish every agent on one endpoint.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent
from praisonai_mcp import serve_agents

support = Agent(name="Support", instructions="Help users with support tickets.")
billing = Agent(name="Billing", instructions="Answer billing questions.")

serve_agents([support, billing], port=7777)
```

**4. Serve Over stdio**

Use `stdio` when the MCP client launches your process locally (Claude Desktop).

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent
from praisonai_mcp import serve_agents

support = Agent(name="Support", instructions="Help users with support tickets.")

serve_agents([support], transport="stdio")
```

***

## How It Works

Each `ask_*` call loads that session's transcript, runs one turn on an isolated copy of your agent, appends the turn, and returns the reply.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant Client as MCP Client
    participant Adapter as serve_agents
    participant Store as Session Store
    participant Agent as Your Agent

    Client->>Adapter: ask_support(message, session_id?)
    Adapter->>Store: load transcript for session
    Store-->>Adapter: chat history
    Adapter->>Agent: achat(message) on isolated copy
    Agent-->>Adapter: reply
    Adapter->>Store: append user + assistant turns
    Adapter-->>Client: {text, structuredContent: {session_id, status}}
```

Pick a transport based on where the client runs.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    Q{Where does the MCP client live?}
    Q -->|Same machine, launched by client<br/>Claude Desktop| S["transport='stdio'"]
    Q -->|Anywhere over the network<br/>Cursor remote, ChatGPT connectors, custom| H["transport='http-stream' (default)"]

    classDef question fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef option fill:#189AB4,stroke:#7C90A0,color:#fff

    class Q question
    class S,H option
```

***

## Configuration Options

| Parameter | Type | Default | Description |
| - | - | - | - |
| `agents` | `Agent \| list[Agent] \| tuple[Agent, ...]` | *required* | A single `Agent` or a list/tuple of them. A single agent is silently wrapped in a list. |
| `host` | `str` | `"127.0.0.1"` | Bind host. Ignored under `stdio`. |
| `port` | `int` | `7777` | Bind port. Ignored under `stdio`. |
| `transport` | `str` | `"http-stream"` | Either `"http-stream"` (spec-compliant streamable HTTP) or `"stdio"`. Anything else raises `ValueError`. |
| `name` | `str` | `"praisonai-agents"` | MCP server name advertised to the client. |
| `**kwargs` | — | — | Forwarded verbatim to the underlying `MCPServer.run(...)` for the chosen transport (e.g. HTTP-only args like `api_key`, `cors_origins`, `session_ttl`). |

> [!NOTE]
>
> Under `transport="stdio"`, `host` and `port` are ignored — the client owns the process over stdin/stdout. Any value other than `"http-stream"` or `"stdio"` raises `ValueError`.

`serve_agents(...)` returns the underlying `MCPServer` instance, which is handy for tests and orchestration.

***

## Published Tools

`serve_agents([...])` registers one `ask_{agent_name}` tool per agent plus a single `list_agents()` discovery tool.

### ask\_{agent_name}

Input schema:

```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
{
  "type": "object",
  "properties": {
    "message":    {"type": "string", "description": "Message for the agent."},
    "session_id": {"type": "string", "description": "Opaque session id for conversation continuity. Omit to start a new session."},
    "user_id":    {"type": "string", "description": "Optional caller id."}
  },
  "required": ["message"]
}
```

Return value:

```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
{
  "text": "<agent reply>",
  "structuredContent": {"session_id": "<echoed or minted id>", "status": "ok"}
}
```

### list\_agents

Returns the agents available on the endpoint:

```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
{"agents": [{"name": "Support", "description": "..."}, {"name": "Billing", "description": "..."}]}
```

The `description` falls back through `agent.role` → the first line of `agent.instructions` → `"Ask the {name} agent."`.

***

## Session Model

Sessions are opaque, isolated, and server-owned — the client controls continuity by echoing back the `session_id`, nothing more.

* **`session_id` is opaque and client-supplied.** When present, the adapter loads that session's transcript, applies it to an isolated per-call agent copy, runs one turn, and appends the user + assistant turns.
* **Omitting `session_id` mints a fresh id per call.** It never falls back to a shared sticky session, so history never leaks across clients. Verified by `test_omitted_session_mints_fresh_id_and_no_shared_history`.
* **Transcripts are namespaced per tool.** The internal store key is `f"{tool_name}:{session_id}"`, so reusing one `session_id` across `ask_support` and `ask_billing` does not mix their histories. Verified by `test_same_session_id_across_agents_does_not_leak_history`.
* **Per-session serialization.** Each session holds a `threading.Lock`, so overlapping calls to the same session can't clobber each other's saved turn. Different sessions never contend.
* **Per-turn agent isolation.** Each turn runs on a shallow `copy.copy(agent)` with the session's history swapped in; the original `Agent` is never mutated by concurrent sessions.
* **Server-owned persona.** There is no tool parameter that lets the client set `instructions` or persona. Verified by `test_no_client_instructions_parameter`.
* **AgentOS `/chat` now uses the same per-request clone model** as `serve_agents`, but drops back to the shared template when the agent has `handoffs`. See [AgentOS Chat Session Isolation](https://praison.ai/docs/features/agentos-chat-session-isolation.md).

> [!WARNING]
>
> Omit `session_id` to start a fresh session — do not pass an empty string. Every call without the field gets a brand-new minted id.

Client-side continuity pattern — store the returned `session_id` and send it back on the next turn:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
first = call_tool("ask_support", {"message": "My order is late."})
session = first["structuredContent"]["session_id"]

follow_up = call_tool("ask_support", {"message": "Any update?", "session_id": session})
```

***

## Naming and Disambiguation

Agent names become tool suffixes deterministically. Collisions get a numeric suffix.

| Input `agent.name` | Published tool |
| - | - |
| `"Support"` | `ask_support` |
| `"sup-port"` | `ask_sup_port` |
| `""` or `None` | `ask_agent` |
| Two agents normalising to `support` (`"Support"` and `"support"`) | `ask_support` + `ask_support_2` |
| Two unnamed agents | `ask_agent` + `ask_agent_2` |

Verified by `test_normalized_name_collision_registers_distinct_tools` and `test_unnamed_agents_do_not_collide`.

***

## What Clients See

With `support` and `billing` served, `list_agents()` returns:

```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
{"agents": [{"name": "Support", "description": "..."}, {"name": "Billing", "description": "..."}]}
```

and the tool list contains `ask_support`, `ask_billing`, and `list_agents`.

Call an agent over the http-stream endpoint:

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
curl -X POST http://127.0.0.1:7777/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": {"name": "ask_support", "arguments": {"message": "My order is late."}},
    "id": 1
  }'
```

***

## Client Integration

> [tab] Claude Desktop (stdio)
>
> ```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
> {
>   "mcpServers": {
>     "praisonai-agents": {
>       "command": "python",
>       "args": ["serve_agents_app.py"]
>     }
>   }
> }
> ```
>
> `serve_agents_app.py`:
>
> ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
> from praisonaiagents import Agent
> from praisonai_mcp import serve_agents
>
> support = Agent(name="Support", instructions="Help users with support tickets.")
>
> serve_agents([support], transport="stdio")
> ```

> [tab] Cursor (http-stream)
>
> Start the server:
>
> ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
> from praisonaiagents import Agent
> from praisonai_mcp import serve_agents
>
> support = Agent(name="Support", instructions="Help users with support tickets.")
> billing = Agent(name="Billing", instructions="Answer billing questions.")
>
> serve_agents([support, billing], port=7777)
> ```
>
> `.cursor/mcp.json`:
>
> ```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
> {
>   "mcpServers": {
>     "praisonai-agents": {
>       "url": "http://127.0.0.1:7777/mcp",
>       "transport": "http-stream"
>     }
>   }
> }
> ```

***

## Best Practices

> [accordion] Serve your own agents with serve\_agents, not praisonai.agent.chat
>
> Use `serve_agents([...])` for your product agents. The `praisonai.agent.chat` tool is a generic, server-owned assistant proxy that intentionally cannot be repersonalised by the client.

> [accordion] Give every agent a name
>
> The name becomes the `ask_{name}` tool suffix the client sees. Unnamed agents collapse to `ask_agent`, `ask_agent_2`, and so on.

> [accordion] Store and resend the session\_id
>
> Keep the returned `session_id` on the client side and pass it back on every follow-up turn for the same conversation. Omitting it always starts a fresh session — that is by design.

> [accordion] Match the transport to the deployment
>
> Prefer `transport='http-stream'` for shared deployments. Use `transport='stdio'` only when the MCP client launches your process (Claude Desktop).

***

## Related

> [card] [Agents MCP](https://praison.ai/docs/deploy/servers/agents-mcp.md)
>
> `launch(protocol='mcp')` — the same code path, delegating here

> [card] [PraisonAI MCP](https://praison.ai/docs/deploy/servers/praisonai-mcp.md)
>
> Umbrella `praisonai mcp serve`

> [card] [MCP](https://praison.ai/docs/features/mcp.md)
>
> Connect agents *to* MCP servers (client side)

> [card] [praisonai-mcp Package](https://praison.ai/docs/features/praisonai-mcp-package.md)
>
> Standalone host package guide

> [card] [MCP Memory Tools](https://praison.ai/docs/features/mcp-memory-tools.md)
>
> Expose memory over MCP alongside your agents

> [card] [AgentOS Serve Reload](https://praison.ai/docs/features/agentos-serve-reload.md)
>
> Why `AgentOS.serve(reload=True)` is unsupported and how to get reload


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.