ambivalence

mcp
Guvenlik Denetimi
Basarisiz
Health Uyari
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 5 GitHub stars
Code Basarisiz
  • spawnSync — Synchronous process spawning in e2e-runner.mjs
  • process.env — Environment variable access in e2e-runner.mjs
  • child_process — Shell command execution capability in npm/cli.cjs
  • spawnSync — Synchronous process spawning in npm/cli.cjs
  • fs module — File system access in npm/cli.cjs
  • fs module — File system access in npm/install.cjs
Permissions Gecti
  • Permissions — No dangerous permissions requested

Bu listing icin henuz AI raporu yok.

SUMMARY

give your coding agent a camera: your phone. pair once over mcp or cli, found on your wi-fi, end-to-end encrypted

README.md

ambivalence logo

release badge npm badge ci badge license badge

the agent checks an arduino through the phone camera, zooms in and answers


ambivalence lets your coding agent use your phone as a camera and microphone. you pair the phone once. after that the agent takes photos, switches between the front and back camera, zooms, turns on the torch, records clips and listens through the microphone while the app is open, through MCP or the cli, and you don't touch the phone again. on the same wi-fi the phone finds the agent by itself. everything between the phone and the agent is end-to-end encrypted.

protocol | audio | npm | releases | demo video | ios build notes

why

your agent can read your code, but it can't see the board on your desk, the label on a box or the error on another screen, and it can't hear a fan, a beep or a machine. ambivalence gives it the phone in your pocket. it sends still photos, not video, because vision models take stills, and audio as short wav chunks because models handle clips better than a live stream. each photo is one jpeg over a websocket, so it works on slow networks too. audio is recorded on the phone and buffered, so a slow model does not miss what happened between chunks.

after the first pairing there is nothing to do on the phone. open the app and ask the agent. if the app is closed when the agent asks, the agent waits up to 30 seconds for you to open it. the link survives app restarts and network drops.

requirements

part needs
agent side linux, macos or windows, amd64 or arm64. node 18+ if you install with npm
android android 8.0 or newer
ios ios 17 or newer, and an apple developer team to sign the app
browser any phone browser with camera and microphone access, over https
optional cloudflared for --tunnel, tailscale for access from anywhere

install

agent side

with npm:

npm install -g ambivalence
ambivalence version

the package downloads the release binary for your platform and checks it against the release's checksums.txt. if your npm runs with install scripts disabled, the download happens on the first ambivalence command instead.

or download a binary from the latest release. the names are ambivalence-<os>-<arch>, for example ambivalence-darwin-arm64 or ambivalence-windows-amd64.exe:

curl -fsSL -o ambivalence https://github.com/Microck/ambivalence/releases/latest/download/ambivalence-linux-amd64
chmod +x ambivalence && mv ambivalence ~/.local/bin/

or build it with go 1.26 or newer:

go install github.com/Microck/ambivalence/cmd/ambivalence@latest

to check a downloaded binary by hand:

gh release download --repo Microck/ambivalence --pattern checksums.txt
sha256sum --check --ignore-missing checksums.txt

phone

android: install ambivalence.apk from the release. allow installs from unknown sources when android asks. the app asks for camera and microphone access on first launch. microphone access is only needed for the audio tools; the camera still works if you deny it.

ios: the release has ambivalence-unsigned.ipa. sign it with your own team before you install it, as apps/ios/README.md describes. on first launch, allow camera, microphone and local network access. without local network access the app cannot find the agent on your wi-fi.

no app: any phone browser works. see from a browser.

connect your agent

1. add the mcp server

claude code:

claude mcp add ambivalence -- ambivalence mcp

codex cli, in ~/.codex/config.toml:

[mcp_servers.ambivalence]
command = "ambivalence"
args = ["mcp"]

any other mcp client:

{
  "mcpServers": {
    "ambivalence": {
      "command": "ambivalence",
      "args": ["mcp"]
    }
  }
}

ambivalence mcp starts a hub on port 7842 inside the same process. you don't need to run ambivalence hub next to it.

2. pair once

open the app on the same wi-fi as the agent's computer. the app finds the hub and shows a 6-character code. give the code to your agent:

connect to my phone camera, the code is K9RBCY

the agent and the phone then both show a second code. if the two match, tap confirm on the phone. if they differ, tap cancel, because something between them is interfering.

3. use it

from then on, ask the agent whenever it needs to see something:

