nl-view-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@nl-view-mcpshow OEE by line for the current shift"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
nl-view-mcp
Viability prototype for a FieldWorks question: can a Knowi/ThoughtSpot-style
"one natural-language instruction builds a dashboard widget" pattern work as
an MCP-native, fieldworks-core consumer, using an MCP Apps-inspired ui://
resource as the declarative UI layer instead of agent-generated rendering
code?
This is a throwaway viability prototype, not production code. It is fully standalone: no dependency on waterworks-ai or any live plant infrastructure. Synthetic OPC-UA-shaped telemetry lives in a local SQLite file.
Status
Prototype complete. See Findings below for the viability verdict.
Related MCP server: Generative UI MCP
Design decisions worth knowing before reading the code
ui://is a naming convention here, not the literal MCP Apps spec. The real MCP Apps spec serves HTML rendered in a sandboxed host iframe. This prototype instead registersui://resources withapplication/jsoncontent — a declarative widget spec, read directly by a small bespoke Vue client. No HTML, no agent-generated code, ever reaches the client. Whether that divergence was the right call is answered in Findings below (short version: marginal, leaning no, for a payload this small).One MCP tool only:
build_view(instruction: str). Schema discovery and chart-type heuristics are internal steps inside that tool, not separate MCP tools an LLM has to sequence.Chart-type is decided by heuristic first (time series numeric -> line, categorical + counts -> bar, single scalar -> stat); an LLM call (Claude Haiku) is only a fallback for the ambiguous cases.
Build order
SQLite schema + synthetic telemetry data generator
build_viewtool: heuristics + query execution +ui://resource emissionLLM fallback for heuristic-ambiguous instructions
Vue + Chart.js renderer, wired to the live server
Stretch: synthetic MES tables + an OEE-by-line instruction
Findings write-up (this README)
Running it
uv sync
uv run python generator.py # seeds ./data/telemetry.db: 6h of synthetic telemetry + MES lines/shifts
uv run pytest -q
uv run python server.py # serves build_view over Streamable HTTP on :8010 (CORS open to :5173)In a second terminal:
cd frontend
npm install
npm run dev # http://localhost:5173
npx vue-tsc --build
npx vitest runbuild_view resolves these purely via heuristic, no LLM call:
"show me tank level trends for the last hour" -> line
"compare pump run hours by zone" -> bar
"what's the current flow rate" -> stat
"show OEE by line for the current shift" -> bar (synthetic MES tables -- lines/shifts/production_events -- discovered as
oee-kind pseudo-tags alongside the telemetry tag catalog; no other build_view changes)
An instruction with no kind keyword (e.g. "how's the west side looking") falls
through to the Claude Haiku classifier -- set ANTHROPIC_API_KEY for that path.
Findings
Verdict: the pattern holds up, and is worth building for real -- with two
concrete fixes to carry forward (below) rather than as a wholesale redesign.
The strongest evidence is the MES stretch goal: getting from tank levels to
"OEE by line" took one new discovery function and two one-line additions to
intent.py. Nothing about build_view's discover -> heuristic -> query ->
emit shape had to change. That's the property the whole prototype existed to
test, and it survived contact with a genuinely different domain.
Where the chart-type heuristics got it wrong (or got lucky)
They resolved all four example instructions correctly, three without ever calling an LLM. But that's a narrower result than it sounds:
The heuristics are a fixed phrasing list, not language understanding. Grouping detection only fires on the literal substrings "by zone" / "by line"; "compare across zones" or "per line" would silently miss and fall through to the LLM. Time windows only parse "last N hour(s)/minute(s)" -- "since this morning" doesn't resolve. That's an acceptable shape for a small, operator-facing instruction vocabulary that gets reused constantly (which is realistic for a plant HMI), but it's a keyword router wearing a heuristic's clothes, not a general fallback-avoidance strategy.
The bigger risk is confident wrongness, not visible ambiguity.
_match_kindis first-match-wins overKIND_SYNONYMS. An instruction naming two kinds ("show tank level and flow rate together") would silently produce a single-kind chart instead of surfacing the ambiguity -- the "fall back to the LLM" safety net only catches the no kind matched case, not the wrong kind matched confidently case. This never came up in the four examples because none of them are multi-kind, which is itself the finding: the heuristic's blind spots are invisible until you instruct outside the set it was tuned against.The one deliberately-ambiguous instruction we exercised ("how's the west side looking") worked, but that's one data point on the LLM fallback path, not a stress test of it.
Was ui:// the right layer?
Mechanically, yes -- it worked. Was it worth it? Marginal, leaning no,
for this design specifically. The JSON-native, spec-adjacent ui://
resource added a second MCP round trip (resources/read after
tools/call) plus a slug/store bookkeeping layer (_VIEW_STORE, slug
generation in server.py) that bought nothing the client couldn't get from
the tool's return value directly. The client already knew how to render the
spec the moment it had it; resource-level indirection didn't add capability
or safety on top of that.
ui://'s real justification in the actual MCP Apps spec is that the
resource is genuinely decoupled content -- HTML/JS a generic host fetches,
sandboxes, and mounts independently of the tool call that triggered it.
None of that applies to a small JSON blob one bespoke client consumes once
and never re-fetches. The one thing URI-addressability gives for free --
a stable handle a second client could re-read, or a specific widget could be
refreshed by URI without re-running build_view -- never came up in a
single-session prototype, but it's the one scenario where this would start
to earn its keep.
Recommendation: for a pure-JSON widget spec, return it as the tool's
structured result and skip ui:// entirely, unless there's a concrete
multi-client or independent-refresh requirement. Reserve ui:// for the
case it was actually designed for: content a generic host needs to sandbox.
Schema gap: no semantic type for a point's x value
Found twice, same root cause. Line charts always use Unix-seconds
timestamps and bar charts always use categorical strings, but nothing in
the schema says so -- chartData.ts guesses based on chart_type. The
OEE-by-line bar chart hit it from another angle: x is the raw line id
(line-1), not the display name (Line 1), because the id doubles as the
SQL join key widgets.py needs and the spec has nowhere to carry both an
identifier and a display label. A real version of this schema needs an
explicit axis type and an id/label split, not client-side inference.
Two smaller things worth knowing if this gets built for real
MCP transport is still moving.
SSEClientTransportwas already deprecated by the TypeScript SDK partway through this build, forcing a switch to Streamable HTTP on the server. Expect more of this from an ecosystem this young.CORS is entirely unsolved by the SDK's high-level
mcp.run(). Any browser-facing MCP client has to drop to the raw ASGI app (mcp.streamable_http_app()) and wrap it manually -- a rough edge for the "MCP-native browser app" pattern generally, not specific to this prototype.
This server cannot be deployed
Maintenance
Related MCP Connectors
Generate production-ready chart code (Recharts, Chart.js, ECharts, Plotly) from a prompt.
Renders interactive Chart.js charts and dashboards inline in AI conversations.
Ask data questions in natural language. Get SQL, insights, and charts from your databases.
Ask your app anything — revenue, errors, read-cost, growth — and get rendered charts back.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceTransforms high-level business intents into structured Data Product Requirement Prompts through AI-powered conversational refinement. Guides users through clarifying questions to gather comprehensive requirements for automated Business Intelligence dashboard generation.1Apache 2.0
- AlicenseAqualityDmaintenanceProvides AI models with structured design guidelines and system prompts for creating consistent, high-quality interactive visualizations like charts, diagrams, and mockups. It enables on-demand loading of UI specifications to optimize token usage while ensuring visually polished and functional widget generation.113 npm58MIT
- AlicenseNot gradedqualityDmaintenanceEnables business intelligence chart creation and visualization using Vega-Lite, with data loading and processing capabilities.13Apache 2.0

Sisense MCP Serverofficial
AlicenseNot gradedqualityBmaintenanceEnables LLMs to interact with Sisense data models and create charts programmatically via natural language, supporting tools for data sources, fields, and chart building.37 npm7Cryptographic Autonomy 1.0 (Combined Work Exception)