MCP / A2A protocol reference
Implementation lives in src/protocol/: execution reuses Tool Runtime / AgentRunner, tasks handles concurrency and cancellation, store persists device tasks, and mcp/a2a adapt their respective protocols. See the deployment guide for startup and clients.
flowchart LR
M[MCP HTTP / local stdio] --> T[Device Tool Runtime]
M --> Q[Device task service]
A[A2A JSON-RPC] --> Q
Q --> G[Built-in Agent]
G --> T
T --> S[Prepare / safety / local approval / execute]
Entry points and authentication
| Entry | Behavior |
|---|---|
/.well-known/agent-card.json |
Public Agent Card, A2A JSONRPC 1.0; no A2A streaming, push, or extended card |
/a2a |
Bearer authentication, JSON-RPC 2.0, A2A-Version: 1.0 |
/mcp |
Bearer authentication, Streamable HTTP; not legacy SSE /sse |
nl2sh protocol stdio |
Device-local stdin/stdout MCP under the launching user's privileges |
HTTP is separate from Web. Defaults are 0.0.0.0:8765 and --allow-insecure-http=true. The advertised IP comes from IPv4 routing/active interfaces, falling back to loopback; --advertised-url overrides the origin used in the Agent Card and Host/Origin checks. When neither NL2SH_PROTOCOL_TOKEN nor configured protocol_token is set, startup generates a 64-character, 256-bit system-random token and prints startup connections. An explicit value must contain 32–256 printable ASCII characters without spaces and is not printed. Generated tokens rotate on restart, are stored in the private 0600 protocol announcement for TUI/Web display and are included in task-snapshot redaction. All holders of the token share one trusted owner; there is no multi-tenant task isolation. Task/context IDs are not access credentials. Host must match the advertised authority or local listener address; any supplied Origin must match the advertised origin. Missing/invalid tokens return 401; invalid Host/Origin returns 403. Use --host 127.0.0.1 for local-only access; --allow-insecure-http=false refuses a non-loopback HTTP listener.
MCP uses the official Rust SDK and supports protocol versions 2025-11-25, 2025-06-18, and 2024-11-05. HTTP uses stateless server transport; clients still follow initialize/initialized lifecycle and need not retain Mcp-Session-Id. Resources/prompts, remote approvals, sampling, and the MCP Tasks extension are not provided.
MCP tools
| Tool | Parameters | Result |
|---|---|---|
nl2sh_inspect |
None | Fixed device environment object |
nl2sh_tools |
None | Object with a tools array of available registered tools and parameter schemas |
nl2sh_invoke |
tool, object arguments |
Direct tool result, without a model call |
nl2sh_read_screen |
None | Text summary and PNG/JPEG MCP image block |
nl2sh_ask |
message, optional context_id |
Device Task for the built-in Agent |
nl2sh_get_task |
task_id |
Saved device Task |
nl2sh_cancel_task |
task_id |
Task after requesting cancellation and waiting for settlement |
Direct results contain tool, success, bounded string output, and optional attachments. output may itself contain JSON text. MCP provides text content and structuredContent; operation failure sets isError to true. Successful screen reads require exactly one PNG/JPEG attachment, returned as native MCP image content.
nl2sh_ask returns the same Task format as A2A. Agent results contain session, answer, steps, tool_calls, and at most eight bounded failed_tools, at artifacts[0].parts[0].data. COMPLETED means the Agent returned, not that every tool succeeded. Missing model configuration produces a FAILED task. MCP tool discovery does not prove device capabilities; the actual catalog reflects configuration and capability discovery. The long-lived server can expose enabled process-lifetime tools.
A2A messages, tasks, and pagination
nl2sh_inspect adds build_identity: compile-time Git/dirty/build ID/target/profile,
running executable SHA-256, protocol version and UID. Missing provenance is null;
matching versions do not prove identical code. nl2sh_ask artifacts add evidence
derived from actual ToolRounds, linking tool names and call IDs to success, failure,
missing results and bounded output (64 entries, 4096 bytes each), with explicit
truncation and total-call metadata. Model answers remain unverified analysis.
See the device development loop.
Accepts ROLE_USER, a nonempty messageId, and text parts only. Parts join with newlines, totaling 1–8192 UTF-8 bytes. All user text enters the Agent; no slash tool commands are parsed. Omit contextId to generate one; reuse it to continue Agent history. Each send creates a new task; continuation with an old taskId is unsupported.
| JSON-RPC method | Parameters and behavior |
|---|---|
SendMessage |
message, optional configuration.returnImmediately/historyLength/acceptedOutputModes; returns {task: ...} |
GetTask |
id, optional historyLength; returns Task directly |
ListTasks |
Optional contextId/status/pageSize/pageToken/historyLength/includeArtifacts/statusTimestampAfter |
CancelTask |
id; requests cooperative cancellation and waits for the current operation to settle |
SendMessage waits for terminal status by default; returnImmediately: true returns a submitted task for polling. historyLength: 0 excludes message history. ListTasks defaults to at most 50 items, maximum 100, ordered by update time and ID descending; returns tasks/totalSize/pageSize/nextPageToken. The final page token is "". Artifacts are omitted unless includeArtifacts: true.
Tasks use id/contextId/status/history/artifacts. States are TASK_STATE_SUBMITTED/WORKING/COMPLETED/FAILED/CANCELED, with RFC3339 timestamps. Artifact parts[0].data is structured device output; no nested JSON text needs parsing. Errors follow JSON-RPC and A2A 1.0 mappings, including missing task -32001, not cancelable -32002, unsupported operation -32004, unsupported content type -32005, and unsupported version -32009.
Execution, approval, and cancellation
Direct and Agent calls reuse registration, argument validation, preparation, assessment, resource locks, approval, privilege binding, and audit. Permissive remote configuration is raised to at least balanced/risk_only; execution uses captured pipelines. Mutations default to local interactive terminal approval, at most eight requests for 120 seconds; dangerous actions require an exact phrase. No remote approval tool exists. Explicit protocol_auto_approve approves all risk levels through the protocol confirmer only.
Locally configured scoped grants are also evaluated after final assessment. Eligible matching actions can proceed without a terminal approval request; Root, Critical, and strong confirmation remain excluded. protocol_auto_approve is a separate explicit confirmer with a different scope. See approvals.
Agent tasks sharing a context serialize within the service and use an isolated protocol- session namespace. Web/TUI do not share that namespace. Android UI operations retain existing cross-process locks. An HTTP disconnect does not automatically replay or roll back A2A tasks; query known tasks before resubmitting. Message IDs do not promise exactly-once execution.
CancelTask/MCP cancellation rejects pending approvals and signals the Agent/captured shell. Execution futures are not directly dropped when client requests end; shell uses existing process-group cleanup, and other tools finish their current operation before stopping. Side effects may already have occurred; CANCELED does not mean rollback. SIGINT/SIGTERM drains tasks through the same boundary before shutdown.
Storage and limits
The device state directory contains protocol/tasks.sqlite3, with directory mode 0700 and file mode 0600. An exclusive process lock prevents multiple protocol servers using the same state directory. Complete Agent history lives privately in sessions/. Known configured credentials and service tokens are redacted from task snapshots. Restart changes SUBMITTED/WORKING to FAILED without recovering, replaying, or approving execution; unfinished turns are not saved as complete history.
| Boundary | Limit |
|---|---|
| HTTP request body | 32 KiB for MCP/A2A |
| MCP argument JSON | 16 KiB |
| Agent user text | 8192 UTF-8 bytes |
| Tool name | 128 UTF-8 bytes |
| task/context/message ID | 256 UTF-8 bytes |
| Active tasks | 16, including context/approval waits |
| Pending local approvals | 8, 120 seconds |
| Stored task document | 4 MiB |
| Retained tasks | 200 / 64 MiB of documents, pruning oldest terminal tasks first |
| MCP screen base64 | 3 MiB, PNG/JPEG only |
Task storage is not an indefinite archive; older terminal tasks may be pruned. Agent/tool configuration controls execution budgets; there is no shared 180-second ADB timeout. Client/proxy timeouts must cover execution and approval waits. Long Agent delegations should use returnImmediately + GetTask.