-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsdk.html
More file actions
355 lines (323 loc) · 18 KB
/
Copy pathsdk.html
File metadata and controls
355 lines (323 loc) · 18 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
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Python SDK - MCP-SD / S2SP</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="MCP-SD">
MCP-SD <span class="nav-sub">/ S2SP</span>
</a>
<ul class="nav-links">
<li><a href="../index.html">Home</a></li>
<li><a href="introduction.html">Docs</a></li>
<li><a href="when-to-use.html">When to Use</a></li>
<li><a href="protocol.html">Protocol</a></li>
<li><a href="sdk.html" class="active">SDK</a></li>
<li><a href="demos.html">Demos</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">Agent Development with S2SP</a>
<a href="tutorial.html">Use S2SP Servers in Claude</a>
</div>
</aside>
<main class="doc-content">
<h1>Python SDK</h1>
<p>
The <code>mcp-sd</code> Python package provides everything you need to build
MCP servers with server-to-server data transfer capabilities. The
<code>S2SPServer</code> class embeds a <code>FastMCP</code> instance automatically
and provides the <code>@sd_resource_tool()</code> decorator, which wraps normal MCP tools
with a control-plane/data-plane split: when <code>abstract_domains</code> is set,
fields are filtered (control plane), full data is cached (data plane), and a
<code>resource_url</code> (presigned URL) is returned for later retrieval.
</p>
<h2>Installation</h2>
<p>Install from PyPI:</p>
<div class="install-block">
<span class="dollar">$</span>
<code>pip install mcp-sd</code>
</div>
<p>Install optional dependencies only when you need demos or agent-framework adapters:</p>
<div class="install-block">
<span class="dollar">$</span>
<code>pip install mcp-sd[demos]</code>
</div>
<div class="install-block">
<span class="dollar">$</span>
<code>pip install mcp-sd[claude-agent]</code>
</div>
<div class="install-block">
<span class="dollar">$</span>
<code>pip install mcp-sd[langgraph]</code>
</div>
<p>Requirements: Python 3.10+, <code>mcp</code>, <code>httpx</code>, <code>starlette</code>, <code>uvicorn</code>, <code>pydantic</code>, and <code>anyio</code>.</p>
<h2>SDK Architecture</h2>
<p>
The SDK is organized around the <code>S2SPServer</code> class and supporting modules:
</p>
<table>
<thead>
<tr>
<th>Module</th>
<th>Responsibility</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>server.py</code></td>
<td>The <code>S2SPServer</code> class. Constructor takes a name and embeds a <code>FastMCP</code> instance automatically. Provides <code>@sd_resource_tool()</code> for the primary decorator, <code>server.mcp</code> for direct FastMCP access, and <code>run()</code> to start as an MCP server.</td>
</tr>
<tr>
<td><code>direct_channel.py</code></td>
<td>The <code>DirectChannel</code> class. Provides the static method <code>fetch_data(resource_url, row_ids=None, columns=None, column_mapping=None)</code> for receiver servers to fetch specific rows from a resource server's data plane.</td>
</tr>
<tr>
<td><code>errors.py</code></td>
<td>Custom exception classes for S2SP error handling.</td>
</tr>
<tr>
<td><code>agent/</code></td>
<td>Agent-side dispatcher, DDI buffer, and optional Claude Agent SDK / LangGraph adapters for sync-mode body routing.</td>
</tr>
</tbody>
</table>
<h2 id="api-reference">S2SPServer API</h2>
<p>
The <code>S2SPServer</code> class is the primary interface for building MCP servers
with S2SP capabilities. It embeds a <code>FastMCP</code> instance and provides the
<code>@sd_resource_tool()</code> decorator as the primary way to expose data tools.
</p>
<h3>Constructor</h3>
<div class="code-block">
<pre><span class="keyword">from</span> mcp_sd <span class="keyword">import</span> S2SPServer
server = <span class="func">S2SPServer</span>(<span class="string">"my-server"</span>) <span class="comment"># Name for the server; embeds FastMCP automatically</span></pre>
</div>
<h3>server.mcp</h3>
<p>
Access the embedded <code>FastMCP</code> instance directly. Use this to register
custom tools, resources, or prompts that don't need the S2SP control-plane/data-plane
split.
</p>
<div class="code-block">
<pre><span class="comment"># Register a plain MCP tool via the embedded FastMCP instance</span>
<span class="decorator">@server.mcp.tool()</span>
<span class="keyword">async def</span> <span class="func">ping</span>() -> <span class="func">str</span>:
<span class="keyword">return</span> <span class="string">"pong"</span></pre>
</div>
<h2>Resource Tools vs Consumer Tools</h2>
<p>
S2SP distinguishes two types of MCP tools:
</p>
<table>
<thead>
<tr><th></th><th>Resource Tool</th><th>Consumer Tool</th></tr>
</thead>
<tbody>
<tr>
<td><strong>Decorator</strong></td>
<td><code>@server.sd_resource_tool()</code></td>
<td><code>@server.sd_consumer_tool()</code></td>
</tr>
<tr>
<td><strong>You write</strong></td>
<td><code>async def get_data(...) -> list[dict]</code></td>
<td><code>async def process(rows: list[dict]) -> str</code></td>
</tr>
<tr>
<td><strong>Returns</strong></td>
<td><code>list[dict]</code> — S2SP adds <code>abstract_domains</code>, <code>mode</code>, <code>_row_id</code></td>
<td>Anything — it's a normal MCP tool</td>
</tr>
<tr>
<td><strong>Data plane</strong></td>
<td>Caches body, serves via <code>/s2sp/data/</code></td>
<td>Fetches + remaps + merges automatically</td>
</tr>
</tbody>
</table>
<h3>@server.sd_resource_tool() — Resource Tool</h3>
<p>
Use this for tools that return tabular data. The decorator wraps a function
returning <code>list[dict]</code> and automatically adds <code>abstract_domains</code>
and <code>mode</code> parameters. When <code>abstract_domains</code> is set, the tool
filters to those columns (+ <code>_row_id</code>), caches the full data, and returns
a <code>resource_url</code> for data-plane access.
</p>
<div class="code-block">
<pre><span class="decorator">@server.sd_resource_tool()</span>
<span class="keyword">async def</span> <span class="func">get_alerts</span>(area: <span class="func">str</span>) -> <span class="func">list</span>[<span class="func">dict</span>]:
<span class="string">"""Get weather alerts for a US state."""</span>
data = <span class="keyword">await</span> <span class="func">fetch_from_nws</span>(area)
<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>]]</pre>
</div>
<h4>Input (added by decorator)</h4>
<table>
<thead><tr><th>Parameter</th><th>Type</th><th>Description</th></tr></thead>
<tbody>
<tr><td><code>abstract_domains</code></td><td>str</td><td>Comma-separated column names for control plane. Omit for all columns.</td></tr>
<tr><td><code>mode</code></td><td>str</td><td><code>"async"</code> (default): cache body, return <code>resource_url</code>. <code>"sync"</code>: return body inline.</td></tr>
</tbody>
</table>
<h4>Output (async mode)</h4>
<div class="code-block">
<pre>{
<span class="string">"total_rows"</span>: 8,
<span class="string">"columns"</span>: [<span class="string">"id"</span>, <span class="string">"event"</span>, <span class="string">"severity"</span>, ... <span class="comment">all 30</span>],
<span class="string">"abstract_domains"</span>: [<span class="string">"event"</span>, <span class="string">"severity"</span>],
<span class="string">"body_domains"</span>: [<span class="string">"description"</span>, <span class="string">"instruction"</span>, ... <span class="comment">remaining 28</span>],
<span class="string">"resource_url"</span>: <span class="string">"http://host:port/s2sp/data/TOKEN..."</span>,
<span class="string">"abstract"</span>: [
{<span class="string">"_row_id"</span>: 0, <span class="string">"event"</span>: <span class="string">"Wind Advisory"</span>, <span class="string">"severity"</span>: <span class="string">"Moderate"</span>},
{<span class="string">"_row_id"</span>: 1, <span class="string">"event"</span>: <span class="string">"Flood Watch"</span>, <span class="string">"severity"</span>: <span class="string">"Minor"</span>},
...
]
}</pre>
</div>
<h4>Output (sync mode)</h4>
<div class="code-block">
<pre>{
<span class="string">"total_rows"</span>: 8,
<span class="string">"abstract_domains"</span>: [<span class="string">"event"</span>, <span class="string">"severity"</span>],
<span class="string">"body_domains"</span>: [<span class="string">"description"</span>, ...],
<span class="string">"abstract"</span>: [
{<span class="string">"_row_id"</span>: 0, <span class="string">"event"</span>: <span class="string">"Wind Advisory"</span>, <span class="string">"severity"</span>: <span class="string">"Moderate"</span>},
...
],
<span class="string">"body"</span>: [
{<span class="string">"_row_id"</span>: 0, <span class="string">"description"</span>: <span class="string">"North winds 25-35 mph..."</span>, ...},
...
]
<span class="comment">// No resource_url — body is inline</span>
}</pre>
</div>
<h4>Output (no abstract_domains)</h4>
<p>Returns the raw <code>list[dict]</code> as-is — standard MCP behavior.</p>
<h3>server.run()</h3>
<p>
Start the server as an MCP server over stdio. Compatible with <code>mcp dev</code>
for local development.
</p>
<div class="code-block">
<pre>server.<span class="func">run</span>()</pre>
</div>
<h2>DirectChannel API (used by Consumer Tools)</h2>
<p>
The <code>DirectChannel</code> class provides a static method for consumer servers
to fetch body data directly from a resource server's data plane.
</p>
<h3>DirectChannel.fetch_data()</h3>
<p>
Static method that consumer tools call to fetch rows from the resource
server. Takes the presigned <code>resource_url</code>, optional
<code>row_ids</code>, optional <code>columns</code>, and optional
<code>column_mapping</code> to rename columns.
</p>
<div class="code-block">
<pre><span class="keyword">from</span> mcp_sd.direct_channel <span class="keyword">import</span> DirectChannel
items = <span class="keyword">await</span> DirectChannel.<span class="func">fetch_data</span>(
resource_url, <span class="comment"># Presigned URL from @sd_resource_tool response</span>
row_ids=[0, 2, 5], <span class="comment"># Optional: select rows by _row_id</span>
columns=<span class="keyword">None</span>, <span class="comment"># Optional: select columns (before mapping)</span>
column_mapping={ <span class="comment"># Optional: rename columns after fetch</span>
<span class="string">"event"</span>: <span class="string">"alert_type"</span>,
<span class="string">"areaDesc"</span>: <span class="string">"location"</span>,
},
)</pre>
</div>
<p>
<strong>Note:</strong> The <code>column_mapping</code> parameter in
<code>@sd_consumer_tool()</code> remaps both abstract and body columns
before merging rows by <code>_row_id</code>. This matches
<code>DirectChannel.resolve()</code>, which applies the same mapping
to inline sync bodies and async data-plane fetches.
<code>DirectChannel.remap_columns(rows, mapping)</code> is available
as a standalone utility for advanced use cases.
</p>
<h2>Complete Example: Resource Server</h2>
<p>
A resource server exposes data via <code>@server.sd_resource_tool()</code>. The tool returns
<code>list[dict]</code> and the S2SP decorator handles caching and field filtering
automatically.
</p>
<div class="code-block">
<pre><span class="keyword">from</span> mcp_sd <span class="keyword">import</span> S2SPServer
server = <span class="func">S2SPServer</span>(<span class="string">"weather-server"</span>)
<span class="decorator">@server.sd_resource_tool()</span>
<span class="keyword">async def</span> <span class="func">get_alerts</span>(area: <span class="func">str</span>) -> <span class="func">list</span>[<span class="func">dict</span>]:
<span class="string">"""Get weather alerts for a US state."""</span>
data = <span class="keyword">await</span> <span class="func">fetch_from_nws</span>(area)
<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>()</pre>
</div>
<h2>Complete Example: Consumer Server</h2>
<p>
A consumer server uses <code>@server.sd_consumer_tool()</code>. The decorator
handles fetching, remapping, and merging — your function just receives
merged rows.
</p>
<div class="code-block">
<pre><span class="keyword">from</span> mcp_sd <span class="keyword">import</span> S2SPServer
server = <span class="func">S2SPServer</span>(<span class="string">"stats-server"</span>)
<span class="decorator">@server.sd_consumer_tool()</span>
<span class="keyword">async def</span> <span class="func">draw_chart</span>(rows: <span class="func">list</span>[<span class="func">dict</span>]) -> <span class="func">str</span>:
<span class="comment"># rows = merged abstract + body, columns remapped</span>
<span class="keyword">return</span> <span class="func">generate_chart</span>(rows)
server.<span class="func">run</span>()</pre>
</div>
<h4>Input (added by decorator)</h4>
<table>
<thead><tr><th>Parameter</th><th>Type</th><th>Description</th></tr></thead>
<tbody>
<tr><td><code>abstract_data</code></td><td>str</td><td>JSON array of abstract rows from the agent (must have <code>_row_id</code>).</td></tr>
<tr><td><code>resource_url</code></td><td>str</td><td>Presigned URL from <code>@sd_resource_tool()</code> response (async mode). Leave empty for sync.</td></tr>
<tr><td><code>body_data</code></td><td>str</td><td>JSON array of body rows (sync mode). Leave empty for async.</td></tr>
<tr><td><code>column_mapping</code></td><td>str</td><td>Optional JSON dict to rename columns. E.g. <code>'{"event": "alert_type"}'</code>.</td></tr>
</tbody>
</table>
<h4>What your function receives</h4>
<p>
A single <code>rows: list[dict]</code> argument — the merged result of
abstract + body, joined by <code>_row_id</code>, with columns renamed if
<code>column_mapping</code> was provided. Each row has all columns (both
abstract and body). Example:
</p>
<div class="code-block">
<pre>[
{<span class="string">"_row_id"</span>: 0, <span class="string">"event"</span>: <span class="string">"Wind Advisory"</span>, <span class="string">"severity"</span>: <span class="string">"Moderate"</span>,
<span class="string">"description"</span>: <span class="string">"North winds 25-35 mph..."</span>, <span class="string">"instruction"</span>: <span class="string">"Use caution..."</span>, ...},
{<span class="string">"_row_id"</span>: 2, <span class="string">"event"</span>: <span class="string">"High Wind Warning"</span>, <span class="string">"severity"</span>: <span class="string">"Severe"</span>,
<span class="string">"description"</span>: <span class="string">"Gusts up to 55 mph..."</span>, ...},
]</pre>
</div>
</main>
</div>
<footer class="footer">
<p>MCP-SD / S2SP — An open extension for MCP. <a href="https://github.com/mcp-sd/python-sdk">GitHub</a></p>
</footer>
</body>
</html>