esp-workspace-mcp 0.5.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 (39) hide show
  1. esp_workspace_mcp-0.5.0/.env.example +35 -0
  2. esp_workspace_mcp-0.5.0/MANIFEST.in +5 -0
  3. esp_workspace_mcp-0.5.0/PKG-INFO +190 -0
  4. esp_workspace_mcp-0.5.0/README.md +159 -0
  5. esp_workspace_mcp-0.5.0/esp_workspace_mcp/__init__.py +2 -0
  6. esp_workspace_mcp-0.5.0/esp_workspace_mcp/__main__.py +3 -0
  7. esp_workspace_mcp-0.5.0/esp_workspace_mcp/auth.py +74 -0
  8. esp_workspace_mcp-0.5.0/esp_workspace_mcp/cli.py +237 -0
  9. esp_workspace_mcp-0.5.0/esp_workspace_mcp/config.py +57 -0
  10. esp_workspace_mcp-0.5.0/esp_workspace_mcp/resources/__init__.py +1 -0
  11. esp_workspace_mcp-0.5.0/esp_workspace_mcp/resources/build.py +1 -0
  12. esp_workspace_mcp-0.5.0/esp_workspace_mcp/resources/git_status.py +1 -0
  13. esp_workspace_mcp-0.5.0/esp_workspace_mcp/resources/project.py +1 -0
  14. esp_workspace_mcp-0.5.0/esp_workspace_mcp/server.py +753 -0
  15. esp_workspace_mcp-0.5.0/esp_workspace_mcp/tools/__init__.py +30 -0
  16. esp_workspace_mcp-0.5.0/esp_workspace_mcp/tools/diagnostics.py +244 -0
  17. esp_workspace_mcp-0.5.0/esp_workspace_mcp/tools/esp_idf.py +530 -0
  18. esp_workspace_mcp-0.5.0/esp_workspace_mcp/tools/filesystem.py +212 -0
  19. esp_workspace_mcp-0.5.0/esp_workspace_mcp/tools/git_tools.py +366 -0
  20. esp_workspace_mcp-0.5.0/esp_workspace_mcp/tools/phase4_debug.py +163 -0
  21. esp_workspace_mcp-0.5.0/esp_workspace_mcp/tools/phase4_symbols.py +266 -0
  22. esp_workspace_mcp-0.5.0/esp_workspace_mcp/tools/phase4_tools.py +150 -0
  23. esp_workspace_mcp-0.5.0/esp_workspace_mcp/tools/phase4_uart.py +228 -0
  24. esp_workspace_mcp-0.5.0/esp_workspace_mcp/tools/search.py +216 -0
  25. esp_workspace_mcp-0.5.0/esp_workspace_mcp/tools/serial_tools.py +309 -0
  26. esp_workspace_mcp-0.5.0/esp_workspace_mcp/tools/session_tools.py +108 -0
  27. esp_workspace_mcp-0.5.0/esp_workspace_mcp/tools/shell.py +120 -0
  28. esp_workspace_mcp-0.5.0/esp_workspace_mcp/utils/__init__.py +1 -0
  29. esp_workspace_mcp-0.5.0/esp_workspace_mcp/utils/process.py +232 -0
  30. esp_workspace_mcp-0.5.0/esp_workspace_mcp/utils/security.py +101 -0
  31. esp_workspace_mcp-0.5.0/esp_workspace_mcp.egg-info/PKG-INFO +190 -0
  32. esp_workspace_mcp-0.5.0/esp_workspace_mcp.egg-info/SOURCES.txt +37 -0
  33. esp_workspace_mcp-0.5.0/esp_workspace_mcp.egg-info/dependency_links.txt +1 -0
  34. esp_workspace_mcp-0.5.0/esp_workspace_mcp.egg-info/entry_points.txt +4 -0
  35. esp_workspace_mcp-0.5.0/esp_workspace_mcp.egg-info/requires.txt +12 -0
  36. esp_workspace_mcp-0.5.0/esp_workspace_mcp.egg-info/top_level.txt +1 -0
  37. esp_workspace_mcp-0.5.0/pyproject.toml +53 -0
  38. esp_workspace_mcp-0.5.0/requirements.txt +7 -0
  39. esp_workspace_mcp-0.5.0/setup.cfg +4 -0
