MCP Workspace Exposure Plan
Goal
Expose workspace read/write and widget schema discovery through the MCP server, so AI agents can construct, inspect, and modify Nagelfluh layouts programmatically.
MCP Tools to Add
| Tool | Description |
|---|---|
list_workspaces |
List all saved workspaces (id, title, timestamps). Use to discover what layouts exist before reading one. |
get_workspace |
Get the full layout tree for a workspace. Returns a recursive JSON tree of nodes with id, widget, children, and widget-specific layoutConfig. Call get_workspace_schema first to understand valid structures. |
create_workspace |
Create a new workspace with a title and layout tree. The layout field must conform to the schema returned by get_workspace_schema. |
get_workspace_schema |
Get the JSON Schema for the entire workspace layout format — the recursive node tree, all valid widget types as a discriminated union, and per-widget layoutConfig schemas including Nagelfluh-specific PlotView layer types. Always call this before constructing a layout. |
Architecture
Why widget schemas can't be extracted at build time automatically
PlotView's get_schema() returns a schema assembled by gladly at import time by merging
schemas from all registered layer types — including Nagelfluh-specific ones
(ResistivityCurtain, FlightlinePlot, MagLinePlot, etc.). This requires:
- A real browser environment (WebGL context)
- All layer types imported and registered as side effects
- gladly's schema-merging logic to have run
Node.js stubs cannot satisfy these constraints without producing wrong/empty schemas.
Solution: committed generated artifact
backend/widget_schemas.json is generated by a Puppeteer script that loads the running
dev server, extracts all widget schemas from window.__nagelfluh_widgets, and writes the
result. The file is committed to git and treated as a checked-in generated artifact —
like a lockfile or generated protobuf stubs.
Docker builds and the production backend simply read the committed file. No schema generation happens at Docker build time or at server startup.
Implementation Steps
Phase 1 — Expose workspace endpoints in MCP
1.1 Add a "Workspaces" tag to all endpoints in backend/routers/workspaces.py.
1.2 Add "Workspaces" to the include_tags list in the FastApiMCP setup in
backend/main.py.
1.3 Remove or do not expose the update and delete endpoints — workspaces are create-and-read only via MCP. Review and improve docstrings on the remaining endpoints; these become the MCP tool descriptions seen by the agent.
Phase 2 — Widget schema endpoint
2.1 Add GET /workspace-schema endpoint to backend/routers/workspaces.py. It reads
backend/widget_schemas.json and assembles a single JSON Schema for the entire workspace
layout format: a recursive $defs-based schema with a discriminated union over all widget
types (container widgets and leaf widgets), each with their layoutConfig inlined.
Tag it "Workspaces".
Return 503 with a clear message if the file is missing, so the developer knows to run the export script.
2.2 Add backend/widget_schemas.json as an empty {} placeholder and commit it, so
the backend starts cleanly before the first real export.
Phase 3 — Frontend schema export
3.1 In frontend/src/App.js, after the widgets object is defined, expose it on
window:
window.__nagelfluh_widgets = widgets;
This must be set before the app renders so Puppeteer can read it after a simple page load without waiting for user interaction.
3.2 Write frontend/scripts/export-widget-schemas.mjs:
import puppeteer from 'puppeteer'
import { writeFileSync } from 'fs'
import { resolve, dirname } from 'path'
import { fileURLToPath } from 'url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const browser = await puppeteer.launch()
const page = await browser.newPage()
// Suppress console noise from the app
page.on('pageerror', () => {})
await page.goto('http://localhost:3000', { waitUntil: 'networkidle0' })
const schemas = await page.evaluate(() => {
const widgets = window.__nagelfluh_widgets
if (!widgets) throw new Error('__nagelfluh_widgets not found — is the dev server running?')
const result = {}
for (const [name, Widget] of Object.entries(widgets)) {
result[name] = {
title: Widget.title ?? name,
schema: Widget.get_schema ? Widget.get_schema({}) : null,
default: Widget.get_default ? Widget.get_default({}) : null,
}
}
return result
})
await browser.close()
const outPath = resolve(__dirname, '../../backend/widget_schemas.json')
writeFileSync(outPath, JSON.stringify(schemas, null, 2))
console.log(`Written to ${outPath}`)
3.3 Add puppeteer as a dev dependency:
npm install --save-dev puppeteer
3.4 Add script to frontend/package.json:
"export-schemas": "node scripts/export-widget-schemas.mjs"
3.5 Run npm run export-schemas with the dev server running, verify
backend/widget_schemas.json looks correct, and commit it.
Phase 4 — AGENTS.md discipline note
Add or update AGENTS.md at the repo root with the following section:
## Widget Schema Discipline
`backend/widget_schemas.json` is a **committed generated file**. It contains the JSON
Schemas for all widget types (including PlotView layer types assembled by gladly at
import time) and is read by the backend to assemble the full workspace JSON Schema
served by the `get_workspace_schema` MCP tool.
**You must regenerate and commit this file whenever you:**
- Add a new widget to `frontend/src/App.js`
- Add or modify a layer type schema in `frontend/src/widgets/PlotView/elements/`
- Change `get_schema()` or `get_default()` on any widget
To regenerate:
1. Ensure the dev server is running (`./runall.sh`)
2. `cd frontend && npm run export-schemas`
3. Commit `backend/widget_schemas.json` alongside your code changes
File Checklist
| File | Change |
|---|---|
backend/routers/workspaces.py |
Add "Workspaces" tag (create/list/get only), add /workspace-schema endpoint, improve docstrings |
backend/main.py |
Add "Workspaces" to include_tags |
backend/widget_schemas.json |
New committed generated file |
frontend/src/App.js |
Expose window.__nagelfluh_widgets |
frontend/scripts/export-widget-schemas.mjs |
New Puppeteer export script |
frontend/package.json |
Add puppeteer devDep, add export-schemas script |
AGENTS.md |
New or updated — discipline note for schema regeneration |