Skip to content

Commit e0f695b

Browse files
committed
Add Tool Calling and MCP content
1 parent 5bcdbc2 commit e0f695b

File tree

8 files changed

+415
-375
lines changed

8 files changed

+415
-375
lines changed

docs/assets/04_llm_mcp.png

5.57 MB
Loading

docs/assets/04_mcp.png

79.8 KB
Loading

docs/assets/04_tool_calling.png

5.86 MB
Loading

docs/module_04_llm_ops/03_mcp_getting_started.ipynb

Lines changed: 136 additions & 375 deletions
Large diffs are not rendered by default.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
2+
import os
3+
import json
4+
from fastmcp import FastMCP
5+
from scraper_utils import NB_Markdown_Scraper
6+
7+
mcp = FastMCP(
8+
name="Calculation Server",
9+
instructions="""
10+
This server provides mathematical capabilities
11+
""",)
12+
13+
@mcp.tool
14+
def greet(self,name: str= None):
15+
'''Greets the User'''
16+
if not name:
17+
return "Hi, I am NotebookServer"
18+
else:
19+
return f"Hi {name}, I am NotebookServer"
20+
21+
@mcp.tool
22+
def add_two_numbers(a: int, b: int) -> int:
23+
"""
24+
Add two numbers
25+
Args:
26+
a: The first integer number
27+
b: The second integer number
28+
29+
Returns:
30+
int: The sum of the two numbers
31+
"""
32+
return a + b
33+
34+
if __name__ == "__main__":
35+
# Initialize and run the server
36+
mcp.run(transport='stdio')
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import asyncio
2+
from fastmcp import Client, FastMCP
3+
4+
# In-memory server (ideal for testing)
5+
server = FastMCP("TestServer")
6+
client = Client(server)
7+
8+
# HTTP server
9+
client = Client("https://example.com/mcp")
10+
11+
# Local Python script
12+
client = Client("notebook_server.py")
13+
14+
async def main():
15+
async with client:
16+
# Basic server interaction
17+
await client.ping()
18+
19+
# List available operations
20+
tools = await client.list_tools()
21+
resources = await client.list_resources()
22+
prompts = await client.list_prompts()
23+
24+
print("-"*30)
25+
print("Tools:")
26+
print("-"*30)
27+
print(tools)
28+
print("-"*30)
29+
print("Resources:")
30+
print("-"*30)
31+
print(resources)
32+
print("-"*30)
33+
print("Prompts:")
34+
print(prompts)
35+
print("-"*30)
36+
# Execute operations
37+
await client.call_tool("scrape_markdowns", {})
38+
result = await client.call_tool("write_json", {"file_name":"test_mcp_server.json"})
39+
print("-"*30)
40+
print(f"Result of tool call for write_json:\n{result}")
41+
42+
asyncio.run(main())
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
from mcp import ClientSession, StdioServerParameters, types
2+
from mcp.client.stdio import stdio_client
3+
from typing import List
4+
import asyncio
5+
import nest_asyncio
6+
# from ollama import Client
7+
import ollama
8+
9+
10+
nest_asyncio.apply()
11+
12+
class MCP_ChatBot:
13+
14+
def __init__(self):
15+
# Initialize session and client objects
16+
self.session: ClientSession = None
17+
self.available_tools: List[dict] = []
18+
# self.open_ai_compat_client = Client(
19+
# host='http://localhost:11434',
20+
# # headers={'x-some-header': 'some-value'}
21+
# )
22+
self.model_name = 'llama3.1'
23+
24+
async def process_query(self, query):
25+
messages = [{'role':'user', 'content':query}]
26+
response = ollama.chat(
27+
model=self.model_name,
28+
messages=messages,tools=self.available_tools) #self.open_ai_compat_client
29+
process_query = True
30+
print(response)
31+
while process_query:
32+
assistant_content = []
33+
# for content in response.message.content:
34+
content = response.message.content
35+
tool_calls = response.message.tool_calls
36+
37+
if not tool_calls:
38+
print("No tool calls detected")
39+
assistant_content.append(content)
40+
if(len(response.message.content) >1):
41+
process_query= False
42+
elif tool_calls:
43+
print(" tool calls detected")
44+
assistant_content.append(content)
45+
messages.append({'role':'assistant', 'content':assistant_content})
46+
tool_args = tool_calls.function.arguments
47+
tool_name = tool_calls.function.name
48+
49+
print(f"Calling tool {tool_name} with args {tool_args}")
50+
51+
# Call a tool
52+
# tool invocation through the client session
53+
result = await self.session.call_tool(tool_name, arguments=tool_args)
54+
messages.append({"role": "user",
55+
"message": [
56+
{
57+
# "tool_use_id":tool_id,
58+
"content": result.message.content
59+
}
60+
]
61+
})
62+
response = self.open_ai_compat_client.chat(model=self.model_name, messages=messages,tools=self.available_tools)
63+
if not response.message.tool_calls:
64+
print(response.message.content)
65+
process_query= False
66+
67+
68+
69+
async def chat_loop(self):
70+
"""Run an interactive chat loop"""
71+
print("\nMCP Chatbot Started!")
72+
print("Type your queries or 'quit' to exit.")
73+
74+
while True:
75+
try:
76+
query = input("\nQuery: ").strip()
77+
78+
if query.lower() == 'quit':
79+
break
80+
81+
await self.process_query(query)
82+
print("\n")
83+
84+
except Exception as e:
85+
print(f"\nError: {str(e)}")
86+
87+
async def connect_to_server_and_run(self):
88+
# Create server parameters for stdio connection
89+
server_params = StdioServerParameters(
90+
command="python3", # Executable
91+
args=["calculation_server.py"], # Optional command line arguments
92+
env=None, # Optional environment variables
93+
)
94+
async with stdio_client(server_params) as (read, write):
95+
async with ClientSession(read, write) as session:
96+
self.session = session
97+
# Initialize the connection
98+
await session.initialize()
99+
100+
# List available tools
101+
response = await session.list_tools()
102+
103+
tools = response.tools
104+
print("\nConnected to server with tools:", [tool.name for tool in tools])
105+
106+
self.available_tools = [{
107+
"name": tool.name,
108+
"description": tool.description,
109+
"parameters": tool.inputSchema,
110+
} for tool in response.tools]
111+
112+
await self.chat_loop()
113+
114+
115+
async def main():
116+
chatbot = MCP_ChatBot()
117+
await chatbot.connect_to_server_and_run()
118+
119+
120+
if __name__ == "__main__":
121+
asyncio.run(main())
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
2+
import os
3+
import json
4+
from fastmcp import FastMCP
5+
from scraper_utils import NB_Markdown_Scraper
6+
7+
mcp = FastMCP(
8+
name="Notebook Server",
9+
instructions="""
10+
This server provides a markdown scraper utility
11+
and a tool to write JSON file
12+
""",)
13+
14+
class NotebookServer():
15+
def __init__(self,mcp_instance):
16+
self.notebook_scraper = NB_Markdown_Scraper(input_paths=[f'../Users/raghavbali/Documents/raghav/work/github/mastering_llms_workshop/docs/{d}' for d in os.listdir("./Users/raghavbali/Documents/raghav/work/github/mastering_llms_workshop/docs/") if d.startswith("module")])
17+
18+
# Register methods
19+
mcp_instance.tool(self.greet)
20+
mcp_instance.tool(self.get_notebook_list)
21+
mcp_instance.tool(self.get_markdown_from_notebook)
22+
mcp_instance.tool(self.notebook_scraper.scrape_markdowns)
23+
mcp_instance.tool(self.write_json)
24+
mcp_instance.tool(self.add_two_numbers)
25+
mcp_instance.resource("resource://data")(self.resource_method)
26+
27+
def greet(self,name: str= None):
28+
'''Greets the User'''
29+
if not name:
30+
return "Hi, I am NotebookServer"
31+
else:
32+
return f"Hi {name}, I am NotebookServer"
33+
def add_two_numbers(self,a: int, b: int) -> int:
34+
"""
35+
Add two numbers
36+
Args:
37+
a: The first integer number
38+
b: The second integer number
39+
40+
Returns:
41+
int: The sum of the two numbers
42+
"""
43+
return a + b
44+
45+
def get_notebook_list(self):
46+
'''Returns List of Notebooks Scraped'''
47+
return list(self.notebook_scraper.notebook_md_dict.keys())
48+
49+
def get_markdown_from_notebook(self,notebook_name):
50+
'''Returns Markdown Cells for specified notebook'''
51+
if notebook_name in list(self.notebook_scraper.notebook_md_dict.keys()):
52+
return self.notebook_scraper.notebook_md_dict[notebook_name]
53+
else:
54+
return f"Requested notebook ({notebook_name}) does not exist"
55+
56+
def write_json(self,file_name: str):
57+
'''Tool to write a json file in the format notebook:markdown content'''
58+
try:
59+
with open(f"./{file_name}", "w") as record_file:
60+
json.dump(self.notebook_scraper.notebook_md_dict,record_file)
61+
return f"File:{file_name} written successfully"
62+
except Exception as ex:
63+
return f"Could not write {file_name} due to {ex}"
64+
65+
66+
def resource_method(self):
67+
return """
68+
Resources provide read-only access to data for the LLM or client application. When a client requests a resource URI:
69+
+ FastMCP finds the corresponding resource definition.
70+
+ If it’s dynamic (defined by a function), the function is executed.
71+
+ The content (text, JSON, binary data) is returned to the client.
72+
This allows LLMs to access files, database content, configuration, or dynamically generated information relevant to the conversation.
73+
"""
74+
75+
# The methods are automatically registered when creating the instance
76+
provider = NotebookServer(mcp)
77+
78+
if __name__ == "__main__":
79+
# Initialize and run the server
80+
mcp.run(transport='stdio')

0 commit comments

Comments
 (0)