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,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
|
+
}
|