Skip to content

Commit a560bbe

Browse files
authored
Add embedded MCP server exposing MapMap control over local HTTP (#612)
* Add embedded MCP server exposing MapMap control over local HTTP Adds a native Model Context Protocol server (QHttpServer, JSON-RPC 2.0) running on the GUI thread, exposing sources, layers, playback, project I/O and state read-back to MCP clients at http://localhost:<port>/mcp. The port is configurable via QSettings (mcpListeningPort, default 49452) and a new -m/--mcp-port CLI flag; 0 disables the server. Requires the QtHttpServer module (and its QtWebSockets dependency). Add port preference UI Adapt McpServer to Paint->Source and Mapping->Layer renames. Fix QHttpServer::listen() removal in Qt 6.11 by using QTcpServer::listen() with QHttpServer::bind(). Add MCP port spinbox to Preferences Controls tab (0 disables the server). * Make MCP server build optional when qthttpserver is unavailable Use qtHaveModule(httpserver) to conditionally enable MCP support, guarded by HAVE_MCP preprocessor define throughout the codebase. This fixes CI builds on platforms without the Qt HttpServer module. * Add create_layer tool to MCP server for triangle, quad and ellipse shapes Move addMesh/addTriangle/addEllipse from private to public slots in MainWindow so the MCP server can call them directly. * Add Claude Code skill for controlling MapMap via MCP * Add set_vertices MCP tool and update skill doc Allows setting the output shape vertices of a layer via MCP. Documents vertex ordering and the new tool in the skill file. * Guard mcpListeningPort write in writeSettings with HAVE_MCP The member variable is only declared under #ifdef HAVE_MCP, so writing it in writeSettings also needs the guard to fix the build when qthttpserver is unavailable.
1 parent c9a0008 commit a560bbe

11 files changed

Lines changed: 938 additions & 3 deletions

File tree

.claude/skills/mapmap-mcp.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
---
2+
name: mapmap-mcp
3+
description: Control MapMap via its embedded MCP server over local HTTP. Use this skill whenever the user asks to create, modify, inspect, or control sources, layers, playback, or project state in the running MapMap instance.
4+
trigger: When the user wants to interact with the running MapMap application - creating sources/layers, controlling playback, querying state, modifying properties, loading/saving projects.
5+
---
6+
7+
# MapMap MCP Control
8+
9+
MapMap exposes a JSON-RPC 2.0 MCP server at `http://localhost:49452/mcp` (POST only).
10+
11+
## How to call
12+
13+
```bash
14+
curl -s -X POST http://localhost:49452/mcp \
15+
-H 'Content-Type: application/json' \
16+
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"TOOL_NAME","arguments":{...}}}' \
17+
| python3 -m json.tool
18+
```
19+
20+
Increment the `id` for each call in a session. Always pipe through `python3 -m json.tool` for readable output.
21+
22+
### First call: initialize
23+
24+
Before any tool calls, send an initialize request:
25+
26+
```bash
27+
curl -s -X POST http://localhost:49452/mcp \
28+
-H 'Content-Type: application/json' \
29+
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"claude","version":"1.0"}}}'
30+
```
31+
32+
## Available tools
33+
34+
### Playback & Project
35+
| Tool | Args | Description |
36+
|------|------|-------------|
37+
| `play` || Start playback |
38+
| `pause` || Pause playback |
39+
| `rewind` || Rewind to start |
40+
| `quit` || Quit MapMap |
41+
| `clear_project` || Clear all sources and layers |
42+
| `load_project` | `path` (string, required) | Load a .mmp project file |
43+
| `save_project` | `path` (string, required) | Save project to file |
44+
| `set_fps` | `fps` (number, required, > 0) | Set playback frame rate |
45+
46+
### Sources
47+
| Tool | Args | Description |
48+
|------|------|-------------|
49+
| `create_color_source` | `color` (string, required: "#rrggbb" or name) | Create solid-color source |
50+
| `create_media_source` | `uri` (string, required), `is_image` (bool, default false) | Import image or video |
51+
| `delete_source` | `id` (int, required) | Delete source and its layers |
52+
53+
### Layers
54+
| Tool | Args | Description |
55+
|------|------|-------------|
56+
| `create_layer` | `source_id` (int, required), `shape` (string, required: "triangle", "quad", or "ellipse") | Create a layer with a shape for a source |
57+
| `delete_layer` | `id` (int, required) | Delete a layer |
58+
| `duplicate_layer` | `id` (int, required) | Duplicate a layer |
59+
| `move_layer` | `id` (int, required), `index` (int, required, 0=bottom) | Move layer in stack |
60+
| `set_layer_visible` | `id` (int), `value` (bool) | Show/hide layer |
61+
| `set_layer_solo` | `id` (int), `value` (bool) | Solo a layer |
62+
| `set_layer_locked` | `id` (int), `value` (bool) | Lock/unlock layer |
63+
64+
### Geometry
65+
| Tool | Args | Description |
66+
|------|------|-------------|
67+
| `set_vertices` | `id` (int, layer id), `vertices` (array of `{x, y}`) | Set the output vertices of a layer's shape |
68+
69+
Vertex order is **clockwise from top-left**: top-left, top-right, bottom-right, bottom-left.
70+
- Quad: 4 vertices
71+
- Triangle: 3 vertices (bottom-left, bottom-right, top-center)
72+
- Ellipse: 5 vertices (left, top, right, bottom, rotation-handle)
73+
74+
### Properties
75+
| Tool | Args | Description |
76+
|------|------|-------------|
77+
| `set_property` | `kind` ("source"/"layer"), `id` (int), `property` (string), `value` (any) | Set arbitrary property |
78+
79+
Common properties:
80+
- **Source (color):** `color` ("#rrggbb"), `name`, `opacity` (0.0-1.0), `locked`
81+
- **Layer:** `name`, `opacity`, `visible`, `solo`, `locked`, `depth`
82+
83+
### Introspection
84+
| Tool | Args | Description |
85+
|------|------|-------------|
86+
| `get_state` || Get playing, fps, counts, current selection |
87+
| `list_sources` || List all sources |
88+
| `list_layers` || List all layers |
89+
| `get_source` | `id` (int) | Get all properties of a source |
90+
| `get_layer` | `id` (int) | Get all properties of a layer |
91+
92+
## Typical workflow
93+
94+
1. Create a source (`create_color_source` or `create_media_source`)
95+
2. Create a layer for it (`create_layer` with `source_id` from step 1)
96+
3. Position the layer (`set_vertices` with the desired corner positions)
97+
4. Adjust properties as needed (`set_property`, `set_layer_visible`, etc.)
98+
5. Control playback (`play`, `pause`, `rewind`)
99+
100+
## Response format
101+
102+
Success returns `{"isError": false, "content": [...]}` with optional `structuredContent`.
103+
Errors return `{"isError": true, "content": [{"type":"text","text":"error message"}]}`.

src/app/main.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,13 @@ int main(int argc, char *argv[])
104104
"Use OSC port number <osc-port>.", "osc-port", "");
105105
parser.addOption(oscPortOption);
106106

107+
#ifdef HAVE_MCP
108+
// --mcp-port option
109+
QCommandLineOption mcpPortOption(QStringList() << "m" << "mcp-port",
110+
"Use MCP server port number <mcp-port> (0 to disable).", "mcp-port", "");
111+
parser.addOption(mcpPortOption);
112+
#endif
113+
107114
// --lang option
108115
QCommandLineOption localeOption(QStringList() << "l" << "lang",
109116
"Use language <lang>.", "lang", "");
@@ -212,6 +219,12 @@ int main(int argc, char *argv[])
212219
if (parser.isSet(verboseOption))
213220
win->setVerbose(true);
214221

222+
#ifdef HAVE_MCP
223+
QString mcpPortValue = parser.value("mcp-port");
224+
if (mcpPortValue != "")
225+
win->setMcpPort(mcpPortValue);
226+
#endif
227+
215228
bool optionOk;
216229
qreal fps = parser.value("frame-rate").toDouble(&optionOk);
217230
if (optionOk)

0 commit comments

Comments
 (0)