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.
Files changed (250) hide show
  1. atlas/__init__.py +40 -0
  2. atlas/application/__init__.py +7 -0
  3. atlas/application/chat/__init__.py +7 -0
  4. atlas/application/chat/agent/__init__.py +10 -0
  5. atlas/application/chat/agent/act_loop.py +179 -0
  6. atlas/application/chat/agent/factory.py +142 -0
  7. atlas/application/chat/agent/protocols.py +46 -0
  8. atlas/application/chat/agent/react_loop.py +338 -0
  9. atlas/application/chat/agent/think_act_loop.py +171 -0
  10. atlas/application/chat/approval_manager.py +151 -0
  11. atlas/application/chat/elicitation_manager.py +191 -0
  12. atlas/application/chat/events/__init__.py +1 -0
  13. atlas/application/chat/events/agent_event_relay.py +112 -0
  14. atlas/application/chat/modes/__init__.py +1 -0
  15. atlas/application/chat/modes/agent.py +125 -0
  16. atlas/application/chat/modes/plain.py +74 -0
  17. atlas/application/chat/modes/rag.py +81 -0
  18. atlas/application/chat/modes/tools.py +179 -0
  19. atlas/application/chat/orchestrator.py +213 -0
  20. atlas/application/chat/policies/__init__.py +1 -0
  21. atlas/application/chat/policies/tool_authorization.py +99 -0
  22. atlas/application/chat/preprocessors/__init__.py +1 -0
  23. atlas/application/chat/preprocessors/message_builder.py +92 -0
  24. atlas/application/chat/preprocessors/prompt_override_service.py +104 -0
  25. atlas/application/chat/service.py +454 -0
  26. atlas/application/chat/utilities/__init__.py +6 -0
  27. atlas/application/chat/utilities/error_handler.py +367 -0
  28. atlas/application/chat/utilities/event_notifier.py +546 -0
  29. atlas/application/chat/utilities/file_processor.py +613 -0
  30. atlas/application/chat/utilities/tool_executor.py +789 -0
  31. atlas/atlas_chat_cli.py +347 -0
  32. atlas/atlas_client.py +238 -0
  33. atlas/core/__init__.py +0 -0
  34. atlas/core/auth.py +205 -0
  35. atlas/core/authorization_manager.py +27 -0
  36. atlas/core/capabilities.py +123 -0
  37. atlas/core/compliance.py +215 -0
  38. atlas/core/domain_whitelist.py +147 -0
  39. atlas/core/domain_whitelist_middleware.py +82 -0
  40. atlas/core/http_client.py +28 -0
  41. atlas/core/log_sanitizer.py +102 -0
  42. atlas/core/metrics_logger.py +59 -0
  43. atlas/core/middleware.py +131 -0
  44. atlas/core/otel_config.py +242 -0
  45. atlas/core/prompt_risk.py +200 -0
  46. atlas/core/rate_limit.py +0 -0
  47. atlas/core/rate_limit_middleware.py +64 -0
  48. atlas/core/security_headers_middleware.py +51 -0
  49. atlas/domain/__init__.py +37 -0
  50. atlas/domain/chat/__init__.py +1 -0
  51. atlas/domain/chat/dtos.py +85 -0
  52. atlas/domain/errors.py +96 -0
  53. atlas/domain/messages/__init__.py +12 -0
  54. atlas/domain/messages/models.py +160 -0
  55. atlas/domain/rag_mcp_service.py +664 -0
  56. atlas/domain/sessions/__init__.py +7 -0
  57. atlas/domain/sessions/models.py +36 -0
  58. atlas/domain/unified_rag_service.py +371 -0
  59. atlas/infrastructure/__init__.py +10 -0
  60. atlas/infrastructure/app_factory.py +135 -0
  61. atlas/infrastructure/events/__init__.py +1 -0
  62. atlas/infrastructure/events/cli_event_publisher.py +140 -0
  63. atlas/infrastructure/events/websocket_publisher.py +140 -0
  64. atlas/infrastructure/sessions/in_memory_repository.py +56 -0
  65. atlas/infrastructure/transport/__init__.py +7 -0
  66. atlas/infrastructure/transport/websocket_connection_adapter.py +33 -0
  67. atlas/init_cli.py +226 -0
  68. atlas/interfaces/__init__.py +15 -0
  69. atlas/interfaces/events.py +134 -0
  70. atlas/interfaces/llm.py +54 -0
  71. atlas/interfaces/rag.py +40 -0
  72. atlas/interfaces/sessions.py +75 -0
  73. atlas/interfaces/tools.py +57 -0
  74. atlas/interfaces/transport.py +24 -0
  75. atlas/main.py +564 -0
  76. atlas/mcp/api_key_demo/README.md +76 -0
  77. atlas/mcp/api_key_demo/main.py +172 -0
  78. atlas/mcp/api_key_demo/run.sh +56 -0
  79. atlas/mcp/basictable/main.py +147 -0
  80. atlas/mcp/calculator/main.py +149 -0
  81. atlas/mcp/code-executor/execution_engine.py +98 -0
  82. atlas/mcp/code-executor/execution_environment.py +95 -0
  83. atlas/mcp/code-executor/main.py +528 -0
  84. atlas/mcp/code-executor/result_processing.py +276 -0
  85. atlas/mcp/code-executor/script_generation.py +195 -0
  86. atlas/mcp/code-executor/security_checker.py +140 -0
  87. atlas/mcp/corporate_cars/main.py +437 -0
  88. atlas/mcp/csv_reporter/main.py +545 -0
  89. atlas/mcp/duckduckgo/main.py +182 -0
  90. atlas/mcp/elicitation_demo/README.md +171 -0
  91. atlas/mcp/elicitation_demo/main.py +262 -0
  92. atlas/mcp/env-demo/README.md +158 -0
  93. atlas/mcp/env-demo/main.py +199 -0
  94. atlas/mcp/file_size_test/main.py +284 -0
  95. atlas/mcp/filesystem/main.py +348 -0
  96. atlas/mcp/image_demo/main.py +113 -0
  97. atlas/mcp/image_demo/requirements.txt +4 -0
  98. atlas/mcp/logging_demo/README.md +72 -0
  99. atlas/mcp/logging_demo/main.py +103 -0
  100. atlas/mcp/many_tools_demo/main.py +50 -0
  101. atlas/mcp/order_database/__init__.py +0 -0
  102. atlas/mcp/order_database/main.py +369 -0
  103. atlas/mcp/order_database/signal_data.csv +1001 -0
  104. atlas/mcp/pdfbasic/main.py +394 -0
  105. atlas/mcp/pptx_generator/main.py +760 -0
  106. atlas/mcp/pptx_generator/requirements.txt +13 -0
  107. atlas/mcp/pptx_generator/run_test.sh +1 -0
  108. atlas/mcp/pptx_generator/test_pptx_generator_security.py +169 -0
  109. atlas/mcp/progress_demo/main.py +167 -0
  110. atlas/mcp/progress_updates_demo/QUICKSTART.md +273 -0
  111. atlas/mcp/progress_updates_demo/README.md +120 -0
  112. atlas/mcp/progress_updates_demo/main.py +497 -0
  113. atlas/mcp/prompts/main.py +222 -0
  114. atlas/mcp/public_demo/main.py +189 -0
  115. atlas/mcp/sampling_demo/README.md +169 -0
  116. atlas/mcp/sampling_demo/main.py +234 -0
  117. atlas/mcp/thinking/main.py +77 -0
  118. atlas/mcp/tool_planner/main.py +240 -0
  119. atlas/mcp/ui-demo/badmesh.png +0 -0
  120. atlas/mcp/ui-demo/main.py +383 -0
  121. atlas/mcp/ui-demo/templates/button_demo.html +32 -0
  122. atlas/mcp/ui-demo/templates/data_visualization.html +32 -0
  123. atlas/mcp/ui-demo/templates/form_demo.html +28 -0
  124. atlas/mcp/username-override-demo/README.md +320 -0
  125. atlas/mcp/username-override-demo/main.py +308 -0
  126. atlas/modules/__init__.py +0 -0
  127. atlas/modules/config/__init__.py +34 -0
  128. atlas/modules/config/cli.py +231 -0
  129. atlas/modules/config/config_manager.py +1096 -0
  130. atlas/modules/file_storage/__init__.py +22 -0
  131. atlas/modules/file_storage/cli.py +330 -0
  132. atlas/modules/file_storage/content_extractor.py +290 -0
  133. atlas/modules/file_storage/manager.py +295 -0
  134. atlas/modules/file_storage/mock_s3_client.py +402 -0
  135. atlas/modules/file_storage/s3_client.py +417 -0
  136. atlas/modules/llm/__init__.py +19 -0
  137. atlas/modules/llm/caller.py +287 -0
  138. atlas/modules/llm/litellm_caller.py +675 -0
  139. atlas/modules/llm/models.py +19 -0
  140. atlas/modules/mcp_tools/__init__.py +17 -0
  141. atlas/modules/mcp_tools/client.py +2123 -0
  142. atlas/modules/mcp_tools/token_storage.py +556 -0
  143. atlas/modules/prompts/prompt_provider.py +130 -0
  144. atlas/modules/rag/__init__.py +24 -0
  145. atlas/modules/rag/atlas_rag_client.py +336 -0
  146. atlas/modules/rag/client.py +129 -0
  147. atlas/routes/admin_routes.py +865 -0
  148. atlas/routes/config_routes.py +484 -0
  149. atlas/routes/feedback_routes.py +361 -0
  150. atlas/routes/files_routes.py +274 -0
  151. atlas/routes/health_routes.py +40 -0
  152. atlas/routes/mcp_auth_routes.py +223 -0
  153. atlas/server_cli.py +164 -0
  154. atlas/tests/conftest.py +20 -0
  155. atlas/tests/integration/test_mcp_auth_integration.py +152 -0
  156. atlas/tests/manual_test_sampling.py +87 -0
  157. atlas/tests/modules/mcp_tools/test_client_auth.py +226 -0
  158. atlas/tests/modules/mcp_tools/test_client_env.py +191 -0
  159. atlas/tests/test_admin_mcp_server_management_routes.py +141 -0
  160. atlas/tests/test_agent_roa.py +135 -0
  161. atlas/tests/test_app_factory_smoke.py +47 -0
  162. atlas/tests/test_approval_manager.py +439 -0
  163. atlas/tests/test_atlas_client.py +188 -0
  164. atlas/tests/test_atlas_rag_client.py +447 -0
  165. atlas/tests/test_atlas_rag_integration.py +224 -0
  166. atlas/tests/test_attach_file_flow.py +287 -0
  167. atlas/tests/test_auth_utils.py +165 -0
  168. atlas/tests/test_backend_public_url.py +185 -0
  169. atlas/tests/test_banner_logging.py +287 -0
  170. atlas/tests/test_capability_tokens_and_injection.py +203 -0
  171. atlas/tests/test_compliance_level.py +54 -0
  172. atlas/tests/test_compliance_manager.py +253 -0
  173. atlas/tests/test_config_manager.py +617 -0
  174. atlas/tests/test_config_manager_paths.py +12 -0
  175. atlas/tests/test_core_auth.py +18 -0
  176. atlas/tests/test_core_utils.py +190 -0
  177. atlas/tests/test_docker_env_sync.py +202 -0
  178. atlas/tests/test_domain_errors.py +329 -0
  179. atlas/tests/test_domain_whitelist.py +359 -0
  180. atlas/tests/test_elicitation_manager.py +408 -0
  181. atlas/tests/test_elicitation_routing.py +296 -0
  182. atlas/tests/test_env_demo_server.py +88 -0
  183. atlas/tests/test_error_classification.py +113 -0
  184. atlas/tests/test_error_flow_integration.py +116 -0
  185. atlas/tests/test_feedback_routes.py +333 -0
  186. atlas/tests/test_file_content_extraction.py +1134 -0
  187. atlas/tests/test_file_extraction_routes.py +158 -0
  188. atlas/tests/test_file_library.py +107 -0
  189. atlas/tests/test_file_manager_unit.py +18 -0
  190. atlas/tests/test_health_route.py +49 -0
  191. atlas/tests/test_http_client_stub.py +8 -0
  192. atlas/tests/test_imports_smoke.py +30 -0
  193. atlas/tests/test_interfaces_llm_response.py +9 -0
  194. atlas/tests/test_issue_access_denied_fix.py +136 -0
  195. atlas/tests/test_llm_env_expansion.py +836 -0
  196. atlas/tests/test_log_level_sensitive_data.py +285 -0
  197. atlas/tests/test_mcp_auth_routes.py +341 -0
  198. atlas/tests/test_mcp_client_auth.py +331 -0
  199. atlas/tests/test_mcp_data_injection.py +270 -0
  200. atlas/tests/test_mcp_get_authorized_servers.py +95 -0
  201. atlas/tests/test_mcp_hot_reload.py +512 -0
  202. atlas/tests/test_mcp_image_content.py +424 -0
  203. atlas/tests/test_mcp_logging.py +172 -0
  204. atlas/tests/test_mcp_progress_updates.py +313 -0
  205. atlas/tests/test_mcp_prompt_override_system_prompt.py +102 -0
  206. atlas/tests/test_mcp_prompts_server.py +39 -0
  207. atlas/tests/test_mcp_tool_result_parsing.py +296 -0
  208. atlas/tests/test_metrics_logger.py +56 -0
  209. atlas/tests/test_middleware_auth.py +379 -0
  210. atlas/tests/test_prompt_risk_and_acl.py +141 -0
  211. atlas/tests/test_rag_mcp_aggregator.py +204 -0
  212. atlas/tests/test_rag_mcp_service.py +224 -0
  213. atlas/tests/test_rate_limit_middleware.py +45 -0
  214. atlas/tests/test_routes_config_smoke.py +60 -0
  215. atlas/tests/test_routes_files_download_token.py +41 -0
  216. atlas/tests/test_routes_files_health.py +18 -0
  217. atlas/tests/test_runtime_imports.py +53 -0
  218. atlas/tests/test_sampling_integration.py +482 -0
  219. atlas/tests/test_security_admin_routes.py +61 -0
  220. atlas/tests/test_security_capability_tokens.py +65 -0
  221. atlas/tests/test_security_file_stats_scope.py +21 -0
  222. atlas/tests/test_security_header_injection.py +191 -0
  223. atlas/tests/test_security_headers_and_filename.py +63 -0
  224. atlas/tests/test_shared_session_repository.py +101 -0
  225. atlas/tests/test_system_prompt_loading.py +181 -0
  226. atlas/tests/test_token_storage.py +505 -0
  227. atlas/tests/test_tool_approval_config.py +93 -0
  228. atlas/tests/test_tool_approval_utils.py +356 -0
  229. atlas/tests/test_tool_authorization_group_filtering.py +223 -0
  230. atlas/tests/test_tool_details_in_config.py +108 -0
  231. atlas/tests/test_tool_planner.py +300 -0
  232. atlas/tests/test_unified_rag_service.py +398 -0
  233. atlas/tests/test_username_override_in_approval.py +258 -0
  234. atlas/tests/test_websocket_auth_header.py +168 -0
  235. atlas/version.py +6 -0
  236. atlas_chat-0.1.0.data/data/.env.example +253 -0
  237. atlas_chat-0.1.0.data/data/config/defaults/compliance-levels.json +44 -0
  238. atlas_chat-0.1.0.data/data/config/defaults/domain-whitelist.json +123 -0
  239. atlas_chat-0.1.0.data/data/config/defaults/file-extractors.json +74 -0
  240. atlas_chat-0.1.0.data/data/config/defaults/help-config.json +198 -0
  241. atlas_chat-0.1.0.data/data/config/defaults/llmconfig-buggy.yml +11 -0
  242. atlas_chat-0.1.0.data/data/config/defaults/llmconfig.yml +19 -0
  243. atlas_chat-0.1.0.data/data/config/defaults/mcp.json +138 -0
  244. atlas_chat-0.1.0.data/data/config/defaults/rag-sources.json +17 -0
  245. atlas_chat-0.1.0.data/data/config/defaults/splash-config.json +16 -0
  246. atlas_chat-0.1.0.dist-info/METADATA +236 -0
  247. atlas_chat-0.1.0.dist-info/RECORD +250 -0
  248. atlas_chat-0.1.0.dist-info/WHEEL +5 -0
  249. atlas_chat-0.1.0.dist-info/entry_points.txt +4 -0
  250. atlas_chat-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,172 @@
