The usual way to give an agent access to your email is to give it your email. OAuth scopes are coarse, the consent screen is a one-time decision, and after that the only thing standing between "read my inbox" and "send as me" is a sentence in a system prompt asking the model not to. That is a policy the model is invited to respect, not one anything enforces.
Lanes Link puts the boundary in the runtime instead. Every capability is denied until you allow it, and policy only ever tightens as a request travels inward. gmail.search = allow and gmail.send = deny are decisions the endpoint enforces before dispatching anything, whatever the model was told or convinced to do.
How Lanes Link does it
- Deny by default. Nothing is reachable until you allow it. Widening access is a decision you make deliberately, rather than the state you land in by accepting a consent screen.
- Per capability, not per account. Search and send are separate capabilities on the same connection, so read-only access to a mailbox is a thing you can actually express.
- Policy tightens inward, never loosens. A request cannot pick up permissions on its way through the endpoint, so a broad grant at one layer is not a way around a narrow one further in.
- One audit event per invocation. Append-only, with per-provider redaction that keeps the identifiers and withholds your content, so the log tells you what happened without becoming a second copy of your mail.
- Refusals are recorded too.
lanes link audit tail --denied-onlyprints just those, which is the fastest way to see an agent repeatedly trying something you did not grant. - Tamper-evident. Records are hash-chained per run, and
lanes link audit verifywalks every chain. - No database to secure. State and the audit log are objects in a blob store: a directory on your machine locally, a bucket when deployed.
The workflow
Grant narrowly and widen when something is actually blocked. A refused call is the permission system working, not an obstacle to route around: a well-briefed agent reports what it was refused and lets you decide whether to open it, rather than looking for another path to the same data.
Then read the log. Because every invocation produces exactly one record, "what did that agent actually touch last night" is a question with an answer, and so is "what did it try to touch." That second one is usually the more interesting log, and it is the one most setups do not keep at all.
Set it up
Install it, start the endpoint, and connect an account:
$ bun install -g @lanes-sh/link
$ lanes link profile add personal --default
$ lanes link start
ok serving http://127.0.0.1:7337/mcp
profiles: personal
$ lanes link connect gmailThen:
- Allow only what the work needs. Leave everything else denied. Read access is often the whole job.
- Register the endpoint.
lanes link mcp addinstalls a skill that tells the agent to surface refusals to you rather than working around them. - Watch what gets refused.
lanes link audit tail --denied-onlyshows the calls policy stopped, which is how you find both a scope that is too tight and an agent doing more than you expected. - Verify the chain.
lanes link audit verifywalks every hash chain when you want proof the log has not been edited.
Further reading
- Lanes Link: one endpoint for everything an agent needs: the permission model and audit design in full.
- Connect your accounts once, for every agent: one grant per account, shared by every client.
- Keep work and personal accounts apart: the profile boundary that sits above per-capability policy.
- Give every agent the same memory: why writing to memory is its own grant.
Browse the source at lanes-sh/link, Apache-2.0, or read the announcement.