-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdemos.html
More file actions
273 lines (243 loc) · 12.8 KB
/
demos.html
File metadata and controls
273 lines (243 loc) · 12.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Agent Development - S2SP Protocol</title>
<link rel="stylesheet" href="../css/style.css">
</head>
<body>
<nav class="nav">
<div class="nav-inner">
<a href="../index.html" class="nav-logo">
<img src="../images/s2sp_icon.png" class="logo-icon" alt="S2SP">
S2SP Protocol
</a>
<ul class="nav-links">
<li><a href="../index.html">Home</a></li>
<li><a href="introduction.html">Docs</a></li>
<li><a href="protocol.html">Protocol</a></li>
<li><a href="sdk.html">SDK</a></li>
<li><a href="demos.html">Examples</a>
</li>
</ul>
</div>
</nav>
<div class="doc-layout">
<aside class="sidebar">
<div class="sidebar-section">
<h4>Getting Started</h4>
<a href="introduction.html">Introduction</a>
<a href="introduction.html#quick-start" class="sub">Quick Start</a>
</div>
<div class="sidebar-section">
<h4>Learn</h4>
<a href="protocol.html">Protocol Design</a>
</div>
<div class="sidebar-section">
<h4>Develop</h4>
<a href="sdk.html">Python SDK</a>
</div>
<div class="sidebar-section">
<h4>Examples</h4>
<a href="demos.html" class="active">Agent Development with S2SP</a>
<a href="tutorial.html">Use S2SP Servers in Claude</a>
</div>
</aside>
<main class="doc-content">
<h1>Agent Development with S2SP Servers</h1>
<p>
This guide shows how to build an agent that uses two S2SP servers:
a <strong>resource server</strong> (<code>@s2sp_resource_tool()</code>) that
provides weather alerts, and a <strong>consumer server</strong>
(<code>@s2sp_consumer_tool()</code>) that generates charts from the data.
</p>
<p>
The example uses real NWS (National Weather Service) data with ~30 columns
per alert. It demonstrates both <strong>async</strong> and <strong>sync</strong>
modes, and works with <strong>Claude Desktop</strong>, <strong>OpenAI</strong>,
and <strong>MCP Inspector</strong>.
</p>
<h2>Architecture</h2>
<div class="code-block">
<pre>
<span class="comment">Weather Server</span> <span class="comment">Agent (LLM)</span> <span class="comment">Stats Server</span>
<span class="keyword">@s2sp_resource_tool()</span> <span class="keyword">@s2sp_consumer_tool()</span>
get_alerts() ──<span class="string">abstract</span>──▶ filters by draw_chart()
get_forecast() event, severity ◀── receives abstract
selects _row_ids + resource_url
│
│ <span class="string">async mode:</span>
▼
Weather Server ◀──<span class="keyword">POST /s2sp/data/{id}</span>── Stats Server
body domains fetched directly (data plane)
agent never sees this data
</pre>
</div>
<h2>Weather Server (Resource)</h2>
<p>
A standard MCP server with <code>@server.s2sp_resource_tool()</code>. Returns all
~30 NWS alert fields. The decorator adds <code>abstract_domains</code> and
<code>mode</code> parameters automatically.
</p>
<div class="code-block">
<pre><span class="keyword">from</span> mcp_s2sp <span class="keyword">import</span> S2SPServer
server = <span class="func">S2SPServer</span>(<span class="string">"weather-server"</span>)
<span class="decorator">@server.s2sp_resource_tool()</span>
<span class="keyword">async def</span> <span class="func">get_alerts</span>(area: str) -> list[dict]:
<span class="string">"""Get weather alerts — ~30 columns per alert."""</span>
data = <span class="keyword">await</span> <span class="func">make_nws_request</span>(
<span class="string">f"https://api.weather.gov/alerts/active?area={area}"</span>
)
<span class="keyword">return</span> [f[<span class="string">"properties"</span>] <span class="keyword">for</span> f <span class="keyword">in</span> data[<span class="string">"features"</span>]]
server.<span class="func">run</span>() <span class="comment"># mcp dev demos/weather_agent/weather_server.py</span></pre>
</div>
<h2>Stats Server (Consumer)</h2>
<p>
Uses <code>@server.s2sp_consumer_tool()</code>. The decorator handles
all S2SP plumbing — parsing, fetching body, remapping columns,
merging by <code>_row_id</code>. Your function just receives merged rows.
</p>
<div class="code-block">
<pre><span class="keyword">from</span> mcp_s2sp <span class="keyword">import</span> S2SPServer
server = <span class="func">S2SPServer</span>(<span class="string">"stats-server"</span>)
<span class="decorator">@server.s2sp_consumer_tool()</span>
<span class="keyword">async def</span> <span class="func">draw_chart</span>(rows: list[dict]) -> str:
<span class="string">"""Receives merged abstract + body rows."""</span>
<span class="keyword">return</span> <span class="func">generate_chart</span>(rows)
server.<span class="func">run</span>()
<span class="comment"># The decorator adds: abstract_data, resource_url, body_data, column_mapping</span>
<span class="comment"># Agent calls: draw_chart(abstract_data='[...]', resource_url='http://...')</span></pre>
</div>
<h2>Async Mode Demo</h2>
<p>
Body data stays cached on the Weather Server. The Stats Server fetches it
directly via the data plane. The agent never sees the body.
</p>
<div class="code-block">
<pre><span class="comment"># Step 1: Agent calls Weather Server with abstract_domains</span>
result = get_alerts(area=<span class="string">"CA"</span>,
abstract_domains=<span class="string">"event,severity,urgency,status"</span>,
mode=<span class="string">"async"</span>)
<span class="comment"># Agent sees only:</span>
<span class="comment"># { "abstract": [{"_row_id": 0, "event": "Wind Advisory", ...}, ...],</span>
<span class="comment"># "resource_url": "http://..." }</span>
<span class="comment"># No body data. No resource_url means body is inline.</span>
<span class="comment"># Step 2: Agent filters on abstract (control plane)</span>
wind_rows = [r <span class="keyword">for</span> r <span class="keyword">in</span> result[<span class="string">"abstract"</span>]
<span class="keyword">if</span> <span class="string">"Wind"</span> <span class="keyword">in</span> r[<span class="string">"event"</span>]]
<span class="comment"># Step 3: Agent passes abstract rows + resource ref to Stats Server</span>
draw_chart(
abstract_data=json.<span class="func">dumps</span>(wind_rows),
resource_url=result[<span class="string">"resource_url"</span>],
)
<span class="comment"># Stats Server fetches body via POST /s2sp/data/dK7x_...</span>
<span class="comment"># Merges abstract + body by _row_id, generates chart</span></pre>
</div>
<div class="code-block">
<pre><span class="string">$</span> python demos/weather_agent/run_async.py --area CA --event Wind</pre>
</div>
<h2>Sync Mode Demo</h2>
<p>
Body data is returned inline alongside the abstract. No
<code>resource_url</code> — no server-to-server
fetch needed. The agent's SDK layer has the body but should not send it to the LLM.
</p>
<div class="code-block">
<pre><span class="comment"># Step 1: Agent calls Weather Server in sync mode</span>
result = get_alerts(area=<span class="string">"CA"</span>,
abstract_domains=<span class="string">"event,severity,urgency,status"</span>,
mode=<span class="string">"sync"</span>)
<span class="comment"># Agent sees abstract AND body inline:</span>
<span class="comment"># { "abstract": [{"_row_id": 0, "event": "Wind Advisory", ...}, ...],</span>
<span class="comment"># "body": [{"_row_id": 0, "description": "...", ...}, ...] }</span>
<span class="comment"># No resource_url — body is right here.</span>
<span class="comment"># Step 2: Agent filters on abstract (control plane)</span>
wind_rows = [r <span class="keyword">for</span> r <span class="keyword">in</span> result[<span class="string">"abstract"</span>]
<span class="keyword">if</span> <span class="string">"Wind"</span> <span class="keyword">in</span> r[<span class="string">"event"</span>]]
<span class="comment"># Step 3: Filter body to match, pass both to Stats Server</span>
wind_ids = {r[<span class="string">"_row_id"</span>] <span class="keyword">for</span> r <span class="keyword">in</span> wind_rows}
wind_body = [b <span class="keyword">for</span> b <span class="keyword">in</span> result[<span class="string">"body"</span>]
<span class="keyword">if</span> b[<span class="string">"_row_id"</span>] <span class="keyword">in</span> wind_ids]
draw_chart(
abstract_data=json.<span class="func">dumps</span>(wind_rows),
body_data=json.<span class="func">dumps</span>(wind_body),
)
<span class="comment"># No data-plane fetch. Stats Server merges and generates chart.</span></pre>
</div>
<div class="code-block">
<pre><span class="string">$</span> python demos/weather_agent/run_sync.py --area CA --event Wind</pre>
</div>
<h2>Running the Servers</h2>
<p>
To use these servers with Claude Desktop or an interactive agent, see
<a href="tutorial.html">Use S2SP Servers in Claude</a>.
</p>
<h2>MCP Inspector</h2>
<p>
Debug each server independently with the MCP Inspector:
</p>
<div class="code-block">
<pre><span class="comment"># Weather Server (source) — tools: get_alerts, get_forecast</span>
<span class="string">$</span> mcp dev demos/weather_agent/weather_server.py
<span class="comment"># Stats Server (consumer) — tool: draw_chart</span>
<span class="string">$</span> CLIENT_PORT=6280 SERVER_PORT=6281 mcp dev demos/weather_agent/stats_server.py</pre>
</div>
<h2>Measured Results</h2>
<table>
<thead>
<tr>
<th>Metric</th>
<th>Traditional MCP</th>
<th>S2SP Async</th>
<th>S2SP Sync</th>
</tr>
</thead>
<tbody>
<tr>
<td>Tokens in agent context</td>
<td>~10,000 (all 30 columns)</td>
<td><strong>~600</strong> (abstract only)</td>
<td><strong>~600</strong> (abstract only*)</td>
</tr>
<tr>
<td>Body data through agent</td>
<td>Yes (all in LLM context)</td>
<td><strong>No</strong> (server-to-server)</td>
<td>Through SDK, <strong>not LLM</strong></td>
</tr>
<tr>
<td>Server-to-server fetch</td>
<td>N/A</td>
<td>Yes (POST /s2sp/data/)</td>
<td>No (inline)</td>
</tr>
<tr>
<td>Token savings</td>
<td>—</td>
<td><strong>85-96%</strong></td>
<td><strong>85-96%</strong></td>
</tr>
</tbody>
</table>
<p style="color: var(--text-secondary); font-size: 13px;">
* In sync mode, body is in the tool response but a well-designed agent SDK
feeds only the abstract to the LLM. The token count reflects what the LLM processes.
</p>
<h2>File Structure</h2>
<div class="code-block">
<pre>demos/weather_agent/
├── weather_server.py <span class="comment"># Resource: @s2sp_resource_tool() — get_alerts, get_forecast</span>
├── stats_server.py <span class="comment"># Consumer: @s2sp_consumer_tool() — draw_chart</span>
├── run_async.py <span class="comment"># Scripted async demo</span>
├── run_sync.py <span class="comment"># Scripted sync demo</span>
├── agent_async.py <span class="comment"># Interactive agent (async mode)</span>
└── agent_sync.py <span class="comment"># Interactive agent (sync mode)</span></pre>
</div>
</main>
</div>
<footer class="footer">
<p>S2SP Protocol — An open extension for MCP.</p>
</footer>
</body>
</html>