take a photo of the board and tell me which led is on

the mcp tools are:

tool does
camera_capture takes a photo and returns the jpeg to the model
camera_connect pairs with a code, or reconnects to the linked phone
camera_switch selects an exact camera ID, or the front or back default
camera_torch turns the torch on or off
camera_zoom sets the zoom factor
camera_list lists selectable lenses, zoom ranges and supported manual controls
camera_configure sets focus, shutter/ISO, exposure bias and white balance where supported
camera_status shows whether the phone is linked and online
camera_disconnect closes the current session; the link stays saved
audio_record records a clip up to 60 seconds and returns wav audio
audio_listen_start starts continuous listening; the phone buffers audio
audio_listen_next returns the next chunk, default 10 seconds, up to 30
audio_listen_stop closes the microphone
audio_level measures rms and peak dbfs without returning audio

See lens selection and manual controls for capability discovery, settings and platform limits, and listening and recording for audio limits, chunking and privacy.

without mcp

the cli does the same from a terminal:

ambivalence hub &                         # skip this if ambivalence mcp is running
ambivalence pair K9RBCY                   # once per phone
ambivalence capture -o shot.jpg           # any time after
ambivalence torch on
ambivalence cameras                       # discover exact lens IDs and capabilities
ambivalence switch front
ambivalence configure '{"focusMode":"manual","focusPosition":0}'
ambivalence zoom 2
ambivalence listen -o clip.wav               # 5 seconds by default
ambivalence listen --duration 0 -o long.wav  # until Ctrl+C
ambivalence level --duration 2s

reach the phone from other networks

on the same wi-fi you need nothing extra. hubs announce themselves over mdns, and the app connects to every hub it finds. guest and office networks often block devices from seeing each other. on those, and away from home, use one of the options below. you only set this up once, because the app saves the hub address.

tailscale

if the agent's computer and the phone are both on your tailnet, the phone can reach the hub from anywhere.

  1. install tailscale on the phone and sign in to the same tailnet.

  2. find the computer's tailnet address with tailscale ip -4, or use its magicdns name, such as my-laptop.tail1234.ts.net.

  3. in the app, type the hub url into the field at the bottom and tap connect:

    ws://my-laptop.tail1234.ts.net:7842/v1
    
  4. pair with the code as above.

typing the tailscale hub url into the app, then pairing

the hub listens on all interfaces by default (0.0.0.0:7842), so tailscale can reach it with no further setup. mdns does not cross the tailnet, which is why you type the address once. the app keeps this hub and also connects to hubs it finds on the local wi-fi.

for the browser page over tailscale you need https. tailscale serve provides it if https certificates are enabled for your tailnet:

tailscale serve --bg 7842
ambivalence hub --public https://my-laptop.tail1234.ts.net

the second command prints a qr code that opens the app already connected, and the https url for the browser.

cloudflare quick tunnel

ambivalence hub --tunnel

this needs cloudflared on your path. it prints a public https://….trycloudflare.com url and a qr code. scan the qr code with the phone's camera to open the app connected to the hub, or open the url in the phone's browser. the url changes every time you start the tunnel, so the phone needs the new one after a restart.

scanning the qr code from ambivalence hub --tunnel opens the app connected, then pairing

your own https proxy

if the hub already sits behind your own https reverse proxy, print the qr code with:

ambivalence hub --public https://camera.example.com

the proxy must pass websocket upgrades through on the /v1 path.

using a tunnel with mcp

ambivalence mcp runs its own hub, and two hubs cannot share port 7842. to put the mcp server behind a tunnel, run the hub yourself and start the mcp server without one:

ambivalence hub --tunnel
{ "command": "ambivalence", "args": ["mcp", "--no-hub"] }

with --no-hub, the mcp server connects to ws://127.0.0.1:7842/v1. set --hub or AMBIVALENCE_HUB to use a hub on another machine.

from a browser

the hub serves a camera and microphone page at /, so a phone can use its browser instead of the app. browsers only allow the camera and microphone on https pages, so open the page through one of the https options above (--tunnel, tailscale serve or your own proxy). allow camera and microphone access, and pair with the code it shows. the page shows a red recording badge while the agent is listening.

the browser page has three limits. it cannot find hubs on the wi-fi. it stops when you close the tab, though it reconnects by itself after a network drop while the tab is open. and it runs javascript that the hub serves, so a hub you don't control could read its link key. use the browser page only with a hub you run. the apps don't have this problem.

