sirup
OfficialConnects GitHub's MCP server through the gateway, exposing tools such as creating issues via the unified endpoint.
Connects Gmail's MCP server through the gateway, exposing tools such as sending email via the unified endpoint.
Connects Linear's MCP server through the gateway, exposing tools such as searching issues via the unified endpoint.
Connects Sentry's MCP server through the gateway, making its monitoring and error-tracking tools available via the unified endpoint.
Click on "Install 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., "@sirupSearch GitHub for the auth bug and create a Linear issue for it."
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.
sirup.gg
One MCP endpoint for your whole company.
Connect every MCP server your team uses — GitHub, Linear, Sentry, your own internal ones — and sirup exposes them through a single endpoint. Point Claude, Cursor, or any MCP client at that one URL. Add a server later and every client picks it up without touching a config file.
Free, open source, and self-hostable.
┌──────────┐ ┌──────────────┐
│ Claude │ ──┐ ┌── │ GitHub MCP │
├──────────┤ │ ┌──────────────────────┐ │ ├──────────────┤
│ Cursor │ ──┼── │ sirup.gg/mcp │ ──┼── │ Linear MCP │
├──────────┤ │ │ one endpoint, one │ │ ├──────────────┤
│ VS Code │ ──┘ │ token, all tools │ └── │ Your MCP │
└──────────┘ └──────────────────────┘ └──────────────┘Quick start
npm install
docker compose -f docker-compose.dev.yml up -d # Postgres
npm run devhttp://localhost:5173. Migrations run automatically at boot, so there is no separate setup step. The app itself runs on the host — one command, one port, one process — so you keep HMR.
No Docker locally? Any Postgres will do; point DATABASE_URL at it.
For production, see Deploying.
Related MCP server: MCP Proxy Server
How it works
Register and name your company. That mints your gateway token.
Connect MCP servers from the catalog, or paste any
http(s)endpoint.Point your AI client at
https://your-host/mcp.
Two ways to authenticate
Clients differ in what they can send, so the endpoint accepts both. Same URL, same tools; only the credential differs.
A profile token, for anything that lets you set a header — Cursor, scripts, CI:
Managed OAuth
Gmail connects through a managed OAuth integration. One sign-in creates one normal sirup connection, so linking several Google accounts gives each account its own label, tool namespace, permissions, profile attachments, and connection-pool entry — exactly like connecting two API keys.
Before Gmail becomes connectable in the catalog:
Enable
gmail.googleapis.comandgmailmcp.googleapis.comin a Google Cloud project enrolled in the Workspace Developer Preview.Create a Web application OAuth client and register
https://your-host/api/integrations/oauth/callbackas an exact redirect URI.Configure
APP_ORIGIN,CREDENTIAL_ENCRYPTION_KEY,GOOGLE_OAUTH_CLIENT_ID, andGOOGLE_OAUTH_CLIENT_SECRET.
The integration requests https://mail.google.com/ — Gmail's full-access scope
— so every tool the endpoint advertises actually works. Requesting the narrower
gmail.readonly + gmail.compose pair only authorizes 8 of the 23 tools;
labelling, trashing, and spam all fail without gmail.modify. OAuth tokens and
PKCE verifiers are encrypted at rest with AES-256-GCM; state is hashed,
single-use, short-lived, and bound to the browser that started the flow.
Note that the Gmail MCP server currently exposes no send tool — the closest
is create_draft. That is Google's decision, not a scope we are withholding.
Adding another provider is one file in server/integrations/ plus a catalog
entry with an integration_key.
{
"mcpServers": {
"sirup": {
"type": "http",
"url": "https://your-host/mcp",
"headers": { "Authorization": "Bearer sirup_..." }
}
}
}OAuth, for clients that only accept a URL. Claude's "Add custom connector" dialog has one field and no place to put a token; VS Code is the same. Those clients get the endpoint on its own:
{
"mcpServers": {
"sirup": { "type": "http", "url": "https://your-host/mcp" }
}
}The client then discovers everything else on its own: it calls the endpoint,
gets a 401 carrying a resource_metadata pointer, reads the metadata,
registers itself, and opens a browser where you choose which profile it may
see. No token is ever copied or pasted.
That flow is OAuth 2.1 with PKCE, implemented against the MCP authorization spec — RFC 8414 metadata, RFC 7591 dynamic client registration, RFC 9728 protected resource metadata, and RFC 8707 resource indicators. Access tokens last an hour, refresh tokens rotate on every use, and replaying a rotated one revokes the whole grant. Authorized clients are listed in the dashboard and can be revoked there.
Both paths resolve to exactly one profile, so a client — however it authenticated — sees precisely the tools that profile exposes and nothing else.
Tool namespacing
Two servers can easily both expose a search tool. sirup prefixes every tool
with its server slug, so the model can tell them apart and names never collide:
gmail__search_threads linear__search github__create_issueTransports
The gateway speaks Streamable HTTP and tries it against upstreams first, falling back to the deprecated HTTP+SSE transport on a 4xx — the compatibility strategy the spec recommends.
stdio upstreams are intentionally not supported: a hosted multi-tenant
gateway must not spawn arbitrary subprocesses on its users' behalf.
The gateway itself runs stateless (a fresh MCP server per request), so it
scales horizontally with no sticky sessions. The only long-lived state is the
upstream connection pool, which reuses connections so each tool call doesn't
pay for a fresh initialize handshake.
/mcp serves two audiences
The same URL is the MCP endpoint and the dashboard. They're told apart by the
Accept header: browser navigations get the app, everything else gets MCP. That
way there's one memorable address to hand out.
Architecture
Single repo, single process. Express owns the HTTP server; in dev, Vite runs as
middleware (so HMR works with no second server, no proxy, no CORS), and in prod
the same server serves dist/.
Backend — route → logic → data
Each feature is one folder with the same three layers. Routes do HTTP only, logic holds the rules, data holds every Objection query.
server/features/mcp-servers/
├── mcpServers.route.ts HTTP: parse, call logic, respond
├── mcpServers.logic.ts validation, orchestration
└── mcpServers.data.ts Objection queries, nothing elseserver/
├── index.ts routes, then /mcp, then the SPA catch-all
├── config.ts
├── database/ migrations, Objection models
├── integrations/ managed OAuth providers and encrypted grants
├── mcp/ the aggregator
│ ├── gatewayRoutes.ts the public /mcp endpoint
│ ├── gatewayServer.ts MCP server exposing aggregated tools
│ ├── aggregator.ts namespacing, fan-out, routing, logging
│ ├── connectionPool.ts reuses upstream connections
│ └── upstreamClient.ts connects out, with transport fallback
└── features/ auth · mcp-servers · mcp-logs · mcp-catalogFrontend — components / hooks / data
One folder per full screen, always the same three subfolders. Components are stateless, hooks hold state and orchestration, data holds interfaces and API calls.
app/src/features/mcp-manage/
├── components/ stateless UI (.tsx)
├── hooks/ state, orchestration
└── data/ API calls, typed by shared/api.tsScreens: marketing · onboarding · mcp-manage · mcp-discover ·
mcp-logs · skills · auth · theme.
Routes: / is the public landing page, /start is onboarding, and everything
else sits behind a finished workspace. The landing page reads the catalog from
GET /api/public/apps — the same list the dashboard uses, so the logos and
counts it shows cannot drift from what the product actually connects to.
Stack
TypeScript · React · Express · Objection.js · Knex · Postgres · shadcn/ui ·
Tailwind v4 · @modelcontextprotocol/sdk
One API contract, checked on both sides
shared/ holds the domain types and the HTTP contract, and both the server and
the browser import them. A route that changes its response shape fails to
compile until every caller is updated — the mismatch surfaces at build time
instead of as a runtime undefined.
shared/
├── domain.ts entities: Company, McpServer, McpTool, McpLog
└── api.ts request bodies and response envelopes per endpointRenaming one field in shared/domain.ts currently produces errors in the
server, the frontend, and the check scripts simultaneously. That is the point.
Type checking is strict (noUncheckedIndexedAccess, noImplicitOverride,
verbatimModuleSyntax) and runs as three projects — server, app, scripts —
behind npm run typecheck. npm run build runs it first, so a type error
fails the Docker image build rather than shipping.
The server runs straight from TypeScript via tsx, so there is no compile step
and no dist/ for the backend. tsx is therefore a runtime dependency, not
a dev one.
Data model
Every primary key is a UUIDv4, generated by Postgres via
gen_random_uuid() so the database stays the source of truth. Ids are
unguessable and safe to expose in URLs, and nothing leaks how many companies or
tool calls exist.
One consequence worth knowing: random ids carry no ordering, so the logs
feed cannot paginate on id DESC. It uses a composite (created_at, id)
keyset cursor instead, matched by an index on the same tuple. The cursor is
base64-encoded and opaque — don't construct one by hand.
Indexes are built against the actual hot paths, and scripts/check-indexes.ts
runs EXPLAIN to prove the planner uses each one rather than trusting that it
exists:
Index | Serves |
| the keyset log feed |
| the 24h rollup |
| per-server logs |
| resolving a tool on every |
|
|
| the dashboard list |
Deploying to Dokploy
The app and Postgres run side by side on one server. docker-compose.yml is
written for it.
Create a Docker Compose service pointed at this repo.
Environment tab — set:
JWT_SECRET=<64 random hex chars> POSTGRES_PASSWORD=<a strong password> APP_ORIGIN=https://your-host CREDENTIAL_ENCRYPTION_KEY=<64 random hex chars> GOOGLE_OAUTH_CLIENT_ID=<optional; enables Gmail> GOOGLE_OAUTH_CLIENT_SECRET=<optional; enables Gmail>Generate the encryption key independently from
JWT_SECRET; changing it later makes existing OAuth grants unreadable and requires reconnecting them. The Google variables are optional when Gmail is not enabled. Generate a secret with:node -e "console.log(require('crypto').randomBytes(48).toString('hex'))"JWT_SECRETandPOSTGRES_PASSWORDare declared${VAR:?}, so a deploy fails loudly rather than silently booting with insecure defaults.APP_ORIGINmust be the https URL users actually reach: it is both the callback origin for outbound OAuth and the issuer MCP clients compare literally when authenticating to your endpoint.Domains tab — add your domain, service
app, port3000. Dokploy injects the Traefik labels and attachesdokploy-networkitself, which is why neither appears in the compose file.Deploy. Migrations run on boot; there is no release step.
Notes on the setup:
Postgres is not published. No
ports:ondb— it is reachable only over the privatesirup_internalnetwork, and it is deliberately kept offdokploy-networkso Traefik has no route to it.The volume is a named volume (
sirup_db_data), which is what Dokploy's Volume Backups can snapshot to S3. Bind mounts cannot be backed up.The app waits for Postgres, on top of the compose healthcheck, so a cold start doesn't crash-loop.
DATABASE_SSLstaysfalse: the database is on the same private network. Set it totrueonly if you move to a managed provider.
Scripts
Command | Does |
| Everything, one port, with HMR and server auto-restart |
| Strict type check across server, app, and scripts |
| Type check, then build the React app |
| Production server |
| Local Postgres via Docker |
| Local Postgres without Docker (embedded binary) |
| Run migrations manually |
| Print the live schema and indexes |
| Type check plus the full suite, against a running dev server |
| Demo account with servers connected |
npm test covers: strict type checking, build/deploy preflight, connection
config, encrypted OAuth state, an end-to-end run against a live MCP server,
regressions, keyset pagination correctness, upstream connection reuse,
database-side timestamps, index usage via EXPLAIN on 43k seeded rows, the
dual-purpose /mcp route, and a full inbound OAuth 2.1 flow (discovery,
dynamic registration, PKCE, consent, token exchange, refresh rotation, and
revocation).
Configuration
The core app has working defaults in development. Managed OAuth requires its
provider credentials and encryption key. See .env.example.
Variable | Default | Notes |
|
| |
| ephemeral | Required in production |
| local app URL in dev | Required in production. Outbound OAuth callbacks, and the issuer/resource identifier for inbound OAuth |
| — | 32-byte base64 or 64-char hex key; required for managed OAuth |
| - | Enables Gmail, Drive, Sheets, Docs, and Slides |
| — | Preferred; what compose uses |
|
| Fallback when |
|
|
|
|
| |
|
| Bound on upstream calls |
Compose passes the discrete PG* variables rather than composing a
DATABASE_URL, because a URL has to be escaped — a generated password
containing /, #, or @ makes URL parsing throw and the app crash-loop.
Status
Working today: registration and onboarding, connecting upstream MCP servers, tool discovery and namespacing, the aggregated gateway, per-tool enable/disable, and the audit log.
Not built yet, and worth being honest about:
Generic OAuth for upstreams. The five Google Workspace apps have managed OAuth integrations with encrypted per-account grants. Other OAuth providers still need an explicit, reviewed integration before they can be connected.
Teams. One user per company. No invites or roles.
Skills. The nav entry is a placeholder. Skills-over-MCP is still an open working group at the MCP project, and guessing at the shape now would mean rewriting it when the spec lands.
Tool-count pressure. Aggregating many servers puts a lot of tools in the model's context. Per-tool disabling exists; smarter filtering doesn't yet.
License
MIT
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
No tool schema history has been recorded yet.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Governed MCP gateway: one endpoint for your tools, with credential custody and audit log.
The MCP server that finds MCP servers. Aggregates Official Registry, Glama, and Smithery.
Unified gateway exposing 150+ tools across all NexGenData MCP servers via one endpoint.
Trust, freshness, policy, and discovery layer for public MCP servers.
Related MCP Servers
- FlicenseNot gradedqualityNot gradedmaintenanceAggregates multiple MCP servers behind a single, secure endpoint with unified tool/resource discovery, OAuth authentication, and resilient request routing. Enables users to manage and interact with multiple MCP backends through one centralized interface with load balancing and circuit breakers.2-
- AlicenseNot gradedqualityCmaintenanceAggregates multiple backend MCP servers into a single unified interface with optional web management UI for tool control and configuration.45193MIT
- FlicenseNot gradedqualityBmaintenanceAggregates multiple child MCP servers into a single MCP server endpoint, enabling clients to use various tools (e.g., filesystem, Brave Search) through one interface.19-
- AlicenseNot gradedqualityAmaintenanceAggregates multiple MCP servers into a single HTTP endpoint with tool namespacing, dashboard, and REST API for management.19MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/roxy-gg/sirup'
If you have feedback or need assistance with the MCP directory API, please join our Discord server