Connect one read-only tool, verify its result and learn how to remove its access. This guide is for users of Sonny 0.12.7 with an approved model connection and permission to review the chosen MCP server. It covers actual stdio and SSE calls; the controlled examples do not certify an external account, production identity provider, GUI consent flow or verified isolation realm.

An MCP configuration identifies a process or endpoint. Approval authorizes a particular launch or URL. The server’s account/scope policy decides which data the resulting tool can read. Inspect all three before sending private information.

Choose a server and its boundary

For stdio, use the available approved-notes server from Build a note lookup with APIC . That tutorial provides the public source, build steps, restricted Recipe and synthetic notes. Work from the built project’s root, where bin/approved-notes exists. For a custom integration, APIC is the recommended starting point; its generated API structure still needs handler and access tests.

TransportSonny 0.12.7 result
stdioActual APIC initialization, discovery, note read and denied collection checked. The process runs on the host in this guide.
sseActual loopback event-stream initialization, discovery, read and denied collection checked with the public fixture below.
streamable_httpConfiguration parses, but Sonny refuses connection admission before a network request. Library support does not establish Sonny support.
http, WebSocket namesNot admitted MCP transport choices here. APIC’s HTTP/WS output is not an SSE compatibility claim.

An approved executable can act with its host permissions. The word stdio does not establish isolation. A verified realm uses different launch/identity controls; remote SSE is refused under active isolation in this release. mcp list --connect is a host introspection process, not proof of an isolated session.

Configure the stdio example

Create a complete sidecar file using the actual built binary’s absolute path:

python3 - <<'PY'
from pathlib import Path
import json
binary = (Path.cwd() / 'bin/approved-notes').resolve(strict=True)
Path('mcp.yaml').write_text(json.dumps({'servers': [{
    'name': 'approved-notes', 'type': 'stdio',
    'command': str(binary), 'enabled': True
}]}, indent=2) + '\n')
PY
sonny mcp list --mcp-config ./mcp.yaml --format json

JSON is accepted as YAML here. Inspect the command, source and server identity. An unapproved entry is withheld. A zero exit from a list command does not mean its server connected: inspect each row’s connected, error and tools fields. The installed negative cases returned exit zero with a disconnected/error row.

Preview, approve and connect

Approval writes the protected operator allowlist in the profile configuration directory. Preview the exact executable first:

sonny mcp approve server approved-notes --command "$PWD/bin/approved-notes" --dry-run
sonny mcp approve server approved-notes --command "$PWD/bin/approved-notes"
sonny mcp list --mcp-config ./mcp.yaml --connect --format json

The dry run wrote no allowlist in qualification. The actual approval pinned the binary bytes and declared launch details. Discovery then reported note_lookup. For a different server, review its exact arguments, working directory and environment references too; follow the trust reference .

sonny mcp connect approved-notes --mcp-config ./mcp.yaml performs an operator connection check. sonny mcp disconnect approved-notes --mcp-config ./mcp.yaml tests clean teardown of its own command-scoped connection. Both commands were exercised. They do not open or close an unrelated long-running TUI connection, and disconnect does not revoke approval.

Perform one bounded read

Use the tutorial’s complete notes-recipe.yaml and your configured primary model category:

sonny run --file ./notes-recipe.yaml --mcp-config ./mcp.yaml --headless-mcp --new --no-trace --prompt 'Read release-checklist from the approved collection using approved-notes.' --result-json ./note-result.json

--headless-mcp explicitly permits host-executed MCP for this headless run. It does not put the server in a verified realm. The prompt/tool result reaches the model service; Sonny also writes local control/result state. --no-trace does not suppress every local record or the provider’s retention.

The actual read returned “Review the change, run the checks, and record the outcome before publishing.” Verify the source and result envelope, not a model’s claim that it read something. Requesting another collection returned permission_denied and a failed tool call. The conversation could still finish; successful process exit does not erase that denial.

Try the supported remote transport locally

The public transport fixture , SSE sidecar and read Recipe form a complete synthetic, read-only SSE example. Download them into a new directory:

curl --fail --location https://sonny.sh/examples/mcp/transport-fixture.py --output transport-fixture.py
curl --fail --location https://sonny.sh/examples/mcp/sse-fixture.yaml --output sse-fixture.yaml
curl --fail --location https://sonny.sh/examples/mcp/read-sse.yaml --output read-sse.yaml

In one terminal, enter a disposable fixture-only token, export it and start the loopback service. Keep that terminal open:

read -r -s DOCS_MCP_FIXTURE_TOKEN
export DOCS_MCP_FIXTURE_TOKEN
python3 ./transport-fixture.py --port 8766

In the second terminal, enter/export the same synthetic value before approval and connection. Keep real credentials out of this fixture and out of checked-in configuration. The YAML references the environment variable; it contains no key.

read -r -s DOCS_MCP_FIXTURE_TOKEN
export DOCS_MCP_FIXTURE_TOKEN
sonny mcp approve server docs-sse --url http://127.0.0.1:8766/sse --dry-run
sonny mcp approve server docs-sse --url http://127.0.0.1:8766/sse
sonny mcp list --mcp-config ./sse-fixture.yaml --connect --format json
sonny run --file ./read-sse.yaml --mcp-config ./sse-fixture.yaml --headless-mcp --new --no-trace --prompt 'Read release-checklist from the approved collection using docs-sse.' --result-json ./sse-result.json

Expect the same synthetic note. The test also requested a different collection and received out_of_scope with a failed tool call. The sidecar’s cleartext and private-network exceptions apply only to this explicit loopback host/port. If that port is busy, select another and update the URL and allowed port together before reviewing approval. For an external service, use its reviewed HTTPS endpoint, TLS/egress policy and account scopes. This loopback test does not qualify an external service’s TLS or OAuth flow.

Revoke access or review an update

Stop the sessions/connections that use the server. Sonny 0.12.7 has no mcp revoke subcommand. Remove only that server’s entry from the protected mcp.allowlist.yaml in the profile configuration directory, preserving its ownership and restrictive permissions. Then use a fresh mcp list --connect check and confirm it is withheld. The disposable qualification removed approval and observed that refusal; it did not equate disconnect with revocation.

For an update, stop users of the old process, review the new executable/version and exact launch, remove the old entry, then approve the new identity deliberately and repeat the bounded read and denial tests. Changing binary bytes at an approved path was refused. Repeating approval while the old entry remained said “already … no change” and did not refresh the pin; that positive check remains nonzero. Removing the old fixture entry before approval allowed the reviewed updated binary to connect. A name or pathname staying the same does not establish that the code stayed the same.

Diagnose the failing layer

ObservationWhat to inspect
Unapproved or changed binaryAllowlist decision and exact launch identity; review before reapproving.
Process exits before initializationExecutable availability, startup requirements and redacted process errors. An approved process can still fail.
HTTP UnauthorizedThe endpoint/account and credential flow. The fixture simulated an expired credential with HTTP401; no real account expiry is certified.
Malformed protocol responseServer/version and the failed negotiation/discovery layer, not the model prompt.
Connected but collection deniedServer authorization and requested scope; transport success does not grant a broader collection.
Parsed but unsupported transportSonny admission, even if its underlying library can dial that transport.

User, registry, project and Recipe configuration merge before approval and scope filters; read the configuration reference for exact precedence. Project-origin servers have workspace-trust and stricter launch-pin requirements. Child-session access is resolved with its own Recipe policy and the same admission/approval boundaries; a parent’s successful call is not proof of every child’s effective scope or of an actual worker launch.

Continue with trust and pinning , the qualified server catalog or the APIC tutorial .