Skip to content

Commit 89f374d

Browse files
committed
add examples to data
1 parent a58cd96 commit 89f374d

File tree

102 files changed

+17814
-60
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

102 files changed

+17814
-60
lines changed

src/mcp_agent/cli/commands/init.py

Lines changed: 16 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
from __future__ import annotations
66

7-
import shutil
87
from pathlib import Path
98
from importlib import resources
109
from importlib.resources import files as _pkg_files
@@ -18,9 +17,6 @@
1817
console = Console()
1918
err_console = Console(stderr=True)
2019

21-
# Path to repository examples
22-
EXAMPLE_ROOT = Path(__file__).parents[4] / "examples"
23-
2420

2521
def _load_template(template_name: str) -> str:
2622
"""Load a template file from the data/templates directory."""
@@ -80,27 +76,6 @@ def _write_readme(dir_path: Path, content: str, force: bool) -> str | None:
8076
return None
8177

8278

83-
def _copy_tree(src: Path, dst: Path, force: bool) -> int:
84-
"""Copy a directory tree from src to dst.
85-
86-
Returns 1 on success, 0 on failure.
87-
"""
88-
if not src.exists():
89-
err_console.print(f"[red]Source not found: {src}[/red]")
90-
return 0
91-
try:
92-
if dst.exists():
93-
if force:
94-
shutil.rmtree(dst)
95-
else:
96-
return 0
97-
shutil.copytree(src, dst)
98-
return 1
99-
except Exception as e:
100-
err_console.print(f"[red]Error copying tree: {e}[/red]")
101-
return 0
102-
103-
10479
def _copy_pkg_tree(pkg_rel: str, dst: Path, force: bool) -> int:
10580
"""Copy packaged examples from mcp_agent.data/examples/<pkg_rel> into dst.
10681
@@ -189,25 +164,24 @@ def init(
189164
templates = {**scaffolding_templates, **example_templates}
190165

191166
# Map template names to their source paths (shared by quickstart and template modes)
192-
# Format: "name": (repo_path, dest_name) for repo examples
193-
# "name": (None, dest_name, pkg_rel) for packaged examples
167+
# Format: "name": (None, dest_name, pkg_rel) for packaged examples
194168
example_map = {
195-
"workflow": (EXAMPLE_ROOT / "workflows", "workflow"),
196-
"researcher": (EXAMPLE_ROOT / "usecases" / "mcp_researcher", "researcher"),
197-
"data-analysis": (EXAMPLE_ROOT / "usecases" / "mcp_financial_analyzer", "data-analysis"),
198-
"state-transfer": (EXAMPLE_ROOT / "workflows" / "workflow_router", "state-transfer"),
199-
"basic-agent-server": (EXAMPLE_ROOT / "mcp_agent_server" / "asyncio", "basic_agent_server"),
169+
"workflow": (None, "workflow", "workflows"),
170+
"researcher": (None, "researcher", "usecases/mcp_researcher"),
171+
"data-analysis": (None, "data-analysis", "usecases/mcp_financial_analyzer"),
172+
"state-transfer": (None, "state-transfer", "workflows/workflow_router"),
173+
"basic-agent-server": (None, "basic_agent_server", "mcp_agent_server/asyncio"),
200174
"mcp-basic-agent": (None, "mcp_basic_agent", "basic/mcp_basic_agent"),
201175
"token-counter": (None, "token_counter", "basic/token_counter"),
202176
"agent-factory": (None, "agent_factory", "basic/agent_factory"),
203177
"reference-agent-server": (None, "reference_agent_server", "mcp_agent_server/reference"),
204178
"elicitation": (None, "elicitation", "mcp_agent_server/elicitation"),
205179
"sampling": (None, "sampling", "mcp_agent_server/sampling"),
206180
"notifications": (None, "notifications", "mcp_agent_server/notifications"),
207-
"hello-world": (EXAMPLE_ROOT / "cloud" / "hello_world", "hello_world"),
208-
"mcp": (EXAMPLE_ROOT / "cloud" / "mcp", "mcp"),
209-
"temporal": (EXAMPLE_ROOT / "cloud" / "temporal", "temporal"),
210-
"chatgpt-app": (EXAMPLE_ROOT / "cloud" / "chatgpt_app", "chatgpt_app"),
181+
"hello-world": (None, "hello_world", "cloud/hello_world"),
182+
"mcp": (None, "mcp", "cloud/mcp"),
183+
"temporal": (None, "temporal", "cloud/temporal"),
184+
"chatgpt-app": (None, "chatgpt_app", "cloud/chatgpt_app"),
211185
}
212186

213187
if list_templates:
@@ -254,18 +228,9 @@ def init(
254228
base_dir = dir.resolve()
255229
base_dir.mkdir(parents=True, exist_ok=True)
256230

257-
if len(mapping) == 3:
258-
_, dst_name, pkg_rel = mapping
259-
dst = base_dir / dst_name
260-
copied = _copy_pkg_tree(pkg_rel, dst, force)
261-
if not copied:
262-
src = EXAMPLE_ROOT / pkg_rel.replace("/", "_")
263-
if src.exists():
264-
copied = _copy_tree(src, dst, force)
265-
else:
266-
src, dst_name = mapping
267-
dst = base_dir / dst_name
268-
copied = _copy_tree(src, dst, force)
231+
_, dst_name, pkg_rel = mapping
232+
dst = base_dir / dst_name
233+
copied = _copy_pkg_tree(pkg_rel, dst, force)
269234

270235
if copied:
271236
console.print(f"Copied {copied} set(s) to {dst}")
@@ -317,18 +282,9 @@ def init(
317282
console.print(f"[red]Example template '{template}' not found[/red]")
318283
raise typer.Exit(1)
319284

320-
if len(mapping) == 3:
321-
_, dst_name, pkg_rel = mapping
322-
dst = dir / dst_name
323-
copied = _copy_pkg_tree(pkg_rel, dst, force)
324-
if not copied:
325-
src = EXAMPLE_ROOT / pkg_rel.replace("/", "_")
326-
if src.exists():
327-
copied = _copy_tree(src, dst, force)
328-
else:
329-
src, dst_name = mapping
330-
dst = dir / dst_name
331-
copied = _copy_tree(src, dst, force)
285+
_, dst_name, pkg_rel = mapping
286+
dst = dir / dst_name
287+
copied = _copy_pkg_tree(pkg_rel, dst, force)
332288

333289
if copied:
334290
console.print(f"\n[green]✅ Successfully copied example '{template}'![/green]")
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
# ChatGPT App Example
2+
3+
This example demonstrates how to create an MCP Agent application with interactive UI widgets for OpenAI's ChatGPT Apps platform. It shows how to build a coin-flip widget that renders interactive UI components directly in the ChatGPT interface.
4+
5+
## Motivation
6+
7+
This example showcases the integration between mcp-agent and OpenAI's ChatGPT Apps SDK, specifically demonstrating:
8+
9+
- **Widget-based UI**: Creating interactive widgets that render in ChatGPT
10+
- **Resource templates**: Serving HTML/JS/CSS as MCP resources
11+
- **Tool invocation metadata**: Using OpenAI-specific metadata for tool behavior
12+
- **Static asset serving**: Two approaches for serving client-side code (inline vs. deployed)
13+
14+
## Concepts Demonstrated
15+
16+
- Creating MCP tools with OpenAI widget metadata
17+
- Serving interactive HTML/JS/CSS widgets through MCP resources
18+
- Using `EmbeddedResource` to pass UI templates to ChatGPT
19+
- Handling tool calls that return structured content for widget hydration
20+
- Deploying web clients alongside MCP servers
21+
22+
## Components in this Example
23+
24+
1. **CoinFlipWidget**: A dataclass that encapsulates all widget metadata:
25+
- Widget identifier and title
26+
- Template URI (cached by ChatGPT)
27+
- Tool invocation state messages
28+
- HTML template content
29+
- Response text
30+
31+
> [!TIP]
32+
> The widget HTML templates are heavily cached by OpenAI Apps. Use date-based URIs (like `ui://widget/coin-flip-10-22-2025-15-48.html`) to bust the cache when updating the widget.
33+
34+
2. **MCP Server**: FastMCP server configured for stateless HTTP with:
35+
36+
- Tool registration (`coin-flip` tool)
37+
- Resource serving (HTML template)
38+
- Resource template registration
39+
- Custom request handlers for tools and resources
40+
41+
3. **Web Client**: A React application (in `web/` directory) that:
42+
- Renders an interactive coin flip interface
43+
- Hydrates with structured data from tool calls
44+
- Provides visual feedback for coin flip results
45+
46+
## Static Asset Serving Approaches
47+
48+
The example demonstrates two methods for serving the web client assets:
49+
50+
### Method 1: Inline Assets (Default)
51+
52+
Embeds the JavaScript and CSS directly into the HTML template. This approach:
53+
54+
- Works immediately for initial deployment
55+
- Can lead to large HTML templates
56+
- May have string escaping issues
57+
- Best for initial development and testing
58+
59+
### Method 2: Deployed Assets (Recommended)
60+
61+
References static files from a deployed server URL:
62+
63+
- Smaller HTML templates
64+
- Better performance with caching
65+
- Requires initial deployment to get the server URL
66+
- Best for production use
67+
68+
## Prerequisites
69+
70+
- Python 3.10+
71+
- [UV](https://github.com/astral-sh/uv) package manager
72+
- Node.js and npm/yarn (for building the web client)
73+
74+
## Building the Web Client
75+
76+
Before running the server, you need to build the React web client:
77+
78+
```bash
79+
cd web
80+
yarn install
81+
yarn build
82+
cd ..
83+
```
84+
85+
This creates optimized production assets in `web/build/` that the server will serve.
86+
87+
## Test Locally
88+
89+
Install the dependencies:
90+
91+
```bash
92+
uv pip install -r requirements.txt
93+
```
94+
95+
Spin up the mcp-agent server locally with SSE transport:
96+
97+
```bash
98+
uv run main.py
99+
```
100+
101+
This will:
102+
103+
- Start the MCP server on port 8000
104+
- Serve the web client at http://127.0.0.1:8000
105+
- Serve static assets (JS/CSS) at http://127.0.0.1:8000/static
106+
107+
Use [MCP Inspector](https://github.com/modelcontextprotocol/inspector) to explore and test the server:
108+
109+
```bash
110+
npx @modelcontextprotocol/inspector --transport sse --server-url http://127.0.0.1:8000/sse
111+
```
112+
113+
In MCP Inspector:
114+
115+
- Click **Tools > List Tools** to see the `coin-flip` tool
116+
- Click **Resources > List Resources** to see the widget HTML template
117+
- Run the `coin-flip` tool to see the widget metadata and structured result
118+
119+
## Deploy to mcp-agent Cloud
120+
121+
You can deploy this MCP-Agent app as a hosted mcp-agent app in the Cloud.
122+
123+
1. In your terminal, authenticate into mcp-agent cloud by running:
124+
125+
```bash
126+
uv run mcp-agent login
127+
```
128+
129+
2. You will be redirected to the login page, create an mcp-agent cloud account through Google or Github
130+
131+
3. Set up your mcp-agent cloud API Key and copy & paste it into your terminal
132+
133+
```bash
134+
uv run mcp-agent login
135+
INFO: Directing to MCP Agent Cloud API login...
136+
Please enter your API key =:
137+
```
138+
139+
4. In your terminal, deploy the MCP app:
140+
141+
```bash
142+
uv run mcp-agent deploy chatgpt-app --no-auth
143+
```
144+
145+
Note the use of `--no-auth` flag here will allow unauthenticated access to this server using its URL.
146+
147+
The `deploy` command will bundle the app files and deploy them, producing a server URL of the form:
148+
`https://<server_id>.deployments.mcp-agent.com`.
149+
150+
5. After deployment, update main.py:767 with your actual server URL:
151+
152+
```python
153+
SERVER_URL = "https://<server_id>.deployments.mcp-agent.com"
154+
```
155+
156+
6. Switch to using deployed assets (optional but recommended):
157+
158+
Update main.py:782 to use `DEPLOYED_HTML_TEMPLATE`:
159+
160+
```python
161+
html=DEPLOYED_HTML_TEMPLATE,
162+
```
163+
164+
Then bump the template uri:
165+
166+
```python
167+
template_uri="ui://widget/coin-flip-<date-string>.html",
168+
```
169+
170+
Then redeploy:
171+
172+
```bash
173+
uv run mcp-agent deploy chatgpt-app --no-auth
174+
```
175+
176+
## Using with OpenAI ChatGPT Apps
177+
178+
Once deployed, you can integrate this server with ChatGPT Apps:
179+
180+
1. In your OpenAI platform account, create a new ChatGPT App
181+
2. Configure the app to connect to your deployed MCP server URL
182+
3. The `coin-flip` tool will appear as an available action
183+
4. When invoked, the widget will render in the ChatGPT interface with interactive UI
184+
185+
## Understanding Widget Metadata
186+
187+
The example uses OpenAI-specific metadata fields:
188+
189+
- `openai/outputTemplate`: URI pointing to the HTML template resource
190+
- `openai/toolInvocation/invoking`: Message shown while tool is being called
191+
- `openai/toolInvocation/invoked`: Message shown after tool completes
192+
- `openai/widgetAccessible`: Indicates the tool can render a widget
193+
- `openai/resultCanProduceWidget`: Indicates the result includes widget data
194+
195+
These metadata fields tell ChatGPT how to handle the tool and render the UI.
196+
197+
## Widget Hydration
198+
199+
When the `coin-flip` tool is called:
200+
201+
1. The server returns an `EmbeddedResource` containing the HTML template
202+
2. The server includes `structuredContent` with the flip result (`{"flipResult": "heads"}`)
203+
3. ChatGPT loads the HTML and executes the embedded JavaScript
204+
4. The React app hydrates with the structured data and displays the result
205+
5. The user can interact with the widget to flip again
206+
207+
## MCP Clients
208+
209+
Since the mcp-agent app is exposed as an MCP server, it can be used in any MCP client just like any other MCP server.
210+
211+
## Test Deployment
212+
213+
Use [MCP Inspector](https://github.com/modelcontextprotocol/inspector) to explore and test this server:
214+
215+
```bash
216+
npx @modelcontextprotocol/inspector --transport sse --server-url https://<server_id>.deployments.mcp-agent.com/sse
217+
```
218+
219+
Make sure Inspector is configured with the following settings:
220+
221+
| Setting | Value |
222+
| ---------------- | --------------------------------------------------- |
223+
| _Transport Type_ | _SSE_ |
224+
| _SSE_ | _https://[server_id].deployments.mcp-agent.com/sse_ |
225+
226+
## Code Structure
227+
228+
- `main.py` - Defines the MCP server, widget metadata, and tool handlers
229+
- `web/` - React web client for the coin flip widget
230+
- `web/src/` - React source code
231+
- `web/build/` - Production build output (generated)
232+
- `web/public/` - Static assets
233+
- `mcp_agent.config.yaml` - App configuration (execution engine, name)
234+
- `requirements.txt` - Python dependencies
235+
236+
## Additional Resources
237+
238+
- [OpenAI Apps SDK Documentation](https://developers.openai.com/apps-sdk/build/mcp-server)

0 commit comments

Comments
 (0)