What your agent gets
A QR code encodes a URL for as long as the ink exists. qr-relay makes that URL one you own the meaning of: the printed pattern never changes, and the destination behind it is editable at any moment. An agent connected here can give a physical thing a permanent address, then re-point it a year later.
- A permanent address — minted in one call, returned ready to print. It is the one thing here that can never be changed afterwards.
- An editable destination — re-point a code and every copy already in the world follows, within a minute, with no reprint.
- Your workspaces, named — an agent acts only in workspaces its human belongs to, with its human’s role there; in more than one, every call names which.
- A history — every destination change is recorded, and a change made by an agent is marked as one.
Connect
One URL, and a browser sign-in. There is no API key to paste, copy into a config file, or leak into a chat log — authorization is OAuth against the same identity provider the web app uses, so the agent’s access is your access, checked on every call: if a workspace admin changes your role or removes you, your agent is held to that on its very next call. There is no page in the app that lists connected agents or disconnects one yet; to stop an agent, remove this server from that agent’s own settings.
https://qr-codes.ink/mcpOr paste this into an agent that can fetch a URL — the setup document walks it through registering the server in its own harness:
Connect yourself to my qr-relay workspace: fetch https://qr-codes.ink/setup.md and follow the instructions.
Claude Code
claude mcp add --transport http qr-relay https://qr-codes.ink/mcpThen run /mcp inside Claude Code and authorize. A browser opens, you sign in to qr-relay as yourself, and the agent can then do what your role allows in your workspaces — nothing more.
Cursor
{
"mcpServers": {
"qr-relay": { "url": "https://qr-codes.ink/mcp" }
}
}In .cursor/mcp.json (this project) or ~/.cursor/mcp.json (everywhere).
Codex CLI
[mcp_servers.qr-relay]
url = "https://qr-codes.ink/mcp"Appended to ~/.codex/config.toml.
Anything else that speaks MCP
Streamable HTTP at https://qr-codes.ink/mcp, OAuth 2.1 with WorkOS AuthKit as the authorization server. Discovery is at /.well-known/oauth-protected-resource; the endpoint answers POST only, and is stateless — no session to open, no event stream to hold.
Tools
| Tool | Scope | What it does |
|---|---|---|
list_workspaces | codes:read | List the workspaces you belong to, with each one’s id, name and your role there. Call this first if you are not sure which workspace to act in, then pass its name or id as workspace to the other tools. |
list_qr_codes | codes:read | List the QR codes in a workspace, with the address each one is printed with and where it currently points. |
get_qr_code | codes:read | Look up one QR code in a workspace by its id or its slug, and report where it currently points. |
create_qr_code | codes:write | Mint a new QR code in a workspace pointing at a destination, and return the value to print. By default the code is relayed: the printed address never changes and the destination stays editable. Pass kind "static" to print the destination directly into the pattern instead — nothing can change it afterwards and its scans are never counted. print_host chooses which address a relayed code carries: "workspace" (the default) uses the workspace’s own connected domain when it has one, "platform" pins the code to qr-codes.ink and needs a connected domain to mean anything different. |
set_qr_destination | codes:write | Re-point an existing QR code in a workspace at a new destination. Anything already printed keeps working and starts leading to the new place within a minute. |
Reconnect after any change here — MCP clients cache the tool list for the life of a session.
Workspaces
Every tool except list_workspaces acts in exactly one workspace. If you belong to one, that is the one, and nothing needs saying. If you belong to more than one — your own and a client’s, say — each call names which with its workspace argument: the workspace’s id, or its name (any case). list_workspaces lists them, with your role in each.
- No default. From someone in more than one workspace, a call that names none is refused with the list of them, and nothing is read or changed. An agent never acts in a workspace nobody chose.
- Only yours. A workspace you do not belong to is refused as unknown, whatever id or name is given, and the refusal says nothing about it.
- Your role there. Each call is checked against your role in the workspace it names: an Editor in one workspace and a Viewer in another can change QR codes only in the first.
- Every answer says where. Each result carries
workspace— its id and name — so an agent can tell you which workspace it just acted in.
Permissions
Your role is the lock. Signing in asks for no qr-relay permission of its own: the token proves who you are, and every call is checked on the server against your role in the workspace it acts in. An agent can do exactly what you could do in the app there, and never more — a Viewer’s agent can look, but cannot mint or re-point.
A token can be narrowed, never widened. The server also honours two qr-relay scopes — codes:read and codes:write — when a token carries them: a codes:read token can look but never change anything. Today’s sign-in issues neither, so in practice your role is the limit that applies.
What no scope grants. There is no scope that deletes a QR code, pauses one, changes workspace settings, or manages teammates. Retiring a pattern that has been printed on ten thousand labels is a decision with consequences an agent cannot see, so it stays a human act, made in the workspace.
Minting is capped per workspace per day. The cap is generous enough that ordinary work never meets it, and low enough that a retry loop cannot mint a hundred thousand codes overnight.
Verify a connection
Ask the agent to list your QR codes. A working connection answers with the codes in your workspace, each with the address it prints and where it currently points; a workspace with none answers with an empty list rather than an error.
If tools are missing after a change, reconnect — the tool list is cached per session. If a call is refused because you belong to more than one workspace, the refusal lists them: tell the agent which one. If it is refused for your role, the fix is in the workspace, not the connection — a workspace admin can change your role. If it is refused for scope, the token carries narrower qr-relay scopes than the call needs, and reconnecting mints a new one.
For agents
- MCP endpoint:
https://qr-codes.ink/mcp(Streamable HTTP,POSTonly) - Setup instructions: https://qr-codes.ink/setup.md
- Overview and link index: https://qr-codes.ink/llms.txt
- This page as markdown: https://qr-codes.ink/docs.md
- Discovery:
https://qr-codes.ink/.well-known/oauth-protected-resource