wcgw 5.0.2__py3-none-any.whl → 5.1.1__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.

Files changed (39) hide show
  1. wcgw/client/bash_state/bash_state.py +2 -2
  2. wcgw/client/file_ops/diff_edit.py +14 -2
  3. wcgw/client/file_ops/extensions.py +137 -0
  4. wcgw/client/file_ops/search_replace.py +1 -2
  5. wcgw/client/mcp_server/server.py +10 -18
  6. wcgw/client/memory.py +4 -1
  7. wcgw/client/repo_ops/display_tree.py +4 -4
  8. wcgw/client/tool_prompts.py +16 -15
  9. wcgw/client/tools.py +95 -38
  10. {wcgw-5.0.2.dist-info → wcgw-5.1.1.dist-info}/METADATA +6 -18
  11. wcgw-5.1.1.dist-info/RECORD +37 -0
  12. wcgw_cli/anthropic_client.py +8 -4
  13. wcgw_cli/openai_client.py +7 -3
  14. mcp_wcgw/__init__.py +0 -114
  15. mcp_wcgw/client/__init__.py +0 -0
  16. mcp_wcgw/client/__main__.py +0 -79
  17. mcp_wcgw/client/session.py +0 -234
  18. mcp_wcgw/client/sse.py +0 -142
  19. mcp_wcgw/client/stdio.py +0 -128
  20. mcp_wcgw/py.typed +0 -0
  21. mcp_wcgw/server/__init__.py +0 -514
  22. mcp_wcgw/server/__main__.py +0 -50
  23. mcp_wcgw/server/models.py +0 -16
  24. mcp_wcgw/server/session.py +0 -288
  25. mcp_wcgw/server/sse.py +0 -178
  26. mcp_wcgw/server/stdio.py +0 -83
  27. mcp_wcgw/server/websocket.py +0 -61
  28. mcp_wcgw/shared/__init__.py +0 -0
  29. mcp_wcgw/shared/context.py +0 -14
  30. mcp_wcgw/shared/exceptions.py +0 -9
  31. mcp_wcgw/shared/memory.py +0 -87
  32. mcp_wcgw/shared/progress.py +0 -40
  33. mcp_wcgw/shared/session.py +0 -288
  34. mcp_wcgw/shared/version.py +0 -3
  35. mcp_wcgw/types.py +0 -1060
  36. wcgw-5.0.2.dist-info/RECORD +0 -58
  37. {wcgw-5.0.2.dist-info → wcgw-5.1.1.dist-info}/WHEEL +0 -0
  38. {wcgw-5.0.2.dist-info → wcgw-5.1.1.dist-info}/entry_points.txt +0 -0
  39. {wcgw-5.0.2.dist-info → wcgw-5.1.1.dist-info}/licenses/LICENSE +0 -0
@@ -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()
@@ -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