Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 46 additions & 2 deletions docs/mcp_excel_server.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ After the client start, you will be prompted for the query.
Query: Who are you?.
```

### Available MCP Server Tools:
### Available Excel MCP Server Tools:

These tool are using mcp resources that are
manipulating and analysing the excel files.
Expand All @@ -67,12 +67,50 @@ manipulating and analysing the excel files.
file
- remove_file: Removes a file from the system

The mcp client in this proect is enabled with
#### Connecting SQL DB with MCP Client:

The mcp client in this project is enabled with
SQLite db connection. The same has been discussed
in this [video](https://youtu.be/FAMg9kZpMQw)

#### Multi Server Connecting with MCP Client:

Multi_server_client.py script is implemented to
connect with multiple mcp servers. Follow the.
below command for executing the script when you
are connecting with multiple python mcp server.

Note: The supporting packages for the server files
has to be made available either through uv or
venv. Else the server tools cannot execute.

```
uv run Multi_server_client.py server1.py
server2.py
```

If you are connecting with Javascript mcp server,
then you need to have the node modules installed
in the same folder where you are executing the mcp
client.

In case of the JS servers, the server file needs
to be usually compiled using npm run build
command. Refer to the documentation of the server
for more details.

The execution of the script can be done as below.

```
uv run Multi_server_client.py server1.py
index.js
```

### Testing with mcp inspector:

When you need use mcp inspector to debug the code,
use the command below. Ensure you have

[npx](https://docs.npmjs.com/cli/v8/commands/npx)
and [node](https://nodejs.org/en/download)
installed
Expand All @@ -91,6 +129,12 @@ server.py
- Notion Code referred from:
[Notion Example code](../fw_ex/notionapi_spike/)

- GMail MCP Server :
[Gmail Server](https://github.com/GongRzhe/Gmail-MCP-Server)

````

```

```
````
250 changes: 250 additions & 0 deletions mcp_excel_server/multi_server_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "anthropic",
# "mcp",
# "openpyxl",
# "python-dotenv",
# ]
# ///
import asyncio
from typing import Optional, List
from contextlib import AsyncExitStack

# pyright: reportMissingImports=false
# pyright: reportOptionalSubscript=false

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

from anthropic import Anthropic
from dotenv import load_dotenv

# uncomment this when running in your local environment
# ensure you have updated the .env file with the Anthropic API Key
# load_dotenv() # load environment variables from .env


class MCPClient:
def __init__(self):
# Initialize session and client objects
self.sessions: Optional[List[ClientSession]] = []
self.exit_stack = AsyncExitStack()
self.anthropic = Anthropic()

async def connect_to_servers(self, server_script_paths: List[str]):
for server_script_path in server_script_paths:
await self.connect_to_server(server_script_path)

# List available tools
for idx, loc in enumerate(self.sessions):
response = await loc.list_tools()
loc_tools = response.tools
print(
f"\nConnected to {server_script_paths[idx]} server with tools:",
[tool.name for tool in loc_tools],
)

# methods will go here
async def connect_to_server(self, server_script_path: str):
"""Connect to notion 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")
if not (is_python or is_js):
raise ValueError("Server script must be a .py or .js file")

command = "python" if is_python else "node"
server_params = StdioServerParameters(
command=command, 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

loc_session = await self.exit_stack.enter_async_context(
ClientSession(self.stdio, self.write)
)
# append the session to list of sessions
self.sessions.append(loc_session)

await self.sessions[-1].initialize()

async def create_or_connect_db(self):
import sqlite3

# Connect to SQLite (creates a file called chatbot.db)
conn = sqlite3.connect("mcphistory.db")
cursor = conn.cursor()

# Create a table to store query-response pairs
cursor.execute("""
CREATE TABLE IF NOT EXISTS conversations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
query TEXT NOT NULL,
response TEXT NOT NULL
)
""")

conn.commit()
conn.close()

async def save_history(self, query: str, response: str):
import sqlite3

# Connect to SQLite (creates a file called chatbot.db)
conn = sqlite3.connect("mcphistory.db")
cursor = conn.cursor()

# Insert the query-response pair into the table
cursor.execute(
"INSERT INTO conversations (query, response) VALUES (?, ?)",
(query, response),
)

conn.commit()
conn.close()

async def read_history(self):
import sqlite3

# Connect to SQLite (creates a file called chatbot.db)
conn = sqlite3.connect("mcphistory.db")
cursor = conn.cursor()

# Insert the query-response pair into the table
cursor.execute("SELECT query, response FROM conversations")

history = ""

rows = cursor.fetchall()
for row in rows:
history += f"Query: {row[0]}\nResponse: {row[1]}\n{history}\n"

return history

async def process_query(self, query: str) -> str:
"""Process a query using Claude and available tools"""

hal_system = """You are hal3025, an expert in working on filesystem and excel sheets.
You have access to a local filesystem with read and write access.
You are very good in analysing xlsx files and you can use the available
tools with you and return the results to the user.
When user asks you to refer to past conversation or history, then refer to
the query and response available with you and respond. Don't say you
do not have access to history.
Just use the tools, and provide the updates the tools are giving.
Do not use external python packages for the analysis.
Use the tools that are available to you.
Do not apologize. Do not provide guidance or examples. Do not share what you cannot do.
Please do not provide the python code for the user requests.
Do not explain how something can be done."""

messages = [{"role": "user", "content": query}]
available_tools = []
for session in self.sessions:
response = await session.list_tools()
available_tools.extend([
{
"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="claude-3-5-haiku-20241022",
max_tokens=1000,
system=hal_system,
messages=messages,
tools=available_tools,
)

# Process response and handle tool calls
tool_results = []
final_text = []

for content in response.content: # each tool call will handle seperately
if content.type == "text":
final_text.append(content.text)
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)
tool_results.append({"call": tool_name, "result": result})
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})

# Get next response from Claude
response = self.anthropic.messages.create(
model="claude-3-5-haiku-20241022",
system=hal_system,
max_tokens=1000,
messages=messages,
)

final_text.append(response.content[0].text)
return "\n".join(final_text)

async def chat_loop(self):
"""Run an interactive chat loop"""
print("\nMCP Client Started!")
print("Type your queries or 'quit' to exit.")
history = await self.read_history()
while True:
try:
query = input("\nInteract with Excel File here: ").strip()
# below the history is assembled
print(history)
query_with_history = (
f"Previous conversation:\n{history}\n Query: {query}"
)
if query.lower() == "quit":
break

response = await self.process_query(query_with_history)
# here is response is apended to history
await self.save_history(query, response)
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) < 3:
# we use uv package manager so uv run mcpclient.py mcpserver.py
print("Usage: uv run mcpclient.py server1.py server2.js")
sys.exit(1)

client = MCPClient()

try:
await client.create_or_connect_db()
await client.connect_to_servers(sys.argv[1:])
await client.chat_loop()
finally:
await client.cleanup()


if __name__ == "__main__":
import sys

asyncio.run(main())