iflow-mcp_lukeage-python-pip-mcp 0.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.
@@ -0,0 +1,40 @@
1
+ Metadata-Version: 2.4
2
+ Name: iflow-mcp_lukeage-python-pip-mcp
3
+ Version: 0.1.0
4
+ Summary: Minimal Example Implementation of an Anthropic MCP Server in Python with Pip
5
+ Project-URL: Homepage, https://github.com/lukeage/python-pip-mcp
6
+ Project-URL: Repository, https://github.com/lukeage/python-pip-mcp
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.8
9
+ Requires-Dist: anthropic>=0.15.0
10
+ Requires-Dist: mcp>=0.1.0
11
+ Requires-Dist: python-dotenv>=1.0.0
12
+ Description-Content-Type: text/markdown
13
+
14
+ # python-pip-mcp
15
+
16
+ Minimal Example Implementation of an Anthropic [MCP](https://modelcontextprotocol.io/introduction) Client and Server in Python with Pip.
17
+
18
+ The goal of this repository is to provide a reference implementation of a **mcp client and server** that can be easily debugged in VSCode on Windows using the Python / Python Debugger extension.
19
+
20
+ Alternative IDEs and OS are not tested but should work with minimal adjustments, too.
21
+
22
+ ## Installation
23
+
24
+ ```powershell
25
+ # create venv
26
+ python -m venv myenv
27
+ myenv\Scripts\activate
28
+
29
+ # install requirements
30
+ pip install -r requirements.txt
31
+
32
+ # create a .env file and set your anthropic api key
33
+ cp .env.sample .env
34
+
35
+ # run mcp_client.py script
36
+ python mcp_client.py
37
+
38
+ # query for current time
39
+ ```
40
+
@@ -0,0 +1,7 @@
1
+ mcp_client.py,sha256=RH5rYC2KnsIMKnJlszCU8Y_1XoVtiglEbzCPqlyb-4g,4897
2
+ mcp_server.py,sha256=0jbowV3SwKxkZnO3KyuTcbWR1lzHoy9bv1alxxOozco,2075
3
+ iflow_mcp_lukeage_python_pip_mcp-0.1.0.dist-info/METADATA,sha256=yB60IlfN_X4RMSP90IngzxDc17GjsVS2gZqsrsaCuvc,1213
4
+ iflow_mcp_lukeage_python_pip_mcp-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
5
+ iflow_mcp_lukeage_python_pip_mcp-0.1.0.dist-info/entry_points.txt,sha256=WYlei30NJFPcCHQvEjOhREZhnCmL7om-EyNcwxRyP1c,48
6
+ iflow_mcp_lukeage_python_pip_mcp-0.1.0.dist-info/licenses/LICENSE,sha256=7UhW6GDE56ZAQopiE5nNZGOr-xez3mMm0sIfIcT_ewk,1067
7
+ iflow_mcp_lukeage_python_pip_mcp-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pythonpipmcp = mcp_server:cli
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Lukas Hain
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
mcp_client.py ADDED
@@ -0,0 +1,149 @@
1
+ # mcp_client.py
2
+ import asyncio
3
+ import sys
4
+ import os
5
+ from typing import Optional
6
+ from contextlib import AsyncExitStack
7
+
8
+ from mcp import ClientSession, StdioServerParameters
9
+ from mcp.client.stdio import stdio_client
10
+
11
+ from anthropic import Anthropic
12
+ from dotenv import load_dotenv
13
+
14
+ load_dotenv() # load environment variables from .env
15
+
16
+ class MCPClient:
17
+ def __init__(self):
18
+ # Initialize session and client objects
19
+ self.session: Optional[ClientSession] = None
20
+ self.exit_stack = AsyncExitStack()
21
+ self.anthropic = Anthropic()
22
+
23
+ async def connect_to_server(self, server_script_path: str):
24
+ """Connect to an MCP server"""
25
+ # Get the current Python interpreter path
26
+ python_path = sys.executable
27
+
28
+ server_params = StdioServerParameters(
29
+ command=python_path,
30
+ args=[server_script_path]
31
+ )
32
+
33
+ stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
34
+ self.stdio, self.write = stdio_transport
35
+ self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
36
+
37
+ await self.session.initialize()
38
+
39
+ # List available tools
40
+ response = await self.session.list_tools()
41
+ tools = response.tools
42
+ print("\nConnected to server with tools:", [tool.name for tool in tools])
43
+
44
+ async def process_query(self, query: str) -> str:
45
+ """Process a query using Claude and available tools"""
46
+ messages = [
47
+ {
48
+ "role": "user",
49
+ "content": query
50
+ }
51
+ ]
52
+
53
+ response = await self.session.list_tools()
54
+ available_tools = [{
55
+ "name": tool.name,
56
+ "description": tool.description,
57
+ "input_schema": tool.inputSchema
58
+ } for tool in response.tools]
59
+
60
+ # Initial Claude API call
61
+ response = self.anthropic.messages.create(
62
+ model="claude-3-sonnet-20240229",
63
+ max_tokens=1000,
64
+ messages=messages,
65
+ tools=available_tools
66
+ )
67
+
68
+ # Process response and handle tool calls
69
+ tool_results = []
70
+ final_text = []
71
+
72
+ for content in response.content:
73
+ if content.type == 'text':
74
+ final_text.append(content.text)
75
+ elif content.type == 'tool_use':
76
+ tool_name = content.name
77
+ tool_args = content.input
78
+
79
+ # Execute tool call
80
+ result = await self.session.call_tool(tool_name, tool_args)
81
+ tool_results.append({"call": tool_name, "result": result})
82
+ final_text.append(f"[Calling tool {tool_name} with args {tool_args}]")
83
+
84
+ # Continue conversation with tool results
85
+ if hasattr(content, 'text') and content.text:
86
+ messages.append({
87
+ "role": "assistant",
88
+ "content": content.text
89
+ })
90
+ messages.append({
91
+ "role": "user",
92
+ "content": f"Tool {tool_name} returned: {result.content[0].text}"
93
+ })
94
+
95
+ # Get next response from Claude
96
+ response = self.anthropic.messages.create(
97
+ model="claude-3-5-sonnet-latest",
98
+ max_tokens=1000,
99
+ messages=messages,
100
+ )
101
+
102
+ final_text.append(response.content[0].text)
103
+
104
+ return "\n".join(final_text)
105
+
106
+ async def chat_loop(self):
107
+ """Run an interactive chat loop"""
108
+ print("\nMCP Client Started!")
109
+ print("Type your queries or 'quit' to exit.")
110
+
111
+ while True:
112
+ try:
113
+ query = input("\nQuery: ").strip()
114
+
115
+ if query.lower() == 'quit':
116
+ break
117
+
118
+ response = await self.process_query(query)
119
+ print("\n" + response)
120
+
121
+ except Exception as e:
122
+ print(f"\nError: {str(e)}")
123
+
124
+ async def cleanup(self):
125
+ """Clean up resources"""
126
+ await self.exit_stack.aclose()
127
+
128
+ async def main():
129
+ if len(sys.argv) < 2:
130
+ print("Usage: python client.py <path_to_server_script>")
131
+ server_path = "./mcp_server.py"
132
+ else:
133
+ server_path = sys.argv[1]
134
+
135
+ # Set binary mode for stdin/stdout on Windows
136
+ if sys.platform == 'win32':
137
+ import msvcrt
138
+ msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
139
+ msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
140
+
141
+ client = MCPClient()
142
+ try:
143
+ await client.connect_to_server(server_path)
144
+ await client.chat_loop()
145
+ finally:
146
+ await client.cleanup()
147
+
148
+ if __name__ == "__main__":
149
+ asyncio.run(main())
mcp_server.py ADDED
@@ -0,0 +1,72 @@
1
+ # mcp_server.py
2
+ import asyncio
3
+ from datetime import datetime
4
+ import sys
5
+ import os
6
+ from mcp.server import Server, NotificationOptions
7
+ from mcp.server.models import InitializationOptions
8
+ import mcp.server.stdio
9
+ import mcp.types as types
10
+
11
+ # Create server instance
12
+ server = Server("date-server")
13
+
14
+ @server.list_tools()
15
+ async def handle_list_tools() -> list[types.Tool]:
16
+ """List available tools"""
17
+ return [
18
+ types.Tool(
19
+ name="get-datetime",
20
+ description="Get the current date and time",
21
+ inputSchema={
22
+ "type": "object",
23
+ "properties": {}, # No input needed
24
+ "required": []
25
+ }
26
+ )
27
+ ]
28
+
29
+ @server.call_tool()
30
+ async def handle_call_tool(
31
+ name: str,
32
+ arguments: dict | None
33
+ ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
34
+ """Handle tool execution"""
35
+ if name == "get-datetime":
36
+ current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
37
+ return [
38
+ types.TextContent(
39
+ type="text",
40
+ text=f"Current date and time: {current_time}"
41
+ )
42
+ ]
43
+ raise ValueError(f"Unknown tool: {name}")
44
+
45
+ async def main():
46
+ """Run the server"""
47
+ # Set binary mode for stdin/stdout on Windows
48
+ if sys.platform == 'win32':
49
+ import msvcrt
50
+ msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
51
+ msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
52
+
53
+ async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
54
+ await server.run(
55
+ read_stream,
56
+ write_stream,
57
+ InitializationOptions(
58
+ server_name="date-server",
59
+ server_version="0.1.0",
60
+ capabilities=server.get_capabilities(
61
+ notification_options=NotificationOptions(),
62
+ experimental_capabilities={},
63
+ ),
64
+ )
65
+ )
66
+
67
+ if __name__ == "__main__":
68
+ asyncio.run(main())
69
+
70
+ def cli():
71
+ """CLI entry point for setuptools"""
72
+ asyncio.run(main())