JouleScope JS220 MCP Server
joulescope-mcp is a Model Context Protocol (MCP) server for the JouleScope JS220 precision energy analyzer. It exposes agent-friendly tools for measuring current, voltage, power, charge, and energy, plus lower-level access to the JouleScope driver PubSub topic tree.
The primary tool is measure_energy: provide a duration and accumulation interval, and it returns total charge and energy plus one sample per interval. For example, duration_s=15 and interval_s=0.5 returns 30 interval samples along with totals such as total_charge_mAh.
Quick Start
Install directly from GitHub with uvx:
{
"mcpServers": {
"joulescope-js220": {
"command": "uvx",
"args": [
"--from",
"git+https://github.com/juanqui/joulescope-mcp",
"joulescope-mcp"
]
}
}
}
If you prefer SSH, use the same Git install shape with the SSH URL:
{
"mcpServers": {
"joulescope-js220": {
"command": "uvx",
"args": [
"--from",
"git+ssh://[email protected]/juanqui/joulescope-mcp.git",
"joulescope-mcp"
]
}
}
}
Then ask your MCP client:
Measure JouleScope power for 15 seconds with 500 ms intervals. Include voltage.
Expected result shape, with compact sample arrays shortened for display:
{
"total_charge_mAh": 0.0051,
"total_energy_mWh": 0.019,
"average_current_mA": 1.23,
"average_voltage_v": 3.70,
"interval_count": 30,
"sample_charge_mAh": [0.00015, 0.00015, 0.00015]
}
Requirements
- Python 3.11 or newer
- JouleScope JS220 connected over USB
uvfor the recommendeduvxinstall path- An MCP client that can run stdio servers
The Python package installs pyjoulescope_driver>=2.1.0 and pyjls>=0.17. On Linux, configure JouleScope udev rules as documented by JouleScope before running the server.
uvx is an alias for uv tool run; it runs Python command-line tools in an isolated environment without a permanent install.
Install Options
Option 1: GitHub with uvx
uvx --from git+https://github.com/juanqui/joulescope-mcp joulescope-mcp
MCP JSON:
{
"mcpServers": {
"joulescope-js220": {
"command": "uvx",
"args": [
"--from",
"git+https://github.com/juanqui/joulescope-mcp",
"joulescope-mcp"
]
}
}
}
Use this for normal installs.
Option 2: GitHub over SSH with uvx
Use this if you prefer SSH or need GitHub SSH authentication:
uvx --from git+ssh://[email protected]/juanqui/joulescope-mcp.git joulescope-mcp
Option 3: Local Checkout
Use this while developing or when you want to pin the MCP server to a local clone:
git clone [email protected]:juanqui/joulescope-mcp.git
cd joulescope-mcp
uv sync --extra dev
uv run joulescope-mcp
Verify that the driver can see the JS220:
uv run python -m pyjoulescope_driver scan
uv run python -m pyjoulescope_driver statistics --frequency 2 --duration 1
Local checkout MCP JSON:
{
"mcpServers": {
"joulescope-js220": {
"command": "uv",
"args": [
"--directory",
"/absolute/path/to/joulescope-mcp",
"run",
"joulescope-mcp"
]
}
}
}
Use an absolute path. Several MCP clients launch servers with a limited PATH; if uv or uvx is not found, replace "command": "uvx" or "command": "uv" with the full executable path from which uvx or which uv.
Client Configuration
Most MCP clients use one of two JSON shapes:
mcpServers: Claude Desktop, Claude Code project config, Cursor, Windsurf, Cline, and many other clientsservers: VS Code / GitHub Copilot MCP config
Choose one install command:
| Current situation | Use this command in client configs |
|---|---|
| Normal GitHub install | uvx --from git+https://github.com/juanqui/joulescope-mcp joulescope-mcp |
| GitHub over SSH | uvx --from git+ssh://[email protected]/juanqui/joulescope-mcp.git joulescope-mcp |
| Local development checkout | uv --directory /absolute/path/to/joulescope-mcp run joulescope-mcp |
The snippets below show the normal GitHub install path.
GitHub replacement:
{
"command": "uvx",
"args": [
"--from",
"git+https://github.com/juanqui/joulescope-mcp",
"joulescope-mcp"
]
}
Local checkout replacement:
{
"command": "uv",
"args": [
"--directory",
"/absolute/path/to/joulescope-mcp",
"run",
"joulescope-mcp"
]
}
Configure only one always-on client for a physical JS220. Many desktop clients auto-start configured MCP servers, and the JS220 should not be shared between multiple MCP server processes.
Timeout Configuration
measure_energy is a blocking hardware measurement. A 15 second measurement takes at least 15 seconds, plus JS220 startup and cleanup time. Configure MCP clients for a 5 minute tool timeout when they expose a timeout setting.
Recommended values:
- Tool call timeout: 300 seconds / 300,000 ms
- Server startup timeout: 60 seconds / 60,000 ms
If a client does not document a timeout setting, keep synchronous measurements short enough for that client or use a client with configurable MCP tool timeouts. A future async measurement API can avoid long single tool calls, but the current measure_energy call is synchronous by design.
Claude Code
Recommended global install:
claude mcp add joulescope-js220 -- uvx --from git+https://github.com/juanqui/joulescope-mcp joulescope-mcp
claude mcp list
Launch Claude Code with 5 minute MCP tool calls and a 60 second server startup timeout:
MCP_TIMEOUT=60000 MCP_TOOL_TIMEOUT=300000 claude
Project .mcp.json:
{
"mcpServers": {
"joulescope-js220": {
"type": "stdio",
"command": "uvx",
"args": [
"--from",
"git+https://github.com/juanqui/joulescope-mcp",
"joulescope-mcp"
]
}
}
}
Inside Claude Code, run /mcp to inspect server status. MCP_TIMEOUT and MCP_TOOL_TIMEOUT are Claude Code process environment variables, not per-server env values.
Claude Desktop
Edit:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"joulescope-js220": {
"command": "uvx",
"args": [
"--from",
"git+https://github.com/juanqui/joulescope-mcp",
"joulescope-mcp"
]
}
}
}
Restart Claude Desktop after editing the file. Claude Desktop does not document a portable per-server timeout field in claude_desktop_config.json; if your launch environment supports MCP timeout environment variables, set MCP_TOOL_TIMEOUT=300000 before starting Claude Desktop.
Codex
Recommended global install:
codex mcp add joulescope-js220 -- uvx --from git+https://github.com/juanqui/joulescope-mcp joulescope-mcp
codex mcp list
Direct ~/.codex/config.toml entry:
[mcp_servers.joulescope-js220]
command = "uvx"
args = ["--from", "git+https://github.com/juanqui/joulescope-mcp", "joulescope-mcp"]
startup_timeout_sec = 60
tool_timeout_sec = 300
The codex mcp add command creates the server entry. Edit ~/.codex/config.toml afterward to add startup_timeout_sec and tool_timeout_sec.
Cursor
Global config:
- macOS/Linux:
~/.cursor/mcp.json - Windows:
%USERPROFILE%\.cursor\mcp.json
Project config:
.cursor/mcp.json
{
"mcpServers": {
"joulescope-js220": {
"type": "stdio",
"command": "uvx",
"args": [
"--from",
"git+https://github.com/juanqui/joulescope-mcp",
"joulescope-mcp"
]
}
}
}
Cursor also supports adding MCP servers from Settings. After changing the config, restart Cursor or refresh MCP tools from the MCP settings panel. Cursor does not currently document a portable mcp.json timeout field; if your Cursor build exposes a request/tool timeout in Settings, set it to 300 seconds.
VS Code / GitHub Copilot Agent Mode
Workspace config:
.vscode/mcp.json
User config:
- Run
MCP: Open User Configurationfrom the Command Palette.
{
"servers": {
"joulescope-js220": {
"type": "stdio",
"command": "uvx",
"args": [
"--from",
"git+https://github.com/juanqui/joulescope-mcp",
"joulescope-mcp"
]
}
}
}
Command-line install:
code --add-mcp '{"name":"joulescope-js220","type":"stdio","command":"uvx","args":["--from","git+https://github.com/juanqui/joulescope-mcp","joulescope-mcp"]}'
VS Code's MCP configuration reference does not document a per-server tool timeout field. Do not add unsupported timeout keys to .vscode/mcp.json; use shorter synchronous measurements if your Copilot host times out long calls.
Windsurf
Edit:
- macOS/Linux:
~/.codeium/windsurf/mcp_config.json - Windows:
%USERPROFILE%\.codeium\windsurf\mcp_config.json
{
"mcpServers": {
"joulescope-js220": {
"command": "uvx",
"args": [
"--from",
"git+https://github.com/juanqui/joulescope-mcp",
"joulescope-mcp"
]
}
}
}
Windsurf Cascade has a total enabled-tool limit. If you use many MCP servers, disable tools you do not need. Windsurf's public MCP docs do not document a portable timeout field in mcp_config.json; if your Windsurf build exposes a request/tool timeout setting, set it to 300 seconds.
Cline
CLI install:
cline mcp add joulescope-js220 -- uvx --from git+https://github.com/juanqui/joulescope-mcp joulescope-mcp
Manual config:
- CLI default:
~/.cline/data/settings/cline_mcp_settings.json - VS Code extension: open the MCP Servers panel, then choose Configure MCP Servers
{
"mcpServers": {
"joulescope-js220": {
"command": "uvx",
"args": [
"--from",
"git+https://github.com/juanqui/joulescope-mcp",
"joulescope-mcp"
],
"timeout": 300,
"disabled": false,
"alwaysAllow": []
}
}
}
Run as HTTP
Most local MCP clients should use stdio. If you need streamable HTTP:
uvx --from git+https://github.com/juanqui/joulescope-mcp joulescope-mcp --transport streamable-http --mount-path /mcp
Troubleshooting
- Client cannot find
uvx: use the absolute path fromwhich uvx. - No JouleScope found: run your configured server command directly in a terminal to check startup errors, then run
uvx --from pyjoulescope-driver pyjoulescope_driver scanto verify the official driver can see the JS220. - Permission denied on Linux: install JouleScope udev rules and reconnect the JS220.
- Multiple clients fight over the device: run only one MCP client/server process against a JS220 at a time.
- GitHub install fails: verify
git clone https://github.com/juanqui/joulescope-mcpworks first. - Tools changed but client still shows old tools: restart the client or reset/reload MCP tools.
Quick local health check:
uv run python scripts/hardware_smoke.py --duration-s 2 --interval-s 0.5
Agent Workflow
For firmware or application power optimization, keep the measurement setup stable:
- Run a baseline measurement with
measure_energy. - Apply one firmware or software change.
- Run the same
measure_energyduration and interval again. - Compare
total_charge_mAh,total_energy_mWh,average_current_mA, and the interval samples. - Repeat the measurement when differences are close to normal run-to-run variance.
Example request:
{
"duration_s": 15,
"interval_s": 0.5,
"compact": true
}
The response includes:
total_charge_mAh: total charge over the measurement windowtotal_energy_mWh: total energy over the measurement windowaverage_current_mA: average current over the actual captured durationaverage_power_mW: average power over the actual captured durationactual_interval_s: average actual interval captured by the JS220sample_charge_mAh: compact per-interval charge list whencompact=truesample_energy_mWh: compact per-interval energy list whencompact=truesample_voltage_avg_v,sample_voltage_min_v,sample_voltage_max_v: compact per-interval voltage lists whencompact=trueandinclude_voltage=truesamples: full per-interval statistics whencompact=false
If duration_s is not an exact multiple of interval_s, the server rounds up to the next full interval and reports both requested_duration_s and actual_duration_s.
Tools
list_devices
Lists connected JouleScope devices. For JS220 devices, it attempts to include hardware, firmware, and FPGA versions.
device_info
Returns retained driver topic values for a selected device. Set include_metadata=true to include topic metadata returned by the driver.
measure_energy
Measures charge and energy using JS220 sensor-side statistics.
Parameters:
duration_s: requested measurement duration in secondsinterval_s: accumulation interval in secondsdevice_path: optional explicit device path, such asu/js220/005920configure_auto_range: defaults to true; configures current and voltage range modes toautocompact: returns compact charge and energy arrays and omits full samplesinclude_voltage: when used withcompact, also returns per-interval voltage arrays
Implementation detail: the JS220 publishes per-interval current.integral in coulombs and power.integral in joules. The server sums those integrals, then also converts charge to mAh and energy to mWh.
capture_statistics
Frequency-based wrapper around measure_energy. Use when you want frequency_hz instead of interval_s.
configure_frontend
Sets current and voltage range modes and optional range selections. Use auto for normal measurements.
target_power_status
Reports whether the JS220 target/DUT power path is connected. For JS220, DUT power is controlled through s/i/range/mode: off disconnects Current+ from Current-, while auto or manual connects the target path for measurement.
set_target_power
Connects or disconnects power to the DUT through the JS220 current path.
Parameters:
power_on: true to connect target power, false to disconnect iton_mode:autoby default, ormanualsettle_ms: optional wait after changing state
cycle_target_power
Power-cycles the DUT by setting target power off, waiting, then restoring target power.
Parameters:
off_ms: hold-off time in millisecondson_mode:autoby default, ormanualsettle_ms: optional wait after restoring power
record_jls
Records raw samples to a JLS v2 file using the JouleScope driver's Record API. This is useful for later waveform analysis in the JouleScope UI or JLS tooling. Existing files are rejected unless overwrite=true.
read_gpi
Reads JS220 general-purpose input state and returns a 32-bit value plus decoded pins.
list_topics
Lists retained driver topics, values, and optional metadata. This is the discovery tool for advanced JS220 capabilities.
query_topic
Queries one driver topic. Relative topics are resolved under the selected device, so c/fw/version becomes u/js220/<serial>/c/fw/version.
publish_topic
Publishes a value to a driver topic. This exposes advanced JS220 features and can change device behavior. Prefer typed tools when available.
Resources and Prompts
Resources:
joulescope://devices: JSON device listjoulescope://driver: server and driver version information
Prompt:
power_optimization_session: template for measurement-driven power optimization loops
Safety and Limits
The server opens a short-lived JouleScope driver connection per tool call and serializes device access inside one server process. Blocking measurements have guardrails:
- Minimum interval: 0.5 ms
- Maximum duration: 3600 seconds
- Maximum returned intervals: 10,000
- Statistics collection times out if the JS220 does not publish the expected samples
set_target_power, cycle_target_power, record_jls, and publish_topic are marked as write/destructive-capable MCP tools. cycle_target_power intentionally interrupts the DUT. Agents should use it only when an interruption is part of the requested test.
Development
Set up a local checkout:
uv sync --extra dev
Run tests:
uv run python -m pytest
Run linting:
uv run python -m ruff check .
Build the package:
uv run python -m build
Hardware smoke test:
uv run python -m pyjoulescope_driver scan
uv run python -m pyjoulescope_driver statistics --frequency 2 --duration 1
uv run python - <<'PY'
from joulescope_mcp.service import Js220Service
r = Js220Service().measure_energy(duration_s=2, interval_s=0.5)
print(r["total_charge_mAh"], r["average_current_mA"], [s["charge_mAh"] for s in r["samples"]])
PY
Repeatable hardware smoke script:
uv run python scripts/hardware_smoke.py --duration-s 2 --interval-s 0.5
Design
See docs/design.md for the MCP design, measurement semantics, tool rationale, and verification strategy.See docs/testing.md for repeatable software, MCP, and hardware checks.See docs/adversarial-reviews.md for the implementation and README review logs.
References
- JouleScope downloads and documentation: https://www.joulescope.com/pages/downloads
- JouleScope driver documentation: https://joulescope-driver.readthedocs.io/
- JouleScope driver source: https://github.com/jetperch/joulescope_driver
- JouleScope
dut_power.pyexample: https://github.com/jetperch/pyjoulescope_examples/blob/main/bin/dut_power.py - MCP Python SDK: https://github.com/modelcontextprotocol/python-sdk
uvxtool execution: https://docs.astral.sh/uv/concepts/tools/
Client configuration references used for the examples above:
| Client | References |
|---|---|
| Claude Code | Claude Code MCP docs, Claude Code support FAQ |
| Claude Desktop | MCP local server quickstart, Anthropic custom connectors note |
| Codex | OpenAI Docs MCP Codex quickstart, Codex MCP interface notes, plus local verification with codex mcp add --help and codex mcp list |
| Cursor | Cursor MCP configuration docs, Cursor CLI MCP docs |
| VS Code / GitHub Copilot | Add and manage MCP servers in VS Code, VS Code MCP configuration reference |
| Windsurf | Windsurf MCP integration docs, Windsurf MCP client guide from Grafana |
| Cline | Cline CLI configuration, Cline MCP overview |
Popular MCP setup examples reviewed:
- GitHub MCP Server installation guides: https://github.com/github/github-mcp-server/tree/main/docs/installation-guides
- Context7 MCP installation guide: https://context7.mintlify.dev/docs/installation
- MCP local server quickstart: https://modelcontextprotocol.io/docs/develop/connect-local-servers
- OpenAI Docs MCP client examples: https://developers.openai.com/learn/docs-mcp
License
Apache License 2.0. See LICENSE.