Skip to main content
Glama
Comet32

remote-mcp-starter

by Comet32

remote-mcp-starter

CI

A production-minded starting point for remote MCP servers that wrap a SaaS API (Jobber, FreshBooks, Toggl, ...).

Most community MCP servers are local stdio processes that ask the user to paste an API key. Business users can't install those, and their admins won't allow them. This starter gives you what a hosted integration needs, so each new integration is mostly tool definitions:

  • Streamable HTTP, stateless. A fresh server per request, so any replica can serve any request.

  • OAuth 2.1 broker. The MCP client logs in through your server, which delegates the login to the upstream SaaS. Upstream tokens stay server-side, encrypted at rest; MCP clients only see tokens you issued, bound to this server (RFC 8707 audience).

  • Client registration both ways. Client ID Metadata Documents (CIMD) and Dynamic Client Registration.

  • Safety layer on every tool.

    • Writes default to dry-run and return the planned change first.

    • Read-only mode hides write tools entirely.

    • mcp:read / mcp:write scopes.

    • Correct readOnlyHint / destructiveHint annotations.

  • Audit log. One JSON line per tool call: who, which tool, outcome, duration, arguments with secrets redacted.

  • Mock upstream. A built-in fake SaaS (OAuth login + notes API), so the whole flow runs locally and in tests with no real account.

Targets MCP protocol 2025-11-25, the latest supported by @modelcontextprotocol/sdk 1.32.

How the login works

MCP client            this server                         upstream SaaS
    │  /authorize (PKCE) ──▶│                                     │
    │                       │── redirect to upstream login ──────▶│ user approves
    │                       │◀── /oauth/callback?code ────────────│
    │                       │  exchange code, keep upstream tokens (sealed)
    │◀── redirect ?code ────│
    │  /token ─────────────▶│  issue our own access + refresh token
    │  POST /mcp (Bearer) ─▶│  tool → upstream API with the user's upstream token
    │                       │         (refreshed automatically when close to expiry)

Related MCP server: test-dcr-mcp-server

Quick start

npm install
npm run dev          # http://localhost:3000/mcp, using the mock upstream

Connect from Claude Code. Your browser opens the mock login page; click Approve:

claude mcp add --transport http demo http://localhost:3000/mcp

Then try: "List my notes", "Create a note titled Hello" (you get a dry-run preview first), "OK, apply it".

Adding tools

Tools live in src/tools/. notes.ts is the reference:

export const deleteNote = defineTool({
  kind: 'write',                 // 'read' | 'write'
  name: 'delete_note',
  title: 'Delete note',
  description: 'Permanently delete a note by id. Use list_notes to find the id first.',
  destructive: true,
  idempotent: true,
  input: { id: z.string() },
  // What would change. May read upstream, must not write.
  preview: async ({ id }, { upstream }) => ({ action: 'delete', note: await upstream.request(`notes/${encodeURIComponent(id)}`) }),
  run: async ({ id }, { upstream }) => {
    await upstream.request(`notes/${encodeURIComponent(id)}`, { method: 'DELETE' });
    return { deleted: id };
  },
});

The framework (src/tools/framework.ts) adds the dryRun parameter, scope checks, annotations, error handling and audit records. Write preview and run; nothing else.

Tips for good tools:

  • Design around user tasks ("quote from a request"), not one tool per endpoint.

  • Write descriptions for the model: when to use the tool, what it returns.

  • Keep outputs small.

Pointing at a real SaaS

  1. Register an OAuth app with the vendor. Set its redirect URI to https://<your-host>/oauth/callback.

  2. Set the UPSTREAM_* variables (see .env.example), and set MOCK_UPSTREAM=false.

  3. Replace identify in src/app.ts with the vendor's "who am I" call (for Jobber, a GraphQL account { id name } query).

  4. If the vendor's OAuth deviates from the standard (non-JSON token response, PKCE required, etc.), implement UpstreamProvider (src/upstream/types.ts) yourself.

Configuration

Variable

Default

Purpose

PUBLIC_URL

http://localhost:3000

Issuer and resource base. Must be https outside localhost

ALLOWED_HOSTS

PUBLIC_URL host

Host header allowlist (DNS rebinding protection)

READ_ONLY

false

Hide all write tools

DRY_RUN_DEFAULT

true

dryRun value when the model omits it

AUDIT_LOG_PATH

stdout

JSON-lines audit file

TOKEN_ENCRYPTION_KEY

ephemeral

32-byte base64 key for upstream tokens at rest

ACCESS_TOKEN_TTL / REFRESH_TOKEN_TTL

1h / 30d

Lifetimes of tokens issued to MCP clients

CIMD_ALLOW_INSECURE

false

Allow http:// and private-network CIMD URLs (tests only)

UPSTREAM_*

mock

Upstream OAuth app and API base

MOCK_UPSTREAM

true

Mount the fake upstream at /mock-upstream

Production checklist

  • MOCK_UPSTREAM=false; a persistent TOKEN_ENCRYPTION_KEY from a secret manager

  • Replace MemoryStore with Redis or similar (implement KVStore in src/auth/store.ts). Required for restarts and for more than one replica

  • Terminate TLS in front; set PUBLIC_URL and ALLOWED_HOSTS to the public hostname

  • Behind a proxy, set Express trust proxy so the SDK's rate limiting sees real client IPs

  • Ship the audit log somewhere durable

Development

npm test             # end-to-end tests: OAuth flow, CIMD, scopes, dry-run, read-only, refresh, revocation
npm run typecheck
npm run lint
npm run build && npm start

License

MIT © Rowan Zhao. See LICENSE.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Convert any OpenAPI spec into a secure MCP server with scoped auth, per-tool allow/deny policies, rate limiting, and a redacted audit trail.
    10 npm
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    A spec-compliant remote MCP server with built-in OAuth 2.1 and Dynamic Client Registration, enabling Notion Custom Agents to connect via 'Sign in with OAuth' without bearer tokens. It supports SSO federation to Google and Microsoft Entra, and includes basic tools like whoami, echo, and slow_task.
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    A universal MCP server for registering internal, external, and OpenAPI-based APIs as MCP tools. It exposes them to MCP clients via Streamable HTTP and provides admin portal, RBAC/session auth, credential injection, and audit logging.
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables you to publish remote MCP tools from your own Lambda behind a single HTTP API endpoint, with selectable authorization (IAM, API key, JWT, Lambda, or Google Workspace), stage throttling, JSON access logs, and RFC 9728 resource metadata discovery. No session table or sticky routing is needed, so any invocation can answer any request.
    17 npm
    MIT