Protocol test
The protocol test service is a public test partner for the OpenBot App Protocol. It publishes made-up Apps that cover each way to connect and sign in. Use it to test a host, a client, or OpenBot itself.
The service lives in the protocol-test folder of the repository. Its package name is @openbot-build/protocol-test. It uses MCP 2026-07-28, Streamable HTTP over HTTPS, and MCP Apps 2026-01-26. The examples use https://protocol-test.example.com as its origin.
A running service also serves a full plain-text guide at /guide.
Scenarios
| App ID | MCP path | Credentials |
|---|---|---|
anonymous |
/mcp/anonymous |
None |
api-key |
/mcp/api-key |
X-API-Key: protocol-test-api-key |
bearer |
/mcp/bearer |
Authorization: Bearer protocol-test-bearer-token |
basic |
/mcp/basic |
Authorization: Basic cHJvdG9jb2wtdGVzdDpwcm90b2NvbC10ZXN0LXBhc3N3b3Jk |
oauth-cimd |
/mcp/oauth-cimd |
Client ID Metadata Document and public PKCE |
oauth-dcr |
/mcp/oauth-dcr |
Dynamic client registration, public or confidential PKCE |
oauth-preregistered |
/mcp/oauth-preregistered |
A registered public or confidential client with PKCE |
oauth-client-credentials |
/mcp/oauth-client-credentials |
A confidential machine client |
Each App has a page at /apps/<id>, a Catalog at /apps/<id>/catalog.json, and a Card at /apps/<id>/server-card.json. The root Catalog at /.well-known/ai-catalog.json lists them all. /scenarios.json lists every scenario and fixture as JSON. /healthz and /readyz report status.
To try it in OpenBot, connect protocol-test.example.com as described in Apps.
Data lifetime
| Data | Lasts |
|---|---|
| Workspace | 30 minutes |
| Access token | 5 minutes |
| Refresh grant | 1 hour |
| Authorization code | 1 minute |
| Consent request | 10 minutes |
Each scenario has its own workspaces. In OAuth scenarios, each account also has its own. Other scenarios share data, so pick a workspace ID that nobody else uses.
Public test credentials
| Client ID | Authentication | Callbacks |
|---|---|---|
https://protocol-test.example.com/clients/public.json |
none, CIMD |
https://protocol-test.example.com/callback |
protocol-test-public |
none |
Public /callback, http://127.0.0.1:8765/callback, http://localhost:8765/callback |
protocol-test-confidential-basic |
client_secret_basic |
Public /callback |
protocol-test-confidential-post |
client_secret_post |
Public /callback |
Both confidential clients use the secret protocol-test-client-secret. For other callbacks, use your own public CIMD document, or dynamic registration at /oauth/dcr/register.
The sign-in page lets you allow or deny access as Alice or Bob.
| Scope | Allows |
|---|---|
test:read |
Discovery and reads. |
test:write |
Also write_note and delete_note. Ask for both. |
OAuth flows
Each mode (cimd, dcr, preregistered, client-credentials) has its own issuer at /oauth/<mode>. The issuer metadata is at /.well-known/oauth-authorization-server/oauth/<mode>. The Protected Resource Metadata is at /.well-known/oauth-protected-resource/mcp/oauth-<mode>.
For a browser sign-in, open /oauth/<mode>/authorize with response_type=code, client_id, the exact redirect_uri, resource, scope, state, and an S256 code_challenge. Then trade the code once at /oauth/<mode>/token with the PKCE verifier. Each request must send the exact resource, such as https://protocol-test.example.com/mcp/oauth-cimd.
Get a machine token:
curl https://protocol-test.example.com/oauth/client-credentials/token \ --data-urlencode grant_type=client_credentials \ --data-urlencode client_id=protocol-test-confidential-post \ --data-urlencode client_secret=protocol-test-client-secret \ --data-urlencode resource=https://protocol-test.example.com/mcp/oauth-client-credentials \ --data-urlencode scope='test:read test:write'A read-only token hides the write tools. A write without test:write gets 403. A bad, expired, revoked, or wrong-resource token gets 401. A refresh never adds scopes. /oauth/<mode>/revoke revokes all tokens of the grant.
What you can test
- Tools.
echo,add,identity,content_samples,read_workspace,write_note(with anidempotencyKey),delete_note(with anexpectedRevision),ui_read_workspace(App-only),test_error,wait, andnotify. - Follow-up input.
request_form,request_url,request_sampling, andrequest_rootstake{ "text": "..." }and ask the client for more input. - Resources.
protocol-test://about,protocol-test://binary,protocol-test://workspace/<workspace>, and 110 fixtures for paging. - Prompt.
summarize_workspace, with completion. - View.
ui://protocol-test/workspace.html, an MCP Apps view of a workspace.
Discovery and indexing fixtures
Each fixture has a Card at /fixtures/<id>/server-card.json.
| ID | What you should see |
|---|---|
noindex |
A listed Card with X-Robots-Tag: noindex |
robots-denied |
A listed Card blocked by /robots.txt |
unlisted |
A valid Card that no Catalog lists |
withdrawn |
410 Gone |
malformed |
Broken JSON |
incompatible |
Only an unsupported protocol version and transport |
redirect |
A redirect to the anonymous Card |
unavailable |
503 with Retry-After |
Run it locally
From the repository root:
devenv shell -- pnpm --filter @openbot-build/protocol builddevenv shell -- pnpm --filter @openbot-build/protocol-test builddevenv shell -- pnpm --filter @openbot-build/protocol-test testPROTOCOL_TEST_PUBLIC_ORIGIN=https://protocol-test.example.com \ devenv shell -- pnpm --filter @openbot-build/protocol-test startThe service speaks HTTP by default. It expects a TLS proxy in front of it. To serve HTTPS directly on your computer, set the origin to https://localhost:7445, the port to 7445, and both TLS paths. Then make your host trust the certificate.
Run it with Docker
The protocol-test target of deploy/docker/Dockerfile makes the image. Build it and start it:
devenv shell -- docker buildx bake -f deploy/docker/docker-bake.hcl --load protocol-testdevenv shell -- docker run -d --name protocol-test -p 127.0.0.1:3001:3001 \ -e PROTOCOL_TEST_PUBLIC_ORIGIN=https://protocol-test.example.com \ openbot-protocol-test:localThe service listens on 127.0.0.1:3001 only. Put a TLS proxy that supports streaming in front of it. deploy/docker/protocol-test.nginx.conf is an example. deploy/docker/compose.production.yaml runs the openbot-protocol-test image in the same way, with no database. Run only one copy, because all data lives in one process.
Configuration
| Variable | Default | Purpose |
|---|---|---|
PROTOCOL_TEST_PUBLIC_ORIGIN |
Required | The exact public HTTPS origin, with no path. |
PROTOCOL_TEST_HOST |
0.0.0.0 |
The address to listen on. |
PROTOCOL_TEST_PORT |
3001 |
The port to listen on. |
PROTOCOL_TEST_ALLOWED_ORIGINS |
The public origin | Browser origins that may call it, split by commas. |
PROTOCOL_TEST_TLS_KEY, PROTOCOL_TEST_TLS_CERT |
Not set | File paths. Set both to serve HTTPS directly. |
Server clients send no Origin header, so they always work. A browser client works only when its exact origin is in PROTOCOL_TEST_ALLOWED_ORIGINS.