remote-mcp-starter
Serves as an example target SaaS for a hosted MCP integration, wrapping Toggl's API so tools can be exposed for time tracking tasks (with OAuth delegated to the upstream service, dry-run write previews, and scope-gated access).
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@remote-mcp-startercreate a note titled Hello"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
remote-mcp-starter
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:writescopes.Correct
readOnlyHint/destructiveHintannotations.
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 upstreamConnect from Claude Code. Your browser opens the mock login page; click Approve:
claude mcp add --transport http demo http://localhost:3000/mcpThen 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
Register an OAuth app with the vendor. Set its redirect URI to
https://<your-host>/oauth/callback.Set the
UPSTREAM_*variables (see.env.example), and setMOCK_UPSTREAM=false.Replace
identifyinsrc/app.tswith the vendor's "who am I" call (for Jobber, a GraphQLaccount { id name }query).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 |
|
| Issuer and resource base. Must be |
| PUBLIC_URL host | Host header allowlist (DNS rebinding protection) |
|
| Hide all write tools |
|
|
|
| stdout | JSON-lines audit file |
| ephemeral | 32-byte base64 key for upstream tokens at rest |
| 1h / 30d | Lifetimes of tokens issued to MCP clients |
|
| Allow |
| mock | Upstream OAuth app and API base |
|
| Mount the fake upstream at |
Production checklist
MOCK_UPSTREAM=false; a persistentTOKEN_ENCRYPTION_KEYfrom a secret managerReplace
MemoryStorewith Redis or similar (implementKVStoreinsrc/auth/store.ts). Required for restarts and for more than one replicaTerminate TLS in front; set
PUBLIC_URLandALLOWED_HOSTSto the public hostnameBehind a proxy, set Express
trust proxyso the SDK's rate limiting sees real client IPsShip 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 startLicense
MIT © Rowan Zhao. See LICENSE.
This server cannot be deployed
Maintenance
Related MCP Connectors
Hosted MCP server with managed OAuth for 15+ toolkits: Google Workspace, Fitbit, Oura, Kalshi, etc.
Managed MCP gateway with OAuth, per-agent permissions, shared accounts and activity logs.
One OAuth connector for your team's remote MCP servers, with Tool Packs, guardrails and logs.
Self-hosted federated MCP gateway: one OAuth 2.1 MCP server in front of N apps, user-level scopes.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceConvert any OpenAPI spec into a secure MCP server with scoped auth, per-tool allow/deny policies, rate limiting, and a redacted audit trail.10 npmMIT
- AlicenseNot gradedqualityAmaintenanceA 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
- FlicenseNot gradedqualityCmaintenanceA 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.-
- AlicenseNot gradedqualityAmaintenanceEnables 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 npmMIT