agentvee-mcp 0.1.0__tar.gz

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 (30) hide show
  1. agentvee_mcp-0.1.0/.gitignore +4 -0
  2. agentvee_mcp-0.1.0/PKG-INFO +152 -0
  3. agentvee_mcp-0.1.0/README.md +133 -0
  4. agentvee_mcp-0.1.0/agentvee_mcp/__init__.py +3 -0
  5. agentvee_mcp-0.1.0/agentvee_mcp/__main__.py +3 -0
  6. agentvee_mcp-0.1.0/agentvee_mcp/api_client.py +175 -0
  7. agentvee_mcp-0.1.0/agentvee_mcp/auth.py +18 -0
  8. agentvee_mcp-0.1.0/agentvee_mcp/config.py +107 -0
  9. agentvee_mcp-0.1.0/agentvee_mcp/http.py +18 -0
  10. agentvee_mcp-0.1.0/agentvee_mcp/security/__init__.py +0 -0
  11. agentvee_mcp-0.1.0/agentvee_mcp/security/audit.py +25 -0
  12. agentvee_mcp-0.1.0/agentvee_mcp/security/metrics.py +66 -0
  13. agentvee_mcp-0.1.0/agentvee_mcp/security/sessions.py +9 -0
  14. agentvee_mcp-0.1.0/agentvee_mcp/security/throttle.py +56 -0
  15. agentvee_mcp-0.1.0/agentvee_mcp/security/wait_gate.py +37 -0
  16. agentvee_mcp-0.1.0/agentvee_mcp/server.py +21 -0
  17. agentvee_mcp-0.1.0/agentvee_mcp/stdio.py +49 -0
  18. agentvee_mcp-0.1.0/agentvee_mcp/tools/__init__.py +0 -0
  19. agentvee_mcp-0.1.0/agentvee_mcp/tools/download_url.py +67 -0
  20. agentvee_mcp-0.1.0/agentvee_mcp/tools/list_on_marketplace.py +95 -0
  21. agentvee_mcp-0.1.0/agentvee_mcp/tools/upload_and_wait.py +231 -0
  22. agentvee_mcp-0.1.0/agentvee_mcp/tools/upload_file.py +110 -0
  23. agentvee_mcp-0.1.0/agentvee_mcp/tools/upload_status.py +68 -0
  24. agentvee_mcp-0.1.0/agentvee_mcp/tools/upload_url.py +83 -0
  25. agentvee_mcp-0.1.0/agentvee_mcp/validation/__init__.py +0 -0
  26. agentvee_mcp-0.1.0/agentvee_mcp/validation/base64_content.py +52 -0
  27. agentvee_mcp-0.1.0/agentvee_mcp/validation/file_path.py +42 -0
  28. agentvee_mcp-0.1.0/agentvee_mcp/validation/upload_id.py +16 -0
  29. agentvee_mcp-0.1.0/agentvee_mcp/validation/url.py +40 -0
  30. agentvee_mcp-0.1.0/pyproject.toml +32 -0
