Skip to main content
Glama

Nojo-MCP

A remote MCP server for the Nojo platform, served over Streamable HTTP at https://nojo.ai/mcp.

It is currently an OAuth bridge only. The sign-in chain is the thing being proved; platform tools come later, on top of it.

Tools

Tool

Arguments

Returns

whoami

none

{"subject", "client_id", "token_expires_at", "exchange": "ok"}

whoami makes no upstream call. It reports what the token exchange already established, so a successful call is a green light for the whole chain: discovery → authorize → token → exchange.

Related MCP server: Smart Irrigation MCP Server

How authentication works

This server is an OAuth resource server, never an authorization server. It issues no tokens and stores no passwords.

  1. A client (Claude, ChatGPT, Codex) hits /mcp with no token and gets 401 plus a WWW-Authenticate header naming /.well-known/oauth-protected-resource/mcp.

  2. That document points the client at the Nojo backend as the authorization server. The client does login, consent and PKCE there and comes back with an OAuth access token.

  3. On each request this server exchanges that token at the backend (RFC 8693), authenticating with its own client credentials, and gets a short-lived Nojo JWT plus the user's sub.

  4. The exchange result is cached until 30s before expiry.

The Nojo JWT never leaves the server — it is excluded from the token's repr and serialization, so it cannot reach the model or the client.

Failure modes are kept distinct on purpose:

  • Backend rejects the token (400/401) → 401 back to the client, which re-authenticates.

  • Backend is down or broken (5xx, unreachable, malformed body) → 500. A client must not read an outage as "your login expired" and loop through sign-in.

/.well-known/oauth-authorization-server and /oauth/* are deliberately not served here — they belong to the backend. See MCP_APIs_Contract.md for the endpoints the backend must provide.

Proxy routing (required)

The authorization server and this server share the host nojo.ai, so the reverse proxy must split them:

Path on nojo.ai

Served by

/.well-known/oauth-authorization-server

Backend

/oauth/authorize, /oauth/token

Backend

/.well-known/oauth-protected-resource/mcp

This server

/mcp

This server

If a backend catch-all swallows /.well-known/oauth-protected-resource/mcp, discovery fails with no visible error. Check this first when a connector will not connect.

Setup

uv sync
cp src/.env.example src/.env   # then fill in MCP_OAUTH_CLIENT_SECRET
uv run uvicorn src.app:app --port 8000

Variable

Required

Default

Purpose

NOJO_ISSUER_URL

yes

—

Authorization server issuer, exact string, no trailing slash

NOJO_RESOURCE_SERVER_URL

yes

—

Public URL of this server including /mcp; drives the 401 challenge and the metadata route

TOKEN_EXCHANGE_URL

yes

—

Backend RFC 8693 endpoint

MCP_OAUTH_CLIENT_ID

yes

—

This server's confidential client id

MCP_OAUTH_CLIENT_SECRET

yes

—

Its secret; keep out of version control

HOST

no

0.0.0.0

Bind address

ALLOWED_HOSTS

no

empty

Host allowlist; a request for an unlisted host gets 421

ALLOWED_ORIGINS

no

empty

Origin allowlist

STATELESS_HTTP

no

false

Set true only for multiple replicas without sticky routing

HTTP_TIMEOUT_SECONDS

no

15.0

Token-exchange timeout

HTTP_MAX_CONNECTIONS

no

100

Shared connection pool size

LOG_LEVEL

no

INFO

ALLOWED_HOSTS accepts a bare hostname and matches it on any port.

Deployment

docker build -t nojo-mcp .
docker run -p 8000:8000 --env-file src/.env nojo-mcp

Runs as a non-root user with a /healthz healthcheck. Terminate TLS at the proxy and pass --proxy-headers (already in the image's CMD); set FORWARDED_ALLOW_IPS to the proxy's network so the app sees the real scheme and host. NOJO_RESOURCE_SERVER_URL must be the public HTTPS URL, not the internal one — clients compare it exactly.

Connecting a client

Add https://nojo.ai/mcp as a custom connector. The client discovers the authorization server on its own, walks the sign-in flow, and then whoami should return the signed-in user's id.

Development

uv run pytest

Tests use httpx.MockTransport for the backend and an ASGI transport for the server, so nothing touches the network.

src/
  app.py              ASGI app; Streamable HTTP at /mcp
  server.py           MCP server, lifespan, /healthz
  tools.py            AppState and the whoami tool
  core/config.py      Settings
  core/logging.py     Logging setup
  services/auth.py    Token exchange and the verifier cache
  services/http.py    Shared httpx client
  services/errors.py  Error types

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Exposes real-time soil sensor metrics as MCP Resources and irrigation valve controls as MCP Tools, enabling AI-driven agronomic decisions with built-in safety guardrails.
    Apache 2.0
  • A
    license
    A
    quality
    C
    maintenance
    MCP server for the Withings Health API with per-user OAuth2, enabling users to securely access their own health data (measures, activity, sleep, workouts, heart rate, devices, and goals) and optionally add measurements.
    12
    Apache 2.0
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables read-only access to Amazfit/Zepp fitness data via the unofficial Zepp cloud API, exposing normalized health and workout metrics to MCP clients.
    -