Content
# 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`:
```json
{
"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:
```json
{
"mcpServers": {
"joulescope-js220": {
"command": "uvx",
"args": [
"--from",
"git+ssh://git@github.com/juanqui/joulescope-mcp.git",
"joulescope-mcp"
]
}
}
}
```
Then ask your MCP client:
```text
Measure JouleScope power for 15 seconds with 500 ms intervals. Include voltage.
```
Expected result shape, with compact sample arrays shortened for display:
```json
{
"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
- [`uv`](https://docs.astral.sh/uv/getting-started/installation/) for the recommended `uvx` install 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`
```bash
uvx --from git+https://github.com/juanqui/joulescope-mcp joulescope-mcp
```
MCP JSON:
```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:
```bash
uvx --from git+ssh://git@github.com/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:
```bash
git clone git@github.com:juanqui/joulescope-mcp.git
cd joulescope-mcp
uv sync --extra dev
uv run joulescope-mcp
```
Verify that the driver can see the JS220:
```bash
uv run python -m pyjoulescope_driver scan
uv run python -m pyjoulescope_driver statistics --frequency 2 --duration 1
```
Local checkout MCP JSON:
```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 clients
- `servers`: 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://git@github.com/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:
```json
{
"command": "uvx",
"args": [
"--from",
"git+https://github.com/juanqui/joulescope-mcp",
"joulescope-mcp"
]
}
```
Local checkout replacement:
```json
{
"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:
```bash
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:
```bash
MCP_TIMEOUT=60000 MCP_TOOL_TIMEOUT=300000 claude
```
Project `.mcp.json`:
```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`
```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:
```bash
codex mcp add joulescope-js220 -- uvx --from git+https://github.com/juanqui/joulescope-mcp joulescope-mcp
codex mcp list
```
Direct `~/.codex/config.toml` entry:
```toml
[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`
```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 Configuration` from the Command Palette.
```json
{
"servers": {
"joulescope-js220": {
"type": "stdio",
"command": "uvx",
"args": [
"--from",
"git+https://github.com/juanqui/joulescope-mcp",
"joulescope-mcp"
]
}
}
}
```
Command-line install:
```bash
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`
```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:
```bash
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**
```json
{
"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:
```bash
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 from `which 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 scan` to 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-mcp` works first.
- **Tools changed but client still shows old tools**: restart the client or reset/reload MCP tools.
Quick local health check:
```bash
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:
1. Run a baseline measurement with `measure_energy`.
2. Apply one firmware or software change.
3. Run the same `measure_energy` duration and interval again.
4. Compare `total_charge_mAh`, `total_energy_mWh`, `average_current_mA`, and the interval samples.
5. Repeat the measurement when differences are close to normal run-to-run variance.
Example request:
```json
{
"duration_s": 15,
"interval_s": 0.5,
"compact": true
}
```
The response includes:
- `total_charge_mAh`: total charge over the measurement window
- `total_energy_mWh`: total energy over the measurement window
- `average_current_mA`: average current over the actual captured duration
- `average_power_mW`: average power over the actual captured duration
- `actual_interval_s`: average actual interval captured by the JS220
- `sample_charge_mAh`: compact per-interval charge list when `compact=true`
- `sample_energy_mWh`: compact per-interval energy list when `compact=true`
- `sample_voltage_avg_v`, `sample_voltage_min_v`, `sample_voltage_max_v`: compact per-interval voltage lists when `compact=true` and `include_voltage=true`
- `samples`: full per-interval statistics when `compact=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 seconds
- `interval_s`: accumulation interval in seconds
- `device_path`: optional explicit device path, such as `u/js220/005920`
- `configure_auto_range`: defaults to true; configures current and voltage range modes to `auto`
- `compact`: returns compact charge and energy arrays and omits full samples
- `include_voltage`: when used with `compact`, 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 it
- `on_mode`: `auto` by default, or `manual`
- `settle_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 milliseconds
- `on_mode`: `auto` by default, or `manual`
- `settle_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 list
- `joulescope://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:
```bash
uv sync --extra dev
```
Run tests:
```bash
uv run python -m pytest
```
Run linting:
```bash
uv run python -m ruff check .
```
Build the package:
```bash
uv run python -m build
```
Hardware smoke test:
```bash
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:
```bash
uv run python scripts/hardware_smoke.py --duration-s 2 --interval-s 0.5
```
## Design
See [docs/design.md](docs/design.md) for the MCP design, measurement semantics, tool rationale, and verification strategy.
See [docs/testing.md](docs/testing.md) for repeatable software, MCP, and hardware checks.
See [docs/adversarial-reviews.md](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.py` example: <https://github.com/jetperch/pyjoulescope_examples/blob/main/bin/dut_power.py>
- MCP Python SDK: <https://github.com/modelcontextprotocol/python-sdk>
- `uvx` tool execution: <https://docs.astral.sh/uv/concepts/tools/>
Client configuration references used for the examples above:
| Client | References |
| --- | --- |
| Claude Code | [Claude Code MCP docs](https://code.claude.com/docs/en/mcp), [Claude Code support FAQ](https://support.claude.com/en/articles/14554922-claude-code-user-faq) |
| Claude Desktop | [MCP local server quickstart](https://modelcontextprotocol.io/docs/develop/connect-local-servers), [Anthropic custom connectors note](https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp) |
| Codex | [OpenAI Docs MCP Codex quickstart](https://developers.openai.com/learn/docs-mcp), [Codex MCP interface notes](https://github.com/openai/codex/blob/main/codex-rs/docs/codex_mcp_interface.md), plus local verification with `codex mcp add --help` and `codex mcp list` |
| Cursor | [Cursor MCP configuration docs](https://docs.cursor.com/advanced/model-context-protocol), [Cursor CLI MCP docs](https://docs.cursor.com/cli/mcp) |
| VS Code / GitHub Copilot | [Add and manage MCP servers in VS Code](https://code.visualstudio.com/docs/copilot/customization/mcp-servers), [VS Code MCP configuration reference](https://code.visualstudio.com/docs/copilot/reference/mcp-configuration) |
| Windsurf | [Windsurf MCP integration docs](https://docs.windsurf.com/windsurf/cascade/mcp), [Windsurf MCP client guide from Grafana](https://grafana.com/docs/grafana/latest/developer-resources/mcp/clients/windsurf/) |
| Cline | [Cline CLI configuration](https://docs.cline.bot/getting-started/config), [Cline MCP overview](https://docs.cline.bot/mcp/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](LICENSE).
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
No recommendations available