[DO NOT MERGE] - POC of MMGIS Agent Design - #229
Open
ajinkyakulkarni wants to merge 70 commits into
Open
Conversation
Design for MCP-driven MMGIS: agent control surface (REST + WebSocket agent bridge), NL-to-dashboard generation on top of the mission config generator, and phased NL plugin scaffolding.
The /add route only checked req.session.permission, so long-term-token requests (used by the MCP server) were always rejected with 403 even when the token's creator was a SuperAdmin. Mirror the pattern already used by checkMissionPermission: allow when the session permission is 111 or when req.isLongTermToken is true and req.tokenUserPermission is 111. Also add mcp client tests for upsertMission and the transport-failure path (fetch throws), both of which were already implemented correctly in mmgisClient.ts.
- Add degradation hint to non-OK STAC response in stacFetch - Encode asset, rescale, colormap in query string via encodeURIComponent - Add tests for non-OK status hint and parameter encoding
…ing PORT getWsPath() only checked ENABLE_MMGIS_WEBSOCKETS, unlike essence.js's bootstrap which also gates on !isStaticBuild() and PORT being set. On a static export or with PORT unset, AgentBridge would otherwise retry a doomed connection every 10s forever. Also adds set_time success-path coverage and broadens the get_view_state test to cover layersOn/currentTime.
Overlapping sendCommand calls issued before the websocket finished connecting each spawned their own WebSocket, orphaning all but the last handle. Memoize the in-flight connect() promise on a private connecting field so concurrent callers await the same socket. Also adds a connect-failure test asserting the ENABLE_MMGIS_WEBSOCKETS hint surfaces as an MMGISError.
ToolController_ (classic) never runs when a mission's msv.mode is 'modern' - only ToolControllerModern_ does, wired behind window.mmgisAPI's show/hide/load-plugin API. commands.js was calling the classic controller unconditionally and reporting ok:true regardless of whether anything actually happened. Replace the ToolController_ dependency with a mode-aware ToolAdapter built in AgentBridge.js: classic missions still drive ToolController_.makeTool, modern missions drive window.mmgisAPI.showPlugin/loadPlugin based on ground-truth isPluginLoaded/isPluginHidden state. open_tool now returns ok:false with "Unknown or unopenable tool: <name>" whenever activation can't be confirmed, in either mode.
dashboard_generate ran the full (expensive) profile-build and config
generation before finding out /api/configure/add would reject the
mission name. Mirror configs.js's add() character/prefix rule
(mcp/src/tools/dashboard.ts) and validate missionName first, returning
{error, hint} without touching the client. The rule is now also
documented in dashboard_profile_schema and the missionName zod
.describe().
Also: dashboard_generate silently produced a config with an unrendered
basemap when MAPBOX_TOKEN was unset. Detect whether the generated
config needed {{MAPBOX_TOKEN}} before resolving placeholders, and
surface a warnings: [...] entry in the success payload when the token
is missing.
A non-JSON response body (e.g. hitting a proxy or login page instead
of the MMGIS API) made res.json() throw a raw SyntaxError with no
actionable context. Wrap it and raise MMGISError('MMGIS returned a
non-JSON response for <path>', ...) with a hint pointing at MMGIS_URL.
JSON.parse(STAC_CATALOGS) alone doesn't guarantee the {name: url}
string map shape stac.ts assumes - an array or an object with
non-string values parsed fine but broke confusingly downstream. Add a
shape assertion and throw an existing-style Error naming the expected
shape when it doesn't hold.
The connect-phase once('error', ...) listener stays attached (it never
fired) for the lifetime of the socket, but nothing cleared this.ws
after a post-open error, so a later sendCommand could keep reusing a
dead socket. Attach a persistent on('error', ...) once the socket
opens that nulls this.ws (and this.connecting), so a later error can't
leave the client stuck and the next command transparently reconnects.
The relay note undersold the exposure: API/websocket.js broadcasts every frame to every connected client with no per-mission routing, so any peer can issue commands for and read view-state acks from any mission - not just "unauthenticated upstream". Spell that out, plus the multi-session semantics (all sessions on a mission execute each command; first ack wins) and that presence frames are broadcast but unconsumed (reserved for Phase 2). Also update the view_open_tool checklist line, which hedged on ToolControllerModern_ wiring that has since landed.
Resolve tool names to their config js id before hitting the id-keyed mmgisAPI show/load/isPluginLoaded/isPluginHidden calls and the classic ToolController_.toolModules/makeTool lookup, since mission configs expose the display name while those APIs are keyed by js. Add a dependency-free resolveToolId() in commands.js so the resolution logic is unit-testable, and use it from both the modern and classic branches of buildToolAdapter(). Also guard the persistent post-open websocket error handler in mcp/src/bridge.ts so it only clears this.connecting when the erroring socket was still the current one, preventing it from nulling out a newer in-flight reconnect's promise.
…anging requests on DB query errors
Modern dashboards have no websocket client of their own (only classic essence.js auto-applies/RELOADs on config broadcasts), so config edits made through the MCP tools (mission_update_config, layer_add/update/ remove, tool_toggle) silently required a manual page reload to show up. AgentBridge already holds a websocket connection in every mode, so give it a pure, unit-tested predicate (shouldReloadForFrame) to recognize forceClientUpdate broadcasts for the current mission and debounce a window.location.reload() so bursts of edits coalesce into one reload. Also updates the MCP edit-tool refresh note, README, and chat system prompt wording to reflect that config edits now apply live instead of requiring a manual RELOAD/view_reload call.
Classic missions already live-apply layer frames natively via essence.js, so reloading on a config-mutation broadcast there is redundant and destructive to unsaved local UI state. Extract the mode check already used by buildToolAdapter into isModernMission() and require it before scheduling the debounced reload.
Previously ingesting under a name that already existed silently replaced all its features. Check geodatasetEntries() first and return a needsConfirmation preview (with the feature count that would be lost) unless confirm: true was passed; brand-new names still ingest in one call.
chat/README.md's E2E checklist said 14 tools; the MCP server actually exposes 28 (admin 10, edit 5, view 6, catalog 3, dashboard 4). Document two more pre-existing security caveats in mcp/README.md: any relay peer can forge a config-mutation frame to force-reload modern-mode sessions of any mission (same unauthenticated-relay family as the existing view-command caveat), and /api/accounts/entries and /api/accounts/update are reachable by any valid long-term token, not just admin ones.
- edit.ts: make LIVE_NOTE/RELOAD_NOTE mode-honest (classic applies live vs. shows RELOAD; modern auto-reloads via AgentBridge; view_reload is a manual fallback either way), matching the AgentBridge modern-only gate. - edit.ts layer_add: error early with a clear hint when layer.name is missing instead of silently inserting an unnamed layer entry; add a test. - server.js: return after failureCallback() in validateLongTermToken's inner catch so a malformed query result can't also fall through and double-invoke a callback. - agentLoop.js SYSTEM_PROMPT: clarify that provided config JSON for an EXISTING mission should go through mission_update_config as a merge patch, reserving dashboard_create_from_config for new missions.
- stac.ts: follow rel=next links in searchCollections (bounded at 10 pages) so paginated STAC catalogs like VEDA don't silently drop collections before keyword filtering. - Standardize the mission-name tool arg to `mission` across edit.ts, admin.ts (mission_delete), and dashboard.ts (schema arg only; maps to DashboardSpec.missionName internally). - Add a `hint` telling the model to retry with confirm: true on every needsConfirmation result (mission_delete, geodataset_delete, geodataset_ingest overwrite, user_create, user_set_permission). - mission_delete now pre-checks the mission exists via listMissions before preview or destroy, since the backend returns success for a nonexistent mission. - view.ts: clarify the mission arg must be the exact mission_list name (case and spaces matter).
Add tools/result.ts's wrap() helper and route every admin/catalog/dashboard/ edit tool handler through it instead of a per-handler try/catch, and factor admin.ts's five confirm-preview payloads through a shared confirmationNeeded() helper. geodataset_ingest now only re-fetches the geodataset list when a preview is actually needed (confirm !== true).
Add API/Backend/Utils/permissions.js's isSuperAdminRequest() and use it in configs.js's /add guard and both signup-gate clauses in users.js (behavior unchanged; the users.js clauses are the negation of the same check). Also factor scripts/server.js's repeated long-term-token -> req field mapping (ensureAdmin, ensureUser, annotateLongTermToken) into one decorateReqFromTokenData() helper.
- chat/lib/agentLoop.js: drop the redundant parsed = null reassignment in
the JSON.parse catch block.
- chat/public/app.js: add an appendAndScroll() helper and use it in
addBubble, addToolCard, and the streaming-text scroll update.
- AgentBridge.js: factor the repeated ok ? {ok:true,...} : {ok:false,...}
shape in the modern openTool branches into a small toOpenResult() helper.
🤖 Version Auto-BumpedThe version has been automatically incremented to This commit was added to your PR branch. When you merge this PR, the new version will be included. If you want a different version, update |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
A working prototype of agent-driven MMGIS: describe a spatial dashboard in plain English and get a real MMGIS mission — built from live NASA data, editable by conversation, with the agent able to drive an open map session.
Three pieces, each usable on its own:
mcp/chat/AgentBridgeTry it in ~5 minutes
Then ask it: "build me an air quality dashboard" → it searches NASA's VEDA catalog, lists candidate datasets with coverage, asks which you want, builds the mission, and opens it in the right-hand panel. Follow with "set the NO2 layer opacity to 0.15" or "fly the map to western Europe".
mcp/README.mdandchat/README.mdcover setup, every tool, and a manual E2E checklist.How it works
Design principle throughout: wrap what MMGIS already has. Tools call the same REST endpoints the Configure page calls, shell out to the repo's own
scripts/generate-mission-config.js, and ride the existing websocket relay — ~1,400 lines total. No LLM calls inside the MCP server; it exposes deterministic typed operations and the client model does the language work, which is why the same tools serve the chat app and Claude Code equally.Please scrutinize: backend changes
The bulk of this PR is new, isolated directories. Four small changes touch existing backend code and deserve the most review attention:
API/Backend/Config/routes/configs.js—/api/configure/addnow also accepts a SuperAdmin long-term token, not just a session. Without this, token-based mission creation is impossible. Mirrors the existingcheckMissionPermissionpattern.scripts/server.js— new non-blockingannotateLongTermTokenmiddleware (validates a token if present, always callsnext()), plus a.catch()onvalidateLongTermToken's DB query that was missing (a rejected query previously hung the request), and a shareddecorateReqFromTokenDatahelper.API/Backend/Users/routes/users.js— the signup gate additionally accepts a SuperAdmin token. Strictly additive: requests without anAuthorizationheader behave exactly as before.API/Backend/Utils/permissions.js(new) — sharedisSuperAdminRequest(req)so the check has one definition.Testing
mcp96,chat30, root suite 814 (was 812 before this branch).confirm: trueon retry, an inconsistentmission/missionNameparameter, and hallucinated mission-name variants. All fixed and re-verified.Known limitations (why this is a POC)
/api/configure/cloneand/destroy, and the/api/accountsendpoints, have no per-permission check upstream — any valid long-term token can invoke them. Pre-existing MMGIS behavior that this surface makes easier to reach; documented inmcp/README.md.X-Frame-Optionsrelaxed; fine in dev, a deployment decision in production.mission_clonedepends on apythonbinary on the MMGIS host.Design docs
Specs and implementation plans are included under
docs/superpowers/for reviewers who want the reasoning: the umbrella design, the chat UI design, and the chat-driven-configuration design, each with its task-level plan.Demo
MMGIS-Chat-Demo-Narrated.mp4
2:19, narrated — recorded against a live deployment: a vague "air quality dashboard" request → the agent listing real NASA datasets and asking which one → the dashboard building with every tool call visible → a live layer edit (including the agent self-correcting from an error hint) → the map flown to western Europe → the config JSON round-trip → the delete-confirmation guardrail. Ends with an animated architecture walkthrough and how the 28 tools were built.