atlas-chat 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.
- atlas/__init__.py +40 -0
- atlas/application/__init__.py +7 -0
- atlas/application/chat/__init__.py +7 -0
- atlas/application/chat/agent/__init__.py +10 -0
- atlas/application/chat/agent/act_loop.py +179 -0
- atlas/application/chat/agent/factory.py +142 -0
- atlas/application/chat/agent/protocols.py +46 -0
- atlas/application/chat/agent/react_loop.py +338 -0
- atlas/application/chat/agent/think_act_loop.py +171 -0
- atlas/application/chat/approval_manager.py +151 -0
- atlas/application/chat/elicitation_manager.py +191 -0
- atlas/application/chat/events/__init__.py +1 -0
- atlas/application/chat/events/agent_event_relay.py +112 -0
- atlas/application/chat/modes/__init__.py +1 -0
- atlas/application/chat/modes/agent.py +125 -0
- atlas/application/chat/modes/plain.py +74 -0
- atlas/application/chat/modes/rag.py +81 -0
- atlas/application/chat/modes/tools.py +179 -0
- atlas/application/chat/orchestrator.py +213 -0
- atlas/application/chat/policies/__init__.py +1 -0
- atlas/application/chat/policies/tool_authorization.py +99 -0
- atlas/application/chat/preprocessors/__init__.py +1 -0
- atlas/application/chat/preprocessors/message_builder.py +92 -0
- atlas/application/chat/preprocessors/prompt_override_service.py +104 -0
- atlas/application/chat/service.py +454 -0
- atlas/application/chat/utilities/__init__.py +6 -0
- atlas/application/chat/utilities/error_handler.py +367 -0
- atlas/application/chat/utilities/event_notifier.py +546 -0
- atlas/application/chat/utilities/file_processor.py +613 -0
- atlas/application/chat/utilities/tool_executor.py +789 -0
- atlas/atlas_chat_cli.py +347 -0
- atlas/atlas_client.py +238 -0
- atlas/core/__init__.py +0 -0
- atlas/core/auth.py +205 -0
- atlas/core/authorization_manager.py +27 -0
- atlas/core/capabilities.py +123 -0
- atlas/core/compliance.py +215 -0
- atlas/core/domain_whitelist.py +147 -0
- atlas/core/domain_whitelist_middleware.py +82 -0
- atlas/core/http_client.py +28 -0
- atlas/core/log_sanitizer.py +102 -0
- atlas/core/metrics_logger.py +59 -0
- atlas/core/middleware.py +131 -0
- atlas/core/otel_config.py +242 -0
- atlas/core/prompt_risk.py +200 -0
- atlas/core/rate_limit.py +0 -0
- atlas/core/rate_limit_middleware.py +64 -0
- atlas/core/security_headers_middleware.py +51 -0
- atlas/domain/__init__.py +37 -0
- atlas/domain/chat/__init__.py +1 -0
- atlas/domain/chat/dtos.py +85 -0
- atlas/domain/errors.py +96 -0
- atlas/domain/messages/__init__.py +12 -0
- atlas/domain/messages/models.py +160 -0
- atlas/domain/rag_mcp_service.py +664 -0
- atlas/domain/sessions/__init__.py +7 -0
- atlas/domain/sessions/models.py +36 -0
- atlas/domain/unified_rag_service.py +371 -0
- atlas/infrastructure/__init__.py +10 -0
- atlas/infrastructure/app_factory.py +135 -0
- atlas/infrastructure/events/__init__.py +1 -0
- atlas/infrastructure/events/cli_event_publisher.py +140 -0
- atlas/infrastructure/events/websocket_publisher.py +140 -0
- atlas/infrastructure/sessions/in_memory_repository.py +56 -0
- atlas/infrastructure/transport/__init__.py +7 -0
- atlas/infrastructure/transport/websocket_connection_adapter.py +33 -0
- atlas/init_cli.py +226 -0
- atlas/interfaces/__init__.py +15 -0
- atlas/interfaces/events.py +134 -0
- atlas/interfaces/llm.py +54 -0
- atlas/interfaces/rag.py +40 -0
- atlas/interfaces/sessions.py +75 -0
- atlas/interfaces/tools.py +57 -0
- atlas/interfaces/transport.py +24 -0
- atlas/main.py +564 -0
- atlas/mcp/api_key_demo/README.md +76 -0
- atlas/mcp/api_key_demo/main.py +172 -0
- atlas/mcp/api_key_demo/run.sh +56 -0
- atlas/mcp/basictable/main.py +147 -0
- atlas/mcp/calculator/main.py +149 -0
- atlas/mcp/code-executor/execution_engine.py +98 -0
- atlas/mcp/code-executor/execution_environment.py +95 -0
- atlas/mcp/code-executor/main.py +528 -0
- atlas/mcp/code-executor/result_processing.py +276 -0
- atlas/mcp/code-executor/script_generation.py +195 -0
- atlas/mcp/code-executor/security_checker.py +140 -0
- atlas/mcp/corporate_cars/main.py +437 -0
- atlas/mcp/csv_reporter/main.py +545 -0
- atlas/mcp/duckduckgo/main.py +182 -0
- atlas/mcp/elicitation_demo/README.md +171 -0
- atlas/mcp/elicitation_demo/main.py +262 -0
- atlas/mcp/env-demo/README.md +158 -0
- atlas/mcp/env-demo/main.py +199 -0
- atlas/mcp/file_size_test/main.py +284 -0
- atlas/mcp/filesystem/main.py +348 -0
- atlas/mcp/image_demo/main.py +113 -0
- atlas/mcp/image_demo/requirements.txt +4 -0
- atlas/mcp/logging_demo/README.md +72 -0
- atlas/mcp/logging_demo/main.py +103 -0
- atlas/mcp/many_tools_demo/main.py +50 -0
- atlas/mcp/order_database/__init__.py +0 -0
- atlas/mcp/order_database/main.py +369 -0
- atlas/mcp/order_database/signal_data.csv +1001 -0
- atlas/mcp/pdfbasic/main.py +394 -0
- atlas/mcp/pptx_generator/main.py +760 -0
- atlas/mcp/pptx_generator/requirements.txt +13 -0
- atlas/mcp/pptx_generator/run_test.sh +1 -0
- atlas/mcp/pptx_generator/test_pptx_generator_security.py +169 -0
- atlas/mcp/progress_demo/main.py +167 -0
- atlas/mcp/progress_updates_demo/QUICKSTART.md +273 -0
- atlas/mcp/progress_updates_demo/README.md +120 -0
- atlas/mcp/progress_updates_demo/main.py +497 -0
- atlas/mcp/prompts/main.py +222 -0
- atlas/mcp/public_demo/main.py +189 -0
- atlas/mcp/sampling_demo/README.md +169 -0
- atlas/mcp/sampling_demo/main.py +234 -0
- atlas/mcp/thinking/main.py +77 -0
- atlas/mcp/tool_planner/main.py +240 -0
- atlas/mcp/ui-demo/badmesh.png +0 -0
- atlas/mcp/ui-demo/main.py +383 -0
- atlas/mcp/ui-demo/templates/button_demo.html +32 -0
- atlas/mcp/ui-demo/templates/data_visualization.html +32 -0
- atlas/mcp/ui-demo/templates/form_demo.html +28 -0
- atlas/mcp/username-override-demo/README.md +320 -0
- atlas/mcp/username-override-demo/main.py +308 -0
- atlas/modules/__init__.py +0 -0
- atlas/modules/config/__init__.py +34 -0
- atlas/modules/config/cli.py +231 -0
- atlas/modules/config/config_manager.py +1096 -0
- atlas/modules/file_storage/__init__.py +22 -0
- atlas/modules/file_storage/cli.py +330 -0
- atlas/modules/file_storage/content_extractor.py +290 -0
- atlas/modules/file_storage/manager.py +295 -0
- atlas/modules/file_storage/mock_s3_client.py +402 -0
- atlas/modules/file_storage/s3_client.py +417 -0
- atlas/modules/llm/__init__.py +19 -0
- atlas/modules/llm/caller.py +287 -0
- atlas/modules/llm/litellm_caller.py +675 -0
- atlas/modules/llm/models.py +19 -0
- atlas/modules/mcp_tools/__init__.py +17 -0
- atlas/modules/mcp_tools/client.py +2123 -0
- atlas/modules/mcp_tools/token_storage.py +556 -0
- atlas/modules/prompts/prompt_provider.py +130 -0
- atlas/modules/rag/__init__.py +24 -0
- atlas/modules/rag/atlas_rag_client.py +336 -0
- atlas/modules/rag/client.py +129 -0
- atlas/routes/admin_routes.py +865 -0
- atlas/routes/config_routes.py +484 -0
- atlas/routes/feedback_routes.py +361 -0
- atlas/routes/files_routes.py +274 -0
- atlas/routes/health_routes.py +40 -0
- atlas/routes/mcp_auth_routes.py +223 -0
- atlas/server_cli.py +164 -0
- atlas/tests/conftest.py +20 -0
- atlas/tests/integration/test_mcp_auth_integration.py +152 -0
- atlas/tests/manual_test_sampling.py +87 -0
- atlas/tests/modules/mcp_tools/test_client_auth.py +226 -0
- atlas/tests/modules/mcp_tools/test_client_env.py +191 -0
- atlas/tests/test_admin_mcp_server_management_routes.py +141 -0
- atlas/tests/test_agent_roa.py +135 -0
- atlas/tests/test_app_factory_smoke.py +47 -0
- atlas/tests/test_approval_manager.py +439 -0
- atlas/tests/test_atlas_client.py +188 -0
- atlas/tests/test_atlas_rag_client.py +447 -0
- atlas/tests/test_atlas_rag_integration.py +224 -0
- atlas/tests/test_attach_file_flow.py +287 -0
- atlas/tests/test_auth_utils.py +165 -0
- atlas/tests/test_backend_public_url.py +185 -0
- atlas/tests/test_banner_logging.py +287 -0
- atlas/tests/test_capability_tokens_and_injection.py +203 -0
- atlas/tests/test_compliance_level.py +54 -0
- atlas/tests/test_compliance_manager.py +253 -0
- atlas/tests/test_config_manager.py +617 -0
- atlas/tests/test_config_manager_paths.py +12 -0
- atlas/tests/test_core_auth.py +18 -0
- atlas/tests/test_core_utils.py +190 -0
- atlas/tests/test_docker_env_sync.py +202 -0
- atlas/tests/test_domain_errors.py +329 -0
- atlas/tests/test_domain_whitelist.py +359 -0
- atlas/tests/test_elicitation_manager.py +408 -0
- atlas/tests/test_elicitation_routing.py +296 -0
- atlas/tests/test_env_demo_server.py +88 -0
- atlas/tests/test_error_classification.py +113 -0
- atlas/tests/test_error_flow_integration.py +116 -0
- atlas/tests/test_feedback_routes.py +333 -0
- atlas/tests/test_file_content_extraction.py +1134 -0
- atlas/tests/test_file_extraction_routes.py +158 -0
- atlas/tests/test_file_library.py +107 -0
- atlas/tests/test_file_manager_unit.py +18 -0
- atlas/tests/test_health_route.py +49 -0
- atlas/tests/test_http_client_stub.py +8 -0
- atlas/tests/test_imports_smoke.py +30 -0
- atlas/tests/test_interfaces_llm_response.py +9 -0
- atlas/tests/test_issue_access_denied_fix.py +136 -0
- atlas/tests/test_llm_env_expansion.py +836 -0
- atlas/tests/test_log_level_sensitive_data.py +285 -0
- atlas/tests/test_mcp_auth_routes.py +341 -0
- atlas/tests/test_mcp_client_auth.py +331 -0
- atlas/tests/test_mcp_data_injection.py +270 -0
- atlas/tests/test_mcp_get_authorized_servers.py +95 -0
- atlas/tests/test_mcp_hot_reload.py +512 -0
- atlas/tests/test_mcp_image_content.py +424 -0
- atlas/tests/test_mcp_logging.py +172 -0
- atlas/tests/test_mcp_progress_updates.py +313 -0
- atlas/tests/test_mcp_prompt_override_system_prompt.py +102 -0
- atlas/tests/test_mcp_prompts_server.py +39 -0
- atlas/tests/test_mcp_tool_result_parsing.py +296 -0
- atlas/tests/test_metrics_logger.py +56 -0
- atlas/tests/test_middleware_auth.py +379 -0
- atlas/tests/test_prompt_risk_and_acl.py +141 -0
- atlas/tests/test_rag_mcp_aggregator.py +204 -0
- atlas/tests/test_rag_mcp_service.py +224 -0
- atlas/tests/test_rate_limit_middleware.py +45 -0
- atlas/tests/test_routes_config_smoke.py +60 -0
- atlas/tests/test_routes_files_download_token.py +41 -0
- atlas/tests/test_routes_files_health.py +18 -0
- atlas/tests/test_runtime_imports.py +53 -0
- atlas/tests/test_sampling_integration.py +482 -0
- atlas/tests/test_security_admin_routes.py +61 -0
- atlas/tests/test_security_capability_tokens.py +65 -0
- atlas/tests/test_security_file_stats_scope.py +21 -0
- atlas/tests/test_security_header_injection.py +191 -0
- atlas/tests/test_security_headers_and_filename.py +63 -0
- atlas/tests/test_shared_session_repository.py +101 -0
- atlas/tests/test_system_prompt_loading.py +181 -0
- atlas/tests/test_token_storage.py +505 -0
- atlas/tests/test_tool_approval_config.py +93 -0
- atlas/tests/test_tool_approval_utils.py +356 -0
- atlas/tests/test_tool_authorization_group_filtering.py +223 -0
- atlas/tests/test_tool_details_in_config.py +108 -0
- atlas/tests/test_tool_planner.py +300 -0
- atlas/tests/test_unified_rag_service.py +398 -0
- atlas/tests/test_username_override_in_approval.py +258 -0
- atlas/tests/test_websocket_auth_header.py +168 -0
- atlas/version.py +6 -0
- atlas_chat-0.1.0.data/data/.env.example +253 -0
- atlas_chat-0.1.0.data/data/config/defaults/compliance-levels.json +44 -0
- atlas_chat-0.1.0.data/data/config/defaults/domain-whitelist.json +123 -0
- atlas_chat-0.1.0.data/data/config/defaults/file-extractors.json +74 -0
- atlas_chat-0.1.0.data/data/config/defaults/help-config.json +198 -0
- atlas_chat-0.1.0.data/data/config/defaults/llmconfig-buggy.yml +11 -0
- atlas_chat-0.1.0.data/data/config/defaults/llmconfig.yml +19 -0
- atlas_chat-0.1.0.data/data/config/defaults/mcp.json +138 -0
- atlas_chat-0.1.0.data/data/config/defaults/rag-sources.json +17 -0
- atlas_chat-0.1.0.data/data/config/defaults/splash-config.json +16 -0
- atlas_chat-0.1.0.dist-info/METADATA +236 -0
- atlas_chat-0.1.0.dist-info/RECORD +250 -0
- atlas_chat-0.1.0.dist-info/WHEEL +5 -0
- atlas_chat-0.1.0.dist-info/entry_points.txt +4 -0
- atlas_chat-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
MCP Server for Web Search using DuckDuckGo.
|
|
4
|
+
Provides a tool to search DuckDuckGo, fetch the content of a result,
|
|
5
|
+
and return it.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Dict, Union
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
from bs4 import BeautifulSoup
|
|
12
|
+
from duckduckgo_search import DDGS
|
|
13
|
+
from fastmcp import FastMCP
|
|
14
|
+
|
|
15
|
+
# Initialize the MCP server
|
|
16
|
+
mcp = FastMCP("WebSearcher")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_page_content(url: str) -> str:
|
|
20
|
+
"""
|
|
21
|
+
Fetches and parses the text content of a given URL.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
url: The URL of the webpage to parse.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
The cleaned text content of the page, or an error message.
|
|
28
|
+
"""
|
|
29
|
+
try:
|
|
30
|
+
headers = {
|
|
31
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
32
|
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
|
33
|
+
'Accept-Language': 'en-US,en;q=0.5',
|
|
34
|
+
'Accept-Encoding': 'gzip, deflate',
|
|
35
|
+
'Referer': 'https://www.google.com/',
|
|
36
|
+
'Connection': 'keep-alive',
|
|
37
|
+
'Upgrade-Insecure-Requests': '1',
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
response = requests.get(url, headers=headers, timeout=15, allow_redirects=True)
|
|
41
|
+
response.raise_for_status() # Raise an exception for bad status codes (4xx or 5xx)
|
|
42
|
+
|
|
43
|
+
soup = BeautifulSoup(response.text, 'html.parser')
|
|
44
|
+
|
|
45
|
+
# Remove script and style elements
|
|
46
|
+
for script_or_style in soup(["script", "style"]):
|
|
47
|
+
script_or_style.decompose()
|
|
48
|
+
|
|
49
|
+
# Get text and clean it up
|
|
50
|
+
text = soup.get_text()
|
|
51
|
+
lines = (line.strip() for line in text.splitlines())
|
|
52
|
+
chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
|
|
53
|
+
cleaned_text = '\n'.join(chunk for chunk in chunks if chunk)
|
|
54
|
+
|
|
55
|
+
return cleaned_text
|
|
56
|
+
|
|
57
|
+
except requests.RequestException as e:
|
|
58
|
+
return f"Error fetching URL {url}: {e}"
|
|
59
|
+
except Exception as e:
|
|
60
|
+
return f"An error occurred during page parsing: {e}"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@mcp.tool
|
|
64
|
+
def search_and_fetch(query: str, max_results: Union[str, int] = 3) -> Dict[str, Any]:
|
|
65
|
+
"""
|
|
66
|
+
Search the web using DuckDuckGo and intelligently fetch full content from the most relevant results.
|
|
67
|
+
|
|
68
|
+
This powerful web search tool combines search and content retrieval:
|
|
69
|
+
- Uses DuckDuckGo search engine for privacy-focused web searching
|
|
70
|
+
- Automatically attempts to fetch and parse content from multiple results
|
|
71
|
+
- Returns the first successfully retrieved page content along with metadata
|
|
72
|
+
- Handles various content types and website structures intelligently
|
|
73
|
+
|
|
74
|
+
**Search Capabilities:**
|
|
75
|
+
- Natural language queries and specific search terms
|
|
76
|
+
- Multiple search result evaluation for best content retrieval
|
|
77
|
+
- Fallback strategy: tries multiple URLs if the first fails
|
|
78
|
+
- Respects website robots.txt and rate limiting
|
|
79
|
+
|
|
80
|
+
**Content Extraction:**
|
|
81
|
+
- Removes navigation, ads, and formatting for clean text
|
|
82
|
+
- Extracts main article/content body from web pages
|
|
83
|
+
- Handles dynamic content and various website layouts
|
|
84
|
+
- Provides webpage title and URL for reference
|
|
85
|
+
|
|
86
|
+
**Privacy & Ethics:**
|
|
87
|
+
- Uses DuckDuckGo for privacy-conscious searching
|
|
88
|
+
- Includes proper user agent and headers
|
|
89
|
+
- Respects website terms and handles errors gracefully
|
|
90
|
+
|
|
91
|
+
**Use Cases:**
|
|
92
|
+
- Research on current events, technical topics, or general information
|
|
93
|
+
- Fact-checking and information verification
|
|
94
|
+
- Content analysis and summarization preparation
|
|
95
|
+
- Market research and competitive analysis
|
|
96
|
+
- Academic research and reference gathering
|
|
97
|
+
|
|
98
|
+
**Examples:**
|
|
99
|
+
- "latest developments in AI" → Recent news and articles
|
|
100
|
+
- "Python pandas tutorial" → Educational content and documentation
|
|
101
|
+
- "climate change statistics 2024" → Current data and reports
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
query: Search query (string). Use natural language or specific keywords.
|
|
105
|
+
max_results: Maximum number of search results to attempt content fetch from (1-10, default 3)
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Dictionary containing:
|
|
109
|
+
- title: Title of the successfully fetched webpage
|
|
110
|
+
- url: URL of the source page
|
|
111
|
+
- content: Cleaned text content from the webpage
|
|
112
|
+
- query: Original search query used
|
|
113
|
+
Or error message if no content could be retrieved from any results
|
|
114
|
+
"""
|
|
115
|
+
try:
|
|
116
|
+
# convert to int. max = 10, min = 1
|
|
117
|
+
max_results = int(max_results)
|
|
118
|
+
if max_results < 1:
|
|
119
|
+
max_results = 1
|
|
120
|
+
elif max_results > 10:
|
|
121
|
+
max_results = 10
|
|
122
|
+
with DDGS() as ddgs:
|
|
123
|
+
# Get multiple results to try if first one fails
|
|
124
|
+
results = list(ddgs.text(query, max_results=max_results))
|
|
125
|
+
|
|
126
|
+
if not results:
|
|
127
|
+
return {"results": {"error": "No results found for your query."}}
|
|
128
|
+
|
|
129
|
+
# Try each result until we successfully fetch content
|
|
130
|
+
errors_encountered = []
|
|
131
|
+
|
|
132
|
+
for i, result in enumerate(results):
|
|
133
|
+
result_title = result.get('title')
|
|
134
|
+
result_url = result.get('href')
|
|
135
|
+
|
|
136
|
+
if not result_url:
|
|
137
|
+
errors_encountered.append(f"Result {i+1}: No URL found")
|
|
138
|
+
continue
|
|
139
|
+
|
|
140
|
+
print(f"[DEBUG] Attempting to fetch content from: {result_url}")
|
|
141
|
+
|
|
142
|
+
# Fetch and parse the content of the page
|
|
143
|
+
content = get_page_content(result_url)
|
|
144
|
+
|
|
145
|
+
if content.startswith("Error"):
|
|
146
|
+
errors_encountered.append(f"Result {i+1} ({result_title}): {content}")
|
|
147
|
+
print(f"[DEBUG] Failed to fetch {result_url}: {content}")
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
# Success! Return the content
|
|
151
|
+
print(f"[DEBUG] Successfully fetched content from {result_url}")
|
|
152
|
+
return {
|
|
153
|
+
"results": {
|
|
154
|
+
"operation": "search_and_fetch",
|
|
155
|
+
"query": query,
|
|
156
|
+
"result_title": result_title,
|
|
157
|
+
"result_url": result_url,
|
|
158
|
+
"content": content,
|
|
159
|
+
"attempt": i + 1,
|
|
160
|
+
"total_results": len(results)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
# If we get here, all results failed
|
|
165
|
+
return {
|
|
166
|
+
"results": {
|
|
167
|
+
"operation": "search_and_fetch",
|
|
168
|
+
"query": query,
|
|
169
|
+
"error": f"Failed to fetch content from all {len(results)} search results",
|
|
170
|
+
"errors": errors_encountered,
|
|
171
|
+
"total_results": len(results)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
except Exception as e:
|
|
176
|
+
return {"results": {"error": f"An unexpected error occurred: {str(e)}"}}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
if __name__ == "__main__":
|
|
180
|
+
# To run this server, you need to install the required libraries:
|
|
181
|
+
# pip install fastmcp duckduckgo-search requests beautifulsoup4
|
|
182
|
+
mcp.run()
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Elicitation Demo MCP Server
|
|
2
|
+
|
|
3
|
+
This MCP server demonstrates **user elicitation** capabilities introduced in FastMCP 2.10.0+. Elicitation allows tools to pause execution and request structured input from users during tool execution, rather than requiring all inputs upfront.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
User elicitation enables interactive workflows where tools can:
|
|
8
|
+
- Request missing or clarifying information mid-execution
|
|
9
|
+
- Collect complex data step-by-step across multiple prompts
|
|
10
|
+
- Ask for user approval or confirmation
|
|
11
|
+
- Adapt behavior based on user responses
|
|
12
|
+
|
|
13
|
+
## Available Tools
|
|
14
|
+
|
|
15
|
+
### Basic Elicitation Types
|
|
16
|
+
|
|
17
|
+
1. **`get_user_name`** - String Input
|
|
18
|
+
- Demonstrates basic string elicitation
|
|
19
|
+
- Asks for user's name and returns a greeting
|
|
20
|
+
|
|
21
|
+
2. **`pick_a_number`** - Integer Input
|
|
22
|
+
- Demonstrates numeric elicitation
|
|
23
|
+
- Asks for a number between 1-100 and performs calculation
|
|
24
|
+
|
|
25
|
+
3. **`confirm_action`** - Boolean Input
|
|
26
|
+
- Demonstrates boolean confirmation
|
|
27
|
+
- Asks yes/no question and proceeds based on response
|
|
28
|
+
|
|
29
|
+
4. **`set_priority`** - Enum Input (Python Enum)
|
|
30
|
+
- Demonstrates enum-based selection
|
|
31
|
+
- User chooses from predefined priority levels (low/medium/high)
|
|
32
|
+
|
|
33
|
+
5. **`choose_option`** - Enum Input (String List)
|
|
34
|
+
- Demonstrates string literal selection
|
|
35
|
+
- User picks favorite color from list of options
|
|
36
|
+
|
|
37
|
+
### Advanced Elicitation
|
|
38
|
+
|
|
39
|
+
6. **`create_task`** - Structured Multi-field Form
|
|
40
|
+
- Demonstrates structured data collection
|
|
41
|
+
- Collects task with title, description, priority, and due date in single form
|
|
42
|
+
|
|
43
|
+
7. **`multi_turn_survey`** - Multi-turn Elicitation
|
|
44
|
+
- Demonstrates progressive data collection
|
|
45
|
+
- Asks 4 questions sequentially: name, age, favorite food, satisfaction rating
|
|
46
|
+
- Can be cancelled at any step
|
|
47
|
+
|
|
48
|
+
8. **`approve_deletion`** - Approval-only (No Data)
|
|
49
|
+
- Demonstrates confirmation without additional data
|
|
50
|
+
- Simple approve/decline for sensitive actions
|
|
51
|
+
|
|
52
|
+
## Usage Examples
|
|
53
|
+
|
|
54
|
+
### In Atlas UI Chat
|
|
55
|
+
|
|
56
|
+
After the elicitation_demo server is enabled, you can test it with prompts like:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
"Get my name using the elicitation demo"
|
|
60
|
+
"Ask me to pick a number"
|
|
61
|
+
"Create a task by asking me for the details"
|
|
62
|
+
"Run a survey about my preferences"
|
|
63
|
+
"Test the approval process for deletion"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Expected User Experience
|
|
67
|
+
|
|
68
|
+
When a tool calls `ctx.elicit()`:
|
|
69
|
+
|
|
70
|
+
1. **Dialog Appears**: A modal dialog pops up with the prompt message
|
|
71
|
+
2. **Form Fields**: Input fields are shown based on the requested type:
|
|
72
|
+
- Text boxes for strings
|
|
73
|
+
- Number inputs for integers/numbers
|
|
74
|
+
- Checkboxes for booleans
|
|
75
|
+
- Dropdowns for enums
|
|
76
|
+
- Multiple fields for structured types
|
|
77
|
+
3. **User Actions**:
|
|
78
|
+
- **Accept**: Submit the provided data (only enabled if required fields are filled)
|
|
79
|
+
- **Decline**: Skip providing data (tool can handle this case)
|
|
80
|
+
- **Cancel**: Abort the entire operation
|
|
81
|
+
|
|
82
|
+
## Technical Details
|
|
83
|
+
|
|
84
|
+
### Response Schema
|
|
85
|
+
|
|
86
|
+
The tool specifies what type of data it expects using JSON Schema. Atlas UI parses this schema to render appropriate form fields:
|
|
87
|
+
|
|
88
|
+
- **Scalar types**: Wrapped in object with `value` field, then unwrapped for tool
|
|
89
|
+
- **Enums**: Rendered as dropdown selects
|
|
90
|
+
- **Structured**: Multiple fields in a form
|
|
91
|
+
- **None/Empty**: Approval-only, no data fields shown
|
|
92
|
+
|
|
93
|
+
### Elicitation Actions
|
|
94
|
+
|
|
95
|
+
Tools receive a response with:
|
|
96
|
+
- `action`: "accept" | "decline" | "cancel"
|
|
97
|
+
- `data`: The user's input (only present on "accept")
|
|
98
|
+
|
|
99
|
+
### Timeouts
|
|
100
|
+
|
|
101
|
+
Elicitation requests timeout after 5 minutes of waiting. The tool receives a "cancel" response if timeout occurs.
|
|
102
|
+
|
|
103
|
+
## Configuration
|
|
104
|
+
|
|
105
|
+
This server is configured in `config/overrides/mcp.json`:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"elicitation_demo": {
|
|
110
|
+
"command": ["python", "mcp/elicitation_demo/main.py"],
|
|
111
|
+
"cwd": "atlas",
|
|
112
|
+
"groups": ["users"],
|
|
113
|
+
"description": "Demonstrates MCP user elicitation capabilities...",
|
|
114
|
+
"compliance_level": "Public"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Development
|
|
120
|
+
|
|
121
|
+
To run the server standalone for testing:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
cd atlas
|
|
125
|
+
python mcp/elicitation_demo/main.py
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The FastMCP framework will display available tools and their schemas.
|
|
129
|
+
|
|
130
|
+
## References
|
|
131
|
+
|
|
132
|
+
- [FastMCP Elicitation Documentation](https://gofastmcp.com/clients/elicitation)
|
|
133
|
+
- [MCP Specification - Elicitation](https://spec.modelcontextprotocol.io/)
|
|
134
|
+
- FastMCP Version: 2.10.0+
|
|
135
|
+
|
|
136
|
+
## Example Tool Implementation
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from fastmcp import FastMCP, Context
|
|
140
|
+
from dataclasses import dataclass
|
|
141
|
+
|
|
142
|
+
mcp = FastMCP("My Server")
|
|
143
|
+
|
|
144
|
+
@dataclass
|
|
145
|
+
class UserInfo:
|
|
146
|
+
name: str
|
|
147
|
+
age: int
|
|
148
|
+
|
|
149
|
+
@mcp.tool
|
|
150
|
+
async def collect_info(ctx: Context) -> str:
|
|
151
|
+
"""Collect user information interactively."""
|
|
152
|
+
result = await ctx.elicit(
|
|
153
|
+
message="Please provide your information",
|
|
154
|
+
response_type=UserInfo
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if result.action == "accept":
|
|
158
|
+
user = result.data
|
|
159
|
+
return f"Hello {user.name}, age {user.age}!"
|
|
160
|
+
elif result.action == "decline":
|
|
161
|
+
return "Information not provided"
|
|
162
|
+
else: # cancel
|
|
163
|
+
return "Operation cancelled"
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Support
|
|
167
|
+
|
|
168
|
+
For issues or questions about elicitation:
|
|
169
|
+
- Check Atlas UI documentation in `/docs` folder
|
|
170
|
+
- Review FastMCP elicitation docs at https://gofastmcp.com
|
|
171
|
+
- Report bugs via GitHub issues
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Elicitation Demo MCP Server using FastMCP
|
|
4
|
+
|
|
5
|
+
This server demonstrates user elicitation capabilities - requesting
|
|
6
|
+
structured input from users during tool execution.
|
|
7
|
+
|
|
8
|
+
Supports:
|
|
9
|
+
- Scalar types (string, int, bool)
|
|
10
|
+
- Enum/constrained options
|
|
11
|
+
- Structured multi-field responses
|
|
12
|
+
- Multi-turn elicitation
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from enum import Enum
|
|
17
|
+
from typing import Literal
|
|
18
|
+
|
|
19
|
+
from fastmcp import Context, FastMCP
|
|
20
|
+
|
|
21
|
+
# Initialize the MCP server
|
|
22
|
+
mcp = FastMCP("Elicitation Demo")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@mcp.tool
|
|
26
|
+
async def get_user_name(ctx: Context) -> str:
|
|
27
|
+
"""
|
|
28
|
+
Simple elicitation example that asks for a string input.
|
|
29
|
+
|
|
30
|
+
This tool demonstrates basic string elicitation - asking the user
|
|
31
|
+
for their name and using it in the response.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Greeting message with the user's name, or indication if not provided
|
|
35
|
+
"""
|
|
36
|
+
result = await ctx.elicit("What's your name?", response_type=str)
|
|
37
|
+
|
|
38
|
+
if result.action == "accept":
|
|
39
|
+
return f"Hello, {result.data}! Nice to meet you."
|
|
40
|
+
elif result.action == "decline":
|
|
41
|
+
return "No name provided. That's okay!"
|
|
42
|
+
else: # cancel
|
|
43
|
+
return "Operation cancelled."
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@mcp.tool
|
|
47
|
+
async def pick_a_number(ctx: Context) -> str:
|
|
48
|
+
"""
|
|
49
|
+
Elicitation example that requests an integer input.
|
|
50
|
+
|
|
51
|
+
This tool demonstrates numeric elicitation - asking the user
|
|
52
|
+
for a number and performing a simple calculation.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Information about the picked number, or indication if not provided
|
|
56
|
+
"""
|
|
57
|
+
result = await ctx.elicit("Pick a number between 1 and 100!", response_type=int)
|
|
58
|
+
|
|
59
|
+
if result.action == "accept":
|
|
60
|
+
number = result.data
|
|
61
|
+
doubled = number * 2
|
|
62
|
+
return f"You picked {number}! Doubled, that's {doubled}."
|
|
63
|
+
elif result.action == "decline":
|
|
64
|
+
return "No number provided."
|
|
65
|
+
else: # cancel
|
|
66
|
+
return "Operation cancelled."
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@mcp.tool
|
|
70
|
+
async def confirm_action(ctx: Context) -> str:
|
|
71
|
+
"""
|
|
72
|
+
Elicitation example that requests boolean confirmation.
|
|
73
|
+
|
|
74
|
+
This tool demonstrates boolean elicitation - asking the user
|
|
75
|
+
to confirm or reject an action.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Result of the confirmation decision
|
|
79
|
+
"""
|
|
80
|
+
result = await ctx.elicit("Do you want to proceed with this action?", response_type=bool)
|
|
81
|
+
|
|
82
|
+
if result.action == "accept":
|
|
83
|
+
if result.data:
|
|
84
|
+
return "Action confirmed! Proceeding..."
|
|
85
|
+
else:
|
|
86
|
+
return "Action not confirmed. Cancelled."
|
|
87
|
+
elif result.action == "decline":
|
|
88
|
+
return "No response provided."
|
|
89
|
+
else: # cancel
|
|
90
|
+
return "Operation cancelled."
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class Priority(Enum):
|
|
94
|
+
"""Priority levels for task creation."""
|
|
95
|
+
LOW = "low"
|
|
96
|
+
MEDIUM = "medium"
|
|
97
|
+
HIGH = "high"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@mcp.tool
|
|
101
|
+
async def set_priority(ctx: Context) -> str:
|
|
102
|
+
"""
|
|
103
|
+
Elicitation example using enum for constrained options.
|
|
104
|
+
|
|
105
|
+
This tool demonstrates enum-based elicitation - asking the user
|
|
106
|
+
to choose from a predefined set of priority levels.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Confirmation of the selected priority level
|
|
110
|
+
"""
|
|
111
|
+
result = await ctx.elicit("What priority level?", response_type=Priority)
|
|
112
|
+
|
|
113
|
+
if result.action == "accept":
|
|
114
|
+
return f"Priority set to: {result.data.value}"
|
|
115
|
+
elif result.action == "decline":
|
|
116
|
+
return "No priority set."
|
|
117
|
+
else: # cancel
|
|
118
|
+
return "Operation cancelled."
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@mcp.tool
|
|
122
|
+
async def choose_option(ctx: Context) -> str:
|
|
123
|
+
"""
|
|
124
|
+
Elicitation example using list of strings for simple options.
|
|
125
|
+
|
|
126
|
+
This tool demonstrates string literal elicitation - asking the user
|
|
127
|
+
to choose from a list of options provided as strings.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Confirmation of the selected option
|
|
131
|
+
"""
|
|
132
|
+
result = await ctx.elicit(
|
|
133
|
+
"Choose your favorite color:",
|
|
134
|
+
response_type=["red", "blue", "green", "yellow"]
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
if result.action == "accept":
|
|
138
|
+
return f"You chose: {result.data}"
|
|
139
|
+
elif result.action == "decline":
|
|
140
|
+
return "No color chosen."
|
|
141
|
+
else: # cancel
|
|
142
|
+
return "Operation cancelled."
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@dataclass
|
|
146
|
+
class TaskDetails:
|
|
147
|
+
"""Structured data for task creation."""
|
|
148
|
+
title: str
|
|
149
|
+
description: str
|
|
150
|
+
priority: Literal["low", "medium", "high"]
|
|
151
|
+
due_date: str
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@mcp.tool
|
|
155
|
+
async def create_task(ctx: Context) -> str:
|
|
156
|
+
"""
|
|
157
|
+
Elicitation example with structured multi-field response.
|
|
158
|
+
|
|
159
|
+
This tool demonstrates structured elicitation - asking the user
|
|
160
|
+
to provide multiple fields at once using a dataclass.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Summary of the created task with all provided details
|
|
164
|
+
"""
|
|
165
|
+
result = await ctx.elicit(
|
|
166
|
+
"Please provide task details",
|
|
167
|
+
response_type=TaskDetails
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
if result.action == "accept":
|
|
171
|
+
task = result.data
|
|
172
|
+
return (
|
|
173
|
+
f"Task created successfully!\n"
|
|
174
|
+
f"Title: {task.title}\n"
|
|
175
|
+
f"Description: {task.description}\n"
|
|
176
|
+
f"Priority: {task.priority}\n"
|
|
177
|
+
f"Due Date: {task.due_date}"
|
|
178
|
+
)
|
|
179
|
+
elif result.action == "decline":
|
|
180
|
+
return "Task creation declined."
|
|
181
|
+
else: # cancel
|
|
182
|
+
return "Task creation cancelled."
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@mcp.tool
|
|
186
|
+
async def multi_turn_survey(ctx: Context) -> str:
|
|
187
|
+
"""
|
|
188
|
+
Multi-turn elicitation example that collects information step by step.
|
|
189
|
+
|
|
190
|
+
This tool demonstrates progressive elicitation - asking multiple
|
|
191
|
+
questions in sequence to gather information gradually.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Summary of all collected survey responses
|
|
195
|
+
"""
|
|
196
|
+
# Step 1: Get name
|
|
197
|
+
name_result = await ctx.elicit("What's your name?", response_type=str)
|
|
198
|
+
if name_result.action != "accept":
|
|
199
|
+
return "Survey cancelled at name question."
|
|
200
|
+
name = name_result.data
|
|
201
|
+
|
|
202
|
+
# Step 2: Get age
|
|
203
|
+
age_result = await ctx.elicit("What's your age?", response_type=int)
|
|
204
|
+
if age_result.action != "accept":
|
|
205
|
+
return f"Survey cancelled at age question. Thanks anyway, {name}!"
|
|
206
|
+
age = age_result.data
|
|
207
|
+
|
|
208
|
+
# Step 3: Get favorite food
|
|
209
|
+
food_result = await ctx.elicit(
|
|
210
|
+
"What's your favorite food?",
|
|
211
|
+
response_type=["pizza", "sushi", "tacos", "burgers", "other"]
|
|
212
|
+
)
|
|
213
|
+
if food_result.action != "accept":
|
|
214
|
+
return f"Survey cancelled at food question. Thanks for participating, {name}!"
|
|
215
|
+
favorite_food = food_result.data
|
|
216
|
+
|
|
217
|
+
# Step 4: Get satisfaction rating
|
|
218
|
+
rating_result = await ctx.elicit(
|
|
219
|
+
"How satisfied are you with this survey? (1-10)",
|
|
220
|
+
response_type=int
|
|
221
|
+
)
|
|
222
|
+
if rating_result.action != "accept":
|
|
223
|
+
return f"Survey almost complete! Thanks for your responses, {name}!"
|
|
224
|
+
rating = rating_result.data
|
|
225
|
+
|
|
226
|
+
# All responses collected successfully
|
|
227
|
+
return (
|
|
228
|
+
f"Survey Complete! Thank you, {name}!\n\n"
|
|
229
|
+
f"Name: {name}\n"
|
|
230
|
+
f"Age: {age}\n"
|
|
231
|
+
f"Favorite Food: {favorite_food}\n"
|
|
232
|
+
f"Satisfaction Rating: {rating}/10\n\n"
|
|
233
|
+
f"We appreciate your time!"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@mcp.tool
|
|
238
|
+
async def approve_deletion(ctx: Context) -> str:
|
|
239
|
+
"""
|
|
240
|
+
Elicitation example requesting approval with no data response.
|
|
241
|
+
|
|
242
|
+
This tool demonstrates approval-only elicitation - asking the user
|
|
243
|
+
to simply approve or reject without providing any additional data.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
Result of the deletion approval
|
|
247
|
+
"""
|
|
248
|
+
result = await ctx.elicit(
|
|
249
|
+
"Are you sure you want to delete this item? This action cannot be undone.",
|
|
250
|
+
response_type=None
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
if result.action == "accept":
|
|
254
|
+
return "Item deleted successfully."
|
|
255
|
+
elif result.action == "decline":
|
|
256
|
+
return "Deletion declined. Item was not deleted."
|
|
257
|
+
else: # cancel
|
|
258
|
+
return "Operation cancelled. Item was not deleted."
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
if __name__ == "__main__":
|
|
262
|
+
mcp.run()
|