Skip to content

Commit 4975d79

Browse files
authored
cleanup: refactor mcp.run client (#12)
1 parent 9d16af8 commit 4975d79

File tree

14 files changed

+174
-735
lines changed

14 files changed

+174
-735
lines changed

README.md

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
11
# mcpx-py
22
[![PyPI](https://img.shields.io/pypi/v/mcpx-py)](https://pypi.org/project/mcpx-py/)
33

4-
5-
A Python library and command line client for https://www.mcp.run. This tool enables seamless interaction with various AI models while providing access to a suite of powerful tools.
4+
A Python library for interacting with LLMs using mcp.run tools
65

76
## Features
87

9-
### Tool Management
10-
- **List Tools**: Browse available tools and their capabilities
11-
- **Direct Tool Execution**: Run tools with specific inputs without LLM interaction
12-
- **Tool Integration**: Use tools seamlessly within AI chat conversations
13-
148
### AI Provider Support
159
- **Ollama**: https://ollama.com/
1610
- **Claude**: https://www.anthropic.com/api
@@ -80,17 +74,27 @@ pip install mcpx-py
8074
### Example code
8175

8276
```python
83-
from mcpx_py import Client # Import the mcp.run client
77+
from mcpx_py import Chat, Claude
78+
79+
llm = Chat(Claude)
80+
81+
# Or OpenAI
82+
# from mcpx import OpenAI
83+
# llm = Chat(OpenAI)
8484

85-
client = Client() # Create the client, this will check the
86-
# `MCP_RUN_SESSION_ID` environment variable
85+
# Or Ollama
86+
# from mcpx import Ollama
87+
# llm = Chat(Ollama)
8788

88-
# Call a tool with the given input
89-
results = client.call("eval-js", {"code": "'Hello, world!'"})
89+
# Or Gemini
90+
# from mcpx import Gemini
91+
# llm = Chat(Gemini)
9092

91-
# Iterate over the results
92-
for content in results.content:
93-
print(content.text)
93+
# Prompt claude and iterate over the results
94+
async for response in llm.send_message(
95+
"summarize the contents of example.com"
96+
):
97+
print(response)
9498
```
9599

96100
More examples can be found in the [examples/](https://github.com/dylibso/mcpx-py/tree/main/examples) directory

examples/chat.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@ async def main():
2121
# llm = Chat(Gemini)
2222

2323
# Prompt claude and iterate over the results
24-
async for response in llm.send_message(
25-
"what is the total volume of nine three foot by 5 foot by four foot boxes"
26-
):
24+
async for response in llm.send_message("summarize the contents of example.com"):
2725
print(response)
2826

2927

examples/hello_world.py

Lines changed: 0 additions & 13 deletions
This file was deleted.

examples/search.py

Lines changed: 0 additions & 13 deletions
This file was deleted.

justfile

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ test:
22
uv run python3 -m unittest
33

44
format:
5-
uv run ruff format mcpx_py tests examples
5+
uv run ruff format mcpx_py examples
66

77
check:
8-
uv run ruff check mcpx_py tests examples
8+
uv run ruff check mcpx_py examples
9+
10+
chat:
11+
uv run python3 -m mcpx_py chat

mcpx_py/__init__.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
from .chat import ChatProvider, ChatConfig, Chat, Ollama, OpenAI, Claude, Gemini
2-
from .client import Client, ClientConfig, Tool
2+
import mcp_run as client
33

44
__all__ = [
55
"Chat",
6-
"Client",
7-
"ClientConfig",
8-
"Tool",
96
"ChatConfig",
107
"ChatProvider",
118
"Ollama",
129
"OpenAI",
1310
"Claude",
1411
"Gemini",
12+
"client",
1513
]

mcpx_py/__main__.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010

1111
from dotenv import load_dotenv
1212

13-
from . import Claude, OpenAI, Ollama, ChatConfig, Client, Gemini, Chat, ClientConfig
13+
from . import Claude, OpenAI, Ollama, ChatConfig, Gemini, Chat
14+
from mcp_run import Client, ClientConfig
1415
from .chat import SYSTEM_PROMPT
1516

1617
CHAT_HELP = """
@@ -35,7 +36,7 @@ async def list_cmd(client, args):
3536

3637
async def tool_cmd(client, args):
3738
try:
38-
res = client.call(tool=args.name, input=json.loads(args.input))
39+
res = client.call_tool(tool=args.name, input=json.loads(args.input))
3940
for c in res:
4041
if c.type == "text":
4142
print(c.text)
@@ -178,8 +179,8 @@ def main():
178179
choices=["off", "critical", "fatal", "warn", "info", "debug", "error"],
179180
help="Select log level",
180181
)
181-
args.add_argument("--base-url", default="https://www.mcp.run", help="nURL")
182-
args.add_argument("--profile", default="default", help="mcpx profile")
182+
args.add_argument("--base-url", default="https://www.mcp.run", help="mcp.run URL")
183+
args.add_argument("--profile", default="~/default", help="mcpx profile")
183184
sub = args.add_subparsers(title="subcommand", help="subcommands", required=True)
184185

185186
# List subcommand

mcpx_py/builtin_tools.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .client import Tool
1+
from mcp_run import Tool
22

33
SEARCH = Tool(
44
name="mcp_run_search_servlets",
@@ -31,3 +31,33 @@
3131
"required": [],
3232
},
3333
)
34+
35+
GET_PROFILES = Tool(
36+
name="mcp_run_get_profiles",
37+
description="""
38+
List all profiles for the current user.
39+
""",
40+
input_schema={
41+
"type": "object",
42+
"properties": {},
43+
"required": [],
44+
},
45+
)
46+
47+
48+
SET_PROFILE = Tool(
49+
name="mcp_run_set_profile",
50+
description="""
51+
Set the active profile
52+
""",
53+
input_schema={
54+
"type": "object",
55+
"properties": {
56+
"profile": {
57+
"type": "string",
58+
"description": """The name of the profile to set as active""",
59+
},
60+
},
61+
"required": ["profile"],
62+
},
63+
)

mcpx_py/chat.py

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import os
1111
import asyncio
1212

13-
from .client import Client, Tool
13+
from mcp_run import Client, Tool
1414
from . import builtin_tools
1515

1616

@@ -234,7 +234,11 @@ def chat_sync(self, *args, **kw) -> Iterator[ChatResponse]:
234234
yield res
235235

236236
def _builtin_tools(self) -> List[object]:
237-
return [self._convert_tool(builtin_tools.SEARCH)]
237+
return [
238+
self._convert_tool(builtin_tools.SEARCH),
239+
self._convert_tool(builtin_tools.GET_PROFILES),
240+
self._convert_tool(builtin_tools.SET_PROFILE),
241+
]
238242

239243
def get_tools(self) -> List[object]:
240244
"""
@@ -253,16 +257,45 @@ def get_tools(self) -> List[object]:
253257
async def call_tool(self, name: str, input: object, **kw) -> Iterator[ChatResponse]:
254258
"""
255259
Call a tool by name with the given input, the extra arguments are passed to
256-
`Client.call`
260+
`Client.call_tool`
257261
"""
258262
if isinstance(input, str):
259263
input = json.loads(input)
260264
self.logger.info(f"Calling tool: {name} with input: {input}")
261265
try:
262266
# Handle builtin tools
263267
if name in ["mcp_run_search_servlets"]:
264-
res = self.config.client.search(input["q"])
265-
c = json.dumps(res)
268+
x = []
269+
for r in self.config.client.search(input["q"]):
270+
x.append(
271+
{
272+
"slug": r.slug,
273+
"meta": r.meta,
274+
"installation_count": r.installation_count,
275+
}
276+
)
277+
c = json.dumps(x)
278+
yield ChatResponse(
279+
role="tool",
280+
content=c,
281+
tool=ToolCall(name=name, input=input),
282+
)
283+
async for res in self.chat(c, tool=name):
284+
yield res
285+
return
286+
elif name in ["mcp_run_get_profiles"]:
287+
p = []
288+
for user, u in self.config.client.profiles.items():
289+
if user == '~':
290+
continue
291+
for profile in u.values():
292+
p.append(
293+
{
294+
"name": f"{user}/{profile.slug.name}",
295+
"description": profile.description,
296+
}
297+
)
298+
c = json.dumps(p)
266299
yield ChatResponse(
267300
role="tool",
268301
content=c,
@@ -271,8 +304,20 @@ async def call_tool(self, name: str, input: object, **kw) -> Iterator[ChatRespon
271304
async for res in self.chat(c, tool=name):
272305
yield res
273306
return
307+
elif name in ["mcp_run_set_profile"]:
308+
profile = input["profile"]
309+
c = f"Active profile set to {profile}"
310+
yield ChatResponse(
311+
role="tool",
312+
content=c,
313+
tool=ToolCall(name=name, input=input),
314+
315+
)
316+
async for res in self.chat(c, tool=name):
317+
yield res
318+
return
274319

275-
res = self.config.client.call(tool=name, input=input, **kw)
320+
res = self.config.client.call_tool(tool=name, params=input, **kw)
276321
for c in res.content:
277322
if c.type == "text":
278323
yield ChatResponse(

0 commit comments

Comments
 (0)