troubleshooting

the app shows no code: the phone and computer probably can't see each other. check that both are on the same network, that ios has local network access for the app, and that the computer's firewall allows tcp port 7842 and mdns (udp 5353). otherwise use tailscale or a tunnel.

the agent says linked phone is offline: open the app. the agent waits up to 30 seconds, then gives up.

the agent says phone is busy with another agent connection: another agent session holds the phone. close it, or wait for the phone to drop it, which takes up to a minute after that agent dies.

the agent says the phone rejected its link: the phone was paired with another agent since. a phone keeps one link at a time, so pair this agent again with the code on the phone.

the two confirmation codes differ: tap cancel and pair again. a mismatch means something between the phone and the agent changed the key exchange.

to unlink, pair again, or delete the device and key fields from the agent's config file.

how it works

flowchart LR
  agent["agent<br/>ambivalence mcp / cli"] -- websocket --> hub["hub<br/>relay"]
  phone["phone app<br/>camera + mic"] -- websocket --> hub
  agent <-. "end-to-end sealed" .-> phone

both the agent and the phone open a connection to a hub, so the phone needs no open ports. the hub passes messages between them and never sees a secret.

pairing runs a p-256 key exchange. both sides show a code derived from that exchange, and the user checks that they match. a hub in the middle that swapped in its own keys would produce different codes. both sides store the resulting link key.

in every later session, the phone and the agent each prove they hold the key. after that, aes-256-gcm encrypts every request, reply, photo and audio chunk, and each message must arrive in exact order. a rogue hub can refuse to relay, but it cannot see photos or hear audio, send its own requests, replay old ones or swap images.

the phone connects to its saved hub and to up to 4 hubs it discovers. the agent reaches it through whichever hub the agent uses.

docs/protocol.md has the full wire format.

where the link key lives

side storage
agent $XDG_CONFIG_HOME/ambivalence/config.json (usually ~/.config/ambivalence/config.json), mode 0600
ios app keychain, this device only
android app app-private shared preferences, not the hardware keystore
browser page localStorage of the hub's origin

cli reference

ambivalence hub [--listen 0.0.0.0:7842] [--tunnel | --public https://…]
ambivalence mcp [--listen 0.0.0.0:7842] [--hub ws://…] [--no-hub] [--public-hub wss://…]
ambivalence pair CODE [--hub ws://…] [--timeout 2m]
ambivalence capture [-o shot.jpg] [--quality 80] [--max-width 1280]
ambivalence status | cameras
ambivalence torch on|off
ambivalence switch front|back
ambivalence zoom FACTOR
ambivalence host fake [--state phone.json]   # a test phone that serves a test pattern
ambivalence version
variable meaning
AMBIVALENCE_HUB hub url the agent connects to, default ws://127.0.0.1:7842/v1
AMBIVALENCE_LISTEN address the hub listens on, default 0.0.0.0:7842
AMBIVALENCE_PUBLIC_HUB public wss:// url that camera_status reports to the agent

build from source

go test ./...
go build -o bin/ambivalence ./cmd/ambivalence
bash scripts/e2e-fake.sh   # pairs a fake phone, restarts it and reconnects
part stack build
cli, mcp server, hub go one static binary
android app kotlin, camerax scripts/build-apk.sh (docker and android sdk 35)
ios app swift, avfoundation, cryptokit xcodegen and xcode, see apps/ios/README.md
browser page html and javascript served by the hub from internal/hub/web/index.html

scripts/browserstack-smoke.py runs the whole flow on a real phone on browserstack. it pairs, checks that the confirmation codes match, taps confirm, takes photos, toggles the torch, switches cameras, restarts the app and takes another photo without a code.

license

mit

deterministic browser flow tests

The root development-only package.json is separate from the published npm/
package. Run npm ci, npx e2e-web install chromium, and npm run test:e2e.
The tester-army/e2e suite uses no model calls and simulates camera denial rather
than requesting access to real hardware. It covers the host controls, invalid
hub addresses, mobile layout and recoverable errors. Existing Go tests,
scripts/e2e-fake.sh and browser-control unit tests cover separate contracts.
Physical camera quality, hardware constraints and native mobile acceptance still
require device testing; a simulated browser pass does not establish those.

Yorumlar (0)

Sonuc bulunamadi