1
+ """
2
+ MCP Server with API Key Authentication Demo
3
+
4
+ This server validates X-API-Key header on all tool calls using middleware.
5
+ Demonstrates per-user API key authentication for Atlas UI.
6
+
7
+ Run with: python main.py
8
+ Or configure in mcp.json with auth_type: "api_key"
9
+ """
10
+
11
+ from contextvars import ContextVar
12
+
13
+ from fastmcp import FastMCP
14
+ from fastmcp.exceptions import ToolError
15
+ from fastmcp.server.dependencies import get_http_headers
16
+ from fastmcp.server.middleware import Middleware, MiddlewareContext
17
+
18
+ # Context variable to store current user during request
19
+ current_user_var: ContextVar[dict | None] = ContextVar("current_user", default=None)
20
+
21
+ # API keys mapped to user info
22
+ # In production, this would be a database lookup
23
+ API_KEY_USERS = {
24
+ "test123": {
25
+ "email": "test@example.com",
26
+ "name": "Test User",
27
+ "role": "developer",
28
+ },
29
+ "admin123": {
30
+ "email": "admin@example.com",
31
+ "name": "Admin User",
32
+ "role": "admin",
33
+ },
34
+ "demo-api-key-12345": {
35
+ "email": "demo@example.com",
36
+ "name": "Demo User",
37
+ "role": "viewer",
38
+ },
39
+ }
40
+
41
+
42
+ def get_current_user() -> dict | None:
43
+ """Get the current authenticated user from context."""
44
+ return current_user_var.get()
45
+
46
+
47
+ class ApiKeyAuthMiddleware(Middleware):
48
+ """Middleware that validates API key and sets current user context."""
49
+
50
+ def __init__(self, api_key_users: dict[str, dict], header_name: str = "x-api-key"):
51
+ self.api_key_users = api_key_users
52
+ self.header_name = header_name.lower()
53
+
54
+ async def on_call_tool(self, context: MiddlewareContext, call_next):
55
+ """Validate API key and set user context before each tool call."""
56
+ # Get tool name from context.message
57
+ tool_name = context.message.name
58
+ print(f"[AUTH] Checking API key for tool '{tool_name}'...", flush=True)
59
+ headers = get_http_headers() or {}
60
+ api_key = headers.get(self.header_name)
61
+
62
+ if not api_key:
63
+ print(f"[AUTH FAILED] Tool '{tool_name}': Missing {self.header_name} header", flush=True)
64
+ raise ToolError(
65
+ f"Authentication required: Missing {self.header_name} header. "
66
+ "Please provide your API key in Atlas UI settings."
67
+ )
68
+
69
+ user = self.api_key_users.get(api_key)
70
+ if not user:
71
+ # Don't log the API key value, even partially
72
+ print(f"[AUTH FAILED] Tool '{tool_name}': Invalid API key", flush=True)
73
+ raise ToolError(
74
+ "Authentication failed: Invalid API key. "
75
+ "Please check your API key and try again."
76
+ )
77
+
78
+ # Log successful authentication - only role, not sensitive user details
79
+ print(f"[AUTH OK] Tool '{tool_name}': Authenticated user with role: {user['role']}", flush=True)
80
+
81
+ # Set current user in context for tools to access
82
+ token = current_user_var.set(user)
83
+ try:
84
+ return await call_next(context)
85
+ finally:
86
+ current_user_var.reset(token)
87
+
88
+
89
+ mcp = FastMCP("ApiKeyDemoServer")
90
+
91
+ # Add authentication middleware
92
+ mcp.add_middleware(ApiKeyAuthMiddleware(api_key_users=API_KEY_USERS))
93
+
94
+
95
+ @mcp.tool
96
+ def echo(message: str) -> str:
97
+ """Echo back a message. Requires valid API key."""
98
+ user = get_current_user()
99
+ return f"Echo from {user['name']}: {message}"
100
+
101
+
102
+ @mcp.tool
103
+ def add_numbers(a: int, b: int) -> int:
104
+ """Add two numbers together. Requires valid API key."""
105
+ return a + b
106
+
107
+
108
+ @mcp.tool
109
+ def whoami() -> dict:
110
+ """Show who you are based on your API key.
111
+
112
+ Returns the user information associated with your API key.
113
+ """
114
+ user = get_current_user()
115
+ return {
116
+ "authenticated": True,
117
+ "user": user,
118
+ "message": f"Hello, {user['name']}! You are authenticated as {user['role']}.",
119
+ }
120
+
121
+
122
+ @mcp.tool
123
+ def get_user_data() -> dict:
124
+ """Get sample user data. Requires valid API key.
125
+
126
+ This demonstrates that the API key was validated successfully
127
+ and shows personalized data for the authenticated user.
128
+ """
129
+ user = get_current_user()
130
+ return {
131
+ "server_name": "ApiKeyDemoServer",
132
+ "authenticated": True,
133
+ "user": user,
134
+ "message": f"Welcome back, {user['name']}!",
135
+ "sample_data": {
136
+ "items": ["item1", "item2", "item3"],
137
+ "count": 3,
138
+ },
139
+ }
140
+
141
+
142
+ @mcp.tool
143
+ def list_valid_keys() -> dict:
144
+ """List the valid demo API keys (for testing purposes only).
145
+
146
+ In a real application, this tool would NOT exist.
147
+ It's here just to help users test the demo.
148
+ """
149
+ return {
150
+ "note": "These are demo keys for testing. In production, keys would be user-specific.",
151
+ "valid_keys": {key: user["email"] for key, user in API_KEY_USERS.items()},
152
+ }
153
+
154
+
155
+ if __name__ == "__main__":
156
+ import sys
157
+
158
+ # Default port
159
+ port = 8006
160
+
161
+ # Allow port override via command line
162
+ if len(sys.argv) > 1:
163
+ try:
164
+ port = int(sys.argv[1])
165
+ except ValueError:
166
+ print(f"Invalid port: {sys.argv[1]}, using default {port}")
167
+
168
+ print(f"Starting API Key Demo MCP server on http://localhost:{port}/mcp")
169
+ print(f"Valid API key count: {len(API_KEY_USERS)} (use list_valid_keys tool to see them)")
170
+ print("\nTo test, set your API key in Atlas UI or use the FastMCP client.")
171
+
172
+ mcp.run(transport="streamable-http", host="0.0.0.0", port=port)
@@ -0,0 +1,56 @@
1
+ #!/bin/bash
2
+ # Run the API Key Demo MCP Server
3
+ #
4
+ # Usage:
5
+ # ./run.sh - Start on default port 8006
6
+ # ./run.sh 9000 - Start on custom port 9000
7
+
8
+ set -e
9
+
10
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+ PROJECT_ROOT="$(dirname "$(dirname "$(dirname "$SCRIPT_DIR")")")"
12
+ PYTHON="$PROJECT_ROOT/.venv/bin/python"
13
+
14
+ # Verify python exists
15
+ if [ ! -f "$PYTHON" ]; then
16
+ echo "Error: Python not found at $PYTHON"
17
+ echo "Run from project root: uv venv && uv pip install -r requirements.txt"
18
+ exit 1
19
+ fi
20
+
21
+ cd "$SCRIPT_DIR"
22
+
23
+ PORT="${1:-8006}"
24
+
25
+ echo "========================================"
26
+ echo "API Key Demo MCP Server"
27
+ echo "========================================"
28
+ echo ""
29
+ echo "Add this to your config/overrides/mcp.json:"
30
+ echo ""
31
+ cat << EOF
32
+ {
33
+ "api_key_demo": {
34
+ "url": "http://127.0.0.1:${PORT}/mcp",
35
+ "auth_type": "api_key",
36
+ "auth_header": "X-API-Key",
37
+ "auth_prompt": "Enter your API key for the demo server",
38
+ "groups": ["users"],
39
+ "description": "Demo server with API key authentication",
40
+ "short_description": "API Key Auth Demo",
41
+ "compliance_level": "Public"
42
+ }
43
+ }
44
+ EOF
45
+ echo ""
46
+ echo "Valid test API keys:"
47
+ echo " - test123 (developer)"
48
+ echo " - admin123 (admin)"
49
+ echo " - demo-api-key-12345 (viewer)"
50
+ echo ""
51
+ echo "========================================"
52
+ echo "Starting server on port $PORT..."
53
+ echo "========================================"
54
+ echo ""
55
+
56
+ "$PYTHON" main.py "$PORT"
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ CSV/XLSX Analyzer MCP Server using FastMCP.
4
+ Detects numerical columns, generates basic statistical plots, and returns as Base64.
5
+ """
6
+
7
+ import base64
8
+ import io
9
+ from typing import Annotated, Any, Dict
10
+
11
+ import matplotlib.pyplot as plt
12
+ import pandas as pd
13
+ from fastmcp import FastMCP
14
+
15
+ # Initialize the MCP server
16
+ mcp = FastMCP("CSV_XLSX_Analyzer")
17
+
18
+ @mcp.tool
19
+ def analyze_spreadsheet(
20
+ instructions: Annotated[str, "Instructions for the tool, not used in this implementation"],
21
+ filename: Annotated[str, "The name of the file (.csv or .xlsx)"],
22
+ file_data_base64: Annotated[str, "LLM agent can leave blank. Do NOT fill. Framework will fill this."] = ""
23
+ ) -> Dict[str, Any]:
24
+ """
25
+ Perform comprehensive spreadsheet analysis with automatic data visualization for CSV and Excel files.
26
+
27
+ This intelligent data analysis tool provides instant insights into spreadsheet data:
28
+
29
+ **File Format Support:**
30
+ - CSV files (.csv) with various delimiters and encodings
31
+ - Excel files (.xlsx) including multiple sheets and complex formatting
32
+ - Automatic format detection and appropriate parsing
33
+ - Robust handling of different data structures and layouts
34
+
35
+ **Data Analysis Capabilities:**
36
+ - Automatic numerical column detection and classification
37
+ - Statistical distribution analysis for all numeric data
38
+ - Data quality assessment and completeness evaluation
39
+ - Column type identification and validation
40
+
41
+ **Visualization Features:**
42
+ - Auto-generated histograms for all numerical columns
43
+ - Multi-panel plots showing data distribution patterns
44
+ - Professional formatting with grid layout optimization
45
+ - High-resolution PNG output suitable for reports and presentations
46
+
47
+ **Data Insights Provided:**
48
+ - Column count and data type summary
49
+ - Numerical data distribution patterns
50
+ - Data range and statistical characteristics
51
+ - Missing value identification
52
+ - Outlier detection through visual inspection
53
+
54
+ **Smart Processing:**
55
+ - Handles large datasets efficiently
56
+ - Automatic plot layout optimization based on column count
57
+ - Error handling for corrupted or invalid data
58
+ - Graceful degradation for edge cases
59
+
60
+ **Use Cases:**
61
+ - Initial data exploration and profiling
62
+ - Data quality assessment before analysis
63
+ - Quick statistical overview for stakeholder presentations
64
+ - Dataset validation and structure verification
65
+ - Automated reporting and data documentation
66
+
67
+ **Examples:**
68
+ - Sales data → Revenue and quantity distribution histograms
69
+ - Survey responses → Response pattern and demographic distributions
70
+ - Financial records → Transaction amount and balance distributions
71
+ - Scientific measurements → Variable distribution and range analysis
72
+
73
+ Args:
74
+ instructions: Analysis instructions or requirements (currently not used in processing)
75
+ filename: Name of the spreadsheet file (.csv or .xlsx extensions required)
76
+ file_data_base64: Base64-encoded file content (automatically provided by framework)
77
+
78
+ Returns:
79
+ Dictionary containing:
80
+ - results: Analysis summary with column and data information
81
+ - artifacts: High-quality histogram visualization as downloadable PNG
82
+ - display: Optimized viewer configuration for data visualization
83
+ - meta_data: Processing statistics and file information
84
+ Or error message if file cannot be processed or contains no numerical data
85
+ """
86
+ try:
87
+ # Validate file extension
88
+ ext = filename.lower().split('.')[-1]
89
+ if ext not in ['csv', 'xlsx']:
90
+ return {"results": {"error": "Invalid file type. Only .csv or .xlsx allowed."}}
91
+
92
+ # Decode file data
93
+ decoded_bytes = base64.b64decode(file_data_base64)
94
+ buffer = io.BytesIO(decoded_bytes)
95
+
96
+ # Load dataframe
97
+ if ext == 'csv':
98
+ df = pd.read_csv(buffer)
99
+ else:
100
+ df = pd.read_excel(buffer)
101
+
102
+ if df.empty:
103
+ return {"results": {"error": "File is empty or has no readable content."}}
104
+
105
+ # Detect numerical columns
106
+ num_cols = df.select_dtypes(include=['number']).columns.tolist()
107
+ if not num_cols:
108
+ return {"results": {"error": "No numerical columns found for plotting."}}
109
+
110
+ # Generate plot
111
+ plt.figure(figsize=(8, 6))
112
+ df[num_cols].hist(bins=20, figsize=(10, 8), grid=False)
113
+ plt.tight_layout()
114
+
115
+ # Save to buffer as PNG
116
+ img_buffer = io.BytesIO()
117
+ plt.savefig(img_buffer, format='png')
118
+ plt.close()
119
+ img_buffer.seek(0)
120
+
121
+ # Encode to Base64
122
+ img_base64 = base64.b64encode(img_buffer.read()).decode('utf-8')
123
+ img_buffer.close()
124
+
125
+ returned_file_names = ["analysis_plot.png"]
126
+ returned_file_contents = [img_base64]
127
+
128
+ return {
129
+ "results": {
130
+ "operation": "spreadsheet_analysis",
131
+ "filename": filename,
132
+ "numerical_columns": num_cols,
133
+ "message": f"Detected numerical columns: {', '.join(num_cols)}. Histogram plot generated."
134
+ },
135
+ "returned_file_names": returned_file_names,
136
+ "returned_file_contents": returned_file_contents
137
+ }
138
+
139
+ except Exception as e:
140
+ # print traceback for debugging
141
+ import traceback
142
+ traceback.print_exc()
143
+ return {"results": {"error": f"Spreadsheet analysis failed: {str(e)}"}}
144
+
145
+ if __name__ == "__main__":
146
+ print("Starting CSV/XLSX Analyzer MCP server...")
147
+ mcp.run()
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Calculator MCP Server using FastMCP
4
+ Provides mathematical operations through MCP protocol.
5
+ """
6
+
7
+ import math
8
+ import time
9
+ from typing import Any, Dict, Union
10
+
11
+ from fastmcp import FastMCP
12
+
13
+ # Initialize the MCP server
14
+ mcp = FastMCP("Calculator")
15
+
16
+
17
+ def to_float(value: Union[str, int, float]) -> float:
18
+ """Convert input to float, handling strings and numbers."""
19
+ try:
20
+ return float(value)
21
+ except (ValueError, TypeError): # pragma: no cover - simple helper
22
+ raise ValueError(f"Cannot convert '{value}' to a number")
23
+
24
+
25
+ def to_int(value: Union[str, int, float]) -> int:
26
+ """Convert input to int, handling strings and numbers."""
27
+ try:
28
+ return int(float(value)) # Convert to float first to handle "5.0" -> 5
29
+ except (ValueError, TypeError): # pragma: no cover - simple helper
30
+ raise ValueError(f"Cannot convert '{value}' to an integer")
31
+
32
+ @mcp.tool
33
+ def evaluate(expression: str) -> Dict[str, Any]:
34
+ """Safely evaluate a wide range of mathematical expressions with comprehensive mathematical functions.
35
+
36
+ This calculator tool provides secure mathematical computation capabilities including:
37
+
38
+ **Basic Operations:**
39
+ - Arithmetic: +, -, *, /, //, %, **
40
+ - Built-in functions: abs(), round(), min(), max(), sum(), pow(), divmod()
41
+
42
+ **Mathematical Constants:**
43
+ - pi, e, tau, inf, nan
44
+
45
+ **Trigonometric Functions:**
46
+ - sin(), cos(), tan(), asin(), acos(), atan(), atan2()
47
+ - degrees(), radians(), hypot()
48
+
49
+ **Hyperbolic Functions:**
50
+ - sinh(), cosh(), tanh(), asinh(), acosh(), atanh()
51
+
52
+ **Exponential & Logarithmic:**
53
+ - exp(), sqrt(), log(), log10(), log2()
54
+
55
+ **Rounding & Numeric Operations:**
56
+ - ceil(), floor(), trunc(), modf(), copysign(), fabs(), fmod()
57
+
58
+ **Combinatorics & Number Theory:**
59
+ - factorial(), comb(), perm(), gcd(), lcm()
60
+
61
+ **Float Validation:**
62
+ - isfinite(), isinf(), isnan()
63
+
64
+ **Security Features:**
65
+ - Expression length limited to 200 characters
66
+ - Only safe mathematical functions are allowed
67
+ - No access to file system, network, or dangerous operations
68
+ - Sandboxed evaluation environment
69
+
70
+ **Examples:**
71
+ - Basic: "2 + 3 * 4" → 14
72
+ - Trigonometry: "sin(pi/2)" → 1.0
73
+ - Logarithms: "log10(100)" → 2.0
74
+ - Combinatorics: "factorial(5)" → 120
75
+ - Complex: "sqrt(pow(3, 2) + pow(4, 2))" → 5.0
76
+
77
+ Args:
78
+ expression: Mathematical expression to evaluate (string, max 200 chars)
79
+
80
+ Returns:
81
+ MCP contract shape with results and timing metadata:
82
+ {
83
+ "results": {"operation": "evaluate", "expression": str, "result": float},
84
+ "meta_data": {"is_error": bool, "elapsed_ms": float, "reason": str}
85
+ }
86
+ """
87
+ start = time.perf_counter()
88
+ expression_str = str(expression)
89
+ meta: Dict[str, Any] = {}
90
+
91
+ # Safety check length
92
+ if len(expression_str) > 200:
93
+ meta.update({"is_error": True, "reason": "too_long"})
94
+ return {
95
+ "results": {"error": "Expression too long", "expression": expression_str},
96
+ "meta_data": _finalize_meta(meta, start)
97
+ }
98
+
99
+ allowed_names = {
100
+ # Built-ins
101
+ "abs": abs, "round": round, "min": min, "max": max, "sum": sum,
102
+ "pow": pow, "divmod": divmod,
103
+ # Constants
104
+ "pi": math.pi, "e": math.e, "tau": math.tau, "inf": math.inf, "nan": math.nan,
105
+ # Trigonometric
106
+ "sin": math.sin, "cos": math.cos, "tan": math.tan,
107
+ "asin": math.asin, "acos": math.acos, "atan": math.atan, "atan2": math.atan2,
108
+ "hypot": math.hypot, "degrees": math.degrees, "radians": math.radians,
109
+ # Hyperbolic
110
+ "sinh": math.sinh, "cosh": math.cosh, "tanh": math.tanh,
111
+ "asinh": math.asinh, "acosh": math.acosh, "atanh": math.atanh,
112
+ # Exponential & logarithmic
113
+ "exp": math.exp, "sqrt": math.sqrt, "log": math.log, "log10": math.log10, "log2": math.log2,
114
+ # Rounding & numeric ops
115
+ "ceil": math.ceil, "floor": math.floor, "trunc": math.trunc, "modf": math.modf,
116
+ "copysign": math.copysign, "fabs": math.fabs, "fmod": math.fmod,
117
+ # Combinatorics & number theory
118
+ "factorial": math.factorial, "comb": math.comb, "perm": math.perm, "gcd": math.gcd, "lcm": math.lcm,
119
+ # Float checks
120
+ "isfinite": math.isfinite, "isinf": math.isinf, "isnan": math.isnan
121
+ }
122
+
123
+ try:
124
+ result = eval(expression_str, {"__builtins__": {}}, allowed_names)
125
+ payload = {
126
+ "operation": "evaluate",
127
+ "expression": expression_str,
128
+ "result": result
129
+ }
130
+ meta.update({"is_error": False})
131
+ return {"results": payload, "meta_data": _finalize_meta(meta, start)}
132
+ except Exception as e: # noqa: BLE001 - broad for safe tool boundary
133
+ meta.update({"is_error": True, "reason": type(e).__name__})
134
+ return {
135
+ "results": {"error": f"Evaluation error: {e}", "expression": expression_str},
136
+ "meta_data": _finalize_meta(meta, start)
137
+ }
138
+
139
+
140
+ def _finalize_meta(meta: Dict[str, Any], start: float) -> Dict[str, Any]:
141
+ """Attach timing info and return meta_data dict."""
142
+ meta = dict(meta) # shallow copy
143
+ meta["elapsed_ms"] = round((time.perf_counter() - start) * 1000, 3)
144
+ return meta
145
+
146
+
147
+
148
+ if __name__ == "__main__":
149
+ mcp.run()
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Execution engine module for code executor.
4
+ Handles safe subprocess execution of Python scripts with resource limits.
5
+ """
6
+
7
+ import json
8
+ import logging
9
+ import subprocess
10
+ import sys
11
+ import traceback
12
+ from pathlib import Path
13
+ from typing import Any, Dict
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ def execute_code_safely(script_path: Path, timeout: int = 30) -> Dict[str, Any]:
19
+ """
20
+ Execute Python script safely with resource limits.
21
+
22
+ Args:
23
+ script_path: Path to the script to execute
24
+ timeout: Maximum execution time in seconds
25
+
26
+ Returns:
27
+ Execution results
28
+ """
29
+ try:
30
+ logger.info(f"Executing script: {script_path} with timeout: {timeout}s")
31
+
32
+ # Execute with subprocess for isolation
33
+ result = subprocess.run(
34
+ [sys.executable, str(script_path)],
35
+ capture_output=True,
36
+ text=True,
37
+ timeout=timeout,
38
+ cwd=script_path.parent
39
+ )
40
+
41
+ logger.info(f"Script execution completed with return code: {result.returncode}")
42
+
43
+ if result.returncode == 0:
44
+ # Parse the JSON output from the script
45
+ try:
46
+ execution_result = json.loads(result.stdout.strip())
47
+ if not execution_result.get("success", True):
48
+ logger.warning(f"Code execution failed: {execution_result.get('stderr', 'Unknown error')}")
49
+ if execution_result.get("error_traceback"):
50
+ logger.error(f"User code traceback:\n{execution_result['error_traceback']}")
51
+ return execution_result
52
+ except json.JSONDecodeError as e:
53
+ error_msg = "Failed to parse execution output"
54
+ logger.error(f"{error_msg}: {str(e)}")
55
+ logger.error(f"Raw stdout: {result.stdout}")
56
+ logger.error(f"Raw stderr: {result.stderr}")
57
+ return {
58
+ "stdout": result.stdout,
59
+ "stderr": result.stderr,
60
+ "success": False,
61
+ "error": error_msg,
62
+ "error_type": "JSONDecodeError"
63
+ }
64
+ else:
65
+ error_msg = f"Script execution failed with return code {result.returncode}"
66
+ logger.error(error_msg)
67
+ logger.error(f"stdout: {result.stdout}")
68
+ logger.error(f"stderr: {result.stderr}")
69
+ return {
70
+ "stdout": result.stdout,
71
+ "stderr": result.stderr,
72
+ "success": False,
73
+ "error": error_msg,
74
+ "error_type": "SubprocessError"
75
+ }
76
+
77
+ except subprocess.TimeoutExpired:
78
+ error_msg = f"Code execution timed out after {timeout} seconds"
79
+ logger.error(error_msg)
80
+ logger.error(f"Traceback: {traceback.format_exc()}")
81
+ return {
82
+ "stdout": "",
83
+ "stderr": "",
84
+ "success": False,
85
+ "error": error_msg,
86
+ "error_type": "TimeoutError"
87
+ }
88
+ except Exception as e:
89
+ error_msg = f"Execution error: {str(e)}"
90
+ logger.error(error_msg)
91
+ logger.error(f"Traceback: {traceback.format_exc()}")
92
+ return {
93
+ "stdout": "",
94
+ "stderr": "",
95
+ "success": False,
96
+ "error": error_msg,
97
+ "error_type": type(e).__name__
98
+ }