Eric Dai
commited on
Commit
·
e51b1cb
1
Parent(s):
dbef77f
Add travel mcp server and client
Browse files- README.md +96 -2
- mcp_config.json +11 -0
- requirements.txt +5 -0
- setup.sh +22 -0
- travel_mcp_client.py +50 -0
- travel_mcp_server.py +208 -0
README.md
CHANGED
@@ -1,2 +1,96 @@
|
|
1 |
-
#
|
2 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Travel Documentation MCP Server
|
2 |
+
|
3 |
+
> An AI-powered travel documentation assistant built with Gradio's MCP server capabilities and smolagents for the Hugging Face MCP Hackathon 2025.
|
4 |
+
|
5 |
+
## What This Project Does
|
6 |
+
|
7 |
+
This project creates an intelligent travel documentation agent that helps travelers understand what documents they need for international trips. It consists of:
|
8 |
+
|
9 |
+
1. **MCP Server** (`travel_mcp_server.py`) - A Gradio interface that exposes travel documentation analysis as an MCP tool, mocking data is used for demonstration
|
10 |
+
2. **MCP Client** (`travel_mcp_client.py`) - An AI agent that uses smolagents to interact with the MCP server and provide conversational assistance
|
11 |
+
|
12 |
+
|
13 |
+
## Technical Implementation
|
14 |
+
- **Gradio MCP Server**: Uses Gradio's built-in MCP server functionality
|
15 |
+
- **smolagents Integration**: AI agent framework for tool execution and conversation
|
16 |
+
- **Local LLM Support**: Uses Ollama for local AI inference
|
17 |
+
|
18 |
+
## Installation & Setup
|
19 |
+
|
20 |
+
### Prerequisites
|
21 |
+
- Python 3.8+
|
22 |
+
- Ollama (for local LLM)
|
23 |
+
|
24 |
+
### 1. Install Dependencies
|
25 |
+
```bash
|
26 |
+
pip install -r requirements.txt
|
27 |
+
```
|
28 |
+
|
29 |
+
### 2. Set up Ollama (for the AI agent)
|
30 |
+
```bash
|
31 |
+
# Install Ollama from https://ollama.ai
|
32 |
+
# Pull the required model
|
33 |
+
ollama pull gemma3:4b
|
34 |
+
```
|
35 |
+
|
36 |
+
### 3. Start the MCP Server
|
37 |
+
```bash
|
38 |
+
python travel_mcp_server.py
|
39 |
+
```
|
40 |
+
The server will start at `http://127.0.0.1:7861` with MCP endpoint at `http://127.0.0.1:7861/gradio_api/mcp/sse`
|
41 |
+
|
42 |
+
### 4. Start the AI Agent Client
|
43 |
+
```bash
|
44 |
+
python travel_mcp_client.py
|
45 |
+
```
|
46 |
+
|
47 |
+
## Usage
|
48 |
+
|
49 |
+
### Direct Web Interface
|
50 |
+
Visit `http://127.0.0.1:7861` to use the travel documentation tool directly:
|
51 |
+
|
52 |
+
**Input Parameters:**
|
53 |
+
- **Your Citizenship Country**: e.g., "Canada", "China", "India"
|
54 |
+
- **Destination Country**: e.g., "Japan", "USA", "Germany"
|
55 |
+
- **Trip Duration**: Number of days (1-365)
|
56 |
+
- **Trip Purpose**: tourism, business, study, transit, work, family visit
|
57 |
+
|
58 |
+
**Example Output:**
|
59 |
+
```json
|
60 |
+
{
|
61 |
+
"trip_info": {
|
62 |
+
"from_country": "Canada",
|
63 |
+
"to_country": "Japan",
|
64 |
+
"duration_days": 30,
|
65 |
+
"purpose": "Tourism"
|
66 |
+
},
|
67 |
+
"visa_requirements": {
|
68 |
+
"visa_required": false,
|
69 |
+
"max_stay": "90 days"
|
70 |
+
},
|
71 |
+
"required_documents": [
|
72 |
+
{
|
73 |
+
"document_type": "Passport",
|
74 |
+
"required": true,
|
75 |
+
"description": "Valid passport with at least 6 months validity remaining"
|
76 |
+
}
|
77 |
+
],
|
78 |
+
"summary": {
|
79 |
+
"required_count": 5,
|
80 |
+
"optional_count": 1,
|
81 |
+
"visa_needed": false
|
82 |
+
}
|
83 |
+
}
|
84 |
+
```
|
85 |
+
|
86 |
+
### AI Agent Chat Interface
|
87 |
+
Use the conversational agent for natural language queries:
|
88 |
+
|
89 |
+
**Example Conversations:**
|
90 |
+
```
|
91 |
+
User: "I'm a Canadian citizen planning to visit Japan for 2 weeks for tourism"
|
92 |
+
Agent: "..."
|
93 |
+
|
94 |
+
User: "What documents do I need for business travel from China to USA?"
|
95 |
+
Agent: "..."
|
96 |
+
```
|
mcp_config.json
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"servers": {
|
3 |
+
"travel-docs": {
|
4 |
+
"command": "python",
|
5 |
+
"args": ["travel_docs_server.py"],
|
6 |
+
"env": {
|
7 |
+
"PYTHONPATH": "."
|
8 |
+
}
|
9 |
+
}
|
10 |
+
}
|
11 |
+
}
|
requirements.txt
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
mcp
|
2 |
+
gradio[mcp]
|
3 |
+
smolagents
|
4 |
+
smolagents[mcp]
|
5 |
+
fastmcp
|
setup.sh
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/bin/bash
|
2 |
+
# Setup script for Travel Documentation MCP Server
|
3 |
+
|
4 |
+
echo "Setting up Travel Documentation MCP Server"
|
5 |
+
echo "=============================================="
|
6 |
+
|
7 |
+
# Check Python version
|
8 |
+
python_version=$(python3 --version 2>&1)
|
9 |
+
echo "Python version: $python_version"
|
10 |
+
|
11 |
+
# Install dependencies
|
12 |
+
echo "Installing dependencies..."
|
13 |
+
pip3 install -r requirements.txt
|
14 |
+
|
15 |
+
|
16 |
+
echo "Setup complete!"
|
17 |
+
echo ""
|
18 |
+
echo "To run the applications:"
|
19 |
+
echo " MCP Server: python travel_mcp_server.py"
|
20 |
+
echo " MCP Client with Agent: python travel_mcp_client.py"
|
21 |
+
echo ""
|
22 |
+
echo "Access the web interface at: http://127.0.0.1:7860"
|
travel_mcp_client.py
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
|
3 |
+
from smolagents import CodeAgent, tool, LiteLLMModel
|
4 |
+
from smolagents.mcp_client import MCPClient
|
5 |
+
|
6 |
+
@tool
|
7 |
+
def self_introduction() -> str:
|
8 |
+
"""
|
9 |
+
Provides information about the agent's identity and capabilities.
|
10 |
+
|
11 |
+
This tool should be triggered when the user asks questions like:
|
12 |
+
- "What's your name?"
|
13 |
+
- "Who are you?"
|
14 |
+
- "What can you do?"
|
15 |
+
- "Tell me about yourself"
|
16 |
+
- "What are your capabilities?"
|
17 |
+
- Any other introductory or identity-related queries
|
18 |
+
|
19 |
+
Returns:
|
20 |
+
str: A friendly introduction explaining the agent's purpose and capabilities. You are free to rewrite the introduction but need to keep the same meaning.
|
21 |
+
"""
|
22 |
+
return "Hello! I am your travel documentation Agent. I can help you find out what documetations are required for your trip, get me your original coutry, destionation country, trip duration and purpose and I can help you."
|
23 |
+
|
24 |
+
|
25 |
+
mcp_client = MCPClient(
|
26 |
+
{"url": "http://127.0.0.1:7861/gradio_api/mcp/sse"}
|
27 |
+
)
|
28 |
+
|
29 |
+
try:
|
30 |
+
tools = mcp_client.get_tools()
|
31 |
+
|
32 |
+
# for local testing
|
33 |
+
model = LiteLLMModel(
|
34 |
+
model_id="ollama_chat/gemma3:4b",
|
35 |
+
api_base="http://127.0.0.1:11434",
|
36 |
+
num_ctx=8192,
|
37 |
+
)
|
38 |
+
agent = CodeAgent(tools=[*tools], model=model)
|
39 |
+
|
40 |
+
demo = gr.ChatInterface(
|
41 |
+
fn=lambda message, history: str(agent.run(message)),
|
42 |
+
type="messages",
|
43 |
+
examples=["Your trip plan..."],
|
44 |
+
title="Travel documentation agent",
|
45 |
+
description="This is a simple agent that uses MCP tools to help you find out required documentations for your international trip.",
|
46 |
+
)
|
47 |
+
|
48 |
+
demo.launch()
|
49 |
+
finally:
|
50 |
+
mcp_client.disconnect()
|
travel_mcp_server.py
ADDED
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
from typing import Dict, List, Any
|
3 |
+
|
4 |
+
class TravelDocumentationService:
|
5 |
+
"""Service for fetching travel documentation requirements"""
|
6 |
+
|
7 |
+
def get_visa_requirements(
|
8 |
+
self,
|
9 |
+
from_country: str,
|
10 |
+
to_country: str,
|
11 |
+
trip_duration: int = 30
|
12 |
+
) -> Dict[str, Any]:
|
13 |
+
"""Get visa requirements for travel between countries"""
|
14 |
+
|
15 |
+
# Mock data
|
16 |
+
visa_free_combinations = {
|
17 |
+
("canada", "japan"): {"visa_required": False, "max_stay": "90 days"},
|
18 |
+
("canada", "uk"): {"visa_required": False, "max_stay": "6 months"},
|
19 |
+
("canada", "usa"): {"visa_required": False, "max_stay": "6 months"},
|
20 |
+
("canada", "germany"): {"visa_required": False, "max_stay": "90 days"},
|
21 |
+
("usa", "japan"): {"visa_required": False, "max_stay": "90 days"},
|
22 |
+
("usa", "uk"): {"visa_required": False, "max_stay": "6 months"},
|
23 |
+
("usa", "germany"): {"visa_required": False, "max_stay": "90 days"},
|
24 |
+
("china", "japan"): {"visa_required": True, "visa_type": "Tourist Visa", "max_stay": "30 days", "processing_time": "5-7 business days", "fee": "$30 USD"},
|
25 |
+
("india", "japan"): {"visa_required": True, "visa_type": "Tourist Visa", "max_stay": "90 days", "processing_time": "5-10 business days", "fee": "$50 USD"},
|
26 |
+
("china", "usa"): {"visa_required": True, "visa_type": "B-2 Tourist Visa", "max_stay": "6 months", "processing_time": "3-5 weeks", "fee": "$160 USD"},
|
27 |
+
("india", "usa"): {"visa_required": True, "visa_type": "B-2 Tourist Visa", "max_stay": "6 months", "processing_time": "3-5 weeks", "fee": "$160 USD"},
|
28 |
+
}
|
29 |
+
|
30 |
+
key = (from_country.lower(), to_country.lower())
|
31 |
+
|
32 |
+
if key in visa_free_combinations:
|
33 |
+
return visa_free_combinations[key]
|
34 |
+
else:
|
35 |
+
return {
|
36 |
+
"visa_required": True,
|
37 |
+
"visa_type": "Tourist Visa",
|
38 |
+
"max_stay": "30 days",
|
39 |
+
"processing_time": "5-10 business days",
|
40 |
+
"fee": "$50-150 USD"
|
41 |
+
}
|
42 |
+
|
43 |
+
def get_document_requirements(
|
44 |
+
self,
|
45 |
+
from_country: str,
|
46 |
+
to_country: str,
|
47 |
+
trip_duration: int = 30,
|
48 |
+
trip_purpose: str = "tourism"
|
49 |
+
) -> List[Dict[str, Any]]:
|
50 |
+
"""Get comprehensive document requirements for travel"""
|
51 |
+
|
52 |
+
requirements = []
|
53 |
+
|
54 |
+
requirements.append({
|
55 |
+
"document_type": "Passport",
|
56 |
+
"required": True,
|
57 |
+
"description": "Valid passport with at least 6 months validity remaining",
|
58 |
+
"validity_period": "At least 6 months from travel date",
|
59 |
+
"additional_notes": "Must have at least 2 blank pages for stamps"
|
60 |
+
})
|
61 |
+
|
62 |
+
visa_info = self.get_visa_requirements(from_country, to_country, trip_duration)
|
63 |
+
|
64 |
+
if visa_info.get("visa_required", False):
|
65 |
+
requirements.append({
|
66 |
+
"document_type": "Visa",
|
67 |
+
"required": True,
|
68 |
+
"description": f"Required {visa_info.get('visa_type', 'Tourist Visa')}",
|
69 |
+
"processing_time": visa_info.get("processing_time", "5-10 business days"),
|
70 |
+
"additional_notes": f"Fee: {visa_info.get('fee', 'Varies by embassy')}"
|
71 |
+
})
|
72 |
+
|
73 |
+
insurance_required_countries = ["schengen", "germany", "france", "italy", "spain", "netherlands", "austria", "belgium"]
|
74 |
+
if to_country.lower() in insurance_required_countries:
|
75 |
+
requirements.append({
|
76 |
+
"document_type": "Travel Insurance",
|
77 |
+
"required": True,
|
78 |
+
"description": "Travel insurance with minimum €30,000 coverage",
|
79 |
+
"additional_notes": "Required for Schengen area countries"
|
80 |
+
})
|
81 |
+
else:
|
82 |
+
requirements.append({
|
83 |
+
"document_type": "Travel Insurance",
|
84 |
+
"required": False,
|
85 |
+
"description": "Travel insurance (highly recommended)",
|
86 |
+
"additional_notes": "Covers medical emergencies, trip cancellation, etc."
|
87 |
+
})
|
88 |
+
|
89 |
+
requirements.append({
|
90 |
+
"document_type": "Return/Onward Ticket",
|
91 |
+
"required": True,
|
92 |
+
"description": "Proof of return or onward travel",
|
93 |
+
"additional_notes": "Flight confirmation or travel itinerary"
|
94 |
+
})
|
95 |
+
|
96 |
+
financial_amount = "$100-150 per day" if to_country.lower() in ["japan", "switzerland", "norway"] else "$50-100 per day"
|
97 |
+
requirements.append({
|
98 |
+
"document_type": "Financial Proof",
|
99 |
+
"required": True,
|
100 |
+
"description": f"Proof of sufficient funds ({financial_amount})",
|
101 |
+
"additional_notes": "Bank statements, credit cards, or traveler's checks"
|
102 |
+
})
|
103 |
+
|
104 |
+
requirements.append({
|
105 |
+
"document_type": "Accommodation Proof",
|
106 |
+
"required": True,
|
107 |
+
"description": "Hotel booking or invitation letter",
|
108 |
+
"additional_notes": "Confirmation of where you'll be staying"
|
109 |
+
})
|
110 |
+
|
111 |
+
if trip_purpose.lower() == "business":
|
112 |
+
requirements.append({
|
113 |
+
"document_type": "Business Invitation Letter",
|
114 |
+
"required": True,
|
115 |
+
"description": "Letter from host company",
|
116 |
+
"additional_notes": "Must include company details and purpose of visit"
|
117 |
+
})
|
118 |
+
elif trip_purpose.lower() == "study":
|
119 |
+
requirements.append({
|
120 |
+
"document_type": "Student Visa/Permit",
|
121 |
+
"required": True,
|
122 |
+
"description": "Student visa or study permit",
|
123 |
+
"additional_notes": "Issued by educational institution"
|
124 |
+
})
|
125 |
+
requirements.append({
|
126 |
+
"document_type": "Acceptance Letter",
|
127 |
+
"required": True,
|
128 |
+
"description": "Letter of acceptance from educational institution",
|
129 |
+
"additional_notes": "Must be from recognized institution"
|
130 |
+
})
|
131 |
+
|
132 |
+
return requirements
|
133 |
+
|
134 |
+
travel_service = TravelDocumentationService()
|
135 |
+
|
136 |
+
def get_requirements(from_country, to_country, trip_duration, trip_purpose):
|
137 |
+
"""
|
138 |
+
Analyze the documentation requirment for user's travel plan
|
139 |
+
|
140 |
+
Args:
|
141 |
+
from_country (str): User's original country
|
142 |
+
to_country (str): The destination country that the user plans to travel to
|
143 |
+
trip_duration (number): The days length that the user plans to stay in the destination country
|
144 |
+
trip_purpose (str): The purpose of the travel, the user is go for business, tourism, study or other purposes
|
145 |
+
|
146 |
+
Returns:
|
147 |
+
json: Contains the comprehensive documentation requires and suggestions based on the user input
|
148 |
+
"""
|
149 |
+
try:
|
150 |
+
requirements = travel_service.get_document_requirements(
|
151 |
+
from_country, to_country, int(trip_duration), trip_purpose
|
152 |
+
)
|
153 |
+
|
154 |
+
visa_info = travel_service.get_visa_requirements(from_country, to_country, int(trip_duration))
|
155 |
+
|
156 |
+
required_docs = [req for req in requirements if req['required']]
|
157 |
+
optional_docs = [req for req in requirements if not req['required']]
|
158 |
+
|
159 |
+
result = {
|
160 |
+
"trip_info": {
|
161 |
+
"from_country": from_country.title(),
|
162 |
+
"to_country": to_country.title(),
|
163 |
+
"duration_days": int(trip_duration),
|
164 |
+
"purpose": trip_purpose.title()
|
165 |
+
},
|
166 |
+
"visa_requirements": {
|
167 |
+
"visa_required": visa_info.get("visa_required", False),
|
168 |
+
"visa_type": visa_info.get("visa_type"),
|
169 |
+
"max_stay": visa_info.get("max_stay"),
|
170 |
+
"processing_time": visa_info.get("processing_time"),
|
171 |
+
"fee": visa_info.get("fee")
|
172 |
+
},
|
173 |
+
"required_documents": required_docs,
|
174 |
+
"optional_documents": optional_docs,
|
175 |
+
"total_documents": len(requirements),
|
176 |
+
"summary": {
|
177 |
+
"required_count": len(required_docs),
|
178 |
+
"optional_count": len(optional_docs),
|
179 |
+
"visa_needed": visa_info.get("visa_required", False)
|
180 |
+
}
|
181 |
+
}
|
182 |
+
|
183 |
+
return result
|
184 |
+
|
185 |
+
except Exception as e:
|
186 |
+
return {"error": str(e)}
|
187 |
+
|
188 |
+
|
189 |
+
travel_demo = gr.Interface(
|
190 |
+
fn=get_requirements,
|
191 |
+
inputs=[
|
192 |
+
gr.Textbox(label="Your Citizenship Country", placeholder="e.g., Canada", value="Canada"),
|
193 |
+
gr.Textbox(label="Destination Country", placeholder="e.g., Japan", value="Japan"),
|
194 |
+
gr.Number(label="Trip Duration (days)", value=30, minimum=1, maximum=365),
|
195 |
+
gr.Dropdown(label="Trip Purpose", choices=["tourism", "business", "transit", "study", "work", "family visit"], value="tourism")
|
196 |
+
],
|
197 |
+
outputs=gr.JSON(),
|
198 |
+
title="Travel Documentation Requirements",
|
199 |
+
description="Get the comprehensive travel documentation requirements for international travel"
|
200 |
+
)
|
201 |
+
|
202 |
+
if __name__ == "__main__":
|
203 |
+
travel_demo.launch(
|
204 |
+
# share=True,
|
205 |
+
show_error=True,
|
206 |
+
mcp_server=True,
|
207 |
+
server_port=7861
|
208 |
+
)
|