arduino-mcp-server 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Akshat Nerella
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # arduino-mcp-server
2
+ Arduino MCP server that wraps `arduino-cli` so AI agents can discover boards/ports, compile/upload sketches, read serial output, and query board pin references.
3
+
4
+ ## Features
5
+ - MCP tools for:
6
+ - listing connected boards and serial ports
7
+ - detecting connected hardware with inferred FQBN and next commands
8
+ - checking `arduino-cli` availability with OS-specific install guidance (`arduino_cli_doctor`)
9
+ - auto-installing `arduino-cli` when missing (`install_arduino_cli`)
10
+ - ensuring required board cores are installed (`ensure_core_installed`)
11
+ - listing supported boards
12
+ - compiling sketches
13
+ - uploading sketches
14
+ - reading a serial snapshot (time-bounded monitor)
15
+ - fetching `arduino-cli board details`
16
+ - querying local board pin/reference metadata
17
+ - Structured JSON responses so agents can reason over output
18
+ - Optional sketch path sandboxing via `ARDUINO_SKETCH_ROOT`
19
+
20
+ ## Requirements
21
+ - Node.js 20+
22
+ - `arduino-cli` installed and available on `PATH` (or set `ARDUINO_CLI_PATH`)
23
+
24
+ ## Agent Workflow Contract
25
+ Use this workflow in AI agents:
26
+ 1. Call `arduino_cli_doctor` first.
27
+ 2. If `installed=false`, call `install_arduino_cli` with `{"method":"auto"}`.
28
+ 3. If auto-install fails, use the returned OS-specific `installGuide`.
29
+ 4. Set `ARDUINO_CLI_PATH` if the binary is not on `PATH`.
30
+ 5. Re-run `arduino_cli_doctor` and continue only when `installed=true`.
31
+ 6. Only then call `detect_hardware`, `compile_sketch`, `upload_sketch`, etc.
32
+
33
+ Do not attempt fallback hardware scans before `arduino-cli` is available.
34
+
35
+ When `detect_hardware` returns unresolved/non-standard board matches, the tool now includes
36
+ `requiresUserBoardConfirmation` and an `agentAction` question payload. Agents should ask the user
37
+ to confirm board model/FQBN before continuing.
38
+
39
+ `compile_sketch` and `upload_sketch` automatically ensure board core installation from FQBN by default
40
+ (`autoInstallCore=true`), so agents should not need manual `arduino-cli core install` in normal flows.
41
+
42
+ ## Install Arduino CLI Quickly
43
+ Official docs: https://docs.arduino.cc/arduino-cli/installation/
44
+
45
+ - Windows (recommended): `winget install ArduinoSA.CLI`
46
+ - macOS: `brew install arduino-cli`
47
+ - Linux: `brew install arduino-cli` or official install script
48
+
49
+ If needed, set `ARDUINO_CLI_PATH`:
50
+ - PowerShell (current session): `$env:ARDUINO_CLI_PATH='C:\\path\\to\\arduino-cli.exe'`
51
+ - Bash/Zsh (current session): `export ARDUINO_CLI_PATH=/absolute/path/to/arduino-cli`
52
+
53
+ ## Install
54
+ ```bash
55
+ npm install
56
+ npm run build
57
+ ```
58
+
59
+ ## Run
60
+ ```bash
61
+ npm start
62
+ ```
63
+
64
+ For local development:
65
+
66
+ ```bash
67
+ npm run dev
68
+ ```
69
+
70
+ ## Environment variables
71
+ - `ARDUINO_CLI_PATH`: path/command for Arduino CLI. Default: `arduino-cli`
72
+ - `ARDUINO_SKETCH_ROOT`: optional absolute path. When set, `sketchPath` inputs must resolve under this root.
73
+
74
+ ## Example MCP client config (stdio)
75
+ Use your built `build/index.js` as the command target.
76
+
77
+ ```json
78
+ {
79
+ "mcpServers": {
80
+ "arduino": {
81
+ "command": "node",
82
+ "args": ["D:/Projects/arduino-mcp-server/build/index.js"],
83
+ "env": {
84
+ "ARDUINO_CLI_PATH": "arduino-cli",
85
+ "ARDUINO_SKETCH_ROOT": "D:/Projects/arduino-sketches"
86
+ }
87
+ }
88
+ }
89
+ }
90
+ ```
91
+
92
+ ## Board Reference Data
93
+ The server includes a starter board reference database at `data/board-reference.json` with common pin mappings.
94
+ You can expand this file or replace it with data from an external source later.
95
+
96
+ ## MCP Capability Coverage
97
+ - Tools: compile/upload/monitor/board discovery and reference lookup
98
+ - Resource: `arduino://boards/reference` for board metadata
99
+ - Prompts:
100
+ - `arduino-cli-bootstrap-policy` for dependency/bootstrap behavior
101
+ - `arduino-setup-assistant` for wiring/setup guidance
102
+
103
+ ## Publish To MCP Registry
104
+ This repo includes a registry manifest at `server.json`.
105
+
106
+ ### Prerequisites
107
+ 1. Publish the npm package first (`identifier` and `version` in `server.json` must exist):
108
+ - `npm login`
109
+ - `npm run build`
110
+ - `npm publish --access public`
111
+ 2. Get a registry auth token (Bearer token) for `registry.modelcontextprotocol.io`.
112
+
113
+ ### Publish command
114
+ PowerShell:
115
+
116
+ ```powershell
117
+ $env:MCP_REGISTRY_TOKEN="<your_registry_token>"
118
+ curl --request POST `
119
+ --url https://registry.modelcontextprotocol.io/v0.1/publish `
120
+ --header "Accept: application/json, application/problem+json" `
121
+ --header "Authorization: Bearer $env:MCP_REGISTRY_TOKEN" `
122
+ --header "Content-Type: application/json" `
123
+ --data-binary "@server.json"
124
+ ```
@@ -0,0 +1,74 @@
1
+ import { spawn } from "node:child_process";
2
+ import path from "node:path";
3
+ export function resolveSketchPath(inputPath, sketchRoot) {
4
+ const resolved = path.resolve(inputPath);
5
+ if (!sketchRoot) {
6
+ return resolved;
7
+ }
8
+ const root = path.resolve(sketchRoot);
9
+ const normalizedResolved = process.platform === "win32" ? resolved.toLowerCase() : resolved;
10
+ const normalizedRoot = process.platform === "win32" ? root.toLowerCase() : root;
11
+ if (normalizedResolved === normalizedRoot || normalizedResolved.startsWith(`${normalizedRoot}${path.sep}`)) {
12
+ return resolved;
13
+ }
14
+ throw new Error(`Path "${resolved}" is outside ARDUINO_SKETCH_ROOT "${root}".`);
15
+ }
16
+ export async function runArduinoCli(config, args, timeoutMs = 60_000) {
17
+ return runCommand(config.cliPath, args, timeoutMs);
18
+ }
19
+ export async function runCommand(command, args, timeoutMs = 60_000) {
20
+ const started = Date.now();
21
+ return await new Promise((resolve) => {
22
+ const child = spawn(command, args, {
23
+ stdio: ["ignore", "pipe", "pipe"]
24
+ });
25
+ let stdout = "";
26
+ let stderr = "";
27
+ let timedOut = false;
28
+ child.stdout.on("data", (chunk) => {
29
+ stdout += chunk.toString();
30
+ });
31
+ child.stderr.on("data", (chunk) => {
32
+ stderr += chunk.toString();
33
+ });
34
+ child.on("error", (err) => {
35
+ const durationMs = Date.now() - started;
36
+ resolve({
37
+ ok: false,
38
+ command,
39
+ args,
40
+ code: null,
41
+ stdout,
42
+ stderr: `${stderr}\n${err.message}`.trim(),
43
+ timedOut: false,
44
+ durationMs
45
+ });
46
+ });
47
+ const timer = setTimeout(() => {
48
+ timedOut = true;
49
+ child.kill();
50
+ }, timeoutMs);
51
+ child.on("close", (code) => {
52
+ clearTimeout(timer);
53
+ const durationMs = Date.now() - started;
54
+ resolve({
55
+ ok: code === 0 && !timedOut,
56
+ command,
57
+ args,
58
+ code,
59
+ stdout,
60
+ stderr,
61
+ timedOut,
62
+ durationMs
63
+ });
64
+ });
65
+ });
66
+ }
67
+ export function tryParseJson(value) {
68
+ try {
69
+ return JSON.parse(value);
70
+ }
71
+ catch {
72
+ return null;
73
+ }
74
+ }
@@ -0,0 +1,26 @@
1
+ import boardReferenceData from "../data/board-reference.json" with { type: "json" };
2
+ const store = boardReferenceData;
3
+ export function listBoardReferences() {
4
+ return store.boards;
5
+ }
6
+ export function findBoardReference(query) {
7
+ const normalized = query.trim().toLowerCase();
8
+ if (!normalized) {
9
+ return [];
10
+ }
11
+ return store.boards.filter((board) => {
12
+ if (board.id.toLowerCase().includes(normalized)) {
13
+ return true;
14
+ }
15
+ if (board.displayName.toLowerCase().includes(normalized)) {
16
+ return true;
17
+ }
18
+ if (board.aliases.some((alias) => alias.toLowerCase().includes(normalized))) {
19
+ return true;
20
+ }
21
+ if (board.fqbnCandidates.some((fqbn) => fqbn.toLowerCase().includes(normalized))) {
22
+ return true;
23
+ }
24
+ return false;
25
+ });
26
+ }