@@ -0,0 +1,4 @@
1
+ .venv/
2
+ dist/
3
+ *.egg-info/
4
+ __pycache__/
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentvee-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for AgentVee — file uploads, marketplace listings, and paid downloads for AI agents
5
+ Project-URL: Homepage, https://agentvee.io
6
+ Project-URL: Repository, https://github.com/agentvee/agentvee
7
+ License-Expression: MIT
8
+ Keywords: agentvee,ai-agents,file-upload,mcp
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Requires-Python: >=3.11
16
+ Requires-Dist: httpx>=0.27.0
17
+ Requires-Dist: mcp>=1.0.0
18
+ Description-Content-Type: text/markdown
19
+
20
+ # agentvee-mcp
21
+
22
+ MCP server for [AgentVee](https://agentvee.io) — file uploads, marketplace listings, and paid downloads for AI agents.
23
+
24
+ ## Quick Start
25
+
26
+ 1. **Get an API key** at [agentvee.io/dashboard](https://agentvee.io/dashboard)
27
+
28
+ 2. **Add to your MCP config** (e.g. `~/.cursor/mcp.json`):
29
+
30
+ ```json
31
+ {
32
+ "mcpServers": {
33
+ "agentvee": {
34
+ "command": "uvx",
35
+ "args": ["agentvee-mcp"],
36
+ "env": {
37
+ "AGENTVEE_API_KEY": "your-api-key-here"
38
+ }
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ Or with `pip`:
45
+
46
+ ```json
47
+ {
48
+ "mcpServers": {
49
+ "agentvee": {
50
+ "command": "python",
51
+ "args": ["-m", "agentvee_mcp"],
52
+ "env": {
53
+ "AGENTVEE_API_KEY": "your-api-key-here"
54
+ }
55
+ }
56
+ }
57
+ }
58
+ ```
59
+
60
+ 3. **Restart your MCP client** (Cursor, Claude Desktop, etc.)
61
+
62
+ ## Installation
63
+
64
+ ```bash
65
+ pip install agentvee-mcp
66
+ ```
67
+
68
+ Or with uv:
69
+
70
+ ```bash
71
+ uvx agentvee-mcp --help
72
+ ```
73
+
74
+ ## Available Tools
75
+
76
+ | Tool | Description |
77
+ |------|-------------|
78
+ | `upload_and_wait` | Upload a file and wait for a ready download link (recommended) |
79
+ | `upload_file` | Upload a local file or base64 content (returns upload ID for polling) |
80
+ | `upload_from_url` | Upload a file from a public URL |
81
+ | `get_upload_status` | Check the processing status of an upload |
82
+ | `get_download_url` | Get a fresh shareable download URL for a completed upload |
83
+ | `list_on_marketplace` | List an uploaded file on the public AgentVee marketplace |
84
+
85
+ ### Upload Methods
86
+
87
+ Each upload tool supports three mutually exclusive input methods:
88
+
89
+ - **`filePath`** — Local file path (stdio only, most efficient — zero tokens)
90
+ - **`url`** — Public URL for the server to fetch
91
+ - **`content`** — Base64-encoded file content (fallback)
92
+
93
+ ### Pricing & Marketplace
94
+
95
+ Agents can set a price per download on any upload:
96
+
97
+ ```
98
+ pricePerDownload: 0.50 // USD, charged in USDC
99
+ ```
100
+
101
+ After upload, list on the marketplace with `list_on_marketplace`:
102
+
103
+ ```
104
+ uploadId: "abc123"
105
+ title: "Dataset Q1 2026"
106
+ description: "Cleaned sales data"
107
+ category: "datasets" // reports | datasets | code | media | models | prompts | other
108
+ tags: ["sales", "q1"]
109
+ ```
110
+
111
+ ## Environment Variables
112
+
113
+ | Variable | Required | Default | Description |
114
+ |----------|----------|---------|-------------|
115
+ | `AGENTVEE_API_KEY` | Yes | — | Your AgentVee API key |
116
+ | `AGENTVEE_API_BASE_URL` | No | `https://agentvee-api.fly.dev` | API base URL |
117
+
118
+ ## Supported Clients
119
+
120
+ Any MCP client that supports stdio transport:
121
+
122
+ - [Cursor](https://cursor.sh)
123
+ - [Claude Desktop](https://claude.ai/download)
124
+ - [Cline](https://github.com/cline/cline)
125
+ - [Continue](https://continue.dev)
126
+
127
+ ## CLI
128
+
129
+ ```bash
130
+ agentvee-mcp --help
131
+ agentvee-mcp --version
132
+ ```
133
+
134
+ ## HTTP Transport
135
+
136
+ ```bash
137
+ agentvee-mcp-http
138
+ ```
139
+
140
+ Or:
141
+
142
+ ```bash
143
+ python -m agentvee_mcp.http
144
+ ```
145
+
146
+ ## Also Available
147
+
148
+ - **Node.js:** `npx -y @agentvee/mcp` ([npm](https://www.npmjs.com/package/@agentvee/mcp))
149
+
150
+ ## License
151
+
152
+ MIT
@@ -0,0 +1,133 @@
1
+ # agentvee-mcp
2
+
3
+ MCP server for [AgentVee](https://agentvee.io) — file uploads, marketplace listings, and paid downloads for AI agents.
4
+
5
+ ## Quick Start
6
+
7
+ 1. **Get an API key** at [agentvee.io/dashboard](https://agentvee.io/dashboard)
8
+
9
+ 2. **Add to your MCP config** (e.g. `~/.cursor/mcp.json`):
10
+
11
+ ```json
12
+ {
13
+ "mcpServers": {
14
+ "agentvee": {
15
+ "command": "uvx",
16
+ "args": ["agentvee-mcp"],
17
+ "env": {
18
+ "AGENTVEE_API_KEY": "your-api-key-here"
19
+ }
20
+ }
21
+ }
22
+ }
23
+ ```
24
+
25
+ Or with `pip`:
26
+
27
+ ```json
28
+ {
29
+ "mcpServers": {
30
+ "agentvee": {
31
+ "command": "python",
32
+ "args": ["-m", "agentvee_mcp"],
33
+ "env": {
34
+ "AGENTVEE_API_KEY": "your-api-key-here"
35
+ }
36
+ }
37
+ }
38
+ }
39
+ ```
40
+
41
+ 3. **Restart your MCP client** (Cursor, Claude Desktop, etc.)
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ pip install agentvee-mcp
47
+ ```
48
+
49
+ Or with uv:
50
+
51
+ ```bash
52
+ uvx agentvee-mcp --help
53
+ ```
54
+
55
+ ## Available Tools
56
+
57
+ | Tool | Description |
58
+ |------|-------------|
59
+ | `upload_and_wait` | Upload a file and wait for a ready download link (recommended) |
60
+ | `upload_file` | Upload a local file or base64 content (returns upload ID for polling) |
61
+ | `upload_from_url` | Upload a file from a public URL |
62
+ | `get_upload_status` | Check the processing status of an upload |
63
+ | `get_download_url` | Get a fresh shareable download URL for a completed upload |
64
+ | `list_on_marketplace` | List an uploaded file on the public AgentVee marketplace |
65
+
66
+ ### Upload Methods
67
+
68
+ Each upload tool supports three mutually exclusive input methods:
69
+
70
+ - **`filePath`** — Local file path (stdio only, most efficient — zero tokens)
71
+ - **`url`** — Public URL for the server to fetch
72
+ - **`content`** — Base64-encoded file content (fallback)
73
+
74
+ ### Pricing & Marketplace
75
+
76
+ Agents can set a price per download on any upload:
77
+
78
+ ```
79
+ pricePerDownload: 0.50 // USD, charged in USDC
80
+ ```
81
+
82
+ After upload, list on the marketplace with `list_on_marketplace`:
83
+
84
+ ```
85
+ uploadId: "abc123"
86
+ title: "Dataset Q1 2026"
87
+ description: "Cleaned sales data"
88
+ category: "datasets" // reports | datasets | code | media | models | prompts | other
89
+ tags: ["sales", "q1"]
90
+ ```
91
+
92
+ ## Environment Variables
93
+
94
+ | Variable | Required | Default | Description |
95
+ |----------|----------|---------|-------------|
96
+ | `AGENTVEE_API_KEY` | Yes | — | Your AgentVee API key |
97
+ | `AGENTVEE_API_BASE_URL` | No | `https://agentvee-api.fly.dev` | API base URL |
98
+
99
+ ## Supported Clients
100
+
101
+ Any MCP client that supports stdio transport:
102
+
103
+ - [Cursor](https://cursor.sh)
104
+ - [Claude Desktop](https://claude.ai/download)
105
+ - [Cline](https://github.com/cline/cline)
106
+ - [Continue](https://continue.dev)
107
+
108
+ ## CLI
109
+
110
+ ```bash
111
+ agentvee-mcp --help
112
+ agentvee-mcp --version
113
+ ```
114
+
115
+ ## HTTP Transport
116
+
117
+ ```bash
118
+ agentvee-mcp-http
119
+ ```
120
+
121
+ Or:
122
+
123
+ ```bash
124
+ python -m agentvee_mcp.http
125
+ ```
126
+
127
+ ## Also Available
128
+
129
+ - **Node.js:** `npx -y @agentvee/mcp` ([npm](https://www.npmjs.com/package/@agentvee/mcp))
130
+
131
+ ## License
132
+
133
+ MIT
@@ -0,0 +1,3 @@
1
+ """AgentVee MCP server — file uploads for AI agents."""
2
+
3
+ __version__ = "0.1.2"
@@ -0,0 +1,3 @@
1
+ from agentvee_mcp.stdio import main
2
+
3
+ main()
@@ -0,0 +1,175 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ import httpx
7
+
8
+ from .config import mcp_config
9
+
10
+
11
+ @dataclass
12
+ class ApiOk:
13
+ ok: bool
14
+ status: int
15
+ data: dict[str, Any]
16
+
17
+ def __init__(self, status: int, data: dict[str, Any]):
18
+ self.ok = True
19
+ self.status = status
20
+ self.data = data
21
+
22
+
23
+ @dataclass
24
+ class ApiErr:
25
+ ok: bool
26
+ status: int
27
+ code: str
28
+ message: str
29
+ retry_after_sec: int | None = None
30
+
31
+ def __init__(
32
+ self,
33
+ status: int,
34
+ code: str,
35
+ message: str,
36
+ retry_after_sec: int | None = None,
37
+ ):
38
+ self.ok = False
39
+ self.status = status
40
+ self.code = code
41
+ self.message = message
42
+ self.retry_after_sec = retry_after_sec
43
+
44
+
45
+ ApiResult = ApiOk | ApiErr
46
+
47
+
48
+ def _map_error_message(
49
+ status: int,
50
+ code: str | None,
51
+ server_message: str | None,
52
+ ) -> str:
53
+ mapping: dict[str, str] = {
54
+ "rate_limit_exceeded": server_message or "Rate limit exceeded. Please wait and retry.",
55
+ "unauthorized": "Invalid or missing API key.",
56
+ "forbidden": "API key does not have required permissions.",
57
+ "size_limit_exceeded": f"File exceeds {mcp_config.max_file_size_bytes // (1024 * 1024)} MB limit.",
58
+ "blocked_mime_type": server_message or "File type not allowed.",
59
+ "empty_file": "File cannot be empty.",
60
+ "upload_worker_unavailable": "Upload service temporarily unavailable. Please retry.",
61
+ "storage_degraded": "Storage backend temporarily degraded. Please retry in 30s.",
62
+ "concurrency_limit_reached": "Too many concurrent uploads. Please wait and retry.",
63
+ "idempotency_in_progress": "A request with this idempotency key is already being processed.",
64
+ "invalid_url": server_message or "URL is not allowed.",
65
+ "ssrf_blocked": server_message or "URL is not allowed.",
66
+ "fetch_failed": server_message or "Failed to fetch file from URL.",
67
+ "fetch_timeout": "Timed out fetching file from URL.",
68
+ "upload_not_found": "Upload not found.",
69
+ }
70
+
71
+ if code and code in mapping:
72
+ return mapping[code]
73
+ if status >= 500:
74
+ return "Upload service error. Please retry."
75
+ return server_message or f"Request failed ({status})."
76
+
77
+
78
+ def _parse_error(response: httpx.Response) -> ApiErr:
79
+ try:
80
+ body = response.json()
81
+ except Exception:
82
+ body = None
83
+
84
+ error_obj = (body or {}).get("error", {}) if isinstance(body, dict) else {}
85
+ code = error_obj.get("code") or f"http_{response.status_code}"
86
+ server_message = error_obj.get("message")
87
+
88
+ retry_after_sec = error_obj.get("retryAfterSec")
89
+ if retry_after_sec is None:
90
+ raw_retry = response.headers.get("Retry-After")
91
+ if raw_retry:
92
+ try:
93
+ retry_after_sec = int(raw_retry)
94
+ except ValueError:
95
+ pass
96
+
97
+ return ApiErr(
98
+ status=response.status_code,
99
+ code=code,
100
+ message=_map_error_message(response.status_code, code, server_message),
101
+ retry_after_sec=retry_after_sec,
102
+ )
103
+
104
+
105
+ def _client() -> httpx.AsyncClient:
106
+ return httpx.AsyncClient(
107
+ timeout=httpx.Timeout(mcp_config.api_timeout_ms / 1000),
108
+ )
109
+
110
+
111
+ async def api_get(path: str, agent_key: str) -> ApiResult:
112
+ url = f"{mcp_config.api_base_url}{path}"
113
+ async with _client() as client:
114
+ try:
115
+ resp = await client.get(url, headers={"X-Agent-Key": agent_key})
116
+ except httpx.TimeoutException:
117
+ return ApiErr(0, "timeout", f"Request timed out after {mcp_config.api_timeout_ms}ms")
118
+ except httpx.HTTPError as exc:
119
+ return ApiErr(0, "network_error", f"Upload service error: {exc}")
120
+
121
+ if resp.is_success:
122
+ return ApiOk(resp.status_code, resp.json())
123
+ return _parse_error(resp)
124
+
125
+
126
+ async def api_post_json(
127
+ path: str,
128
+ agent_key: str,
129
+ body: dict[str, Any],
130
+ extra_headers: dict[str, str] | None = None,
131
+ ) -> ApiResult:
132
+ url = f"{mcp_config.api_base_url}{path}"
133
+ headers: dict[str, str] = {"X-Agent-Key": agent_key, "Content-Type": "application/json"}
134
+ if extra_headers:
135
+ headers.update(extra_headers)
136
+
137
+ async with _client() as client:
138
+ try:
139
+ resp = await client.post(url, headers=headers, json=body)
140
+ except httpx.TimeoutException:
141
+ return ApiErr(0, "timeout", f"Request timed out after {mcp_config.api_timeout_ms}ms")
142
+ except httpx.HTTPError as exc:
143
+ return ApiErr(0, "network_error", f"Upload service error: {exc}")
144
+
145
+ if resp.is_success:
146
+ return ApiOk(resp.status_code, resp.json())
147
+ return _parse_error(resp)
148
+
149
+
150
+ async def api_post_multipart(
151
+ path: str,
152
+ agent_key: str,
153
+ file_data: bytes,
154
+ file_name: str,
155
+ mime_type: str = "application/octet-stream",
156
+ extra_headers: dict[str, str] | None = None,
157
+ ) -> ApiResult:
158
+ url = f"{mcp_config.api_base_url}{path}"
159
+ headers: dict[str, str] = {"X-Agent-Key": agent_key}
160
+ if extra_headers:
161
+ headers.update(extra_headers)
162
+
163
+ files = {"file": (file_name, file_data, mime_type)}
164
+
165
+ async with _client() as client:
166
+ try:
167
+ resp = await client.post(url, headers=headers, files=files)
168
+ except httpx.TimeoutException:
169
+ return ApiErr(0, "timeout", f"Request timed out after {mcp_config.api_timeout_ms}ms")
170
+ except httpx.HTTPError as exc:
171
+ return ApiErr(0, "network_error", f"Upload service error: {exc}")
172
+
173
+ if resp.is_success:
174
+ return ApiOk(resp.status_code, resp.json())
175
+ return _parse_error(resp)
@@ -0,0 +1,18 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from dataclasses import dataclass
5
+
6
+
7
+ @dataclass
8
+ class AuthContext:
9
+ agent_key: str
10
+ token_prefix: str
11
+
12
+
13
+ def resolve_auth() -> AuthContext | None:
14
+ """Resolve API key from environment variable (stdio transport)."""
15
+ env_key = (os.environ.get("AGENTVEE_API_KEY") or "").strip()
16
+ if env_key:
17
+ return AuthContext(agent_key=env_key, token_prefix=env_key[:12])
18
+ return None
@@ -0,0 +1,107 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from dataclasses import dataclass, field
5
+ from typing import Literal
6
+
7
+ TransportMode = Literal["http", "stdio"]
8
+
9
+ _transport_mode: TransportMode = "http"
10
+
11
+
12
+ def set_transport_mode(mode: TransportMode) -> None:
13
+ global _transport_mode
14
+ _transport_mode = mode
15
+
16
+
17
+ def get_transport_mode() -> TransportMode:
18
+ return _transport_mode
19
+
20
+
21
+ def _optional_env(name: str, fallback: str) -> str:
22
+ val = (os.environ.get(name) or "").strip()
23
+ return val if val else fallback
24
+
25
+
26
+ def _number_env(name: str, fallback: int) -> int:
27
+ raw = os.environ.get(name)
28
+ if not raw:
29
+ return fallback
30
+ try:
31
+ parsed = int(raw)
32
+ return parsed if parsed > 0 else fallback
33
+ except ValueError:
34
+ return fallback
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class ThrottleConfig:
39
+ upload_window_ms: int = 15 * 60 * 1000
40
+ upload_max_requests: int = 30
41
+ status_window_ms: int = 15 * 60 * 1000
42
+ status_max_requests: int = 120
43
+ download_url_window_ms: int = 15 * 60 * 1000
44
+ download_url_max_requests: int = 60
45
+
46
+
47
+ @dataclass(frozen=True)
48
+ class WaitConfig:
49
+ max_concurrent: int = 50
50
+ max_duration_ms: int = 120_000
51
+ poll_initial_ms: int = 2_000
52
+ poll_max_ms: int = 8_000
53
+ poll_backoff_factor: float = 1.5
54
+
55
+
56
+ @dataclass(frozen=True)
57
+ class SessionConfig:
58
+ max_total: int = 1000
59
+ max_per_token: int = 50
60
+ idle_timeout_ms: int = 5 * 60 * 1000
61
+
62
+
63
+ @dataclass(frozen=True)
64
+ class McpConfig:
65
+ api_base_url: str = ""
66
+ port: int = 8082
67
+ allowed_origins: list[str] = field(default_factory=list)
68
+ log_level: str = "info"
69
+ max_file_size_bytes: int = 5 * 1024 * 1024
70
+ api_timeout_ms: int = 60_000
71
+ metrics_token: str = ""
72
+ throttle: ThrottleConfig = field(default_factory=ThrottleConfig)
73
+ wait: WaitConfig = field(default_factory=WaitConfig)
74
+ session: SessionConfig = field(default_factory=SessionConfig)
75
+
76
+
77
+ def _build_config() -> McpConfig:
78
+ origins_raw = _optional_env("MCP_ALLOWED_ORIGINS", "")
79
+ origins = [o.strip() for o in origins_raw.split(",") if o.strip()]
80
+
81
+ return McpConfig(
82
+ api_base_url=_optional_env(
83
+ "AGENTVEE_API_BASE_URL",
84
+ "https://agentvee-api-develop.fly.dev",
85
+ ).rstrip("/"),
86
+ port=_number_env("MCP_PORT", 8082),
87
+ allowed_origins=origins,
88
+ log_level=_optional_env("MCP_LOG_LEVEL", "info"),
89
+ max_file_size_bytes=5 * 1024 * 1024,
90
+ api_timeout_ms=_number_env("MCP_API_TIMEOUT_MS", 60_000),
91
+ metrics_token=_optional_env("MCP_METRICS_TOKEN", ""),
92
+ throttle=ThrottleConfig(),
93
+ wait=WaitConfig(
94
+ max_concurrent=_number_env("MCP_WAIT_MAX_CONCURRENT", 50),
95
+ max_duration_ms=_number_env("MCP_WAIT_MAX_DURATION_MS", 120_000),
96
+ poll_initial_ms=_number_env("MCP_WAIT_POLL_INITIAL_MS", 2_000),
97
+ poll_max_ms=_number_env("MCP_WAIT_POLL_MAX_MS", 8_000),
98
+ ),
99
+ session=SessionConfig(
100
+ max_total=_number_env("MCP_SESSION_MAX_TOTAL", 1000),
101
+ max_per_token=_number_env("MCP_SESSION_MAX_PER_TOKEN", 50),
102
+ idle_timeout_ms=_number_env("MCP_SESSION_IDLE_TIMEOUT_MS", 5 * 60 * 1000),
103
+ ),
104
+ )
105
+
106
+
107
+ mcp_config = _build_config()
@@ -0,0 +1,18 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+
5
+ from .config import mcp_config, set_transport_mode
6
+ from .security.audit import audit_log
7
+ from .server import create_mcp_server
8
+
9
+
10
+ def main() -> None:
11
+ set_transport_mode("http")
12
+ server = create_mcp_server()
13
+ audit_log({"event": "startup", "transport": "streamable-http", "port": mcp_config.port})
14
+ server.run(transport="streamable-http", host="0.0.0.0", port=mcp_config.port)
15
+
16
+
17
+ if __name__ == "__main__":
18
+ main()
File without changes
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import sys
5
+ from datetime import datetime, timezone
6
+ from typing import Any
7
+
8
+
9
+ def audit_log(entry: dict[str, Any]) -> None:
10
+ record = {"ts": datetime.now(timezone.utc).isoformat(), **entry}
11
+ print(json.dumps(record, default=str), file=sys.stderr, flush=True)
12
+
13
+
14
+ def sanitize_args(tool_name: str, args: dict[str, Any]) -> dict[str, Any]:
15
+ """Sanitize tool arguments for logging — never log tokens or large payloads."""
16
+ safe: dict[str, Any] = {}
17
+ for key, value in args.items():
18
+ if key == "content" and isinstance(value, str):
19
+ safe["contentSizeBytes"] = len(value) * 3 // 4
20
+ continue
21
+ if isinstance(value, str) and len(value) > 200:
22
+ safe[key] = f"{value[:50]}...[{len(value)} chars]"
23
+ continue
24
+ safe[key] = value
25
+ return safe