Learning Objectives:
Extended session: ~60 minutes (includes MCP content)
Session 3 - RAG:
Today - Tools:
Also today - MCP:
LLMs are great at:
LLMs are terrible at:
Solution: Let LLMs orchestrate, tools execute
Part 1: Direct Tool Integration (30 min)
Part 2: Model Context Protocol (25 min)
Part 3: Best Practices (5 min)
Tool use = LLM can request external function execution
Flow:
LLM acts as orchestrator, not executor
Also called:
Core concept: LLM outputs structured JSON specifying:
System responsibility:
User: “What is 1247 times 8593?”
LLM thinks: “I need a calculator for this”
LLM outputs:
{
"tool": "calculator",
"operation": "multiply",
"arguments": {
"a": 1247,
"b": 8593
}
}
System: Executes calculator(1247, 8593) → 10,715,671
LLM final answer: “1247 times 8593 equals 10,715,671”
Tools must be defined using JSON Schema:
{
"name": "get_gene_info",
"description": "Retrieve gene information from NCBI",
"parameters": {
"type": "object",
"properties": {
"gene_symbol": {
"type": "string",
"description": "Official gene symbol (e.g., 'BRCA1')"
},
"species": {
"type": "string",
"enum": ["human", "mouse", "rat"],
"default": "human"
}
},
"required": ["gene_symbol"]
}
}
Good schema → Accurate tool selection
The LLM uses:
name: Quick identificationdescription: When to use this toolparameters.description: What to passPrinciple: Write schemas for LLMs, not humans
Bad: “gene” (ambiguous) Good: “gene_symbol: Official HUGO gene symbol”
1. Descriptive names
{"name": "search_pubmed"} // ✅ Clear
{"name": "search"} // ❌ Ambiguous
2. Detailed descriptions
"description": "Search PubMed database for articles matching query terms. Returns up to max_results article PMIDs." // ✅
"description": "Searches PubMed" // ❌ Too vague
3. Constrain parameters
{"enum": ["human", "mouse"]} // ✅ Prevents invalid input
from litellm import completion
tools = [
{
"type": "function",
"function": {
"name": "get_sequence",
"description": "Fetch DNA sequence from NCBI",
"parameters": {
"type": "object",
"properties": {
"accession": {
"type": "string",
"description": "GenBank accession number"
}
},
"required": ["accession"]
}
}
}
]
response = completion(
model="anthropic/claude-sonnet-4-20250514",
messages=[{"role": "user", "content": "Get sequence for NC_000001.11"}],
tools=tools
)
response = completion(model="...", messages=[...], tools=tools)
# Check if LLM wants to use a tool
if response.choices[0].message.tool_calls:
tool_call = response.choices[0].message.tool_calls[0]
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
# Execute the function
if function_name == "get_sequence":
result = get_sequence(**arguments)
# Return result to LLM
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(result)
})
# Get final response
final = completion(model="...", messages=messages, tools=tools)
Implement a direct tool for gene lookup:
import requests
def get_gene_info(gene_symbol: str, species: str = "human"):
"""Get gene information from NCBI Gene database."""
# Map species to taxonomy IDs
tax_ids = {"human": "9606", "mouse": "10090"}
# Search for gene
search_url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi"
params = {
"db": "gene",
"term": f"{gene_symbol}[Gene Name] AND {tax_ids[species]}[Taxonomy ID]",
"retmode": "json"
}
response = requests.get(search_url, params=params)
data = response.json()
if data['esearchresult']['idlist']:
gene_id = data['esearchresult']['idlist'][0]
# Fetch details...
return {"gene_id": gene_id, "symbol": gene_symbol}
return {"error": "Gene not found"}
Live demo: Building NCBI gene lookup tool
Steps:
Watch for: How LLM decides to use tool
LLMs can chain multiple tool calls:
User: “Find papers about BRCA1 and summarize the most cited one”
Step 1: Call search_pubmed("BRCA1") → [PMID1, PMID2, …]
Step 2: Call get_citations(PMID1) → 523 citations
Step 3: Call get_citations(PMID2) → 1205 citations (highest)
Step 4: Call get_abstract(PMID2) → Abstract text
Step 5: Generate summary based on abstract
LLM orchestrates the sequence
Model Context Protocol (MCP) = Open standard by Anthropic
Purpose: Standardize how LLMs connect to:
Analogy: USB for AI
Released: Late 2024, gaining rapid adoption
Before MCP:
With MCP:
Result: Ecosystem of reusable tools
┌─────────────┐
│ LLM App │ (OpenCode, Claude Desktop, etc.)
│ (Client) │
└──────┬──────┘
│
│ MCP Protocol
│ (JSON-RPC)
│
┌──────┴──────┐
│ MCP Server │ (Provides tools/resources)
└──────┬──────┘
│
├─── Tool 1 (search files)
├─── Tool 2 (read database)
└─── Tool 3 (call API)
Client: Application using LLM Server: Provides tools/data Protocol: Standardized communication
MCP Server provides:
Client discovers and uses these automatically
| Aspect | Direct Tools | MCP |
|---|---|---|
| Reusability | Project-specific | Reusable across apps |
| Setup | Write Python functions | Install MCP server |
| Discovery | Manual schema definition | Automatic discovery |
| Security | DIY | Standardized permissions |
| Community | Limited sharing | Growing ecosystem |
| Flexibility | Full control | Some constraints |
When to use which? (covered later)
Available MCP servers:
File System:
@modelcontextprotocol/server-filesystemDatabases:
@modelcontextprotocol/server-postgres@modelcontextprotocol/server-sqliteDevelopment:
@modelcontextprotocol/server-github@context7/mcp-server (documentation)Web:
@modelcontextprotocol/server-fetch@modelcontextprotocol/server-puppeteerFull list: https://github.com/modelcontextprotocol/servers
Context7 = Documentation search and retrieval
What it provides:
Use case: “How do I use pandas.merge()?” → Context7 retrieves official docs
We’ll use this as our MCP example
MCP servers are typically Node.js packages:
# Install an MCP server globally
npm install -g @context7/mcp-server
# Or use npx to run without installing
npx @context7/mcp-server
MCP servers run as separate processes
Client applications connect to them via:
Example: Claude Desktop MCP config
{
"mcpServers": {
"context7": {
"command": "npx",
"args": ["-y", "@context7/mcp-server"],
"env": {
"CONTEXT7_API_KEY": "your-key-here"
}
},
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/workspace"]
}
}
}
Config location:
~/Library/Application Support/Claude/claude_desktop_config.json%APPDATA%/Claude/claude_desktop_config.jsonOnce configured, tools appear automatically:
User to LLM: “Search the pandas documentation for merge examples”
LLM sees available tools:
{
"name": "search_docs",
"description": "Search documentation using Context7",
"parameters": {
"query": "string",
"language": "string"
}
}
LLM calls tool:
{
"tool": "search_docs",
"arguments": {
"query": "pandas merge examples",
"language": "python"
}
}
Result: Relevant documentation returned to LLM
Live demo: Setting up and using Context7
Steps:
Key point: No Python code written by us!
MCP provides built-in security:
1. Explicit permissions
"filesystem": {
"command": "...",
"allowedPaths": ["/safe/directory"],
"readOnly": true
}
2. Capability-based access
3. Sandboxing
4. Audit trail
How client learns about tools:
1. Client starts MCP server
↓
2. Client: "initialize" request
↓
3. Server responds with capabilities:
{
"tools": [
{"name": "search_docs", ...},
{"name": "get_example", ...}
],
"resources": [...],
"prompts": [...]
}
↓
4. Client now knows all available tools
↓
5. LLM can call tools as needed
No manual schema writing!
MCP uses JSON-RPC 2.0:
Tool call request:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "search_docs",
"arguments": {
"query": "pandas merge"
}
}
}
Tool call response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "pandas.merge() documentation..."
}
]
}
}
Advanced topic (not covered today, but preview):
// TypeScript MCP Server skeleton
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new Server({
name: "my-bioinformatics-server",
version: "1.0.0"
});
// Register a tool
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: "query_gene_database",
description: "Query gene information",
inputSchema: { /* JSON Schema */ }
}]
}));
// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "query_gene_database") {
// Execute tool logic
return { result: data };
}
});
For future sessions or advanced students
Choose direct tools when:
✅ One-off integration - Won’t reuse elsewhere
✅ Simple function - Just a few lines of Python
✅ Full control needed - Custom error handling, caching
✅ No suitable MCP server - Tool doesn’t exist yet
✅ Learning - Understanding tool mechanics
✅ Prototype - Quick testing
Example: Custom lab equipment API
Choose MCP when:
✅ Reusability - Multiple projects will use it
✅ Security matters - Need controlled access
✅ Community tools - Existing MCP server available
✅ Standard operations - File access, DB queries, web search
✅ Multiple LLM apps - Want consistent interface
✅ Team collaboration - Shared tool infrastructure
Example: Documentation search, file system access
Best practice: Use both!
# MCP for standard operations
- File system access (MCP filesystem server)
- Documentation search (Context7 MCP)
- Database queries (MCP postgres server)
# Direct tools for custom needs
- Lab-specific API integrations
- Custom bioinformatics algorithms
- Proprietary database access
- One-off calculations
MCP for infrastructure, direct tools for specialization
def get_gene_info(gene_symbol: str):
try:
result = query_ncbi(gene_symbol)
return {"success": True, "data": result}
except requests.RequestException as e:
return {
"success": False,
"error": f"Network error: {str(e)}",
"retry": True
}
except KeyError:
return {
"success": False,
"error": "Gene not found",
"retry": False
}
Return structured errors to LLM
MCP has standardized error responses:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32603,
"message": "Internal error",
"data": {
"details": "Network timeout"
}
}
}
Error codes:
-32700: Parse error-32600: Invalid request-32601: Method not found-32603: Internal errorLLM can interpret and retry appropriately
Direct tools:
MCP:
Defense in depth: Security at all layers
Direct tools:
MCP:
Benchmark: MCP adds ~10-50ms per tool call Usually negligible compared to LLM latency
Growing rapidly:
Official servers: 20+ from Anthropic Community servers: 100+ and counting Platforms integrating MCP:
Future: Expected to become standard
Get involved: https://github.com/modelcontextprotocol
What we demonstrated:
✅ Direct tools:
✅ MCP:
Key insight: Both approaches valid, choose by needs
MCP tools can be chained:
User: “Find Python documentation for file I/O, then show me an example from our codebase”
Step 1 (MCP): Context7 searches Python docs Step 2 (MCP): Filesystem server searches local files Step 3 (LLM): Synthesizes example
MCP servers can be composed naturally
Looking ahead to Session 5:
OpenCode integrates with MCP servers:
Session 5 will show:
MCP is becoming infrastructure for AI coding
Two approaches: Direct tools (Python functions) vs MCP (standardized servers)
Direct tools: Full control, quick prototyping, custom needs
MCP: Reusability, security, community ecosystem
MCP architecture: Client-server with JSON-RPC protocol
Use both: MCP for infrastructure, direct for specialization
Context7 example: Documentation search without custom code
Security: MCP provides standardized permissions
Growing ecosystem: Many servers available, more coming
Theory: LLMs trained on API documentation and function signatures
Practice: Both direct tools and MCP leverage this training
Theory: Context window limitations require external data access
Practice: Tools (direct or MCP) fetch data dynamically
Theory: Standardization reduces cognitive load
Practice: MCP’s standard protocol simplifies integration
MCP:
Context7:
Direct tool integration:
Demo code: lectures/demos/session_4/
Next topic: AI Agents
The evolution:
Session 0: Foundation - LLM fundamentals
Session 1: Interface - Coding with LLMs
Session 2: Advanced prompting for single interactions
Session 3: RAG for knowledge retrieval
Session 4: Tools (direct + MCP) for dynamic actions
Session 5: Agents that autonomously loop: Perceive → Reason → Act
Next session: AI Agents - Autonomous Reasoning Systems
Optional homework: