Skip to content

Commit a442e70

Browse files
matixanclaude
andcommitted
feat: symbol resource template + optional HTTP transport (v8.1.0)
- crosspad://symbols/{repo}/{symbol} ResourceTemplate — MCP-native single-symbol lookup; tool crosspad_search_symbols stays for substring/wildcard - --http <port> flag enables StreamableHTTPServerTransport at /mcp (stateful, sessionIdGenerator → randomUUID); stdio remains default - README: Transport section + apps registry/installed + symbols template in resources table - todo.md: items #25 and #26 marked done; only prompts expansion (#6 partial) deferred Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 27e4e72 commit a442e70

4 files changed

Lines changed: 126 additions & 13 deletions

File tree

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ Each tool is focused on a single action. Strict schema validation (ranges on MID
125125
| URI | Purpose |
126126
|-----|---------|
127127
| `crosspad://workspace` | JSON snapshot: detected repos, branches, HEADs, dirty counts, PC simulator running status. Loadable without a tool call — clients (e.g. Claude Code) can pin it as session context. |
128+
| `crosspad://apps/registry/<platform>` | Raw `app-registry.json` per detected platform (pc / idf / esp32-s3). |
129+
| `crosspad://apps/installed/<platform>` | Raw `apps.json` (installed manifest) per detected platform. |
130+
| `crosspad://symbols/{repo}/{symbol}` | Resource template — resolves a single symbol's definitions in `<repo>` (or `all`). MCP-native alternative to `crosspad_search_symbols` for known symbol+repo pairs. |
128131

129132
### Migration: v7 → v8
130133

@@ -179,6 +182,21 @@ Each repo path is individually configurable via env vars. If not set, falls back
179182

180183
Repos are discovered dynamically — only repos that exist on disk appear in tool results. No flat directory structure is assumed when env vars are set.
181184

185+
## Transport
186+
187+
**stdio (default)**`npx crosspad-mcp-server`. Standard MCP transport for Claude Code / Claude Desktop / IDE plugins.
188+
189+
**HTTP (`--http <port>`)**`npx crosspad-mcp-server --http 3000`. Exposes a Streamable HTTP endpoint at `http://localhost:<port>/mcp` for remote dev boxes or browser-based MCP clients. Stateful sessions (`Mcp-Session-Id` header echoed after `initialize`). One transport, multi-session multiplexed internally.
190+
191+
```bash
192+
# Minimal HTTP smoke test:
193+
npx crosspad-mcp-server --http 3000
194+
curl -X POST http://localhost:3000/mcp \
195+
-H "Content-Type: application/json" \
196+
-H "Accept: application/json, text/event-stream" \
197+
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"x","version":"0"}}}'
198+
```
199+
182200
## How it works
183201

184202
**Static tools** (build, repos, code, apps) work without the simulator — they operate on the filesystem, git, and Python package manager.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "crosspad-mcp-server",
3-
"version": "8.0.0",
3+
"version": "8.1.0",
44
"description": "MCP development server for CrossPad — build, test, manage apps, interact with simulator",
55
"type": "module",
66
"main": "dist/index.js",

src/index.ts

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env node
22

3-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3+
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
44
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
55
import { z } from "zod";
66

@@ -1160,10 +1160,98 @@ import path from "path";
11601160
})();
11611161

11621162
// ═══════════════════════════════════════════════════════════════════════
1163-
// START
1163+
// RESOURCES — code navigation via URI templates (MCP-native)
1164+
// crosspad://symbols/{repo}/{symbol} — resolve a single symbol's definitions
1165+
// in a single repo without spending a tool call. Repo "all" searches every
1166+
// detected repo. listCallback is undefined (cannot enumerate every symbol);
1167+
// clients must construct concrete URIs.
11641168
// ═══════════════════════════════════════════════════════════════════════
11651169

