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/client/__main__.py
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import argparse
|
|
2
|
-
import logging
|
|
3
|
-
import sys
|
|
4
|
-
from functools import partial
|
|
5
|
-
from urllib.parse import urlparse
|
|
6
|
-
|
|
7
|
-
import anyio
|
|
8
|
-
|
|
9
|
-
from mcp_wcgw.client.session import ClientSession
|
|
10
|
-
from mcp_wcgw.client.sse import sse_client
|
|
11
|
-
from mcp_wcgw.client.stdio import StdioServerParameters, stdio_client
|
|
12
|
-
|
|
13
|
-
if not sys.warnoptions:
|
|
14
|
-
import warnings
|
|
15
|
-
|
|
16
|
-
warnings.simplefilter("ignore")
|
|
17
|
-
|
|
18
|
-
logging.basicConfig(level=logging.INFO)
|
|
19
|
-
logger = logging.getLogger("client")
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
async def receive_loop(session: ClientSession):
|
|
23
|
-
logger.info("Starting receive loop")
|
|
24
|
-
async for message in session.incoming_messages:
|
|
25
|
-
if isinstance(message, Exception):
|
|
26
|
-
logger.error("Error: %s", message)
|
|
27
|
-
continue
|
|
28
|
-
|
|
29
|
-
logger.info("Received message from server: %s", message)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
async def run_session(read_stream, write_stream):
|
|
33
|
-
async with (
|
|
34
|
-
ClientSession(read_stream, write_stream) as session,
|
|
35
|
-
anyio.create_task_group() as tg,
|
|
36
|
-
):
|
|
37
|
-
tg.start_soon(receive_loop, session)
|
|
38
|
-
|
|
39
|
-
logger.info("Initializing session")
|
|
40
|
-
await session.initialize()
|
|
41
|
-
logger.info("Initialized")
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
async def main(command_or_url: str, args: list[str], env: list[tuple[str, str]]):
|
|
45
|
-
env_dict = dict(env)
|
|
46
|
-
|
|
47
|
-
if urlparse(command_or_url).scheme in ("http", "https"):
|
|
48
|
-
# Use SSE client for HTTP(S) URLs
|
|
49
|
-
async with sse_client(command_or_url) as streams:
|
|
50
|
-
await run_session(*streams)
|
|
51
|
-
else:
|
|
52
|
-
# Use stdio client for commands
|
|
53
|
-
server_parameters = StdioServerParameters(
|
|
54
|
-
command=command_or_url, args=args, env=env_dict
|
|
55
|
-
)
|
|
56
|
-
async with stdio_client(server_parameters) as streams:
|
|
57
|
-
await run_session(*streams)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def cli():
|
|
61
|
-
parser = argparse.ArgumentParser()
|
|
62
|
-
parser.add_argument("command_or_url", help="Command or URL to connect to")
|
|
63
|
-
parser.add_argument("args", nargs="*", help="Additional arguments")
|
|
64
|
-
parser.add_argument(
|
|
65
|
-
"-e",
|
|
66
|
-
"--env",
|
|
67
|
-
nargs=2,
|
|
68
|
-
action="append",
|
|
69
|
-
metavar=("KEY", "VALUE"),
|
|
70
|
-
help="Environment variables to set. Can be used multiple times.",
|
|
71
|
-
default=[],
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
args = parser.parse_args()
|
|
75
|
-
anyio.run(partial(main, args.command_or_url, args.args, args.env), backend="trio")
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if __name__ == "__main__":
|
|
79
|
-
cli()
|
mcp_wcgw/client/session.py
DELETED
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
from datetime import timedelta
|
|
2
|
-
|
|
3
|
-
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
|
4
|
-
from pydantic import AnyUrl
|
|
5
|
-
|
|
6
|
-
import mcp_wcgw.types as types
|
|
7
|
-
from mcp_wcgw.shared.session import BaseSession
|
|
8
|
-
from mcp_wcgw.shared.version import SUPPORTED_PROTOCOL_VERSIONS
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class ClientSession(
|
|
12
|
-
BaseSession[
|
|
13
|
-
types.ClientRequest,
|
|
14
|
-
types.ClientNotification,
|
|
15
|
-
types.ClientResult,
|
|
16
|
-
types.ServerRequest,
|
|
17
|
-
types.ServerNotification,
|
|
18
|
-
]
|
|
19
|
-
):
|
|
20
|
-
def __init__(
|
|
21
|
-
self,
|
|
22
|
-
read_stream: MemoryObjectReceiveStream[types.JSONRPCMessage | Exception],
|
|
23
|
-
write_stream: MemoryObjectSendStream[types.JSONRPCMessage],
|
|
24
|
-
read_timeout_seconds: timedelta | None = None,
|
|
25
|
-
) -> None:
|
|
26
|
-
super().__init__(
|
|
27
|
-
read_stream,
|
|
28
|
-
write_stream,
|
|
29
|
-
types.ServerRequest,
|
|
30
|
-
types.ServerNotification,
|
|
31
|
-
read_timeout_seconds=read_timeout_seconds,
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
async def initialize(self) -> types.InitializeResult:
|
|
35
|
-
result = await self.send_request(
|
|
36
|
-
types.ClientRequest(
|
|
37
|
-
types.InitializeRequest(
|
|
38
|
-
method="initialize",
|
|
39
|
-
params=types.InitializeRequestParams(
|
|
40
|
-
protocolVersion=types.LATEST_PROTOCOL_VERSION,
|
|
41
|
-
capabilities=types.ClientCapabilities(
|
|
42
|
-
sampling=None,
|
|
43
|
-
experimental=None,
|
|
44
|
-
roots=types.RootsCapability(
|
|
45
|
-
# TODO: Should this be based on whether we
|
|
46
|
-
# _will_ send notifications, or only whether
|
|
47
|
-
# they're supported?
|
|
48
|
-
listChanged=True
|
|
49
|
-
),
|
|
50
|
-
),
|
|
51
|
-
clientInfo=types.Implementation(name="mcp", version="0.1.0"),
|
|
52
|
-
),
|
|
53
|
-
)
|
|
54
|
-
),
|
|
55
|
-
types.InitializeResult,
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
if result.protocolVersion not in SUPPORTED_PROTOCOL_VERSIONS:
|
|
59
|
-
raise RuntimeError(
|
|
60
|
-
"Unsupported protocol version from the server: "
|
|
61
|
-
f"{result.protocolVersion}"
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
await self.send_notification(
|
|
65
|
-
types.ClientNotification(
|
|
66
|
-
types.InitializedNotification(method="notifications/initialized")
|
|
67
|
-
)
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
return result
|
|
71
|
-
|
|
72
|
-
async def send_ping(self) -> types.EmptyResult:
|
|
73
|
-
"""Send a ping request."""
|
|
74
|
-
return await self.send_request(
|
|
75
|
-
types.ClientRequest(
|
|
76
|
-
types.PingRequest(
|
|
77
|
-
method="ping",
|
|
78
|
-
)
|
|
79
|
-
),
|
|
80
|
-
types.EmptyResult,
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
async def send_progress_notification(
|
|
84
|
-
self, progress_token: str | int, progress: float, total: float | None = None
|
|
85
|
-
) -> None:
|
|
86
|
-
"""Send a progress notification."""
|
|
87
|
-
await self.send_notification(
|
|
88
|
-
types.ClientNotification(
|
|
89
|
-
types.ProgressNotification(
|
|
90
|
-
method="notifications/progress",
|
|
91
|
-
params=types.ProgressNotificationParams(
|
|
92
|
-
progressToken=progress_token,
|
|
93
|
-
progress=progress,
|
|
94
|
-
total=total,
|
|
95
|
-
),
|
|
96
|
-
),
|
|
97
|
-
)
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
async def set_logging_level(self, level: types.LoggingLevel) -> types.EmptyResult:
|
|
101
|
-
"""Send a logging/setLevel request."""
|
|
102
|
-
return await self.send_request(
|
|
103
|
-
types.ClientRequest(
|
|
104
|
-
types.SetLevelRequest(
|
|
105
|
-
method="logging/setLevel",
|
|
106
|
-
params=types.SetLevelRequestParams(level=level),
|
|
107
|
-
)
|
|
108
|
-
),
|
|
109
|
-
types.EmptyResult,
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
async def list_resources(self) -> types.ListResourcesResult:
|
|
113
|
-
"""Send a resources/list request."""
|
|
114
|
-
return await self.send_request(
|
|
115
|
-
types.ClientRequest(
|
|
116
|
-
types.ListResourcesRequest(
|
|
117
|
-
method="resources/list",
|
|
118
|
-
)
|
|
119
|
-
),
|
|
120
|
-
types.ListResourcesResult,
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
async def read_resource(self, uri: AnyUrl) -> types.ReadResourceResult:
|
|
124
|
-
"""Send a resources/read request."""
|
|
125
|
-
return await self.send_request(
|
|
126
|
-
types.ClientRequest(
|
|
127
|
-
types.ReadResourceRequest(
|
|
128
|
-
method="resources/read",
|
|
129
|
-
params=types.ReadResourceRequestParams(uri=uri),
|
|
130
|
-
)
|
|
131
|
-
),
|
|
132
|
-
types.ReadResourceResult,
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
async def subscribe_resource(self, uri: AnyUrl) -> types.EmptyResult:
|
|
136
|
-
"""Send a resources/subscribe request."""
|
|
137
|
-
return await self.send_request(
|
|
138
|
-
types.ClientRequest(
|
|
139
|
-
types.SubscribeRequest(
|
|
140
|
-
method="resources/subscribe",
|
|
141
|
-
params=types.SubscribeRequestParams(uri=uri),
|
|
142
|
-
)
|
|
143
|
-
),
|
|
144
|
-
types.EmptyResult,
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
async def unsubscribe_resource(self, uri: AnyUrl) -> types.EmptyResult:
|
|
148
|
-
"""Send a resources/unsubscribe request."""
|
|
149
|
-
return await self.send_request(
|
|
150
|
-
types.ClientRequest(
|
|
151
|
-
types.UnsubscribeRequest(
|
|
152
|
-
method="resources/unsubscribe",
|
|
153
|
-
params=types.UnsubscribeRequestParams(uri=uri),
|
|
154
|
-
)
|
|
155
|
-
),
|
|
156
|
-
types.EmptyResult,
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
async def call_tool(
|
|
160
|
-
self, name: str, arguments: dict | None = None
|
|
161
|
-
) -> types.CallToolResult:
|
|
162
|
-
"""Send a tools/call request."""
|
|
163
|
-
return await self.send_request(
|
|
164
|
-
types.ClientRequest(
|
|
165
|
-
types.CallToolRequest(
|
|
166
|
-
method="tools/call",
|
|
167
|
-
params=types.CallToolRequestParams(name=name, arguments=arguments),
|
|
168
|
-
)
|
|
169
|
-
),
|
|
170
|
-
types.CallToolResult,
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
async def list_prompts(self) -> types.ListPromptsResult:
|
|
174
|
-
"""Send a prompts/list request."""
|
|
175
|
-
return await self.send_request(
|
|
176
|
-
types.ClientRequest(
|
|
177
|
-
types.ListPromptsRequest(
|
|
178
|
-
method="prompts/list",
|
|
179
|
-
)
|
|
180
|
-
),
|
|
181
|
-
types.ListPromptsResult,
|
|
182
|
-
)
|
|
183
|
-
|
|
184
|
-
async def get_prompt(
|
|
185
|
-
self, name: str, arguments: dict[str, str] | None = None
|
|
186
|
-
) -> types.GetPromptResult:
|
|
187
|
-
"""Send a prompts/get request."""
|
|
188
|
-
return await self.send_request(
|
|
189
|
-
types.ClientRequest(
|
|
190
|
-
types.GetPromptRequest(
|
|
191
|
-
method="prompts/get",
|
|
192
|
-
params=types.GetPromptRequestParams(name=name, arguments=arguments),
|
|
193
|
-
)
|
|
194
|
-
),
|
|
195
|
-
types.GetPromptResult,
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
async def complete(
|
|
199
|
-
self, ref: types.ResourceReference | types.PromptReference, argument: dict
|
|
200
|
-
) -> types.CompleteResult:
|
|
201
|
-
"""Send a completion/complete request."""
|
|
202
|
-
return await self.send_request(
|
|
203
|
-
types.ClientRequest(
|
|
204
|
-
types.CompleteRequest(
|
|
205
|
-
method="completion/complete",
|
|
206
|
-
params=types.CompleteRequestParams(
|
|
207
|
-
ref=ref,
|
|
208
|
-
argument=types.CompletionArgument(**argument),
|
|
209
|
-
),
|
|
210
|
-
)
|
|
211
|
-
),
|
|
212
|
-
types.CompleteResult,
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
async def list_tools(self) -> types.ListToolsResult:
|
|
216
|
-
"""Send a tools/list request."""
|
|
217
|
-
return await self.send_request(
|
|
218
|
-
types.ClientRequest(
|
|
219
|
-
types.ListToolsRequest(
|
|
220
|
-
method="tools/list",
|
|
221
|
-
)
|
|
222
|
-
),
|
|
223
|
-
types.ListToolsResult,
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
async def send_roots_list_changed(self) -> None:
|
|
227
|
-
"""Send a roots/list_changed notification."""
|
|
228
|
-
await self.send_notification(
|
|
229
|
-
types.ClientNotification(
|
|
230
|
-
types.RootsListChangedNotification(
|
|
231
|
-
method="notifications/roots/list_changed",
|
|
232
|
-
)
|
|
233
|
-
)
|
|
234
|
-
)
|
mcp_wcgw/client/sse.py
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from contextlib import asynccontextmanager
|
|
3
|
-
from typing import Any
|
|
4
|
-
from urllib.parse import urljoin, urlparse
|
|
5
|
-
|
|
6
|
-
import anyio
|
|
7
|
-
import httpx
|
|
8
|
-
from anyio.abc import TaskStatus
|
|
9
|
-
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
|
10
|
-
from httpx_sse import aconnect_sse
|
|
11
|
-
|
|
12
|
-
import mcp_wcgw.types as types
|
|
13
|
-
|
|
14
|
-
logger = logging.getLogger(__name__)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def remove_request_params(url: str) -> str:
|
|
18
|
-
return urljoin(url, urlparse(url).path)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@asynccontextmanager
|
|
22
|
-
async def sse_client(
|
|
23
|
-
url: str,
|
|
24
|
-
headers: dict[str, Any] | None = None,
|
|
25
|
-
timeout: float = 5,
|
|
26
|
-
sse_read_timeout: float = 60 * 5,
|
|
27
|
-
):
|
|
28
|
-
"""
|
|
29
|
-
Client transport for SSE.
|
|
30
|
-
|
|
31
|
-
`sse_read_timeout` determines how long (in seconds) the client will wait for a new
|
|
32
|
-
event before disconnecting. All other HTTP operations are controlled by `timeout`.
|
|
33
|
-
"""
|
|
34
|
-
read_stream: MemoryObjectReceiveStream[types.JSONRPCMessage | Exception]
|
|
35
|
-
read_stream_writer: MemoryObjectSendStream[types.JSONRPCMessage | Exception]
|
|
36
|
-
|
|
37
|
-
write_stream: MemoryObjectSendStream[types.JSONRPCMessage]
|
|
38
|
-
write_stream_reader: MemoryObjectReceiveStream[types.JSONRPCMessage]
|
|
39
|
-
|
|
40
|
-
read_stream_writer, read_stream = anyio.create_memory_object_stream(0)
|
|
41
|
-
write_stream, write_stream_reader = anyio.create_memory_object_stream(0)
|
|
42
|
-
|
|
43
|
-
async with anyio.create_task_group() as tg:
|
|
44
|
-
try:
|
|
45
|
-
logger.info(f"Connecting to SSE endpoint: {remove_request_params(url)}")
|
|
46
|
-
async with httpx.AsyncClient(headers=headers) as client:
|
|
47
|
-
async with aconnect_sse(
|
|
48
|
-
client,
|
|
49
|
-
"GET",
|
|
50
|
-
url,
|
|
51
|
-
timeout=httpx.Timeout(timeout, read=sse_read_timeout),
|
|
52
|
-
) as event_source:
|
|
53
|
-
event_source.response.raise_for_status()
|
|
54
|
-
logger.debug("SSE connection established")
|
|
55
|
-
|
|
56
|
-
async def sse_reader(
|
|
57
|
-
task_status: TaskStatus[str] = anyio.TASK_STATUS_IGNORED,
|
|
58
|
-
):
|
|
59
|
-
try:
|
|
60
|
-
async for sse in event_source.aiter_sse():
|
|
61
|
-
logger.debug(f"Received SSE event: {sse.event}")
|
|
62
|
-
match sse.event:
|
|
63
|
-
case "endpoint":
|
|
64
|
-
endpoint_url = urljoin(url, sse.data)
|
|
65
|
-
logger.info(
|
|
66
|
-
f"Received endpoint URL: {endpoint_url}"
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
url_parsed = urlparse(url)
|
|
70
|
-
endpoint_parsed = urlparse(endpoint_url)
|
|
71
|
-
if (
|
|
72
|
-
url_parsed.netloc != endpoint_parsed.netloc
|
|
73
|
-
or url_parsed.scheme
|
|
74
|
-
!= endpoint_parsed.scheme
|
|
75
|
-
):
|
|
76
|
-
error_msg = (
|
|
77
|
-
"Endpoint origin does not match "
|
|
78
|
-
f"connection origin: {endpoint_url}"
|
|
79
|
-
)
|
|
80
|
-
logger.error(error_msg)
|
|
81
|
-
raise ValueError(error_msg)
|
|
82
|
-
|
|
83
|
-
task_status.started(endpoint_url)
|
|
84
|
-
|
|
85
|
-
case "message":
|
|
86
|
-
try:
|
|
87
|
-
message = types.JSONRPCMessage.model_validate_json( # noqa: E501
|
|
88
|
-
sse.data
|
|
89
|
-
)
|
|
90
|
-
logger.debug(
|
|
91
|
-
f"Received server message: {message}"
|
|
92
|
-
)
|
|
93
|
-
except Exception as exc:
|
|
94
|
-
logger.error(
|
|
95
|
-
f"Error parsing server message: {exc}"
|
|
96
|
-
)
|
|
97
|
-
await read_stream_writer.send(exc)
|
|
98
|
-
continue
|
|
99
|
-
|
|
100
|
-
await read_stream_writer.send(message)
|
|
101
|
-
except Exception as exc:
|
|
102
|
-
logger.error(f"Error in sse_reader: {exc}")
|
|
103
|
-
await read_stream_writer.send(exc)
|
|
104
|
-
finally:
|
|
105
|
-
await read_stream_writer.aclose()
|
|
106
|
-
|
|
107
|
-
async def post_writer(endpoint_url: str):
|
|
108
|
-
try:
|
|
109
|
-
async with write_stream_reader:
|
|
110
|
-
async for message in write_stream_reader:
|
|
111
|
-
logger.debug(f"Sending client message: {message}")
|
|
112
|
-
response = await client.post(
|
|
113
|
-
endpoint_url,
|
|
114
|
-
json=message.model_dump(
|
|
115
|
-
by_alias=True,
|
|
116
|
-
mode="json",
|
|
117
|
-
exclude_none=True,
|
|
118
|
-
),
|
|
119
|
-
)
|
|
120
|
-
response.raise_for_status()
|
|
121
|
-
logger.debug(
|
|
122
|
-
"Client message sent successfully: "
|
|
123
|
-
f"{response.status_code}"
|
|
124
|
-
)
|
|
125
|
-
except Exception as exc:
|
|
126
|
-
logger.error(f"Error in post_writer: {exc}")
|
|
127
|
-
finally:
|
|
128
|
-
await write_stream.aclose()
|
|
129
|
-
|
|
130
|
-
endpoint_url = await tg.start(sse_reader)
|
|
131
|
-
logger.info(
|
|
132
|
-
f"Starting post writer with endpoint URL: {endpoint_url}"
|
|
133
|
-
)
|
|
134
|
-
tg.start_soon(post_writer, endpoint_url)
|
|
135
|
-
|
|
136
|
-
try:
|
|
137
|
-
yield read_stream, write_stream
|
|
138
|
-
finally:
|
|
139
|
-
tg.cancel_scope.cancel()
|
|
140
|
-
finally:
|
|
141
|
-
await read_stream_writer.aclose()
|
|
142
|
-
await write_stream.aclose()
|
mcp_wcgw/client/stdio.py
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import sys
|
|
3
|
-
from contextlib import asynccontextmanager
|
|
4
|
-
|
|
5
|
-
import anyio
|
|
6
|
-
import anyio.lowlevel
|
|
7
|
-
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
|
8
|
-
from anyio.streams.text import TextReceiveStream
|
|
9
|
-
from pydantic import BaseModel, Field
|
|
10
|
-
|
|
11
|
-
import mcp_wcgw.types as types
|
|
12
|
-
|
|
13
|
-
# Environment variables to inherit by default
|
|
14
|
-
DEFAULT_INHERITED_ENV_VARS = (
|
|
15
|
-
[
|
|
16
|
-
"APPDATA",
|
|
17
|
-
"HOMEDRIVE",
|
|
18
|
-
"HOMEPATH",
|
|
19
|
-
"LOCALAPPDATA",
|
|
20
|
-
"PATH",
|
|
21
|
-
"PROCESSOR_ARCHITECTURE",
|
|
22
|
-
"SYSTEMDRIVE",
|
|
23
|
-
"SYSTEMROOT",
|
|
24
|
-
"TEMP",
|
|
25
|
-
"USERNAME",
|
|
26
|
-
"USERPROFILE",
|
|
27
|
-
]
|
|
28
|
-
if sys.platform == "win32"
|
|
29
|
-
else ["HOME", "LOGNAME", "PATH", "SHELL", "TERM", "USER"]
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def get_default_environment() -> dict[str, str]:
|
|
34
|
-
"""
|
|
35
|
-
Returns a default environment object including only environment variables deemed
|
|
36
|
-
safe to inherit.
|
|
37
|
-
"""
|
|
38
|
-
env: dict[str, str] = {}
|
|
39
|
-
|
|
40
|
-
for key in DEFAULT_INHERITED_ENV_VARS:
|
|
41
|
-
value = os.environ.get(key)
|
|
42
|
-
if value is None:
|
|
43
|
-
continue
|
|
44
|
-
|
|
45
|
-
if value.startswith("()"):
|
|
46
|
-
# Skip functions, which are a security risk
|
|
47
|
-
continue
|
|
48
|
-
|
|
49
|
-
env[key] = value
|
|
50
|
-
|
|
51
|
-
return env
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
class StdioServerParameters(BaseModel):
|
|
55
|
-
command: str
|
|
56
|
-
"""The executable to run to start the server."""
|
|
57
|
-
|
|
58
|
-
args: list[str] = Field(default_factory=list)
|
|
59
|
-
"""Command line arguments to pass to the executable."""
|
|
60
|
-
|
|
61
|
-
env: dict[str, str] | None = None
|
|
62
|
-
"""
|
|
63
|
-
The environment to use when spawning the process.
|
|
64
|
-
|
|
65
|
-
If not specified, the result of get_default_environment() will be used.
|
|
66
|
-
"""
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
@asynccontextmanager
|
|
70
|
-
async def stdio_client(server: StdioServerParameters):
|
|
71
|
-
"""
|
|
72
|
-
Client transport for stdio: this will connect to a server by spawning a
|
|
73
|
-
process and communicating with it over stdin/stdout.
|
|
74
|
-
"""
|
|
75
|
-
read_stream: MemoryObjectReceiveStream[types.JSONRPCMessage | Exception]
|
|
76
|
-
read_stream_writer: MemoryObjectSendStream[types.JSONRPCMessage | Exception]
|
|
77
|
-
|
|
78
|
-
write_stream: MemoryObjectSendStream[types.JSONRPCMessage]
|
|
79
|
-
write_stream_reader: MemoryObjectReceiveStream[types.JSONRPCMessage]
|
|
80
|
-
|
|
81
|
-
read_stream_writer, read_stream = anyio.create_memory_object_stream(0)
|
|
82
|
-
write_stream, write_stream_reader = anyio.create_memory_object_stream(0)
|
|
83
|
-
|
|
84
|
-
process = await anyio.open_process(
|
|
85
|
-
[server.command, *server.args],
|
|
86
|
-
env=server.env if server.env is not None else get_default_environment(),
|
|
87
|
-
stderr=sys.stderr,
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
async def stdout_reader():
|
|
91
|
-
assert process.stdout, "Opened process is missing stdout"
|
|
92
|
-
|
|
93
|
-
try:
|
|
94
|
-
async with read_stream_writer:
|
|
95
|
-
buffer = ""
|
|
96
|
-
async for chunk in TextReceiveStream(process.stdout):
|
|
97
|
-
lines = (buffer + chunk).split("\n")
|
|
98
|
-
buffer = lines.pop()
|
|
99
|
-
|
|
100
|
-
for line in lines:
|
|
101
|
-
try:
|
|
102
|
-
message = types.JSONRPCMessage.model_validate_json(line)
|
|
103
|
-
except Exception as exc:
|
|
104
|
-
await read_stream_writer.send(exc)
|
|
105
|
-
continue
|
|
106
|
-
|
|
107
|
-
await read_stream_writer.send(message)
|
|
108
|
-
except anyio.ClosedResourceError:
|
|
109
|
-
await anyio.lowlevel.checkpoint()
|
|
110
|
-
|
|
111
|
-
async def stdin_writer():
|
|
112
|
-
assert process.stdin, "Opened process is missing stdin"
|
|
113
|
-
|
|
114
|
-
try:
|
|
115
|
-
async with write_stream_reader:
|
|
116
|
-
async for message in write_stream_reader:
|
|
117
|
-
json = message.model_dump_json(by_alias=True, exclude_none=True)
|
|
118
|
-
await process.stdin.send((json + "\n").encode())
|
|
119
|
-
except anyio.ClosedResourceError:
|
|
120
|
-
await anyio.lowlevel.checkpoint()
|
|
121
|
-
|
|
122
|
-
async with (
|
|
123
|
-
anyio.create_task_group() as tg,
|
|
124
|
-
process,
|
|
125
|
-
):
|
|
126
|
-
tg.start_soon(stdout_reader)
|
|
127
|
-
tg.start_soon(stdin_writer)
|
|
128
|
-
yield read_stream, write_stream
|
mcp_wcgw/py.typed
DELETED
|
File without changes
|