Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

gallo-mcp — MCP server

gallo-mcp bridges a Pico de Gallo to AI coding agents. It runs a Model Context Protocol server over stdio, wrapping pico-de-gallo-lib and exposing one tool per peripheral operation across I2C, SPI, UART, GPIO, PWM, ADC, and 1-Wire.

The point is the on-your-laptop development loop: while an agent writes an embedded driver, it can probe and drive real hardware through the same board gallo talks to — reading a sensor register, scanning a bus, toggling a pin — without cross-compiling or flashing anything.

Use it for:

  • letting an AI agent explore an unfamiliar device interactively,
  • generating and validating register-level driver code against real silicon,
  • turning “what does this chip return?” into a tool call the agent can make itself.

Install

$ cargo install gallo-mcp

Run

$ gallo-mcp [--serial-number <SN>]

gallo-mcp speaks the MCP protocol on stdout and logs to stderr, so it is meant to be launched by an MCP client rather than run interactively.

  • -s, --serial-number <SN> selects a specific board by USB serial when more than one is attached.
  • Per-call connection: the server holds no persistent USB claim. Each tool call opens the board, runs, and releases it when the call completes, so the device is free for the gallo CLI or other host processes between calls. Each call re-opens and re-validates the board, so there is a small fixed per-call connection cost. The server starts even with no board attached and tools begin working as soon as a Pico de Gallo is present; you can plug the board in mid-session.

Using it with an MCP client

Add gallo-mcp as a local (stdio) server in your client’s config. These files are safe to commit per-project, so the tools appear only in repos that opt in.

opencode (opencode.json)

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "pico-de-gallo": { "type": "local", "command": ["gallo-mcp"], "enabled": true }
  }
}

Claude Code (.mcp.json)

{ "mcpServers": { "pico-de-gallo": { "command": "gallo-mcp", "args": [] } } }

Cursor (.cursor/mcp.json)

{ "mcpServers": { "pico-de-gallo": { "command": "gallo-mcp", "args": [] } } }

Byte conventions

Byte payloads go in as hex strings and come back as both hex and a decimal array. Input accepts comma-separated or bare hex ("0x00,0x10" or "0010"); reads return { "hex": ..., "bytes": ... }.

// i2c_write_read {"address":72,"data":"0x00","count":2}
{ "hex": "0x0B,0xCF", "bytes": [11, 207] }

Tool catalog

35 tools, grouped by peripheral. Read-only tools carry the readOnlyHint annotation; write/actuation tools carry destructiveHint.

device (read-only)

ToolDescription
list_devicesList connected Pico de Gallo boards
statusReport attach state and firmware/schema version
device_infoFirmware version, schema version, capabilities
versionFirmware version
pingEcho a value (liveness check)

i2c

ToolDescriptionKind
i2c_readRead bytes from a target addressread-only
i2c_write_readWrite then read without releasing the busread-only
i2c_scanProbe the bus for responding addressesread-only
i2c_get_configShow the active I2C frequencyread-only
i2c_writeWrite bytes to a target addressdestructive
i2c_set_configSet the I2C frequencydestructive
i2c_batchExecute several I2C operations in one transferdestructive

spi

ToolDescriptionKind
spi_readClock in bytesread-only
spi_transferFull-duplex transferread-only
spi_get_configShow the active SPI configurationread-only
spi_writeClock out bytesdestructive
spi_flushFlush the SPI bufferdestructive
spi_set_configSet frequency, phase, and polaritydestructive
spi_batchAtomic multi-step transaction under chip-selectdestructive

uart

ToolDescriptionKind
uart_readRead bytes with a timeoutread-only
uart_get_configShow the active UART configurationread-only
uart_writeWrite raw bytesdestructive
uart_flushDrain the transmit bufferdestructive
uart_set_configSet baud ratedestructive

gpio

ToolDescriptionKind
gpio_getRead the current level of a pinread-only
gpio_wait_for_rising_edge_with_timeoutWait for a rising edge (bounded)read-only
gpio_wait_for_falling_edge_with_timeoutWait for a falling edge (bounded)read-only
gpio_wait_for_any_edge_with_timeoutWait for any edge (bounded)read-only
gpio_putDrive a pin high or lowdestructive
gpio_set_configSet direction and pull resistordestructive

pwm

ToolDescriptionKind
pwm_get_duty_cycleRead current and maximum dutyread-only
pwm_get_configShow the active PWM configurationread-only
pwm_set_duty_cycleSet a raw duty-cycle valuedestructive
pwm_enableEnable the slice behind a channeldestructive
pwm_disableDisable the slice behind a channeldestructive
pwm_set_configSet frequency and phase-correct modedestructive

adc (read-only)

ToolDescription
adc_readRead one ADC sample
adc_get_configShow resolution, reference, and channel count

onewire

ToolDescriptionKind
onewire_readRead raw bytesread-only
onewire_searchEnumerate ROM IDs on the busread-only
onewire_resetReset the bus and report presencedestructive
onewire_writeWrite raw bytesdestructive
onewire_write_pullupWrite, then hold the line high (parasitic power)destructive

GPIO waits and v1 limits

GPIO edge waits are timeout-bounded only: each of the three wait tools requires a non-zero timeout_ms. This release deliberately does not expose infinite/no-timeout waits or push-based edge subscriptions — a wait that never returns would stall the stdio session, and event streaming is out of scope for v1.

Security

gallo-mcp does not gate writes itself. Write approval is delegated to the MCP client through tool annotations:

  • read tools are marked readOnlyHint,
  • write and actuation tools are marked destructiveHint.

A well-configured client uses those hints to prompt for confirmation before a destructive tool call. Configure your client’s permissions accordingly.

Warning

Under a permission-less or blanket-allow client, an agent can actuate hardware — drive pins, write buses, change configuration — without confirmation. If the board is wired to anything you care about, run the server behind a client that honors destructiveHint.

Validation

The server was validated on real hardware: a Pico de Gallo (serial 5256657D8A5D7F03, firmware v0.10.0, schema v0.6.0, HW rev2) with a TMP108 temperature sensor on I2C.

Over stdio, status reports the attached board:

{ "attached": true, "firmware_version": "0.10.0", "schema_major": 0, "schema_minor": 6 }

i2c_scan finds the sensor at address 0x48:

// i2c_scan {"include_reserved":false}
{ "addresses": ["0x48"], "raw": [72] }

And i2c_write_read reads its two temperature bytes:

// i2c_write_read {"address":72,"data":"0x00","count":2}
{ "hex": "0x0B,0xCF", "bytes": [11, 207] }

Those bytes are byte-for-byte identical to the gallo CLI:

$ gallo i2c write-read -a 0x48 -b 0x00 -c 2
0b cf

Note

Decoding 0x0BCF into degrees Celsius is left to the reader and the TMP108 datasheet — it is not asserted here. The point of this walkthrough is that the MCP round-trip returns exactly what the CLI does, so an agent driving the board through gallo-mcp sees the same truth you would at the shell.