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
30 changes: 30 additions & 0 deletions examples/mcp-shop-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@ A tiny [MCP](https://modelcontextprotocol.io) server for the [ChatGPTStyleChat](
Swift sample. It exposes an order-lookup tool that advertises a **UI widget**, over streamable HTTP,
so the native app can point at it instead of its built-in in-app tool.

Built on `mcp` 2.x, so it serves MCP protocol **2026-07-28** and still answers older clients
through the legacy handshake — the same server works for every consumer below, old or new.

It shows the same contract ChatGPT Apps use: a tool returns `structuredContent` and advertises
`_meta.ui.resourceUri`. The app renders that widget as a native SwiftUI card, hydrated from the
tool's data — no HTML, no web view.

## Run it

Requires Python >= 3.10 (the `mcp` 2.x floor).

```bash
# Optional: Set up a virtual environment
python -m venv venv
Expand All @@ -19,6 +24,31 @@ python shop_server.py # serves on http://127.0.0.1:8000/mcp (l
HOST=0.0.0.0 python shop_server.py # bind to the LAN, for a physical device
```

## Consume it from agent-squad (TypeScript / Python)

`MCPToolProvider` connects over the `streamable-http` transport; the protocol era is negotiated
per server, so nothing else is configured. With the v2 SDKs installed
(`@modelcontextprotocol/client` / `mcp>=2`) the session runs on protocol 2026-07-28; with the v1
SDKs it falls back to the legacy handshake — same code either way.

```typescript
import { MCPToolProvider } from "agent-squad";

const provider = await MCPToolProvider.create([
{ type: "streamable-http", url: "http://127.0.0.1:8000/mcp" },
]);
// get_order is offered to the model; refresh_order stays app-only.
```

```python
from agent_squad.tools import MCPToolProvider, MCPServerConfig

provider = await MCPToolProvider.create([
MCPServerConfig(type="streamable-http", url="http://127.0.0.1:8000/mcp"),
])
# get_order is offered to the model; refresh_order stays app-only.
```

## Point the app at it

In the Swift sample, set the toggle in `Config.swift`:
Expand Down
5 changes: 2 additions & 3 deletions examples/mcp-shop-server/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
# Tested with mcp 1.28.1. `meta=` on @mcp.tool and structured_output need a recent build.
# Capped to <2: mcp 2.0.0 is a breaking release (FastMCP renamed, snake_case types).
mcp>=1.28.0,<2
# mcp 2.x (MCPServer) serves MCP protocol 2026-07-28 and answers legacy clients too.
mcp>=2.0.0
20 changes: 12 additions & 8 deletions examples/mcp-shop-server/shop_server.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""A tiny MCP shop server for the ChatGPTStyleChat sample.

Exposes an order-lookup tool that advertises a UI widget, over streamable HTTP so a native app
(the Swift ChatGPTStyleChat sample) can point at it with `MCPServer(url: "http://…/mcp")`.
(the Swift ChatGPTStyleChat sample) can point at it with AgentSquadMCP's `MCPServer(url: "http://…/mcp")`.

pip install -r requirements.txt
python shop_server.py # serves on http://127.0.0.1:8000/mcp
Expand All @@ -20,19 +20,17 @@

import os

from mcp.server.fastmcp import FastMCP
from mcp.server import MCPServer
from pydantic import BaseModel

ORDER_CARD_URI = "ui://shop/order-card"

# Loopback by default (Simulator reaches it). For a physical device, bind to the LAN with
# `HOST=0.0.0.0 python shop_server.py` and point the app at your Mac's LAN IP.
mcp = FastMCP("shop", host=os.environ.get("HOST", "127.0.0.1"), port=int(os.environ.get("PORT", "8000")))
mcp = MCPServer("shop")


class Order(BaseModel):
"""The order shape. A typed return makes FastMCP emit it as `structuredContent` — the render-only
data the native card hydrates from."""
"""The order shape. A typed return makes the server emit it as `structuredContent` — the
render-only data the native card hydrates from."""

orderId: str
status: str
Expand Down Expand Up @@ -81,4 +79,10 @@ def order_card_resource() -> str:


if __name__ == "__main__":
mcp.run(transport="streamable-http")
# Loopback by default (Simulator reaches it). For a physical device, bind to the LAN with
# `HOST=0.0.0.0 python shop_server.py` and point the app at your Mac's LAN IP.
mcp.run(
transport="streamable-http",
host=os.environ.get("HOST", "127.0.0.1"),
port=int(os.environ.get("PORT", "8000")),
)
Loading