Model Context Protocol (MCP)
FrontPanel MCP is an MCP server for the apps you build on FrontPanel Platform. The instrument or test tool you ship to your customers — the one that talks to your gateware on an Opal Kelly FPGA — gains an extra path: an AI assistant can read its state and operate its controls the same way a human user would.
You annotate your app — say which UI actions and gateware operations you want exposed — and FrontPanel Platform handles the rest: the local HTTPS server, the port, schema validation, sandboxing, credentials, and the wire protocol. Your job is to describe the surface; the Platform delivers it to the AI client.
From an implementation perspective, the construction follows this mental model: Each UI control in your app invokes a handler. With MCP enabled, those same handlers are also exposed as tools an AI client can call. One handler, two callers: the user and the AI.
Motivation
Test and measurement work involves long sequences of parameter changes, captures, and register reads — repetitive work that AI assistants are well-suited to, provided they can reach the instrument.
Letting an AI drive an instrument has historically meant building a custom server: choosing a transport, designing a protocol, adding authentication, documenting it for clients, and maintaining it. That is a sizable project on its own.
FrontPanel MCP removes that work. The Model Context Protocol — the open standard AI clients already use — is built into FrontPanel Platform. Your app declares what it exposes; the Platform handles transport, sandboxing, schema validation, audit, per-app permissions, and the wire protocol. You write the part specific to your hardware: what each tool does.
Use Cases
Bench Operations
“Set the function generator to 1 MHz square wave at 2 V, capture 1 ms, FFT it, and report the dominant harmonic.”
Your desktop AI translates the request into a sequence of tool calls. UI controls move in real time and results return in chat.
Bring-up and debug
“This board isn’t initializing. Walk through what the firmware sees.”
The AI reads device status, toggles diagnostic registers, and explains the readings using the tools the app already provides.
Engineer Scripting
“Sweep the bias from 0 to 5 V in 100 mV steps, log the counter at each step, save a CSV.”
Claude Code writes a JavaScript snippet, runs it in the Platform’s sandboxed scripting environment, iterates on the output, and saves the final version as a reusable fixture.
Semiconductor Evaluation Kits
A semiconductor vendor ships a DAC or ADC evaluation board as a SYZYGY peripheral that plugs into the Opal Kelly SZG-HUB1450 host board. The vendor writes the gateware that drives their part, pairs it with a FrontPanel Platform app for the operator UI, and ships the result as an .fpp their customers install alongside FrontPanel Platform.
With MCP enabled on that .fpp, the evaluating engineer can drive the eval board through an AI assistant in addition to the UI. The vendor’s gateware exposes the operations a characterization session actually needs — set DAC code, capture ADC samples, run a standard SNR or INL sweep, read on-chip register state — and the AI orchestrates them in response to natural-language requests such as:
“characterize linearity from 0 to full scale at 1 mV resolution” or “compare measured SNR to the datasheet at three reference voltages.”
For the vendor, this is leverage. The same gateware and tool surface serves an engineer clicking through the UI, a customer driving an AI-led characterization, and a regression script in a CI pipeline — all from the same .fpp, with no separate test harness or scripting layer to ship and maintain.
Lab Automation
An AI-driven test runner uses the same tool surface a human operator uses. No second binary, no separate firmware server, no extra trust boundary.
Customer Support and Tutorials
An AI walks a new user through their first capture, observing live device state and acting on it with the user’s approval.
Reproducible Measurements
An MCP-driven script is a concise record of how a measurement was taken, and can be re-run on a different unit or bench to reproduce the result.
System Components
Four pieces sit on the path between your gateware and the AI. You write two of them; FrontPanel Platform supplies the rest.

