-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathintroduction.html
More file actions
273 lines (249 loc) · 14.8 KB
/
Copy pathintroduction.html
File metadata and controls
273 lines (249 loc) · 14.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>Introduction - 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" class="active">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">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>What is MCP-SD?</h1>
<p>
<strong>MCP-SD</strong> (Selective Disclosure for MCP) is an open protocol pattern for the
<a href="https://modelcontextprotocol.io">Model Context Protocol (MCP)</a>. The agent declares
which <em>attributes</em> of a tool result it wants to see (via an <code>abstract_domains</code>
parameter) within a single tool call — the remaining attributes are withheld from the LLM's
context, shrinking per-turn context cost without changing what the agent can reason about.
</p>
<p>
<strong>S2SP</strong> (Server-to-Server Protocol) is the reference implementation of MCP-SD.
It pairs selective disclosure at the control plane with a dedicated <em>Direct Data Interface</em>
(DDI) at the data plane, so withheld columns flow directly between MCP servers without ever entering
the agent's context. An S2SP server <em>is</em> an MCP server — the <code>S2SPServer</code> class
embeds a FastMCP instance and adds the <code>@server.sd_resource_tool()</code> decorator plus a
local HTTP endpoint for the DDI.
</p>
<h2>The Problem</h2>
<p>
Today, when an AI agent needs to move data between two MCP servers, every byte must flow
through the agent itself. Consider a simple scenario: "Get weather alerts from the
weather server and chart them on the analytics server." Here is what happens without S2SP:
</p>
<ol>
<li>The agent calls the weather server's <code>get_alerts</code> tool and receives hundreds of full alert objects in its context.</li>
<li>The agent picks the alerts it cares about, then calls the analytics server's <code>draw_chart</code> tool, passing full alert data as a tool argument.</li>
<li>The LLM processes every token of that data twice — once on read, once on write — even though it only needed a few fields to make its decision.</li>
</ol>
<p>
This approach has three compounding costs:
</p>
<ul>
<li><strong>Token waste:</strong> Large payloads consume thousands of input and output tokens. Full alert objects with descriptions, coordinates, and metadata burn tokens the agent never reasons over.</li>
<li><strong>Latency:</strong> Full payloads must serialize into the LLM-facing tool result and often back into another tool call. For large tables, this can add avoidable model and serialization latency.</li>
<li><strong>Context saturation:</strong> Bulk data crowds out the reasoning tokens the model needs for its actual job — deciding <em>what</em> to do, not shuttling bytes.</li>
</ul>
<h2>The Solution</h2>
<p>
S2SP solves this by introducing a clean separation between the <strong>control plane</strong>
(abstract fields the agent sees) and the <strong>data plane</strong> (full data that flows
server-to-server):
</p>
<div class="code-block">
<pre><span class="comment"> Without S2SP With S2SP (abstract_domains)</span>
Agent Agent
| |
| 1. get_alerts(area="CA") | 1. get_alerts(area="CA",
v | abstract_domains="event,severity")
Weather Server v
| Weather Server
| 2. full alert objects |
| in context | 2. only event+severity+_row_id
v | + resource_url
Agent v
| Agent (filters by event)
| 3. draw_chart(full data) |
v | 3. draw_chart(abstract_data,
Stats Server | resource_url)
v
Stats Server ---<span class="keyword">POST /s2sp/data</span>---> Weather Server
| (fetches full data directly)
v
Chart generated. Agent never saw full data.</pre>
</div>
<p>
The agent stays in the loop for <em>reasoning and orchestration</em> — it sees only the
abstract fields it needs to make decisions. When another server needs the full data, it fetches
it directly from the resource server's data plane endpoint, bypassing the agent entirely.
</p>
<h2>Key Concepts</h2>
<h3>S2SPServer and @sd_resource_tool()</h3>
<p>
<code>S2SPServer</code> embeds a FastMCP instance — every S2SP server <em>is</em> an MCP
server. You write normal MCP tools that return <code>list[dict]</code>, then decorate them with
<code>@server.sd_resource_tool()</code>. Without any special arguments from the caller, the tool
behaves exactly like a standard MCP tool. But when the agent passes
<code>abstract_domains</code> (a comma-separated list of column names), S2SP activates: only
those columns plus a <code>_row_id</code> integer index are returned to the agent (the abstract).
The remaining columns become the <code>body_domains</code> and are cached on the server's
data plane. A <code>resource_url</code> is returned so another
server can fetch the body data later. An optional <code>mode</code> parameter controls
whether the body is returned inline (<code>"sync"</code>) or only via later data-plane fetch
(<code>"async"</code>, the default).
</p>
<h3>Control Plane</h3>
<p>
The control plane is the agent's view of the data: standard MCP tool calls that return
only the abstract fields specified by <code>abstract_domains</code>. The agent uses these
lightweight results to reason, filter, and decide which records matter — without ever
seeing full payloads. No bulk data crosses the control plane.
</p>
<h3>Data Plane</h3>
<p>
The data plane carries body domains — the full data that the LLM never needs to see.
It operates in two modes:
</p>
<ul>
<li><strong>Async mode</strong>: Body data is cached on the resource server. A downstream
server (e.g., Stats Server) fetches it directly via
<code>POST <resource_url></code> with
<code>{"row_ids": [...], "columns": [...]}</code>.
The agent has no visibility into this channel — data flows server-to-server only.</li>
<li><strong>Sync mode</strong>: Body data is returned inline alongside the abstract in
a single tool response. An S2SP-aware agent SDK moves the body into an in-process
DDI buffer before the tool result is shown to the LLM; only the abstract enters
the LLM context. No server-to-server fetch is needed.</li>
</ul>
<h3>The Complete Flow</h3>
<p>
A typical S2SP interaction works like this:
</p>
<ol>
<li>Agent calls <code>get_alerts(area="CA", abstract_domains="event,severity,urgency,status")</code> on Weather Server.</li>
<li>Weather Server returns only those fields + <code>_row_id</code> for each alert, plus a <code>resource_url</code>. Full data is cached on the server.</li>
<li>Agent examines the abstract results and filters: picks the wind alert <code>_row_id</code> values.</li>
<li>Agent tells Stats Server: <code>draw_chart(abstract_data="[...]",
resource_url="http://...")</code>. The selected <code>_row_id</code> values are
carried in <code>abstract_data</code>.</li>
<li>Stats Server <code>POST</code>s to the presigned <code>resource_url</code> directly (data plane), passing the selected row IDs.</li>
<li>Stats Server receives full alert data, generates the chart. The agent never saw the full data.</li>
</ol>
<h2>The Analogy</h2>
<p>
Think of S2SP like a <strong>manager reading executive summaries</strong>. Without S2SP, the
manager reads every page of every report before deciding what to forward. With S2SP, the
manager sees only a summary (control plane) — event name, severity, status — and
says "send the full wind reports to the analyst." The analyst then pulls the complete
files directly from the filing cabinet (data plane). The manager still decides <em>what</em>
matters and <em>who</em> gets it, but never handles the raw data.
</p>
<h2>Who Benefits?</h2>
<h3>Developers</h3>
<ul>
<li>Decorator-based integration — add <code>@server.sd_resource_tool()</code> to an MCP tool that returns <code>list[dict]</code>.</li>
<li>Standard HTTP data plane — <code>POST <resource_url></code>, no proprietary transport.</li>
<li>Each server is a normal MCP server — <code>mcp dev</code> works for debugging independently.</li>
</ul>
<h3>Agent Builders</h3>
<ul>
<li>Dramatically reduce token costs — the agent only sees abstract fields, not full payloads.</li>
<li>Keep the agent's context window free for reasoning and decision-making.</li>
<li>Standard MCP tool interface — no new protocol to learn, just pass <code>abstract_domains</code>.</li>
</ul>
<h3>End Users</h3>
<ul>
<li>Lower LLM token usage for workflows where the agent only needs selected fields.</li>
<li>Less context pressure from raw tabular payloads.</li>
<li>Clearer separation between orchestration data and downstream processing data.</li>
</ul>
<h2 id="quick-start">Quick Start</h2>
<p>Install the S2SP Python SDK:</p>
<div class="install-block">
<span class="dollar">$</span>
<code>pip install mcp-sd</code>
</div>
<p>
Create an S2SP server with a tool — it is just a normal MCP tool with a decorator:
</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>) -> list[dict]:
<span class="comment"># Just return full data — S2SP handles the rest</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>
<p>
Without <code>abstract_domains</code>, the tool behaves like any standard MCP tool and
returns all fields. But when the agent wants only a summary for reasoning:
</p>
<div class="code-block">
<pre><span class="comment"># Agent calls with abstract_domains to get only the fields it needs</span>
<span class="func">get_alerts</span>(area=<span class="string">"CA"</span>, abstract_domains=<span class="string">"event,severity,headline"</span>)
<span class="comment"># Returns: only event, severity, headline, and _row_id for each alert</span>
<span class="comment"># + resource_url for later data-plane fetch</span>
<span class="comment"># Full data is cached on the Weather Server</span></pre>
</div>
<p>
The agent filters the abstract results, then tells another server to fetch the full data
directly from the resource server:
</p>
<div class="code-block">
<pre><span class="comment"># Agent filters abstract rows, passes them + resource_url to Stats Server:</span>
<span class="func">draw_chart</span>(
abstract_data=<span class="func">json.dumps</span>(wind_rows),
resource_url=<span class="string">"http://weather:9001/s2sp/data/dK7x_..."</span>)
<span class="comment"># Stats Server calls POST /s2sp/data/dK7x_... on Weather Server</span>
<span class="comment"># fetches full body data directly — agent never sees it</span></pre>
</div>
<p>
For a complete walkthrough, see the <a href="sdk.html">Python SDK documentation</a>.
For protocol internals, see <a href="protocol.html">Protocol Design</a>.
</p>
</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>