wcgw 5.0.2__py3-none-any.whl → 5.1.0__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.
Potentially problematic release.
This version of wcgw might be problematic. Click here for more details.
- wcgw/client/bash_state/bash_state.py +2 -2
- wcgw/client/file_ops/diff_edit.py +14 -2
- wcgw/client/file_ops/extensions.py +137 -0
- wcgw/client/file_ops/search_replace.py +1 -2
- wcgw/client/mcp_server/server.py +10 -18
- wcgw/client/memory.py +4 -1
- wcgw/client/tool_prompts.py +16 -15
- wcgw/client/tools.py +95 -38
- {wcgw-5.0.2.dist-info → wcgw-5.1.0.dist-info}/METADATA +2 -1
- wcgw-5.1.0.dist-info/RECORD +37 -0
- wcgw_cli/anthropic_client.py +8 -4
- wcgw_cli/openai_client.py +7 -3
- mcp_wcgw/__init__.py +0 -114
- mcp_wcgw/client/__init__.py +0 -0
- mcp_wcgw/client/__main__.py +0 -79
- mcp_wcgw/client/session.py +0 -234
- mcp_wcgw/client/sse.py +0 -142
- mcp_wcgw/client/stdio.py +0 -128
- mcp_wcgw/py.typed +0 -0
- mcp_wcgw/server/__init__.py +0 -514
- mcp_wcgw/server/__main__.py +0 -50
- mcp_wcgw/server/models.py +0 -16
- mcp_wcgw/server/session.py +0 -288
- mcp_wcgw/server/sse.py +0 -178
- mcp_wcgw/server/stdio.py +0 -83
- mcp_wcgw/server/websocket.py +0 -61
- mcp_wcgw/shared/__init__.py +0 -0
- mcp_wcgw/shared/context.py +0 -14
- mcp_wcgw/shared/exceptions.py +0 -9
- mcp_wcgw/shared/memory.py +0 -87
- mcp_wcgw/shared/progress.py +0 -40
- mcp_wcgw/shared/session.py +0 -288
- mcp_wcgw/shared/version.py +0 -3
- mcp_wcgw/types.py +0 -1060
- wcgw-5.0.2.dist-info/RECORD +0 -58
- {wcgw-5.0.2.dist-info → wcgw-5.1.0.dist-info}/WHEEL +0 -0
- {wcgw-5.0.2.dist-info → wcgw-5.1.0.dist-info}/entry_points.txt +0 -0
- {wcgw-5.0.2.dist-info → wcgw-5.1.0.dist-info}/licenses/LICENSE +0 -0
mcp_wcgw/shared/memory.py
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
In-memory transports
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from contextlib import asynccontextmanager
|
|
6
|
-
from datetime import timedelta
|
|
7
|
-
from typing import AsyncGenerator
|
|
8
|
-
|
|
9
|
-
import anyio
|
|
10
|
-
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
|
11
|
-
|
|
12
|
-
from mcp_wcgw.client.session import ClientSession
|
|
13
|
-
from mcp_wcgw.server import Server
|
|
14
|
-
from mcp_wcgw.types import JSONRPCMessage
|
|
15
|
-
|
|
16
|
-
MessageStream = tuple[
|
|
17
|
-
MemoryObjectReceiveStream[JSONRPCMessage | Exception],
|
|
18
|
-
MemoryObjectSendStream[JSONRPCMessage],
|
|
19
|
-
]
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@asynccontextmanager
|
|
23
|
-
async def create_client_server_memory_streams() -> (
|
|
24
|
-
AsyncGenerator[tuple[MessageStream, MessageStream], None]
|
|
25
|
-
):
|
|
26
|
-
"""
|
|
27
|
-
Creates a pair of bidirectional memory streams for client-server communication.
|
|
28
|
-
|
|
29
|
-
Returns:
|
|
30
|
-
A tuple of (client_streams, server_streams) where each is a tuple of
|
|
31
|
-
(read_stream, write_stream)
|
|
32
|
-
"""
|
|
33
|
-
# Create streams for both directions
|
|
34
|
-
server_to_client_send, server_to_client_receive = anyio.create_memory_object_stream[
|
|
35
|
-
JSONRPCMessage | Exception
|
|
36
|
-
](1)
|
|
37
|
-
client_to_server_send, client_to_server_receive = anyio.create_memory_object_stream[
|
|
38
|
-
JSONRPCMessage | Exception
|
|
39
|
-
](1)
|
|
40
|
-
|
|
41
|
-
client_streams = (server_to_client_receive, client_to_server_send)
|
|
42
|
-
server_streams = (client_to_server_receive, server_to_client_send)
|
|
43
|
-
|
|
44
|
-
async with (
|
|
45
|
-
server_to_client_receive,
|
|
46
|
-
client_to_server_send,
|
|
47
|
-
client_to_server_receive,
|
|
48
|
-
server_to_client_send,
|
|
49
|
-
):
|
|
50
|
-
yield client_streams, server_streams
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@asynccontextmanager
|
|
54
|
-
async def create_connected_server_and_client_session(
|
|
55
|
-
server: Server,
|
|
56
|
-
read_timeout_seconds: timedelta | None = None,
|
|
57
|
-
raise_exceptions: bool = False,
|
|
58
|
-
) -> AsyncGenerator[ClientSession, None]:
|
|
59
|
-
"""Creates a ClientSession that is connected to a running MCP server."""
|
|
60
|
-
async with create_client_server_memory_streams() as (
|
|
61
|
-
client_streams,
|
|
62
|
-
server_streams,
|
|
63
|
-
):
|
|
64
|
-
client_read, client_write = client_streams
|
|
65
|
-
server_read, server_write = server_streams
|
|
66
|
-
|
|
67
|
-
# Create a cancel scope for the server task
|
|
68
|
-
async with anyio.create_task_group() as tg:
|
|
69
|
-
tg.start_soon(
|
|
70
|
-
lambda: server.run(
|
|
71
|
-
server_read,
|
|
72
|
-
server_write,
|
|
73
|
-
server.create_initialization_options(),
|
|
74
|
-
raise_exceptions=raise_exceptions,
|
|
75
|
-
)
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
try:
|
|
79
|
-
async with ClientSession(
|
|
80
|
-
read_stream=client_read,
|
|
81
|
-
write_stream=client_write,
|
|
82
|
-
read_timeout_seconds=read_timeout_seconds,
|
|
83
|
-
) as client_session:
|
|
84
|
-
await client_session.initialize()
|
|
85
|
-
yield client_session
|
|
86
|
-
finally:
|
|
87
|
-
tg.cancel_scope.cancel()
|
mcp_wcgw/shared/progress.py
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
from contextlib import contextmanager
|
|
2
|
-
from dataclasses import dataclass, field
|
|
3
|
-
|
|
4
|
-
from pydantic import BaseModel
|
|
5
|
-
|
|
6
|
-
from mcp_wcgw.shared.context import RequestContext
|
|
7
|
-
from mcp_wcgw.shared.session import BaseSession
|
|
8
|
-
from mcp_wcgw.types import ProgressToken
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class Progress(BaseModel):
|
|
12
|
-
progress: float
|
|
13
|
-
total: float | None
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@dataclass
|
|
17
|
-
class ProgressContext:
|
|
18
|
-
session: BaseSession
|
|
19
|
-
progress_token: ProgressToken
|
|
20
|
-
total: float | None
|
|
21
|
-
current: float = field(default=0.0, init=False)
|
|
22
|
-
|
|
23
|
-
async def progress(self, amount: float) -> None:
|
|
24
|
-
self.current += amount
|
|
25
|
-
|
|
26
|
-
await self.session.send_progress_notification(
|
|
27
|
-
self.progress_token, self.current, total=self.total
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@contextmanager
|
|
32
|
-
def progress(ctx: RequestContext, total: float | None = None):
|
|
33
|
-
if ctx.meta is None or ctx.meta.progressToken is None:
|
|
34
|
-
raise ValueError("No progress token provided")
|
|
35
|
-
|
|
36
|
-
progress_ctx = ProgressContext(ctx.session, ctx.meta.progressToken, total)
|
|
37
|
-
try:
|
|
38
|
-
yield progress_ctx
|
|
39
|
-
finally:
|
|
40
|
-
pass
|
mcp_wcgw/shared/session.py
DELETED
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
from contextlib import AbstractAsyncContextManager
|
|
2
|
-
from datetime import timedelta
|
|
3
|
-
from typing import Generic, TypeVar
|
|
4
|
-
|
|
5
|
-
import anyio
|
|
6
|
-
import anyio.lowlevel
|
|
7
|
-
import httpx
|
|
8
|
-
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
|
9
|
-
from pydantic import BaseModel
|
|
10
|
-
|
|
11
|
-
from mcp_wcgw.shared.exceptions import McpError
|
|
12
|
-
from mcp_wcgw.types import (
|
|
13
|
-
ClientNotification,
|
|
14
|
-
ClientRequest,
|
|
15
|
-
ClientResult,
|
|
16
|
-
ErrorData,
|
|
17
|
-
JSONRPCError,
|
|
18
|
-
JSONRPCMessage,
|
|
19
|
-
JSONRPCNotification,
|
|
20
|
-
JSONRPCRequest,
|
|
21
|
-
JSONRPCResponse,
|
|
22
|
-
RequestParams,
|
|
23
|
-
ServerNotification,
|
|
24
|
-
ServerRequest,
|
|
25
|
-
ServerResult,
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
SendRequestT = TypeVar("SendRequestT", ClientRequest, ServerRequest)
|
|
29
|
-
SendResultT = TypeVar("SendResultT", ClientResult, ServerResult)
|
|
30
|
-
SendNotificationT = TypeVar("SendNotificationT", ClientNotification, ServerNotification)
|
|
31
|
-
ReceiveRequestT = TypeVar("ReceiveRequestT", ClientRequest, ServerRequest)
|
|
32
|
-
ReceiveResultT = TypeVar("ReceiveResultT", bound=BaseModel)
|
|
33
|
-
ReceiveNotificationT = TypeVar(
|
|
34
|
-
"ReceiveNotificationT", ClientNotification, ServerNotification
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
RequestId = str | int
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class RequestResponder(Generic[ReceiveRequestT, SendResultT]):
|
|
41
|
-
def __init__(
|
|
42
|
-
self,
|
|
43
|
-
request_id: RequestId,
|
|
44
|
-
request_meta: RequestParams.Meta | None,
|
|
45
|
-
request: ReceiveRequestT,
|
|
46
|
-
session: "BaseSession",
|
|
47
|
-
) -> None:
|
|
48
|
-
self.request_id = request_id
|
|
49
|
-
self.request_meta = request_meta
|
|
50
|
-
self.request = request
|
|
51
|
-
self._session = session
|
|
52
|
-
self._responded = False
|
|
53
|
-
|
|
54
|
-
async def respond(self, response: SendResultT | ErrorData) -> None:
|
|
55
|
-
assert not self._responded, "Request already responded to"
|
|
56
|
-
self._responded = True
|
|
57
|
-
|
|
58
|
-
await self._session._send_response(
|
|
59
|
-
request_id=self.request_id, response=response
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
class BaseSession(
|
|
64
|
-
AbstractAsyncContextManager,
|
|
65
|
-
Generic[
|
|
66
|
-
SendRequestT,
|
|
67
|
-
SendNotificationT,
|
|
68
|
-
SendResultT,
|
|
69
|
-
ReceiveRequestT,
|
|
70
|
-
ReceiveNotificationT,
|
|
71
|
-
],
|
|
72
|
-
):
|
|
73
|
-
"""
|
|
74
|
-
Implements an MCP "session" on top of read/write streams, including features
|
|
75
|
-
like request/response linking, notifications, and progress.
|
|
76
|
-
|
|
77
|
-
This class is an async context manager that automatically starts processing
|
|
78
|
-
messages when entered.
|
|
79
|
-
"""
|
|
80
|
-
|
|
81
|
-
_response_streams: dict[
|
|
82
|
-
RequestId, MemoryObjectSendStream[JSONRPCResponse | JSONRPCError]
|
|
83
|
-
]
|
|
84
|
-
_request_id: int
|
|
85
|
-
|
|
86
|
-
def __init__(
|
|
87
|
-
self,
|
|
88
|
-
read_stream: MemoryObjectReceiveStream[JSONRPCMessage | Exception],
|
|
89
|
-
write_stream: MemoryObjectSendStream[JSONRPCMessage],
|
|
90
|
-
receive_request_type: type[ReceiveRequestT],
|
|
91
|
-
receive_notification_type: type[ReceiveNotificationT],
|
|
92
|
-
# If none, reading will never time out
|
|
93
|
-
read_timeout_seconds: timedelta | None = None,
|
|
94
|
-
) -> None:
|
|
95
|
-
self._read_stream = read_stream
|
|
96
|
-
self._write_stream = write_stream
|
|
97
|
-
self._response_streams = {}
|
|
98
|
-
self._request_id = 0
|
|
99
|
-
self._receive_request_type = receive_request_type
|
|
100
|
-
self._receive_notification_type = receive_notification_type
|
|
101
|
-
self._read_timeout_seconds = read_timeout_seconds
|
|
102
|
-
|
|
103
|
-
self._incoming_message_stream_writer, self._incoming_message_stream_reader = (
|
|
104
|
-
anyio.create_memory_object_stream[
|
|
105
|
-
RequestResponder[ReceiveRequestT, SendResultT]
|
|
106
|
-
| ReceiveNotificationT
|
|
107
|
-
| Exception
|
|
108
|
-
]()
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
async def __aenter__(self):
|
|
112
|
-
self._task_group = anyio.create_task_group()
|
|
113
|
-
await self._task_group.__aenter__()
|
|
114
|
-
self._task_group.start_soon(self._receive_loop)
|
|
115
|
-
return self
|
|
116
|
-
|
|
117
|
-
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
118
|
-
# Using BaseSession as a context manager should not block on exit (this
|
|
119
|
-
# would be very surprising behavior), so make sure to cancel the tasks
|
|
120
|
-
# in the task group.
|
|
121
|
-
self._task_group.cancel_scope.cancel()
|
|
122
|
-
return await self._task_group.__aexit__(exc_type, exc_val, exc_tb)
|
|
123
|
-
|
|
124
|
-
async def send_request(
|
|
125
|
-
self,
|
|
126
|
-
request: SendRequestT,
|
|
127
|
-
result_type: type[ReceiveResultT],
|
|
128
|
-
) -> ReceiveResultT:
|
|
129
|
-
"""
|
|
130
|
-
Sends a request and wait for a response. Raises an McpError if the
|
|
131
|
-
response contains an error.
|
|
132
|
-
|
|
133
|
-
Do not use this method to emit notifications! Use send_notification()
|
|
134
|
-
instead.
|
|
135
|
-
"""
|
|
136
|
-
|
|
137
|
-
request_id = self._request_id
|
|
138
|
-
self._request_id = request_id + 1
|
|
139
|
-
|
|
140
|
-
response_stream, response_stream_reader = anyio.create_memory_object_stream[
|
|
141
|
-
JSONRPCResponse | JSONRPCError
|
|
142
|
-
](1)
|
|
143
|
-
self._response_streams[request_id] = response_stream
|
|
144
|
-
|
|
145
|
-
jsonrpc_request = JSONRPCRequest(
|
|
146
|
-
jsonrpc="2.0",
|
|
147
|
-
id=request_id,
|
|
148
|
-
**request.model_dump(by_alias=True, mode="json", exclude_none=True),
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
# TODO: Support progress callbacks
|
|
152
|
-
|
|
153
|
-
await self._write_stream.send(JSONRPCMessage(jsonrpc_request))
|
|
154
|
-
|
|
155
|
-
try:
|
|
156
|
-
with anyio.fail_after(
|
|
157
|
-
None
|
|
158
|
-
if self._read_timeout_seconds is None
|
|
159
|
-
else self._read_timeout_seconds.total_seconds()
|
|
160
|
-
):
|
|
161
|
-
response_or_error = await response_stream_reader.receive()
|
|
162
|
-
except TimeoutError:
|
|
163
|
-
raise McpError(
|
|
164
|
-
ErrorData(
|
|
165
|
-
code=httpx.codes.REQUEST_TIMEOUT,
|
|
166
|
-
message=(
|
|
167
|
-
f"Timed out while waiting for response to "
|
|
168
|
-
f"{request.__class__.__name__}. Waited "
|
|
169
|
-
f"{self._read_timeout_seconds} seconds."
|
|
170
|
-
),
|
|
171
|
-
)
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
if isinstance(response_or_error, JSONRPCError):
|
|
175
|
-
raise McpError(response_or_error.error)
|
|
176
|
-
else:
|
|
177
|
-
return result_type.model_validate(response_or_error.result)
|
|
178
|
-
|
|
179
|
-
async def send_notification(self, notification: SendNotificationT) -> None:
|
|
180
|
-
"""
|
|
181
|
-
Emits a notification, which is a one-way message that does not expect
|
|
182
|
-
a response.
|
|
183
|
-
"""
|
|
184
|
-
jsonrpc_notification = JSONRPCNotification(
|
|
185
|
-
jsonrpc="2.0",
|
|
186
|
-
**notification.model_dump(by_alias=True, mode="json", exclude_none=True),
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
await self._write_stream.send(JSONRPCMessage(jsonrpc_notification))
|
|
190
|
-
|
|
191
|
-
async def _send_response(
|
|
192
|
-
self, request_id: RequestId, response: SendResultT | ErrorData
|
|
193
|
-
) -> None:
|
|
194
|
-
if isinstance(response, ErrorData):
|
|
195
|
-
jsonrpc_error = JSONRPCError(jsonrpc="2.0", id=request_id, error=response)
|
|
196
|
-
await self._write_stream.send(JSONRPCMessage(jsonrpc_error))
|
|
197
|
-
else:
|
|
198
|
-
jsonrpc_response = JSONRPCResponse(
|
|
199
|
-
jsonrpc="2.0",
|
|
200
|
-
id=request_id,
|
|
201
|
-
result=response.model_dump(
|
|
202
|
-
by_alias=True, mode="json", exclude_none=True
|
|
203
|
-
),
|
|
204
|
-
)
|
|
205
|
-
await self._write_stream.send(JSONRPCMessage(jsonrpc_response))
|
|
206
|
-
|
|
207
|
-
async def _receive_loop(self) -> None:
|
|
208
|
-
async with (
|
|
209
|
-
self._read_stream,
|
|
210
|
-
self._write_stream,
|
|
211
|
-
self._incoming_message_stream_writer,
|
|
212
|
-
):
|
|
213
|
-
async for message in self._read_stream:
|
|
214
|
-
if isinstance(message, Exception):
|
|
215
|
-
await self._incoming_message_stream_writer.send(message)
|
|
216
|
-
elif isinstance(message.root, JSONRPCRequest):
|
|
217
|
-
validated_request = self._receive_request_type.model_validate(
|
|
218
|
-
message.root.model_dump(
|
|
219
|
-
by_alias=True, mode="json", exclude_none=True
|
|
220
|
-
)
|
|
221
|
-
)
|
|
222
|
-
responder = RequestResponder(
|
|
223
|
-
request_id=message.root.id,
|
|
224
|
-
request_meta=validated_request.root.params._meta
|
|
225
|
-
if validated_request.root.params
|
|
226
|
-
else None,
|
|
227
|
-
request=validated_request,
|
|
228
|
-
session=self,
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
await self._received_request(responder)
|
|
232
|
-
if not responder._responded:
|
|
233
|
-
await self._incoming_message_stream_writer.send(responder)
|
|
234
|
-
elif isinstance(message.root, JSONRPCNotification):
|
|
235
|
-
notification = self._receive_notification_type.model_validate(
|
|
236
|
-
message.root.model_dump(
|
|
237
|
-
by_alias=True, mode="json", exclude_none=True
|
|
238
|
-
)
|
|
239
|
-
)
|
|
240
|
-
|
|
241
|
-
await self._received_notification(notification)
|
|
242
|
-
await self._incoming_message_stream_writer.send(notification)
|
|
243
|
-
else: # Response or error
|
|
244
|
-
stream = self._response_streams.pop(message.root.id, None)
|
|
245
|
-
if stream:
|
|
246
|
-
await stream.send(message.root)
|
|
247
|
-
else:
|
|
248
|
-
await self._incoming_message_stream_writer.send(
|
|
249
|
-
RuntimeError(
|
|
250
|
-
"Received response with an unknown "
|
|
251
|
-
f"request ID: {message}"
|
|
252
|
-
)
|
|
253
|
-
)
|
|
254
|
-
|
|
255
|
-
async def _received_request(
|
|
256
|
-
self, responder: RequestResponder[ReceiveRequestT, SendResultT]
|
|
257
|
-
) -> None:
|
|
258
|
-
"""
|
|
259
|
-
Can be overridden by subclasses to handle a request without needing to
|
|
260
|
-
listen on the message stream.
|
|
261
|
-
|
|
262
|
-
If the request is responded to within this method, it will not be
|
|
263
|
-
forwarded on to the message stream.
|
|
264
|
-
"""
|
|
265
|
-
|
|
266
|
-
async def _received_notification(self, notification: ReceiveNotificationT) -> None:
|
|
267
|
-
"""
|
|
268
|
-
Can be overridden by subclasses to handle a notification without needing
|
|
269
|
-
to listen on the message stream.
|
|
270
|
-
"""
|
|
271
|
-
|
|
272
|
-
async def send_progress_notification(
|
|
273
|
-
self, progress_token: str | int, progress: float, total: float | None = None
|
|
274
|
-
) -> None:
|
|
275
|
-
"""
|
|
276
|
-
Sends a progress notification for a request that is currently being
|
|
277
|
-
processed.
|
|
278
|
-
"""
|
|
279
|
-
|
|
280
|
-
@property
|
|
281
|
-
def incoming_messages(
|
|
282
|
-
self,
|
|
283
|
-
) -> MemoryObjectReceiveStream[
|
|
284
|
-
RequestResponder[ReceiveRequestT, SendResultT]
|
|
285
|
-
| ReceiveNotificationT
|
|
286
|
-
| Exception
|
|
287
|
-
]:
|
|
288
|
-
return self._incoming_message_stream_reader
|
mcp_wcgw/shared/version.py
DELETED