Component-by-component
| Component | Who provides it | Role |
|---|---|---|
| AI client | Off-the-shelf | Claude Desktop, Claude Code, Cursor, or any MCP-compatible client. The user configures it once to point at FrontPanel Platform’s local endpoint. |
| FrontPanel Platform | Opal Kelly | The desktop app your customers already use to launch FrontPanel apps. With MCP enabled, it also exposes a local HTTPS server, validates schemas, multiplexes multiple apps for one client, and writes an audit log. |
| Your app | You | The instrument or test UI you ship as a .fpp. It declares its MCP surface in two files (the manifest and mcp.json) and registers handler functions at startup. |
| FPGA + gateware | You / your customer | Unchanged. The MCP layer adds a second invocation path on top of your existing software stack — the gateware doesn’t see anything new. |
MCP Manifest
Two small files describe your app’s MCP surface. Together they tell FrontPanel Platform what to expose and how to validate calls coming in.
App Manifest – Opt-in and Permissions
The app manifest your app already ships gets a new permissions.mcp block. This is the gate: without it, MCP for your app is off, full stop.
{
"appGuid": "...",
"name": "Counters",
"permissions": {
"mcp": {
"bindings": true, // expose this app's tools to AI clients
"script": true // allow run_javascript for batched calls
}
}
}Code language: JSON / JSON with Comments (json)Two independent switches:
bindings— expose the tools you declare inmcp.json. This is the typical “AI can drive my app” switch.script— additionally expose arun_javascripttool that lets the AI batch several of your tools into one local execution. Big efficiency win for multi-step workflows; off by default if you’d rather keep the surface narrow.
The Tool List (mcp.json)
Sits next to your app source. It’s the contract you ship to AI clients. Each entry names a tool, describes it in one sentence (the AI reads this to decide when to call it), and specifies a JSON Schema for the inputs.
{
"tools": [
{
"name": "counter1_set_enabled",
"description": "Enable or disable counter 1.",
"inputSchema": {
"type": "object",
"properties": { "enabled": { "type": "boolean" } },
"required": ["enabled"],
"additionalProperties": false
}
},
{
"name": "counter1_read",
"description": "Read the current value of counter 1.",
"inputSchema": { "type": "object", "additionalProperties": false }
}
]
}Code language: JSON / JSON with Comments (json)At app launch, your code registers a handler function for each declared name. The Platform enforces the relationship in both directions: a tool declared but never registered is invisible, and a name your code tries to register that isn’t in mcp.json is rejected. The manifest is the upper bound; the registered set is the truth.
Those two files describe the surface. The behavior of each tool — what it does to your gateware — lives in your normal app code.
Using a Desktop AI Client
Desktop AI (e.g. Claude Desktop or ChatGPT) is an example front-end for an operator at the bench. The user describes an intent in natural language, the AI plans a sequence of tool calls, and the controls in your app move accordingly.
- Launch the app. Open FrontPanel Platform, launch your MCP-capable app, and grant it MCP permission when prompted.
- Connect Claude Desktop. A one-time step: add FrontPanel Platform’s local MCP endpoint to Claude Desktop’s MCP server list. The Platform’s settings dialog shows the URL and provides a copy button.
- Operate the device. “Show me the current counter values.” “Reset counter 2 and watch it for ten seconds.” The AI selects the appropriate tool, runs it, and returns the result.
Each tool call is recorded in the Platform’s Activity window, and the operator can interrupt or override at any time.
Where the desktop client works well. Long sequences of parameter changes, parameter sweeps, and conditional waits (“watch this value until it stabilizes”) — workflows that are tedious to drive by hand but easy to describe in a sentence.
Using a Coding Tool (Claude Code)
Claude Code uses the same MCP endpoint and the same tools, but the workflow shifts toward code: the user is in a terminal, and the AI is writing scripts rather than chatting through individual calls.
The key feature in this mode is the run_javascript tool (the script permission from earlier). Instead of issuing one MCP tool call per step, the AI writes a JavaScript program that calls your tools in sequence — locally, inside a sandboxed scripting context provided by FrontPanel Platform — and returns one consolidated result.
// What Claude Code might write and run via run_javascript:
await mcp.tools.waveform_set_frequency({ hz: 1_000_000 });
await mcp.tools.waveform_set_amplitude({ volts: 2.0 });
await mcp.tools.digitizer_capture_once({ duration_us: 1000 });
const samples = await mcp.tools.digitizer_read_samples();
return analyzeFFT(samples);Code language: JavaScript (javascript)This is one round-trip on the MCP wire instead of five. For a sweep with hundreds of points, the difference is significant.
The coding-tool workflow also produces an artifact the desktop flow does not: a script that can be saved, refined, version-controlled, and replayed later as a regression test.
Typical Workflows
- “Write a script that sweeps X.” Claude Code drafts the script, runs it via
run_javascript, iterates on the output, and writes the final version to a file in your repo. - “Why is this measurement noisy?” Claude Code probes device state, plots the data, and uses the same diagnostic tools available to the support team.
- “Add a regression test for this fix.” The script the AI just ran becomes the basis of a new automated test, checked in alongside the gateware change.
JavaScript Scripting
When an app declares permissions.mcp.script, FrontPanel Platform synthesizes one extra tool — run_javascript — alongside the tools the app declared in mcp.json. The AI calls it like any other tool, passing a string of JavaScript source. The Platform runs that script in a sandboxed renderer dedicated to the app, and the script can call the app’s other tools as ordinary await mcp.tools.<name>(args) JavaScript expressions.
The script runs locally — every inner tool call is an in-process IPC, not an MCP wire round-trip — but the resulting calls travel the same path as any other tool call. UI clicks, direct AI tool calls, and inner script calls all converge on the same shared handler and post to the same per-device WorkQueue.
Script Sandbox
The sandbox exposes a single object — mcp.tools — passed in as a parameter to the AI’s code body. It is a plain JavaScript object whose keys are the names of every tool the app registered (e.g. counter1_read, digitizer_capture_once, and so on), and whose values are async functions. Each function takes the tool’s argument object and returns a Promise that resolves with the tool’s normal return value or rejects with the error the tool produced. The script can call them in any order, branch on results, accumulate data, and return any JSON-serializable value — that return value is what the AI receives as the run_javascript tool result.
The execution environment is deliberately minimal:
- No DOM, no
fetch, no Node APIs. - No filesystem, no network, no access to other apps or to the launcher.
- No reach into the app renderer’s React state — the script can only observe app state through tools the app chose to expose.
mcp.toolsis passed in as a parameter rather than living onglobalThis, so prototype tampering inside the main world cannot redirect it.
Standard JavaScript built-ins (Math, Array, Promise, JSON, etc.) are available, so any computation the AI wants to do on tool results — averages, peak finding, FFT post-processing, conditional control flow — happens locally in the sandbox without further round-trips.
Convergent Paths at the Work Queue
The WorkQueue is a per-device FIFO that serializes everything the app sends to the FrontPanel SDK. Without it, a UI click, a direct AI tool call, and an inner call from a running script could interleave on the wire in arbitrary order — corrupting device state in ways that look like sporadic firmware bugs.
Because every invocation path eventually posts to the same queue, the device sees one well-ordered stream of operations regardless of who issued them. This is also why integration guidance asks app developers to lift handlers to the root and have UI controls call them: the UI button and the MCP tool are two callers of one function, not parallel implementations of the same behavior.
Trust & Control
Access is controlled at three layers, all visible to the user.
- Per-app opt-in. An app without
permissions.mcpin its manifest is invisible to AI clients. There is no default-on; apps that do not want this surface are not part of it. - Per-app override in the Platform. An app that requests MCP can be denied in Platform’s MCP Settings. The override is revoke-only: the user can narrow what an app receives, never expand it past what the manifest declared.
- Live activity window. Every tool call appears in real time, with the app, tool, arguments, outcome, and duration. Recent history is retained in a rolling audit log.
Tool calls pass through schema validation, a per-call timeout, and (for scripted calls) a sandboxed renderer with no DOM, network, or filesystem access. The AI’s reach is bounded to the tools you declared: it cannot browse the disk, communicate with other apps, or extend its surface mid-session.
What the Platform does not enforce. The Platform validates that only declared tools with valid arguments are called, but it does not constrain what those tools do. If erase_flash is exposed as a tool, the AI can call it. Choose the exposed surface with the same care you would apply to a public API.
Getting Started
If you have an existing FrontPanel app and want to make it MCP-capable, the path is:
- Confirm the app’s structure. MCP integrates most easily with apps that already follow the Driver / WorkQueue / shared-handler pattern. If UI controls currently use inline handlers, those handlers should be lifted to the root first. This is the largest part of the work and improves the UI path independently of MCP.
- Choose the tool surface. List the reads, writes, and one-shot actions an AI should be able to perform. This list is the contract that AI clients will see.
- Add three files and modify two. Declare opt-in in
frontpanel-app.json, list tools inmcp.json, implement bindings inMCPBindings.ts, and register them at app startup. The full step-by-step is in the developer guide. - Verify with a real client. Connect Claude Desktop or Claude Code, list the tools, invoke a write tool from the AI, and confirm the corresponding UI control updates. A round-trip from AI to Platform to handler to gateware and back, with the UI in sync, confirms the integration.
New apps can start from a Platform-app scaffold that ships with the MCP wiring in place. Existing apps follow the integration guide section by section. Two reference apps illustrate the pattern at different scales — see Reference apps below.
Connecting an AI Client
Each AI client — Claude Desktop, Claude Code, Cursor, and others — has its own way of registering an MCP server, and the config formats change over time. Rather than maintain per-client setup walkthroughs in this guide, the Platform’s MCP Settings dialog offers a one-click “Copy connection prompt” action: it generates a short, paste-ready prompt containing the live endpoint URL, transport type, and any configured credentials. The user pastes that prompt into their AI client and the AI itself walks them through the client-specific setup.
For example, the prompt might look like this:
I have a Model Context Protocol (MCP) server running locally that exposes tools for hardware running on Opal Kelly’s FrontPanel Platform. The MCP server is reachable at https://localhost:3737/mcp over Streamable HTTP. Can you walk me through the steps required to connect you to this local MCP server?
and the response from Claude Code:
Sure — Claude Code supports MCP servers, including Streamable HTTP endpoints
like the one you've described. You have two options: the <span style="background-color: initial; font-family: inherit; font-size: inherit; text-align: initial; white-space: pre-wrap; color: initial;">claude mcp CLI</span>
<span style="background-color: initial; font-family: inherit; font-size: inherit; text-align: initial; white-space: pre-wrap; color: initial;">or a config file.</span>
<strong>Option 1 — CLI (quickest)</strong>:
claude mcp add frontpanel --transport http https://localhost:3737/mcp
That registers the server under the name frontpanel for the current project.
To make it available in every project, add --scope user:
claude mcp add frontpanel --scope user --transport http https://localhost:3737/mcp
<strong>Option 2 — .mcp.json (project-scoped, version-controllable):</strong>
Create or update .mcp.json at your project root:
{
"mcpServers": {
"frontpanel": {
"type": "http",
"url": "https://localhost:3737/mcp"
}
}
}
<strong>Verify:</strong>
claude mcp list
You should see frontpanel listed. Inside a session, ask me to list available
tools — you should see entries prefixed app:<runtimeId>: for each FrontPanel
app you have running.
One gotcha: https://localhost typically uses a self-signed cert. If you see
TLS errors, the cleanest fix is to trust the FrontPanel Platform certificate
in your system trust store. Setting NODE_TLS_REJECT_UNAUTHORIZED=0 works as
a quick test but should not be left on.Code language: plaintext (plaintext)Reference Apps
Two MCP-capable apps ship as worked examples. Both are fully functional FrontPanel Platform apps; both have their MCP integration wired through the same patterns described in the developer guide. Use them as templates and as reference material when designing your own tool surface.
Counters
Counters drives two simple peripherals on the FPGA: an autonomous counter with an enable line, and a stepwise counter with an optional auto-count mode. It is the smallest useful MCP-capable app and is the recommended starting point for learning the pattern. The MCP surface is six tools across the two counters.
| Tool | Kind | Description |
|---|---|---|
counter1_reset | action | Pulse the reset line for counter 1, returning it to zero. |
counter1_set_enabled | write | Enable or disable counting on counter 1. When disabled the counter holds its current value. |
counter1_read | read | Read the current value of counter 1. |
counter2_step | action | Step counter 2 by one in the chosen direction ("up" or "down"). |
counter2_set_auto_count | write | Toggle counter 2’s autonomous-count mode. |
counter2_read | read | Read the current value of counter 2. |
Example Session — Claude Desktop
A short conversational interaction with Counters. The user’s requests are in italics; the AI’s tool calls and the values returned are shown beneath each one.
User: <em>What are the counters reading?</em>
→ counter1_read() → 4218
→ counter2_read() → 17
AI: Counter 1 is at 4218 and counter 2 is at 17.
User: <em>Stop counter 1 and reset it, then step counter 2 down by five.</em>
→ counter1_set_enabled({ enabled: false })
→ counter1_reset()
→ counter2_step({ direction: "down" }) × 5
→ counter1_read() → 0
→ counter2_read() → 12
AI: Counter 1 is held at 0; counter 2 is now at 12.Code language: plaintext (plaintext)Each tool call in the trace also produces a UI-side update — the user watches the on-screen counter values change in real time as the AI works through the request.
FFTScope — Multi-Instrument Coordination
FFTScope is a software-defined signal-chain demo: a waveform generator drives an output, a two-channel digitizer captures the result, and a spectrum / spectrogram view displays the FFT. It is the larger of the two reference apps and is useful when designing an MCP surface that spans several cooperating instruments. The MCP surface is 21 tools across three subsystems.
Waveform Generator (7 Tools)
Eight independent frequency-vector slots are summed by an IFFT engine. Each slot has an FFT bin number, a dBFS amplitude, and an enable flag.
waveform_initialize— bring the IFFT engine up to ResetComplete and replay saved vectors.waveform_reset— clear all eight slots and silence the output.waveform_set_vector— configure one slot (vector_id,bin,amplitude_dbfs,enabled).waveform_set_autoscale— enable IFFT-clip protection (sum of amplitudes clamped to ≤ 1.0).waveform_get_vectors— read the current state of all eight slots.waveform_get_sample_rate— read DAC sample rate, IFFT length, and per-bin frequency spacing.waveform_get_amplitude_scale— read the active autoscale divisor.
Digitizer (7 Tools)
Two-channel ADC capture, with both a continuous-refresh loop and a single-shot mode.
digitizer_reset— reset the digitizer state machine.digitizer_set_channel_enabled— show or hide a channel’s trace.digitizer_set_continuous— start or stop the periodic capture loop (≈ 35 ms refresh).digitizer_capture_once— capture one 1024-sample frame on both channels.digitizer_read_samples— read the most recent capture as raw signed 16-bit ADC values.digitizer_get_sample_rate— read the ADC sample rate in Hz.digitizer_get_channel_enabled— read whether a channel is currently displayed.
Spectrum / Spectrogram (7 Tools)
FFT-domain display of the most recent capture, with a switchable spectrogram view.
spectrum_set_mode/spectrum_get_mode— switch between live spectrum and rolling spectrogram.spectrum_set_noise_floor/spectrum_get_noise_floor— spectrogram floor in dBFS.spectrum_set_gamma/spectrum_get_gamma— spectrogram contrast curve.spectrum_read_latest— read the most recent FFT magnitudes (512-element dBFS array per channel).
Example Session — Claude Code with Scripting
FFTScope is the canonical example of where the script tool earns its keep: configure a tone, capture, locate the peak, report. The AI writes a short script and submits it as one MCP call.
User: <em>Drive a 5 kHz tone at -6 dBFS, capture, and tell me which FFT bin holds
the peak and what its level is.</em>
The AI writes and runs:
const { sample_rate, fft_length } = await mcp.tools.waveform_get_sample_rate();
const targetHz = 5000;
const bin = Math.round(targetHz * fft_length / sample_rate);
await mcp.tools.waveform_reset();
await mcp.tools.waveform_set_vector({
vector_id: 0, bin, amplitude_dbfs: -6, enabled: true,
});
await mcp.tools.digitizer_capture_once();
const { channel1 } = await mcp.tools.spectrum_read_latest();
let peakBin = 0, peakDb = -Infinity;
for (let i = 1; i < channel1.length; i++) {
if (channel1[i] > peakDb) { peakDb = channel1[i]; peakBin = i; }
}
const peakHz = peakBin * sample_rate / fft_length;
return { peakBin, peakHz, peakDb };
AI: The peak is at bin 51 (4980 Hz) at -6.2 dBFS, which matches the
requested 5 kHz tone within one bin of resolution.Code language: JavaScript (javascript)The script makes seven tool calls but uses only one round-trip on the MCP wire — a meaningful difference for sweeps or any iterative measurement. The script body itself is a useful artifact: the engineer can refine it, parameterize the frequency, and check it in as a regression test.
Next Steps
Start with Counters when learning the integration pattern: it is small enough to read end-to-end in one sitting, and the six tools cover the three tool kinds (read, write, action) without distraction. Move to FFTScope when designing for an app that has multiple cooperating instruments, several state machines, or a tool surface large enough that naming conventions and grouping matter.