9.8 KiB
McpScreen for Tactility — Implementation Plan
Goal
Port the hardware-agnostic MCP Screen design from:
/Users/adolforeyna/Projects/MicroPython/test1/Screen
into a native Tactility application while preserving compatibility with the existing host-side MCP bridge and Hermes voice service.
The original reference documents and implementations are:
system_design_specs.mdmcp_server.pymcp_bridge.pymain.pyvideo_stream.pyvoice_assistant.pylib/audio_util.py
Target application
- Source folder:
Apps/McpScreen - App ID:
one.tactility.mcpscreen - Initial platform: ESP32-S3
- Initial Tactility SDK:
0.7.0-dev - Language: C/C++ with ESP-IDF, FreeRTOS, Tactility SDK, and LVGL
Design principles
- Preserve the existing MCP tool names and JSON-RPC contract wherever the underlying Tactility hardware supports them.
- Keep network, audio, and long-running work outside the LVGL task.
- Perform all LVGL operations under the Tactility LVGL lock or dispatch them onto the LVGL task.
- Use Tactility device and HAL interfaces instead of board-specific GPIO assumptions wherever possible.
- Return structured unsupported-capability errors instead of silently emulating unavailable hardware.
- Treat files supplied over MCP as untrusted input and constrain file access to the application's user-data directory.
- Do not port
execute_pythonliterally. Native firmware must not expose arbitrary code execution.
Proposed application structure
Apps/McpScreen/
├── CMakeLists.txt
├── manifest.properties
├── IMPLEMENTATION_PLAN.md
└── main/
├── CMakeLists.txt
└── Source/
├── main.cpp
├── McpScreenApp.cpp
├── McpScreenApp.h
├── McpServer.cpp
├── McpServer.h
├── DiscoveryService.cpp
├── DiscoveryService.h
├── ToolRegistry.cpp
├── ToolRegistry.h
├── ToolDispatcher.cpp
├── ToolDispatcher.h
├── DisplayTools.cpp
├── DisplayTools.h
├── AudioTools.cpp
├── AudioTools.h
├── SystemTools.cpp
├── SystemTools.h
├── DashboardView.cpp
└── DashboardView.h
The file boundaries may be consolidated during the first slice if a smaller implementation is easier to validate.
Runtime architecture
Application lifecycle
onCreate- Allocate application state.
- Start UDP discovery and HTTP MCP service tasks.
onShow- Build the dashboard and MCP-controlled display canvas.
- Attach the current UI objects to the shared application state.
onHide- Detach and invalidate UI object pointers.
- Keep network services running while the application is merely hidden, if Tactility preserves the app instance.
onDestroy- Stop tasks and sockets.
- Release buffers, devices, and application state.
The first implementation slice must explicitly verify whether Tactility keeps
the app process and onCreate state alive while another app is visible.
FreeRTOS responsibilities
- MCP HTTP task
- Listen for HTTP requests.
- Parse JSON-RPC requests.
- Dispatch tools.
- Serialize JSON-RPC responses.
- UDP discovery task
- Bind UDP port 5000.
- Reply to discovery packets.
- Display work
- Marshal changes through the LVGL lock or an LVGL async callback.
- Audio tasks
- Record and play I2S data without blocking the UI or MCP listener.
- Future streaming tasks
- Own TCP/UDP frame sockets and preallocated frame buffers.
Network compatibility
UDP discovery
- Port:
5000 - Request: exact bytes
DISCOVER_SCREEN - Response: exact bytes
SCREEN_IP_80
MCP HTTP endpoint
- Port:
80 - Method and path:
POST /api/mcp - Content type:
application/json - Protocol: JSON-RPC 2.0
- Required methods:
tools/listtools/call
Raw display endpoint
Added after the basic MCP slice:
- Method and path:
POST /api/screen/raw - Query parameters:
x,y,w,h - Body: raw big-endian RGB565 bytes
MCP response behavior
Successful tool calls retain the existing shape:
{
"jsonrpc": "2.0",
"result": {
"content": [
{
"type": "text",
"text": "..."
}
]
},
"id": 1
}
Errors use JSON-RPC error objects:
{
"jsonrpc": "2.0",
"error": {
"code": -32000,
"message": "..."
},
"id": 1
}
Phase 1 — MCP foundation
Scope
- Scaffold
Apps/McpScreen. - Add a status/dashboard view showing:
- Wi-Fi state
- server state
- port
- last tool
- last error
- Start the HTTP server on port 80.
- Implement
POST /api/mcp. - Implement JSON-RPC request validation and error handling.
- Implement
tools/list. - Implement UDP discovery on port 5000.
- Implement the first three tools:
get_capabilitiesclear_screendraw_text
- Test with the existing
mcp_bridge.py.
Initial tool semantics
get_capabilities
Return live display information:
{
"color": true,
"width": 320,
"height": 240,
"formats": ["rgb565_base64", "bmp_base64"],
"platform": "tactility",
"appVersion": "0.1.0"
}
Width, height, and color format must be queried at runtime.
clear_screen
- Accept
colorvalues compatible with the original API:0: white1: black
- Mark MCP display override active.
- Clear the app-owned canvas/display area.
draw_text
- Accept
text,x,y, and optionalsize. - Mark MCP display override active.
- Render inside the app-owned canvas.
- Clamp coordinates to the drawable area.
Phase 1 acceptance criteria
- The app builds and packages for ESP32-S3.
- Opening the app displays its server status.
- UDP discovery returns
SCREEN_IP_80. - The existing bridge can discover the device.
tools/listreturns valid schemas for the three initial tools.get_capabilitiesreports the actual Tactility display dimensions.clear_screenanddraw_textvisibly update the app.- Malformed JSON and unknown methods return JSON-RPC errors without crashing.
- Repeated requests do not leak tasks, sockets, or request buffers.
- App hiding/showing behavior is documented from a device test.
Phase 2 — Display tools
Add:
draw_raw_rgb565POST /api/screen/rawdraw_color_bmpdraw_imageget_screenshotset_backlightset_screen_power
Display commands should target an app-owned LVGL canvas or image buffer. Direct panel access should only be used when the Tactility display HAL guarantees safe ownership and synchronization.
Image preprocessing may remain in mcp_bridge.py initially. That avoids large
PNG/JPEG decoders and unnecessary memory pressure on the ESP32-S3.
Phase 3 — Audio tools
Port the working implementation from Apps/AudioTest:
play_tonerecord_voiceplay_audioplay_audio_base64
Audio baseline:
- 16 kHz
- 16-bit signed PCM
- mono
- I2S device discovered through
device_find_by_name("i2s0")
Recordings should be streamed to the application's user-data directory instead of requiring one large fixed-duration RAM allocation.
Phase 4 — Hardware and system tools
Add where supported:
get_touchget_batteryset_ledget_sensorsscan_blesync_timeread_filewrite_filedownload_file
File operations must:
- resolve paths under the app user-data directory;
- reject absolute paths and traversal such as
../; - enforce practical request and file-size limits.
execute_python is intentionally excluded. A future allow-listed diagnostic
command tool may replace it if needed.
Phase 5 — Video streaming
Add:
- TCP frame server
- UDP chunked frame server
- stream activity timeout
get_video_streaming_instructionsget_stream_stats
The new protocol should advertise the actual Tactility display dimensions and native color format. The old RLCD-specific 15,000-byte monochrome mapping may be offered only as an optional compatibility mode.
Use preallocated buffers and avoid per-frame heap allocation.
Phase 6 — Dashboard and Hermes voice assistant
Add:
- five-second dashboard refresh;
- MCP override mode;
- an explicit local action to leave override mode;
- Hermes WebSocket client;
- touch-to-talk;
- 16 kHz, 16-bit mono microphone streaming;
- incoming PCM/WAV playback;
- transcript, thinking, listening, and speaking UI states.
Compatibility notes
- Keep the existing host bridge usable throughout development.
- Preserve tool names and argument names unless a compatibility defect is documented.
draw_imagecan continue to be host-preprocessed into PBM or RGB565.- Screenshot output may change from PBM to PNG/RGB565 if the bridge is updated to understand both.
- Hardware-specific tools should report support through
get_capabilities.
Security and robustness limits
The first implementation should define conservative limits for:
- HTTP header size
- JSON body size
- base64 payload size
- socket read timeout
- simultaneous clients
- filename length
- file size
- text length
- drawing bounds
- audio duration
Only one display mutation should run at a time. Audio operations should also be serialized around the shared I2S device.
Build workflow
From the TactilityApps repository:
. /Users/adolforeyna/esp/esp-idf/export.sh
export TACTILITY_SDK_PATH=/Users/adolforeyna/.gemini/antigravity/scratch/tactility/release/TactilitySDK
python3 tactility.py Apps/McpScreen build esp32s3 --local-sdk
Immediate next task
Implement Phase 1 as a minimal vertical slice:
- Scaffold the app and lifecycle.
- Add a small server-status UI.
- Add UDP discovery.
- Add HTTP and JSON-RPC parsing.
- Add
tools/list. - Add
get_capabilities,clear_screen, anddraw_text. - Build.
- Run a host smoke test through the existing bridge.