Skip to main content
Glama

MCP Server

Node.js + Express MCP server for ChatGPT with:

  • OAuth 2.0 Authorization Code Flow + PKCE S256

  • refresh token rotation

  • Supabase Postgres persistence

  • MCP Streamable HTTP endpoint at /mcp

  • sample tools: calculator, get_weather, search_web

  • cleanup job for expired OAuth records

Structure

mcp-server/
|-- .env.example
|-- IMPLEMENTATION_REPORT.md
|-- package.json
|-- README.md
|-- supabase/
|   `-- schema.sql
`-- src/
    |-- app.js
    |-- server.js
    |-- config/
    |-- mcp/
    |-- middleware/
    |-- routes/
    |-- scripts/
    |-- services/
    |-- tools/
    `-- utils/

1. Supabase setup

  1. Create a Supabase project.

  2. Open the SQL editor.

  3. Run supabase/schema.sql.

  4. Copy the Postgres connection string into .env as DATABASE_URL.

For IPv4-only networks, use the Supabase Session Pooler URI instead of the direct IPv6 connection string.

Seed an OAuth client

You need at least one record in oauth_clients.

For local development the server can seed one automatically from env:

  • AUTO_SEED_CLIENT_ID

  • AUTO_SEED_CLIENT_NAME

  • AUTO_SEED_CLIENT_SECRET

  • AUTO_SEED_REDIRECT_URIS

If AUTO_SEED_CLIENT_SECRET is empty, the client is treated as public and may use none auth at the token endpoint. If it is set, the client supports both client_secret_basic and client_secret_post.

2. Local setup

cd mcp-server
npm install
copy .env.example .env

Edit .env:

  • BASE_URL

  • DATABASE_URL

  • CORS_ORIGINS

  • MCP_ALLOWED_ORIGINS

  • MCP_ALLOWED_HOSTS

  • ENABLE_CLEANUP_JOB

  • CLEANUP_INTERVAL_MINUTES

  • AUTO_SEED_*

Check database connectivity:

npm run db:check
npm run db:init

Run locally:

npm run dev

Health check:

GET http://localhost:3000/healthz

Run cleanup manually:

npm run cleanup:run

3. Expose with ngrok

ngrok http 3000

Take the HTTPS URL from ngrok and set it as:

  • BASE_URL

  • add the ngrok origin to MCP_ALLOWED_ORIGINS

  • add the ngrok host to MCP_ALLOWED_HOSTS

  • one of the registered redirect URIs in oauth_clients

Restart the server after updating .env.

4. OAuth endpoints

Authorization endpoint

  • GET /authorize

  • POST /authorize/decision

  • demo consent screen with optional auto-approve mode

  • validates:

    • client_id

    • redirect_uri

    • response_type=code

    • code_challenge

    • code_challenge_method=S256

    • scopes

Token endpoint

  • POST /token

  • supports:

    • grant_type=authorization_code

    • grant_type=refresh_token

Client authentication:

  • none

  • client_secret_basic

  • client_secret_post

Discovery

  • GET /.well-known/oauth-authorization-server

  • GET /.well-known/openid-configuration

Revoke

  • POST /revoke

5. Refresh token rotation

This server rotates refresh tokens on every refresh request:

  1. the current refresh token row is locked

  2. the old refresh token is revoked

  3. a new access token is issued

  4. a new refresh token is issued

  5. rotation lineage is stored in Postgres

If a revoked or replaced refresh token is reused:

  • the backing mcp_session is marked compromised

  • session tokens are revoked

  • the request fails with invalid_grant

6. MCP endpoint

Main MCP endpoint:

  • POST /mcp

Behavior:

  • uses MCP Streamable HTTP transport

  • path is /mcp

  • bearer access token is required

  • host and origin allowlists are enforced

  • the implementation is stateless on the transport layer

  • each HTTP request creates a fresh MCP server and transport instance

  • no in-memory MCP transport session map is retained across requests or restarts

Compatibility note:

  • GET /mcp and DELETE /mcp currently return 405

  • this server is using the stateless streamable HTTP pattern, not SSE session transport

7. Sample tools

  • calculator

  • get_weather

  • search_web

These are in src/tools/ and registered via src/mcp/tool-registry.js.

8. Cleanup job

The server starts a background cleanup job on boot when ENABLE_CLEANUP_JOB=true.

It removes:

  • expired or stale authorization codes

  • expired or revoked access tokens older than 1 day

  • expired or revoked refresh tokens older than 7 days

Interval is controlled by CLEANUP_INTERVAL_MINUTES.

9. Register in ChatGPT

Use your deployed server values:

  • Authorization URL: https://your-domain/authorize

  • Token URL: https://your-domain/token

  • Revoke URL: https://your-domain/revoke

  • MCP URL: https://your-domain/mcp

Recommended scopes:

  • mcp.tools.call

  • offline_access

PKCE:

  • required

  • method: S256

If ChatGPT or your connector uses a confidential client:

  • create the client in oauth_clients

  • store the secret hash

  • allow client_secret_basic and/or client_secret_post

10. Security notes

  • token values are stored hashed in Postgres

  • client secrets are stored hashed

  • rate limiting is applied to /authorize, /token, /revoke

  • OAuth errors use standard fields:

    • error

    • error_description

  • raw secrets and tokens are not logged

  • refresh token reuse triggers session compromise handling

11. Current status

Verified against real Supabase:

  • npm run db:check

  • npm run db:init

  • npm run cleanup:run

What is still intentionally basic:

  1. the consent page is a demo flow, not a real user login system

  2. access tokens are opaque and require a DB lookup

  3. there is no automated test suite yet

  4. rate limits and allowlists should be tightened before production

A
license - permissive license
-
quality - not tested
C
maintenance

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/buitanphat247/mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server