Skip to main content
Glama
remote-mcp-starter

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: Remote MCP Server Template

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.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      Not graded
      quality
      D
      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
      C
      maintenance
      Enables MCP clients to connect over stateless Streamable HTTP and authenticate through an existing OAuth 2.1 / OpenID Connect authorization server, with JWT validation, per-tool scope enforcement, and step-up challenges. It ships example scoped tools for reading and creating per-user notes that developers replace with their own.
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables MCP clients to securely connect to your data through a remote deployment with OAuth 2.1 and PKCE, configurable sign-in, read/write consent, and audit logging. It exposes public tools without sign-in and authenticated tools that respect each user's permissions while validating arguments and recording every call.
      Apache 2.0
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables running a stateless Streamable HTTP MCP server on AWS Lambda behind API Gateway with JWT bearer authentication, per-scope tool allowlists, OAuth protected-resource metadata, and a CDK construct for deployment.
      MIT