1170+
server.registerResource(
1171+
"crosspad-symbol",
1172+
new ResourceTemplate("crosspad://symbols/{repo}/{symbol}", { list: undefined }),
1173+
{
1174+
description: "Resolve a single symbol by repo+name. URI: crosspad://symbols/<repo>/<symbol>. <repo> is one of: crosspad-core, crosspad-gui, crosspad-pc, platform-idf, ESP32-S3, or 'all'. Returns JSON with matching definition(s) (class/function/macro/enum/typedef). For substring/wildcard search, use the crosspad_search_symbols tool.",
1175+
mimeType: "application/json",
1176+
},
1177+
async (uri, variables) => {
1178+
const repo = decodeURIComponent(String(Array.isArray(variables.repo) ? variables.repo[0] : variables.repo ?? "")).trim();
1179+
const symbol = decodeURIComponent(String(Array.isArray(variables.symbol) ? variables.symbol[0] : variables.symbol ?? "")).trim();
1180+
if (!repo || !symbol) {
1181+
return {
1182+
contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify({ error: "URI must be crosspad://symbols/<repo>/<symbol>" }, null, 2) }],
1183+
};
1184+
}
1185+
const reposScope = repo === "all" ? ["all"] : [repo];
1186+
const result = crosspadSearchSymbols(symbol, "all", reposScope, 50, 0);
1187+
return {
1188+
contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(result, null, 2) }],
1189+
};
1190+
}
1191+
);
1192+
1193+
// ═══════════════════════════════════════════════════════════════════════
1194+
// START — stdio (default) or HTTP (--http <port>)
1195+
// HTTP transport is opt-in via CLI flag for remote dev boxes / browsers.
1196+
// Stateful sessions: each initialize gets a session ID; subsequent requests
1197+
// must echo it. Single shared transport multiplexes sessions internally.
1198+
// ═══════════════════════════════════════════════════════════════════════
1199+
1200+
function parseHttpPort(argv: string[]): number | null {
1201+
for (let i = 0; i < argv.length; i++) {
1202+
const a = argv[i];
1203+
if (a === "--http") {
1204+
const next = argv[i + 1];
1205+
if (!next) return 3000;
1206+
const n = parseInt(next, 10);
1207+
return Number.isFinite(n) && n > 0 && n < 65536 ? n : NaN as unknown as number;
1208+
}
1209+
if (a.startsWith("--http=")) {
1210+
const n = parseInt(a.slice("--http=".length), 10);
1211+
return Number.isFinite(n) && n > 0 && n < 65536 ? n : NaN as unknown as number;
1212+
}
1213+
}
1214+
return null;
1215+
}
1216+
11661217
async function main() {
1218+
const httpPort = parseHttpPort(process.argv.slice(2));
1219+
if (httpPort !== null) {
1220+
if (Number.isNaN(httpPort)) {
1221+
console.error("Invalid --http port (must be 1..65535)");
1222+
process.exit(1);
1223+
}
1224+
const { StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js");
1225+
const { createServer } = await import("http");
1226+
const { randomUUID } = await import("crypto");
1227+
1228+
const transport = new StreamableHTTPServerTransport({
1229+
sessionIdGenerator: () => randomUUID(),
1230+
});
1231+
await server.connect(transport);
1232+
1233+
const httpServer = createServer((req, res) => {
1234+
const pathname = (req.url ?? "/").split("?")[0];
1235+
if (pathname !== "/mcp") {
1236+
res.writeHead(404, { "Content-Type": "text/plain" });
1237+
res.end("Not Found — MCP endpoint is at /mcp");
1238+
return;
1239+
}
1240+
transport.handleRequest(req, res).catch((e) => {
1241+
console.error("MCP HTTP request failed:", e);
1242+
if (!res.headersSent) {
1243+
res.writeHead(500, { "Content-Type": "text/plain" });
1244+
res.end("Internal error");
1245+
}
1246+
});
1247+
});
1248+
1249+
httpServer.listen(httpPort, () => {
1250+
console.error(`crosspad-mcp HTTP transport listening on http://localhost:${httpPort}/mcp`);
1251+
});
1252+
return;
1253+
}
1254+
11671255
const transport = new StdioServerTransport();
11681256
await server.connect(transport);
11691257
}

todo.md

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Review krytyczny z perspektywy zgodności z MCP spec, security i idiomatyki protokołu.
44
Skala: 🔴 krytyczne · 🟠 anti-pattern · 🟡 średnie · 🟢 nice-to-have
55

6-
**Status:** runda 1 (`7ac0f17`) + runda 2 + runda 3 (`a8a9c0c`, v7.0.0) + runda 4 (`d696820`, security/cancellation/progress) + runda 5 (`fedae5c`, outputSchema + structuredContent) + runda 6 (`ca07999`, TCP zod / envelope cleanup / registry resources) + runda 7 (uncommitted — v8.0.0 platform-axis unification: 30 → 26 tools).
6+
**Status:** runda 1 (`7ac0f17`) + runda 2 + runda 3 (`a8a9c0c`, v7.0.0) + runda 4 (`d696820`, security/cancellation/progress) + runda 5 (`fedae5c`, outputSchema + structuredContent) + runda 6 (`ca07999`, TCP zod / envelope cleanup / registry resources) + runda 7 (`27e4e72`, v8.0.0 platform-axis unification: 30 → 28 tools) + runda 8 (uncommitted — symbol resource template + HTTP transport).
77

88
---
99

@@ -20,7 +20,15 @@ To obchodzi zależność od CLAUDE.md i memory — sygnał idzie kanałem MCP-pr
2020

2121
---
2222

23-
## Co dało runda 7 (uncommitted, v8.0.0)
23+
## Co dało runda 8 (uncommitted)
24+
25+
- **MCP-native code search resource (#25):** `ResourceTemplate("crosspad://symbols/{repo}/{symbol}", { list: undefined })` zarejestrowany. `<repo>` accepts repo name (crosspad-core, crosspad-pc, etc.) lub `all`. Read callback wywołuje `crosspadSearchSymbols(symbol, "all", [repo], 50, 0)`. listCallback = `undefined` (nie da się sensownie wyliczyć wszystkich symboli) — clients muszą skonstruować konkretny URI. Tool `crosspad_search_symbols` zostaje dla substring/wildcard.
26+
- **HTTP transport (#26):** `--http <port>` flag w main(). `StreamableHTTPServerTransport` w stateful mode (sessionIdGenerator → randomUUID). Endpoint `/mcp` (POST/GET/DELETE), 404 na innych ścieżkach. Smoke OK: `initialize` zwraca `Mcp-Session-Id`, kolejne wywołania z headerem działają.
27+
- **Smoke verify (stdio):** 28 tools, 7 static resources, 1 resourceTemplate, `resources/read crosspad://symbols/all/CrossPad` zwraca prawidłowy JSON envelope.
28+
- **Smoke verify (HTTP):** `curl POST /mcp initialize` zwraca SSE event z `serverInfo.name=crosspad`, `tools/list` i `resources/templates/list` działają z session id.
29+
- README: dodana sekcja **Transport**, dopisane apps registry/installed + symbols template w resources table.
30+
31+
## Co dało runda 7 (`27e4e72`, v8.0.0)
2432

2533
**Platform-axis unification (#8) — breaking, intentional.** Net: 30 → 28 tools.
2634

@@ -278,13 +286,14 @@ To obchodzi zależność od CLAUDE.md i memory — sygnał idzie kanałem MCP-pr
278286
- **Problem:** Hardcoded `-DCMAKE_BUILD_TYPE=Debug`.
279287
- **Fix:** Param `build_type: z.enum(["Debug","Release","RelWithDebInfo"])` default `Debug`.
280288

281-
### [ ] 25. Bardziej "MCP-native" code search
289+
### [x] 25. Bardziej "MCP-native" code search ✅ runda 8
282290
- **Idea:** Zamiast `crosspad_search_symbols` zwracać JSON, eksponować jako `resources` z URI `crosspad://symbols/<repo>/<symbol>` — LLM nawiguje, nie filtruje.
291+
- **Done:** `ResourceTemplate` `crosspad://symbols/{repo}/{symbol}` (listCallback undefined). Tool `crosspad_search_symbols` zostaje dla substring/wildcard.
283292

284-
### [ ] 26. HTTP/SSE transport opcjonalnie
285-
- **Plik:** [src/index.ts:626](src/index.ts#L626)
293+
### [x] 26. HTTP/SSE transport opcjonalnie ✅ runda 8
294+
- **Plik:** [src/index.ts](src/index.ts)
286295
- **Problem:** Tylko stdio. Dla embedded dev OK, ale ogranicza remote dev box.
287-
- **Fix:** CLI flag `--http :PORT` z `HttpServerTransport`. Optional.
296+
- **Fix:** CLI flag `--http <port>` z `StreamableHTTPServerTransport`. Stateful (sessionIdGenerator). Endpoint `/mcp`.
288297

289298
---
290299

@@ -296,7 +305,5 @@ To obchodzi zależność od CLAUDE.md i memory — sygnał idzie kanałem MCP-pr
296305
4. **Konsolidacja** 41 → ~25 tools (input + midi do discriminatedUnion) → poz. 7. ✅
297306
5. **Progress notifications** zamiast logging dla build/test/flash + cancellation → poz. 4, 5. ✅
298307

299-
Pozostałe (deferred — wymagają większego refaktoru lub mają niski ROI):
300-
- Resources/Prompts expansion (poz. 6) — workspace done; reszta (registry, manifesty, prompts) odłożona.
301-
- HTTP/SSE transport (poz. 26) — niche, stdio jest ok dla embedded dev.
302-
- MCP-native code search jako resources (poz. 25) — niespójne z innymi tool-based searches.
308+
Pozostałe (deferred):
309+
- Prompts expansion (poz. 6 — pozostała część) — odłożone, na razie polegamy na server `instructions` + resources.

0 commit comments

Comments
 (0)