diff --git a/mcp-client-python/client.py b/mcp-client-python/client.py index 219f1ae..8b77820 100644 --- a/mcp-client-python/client.py +++ b/mcp-client-python/client.py @@ -1,34 +1,33 @@ import asyncio -from typing import Optional from contextlib import AsyncExitStack - -from mcp import ClientSession, StdioServerParameters -from mcp.client.stdio import stdio_client +from pathlib import Path from anthropic import Anthropic from dotenv import load_dotenv -from pathlib import Path +from mcp import ClientSession, StdioServerParameters +from mcp.client.stdio import stdio_client load_dotenv() # load environment variables from .env # Claude model constant ANTHROPIC_MODEL = "claude-sonnet-4-5" + class MCPClient: def __init__(self): # Initialize session and client objects - self.session: Optional[ClientSession] = None + self.session: ClientSession | None = None self.exit_stack = AsyncExitStack() self.anthropic = Anthropic() async def connect_to_server(self, server_script_path: str): """Connect to an MCP server - + Args: server_script_path: Path to the server script (.py or .js) """ - is_python = server_script_path.endswith('.py') - is_js = server_script_path.endswith('.js') + is_python = server_script_path.endswith(".py") + is_js = server_script_path.endswith(".js") if not (is_python or is_js): raise ValueError("Server script must be a .py or .js file") @@ -40,16 +39,14 @@ async def connect_to_server(self, server_script_path: str): env=None, ) else: - server_params = StdioServerParameters( - command="node", args=[server_script_path], env=None - ) + server_params = StdioServerParameters(command="node", args=[server_script_path], env=None) stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params)) self.stdio, self.write = stdio_transport self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write)) - + await self.session.initialize() - + # List available tools response = await self.session.list_tools() tools = response.tools @@ -57,52 +54,37 @@ async def connect_to_server(self, server_script_path: str): async def process_query(self, query: str) -> str: """Process a query using Claude and available tools""" - messages = [ - { - "role": "user", - "content": query - } - ] + messages = [{"role": "user", "content": query}] response = await self.session.list_tools() - available_tools = [{ - "name": tool.name, - "description": tool.description, - "input_schema": tool.inputSchema - } for tool in response.tools] + available_tools = [ + {"name": tool.name, "description": tool.description, "input_schema": tool.inputSchema} + for tool in response.tools + ] # Initial Claude API call response = self.anthropic.messages.create( - model=ANTHROPIC_MODEL, - max_tokens=1000, - messages=messages, - tools=available_tools + model=ANTHROPIC_MODEL, max_tokens=1000, messages=messages, tools=available_tools ) # Process response and handle tool calls final_text = [] for content in response.content: - if content.type == 'text': + if content.type == "text": final_text.append(content.text) - elif content.type == 'tool_use': + elif content.type == "tool_use": tool_name = content.name tool_args = content.input - + # Execute tool call result = await self.session.call_tool(tool_name, tool_args) final_text.append(f"[Calling tool {tool_name} with args {tool_args}]") # Continue conversation with tool results - if hasattr(content, 'text') and content.text: - messages.append({ - "role": "assistant", - "content": content.text - }) - messages.append({ - "role": "user", - "content": result.content - }) + if hasattr(content, "text") and content.text: + messages.append({"role": "assistant", "content": content.text}) + messages.append({"role": "user", "content": result.content}) # Get next response from Claude response = self.anthropic.messages.create( @@ -119,29 +101,30 @@ async def chat_loop(self): """Run an interactive chat loop""" print("\nMCP Client Started!") print("Type your queries or 'quit' to exit.") - + while True: try: query = input("\nQuery: ").strip() - - if query.lower() == 'quit': + + if query.lower() == "quit": break - + response = await self.process_query(query) print("\n" + response) - + except Exception as e: print(f"\nError: {str(e)}") - + async def cleanup(self): """Clean up resources""" await self.exit_stack.aclose() + async def main(): if len(sys.argv) < 2: print("Usage: python client.py ") sys.exit(1) - + client = MCPClient() try: await client.connect_to_server(sys.argv[1]) @@ -149,6 +132,8 @@ async def main(): finally: await client.cleanup() + if __name__ == "__main__": import sys + asyncio.run(main()) diff --git a/mcp-client-python/pyproject.toml b/mcp-client-python/pyproject.toml index 9af264a..4bc10b8 100644 --- a/mcp-client-python/pyproject.toml +++ b/mcp-client-python/pyproject.toml @@ -9,3 +9,34 @@ dependencies = [ "mcp>=1.21.0", "python-dotenv>=1.2.1", ] + +[dependency-groups] +dev = [ + "ruff>=0.14.9", +] + +[tool.ruff] +line-length = 120 +target-version = "py310" +extend-exclude = ["README.md"] + +[tool.ruff.lint] +select = [ + "C4", # flake8-comprehensions + "C90", # mccabe + "E", # pycodestyle + "F", # pyflakes + "I", # isort + "PERF", # Perflint + "PL", # Pylint + "UP", # pyupgrade +] +ignore = ["PERF203", "PLC0415", "PLR0402"] +mccabe.max-complexity = 24 # Default is 10 + +[tool.ruff.lint.pylint] +allow-magic-value-types = ["bytes", "float", "int", "str"] +max-args = 23 # Default is 5 +max-branches = 23 # Default is 12 +max-returns = 13 # Default is 6 +max-statements = 102 # Default is 50 diff --git a/mcp-client-python/uv.lock b/mcp-client-python/uv.lock index f19860f..e989c3b 100644 --- a/mcp-client-python/uv.lock +++ b/mcp-client-python/uv.lock @@ -473,6 +473,11 @@ dependencies = [ { name = "python-dotenv" }, ] +[package.dev-dependencies] +dev = [ + { name = "ruff" }, +] + [package.metadata] requires-dist = [ { name = "anthropic", specifier = ">=0.72.0" }, @@ -480,6 +485,9 @@ requires-dist = [ { name = "python-dotenv", specifier = ">=1.2.1" }, ] +[package.metadata.requires-dev] +dev = [{ name = "ruff", specifier = ">=0.14.9" }] + [[package]] name = "pycparser" version = "2.23" @@ -826,6 +834,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ed/d2/4a73b18821fd4669762c855fd1f4e80ceb66fb72d71162d14da58444a763/rpds_py-0.28.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:5d0145edba8abd3db0ab22b5300c99dc152f5c9021fab861be0f0544dc3cbc5f", size = 552199, upload-time = "2025-10-22T22:24:26.54Z" }, ] +[[package]] +name = "ruff" +version = "0.14.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/1b/ab712a9d5044435be8e9a2beb17cbfa4c241aa9b5e4413febac2a8b79ef2/ruff-0.14.9.tar.gz", hash = "sha256:35f85b25dd586381c0cc053f48826109384c81c00ad7ef1bd977bfcc28119d5b", size = 5809165, upload-time = "2025-12-11T21:39:47.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/1c/d1b1bba22cffec02351c78ab9ed4f7d7391876e12720298448b29b7229c1/ruff-0.14.9-py3-none-linux_armv6l.whl", hash = "sha256:f1ec5de1ce150ca6e43691f4a9ef5c04574ad9ca35c8b3b0e18877314aba7e75", size = 13576541, upload-time = "2025-12-11T21:39:14.806Z" }, + { url = "https://files.pythonhosted.org/packages/94/ab/ffe580e6ea1fca67f6337b0af59fc7e683344a43642d2d55d251ff83ceae/ruff-0.14.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ed9d7417a299fc6030b4f26333bf1117ed82a61ea91238558c0268c14e00d0c2", size = 13779363, upload-time = "2025-12-11T21:39:20.29Z" }, + { url = "https://files.pythonhosted.org/packages/7d/f8/2be49047f929d6965401855461e697ab185e1a6a683d914c5c19c7962d9e/ruff-0.14.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d5dc3473c3f0e4a1008d0ef1d75cee24a48e254c8bed3a7afdd2b4392657ed2c", size = 12925292, upload-time = "2025-12-11T21:39:38.757Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e9/08840ff5127916bb989c86f18924fd568938b06f58b60e206176f327c0fe/ruff-0.14.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84bf7c698fc8f3cb8278830fb6b5a47f9bcc1ed8cb4f689b9dd02698fa840697", size = 13362894, upload-time = "2025-12-11T21:39:02.524Z" }, + { url = "https://files.pythonhosted.org/packages/31/1c/5b4e8e7750613ef43390bb58658eaf1d862c0cc3352d139cd718a2cea164/ruff-0.14.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa733093d1f9d88a5d98988d8834ef5d6f9828d03743bf5e338bf980a19fce27", size = 13311482, upload-time = "2025-12-11T21:39:17.51Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3a/459dce7a8cb35ba1ea3e9c88f19077667a7977234f3b5ab197fad240b404/ruff-0.14.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a1cfb04eda979b20c8c19550c8b5f498df64ff8da151283311ce3199e8b3648", size = 14016100, upload-time = "2025-12-11T21:39:41.948Z" }, + { url = "https://files.pythonhosted.org/packages/a6/31/f064f4ec32524f9956a0890fc6a944e5cf06c63c554e39957d208c0ffc45/ruff-0.14.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1e5cb521e5ccf0008bd74d5595a4580313844a42b9103b7388eca5a12c970743", size = 15477729, upload-time = "2025-12-11T21:39:23.279Z" }, + { url = "https://files.pythonhosted.org/packages/7a/6d/f364252aad36ccd443494bc5f02e41bf677f964b58902a17c0b16c53d890/ruff-0.14.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd429a8926be6bba4befa8cdcf3f4dd2591c413ea5066b1e99155ed245ae42bb", size = 15122386, upload-time = "2025-12-11T21:39:33.125Z" }, + { url = "https://files.pythonhosted.org/packages/20/02/e848787912d16209aba2799a4d5a1775660b6a3d0ab3944a4ccc13e64a02/ruff-0.14.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab208c1b7a492e37caeaf290b1378148f75e13c2225af5d44628b95fd7834273", size = 14497124, upload-time = "2025-12-11T21:38:59.33Z" }, + { url = "https://files.pythonhosted.org/packages/f3/51/0489a6a5595b7760b5dbac0dd82852b510326e7d88d51dbffcd2e07e3ff3/ruff-0.14.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72034534e5b11e8a593f517b2f2f2b273eb68a30978c6a2d40473ad0aaa4cb4a", size = 14195343, upload-time = "2025-12-11T21:39:44.866Z" }, + { url = "https://files.pythonhosted.org/packages/f6/53/3bb8d2fa73e4c2f80acc65213ee0830fa0c49c6479313f7a68a00f39e208/ruff-0.14.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:712ff04f44663f1b90a1195f51525836e3413c8a773574a7b7775554269c30ed", size = 14346425, upload-time = "2025-12-11T21:39:05.927Z" }, + { url = "https://files.pythonhosted.org/packages/ad/04/bdb1d0ab876372da3e983896481760867fc84f969c5c09d428e8f01b557f/ruff-0.14.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a111fee1db6f1d5d5810245295527cda1d367c5aa8f42e0fca9a78ede9b4498b", size = 13258768, upload-time = "2025-12-11T21:39:08.691Z" }, + { url = "https://files.pythonhosted.org/packages/40/d9/8bf8e1e41a311afd2abc8ad12be1b6c6c8b925506d9069b67bb5e9a04af3/ruff-0.14.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8769efc71558fecc25eb295ddec7d1030d41a51e9dcf127cbd63ec517f22d567", size = 13326939, upload-time = "2025-12-11T21:39:53.842Z" }, + { url = "https://files.pythonhosted.org/packages/f4/56/a213fa9edb6dd849f1cfbc236206ead10913693c72a67fb7ddc1833bf95d/ruff-0.14.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:347e3bf16197e8a2de17940cd75fd6491e25c0aa7edf7d61aa03f146a1aa885a", size = 13578888, upload-time = "2025-12-11T21:39:35.988Z" }, + { url = "https://files.pythonhosted.org/packages/33/09/6a4a67ffa4abae6bf44c972a4521337ffce9cbc7808faadede754ef7a79c/ruff-0.14.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7715d14e5bccf5b660f54516558aa94781d3eb0838f8e706fb60e3ff6eff03a8", size = 14314473, upload-time = "2025-12-11T21:39:50.78Z" }, + { url = "https://files.pythonhosted.org/packages/12/0d/15cc82da5d83f27a3c6b04f3a232d61bc8c50d38a6cd8da79228e5f8b8d6/ruff-0.14.9-py3-none-win32.whl", hash = "sha256:df0937f30aaabe83da172adaf8937003ff28172f59ca9f17883b4213783df197", size = 13202651, upload-time = "2025-12-11T21:39:26.628Z" }, + { url = "https://files.pythonhosted.org/packages/32/f7/c78b060388eefe0304d9d42e68fab8cffd049128ec466456cef9b8d4f06f/ruff-0.14.9-py3-none-win_amd64.whl", hash = "sha256:c0b53a10e61df15a42ed711ec0bda0c582039cf6c754c49c020084c55b5b0bc2", size = 14702079, upload-time = "2025-12-11T21:39:11.954Z" }, + { url = "https://files.pythonhosted.org/packages/26/09/7a9520315decd2334afa65ed258fed438f070e31f05a2e43dd480a5e5911/ruff-0.14.9-py3-none-win_arm64.whl", hash = "sha256:8e821c366517a074046d92f0e9213ed1c13dbc5b37a7fc20b07f79b64d62cc84", size = 13744730, upload-time = "2025-12-11T21:39:29.659Z" }, +] + [[package]] name = "sniffio" version = "1.3.1"