- TypeScript 55.5%
- Nix 44.5%
| nix | ||
| src | ||
| tests | ||
| .gitignore | ||
| .prettierrc | ||
| bun.lock | ||
| bun.nix | ||
| bunfig.toml | ||
| flake.lock | ||
| flake.nix | ||
| LICENSE | ||
| package.json | ||
| README.md | ||
| THIRD_PARTY_NOTICES.md | ||
| tsconfig.json | ||
bard
bard connects the official Gemini web and mobile apps (Connected Apps → Custom apps) to your own machine over MCP. Once linked, Gemini can read files, edit code, and run commands on your host from your browser or phone.
Tools run directly on the host as the service user — there is no container or sandbox. Access is gated by a built-in, single-owner OAuth 2.1 server whose approval page stays dead-bolted until you arm a single-use, 10-minute link code from the server shell.
Tools
bard exposes OMP's core workspace tools plus durable background Fish jobs. read, glob, and grep are annotated read-only; everything else is marked destructive so Gemini asks before changing files or running commands.
| Tools | Mode | Description |
|---|---|---|
read, glob, grep |
Read-only | Read files, match paths, and search contents without confirmation prompts |
write, edit |
Confirmation required | Create files or apply surgical edits |
bash |
Confirmation required | Run a foreground Fish command and return its output |
process, jobs |
Confirmation required | Start, inspect, and stop long-running background Fish jobs |
Setup
1. Run the server
Put bard behind a TLS reverse proxy (Caddy, nginx, Cloudflare Tunnel…) and pass its public origin:
HOST=127.0.0.1 PORT=3000 ORIGIN=https://bard.example.com BARD_WORKSPACE=$HOME nix run .#bard
Caddy example:
bard.example.com {
reverse_proxy 127.0.0.1:3000
}
| Variable | Default | Meaning |
|---|---|---|
HOST |
127.0.0.1 |
Listen address |
PORT |
3000 |
Listen port |
ORIGIN |
from X-Forwarded-Proto/-Host |
Public https:// origin used in OAuth metadata; set explicitly in production |
BARD_WORKSPACE |
current directory | Tool working directory |
BARD_STATE_DIR |
$XDG_STATE_HOME/bard (~/.local/state) |
Private (0700) state: auth.sqlite, workspace/jobs.json |
BARD_WORKSPACE_TOOLS |
all eight | Comma-separated allowlist of read,write,edit,glob,grep,bash,process,jobs |
For production, run bard under systemd. Background jobs start in their own session, so if bard dies uncleanly (SIGKILL, OOM), only cgroup teardown stops them — systemd's default KillMode=control-group handles this automatically, and bard marks those jobs interrupted on restart. A dropped HTTP connection aborts its foreground bash command, so keep proxy read timeouts generous and use process for long work.
2. Link Gemini
- On the server, arm a link window (using the same
BARD_STATE_DIRas the server):
This prints the Client ID, Client secret, and a one-time link code valid for 10 minutes.nix run .#bard-cli -- link - Open gemini.google.com → Settings → Connected Apps → + Custom (Create custom app).
- Enter MCP Server URL
https://bard.example.com/mcp, expand Additional settings, paste the Client ID and Client secret, and click Next. (barddoes not support dynamic client registration, so credentials are required.) - Under Connect this MCP server, tick the risk acknowledgement and click Connect.
- On Google's Link your Google Account and [Custom] … account prompt, click Agree and continue.
- In
bard's approval popup, enter the 10-minute link code and click Approve. Google redirects back and exchanges the code at/token. - Under Save your custom app, verify
bard's tools are listed and click Connect.bardnow appears under Custom apps on both web and mobile.
To re-link later, run bard-cli link again for a fresh code; the Client ID and secret stay the same unless you run rotate-client.
Troubleshooting
- "Account linking is required to use this custom app" — Google never redeemed the authorization code (
bard-cli statuswill show zero active tokens). Browser extensions that inject into pages (password managers, autofill) can break the popup redirect; disable them or set their site access to "On click" forgemini.google.com, then link again. - Gemini answers from its own sandbox (for example, showing a
gvisorkernel) instead of callingbard— either prefix the prompt with@Bardor add the standing instructions in step 3.
3. Tell Gemini about bard
bard's MCP initialize response and tool descriptions already identify your host (hostname, OS, user, working directory), but Gemini still decides per prompt whether to invoke a connected app. Giving it standing context prevents it from falling back to its own cloud sandbox.
Paste this into Gemini under Settings → Personal Intelligence → Instructions for Gemini, filling in your host details:
I have a custom connected app called Bard. It is my own MCP server running on my computer
(host "<hostname>", <OS>, user "<user>"). Bard is always available to you and is how you reach
my real machine.
- Anything about "my PC", "my computer", my system, files, projects, configs, running processes,
logs, or "run/check/fix X": use Bard's tools. Never answer those from your own code sandbox.
Its environment is not my machine.
- Bard tools: read, glob, grep (inspect files), write and edit (change files), bash (run a
command and return its output), process and jobs (start and manage long-running background
Fish commands).
- Check first, then answer: run the command or read the file instead of guessing or telling me
to run it myself.
- Use process for anything that may take more than a minute (builds, downloads, rebuilds), then
check on it with jobs.
- Before changing files or running anything destructive, briefly say what you'll do. I'll
confirm in the approval prompt.
- Act like my personal assistant with hands on my machine: do the task end to end, then report
what you ran and what you found.
Managing access (bard-cli)
| Command | Effect |
|---|---|
bard-cli link |
Print Client ID/Secret and arm a 10-minute link code (5 wrong tries burn it) |
bard-cli status |
Show Client ID, pinned redirect URI, link window, and active token counts |
bard-cli revoke |
Invalidate every token, auth code, and link code immediately |
bard-cli rotate-client |
Generate a new Client ID/Secret, unpin the redirect URI, and revoke everything |
bard-cli unpin-redirect |
Allow the next approved link to pin a new callback URL |
How it works & security model
OAuth 2.1 flow
bard runs a self-hosted OAuth 2.1 authorization server (authorization code + PKCE S256 with a static confidential client).
sequenceDiagram
participant You
participant Gemini as Gemini (Google)
participant Bard as bard
You->>Bard: bard-cli link (prints Client ID, Secret, link code)
You->>Gemini: MCP URL + Client ID + Client secret, Next
Gemini->>You: popup → https://host/authorize
You->>Bard: enter link code, Approve
Bard-->>Gemini: 302 to Google callback with auth code
Gemini->>Bard: POST /token (client credentials + PKCE verifier)
Bard-->>Gemini: access token (1 h) + refresh token (30 d, sliding)
- Persistent linking: Google stores the tokens and refreshes them silently. Each refresh extends the refresh token's 30-day expiry instead of rotating it, keeping web and mobile connected until you run
bard-cli revoke. - Callback pinning: The first approved link pins Google's callback URL; subsequent authorizations must match it unless cleared with
bard-cli unpin-redirect. - Credential storage: Access tokens, refresh tokens, auth codes, and link codes are stored only as SHA-256 hashes. The client secret is stored in the
0600SQLite database (auth.sqlite) sobard-cli linkcan print it. - Endpoints:
/.well-known/oauth-protected-resource[/mcp],/.well-known/oauth-authorization-server(and/openid-configuration),/authorize,/token,/mcp. Dynamic client registration is intentionally omitted.
Host execution & state
- Environment scrubbing: At startup,
bardstrips the process environment (including the Cenvironinherited by the native shell) down to a minimal allowlist (PATH,HOME, locale, TLS…) soenvdoes not leak service credentials into model context. This is hygiene, not isolation: tools run as your user and can read anything your user can, including/proc/self/environandbard's state directory. - Transport & job persistence:
/mcpuses stateless Streamable HTTP with JSON responses, while shell sessions and background jobs live in the long-running server process across requests. Job metadata persists inworkspace/jobs.jsonand retains the newest 100 finished jobs.
Development
nix develop
bun install
bun run check && bun test
bun2nix -o bun.nix # after dependency changes