acp-sdk 0.0.6__py3-none-any.whl → 1.0.0rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- acp_sdk/client/__init__.py +1 -0
- acp_sdk/client/client.py +135 -0
- acp_sdk/models.py +219 -0
- acp_sdk/server/__init__.py +2 -0
- acp_sdk/server/agent.py +32 -0
- acp_sdk/server/bundle.py +133 -0
- acp_sdk/server/context.py +6 -0
- acp_sdk/server/server.py +137 -0
- acp_sdk/server/telemetry.py +45 -0
- acp_sdk/server/utils.py +12 -0
- acp_sdk-1.0.0rc1.dist-info/METADATA +53 -0
- acp_sdk-1.0.0rc1.dist-info/RECORD +15 -0
- acp/__init__.py +0 -138
- acp/cli/__init__.py +0 -6
- acp/cli/claude.py +0 -139
- acp/cli/cli.py +0 -471
- acp/client/__main__.py +0 -79
- acp/client/session.py +0 -372
- acp/client/sse.py +0 -145
- acp/client/stdio.py +0 -153
- acp/server/__init__.py +0 -3
- acp/server/__main__.py +0 -50
- acp/server/highlevel/__init__.py +0 -9
- acp/server/highlevel/agents/__init__.py +0 -5
- acp/server/highlevel/agents/agent_manager.py +0 -110
- acp/server/highlevel/agents/base.py +0 -20
- acp/server/highlevel/agents/templates.py +0 -21
- acp/server/highlevel/context.py +0 -185
- acp/server/highlevel/exceptions.py +0 -25
- acp/server/highlevel/prompts/__init__.py +0 -4
- acp/server/highlevel/prompts/base.py +0 -167
- acp/server/highlevel/prompts/manager.py +0 -50
- acp/server/highlevel/prompts/prompt_manager.py +0 -33
- acp/server/highlevel/resources/__init__.py +0 -23
- acp/server/highlevel/resources/base.py +0 -48
- acp/server/highlevel/resources/resource_manager.py +0 -94
- acp/server/highlevel/resources/templates.py +0 -80
- acp/server/highlevel/resources/types.py +0 -185
- acp/server/highlevel/server.py +0 -705
- acp/server/highlevel/tools/__init__.py +0 -4
- acp/server/highlevel/tools/base.py +0 -83
- acp/server/highlevel/tools/tool_manager.py +0 -53
- acp/server/highlevel/utilities/__init__.py +0 -1
- acp/server/highlevel/utilities/func_metadata.py +0 -210
- acp/server/highlevel/utilities/logging.py +0 -43
- acp/server/highlevel/utilities/types.py +0 -54
- acp/server/lowlevel/__init__.py +0 -3
- acp/server/lowlevel/helper_types.py +0 -9
- acp/server/lowlevel/server.py +0 -643
- acp/server/models.py +0 -17
- acp/server/session.py +0 -315
- acp/server/sse.py +0 -175
- acp/server/stdio.py +0 -83
- acp/server/websocket.py +0 -61
- acp/shared/__init__.py +0 -0
- acp/shared/context.py +0 -14
- acp/shared/exceptions.py +0 -14
- acp/shared/memory.py +0 -87
- acp/shared/progress.py +0 -40
- acp/shared/session.py +0 -413
- acp/shared/version.py +0 -3
- acp/types.py +0 -1258
- acp_sdk-0.0.6.dist-info/METADATA +0 -46
- acp_sdk-0.0.6.dist-info/RECORD +0 -57
- acp_sdk-0.0.6.dist-info/entry_points.txt +0 -2
- acp_sdk-0.0.6.dist-info/licenses/LICENSE +0 -22
- {acp/client → acp_sdk}/__init__.py +0 -0
- {acp → acp_sdk}/py.typed +0 -0
- {acp_sdk-0.0.6.dist-info → acp_sdk-1.0.0rc1.dist-info}/WHEEL +0 -0
acp/server/session.py
DELETED
@@ -1,315 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
ServerSession Module
|
3
|
-
|
4
|
-
This module provides the ServerSession class, which manages communication between the
|
5
|
-
server and client in the MCP (Model Context Protocol) framework. It is most commonly
|
6
|
-
used in MCP servers to interact with the client.
|
7
|
-
|
8
|
-
Common usage pattern:
|
9
|
-
```
|
10
|
-
server = Server(name)
|
11
|
-
|
12
|
-
@server.call_tool()
|
13
|
-
async def handle_tool_call(ctx: RequestContext, arguments: dict[str, Any]) -> Any:
|
14
|
-
# Check client capabilities before proceeding
|
15
|
-
if ctx.session.check_client_capability(
|
16
|
-
types.ClientCapabilities(experimental={"advanced_tools": dict()})
|
17
|
-
):
|
18
|
-
# Perform advanced tool operations
|
19
|
-
result = await perform_advanced_tool_operation(arguments)
|
20
|
-
else:
|
21
|
-
# Fall back to basic tool operations
|
22
|
-
result = await perform_basic_tool_operation(arguments)
|
23
|
-
|
24
|
-
return result
|
25
|
-
|
26
|
-
@server.list_prompts()
|
27
|
-
async def handle_list_prompts(ctx: RequestContext) -> list[types.Prompt]:
|
28
|
-
# Access session for any necessary checks or operations
|
29
|
-
if ctx.session.client_params:
|
30
|
-
# Customize prompts based on client initialization parameters
|
31
|
-
return generate_custom_prompts(ctx.session.client_params)
|
32
|
-
else:
|
33
|
-
return default_prompts
|
34
|
-
```
|
35
|
-
|
36
|
-
The ServerSession class is typically used internally by the Server class and should not
|
37
|
-
be instantiated directly by users of the MCP framework.
|
38
|
-
"""
|
39
|
-
|
40
|
-
from enum import Enum
|
41
|
-
from typing import Any
|
42
|
-
|
43
|
-
import anyio
|
44
|
-
import anyio.lowlevel
|
45
|
-
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
46
|
-
from pydantic import AnyUrl
|
47
|
-
|
48
|
-
import acp.types as types
|
49
|
-
from acp.server.models import InitializationOptions
|
50
|
-
from acp.shared.session import (
|
51
|
-
BaseSession,
|
52
|
-
RequestResponder,
|
53
|
-
)
|
54
|
-
|
55
|
-
|
56
|
-
class InitializationState(Enum):
|
57
|
-
NotInitialized = 1
|
58
|
-
Initializing = 2
|
59
|
-
Initialized = 3
|
60
|
-
|
61
|
-
|
62
|
-
class ServerSession(
|
63
|
-
BaseSession[
|
64
|
-
types.ServerRequest,
|
65
|
-
types.ServerNotification,
|
66
|
-
types.ServerResult,
|
67
|
-
types.ClientRequest,
|
68
|
-
types.ClientNotification,
|
69
|
-
]
|
70
|
-
):
|
71
|
-
_initialized: InitializationState = InitializationState.NotInitialized
|
72
|
-
_client_params: types.InitializeRequestParams | None = None
|
73
|
-
|
74
|
-
def __init__(
|
75
|
-
self,
|
76
|
-
read_stream: MemoryObjectReceiveStream[types.JSONRPCMessage | Exception],
|
77
|
-
write_stream: MemoryObjectSendStream[types.JSONRPCMessage],
|
78
|
-
init_options: InitializationOptions,
|
79
|
-
) -> None:
|
80
|
-
super().__init__(
|
81
|
-
read_stream, write_stream, types.ClientRequest, types.ClientNotification
|
82
|
-
)
|
83
|
-
self._initialization_state = InitializationState.NotInitialized
|
84
|
-
self._init_options = init_options
|
85
|
-
|
86
|
-
@property
|
87
|
-
def client_params(self) -> types.InitializeRequestParams | None:
|
88
|
-
return self._client_params
|
89
|
-
|
90
|
-
def check_client_capability(self, capability: types.ClientCapabilities) -> bool:
|
91
|
-
"""Check if the client supports a specific capability."""
|
92
|
-
if self._client_params is None:
|
93
|
-
return False
|
94
|
-
|
95
|
-
# Get client capabilities from initialization params
|
96
|
-
client_caps = self._client_params.capabilities
|
97
|
-
|
98
|
-
# Check each specified capability in the passed in capability object
|
99
|
-
if capability.roots is not None:
|
100
|
-
if client_caps.roots is None:
|
101
|
-
return False
|
102
|
-
if capability.roots.listChanged and not client_caps.roots.listChanged:
|
103
|
-
return False
|
104
|
-
|
105
|
-
if capability.sampling is not None:
|
106
|
-
if client_caps.sampling is None:
|
107
|
-
return False
|
108
|
-
|
109
|
-
if capability.experimental is not None:
|
110
|
-
if client_caps.experimental is None:
|
111
|
-
return False
|
112
|
-
# Check each experimental capability
|
113
|
-
for exp_key, exp_value in capability.experimental.items():
|
114
|
-
if (
|
115
|
-
exp_key not in client_caps.experimental
|
116
|
-
or client_caps.experimental[exp_key] != exp_value
|
117
|
-
):
|
118
|
-
return False
|
119
|
-
|
120
|
-
return True
|
121
|
-
|
122
|
-
async def _received_request(
|
123
|
-
self, responder: RequestResponder[types.ClientRequest, types.ServerResult]
|
124
|
-
):
|
125
|
-
match responder.request.root:
|
126
|
-
case types.InitializeRequest(params=params):
|
127
|
-
self._initialization_state = InitializationState.Initializing
|
128
|
-
self._client_params = params
|
129
|
-
async with responder:
|
130
|
-
await responder.respond(
|
131
|
-
types.ServerResult(
|
132
|
-
types.InitializeResult(
|
133
|
-
protocolVersion=types.LATEST_PROTOCOL_VERSION,
|
134
|
-
capabilities=self._init_options.capabilities,
|
135
|
-
serverInfo=types.Implementation(
|
136
|
-
name=self._init_options.server_name,
|
137
|
-
version=self._init_options.server_version,
|
138
|
-
),
|
139
|
-
instructions=self._init_options.instructions,
|
140
|
-
)
|
141
|
-
)
|
142
|
-
)
|
143
|
-
case _:
|
144
|
-
if self._initialization_state != InitializationState.Initialized:
|
145
|
-
raise RuntimeError(
|
146
|
-
"Received request before initialization was complete"
|
147
|
-
)
|
148
|
-
|
149
|
-
async def _received_notification(
|
150
|
-
self, notification: types.ClientNotification
|
151
|
-
) -> None:
|
152
|
-
# Need this to avoid ASYNC910
|
153
|
-
await anyio.lowlevel.checkpoint()
|
154
|
-
match notification.root:
|
155
|
-
case types.InitializedNotification():
|
156
|
-
self._initialization_state = InitializationState.Initialized
|
157
|
-
case _:
|
158
|
-
if self._initialization_state != InitializationState.Initialized:
|
159
|
-
raise RuntimeError(
|
160
|
-
"Received notification before initialization was complete"
|
161
|
-
)
|
162
|
-
|
163
|
-
async def send_log_message(
|
164
|
-
self, level: types.LoggingLevel, data: Any, logger: str | None = None
|
165
|
-
) -> None:
|
166
|
-
"""Send a log message notification."""
|
167
|
-
await self.send_notification(
|
168
|
-
types.ServerNotification(
|
169
|
-
types.LoggingMessageNotification(
|
170
|
-
method="notifications/message",
|
171
|
-
params=types.LoggingMessageNotificationParams(
|
172
|
-
level=level,
|
173
|
-
data=data,
|
174
|
-
logger=logger,
|
175
|
-
),
|
176
|
-
)
|
177
|
-
)
|
178
|
-
)
|
179
|
-
|
180
|
-
async def send_resource_updated(self, uri: AnyUrl) -> None:
|
181
|
-
"""Send a resource updated notification."""
|
182
|
-
await self.send_notification(
|
183
|
-
types.ServerNotification(
|
184
|
-
types.ResourceUpdatedNotification(
|
185
|
-
method="notifications/resources/updated",
|
186
|
-
params=types.ResourceUpdatedNotificationParams(uri=uri),
|
187
|
-
)
|
188
|
-
)
|
189
|
-
)
|
190
|
-
|
191
|
-
async def create_message(
|
192
|
-
self,
|
193
|
-
messages: list[types.SamplingMessage],
|
194
|
-
*,
|
195
|
-
max_tokens: int,
|
196
|
-
system_prompt: str | None = None,
|
197
|
-
include_context: types.IncludeContext | None = None,
|
198
|
-
temperature: float | None = None,
|
199
|
-
stop_sequences: list[str] | None = None,
|
200
|
-
metadata: dict[str, Any] | None = None,
|
201
|
-
model_preferences: types.ModelPreferences | None = None,
|
202
|
-
) -> types.CreateMessageResult:
|
203
|
-
"""Send a sampling/create_message request."""
|
204
|
-
return await self.send_request(
|
205
|
-
types.ServerRequest(
|
206
|
-
types.CreateMessageRequest(
|
207
|
-
method="sampling/createMessage",
|
208
|
-
params=types.CreateMessageRequestParams(
|
209
|
-
messages=messages,
|
210
|
-
systemPrompt=system_prompt,
|
211
|
-
includeContext=include_context,
|
212
|
-
temperature=temperature,
|
213
|
-
maxTokens=max_tokens,
|
214
|
-
stopSequences=stop_sequences,
|
215
|
-
metadata=metadata,
|
216
|
-
modelPreferences=model_preferences,
|
217
|
-
),
|
218
|
-
)
|
219
|
-
),
|
220
|
-
types.CreateMessageResult,
|
221
|
-
)
|
222
|
-
|
223
|
-
async def list_roots(self) -> types.ListRootsResult:
|
224
|
-
"""Send a roots/list request."""
|
225
|
-
return await self.send_request(
|
226
|
-
types.ServerRequest(
|
227
|
-
types.ListRootsRequest(
|
228
|
-
method="roots/list",
|
229
|
-
)
|
230
|
-
),
|
231
|
-
types.ListRootsResult,
|
232
|
-
)
|
233
|
-
|
234
|
-
async def send_ping(self) -> types.EmptyResult:
|
235
|
-
"""Send a ping request."""
|
236
|
-
return await self.send_request(
|
237
|
-
types.ServerRequest(
|
238
|
-
types.PingRequest(
|
239
|
-
method="ping",
|
240
|
-
)
|
241
|
-
),
|
242
|
-
types.EmptyResult,
|
243
|
-
)
|
244
|
-
|
245
|
-
async def send_progress_notification(
|
246
|
-
self, progress_token: str | int, progress: float, total: float | None = None
|
247
|
-
) -> None:
|
248
|
-
"""Send a progress notification."""
|
249
|
-
await self.send_notification(
|
250
|
-
types.ServerNotification(
|
251
|
-
types.ProgressNotification(
|
252
|
-
method="notifications/progress",
|
253
|
-
params=types.ProgressNotificationParams(
|
254
|
-
progressToken=progress_token,
|
255
|
-
progress=progress,
|
256
|
-
total=total,
|
257
|
-
),
|
258
|
-
)
|
259
|
-
)
|
260
|
-
)
|
261
|
-
|
262
|
-
async def send_resource_list_changed(self) -> None:
|
263
|
-
"""Send a resource list changed notification."""
|
264
|
-
await self.send_notification(
|
265
|
-
types.ServerNotification(
|
266
|
-
types.ResourceListChangedNotification(
|
267
|
-
method="notifications/resources/list_changed",
|
268
|
-
)
|
269
|
-
)
|
270
|
-
)
|
271
|
-
|
272
|
-
async def send_tool_list_changed(self) -> None:
|
273
|
-
"""Send a tool list changed notification."""
|
274
|
-
await self.send_notification(
|
275
|
-
types.ServerNotification(
|
276
|
-
types.ToolListChangedNotification(
|
277
|
-
method="notifications/tools/list_changed",
|
278
|
-
)
|
279
|
-
)
|
280
|
-
)
|
281
|
-
|
282
|
-
async def send_prompt_list_changed(self) -> None:
|
283
|
-
"""Send a prompt list changed notification."""
|
284
|
-
await self.send_notification(
|
285
|
-
types.ServerNotification(
|
286
|
-
types.PromptListChangedNotification(
|
287
|
-
method="notifications/prompts/list_changed",
|
288
|
-
)
|
289
|
-
)
|
290
|
-
)
|
291
|
-
|
292
|
-
async def send_agents_list_changed(self) -> None:
|
293
|
-
"""Send an agent list changed notification."""
|
294
|
-
await self.send_notification(
|
295
|
-
types.ServerNotification(
|
296
|
-
types.AgentListChangedNotification(
|
297
|
-
method="notifications/agents/list_changed",
|
298
|
-
)
|
299
|
-
)
|
300
|
-
)
|
301
|
-
|
302
|
-
async def send_agent_run_progress(
|
303
|
-
self, progress_token: str | int, delta: dict[str, Any]
|
304
|
-
) -> None:
|
305
|
-
"""Send an agent run progress notification."""
|
306
|
-
await self.send_notification(
|
307
|
-
types.ServerNotification(
|
308
|
-
types.AgentRunProgressNotification(
|
309
|
-
method="notifications/agents/run/progress",
|
310
|
-
params=types.AgentRunProgressNotificationParams(
|
311
|
-
progressToken=progress_token, delta=delta
|
312
|
-
),
|
313
|
-
)
|
314
|
-
)
|
315
|
-
)
|
acp/server/sse.py
DELETED
@@ -1,175 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
SSE Server Transport Module
|
3
|
-
|
4
|
-
This module implements a Server-Sent Events (SSE) transport layer for MCP servers.
|
5
|
-
|
6
|
-
Example usage:
|
7
|
-
```
|
8
|
-
# Create an SSE transport at an endpoint
|
9
|
-
sse = SseServerTransport("/messages/")
|
10
|
-
|
11
|
-
# Create Starlette routes for SSE and message handling
|
12
|
-
routes = [
|
13
|
-
Route("/sse", endpoint=handle_sse),
|
14
|
-
Mount("/messages/", app=sse.handle_post_message),
|
15
|
-
]
|
16
|
-
|
17
|
-
# Define handler functions
|
18
|
-
async def handle_sse(request):
|
19
|
-
async with sse.connect_sse(
|
20
|
-
request.scope, request.receive, request._send
|
21
|
-
) as streams:
|
22
|
-
await app.run(
|
23
|
-
streams[0], streams[1], app.create_initialization_options()
|
24
|
-
)
|
25
|
-
|
26
|
-
# Create and run Starlette app
|
27
|
-
starlette_app = Starlette(routes=routes)
|
28
|
-
uvicorn.run(starlette_app, host="0.0.0.0", port=port)
|
29
|
-
```
|
30
|
-
|
31
|
-
See SseServerTransport class documentation for more details.
|
32
|
-
"""
|
33
|
-
|
34
|
-
import logging
|
35
|
-
from contextlib import asynccontextmanager
|
36
|
-
from typing import Any
|
37
|
-
from urllib.parse import quote
|
38
|
-
from uuid import UUID, uuid4
|
39
|
-
|
40
|
-
import anyio
|
41
|
-
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
42
|
-
from pydantic import ValidationError
|
43
|
-
from sse_starlette import EventSourceResponse
|
44
|
-
from starlette.requests import Request
|
45
|
-
from starlette.responses import Response
|
46
|
-
from starlette.types import Receive, Scope, Send
|
47
|
-
|
48
|
-
import acp.types as types
|
49
|
-
|
50
|
-
logger = logging.getLogger(__name__)
|
51
|
-
|
52
|
-
|
53
|
-
class SseServerTransport:
|
54
|
-
"""
|
55
|
-
SSE server transport for MCP. This class provides _two_ ASGI applications,
|
56
|
-
suitable to be used with a framework like Starlette and a server like Hypercorn:
|
57
|
-
|
58
|
-
1. connect_sse() is an ASGI application which receives incoming GET requests,
|
59
|
-
and sets up a new SSE stream to send server messages to the client.
|
60
|
-
2. handle_post_message() is an ASGI application which receives incoming POST
|
61
|
-
requests, which should contain client messages that link to a
|
62
|
-
previously-established SSE session.
|
63
|
-
"""
|
64
|
-
|
65
|
-
_endpoint: str
|
66
|
-
_read_stream_writers: dict[
|
67
|
-
UUID, MemoryObjectSendStream[types.JSONRPCMessage | Exception]
|
68
|
-
]
|
69
|
-
|
70
|
-
def __init__(self, endpoint: str) -> None:
|
71
|
-
"""
|
72
|
-
Creates a new SSE server transport, which will direct the client to POST
|
73
|
-
messages to the relative or absolute URL given.
|
74
|
-
"""
|
75
|
-
|
76
|
-
super().__init__()
|
77
|
-
self._endpoint = endpoint
|
78
|
-
self._read_stream_writers = {}
|
79
|
-
logger.debug(f"SseServerTransport initialized with endpoint: {endpoint}")
|
80
|
-
|
81
|
-
@asynccontextmanager
|
82
|
-
async def connect_sse(self, scope: Scope, receive: Receive, send: Send):
|
83
|
-
if scope["type"] != "http":
|
84
|
-
logger.error("connect_sse received non-HTTP request")
|
85
|
-
raise ValueError("connect_sse can only handle HTTP requests")
|
86
|
-
|
87
|
-
logger.debug("Setting up SSE connection")
|
88
|
-
read_stream: MemoryObjectReceiveStream[types.JSONRPCMessage | Exception]
|
89
|
-
read_stream_writer: MemoryObjectSendStream[types.JSONRPCMessage | Exception]
|
90
|
-
|
91
|
-
write_stream: MemoryObjectSendStream[types.JSONRPCMessage]
|
92
|
-
write_stream_reader: MemoryObjectReceiveStream[types.JSONRPCMessage]
|
93
|
-
|
94
|
-
read_stream_writer, read_stream = anyio.create_memory_object_stream(0)
|
95
|
-
write_stream, write_stream_reader = anyio.create_memory_object_stream(0)
|
96
|
-
|
97
|
-
session_id = uuid4()
|
98
|
-
session_uri = f"{quote(self._endpoint)}?session_id={session_id.hex}"
|
99
|
-
self._read_stream_writers[session_id] = read_stream_writer
|
100
|
-
logger.debug(f"Created new session with ID: {session_id}")
|
101
|
-
|
102
|
-
sse_stream_writer, sse_stream_reader = anyio.create_memory_object_stream[
|
103
|
-
dict[str, Any]
|
104
|
-
](0)
|
105
|
-
|
106
|
-
async def sse_writer():
|
107
|
-
logger.debug("Starting SSE writer")
|
108
|
-
async with sse_stream_writer, write_stream_reader:
|
109
|
-
await sse_stream_writer.send({"event": "endpoint", "data": session_uri})
|
110
|
-
logger.debug(f"Sent endpoint event: {session_uri}")
|
111
|
-
|
112
|
-
async for message in write_stream_reader:
|
113
|
-
logger.debug(f"Sending message via SSE: {message}")
|
114
|
-
await sse_stream_writer.send(
|
115
|
-
{
|
116
|
-
"event": "message",
|
117
|
-
"data": message.model_dump_json(
|
118
|
-
by_alias=True, exclude_none=True
|
119
|
-
),
|
120
|
-
}
|
121
|
-
)
|
122
|
-
|
123
|
-
async with anyio.create_task_group() as tg:
|
124
|
-
response = EventSourceResponse(
|
125
|
-
content=sse_stream_reader, data_sender_callable=sse_writer
|
126
|
-
)
|
127
|
-
logger.debug("Starting SSE response task")
|
128
|
-
tg.start_soon(response, scope, receive, send)
|
129
|
-
|
130
|
-
logger.debug("Yielding read and write streams")
|
131
|
-
yield (read_stream, write_stream)
|
132
|
-
|
133
|
-
async def handle_post_message(
|
134
|
-
self, scope: Scope, receive: Receive, send: Send
|
135
|
-
) -> None:
|
136
|
-
logger.debug("Handling POST message")
|
137
|
-
request = Request(scope, receive)
|
138
|
-
|
139
|
-
session_id_param = request.query_params.get("session_id")
|
140
|
-
if session_id_param is None:
|
141
|
-
logger.warning("Received request without session_id")
|
142
|
-
response = Response("session_id is required", status_code=400)
|
143
|
-
return await response(scope, receive, send)
|
144
|
-
|
145
|
-
try:
|
146
|
-
session_id = UUID(hex=session_id_param)
|
147
|
-
logger.debug(f"Parsed session ID: {session_id}")
|
148
|
-
except ValueError:
|
149
|
-
logger.warning(f"Received invalid session ID: {session_id_param}")
|
150
|
-
response = Response("Invalid session ID", status_code=400)
|
151
|
-
return await response(scope, receive, send)
|
152
|
-
|
153
|
-
writer = self._read_stream_writers.get(session_id)
|
154
|
-
if not writer:
|
155
|
-
logger.warning(f"Could not find session for ID: {session_id}")
|
156
|
-
response = Response("Could not find session", status_code=404)
|
157
|
-
return await response(scope, receive, send)
|
158
|
-
|
159
|
-
json = await request.json()
|
160
|
-
logger.debug(f"Received JSON: {json}")
|
161
|
-
|
162
|
-
try:
|
163
|
-
message = types.JSONRPCMessage.model_validate(json)
|
164
|
-
logger.debug(f"Validated client message: {message}")
|
165
|
-
except ValidationError as err:
|
166
|
-
logger.error(f"Failed to parse message: {err}")
|
167
|
-
response = Response("Could not parse message", status_code=400)
|
168
|
-
await response(scope, receive, send)
|
169
|
-
await writer.send(err)
|
170
|
-
return
|
171
|
-
|
172
|
-
logger.debug(f"Sending message to writer: {message}")
|
173
|
-
response = Response("Accepted", status_code=202)
|
174
|
-
await response(scope, receive, send)
|
175
|
-
await writer.send(message)
|
acp/server/stdio.py
DELETED
@@ -1,83 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Stdio Server Transport Module
|
3
|
-
|
4
|
-
This module provides functionality for creating an stdio-based transport layer
|
5
|
-
that can be used to communicate with an MCP client through standard input/output
|
6
|
-
streams.
|
7
|
-
|
8
|
-
Example usage:
|
9
|
-
```
|
10
|
-
async def run_server():
|
11
|
-
async with stdio_server() as (read_stream, write_stream):
|
12
|
-
# read_stream contains incoming JSONRPCMessages from stdin
|
13
|
-
# write_stream allows sending JSONRPCMessages to stdout
|
14
|
-
server = await create_my_server()
|
15
|
-
await server.run(read_stream, write_stream, init_options)
|
16
|
-
|
17
|
-
anyio.run(run_server)
|
18
|
-
```
|
19
|
-
"""
|
20
|
-
|
21
|
-
import sys
|
22
|
-
from contextlib import asynccontextmanager
|
23
|
-
|
24
|
-
import anyio
|
25
|
-
import anyio.lowlevel
|
26
|
-
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
27
|
-
|
28
|
-
import acp.types as types
|
29
|
-
|
30
|
-
|
31
|
-
@asynccontextmanager
|
32
|
-
async def stdio_server(
|
33
|
-
stdin: anyio.AsyncFile[str] | None = None,
|
34
|
-
stdout: anyio.AsyncFile[str] | None = None,
|
35
|
-
):
|
36
|
-
"""
|
37
|
-
Server transport for stdio: this communicates with an MCP client by reading
|
38
|
-
from the current process' stdin and writing to stdout.
|
39
|
-
"""
|
40
|
-
# Purposely not using context managers for these, as we don't want to close
|
41
|
-
# standard process handles.
|
42
|
-
if not stdin:
|
43
|
-
stdin = anyio.wrap_file(sys.stdin)
|
44
|
-
if not stdout:
|
45
|
-
stdout = anyio.wrap_file(sys.stdout)
|
46
|
-
|
47
|
-
read_stream: MemoryObjectReceiveStream[types.JSONRPCMessage | Exception]
|
48
|
-
read_stream_writer: MemoryObjectSendStream[types.JSONRPCMessage | Exception]
|
49
|
-
|
50
|
-
write_stream: MemoryObjectSendStream[types.JSONRPCMessage]
|
51
|
-
write_stream_reader: MemoryObjectReceiveStream[types.JSONRPCMessage]
|
52
|
-
|
53
|
-
read_stream_writer, read_stream = anyio.create_memory_object_stream(0)
|
54
|
-
write_stream, write_stream_reader = anyio.create_memory_object_stream(0)
|
55
|
-
|
56
|
-
async def stdin_reader():
|
57
|
-
try:
|
58
|
-
async with read_stream_writer:
|
59
|
-
async for line in stdin:
|
60
|
-
try:
|
61
|
-
message = types.JSONRPCMessage.model_validate_json(line)
|
62
|
-
except Exception as exc:
|
63
|
-
await read_stream_writer.send(exc)
|
64
|
-
continue
|
65
|
-
|
66
|
-
await read_stream_writer.send(message)
|
67
|
-
except anyio.ClosedResourceError:
|
68
|
-
await anyio.lowlevel.checkpoint()
|
69
|
-
|
70
|
-
async def stdout_writer():
|
71
|
-
try:
|
72
|
-
async with write_stream_reader:
|
73
|
-
async for message in write_stream_reader:
|
74
|
-
json = message.model_dump_json(by_alias=True, exclude_none=True)
|
75
|
-
await stdout.write(json + "\n")
|
76
|
-
await stdout.flush()
|
77
|
-
except anyio.ClosedResourceError:
|
78
|
-
await anyio.lowlevel.checkpoint()
|
79
|
-
|
80
|
-
async with anyio.create_task_group() as tg:
|
81
|
-
tg.start_soon(stdin_reader)
|
82
|
-
tg.start_soon(stdout_writer)
|
83
|
-
yield read_stream, write_stream
|
acp/server/websocket.py
DELETED
@@ -1,61 +0,0 @@
|
|
1
|
-
import logging
|
2
|
-
from contextlib import asynccontextmanager
|
3
|
-
|
4
|
-
import anyio
|
5
|
-
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
6
|
-
from starlette.types import Receive, Scope, Send
|
7
|
-
from starlette.websockets import WebSocket
|
8
|
-
|
9
|
-
import acp.types as types
|
10
|
-
|
11
|
-
logger = logging.getLogger(__name__)
|
12
|
-
|
13
|
-
|
14
|
-
@asynccontextmanager
|
15
|
-
async def websocket_server(scope: Scope, receive: Receive, send: Send):
|
16
|
-
"""
|
17
|
-
WebSocket server transport for MCP. This is an ASGI application, suitable to be
|
18
|
-
used with a framework like Starlette and a server like Hypercorn.
|
19
|
-
"""
|
20
|
-
|
21
|
-
websocket = WebSocket(scope, receive, send)
|
22
|
-
await websocket.accept(subprotocol="mcp")
|
23
|
-
|
24
|
-
read_stream: MemoryObjectReceiveStream[types.JSONRPCMessage | Exception]
|
25
|
-
read_stream_writer: MemoryObjectSendStream[types.JSONRPCMessage | Exception]
|
26
|
-
|
27
|
-
write_stream: MemoryObjectSendStream[types.JSONRPCMessage]
|
28
|
-
write_stream_reader: MemoryObjectReceiveStream[types.JSONRPCMessage]
|
29
|
-
|
30
|
-
read_stream_writer, read_stream = anyio.create_memory_object_stream(0)
|
31
|
-
write_stream, write_stream_reader = anyio.create_memory_object_stream(0)
|
32
|
-
|
33
|
-
async def ws_reader():
|
34
|
-
try:
|
35
|
-
async with read_stream_writer:
|
36
|
-
async for message in websocket.iter_json():
|
37
|
-
try:
|
38
|
-
client_message = types.JSONRPCMessage.model_validate(message)
|
39
|
-
except Exception as exc:
|
40
|
-
await read_stream_writer.send(exc)
|
41
|
-
continue
|
42
|
-
|
43
|
-
await read_stream_writer.send(client_message)
|
44
|
-
except anyio.ClosedResourceError:
|
45
|
-
await websocket.close()
|
46
|
-
|
47
|
-
async def ws_writer():
|
48
|
-
try:
|
49
|
-
async with write_stream_reader:
|
50
|
-
async for message in write_stream_reader:
|
51
|
-
obj = message.model_dump(
|
52
|
-
by_alias=True, mode="json", exclude_none=True
|
53
|
-
)
|
54
|
-
await websocket.send_json(obj)
|
55
|
-
except anyio.ClosedResourceError:
|
56
|
-
await websocket.close()
|
57
|
-
|
58
|
-
async with anyio.create_task_group() as tg:
|
59
|
-
tg.start_soon(ws_reader)
|
60
|
-
tg.start_soon(ws_writer)
|
61
|
-
yield (read_stream, write_stream)
|
acp/shared/__init__.py
DELETED
File without changes
|
acp/shared/context.py
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
from dataclasses import dataclass
|
2
|
-
from typing import Generic, TypeVar
|
3
|
-
|
4
|
-
from acp.shared.session import BaseSession
|
5
|
-
from acp.types import RequestId, RequestParams
|
6
|
-
|
7
|
-
SessionT = TypeVar("SessionT", bound=BaseSession)
|
8
|
-
|
9
|
-
|
10
|
-
@dataclass
|
11
|
-
class RequestContext(Generic[SessionT]):
|
12
|
-
request_id: RequestId
|
13
|
-
meta: RequestParams.Meta | None
|
14
|
-
session: SessionT
|
acp/shared/exceptions.py
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
from acp.types import ErrorData
|
2
|
-
|
3
|
-
|
4
|
-
class McpError(Exception):
|
5
|
-
"""
|
6
|
-
Exception type raised when an error arrives over an MCP connection.
|
7
|
-
"""
|
8
|
-
|
9
|
-
error: ErrorData
|
10
|
-
|
11
|
-
def __init__(self, error: ErrorData):
|
12
|
-
"""Initialize McpError."""
|
13
|
-
super().__init__(error.message)
|
14
|
-
self.error = error
|