Render Tutorials
Build and host a full-featured, secure MCP server on Render

Connect a real MCP client

⏱ 7 min

The server is live. Time to use it from the kind of client you actually ship to users.

1. Smoke-test with the MCP Inspector

Same drill as step 3, but the URL changes to your deployed one.

Terminal
npx @modelcontextprotocol/inspector

In the connection form:

Hit Connect. The inspector detects the 401 + WWW-Authenticate and runs the full OAuth flow you debugged locally in step 5 - now against the production URL.

A few things to confirm in the network panel:

RequestStatusWhat to check
GET /.well-known/oauth-protected-resource200resource and authorization_servers are the HTTPS Render URL, not http://. If you see http://, something is mangling PUBLIC_URL - check the Environment tab in the Render Dashboard.
POST /register201Dynamic client registration worked; the response carries a fresh client_id.
GitHub redirect302The browser hops through github.com/login/oauth/authorize and back to /oauth/github/callback.
POST /token200Returns a JWT. Audience claim is https://notes-mcp-abc1.onrender.com/mcp.
POST /mcp (with Bearer)200First successful tool list.

Call notes.create once. Then jump to the Render Dashboard’s Logs tab and confirm the structured log line shows up with your GitHub user field:

Render Logs
{"level":"info","time":1718900923012,"req":{"method":"POST","url":"/mcp","id":"a1c..."},"res":{"statusCode":200},"responseTime":58,"user":"github:1234567","msg":"request completed"}

That single line tells you: the request was authenticated, the user’s GitHub identity made it through, the response was fast, and the request ID gives you a thread to pull on if anything went wrong.

2. Client configs

The template’s README ships configs for the three big clients. Your config differs in exactly one way: we don’t send an Authorization: Bearer header, because the server returns the WWW-Authenticate challenge that tells the client to do OAuth on its own. The header would short-circuit that.

Open Settings -> Connectors -> Add custom connector (label moves between versions; on macOS it’s under the Claude menu).

FieldValue
Namenotes-mcp
Server URLhttps://notes-mcp-abc1.onrender.com/mcp
AuthenticationOAuth (selected automatically once Claude sees the WWW-Authenticate header)

Click Add. Claude pops the GitHub OAuth window. Approve, and the connector turns green.

Add to your project’s .cursor/mcp.json:

.cursor/mcp.json
{
"mcpServers": {
"notes-mcp": {
"url": "https://notes-mcp-abc1.onrender.com/mcp"
}
}
}

No headers block - Cursor sees the 401 + WWW-Authenticate and runs the OAuth flow.

Terminal
codex mcp add --transport streamable-http \
--url https://notes-mcp-abc1.onrender.com/mcp \
notes-mcp

Or declare it in .codex/config.toml:

.codex/config.toml
[mcp_servers.notes-mcp]
url = "https://notes-mcp-abc1.onrender.com/mcp"

Same pattern: no header block, OAuth handled by the client.

3. Drive it from Claude

Ask Claude something that needs the tool:

Prompt
Save a note titled "Render MCP tutorial" with the body
"Connected from Claude Desktop, end-to-end OAuth works."
Then read back the 3 most recent notes.

You’ll see Claude pick notes.create from its tool list, run it, then read notes://recent and summarize. Two MCP calls; both authenticated; both logged with your GitHub identity in the Render logs.

4. Inspect what’s reaching the server

This is a good moment to learn the Render logs query syntax. Two queries you’ll want to keep in your back pocket:

Logs query - every authenticated MCP call from your account
service:notes-mcp user:"github:1234567" url:/mcp
Logs query - every failed request
service:notes-mcp level:error

The user: filter only works because you wired customProps to include sub on every log line in step 6. Without that, you’d be grepping JWT payloads by hand.

5. End-to-end picture

sequenceDiagram
  participant U as You (GitHub login)
  participant C as Claude Desktop
  participant R as notes-mcp.onrender.com
  participant DB as Render Postgres<br/>(private)

  C->>R: POST /mcp (no auth)
  R-->>C: 401 + WWW-Authenticate
  C->>R: discovery + dynamic registration
  C->>U: Open browser -> GitHub OAuth
  U-->>R: callback with code
  R-->>C: redirect with the code
  C->>R: POST /token (PKCE)
  R-->>C: JWT (aud:.../mcp)
  C->>R: POST /mcp tools/call notes.create<br/>+ Bearer JWT
  R->>DB: INSERT INTO notes
  R-->>C: { structuredContent: note }
  C->>R: POST /mcp resources/read notes://recent<br/>+ Bearer JWT
  R->>DB: SELECT... ORDER BY created_at DESC
  R-->>C: list of recent notes

Everything between Claude and Render is over public HTTPS with OAuth. Everything between Render and Postgres is on the private network. Each leg is encrypted, and the database has no public surface to attack.

6. What you’ve shipped

Take stock - this is the real list of features behind the deployed URL:

  • Render’s MCP template as a foundation, extended with a real tool surface.
  • Streamable HTTP transport in stateless mode (horizontally scalable).
  • OAuth 2.1 with dynamic client registration, PKCE, and short-lived audience-restricted JWTs.
  • GitHub as the identity layer - no passwords stored, no user table to leak.
  • Postgres persistence behind a clean interface, with TLS and no public network exposure.
  • Per-identity rate limiting so one runaway client can’t take you down.
  • Structured JSON logs with request IDs, redacted secrets, and a queryable user field.
  • DB-backed /health so Render’s load balancer routes around degraded instances.
  • One Blueprint that provisions every piece reproducibly.

That’s roughly the production envelope you’d find in a paid MCP service, sitting on Render’s Starter plan.

A user reports that one of their `notes.create` calls 'didn't work.' Which combination of the things you wired in this tutorial gets you to a root cause fastest?

What you learned

  • Same OAuth flow you debugged locally just works against the deployed URL - nothing changes except `PUBLIC_URL`
  • Claude Desktop, Cursor, and Codex all speak Streamable HTTP + OAuth; the template's README configs work with the `Authorization` header removed
  • Structured logs + a `user` field make `service:notes-mcp user:"github:..."` a useful single-query debug tool
  • End-to-end: public HTTPS + OAuth from client to Render, private network from Render web to Postgres