Blog

Engineering3 min read

How we built MCP1 on Appwrite

Appwrite’s OAuth2 server for every client, Teams for workspaces, TablesDB for the audit log and Sites for the rest. The stack behind MCP1.

The MCP1 teamAppwrite
AuthDatabasesStorageSites Built on Appwrite
AuthOAuth2 and TeamsDatabasesTablesDBStorageLogos and photosSitesHosting Built on Appwrite

MCP1 is an MCP gateway: one URL in front of every MCP server a team uses, with authentication, policies and an audit log. It’s also an Appwrite app, top to bottom. There’s no separate identity provider, database or host to run. Everything MCP1 needs is a service in one Appwrite project.

ServiceUsed for
Auth
Accounts, sessions, sign-in links, recovery and two-factor sign-in
Teams
Workspaces, roles and invitations
OAuth2
Every MCP client’s authentication: registration, PKCE, consent, JWTs, revocation and devices
Databases
Gateways, groups, servers, tools, keys, clients and the audit log, in TablesDB
Storage
Workspace logos and profile photos
Sites
The dashboard, the docs, this blog and the gateway itself, server-rendered

Appwrite is the authorization server

The hardest part of a gateway is authentication. MCP clients expect OAuth 2.1, and they expect to find it on their own. Appwrite has an OAuth2 server built in, so MCP1 uses its project’s server directly instead of running one of its own.

  1. 1A client calls a gateway URL without a token. MCP1 answers 401 with a pointer to its protected resource metadata (RFC 9728), which names the Appwrite project as the authorization server.
  2. 2The client registers itself with Appwrite (RFC 7591). There’s no list of clients to maintain, so any client that follows the standard works the day it ships.
  3. 3The human authenticates in the browser, with PKCE. MCP1 hosts the consent screen, where they pick the workspace.
  4. 4Appwrite issues a signed JWT access token whose audience is the gateway URL the client asked for (RFC 8707).
  5. 5On every call, MCP1 verifies the token against the project’s published keys and opens only the workspace it was approved for.

Headless tools use the device flow instead: they show a code, and the human enters it at /oauth/device. Tokens last eight hours and refresh on their own. Revoking a client from the dashboard ends its access on its next call.

Workspaces are Teams

Each workspace is an Appwrite Team, and its roles map one to one to team membership roles: owner, admin, developer, member and auditor. Invitations, accepting them and leaving are Teams features, so MCP1 didn’t build any of them.

The gateway looks up the caller’s membership as it answers, so removing someone from the team ends their access everywhere within moments: every client they approved and every key they made.

A stateless gateway

The gateway is a stateless Streamable HTTP server. Each request carries its token. MCP1 resolves the workspace and gateway from the URL, loads the caller’s role and groups, and decides each tool against the workspace’s policy, in a fixed order: gateway access, server, tool rule, read-only grant, rules by name, guardrails, rate limit, and a confirmation where one is needed.

Tools are exposed as server__tool, like github__create_issue, so names from different servers never collide. Calls go on to the upstream servers over HTTP or SSE, and interactive views (MCP Apps) and structured results pass through. Because no call depends on the one before it, any instance can answer any request.

The audit log is a table

Gateways, groups, servers, tools, keys, clients, catalog submissions and the audit log are tables in one Appwrite database. The audit log is the busiest: a row for every call, authentication and change, with the human, client, gateway, tool, duration and result. The dashboard follows it live, filters it and exports it as CSV.

Secrets, sealed

  • Upstream server tokens, OAuth grants, call arguments and IP addresses are sealed with AES-256-GCM before they reach the database. Secrets in arguments are masked first.
  • API keys are stored only as SHA-256 hashes, so a key can be checked but never read back.
  • Anyone can add a server, so the hosted MCP1 refuses to connect to private and local addresses: loopback, private ranges and cloud metadata endpoints.

One deploy

The dashboard, the docs, the server catalog, this blog and the gateway are one TanStack Start app, server-rendered on Appwrite Sites. One deploy ships all of it, and the gateway’s URL never changes.

What we learned

  • Standards do the integration work. Because clients discover the server and register themselves, we never wrote a client integration. New clients work when they ship.
  • The platform shaped the product. Teams gave us roles and invitations, OAuth2 gave us every client, and the audit log is just another table. Most of our time went into policy and the gateway itself.
  • Running it ourselves keeps us honest. We run Appwrite’s work through MCP1, and MCP1 on Appwrite. When something is rough in either, we feel it first.

Every service MCP1 uses is part of Appwrite. If you’re building something of your own, start at appwrite.io.

Give your team one URL

Create a workspace, install your servers and share a gateway with a group. Free for one human.