@@ -0,0 +1,35 @@
1
+ # ESP-Workspace MCP Server Configuration
2
+ # Copy to .env and fill in your values
3
+
4
+ # Authentication (required)
5
+ # Generate a random token: python3 -c "import secrets; print(secrets.token_urlsafe(32))"
6
+ MCP_API_TOKEN=
7
+
8
+ # Network
9
+ MCP_HOST=0.0.0.0
10
+ MCP_PORT=8765
11
+
12
+ # Logging
13
+ MCP_LOG_LEVEL=INFO
14
+
15
+ # Filesystem sandbox — comma-separated allowed roots
16
+ MCP_ALLOWED_ROOTS=/home/juergen/AIRcableLLC
17
+
18
+ # ESP-IDF / EIM configuration
19
+ # Default WISH_PRODUCT value (e.g. TargetS3, TargetC6)
20
+ # Can be overridden per tool call
21
+ MCP_WISH_PRODUCT=TargetS3
22
+
23
+ # Path to ESP-IDF installation (optional, eim handles env)
24
+ MCP_IDF_PATH=
25
+
26
+ # Path to eim executable
27
+ MCP_EIM_PATH=eim
28
+
29
+ # Timeouts and limits
30
+ MCP_DEFAULT_TIMEOUT=30
31
+ MCP_MAX_TIMEOUT=300
32
+ MCP_OUTPUT_LIMIT=51200
33
+
34
+ # Job TTL in seconds
35
+ MCP_JOB_TTL_SECONDS=3600
@@ -0,0 +1,5 @@
1
+ include README.md
2
+ include LICENSE
3
+ include requirements.txt
4
+ include .env.example
5
+ recursive-include esp_workspace_mcp/resources *
@@ -0,0 +1,190 @@
1
+ Metadata-Version: 2.4
2
+ Name: esp-workspace-mcp
3
+ Version: 0.5.0
4
+ Summary: Full autonomous embedded firmware workspace MCP server for ESP-IDF
5
+ Author: AIRcable LLC
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/aircable/ESP-Workspace-MCP-Server-for-Hermes
8
+ Project-URL: Repository, https://github.com/aircable/ESP-Workspace-MCP-Server-for-Hermes
9
+ Project-URL: Issues, https://github.com/aircable/ESP-Workspace-MCP-Server-for-Hermes/issues
10
+ Keywords: mcp,esp-idf,esp32,firmware,embedded,ai-agent,llm
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Embedded Systems
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: mcp>=1.0
21
+ Requires-Dist: fastapi>=0.115
22
+ Requires-Dist: uvicorn>=0.34
23
+ Requires-Dist: pydantic>=2.0
24
+ Requires-Dist: pydantic-settings>=2.0
25
+ Requires-Dist: gitpython>=3.1
26
+ Requires-Dist: pyserial>=3.5
27
+ Requires-Dist: python-dotenv>=1.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=8.0; extra == "dev"
30
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
31
+
32
+ # ESP-Workspace MCP
33
+
34
+ A full autonomous embedded firmware workspace server — the MCP (Model Context Protocol) infrastructure layer for AI agents working with ESP-IDF projects.
35
+
36
+ ## What it does
37
+
38
+ ESP-Workspace MCP turns an AI coding assistant into an autonomous firmware engineer. It provides tools for:
39
+
40
+ - **Filesystem operations** — read, write, list, glob with path sandboxing
41
+ - **Shell execution** — run commands with timeout, background jobs, output capture
42
+ - **ESP-IDF build system** — build, flash, clean, reconfigure via `eim run` with `WISH_PRODUCT` support
43
+ - **Security** — Bearer token auth, path traversal prevention, configurable filesystem roots
44
+
45
+ ## Quick Start
46
+
47
+ ### Prerequisites
48
+
49
+ - Python 3.10+
50
+ - [Espressif Install Manager (`eim`](https://github.com/espressif/esp-idf-installer) — handles ESP-IDF installation and environment setup
51
+
52
+ ### Installation
53
+
54
+ ```bash
55
+ pip install esp-workspace-mcp
56
+ ```
57
+
58
+ Or from source:
59
+
60
+ ```bash
61
+ git clone https://github.com/AIRcableLLC/esp-workspace-mcp.git
62
+ cd esp-workspace-mcp
63
+ python -m venv .venv
64
+ source .venv/bin/activate
65
+ pip install -e ".[dev]"
66
+ ```
67
+
68
+ ### Configuration
69
+
70
+ Create a `.env` file:
71
+
72
+ ```env
73
+ MCP_API_TOKEN=your-secret-token-here
74
+ MCP_HOST=0.0.0.0
75
+ MCP_PORT=8765
76
+ MCP_ALLOWED_ROOTS=/home/user/projects
77
+ MCP_WISH_PRODUCT=TargetS3
78
+ MCP_EIM_PATH=eim
79
+ ```
80
+
81
+ Or set environment variables directly.
82
+
83
+ ### Run the server
84
+
85
+ ```bash
86
+ # SSE mode (default, for network access)
87
+ python run_server.py
88
+
89
+ # Stdio mode (for local MCP clients)
90
+ python run_server.py --stdio
91
+ ```
92
+
93
+ The server exposes:
94
+ - `GET /sse` — SSE connection endpoint (requires Bearer token)
95
+ - `POST /messages` — MCP message endpoint
96
+ - `GET /health` — health check (no auth required)
97
+
98
+ ## MCP Tools
99
+
100
+ ### Filesystem Tools
101
+
102
+ | Tool | Description |
103
+ |---|---|
104
+ | `read_file(path, offset, limit)` | Read text file with pagination |
105
+ | `write_file(path, content)` | Write content to a file |
106
+ | `append_file(path, content)` | Append to an existing file |
107
+ | `list_dir(path)` | List directory entries |
108
+ | `create_dir(path)` | Create directory recursively |
109
+ | `delete_path(path)` | Delete file or empty directory |
110
+ | `file_stat(path)` | Get file/directory metadata |
111
+ | `glob_search(pattern, path)` | Find files by glob pattern |
112
+
113
+ ### Shell Tools
114
+
115
+ | Tool | Description |
116
+ |---|---|
117
+ | `run_command(cmd, cwd, timeout)` | Execute command and wait |
118
+ | `start_process(cmd, cwd)` | Start background job |
119
+ | `get_job_output(job_id, offset)` | Read job output |
120
+ | `kill_job(job_id)` | Terminate background job |
121
+ | `list_jobs()` | List all jobs |
122
+
123
+ ### ESP-IDF Tools
124
+
125
+ All ESP-IDF operations are invoked through `eim run "WISH_PRODUCT=<product> idf.py ..."` which handles environment setup automatically.
126
+
127
+ | Tool | Description |
128
+ |---|---|
129
+ | `eim_run(commands, project_dir, wish_product)` | Run any `idf.py` command via `eim` |
130
+ | `build_project(project_dir, wish_product, reconfigure)` | Build an ESP-IDF project |
131
+ | `set_target(project_dir, target, wish_product)` | Set target chip (esp32, esp32s3, etc.) |
132
+ | `flash_project(project_dir, port, wish_product)` | Flash firmware to device |
133
+ | `clean_project(project_dir)` | Clean build artifacts |
134
+ | `fullclean_project(project_dir)` | Remove entire build directory |
135
+ | `reconfigure_project(project_dir, wish_product)` | Regenerate sdkconfig + CMake cache |
136
+
137
+ ### Build Step Decision Tree
138
+
139
+ ```
140
+ Is sdkconfig missing or have defaults changed?
141
+ ├── YES → eim_run("reconfigure build", project_dir, wish_product)
142
+ └── NO → eim_run("build", project_dir, wish_product)
143
+ ```
144
+
145
+ ## Security
146
+
147
+ | Control | Implementation |
148
+ |---|---|
149
+ | Filesystem sandbox | All paths validated against `MCP_ALLOWED_ROOTS` |
150
+ | Path traversal prevention | Symlink-resolved, `..` escapes rejected |
151
+ | Shell timeout | Configurable, default 30s, max 300s |
152
+ | Output truncation | 50 KB max per response |
153
+ | Authentication | Bearer token on every request |
154
+ | Credential safety | Tokens in env vars only, never logged |
155
+
156
+ ## Integration with AI Agents
157
+
158
+ ### Hermes
159
+
160
+ ```bash
161
+ hermes mcp add esp-workspace \
162
+ --url http://host:port/sse \
163
+ --header "Authorization: Bearer your-token"
164
+ ```
165
+
166
+ ### Generic MCP Client
167
+
168
+ Any MCP-compatible client can connect to the SSE endpoint. The server implements the standard MCP protocol over SSE/HTTP transport.
169
+
170
+ ## Architecture
171
+
172
+ ```
173
+ esp_workspace_mcp/
174
+ ├── config.py # Settings from env, fail-fast validation
175
+ ├── server.py # FastMCP server, tool registration
176
+ ├── auth.py # Bearer token middleware
177
+ ├── tools/
178
+ │ ├── filesystem.py # 8 path-validated file operations
179
+ │ ├── shell.py # Command execution + background jobs
180
+ │ └── esp_idf.py # eim-run wrapper, build/flash/clean
181
+ ├── utils/
182
+ │ ├── security.py # Path sandboxing, symlink resolution
183
+ │ └── process.py # Subprocess management, JobManager
184
+ └── resources/
185
+ └── project.py # MCP resources (project status, etc.)
186
+ ```
187
+
188
+ ## License
189
+
190
+ Apache-2.0
@@ -0,0 +1,159 @@
1
+ # ESP-Workspace MCP
2
+
3
+ A full autonomous embedded firmware workspace server — the MCP (Model Context Protocol) infrastructure layer for AI agents working with ESP-IDF projects.
4
+
5
+ ## What it does
6
+
7
+ ESP-Workspace MCP turns an AI coding assistant into an autonomous firmware engineer. It provides tools for:
8
+
9
+ - **Filesystem operations** — read, write, list, glob with path sandboxing
10
+ - **Shell execution** — run commands with timeout, background jobs, output capture
11
+ - **ESP-IDF build system** — build, flash, clean, reconfigure via `eim run` with `WISH_PRODUCT` support
12
+ - **Security** — Bearer token auth, path traversal prevention, configurable filesystem roots
13
+
14
+ ## Quick Start
15
+
16
+ ### Prerequisites
17
+
18
+ - Python 3.10+
19
+ - [Espressif Install Manager (`eim`](https://github.com/espressif/esp-idf-installer) — handles ESP-IDF installation and environment setup
20
+
21
+ ### Installation
22
+
23
+ ```bash
24
+ pip install esp-workspace-mcp
25
+ ```
26
+
27
+ Or from source:
28
+
29
+ ```bash
30
+ git clone https://github.com/AIRcableLLC/esp-workspace-mcp.git
31
+ cd esp-workspace-mcp
32
+ python -m venv .venv
33
+ source .venv/bin/activate
34
+ pip install -e ".[dev]"
35
+ ```
36
+
37
+ ### Configuration
38
+
39
+ Create a `.env` file:
40
+
41
+ ```env
42
+ MCP_API_TOKEN=your-secret-token-here
43
+ MCP_HOST=0.0.0.0
44
+ MCP_PORT=8765
45
+ MCP_ALLOWED_ROOTS=/home/user/projects
46
+ MCP_WISH_PRODUCT=TargetS3
47
+ MCP_EIM_PATH=eim
48
+ ```
49
+
50
+ Or set environment variables directly.
51
+
52
+ ### Run the server
53
+
54
+ ```bash
55
+ # SSE mode (default, for network access)
56
+ python run_server.py
57
+
58
+ # Stdio mode (for local MCP clients)
59
+ python run_server.py --stdio
60
+ ```
61
+
62
+ The server exposes:
63
+ - `GET /sse` — SSE connection endpoint (requires Bearer token)
64
+ - `POST /messages` — MCP message endpoint
65
+ - `GET /health` — health check (no auth required)
66
+
67
+ ## MCP Tools
68
+
69
+ ### Filesystem Tools
70
+
71
+ | Tool | Description |
72
+ |---|---|
73
+ | `read_file(path, offset, limit)` | Read text file with pagination |
74
+ | `write_file(path, content)` | Write content to a file |
75
+ | `append_file(path, content)` | Append to an existing file |
76
+ | `list_dir(path)` | List directory entries |
77
+ | `create_dir(path)` | Create directory recursively |
78
+ | `delete_path(path)` | Delete file or empty directory |
79
+ | `file_stat(path)` | Get file/directory metadata |
80
+ | `glob_search(pattern, path)` | Find files by glob pattern |
81
+
82
+ ### Shell Tools
83
+
84
+ | Tool | Description |
85
+ |---|---|
86
+ | `run_command(cmd, cwd, timeout)` | Execute command and wait |
87
+ | `start_process(cmd, cwd)` | Start background job |
88
+ | `get_job_output(job_id, offset)` | Read job output |
89
+ | `kill_job(job_id)` | Terminate background job |
90
+ | `list_jobs()` | List all jobs |
91
+
92
+ ### ESP-IDF Tools
93
+
94
+ All ESP-IDF operations are invoked through `eim run "WISH_PRODUCT=<product> idf.py ..."` which handles environment setup automatically.
95
+
96
+ | Tool | Description |
97
+ |---|---|
98
+ | `eim_run(commands, project_dir, wish_product)` | Run any `idf.py` command via `eim` |
99
+ | `build_project(project_dir, wish_product, reconfigure)` | Build an ESP-IDF project |
100
+ | `set_target(project_dir, target, wish_product)` | Set target chip (esp32, esp32s3, etc.) |
101
+ | `flash_project(project_dir, port, wish_product)` | Flash firmware to device |
102
+ | `clean_project(project_dir)` | Clean build artifacts |
103
+ | `fullclean_project(project_dir)` | Remove entire build directory |
104
+ | `reconfigure_project(project_dir, wish_product)` | Regenerate sdkconfig + CMake cache |
105
+
106
+ ### Build Step Decision Tree
107
+
108
+ ```
109
+ Is sdkconfig missing or have defaults changed?
110
+ ├── YES → eim_run("reconfigure build", project_dir, wish_product)
111
+ └── NO → eim_run("build", project_dir, wish_product)
112
+ ```
113
+
114
+ ## Security
115
+
116
+ | Control | Implementation |
117
+ |---|---|
118
+ | Filesystem sandbox | All paths validated against `MCP_ALLOWED_ROOTS` |
119
+ | Path traversal prevention | Symlink-resolved, `..` escapes rejected |
120
+ | Shell timeout | Configurable, default 30s, max 300s |
121
+ | Output truncation | 50 KB max per response |
122
+ | Authentication | Bearer token on every request |
123
+ | Credential safety | Tokens in env vars only, never logged |
124
+
125
+ ## Integration with AI Agents
126
+
127
+ ### Hermes
128
+
129
+ ```bash
130
+ hermes mcp add esp-workspace \
131
+ --url http://host:port/sse \
132
+ --header "Authorization: Bearer your-token"
133
+ ```
134
+
135
+ ### Generic MCP Client
136
+
137
+ Any MCP-compatible client can connect to the SSE endpoint. The server implements the standard MCP protocol over SSE/HTTP transport.
138
+
139
+ ## Architecture
140
+
141
+ ```
142
+ esp_workspace_mcp/
143
+ ├── config.py # Settings from env, fail-fast validation
144
+ ├── server.py # FastMCP server, tool registration
145
+ ├── auth.py # Bearer token middleware
146
+ ├── tools/
147
+ │ ├── filesystem.py # 8 path-validated file operations
148
+ │ ├── shell.py # Command execution + background jobs
149
+ │ └── esp_idf.py # eim-run wrapper, build/flash/clean
150
+ ├── utils/
151
+ │ ├── security.py # Path sandboxing, symlink resolution
152
+ │ └── process.py # Subprocess management, JobManager
153
+ └── resources/
154
+ └── project.py # MCP resources (project status, etc.)
155
+ ```
156
+
157
+ ## License
158
+
159
+ Apache-2.0
@@ -0,0 +1,2 @@
1
+ """ESP-Workspace MCP — Full autonomous embedded firmware workspace server."""
2
+ __version__ = "0.5.0"
@@ -0,0 +1,3 @@
1
+ """Allow running as: python -m esp_workspace_mcp"""
2
+ from esp_workspace_mcp.cli import main
3
+ main()
@@ -0,0 +1,74 @@
1
+ """SSE transport with Bearer token authentication."""
2
+ import logging
3
+ from functools import wraps
4
+ from typing import Optional
5
+
6
+ from fastapi import Request, Response
7
+ from fastapi.responses import JSONResponse
8
+ from starlette.middleware.base import BaseHTTPMiddleware
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def _mask_token(token: str, show: int = 4) -> str:
14
+ if not token:
15
+ return ""
16
+ if len(token) <= show:
17
+ return "*" * len(token)
18
+ return f"***{token[-show:]}"
19
+
20
+
21
+ class BearerAuthMiddleware(BaseHTTPMiddleware):
22
+ """Middleware that enforces Bearer token authentication."""
23
+
24
+ def __init__(self, app, api_token: str, exempt_paths: Optional[list] = None):
25
+ super().__init__(app)
26
+ self.api_token = api_token
27
+ self.exempt_paths = exempt_paths or ["/health"]
28
+
29
+ async def dispatch(self, request: Request, call_next):
30
+ # Allow health check without auth
31
+ if request.url.path in self.exempt_paths:
32
+ logger.debug("Auth: exempt path %s", request.url.path)
33
+ return await call_next(request)
34
+
35
+ # Check Authorization header
36
+ auth = request.headers.get("Authorization", "")
37
+ provided_token = None
38
+ if auth.startswith("Bearer "):
39
+ provided_token = auth[7:]
40
+
41
+ # Debug logging (masked tokens only)
42
+ logger.debug(
43
+ "Auth check: path=%s method=%s auth_header=%s provided=%s expected=%s",
44
+ request.url.path,
45
+ request.method,
46
+ auth[:7] + "..." if auth else "",
47
+ _mask_token(provided_token),
48
+ _mask_token(self.api_token),
49
+ )
50
+
51
+ if not provided_token or provided_token != self.api_token:
52
+ logger.warning(
53
+ "Unauthorized access attempt on %s: provided=%s expected=%s",
54
+ request.url.path,
55
+ _mask_token(provided_token),
56
+ _mask_token(self.api_token),
57
+ )
58
+ return JSONResponse(
59
+ status_code=401,
60
+ content={"error": "Unauthorized: invalid or missing Bearer token"},
61
+ headers={"WWW-Authenticate": "Bearer"},
62
+ )
63
+
64
+ return await call_next(request)
65
+
66
+
67
+ def require_token(api_token: str):
68
+ """Decorator for requiring Bearer token on a specific endpoint."""
69
+ def decorator(func):
70
+ @wraps(func)
71
+ async def wrapper(*args, **kwargs):
72
+ return await func(*args, **kwargs)
73
+ return wrapper
74
+ return decorator