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,348 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Filesystem MCP Server using FastMCP
|
|
4
|
+
Provides file system read/write operations through MCP protocol.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Dict
|
|
9
|
+
|
|
10
|
+
from fastmcp import FastMCP
|
|
11
|
+
|
|
12
|
+
# Initialize the MCP server
|
|
13
|
+
mcp = FastMCP("Filesystem")
|
|
14
|
+
|
|
15
|
+
# Base path for file operations (security constraint)
|
|
16
|
+
BASE_PATH = Path(".").resolve()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _safe_path(path: str) -> Path:
|
|
20
|
+
"""Ensure path is within base directory for security."""
|
|
21
|
+
requested_path = Path(path)
|
|
22
|
+
if requested_path.is_absolute():
|
|
23
|
+
full_path = requested_path
|
|
24
|
+
else:
|
|
25
|
+
full_path = BASE_PATH / requested_path
|
|
26
|
+
|
|
27
|
+
resolved_path = full_path.resolve()
|
|
28
|
+
|
|
29
|
+
# Ensure the path is within BASE_PATH
|
|
30
|
+
try:
|
|
31
|
+
resolved_path.relative_to(BASE_PATH)
|
|
32
|
+
except ValueError:
|
|
33
|
+
raise PermissionError("Access denied: path outside base directory")
|
|
34
|
+
|
|
35
|
+
return resolved_path
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@mcp.tool
|
|
39
|
+
def read_file(path: str) -> Dict[str, Any]:
|
|
40
|
+
"""
|
|
41
|
+
Read the complete contents of a text file from the secure filesystem.
|
|
42
|
+
|
|
43
|
+
This tool provides safe file reading capabilities with security restrictions:
|
|
44
|
+
- Only allows access to files within the designated base directory
|
|
45
|
+
- Supports both absolute and relative paths (relative paths are resolved from base)
|
|
46
|
+
- Automatically handles UTF-8 text encoding
|
|
47
|
+
- Returns file metadata along with content
|
|
48
|
+
|
|
49
|
+
**Security Features:**
|
|
50
|
+
- Path traversal protection (no access outside base directory)
|
|
51
|
+
- File existence validation
|
|
52
|
+
- Error handling for permission and access issues
|
|
53
|
+
|
|
54
|
+
**Use Cases:**
|
|
55
|
+
- Reading configuration files, logs, documentation
|
|
56
|
+
- Loading data files for processing
|
|
57
|
+
- Inspecting file contents for analysis
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
path: File path to read (string). Can be relative to base directory or absolute within allowed area.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Dictionary containing:
|
|
64
|
+
- success: boolean indicating operation success
|
|
65
|
+
- content: string with complete file contents (UTF-8)
|
|
66
|
+
- path: resolved relative path from base directory
|
|
67
|
+
- size: file size in bytes
|
|
68
|
+
Or error message if operation fails
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
file_path = _safe_path(path)
|
|
72
|
+
if not file_path.exists():
|
|
73
|
+
return {"results": {"error": f"File not found: {path}"}}
|
|
74
|
+
|
|
75
|
+
if file_path.is_dir():
|
|
76
|
+
return {"results": {"error": f"Path is a directory: {path}"}}
|
|
77
|
+
|
|
78
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
79
|
+
content = f.read()
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
"results": {
|
|
83
|
+
"content": content,
|
|
84
|
+
"size": len(content),
|
|
85
|
+
"path": str(file_path.relative_to(BASE_PATH))
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
except PermissionError as e:
|
|
89
|
+
return {"results": {"error": str(e)}}
|
|
90
|
+
except Exception as e:
|
|
91
|
+
return {"results": {"error": f"Error reading file: {str(e)}"}}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@mcp.tool
|
|
95
|
+
def write_file(path: str, content: str) -> Dict[str, Any]:
|
|
96
|
+
"""
|
|
97
|
+
Write text content to a file, creating the file and any necessary parent directories.
|
|
98
|
+
|
|
99
|
+
This tool provides safe file writing capabilities with automatic directory creation:
|
|
100
|
+
- Creates parent directories if they don't exist
|
|
101
|
+
- Overwrites existing files completely with new content
|
|
102
|
+
- Uses UTF-8 encoding for text files
|
|
103
|
+
- Security restrictions apply (no access outside base directory)
|
|
104
|
+
|
|
105
|
+
**Security Features:**
|
|
106
|
+
- Path traversal protection
|
|
107
|
+
- Safe directory creation with proper permissions
|
|
108
|
+
- Error handling for permission and access issues
|
|
109
|
+
|
|
110
|
+
**Use Cases:**
|
|
111
|
+
- Creating configuration files, reports, logs
|
|
112
|
+
- Saving processed data, analysis results
|
|
113
|
+
- Generating documentation or output files
|
|
114
|
+
- Creating backup copies of modified content
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
path: File path to write (string). Can be relative to base directory or absolute within allowed area.
|
|
118
|
+
content: Text content to write to the file (string, UTF-8 encoded)
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Dictionary containing:
|
|
122
|
+
- success: boolean indicating operation success
|
|
123
|
+
- path: resolved relative path from base directory
|
|
124
|
+
- size: number of characters written
|
|
125
|
+
Or error message if operation fails
|
|
126
|
+
"""
|
|
127
|
+
try:
|
|
128
|
+
file_path = _safe_path(path)
|
|
129
|
+
|
|
130
|
+
# Create parent directories if they don't exist
|
|
131
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
132
|
+
|
|
133
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
134
|
+
f.write(content)
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
"results": {
|
|
138
|
+
"success": True,
|
|
139
|
+
"path": str(file_path.relative_to(BASE_PATH)),
|
|
140
|
+
"size": len(content)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
except PermissionError as e:
|
|
144
|
+
return {"results": {"error": str(e)}}
|
|
145
|
+
except Exception as e:
|
|
146
|
+
return {"results": {"error": f"Error writing file: {str(e)}"}}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@mcp.tool
|
|
150
|
+
def list_directory(path: str = ".") -> Dict[str, Any]:
|
|
151
|
+
"""
|
|
152
|
+
List all files and subdirectories within a specified directory with detailed metadata.
|
|
153
|
+
|
|
154
|
+
This tool provides comprehensive directory browsing capabilities:
|
|
155
|
+
- Lists both files and subdirectories with type identification
|
|
156
|
+
- Provides metadata including size, modification time, and permissions
|
|
157
|
+
- Supports recursive directory exploration within security boundaries
|
|
158
|
+
- Defaults to current working directory if no path specified
|
|
159
|
+
|
|
160
|
+
**Security Features:**
|
|
161
|
+
- Path traversal protection (no access outside base directory)
|
|
162
|
+
- Directory existence validation
|
|
163
|
+
- Safe handling of permission-restricted items
|
|
164
|
+
|
|
165
|
+
**Returned Information:**
|
|
166
|
+
- Item name and type (file/directory)
|
|
167
|
+
- File size (for files)
|
|
168
|
+
- Last modification timestamp
|
|
169
|
+
- Basic permission information
|
|
170
|
+
|
|
171
|
+
**Use Cases:**
|
|
172
|
+
- Exploring project structure and organization
|
|
173
|
+
- Finding specific files or directories
|
|
174
|
+
- Checking file metadata before processing
|
|
175
|
+
- Navigating filesystem for file operations
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
path: Directory path to list (string, defaults to current directory)
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Dictionary containing:
|
|
182
|
+
- path: resolved directory path
|
|
183
|
+
- items: list of dictionaries with file/directory information
|
|
184
|
+
Each item includes: name, type, size (if file), modified_time, is_directory
|
|
185
|
+
Or error message if operation fails
|
|
186
|
+
"""
|
|
187
|
+
try:
|
|
188
|
+
dir_path = _safe_path(path)
|
|
189
|
+
if not dir_path.exists():
|
|
190
|
+
return {"results": {"error": f"Directory not found: {path}"}}
|
|
191
|
+
|
|
192
|
+
if not dir_path.is_dir():
|
|
193
|
+
return {"results": {"error": f"Path is not a directory: {path}"}}
|
|
194
|
+
|
|
195
|
+
items = []
|
|
196
|
+
for item in dir_path.iterdir():
|
|
197
|
+
items.append({
|
|
198
|
+
"name": item.name,
|
|
199
|
+
"type": "directory" if item.is_dir() else "file",
|
|
200
|
+
"size": item.stat().st_size if item.is_file() else None
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
"results": {
|
|
205
|
+
"path": str(dir_path.relative_to(BASE_PATH)),
|
|
206
|
+
"items": sorted(items, key=lambda x: (x["type"], x["name"]))
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
except PermissionError as e:
|
|
210
|
+
return {"results": {"error": str(e)}}
|
|
211
|
+
except Exception as e:
|
|
212
|
+
return {"results": {"error": f"Error listing directory: {str(e)}"}}
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@mcp.tool
|
|
216
|
+
def create_directory(path: str) -> Dict[str, Any]:
|
|
217
|
+
"""
|
|
218
|
+
Create a new directory, including any necessary parent directories in the path.
|
|
219
|
+
|
|
220
|
+
This tool provides comprehensive directory creation capabilities:
|
|
221
|
+
- Creates the target directory and all parent directories if they don't exist
|
|
222
|
+
- Uses safe permissions and follows system defaults
|
|
223
|
+
- Handles existing directories gracefully (no error if already exists)
|
|
224
|
+
- Security restrictions apply (no access outside base directory)
|
|
225
|
+
|
|
226
|
+
**Security Features:**
|
|
227
|
+
- Path traversal protection
|
|
228
|
+
- Safe directory creation with proper permissions
|
|
229
|
+
- Error handling for permission and access issues
|
|
230
|
+
|
|
231
|
+
**Use Cases:**
|
|
232
|
+
- Setting up project folder structures
|
|
233
|
+
- Creating organized storage for different file types
|
|
234
|
+
- Preparing directories for batch file operations
|
|
235
|
+
- Establishing workspace organization
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
path: Directory path to create (string). Can be relative to base or absolute within allowed area.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
Dictionary containing:
|
|
242
|
+
- success: boolean indicating operation success
|
|
243
|
+
- path: resolved relative path from base directory
|
|
244
|
+
Or error message if operation fails
|
|
245
|
+
"""
|
|
246
|
+
try:
|
|
247
|
+
dir_path = _safe_path(path)
|
|
248
|
+
dir_path.mkdir(parents=True, exist_ok=True)
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
"results": {
|
|
252
|
+
"success": True,
|
|
253
|
+
"path": str(dir_path.relative_to(BASE_PATH))
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
except PermissionError as e:
|
|
257
|
+
return {"results": {"error": str(e)}}
|
|
258
|
+
except Exception as e:
|
|
259
|
+
return {"results": {"error": f"Error creating directory: {str(e)}"}}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@mcp.tool
|
|
263
|
+
def delete_file(path: str) -> Dict[str, Any]:
|
|
264
|
+
"""
|
|
265
|
+
Permanently delete a single file from the filesystem with safety validations.
|
|
266
|
+
|
|
267
|
+
This tool provides secure file deletion capabilities:
|
|
268
|
+
- Only deletes files (not directories - use appropriate directory removal tools)
|
|
269
|
+
- Validates file existence before attempting deletion
|
|
270
|
+
- Provides clear error messages for different failure conditions
|
|
271
|
+
- Security restrictions apply (no access outside base directory)
|
|
272
|
+
|
|
273
|
+
**Security Features:**
|
|
274
|
+
- Path traversal protection
|
|
275
|
+
- File vs directory validation (prevents accidental directory deletion)
|
|
276
|
+
- File existence verification
|
|
277
|
+
- Error handling for permission issues
|
|
278
|
+
|
|
279
|
+
**Important Notes:**
|
|
280
|
+
- This operation is permanent and cannot be undone
|
|
281
|
+
- Only works on files, not directories
|
|
282
|
+
- Will fail safely if target is a directory or doesn't exist
|
|
283
|
+
|
|
284
|
+
**Use Cases:**
|
|
285
|
+
- Cleaning up temporary or outdated files
|
|
286
|
+
- Removing processed files after successful operations
|
|
287
|
+
- Managing storage space by removing unnecessary files
|
|
288
|
+
- Maintaining clean project directories
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
path: File path to delete (string). Can be relative to base or absolute within allowed area.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
Dictionary containing:
|
|
295
|
+
- success: boolean indicating operation success
|
|
296
|
+
- path: resolved relative path of deleted file
|
|
297
|
+
Or error message if operation fails (file not found, is directory, permission denied)
|
|
298
|
+
"""
|
|
299
|
+
try:
|
|
300
|
+
file_path = _safe_path(path)
|
|
301
|
+
if not file_path.exists():
|
|
302
|
+
return {"results": {"error": f"File not found: {path}"}}
|
|
303
|
+
|
|
304
|
+
if file_path.is_dir():
|
|
305
|
+
return {"results": {"error": f"Path is a directory (use rmdir): {path}"}}
|
|
306
|
+
|
|
307
|
+
file_path.unlink()
|
|
308
|
+
return {
|
|
309
|
+
"results": {
|
|
310
|
+
"success": True,
|
|
311
|
+
"path": str(file_path.relative_to(BASE_PATH))
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
except PermissionError as e:
|
|
315
|
+
return {"error": str(e)}
|
|
316
|
+
except Exception as e:
|
|
317
|
+
return {"error": f"Error deleting file: {str(e)}"}
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
@mcp.tool
|
|
321
|
+
def file_exists(path: str) -> Dict[str, Any]:
|
|
322
|
+
"""
|
|
323
|
+
Check if a file or directory exists.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
path: Path to check
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
Dictionary with existence status and file type information
|
|
330
|
+
"""
|
|
331
|
+
try:
|
|
332
|
+
file_path = _safe_path(path)
|
|
333
|
+
return {
|
|
334
|
+
"results": {
|
|
335
|
+
"exists": file_path.exists(),
|
|
336
|
+
"is_file": file_path.is_file(),
|
|
337
|
+
"is_directory": file_path.is_dir(),
|
|
338
|
+
"path": str(file_path.relative_to(BASE_PATH))
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
except PermissionError as e:
|
|
342
|
+
return {"results": {"error": str(e)}}
|
|
343
|
+
except Exception as e:
|
|
344
|
+
return {"results": {"error": f"Error checking file: {str(e)}"}}
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
if __name__ == "__main__":
|
|
348
|
+
mcp.run()
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP server that demonstrates returning ImageContent.
|
|
3
|
+
This is a test server to validate image display functionality.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import base64
|
|
7
|
+
from io import BytesIO
|
|
8
|
+
|
|
9
|
+
from fastmcp import FastMCP
|
|
10
|
+
from fastmcp.tools.tool import ToolResult
|
|
11
|
+
from mcp.types import ImageContent, TextContent
|
|
12
|
+
|
|
13
|
+
mcp = FastMCP("Image Demo MCP")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@mcp.tool()
|
|
17
|
+
def generate_test_image() -> ToolResult:
|
|
18
|
+
"""
|
|
19
|
+
Generate a simple test PNG image and return it as ImageContent.
|
|
20
|
+
This demonstrates the ImageContent return type for MCP tools.
|
|
21
|
+
"""
|
|
22
|
+
try:
|
|
23
|
+
from PIL import Image, ImageDraw, ImageFont
|
|
24
|
+
except ImportError:
|
|
25
|
+
# If PIL is not available, return a minimal 1x1 PNG
|
|
26
|
+
# This is a valid 1x1 red pixel PNG in base64
|
|
27
|
+
minimal_png = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg=="
|
|
28
|
+
return ToolResult(
|
|
29
|
+
content=[
|
|
30
|
+
TextContent(type="text", text="Success: Generated fallback image (PIL not available). Returned image_0.png"),
|
|
31
|
+
ImageContent(type="image", data=minimal_png, mimeType="image/png")
|
|
32
|
+
]
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Create a 400x300 image with a gradient background
|
|
36
|
+
img = Image.new('RGB', (400, 300), color='white')
|
|
37
|
+
draw = ImageDraw.Draw(img)
|
|
38
|
+
|
|
39
|
+
# Draw a gradient
|
|
40
|
+
for y in range(300):
|
|
41
|
+
color_val = int(255 * (y / 300))
|
|
42
|
+
draw.rectangle([(0, y), (400, y+1)], fill=(color_val, 100, 255 - color_val))
|
|
43
|
+
|
|
44
|
+
# Draw some text
|
|
45
|
+
try:
|
|
46
|
+
# Try to use a default font
|
|
47
|
+
font = ImageFont.load_default()
|
|
48
|
+
except Exception:
|
|
49
|
+
font = None
|
|
50
|
+
|
|
51
|
+
draw.text((50, 130), "Test Image from MCP Tool", fill=(255, 255, 255), font=font)
|
|
52
|
+
draw.text((50, 160), "This demonstrates ImageContent", fill=(255, 255, 255), font=font)
|
|
53
|
+
|
|
54
|
+
# Convert to base64
|
|
55
|
+
buffer = BytesIO()
|
|
56
|
+
img.save(buffer, format='PNG')
|
|
57
|
+
img_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
|
|
58
|
+
|
|
59
|
+
return ToolResult(
|
|
60
|
+
content=[
|
|
61
|
+
TextContent(type="text", text="Success: Generated 400x300 PNG test image with gradient background. Returned image_0.png - the image should now be visible in the canvas panel."),
|
|
62
|
+
ImageContent(type="image", data=img_base64, mimeType="image/png")
|
|
63
|
+
]
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@mcp.tool()
|
|
68
|
+
def generate_multiple_images() -> ToolResult:
|
|
69
|
+
"""
|
|
70
|
+
Generate multiple test images and return them as a list of ImageContent.
|
|
71
|
+
This tests handling multiple images in a single tool response.
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
from PIL import Image, ImageDraw
|
|
75
|
+
except ImportError:
|
|
76
|
+
# Return minimal PNGs if PIL not available
|
|
77
|
+
minimal_png = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg=="
|
|
78
|
+
return ToolResult(
|
|
79
|
+
content=[
|
|
80
|
+
TextContent(type="text", text="Success: Generated 2 fallback images (PIL not available). Returned image_0.png, image_1.png"),
|
|
81
|
+
ImageContent(type="image", data=minimal_png, mimeType="image/png"),
|
|
82
|
+
ImageContent(type="image", data=minimal_png, mimeType="image/png")
|
|
83
|
+
]
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
content_blocks = [
|
|
87
|
+
TextContent(type="text", text="Success: Generated 3 colored PNG images (200x200 each: red, green, blue with white circles). Returned image_0.png, image_1.png, image_2.png - images should now be visible in the canvas panel.")
|
|
88
|
+
]
|
|
89
|
+
colors = [(255, 100, 100), (100, 255, 100), (100, 100, 255)]
|
|
90
|
+
|
|
91
|
+
for i, color in enumerate(colors):
|
|
92
|
+
img = Image.new('RGB', (200, 200), color=color)
|
|
93
|
+
draw = ImageDraw.Draw(img)
|
|
94
|
+
|
|
95
|
+
# Draw a circle
|
|
96
|
+
draw.ellipse([50, 50, 150, 150], fill=(255, 255, 255))
|
|
97
|
+
|
|
98
|
+
# Convert to base64
|
|
99
|
+
buffer = BytesIO()
|
|
100
|
+
img.save(buffer, format='PNG')
|
|
101
|
+
img_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
|
|
102
|
+
|
|
103
|
+
content_blocks.append(ImageContent(
|
|
104
|
+
type="image",
|
|
105
|
+
data=img_base64,
|
|
106
|
+
mimeType="image/png"
|
|
107
|
+
))
|
|
108
|
+
|
|
109
|
+
return ToolResult(content=content_blocks)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
if __name__ == "__main__":
|
|
113
|
+
mcp.run()
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Logging Demo MCP Server
|
|
2
|
+
|
|
3
|
+
A demonstration MCP server that showcases the logging capabilities of Atlas UI 3's MCP integration.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
This server provides a simple tool that emits log messages at various levels to test and demonstrate the MCP logging infrastructure.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
### Configuration
|
|
12
|
+
|
|
13
|
+
Add to your `mcp.json`:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"logging_demo": {
|
|
18
|
+
"command": ["python", "main.py"],
|
|
19
|
+
"cwd": "backend/mcp/logging_demo",
|
|
20
|
+
"transport": "stdio"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Tool: test_logging
|
|
26
|
+
|
|
27
|
+
Test MCP server logging at various levels.
|
|
28
|
+
|
|
29
|
+
**Parameters:**
|
|
30
|
+
- `operation` (string): Which logging levels to test
|
|
31
|
+
- `"all"` - Test all log levels (debug, info, warning, error)
|
|
32
|
+
- `"debug"` - Debug level only
|
|
33
|
+
- `"info"` - Info level only
|
|
34
|
+
- `"warning"` - Warning level only
|
|
35
|
+
- `"error"` - Error level only
|
|
36
|
+
- `"mixed"` - A realistic mix of levels simulating an operation
|
|
37
|
+
|
|
38
|
+
**Examples:**
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
Use the tool: logging_demo_test_logging with operation="all"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
Use the tool: logging_demo_test_logging with operation="mixed"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Expected Output
|
|
49
|
+
|
|
50
|
+
When you call this tool with `operation="all"`, you should see:
|
|
51
|
+
1. The tool result showing success
|
|
52
|
+
2. Log messages appearing in the chat window with colored badges:
|
|
53
|
+
- DEBUG (gray) - "This is a DEBUG message..."
|
|
54
|
+
- INFO (blue) - "This is an INFO message..."
|
|
55
|
+
- WARNING (yellow) - "This is a WARNING message..."
|
|
56
|
+
- ERROR (red) - "This is an ERROR message..."
|
|
57
|
+
|
|
58
|
+
**Note:** DEBUG logs will only appear if `LOG_LEVEL=DEBUG` is set in your environment.
|
|
59
|
+
|
|
60
|
+
## Testing Log Levels
|
|
61
|
+
|
|
62
|
+
To test filtering:
|
|
63
|
+
|
|
64
|
+
1. Set `LOG_LEVEL=WARNING` in `.env`
|
|
65
|
+
2. Restart the backend
|
|
66
|
+
3. Call the tool with `operation="all"`
|
|
67
|
+
4. You should only see WARNING and ERROR messages (INFO and DEBUG filtered out)
|
|
68
|
+
|
|
69
|
+
## See Also
|
|
70
|
+
|
|
71
|
+
- [MCP Server Logging Documentation](../../docs/developer/mcp-server-logging.md)
|
|
72
|
+
- [FastMCP Logging Documentation](https://gofastmcp.com/clients/logging)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Logging Demo MCP Server using FastMCP
|
|
4
|
+
Demonstrates MCP server logging at various levels.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import time
|
|
9
|
+
from typing import Any, Dict
|
|
10
|
+
|
|
11
|
+
from fastmcp import Context, FastMCP
|
|
12
|
+
|
|
13
|
+
# Initialize the MCP server
|
|
14
|
+
mcp = FastMCP("Logging Demo")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@mcp.tool
|
|
18
|
+
async def test_logging(operation: str, ctx: Context) -> Dict[str, Any]:
|
|
19
|
+
"""Test MCP server logging at various levels.
|
|
20
|
+
|
|
21
|
+
This tool demonstrates log output from an MCP server. It emits log messages
|
|
22
|
+
at different levels (debug, info, warning, error) to test the logging infrastructure.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
operation: Which logging levels to test. Options:
|
|
26
|
+
- "all": Test all log levels
|
|
27
|
+
- "debug": Debug level only
|
|
28
|
+
- "info": Info level only
|
|
29
|
+
- "warning": Warning level only
|
|
30
|
+
- "error": Error level only
|
|
31
|
+
- "mixed": A realistic mix of levels
|
|
32
|
+
- "mixed-delay": Like "mixed", but with short delays between log calls
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Dict with results and logs emitted
|
|
36
|
+
"""
|
|
37
|
+
start = time.perf_counter()
|
|
38
|
+
|
|
39
|
+
logs_emitted = []
|
|
40
|
+
|
|
41
|
+
if operation == "all" or operation == "debug":
|
|
42
|
+
await ctx.debug("This is a DEBUG message - detailed information for diagnostics")
|
|
43
|
+
logs_emitted.append("debug")
|
|
44
|
+
|
|
45
|
+
if operation == "all" or operation == "info":
|
|
46
|
+
await ctx.info("This is an INFO message - general informational message")
|
|
47
|
+
logs_emitted.append("info")
|
|
48
|
+
|
|
49
|
+
if operation == "all" or operation == "warning":
|
|
50
|
+
await ctx.warning("This is a WARNING message - something unexpected happened")
|
|
51
|
+
logs_emitted.append("warning")
|
|
52
|
+
|
|
53
|
+
if operation == "all" or operation == "error":
|
|
54
|
+
await ctx.error("This is an ERROR message - something went wrong")
|
|
55
|
+
logs_emitted.append("error")
|
|
56
|
+
|
|
57
|
+
if operation == "mixed":
|
|
58
|
+
await ctx.info("Starting operation...")
|
|
59
|
+
await ctx.debug("Processing step 1")
|
|
60
|
+
await ctx.debug("Processing step 2")
|
|
61
|
+
await ctx.info("Operation in progress (50% complete)")
|
|
62
|
+
await ctx.debug("Processing step 3")
|
|
63
|
+
await ctx.warning("Encountered a minor issue, continuing...")
|
|
64
|
+
await ctx.debug("Processing step 4")
|
|
65
|
+
await ctx.info("Operation completed successfully")
|
|
66
|
+
logs_emitted = ["info", "debug", "debug", "info", "debug", "warning", "debug", "info"]
|
|
67
|
+
|
|
68
|
+
if operation == "mixed-delay":
|
|
69
|
+
delay_s = 0.35
|
|
70
|
+
await ctx.info("Starting operation...")
|
|
71
|
+
await asyncio.sleep(delay_s)
|
|
72
|
+
await ctx.debug("Processing step 1")
|
|
73
|
+
await asyncio.sleep(delay_s)
|
|
74
|
+
await ctx.debug("Processing step 2")
|
|
75
|
+
await asyncio.sleep(delay_s)
|
|
76
|
+
await ctx.info("Operation in progress (50% complete)")
|
|
77
|
+
await asyncio.sleep(delay_s)
|
|
78
|
+
await ctx.debug("Processing step 3")
|
|
79
|
+
await asyncio.sleep(delay_s)
|
|
80
|
+
await ctx.warning("Encountered a minor issue, continuing...")
|
|
81
|
+
await asyncio.sleep(delay_s)
|
|
82
|
+
await ctx.debug("Processing step 4")
|
|
83
|
+
await asyncio.sleep(delay_s)
|
|
84
|
+
await ctx.info("Operation completed successfully")
|
|
85
|
+
logs_emitted = ["info", "debug", "debug", "info", "debug", "warning", "debug", "info"]
|
|
86
|
+
|
|
87
|
+
elapsed_ms = round((time.perf_counter() - start) * 1000, 3)
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
"results": {
|
|
91
|
+
"operation": operation,
|
|
92
|
+
"logs_emitted": logs_emitted,
|
|
93
|
+
"message": f"Successfully tested {operation} logging level(s)"
|
|
94
|
+
},
|
|
95
|
+
"meta_data": {
|
|
96
|
+
"is_error": False,
|
|
97
|
+
"elapsed_ms": elapsed_ms
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
mcp.run()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
MCP Server with 64 tools for testing UI with many tools.
|
|
4
|
+
Demonstrates that the collapsible UI can handle servers with large numbers of tools.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from fastmcp import FastMCP
|
|
8
|
+
|
|
9
|
+
# Create the MCP server
|
|
10
|
+
mcp = FastMCP("ManyToolsDemo")
|
|
11
|
+
|
|
12
|
+
# Generate 64 tools dynamically to test UI scalability
|
|
13
|
+
# Categories: data, analytics, file, network, system, database, security, report
|
|
14
|
+
|
|
15
|
+
TOOL_CATEGORIES = [
|
|
16
|
+
("data", 10, "Process and transform data"),
|
|
17
|
+
("analytics", 10, "Analyze data and generate insights"),
|
|
18
|
+
("file", 8, "File operations and management"),
|
|
19
|
+
("network", 8, "Network operations and monitoring"),
|
|
20
|
+
("system", 8, "System administration tasks"),
|
|
21
|
+
("database", 8, "Database operations"),
|
|
22
|
+
("security", 6, "Security and encryption tasks"),
|
|
23
|
+
("report", 6, "Report generation and formatting"),
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
# Dynamically create tools for each category
|
|
27
|
+
for category, count, description_base in TOOL_CATEGORIES:
|
|
28
|
+
for i in range(1, count + 1):
|
|
29
|
+
tool_name = f"{category}_operation_{i}"
|
|
30
|
+
tool_description = f"{description_base} - Operation {i}"
|
|
31
|
+
|
|
32
|
+
# Use exec to create properly named functions
|
|
33
|
+
exec(f"""
|
|
34
|
+
@mcp.tool()
|
|
35
|
+
def {tool_name}(input_data: str = "default") -> str:
|
|
36
|
+
'''
|
|
37
|
+
{tool_description}
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
input_data: Input data to process
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
str: Result of the operation
|
|
44
|
+
'''
|
|
45
|
+
return f"Executed {tool_name} with input: {{input_data}}"
|
|
46
|
+
""")
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
mcp.run()
|
|
50
|
+
|
|
File without changes
|