Skip to content

Protect an MCP server

Availability: this walkthrough describes a service template available through beta repository access. It is not a public standalone starter. You need an accessible Udibo tenant. For runnable public package examples, see starter projects.

The template runs a stateless MCP server on Deno and Hono. Its team_notes tool returns sample notes for one configured organization. The server accepts people whose access token names that organization and carries notes:read.

Register the client and grant access

Choose the server's canonical external URL, ending in /mcp, such as https://notes.example.com/mcp. In your tenant, define notes:read, create a role carrying it, and grant that role to the intended people inside the organization. Follow permissions for role assignment.

Manually register the MCP client's application with these settings:

  • Type: third-party. Hosted authorization requests consent for scopes the person has not already approved.

  • Client authentication: public (confidential: false), with no secret.

  • Grant: authorization code.

  • Access token format: JWT.

  • Audience: the exact external MCP URL, including /mcp.

  • Redirect URI: the client's IP-loopback callback, including the path and any fixed query parameters. Production registrations permit HTTP callbacks on 127.0.0.1 or [::1] and allow an ephemeral port. A localhost hostname is not the production loopback exception.

See application registration for the dashboard and API workflow. Dynamic client registration is not part of this starter.

Configure your MCP client with its registered client ID and openid profile scopes. Permission names are separate from OAuth scopes. The client must use PKCE S256, generate and verify OAuth state, and request the intended organization with the hosted authorization endpoint's organization slug parameter. The TypeScript SDK's OAuth provider supports state() but makes it optional; supply it for Udibo. Sign in and approve the displayed consent.

Configure the server

Set three environment values in the template's .env:

  • UDIBO_ISSUER: your tenant origin, such as https://<tenant-id>.udibo.com.

  • MCP_RESOURCE_URL: the canonical external server URL ending in /mcp.

  • MCP_ORGANIZATION_ID: the ID of the organization whose notes it exposes.

Run deno task serve from templates/mcp-server in the template checkout. The development defaults listen on 127.0.0.1:8010 and identify the resource as http://127.0.0.1:8010/mcp. Set DENO_SERVE_ADDRESS for your deployment, serve HTTPS through your proxy, and preserve the canonical Host header. The server rejects other Host values and foreign Origin headers; this template is intended for native MCP clients, not cross-origin browser clients.

Checkpoint: call /mcp without a bearer token. It should return 401 with a WWW-Authenticate header containing resource_metadata. Fetch that URL: its resource must be this MCP server's URL, and its authorization_servers must contain your tenant origin. The client uses those documents to discover the hosted authorization and token endpoints.

Verify a tool call and a refusal

Connect the MCP client using Streamable HTTP. After authorization, initialize the connection, list tools and call team_notes. A successful call returns the configured organization ID and a sample welcome note.

Repeat with a member who has no notes:read grant, and with a permitted person acting in another organization. Both must receive 403. The organization check compares the verified ID exactly; another organization's matching slug does not grant access. A token from another tenant or for another audience must receive 401. Machine credentials cannot supply the required openid scope.

The server creates a fresh transport per POST and returns JSON without a server-side session ID. GET streams and session DELETE return 405. Each request must carry its own bearer token in the Authorization header.

Keep the boundaries when extending it

Replace the sample notes with your application's storage and keep its queries scoped to the configured organization. This starter demonstrates a read-only tool, not tenant administration or resource-level sharing.

Udibo currently mints the application's fixed registered audience. MCP clients send resource on authorization and token requests, but that parameter does not select or narrow token issuance. Register a separate application for another resource; this is not general RFC 8707 resource selection. Never pass the MCP token onward to UserInfo, /api/check, or another API.

JWT validation uses the tenant's public keys and checks signature, access-token type, issuer, audience and expiry. It does not perform an immediate revocation lookup. Choose an access-token lifetime appropriate for your data and keep host clocks synchronized: the starter allows no clock leeway. See application registration and the production checklist before deploying.

Last verified 2026-09-09.