Selat
Allows listing, getting and creating GitHub issues, plus repository search, using a self-hosted OAuth application.
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., "@SelatList open issues for the vercel/next.js repo"
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.
Selat
One credential for every tool your agent calls.
You are building an agent. It needs GitHub, then Jira, then Drive. Each one wants its own OAuth dance, its own token refresh, its own schema, its own error shape. So you write a vault, a refresher, and a retry loop, and you write them again for the next agent.
Selat is the gateway that holds all of it. Your workspace connects each upstream
once. Your agent holds one bearer token and calls github__list_issues. When you
connect or disconnect an upstream, that token does not change.
Selat is Indonesian for strait, the narrow passage every ship has to pass through.
60 seconds to a real tool call
git clone https://github.com/fajarhide/selat && cd selat
cp .env.example .env
perl -pi -e "s/^VAULT_KEY=.*/VAULT_KEY=$(openssl rand -hex 32)/" .env
docker compose up -d
npm run quickstartWorkspace 3c0862e2-d0eb-4ca3-87e9-fcb80b683b44 created.
Registered providers: fake
Your gateway credential, shown once:
slt_live_gdOhIUCc1cBeZP5…That credential is shown once and stored as a hash, so keep it now:
export SELAT_TOKEN=slt_live_gdOhIUCc1cBeZP5…Ask what it can do:
$ curl -s localhost:8080/v1/tools -H "Authorization: Bearer $SELAT_TOKEN"{
"tools": [
{
"name": "fake__echo",
"description": "Return the message argument unchanged, for connectivity checks",
"inputSchema": {
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
},
"write": false,
"provider": "fake",
"maturity": "experimental"
}
],
"catalog_truncated": false,
"request_id": "c45c3d9b-9176-4b64-8f3f-e8363b3b837e"
}Call one:
$ curl -s -X POST localhost:8080/v1/tools/fake__echo/call \
-H "Authorization: Bearer $SELAT_TOKEN" -H 'content-type: application/json' \
-d '{"message":"hello"}'{ "content": { "message": "hello" }, "nextCursor": null, "hasMore": false,
"request_id": "ebe4d67a-f272-4646-8c03-50e551b6c880" }No vendor account, no OAuth application, no waiting on an app review. The fake
provider ships enabled so you can see the whole shape of the thing before you
decide to care.
Point an MCP client at the same workspace and the same tools appear:
{
"mcpServers": {
"selat": {
"type": "http",
"url": "http://localhost:8080/mcp",
"headers": { "Authorization": "Bearer slt_live_…" }
}
}
}MCP and REST are equals here. Claude Desktop and your LangGraph runtime see the same catalog, backed by the same vault, metered on the same counter.
Related MCP server: agentbridge
Errors your agent can branch on
Most gateways hand your model a 500 and a stack trace. Selat answers with a closed set of codes, so your agent can decide what to do instead of guessing.
$ curl -i -X POST localhost:8080/v1/tools/github__list_issues/call \
-H "Authorization: Bearer $SELAT_TOKEN" -H 'content-type: application/json' \
-d '{"owner":"vercel","repo":"next.js"}'HTTP/1.1 403 Forbidden
X-Request-Id: ffef9d9c-4777-4320-a4bc-f43f0476916c{ "error": { "code": "provider_not_connected", "message": "github is not connected",
"provider": "github", "request_id": "ffef9d9c-4777-4320-a4bc-f43f0476916c" } }The full set: invalid_arguments, invalid_credential, provider_not_connected,
credential_scope_denied, plan_blocked, tool_not_found, reauth_required,
quota_exceeded, rate_limited, upstream_error, upstream_timeout. Nothing
else is ever returned.
A reauth_required carries a reauth_url, so your agent can tell a human
exactly what to click. A rate_limited carries retry_after in seconds. Every
response, success or failure, carries a request_id that also appears in the
log line for that call.
List-shaped tools always answer with hasMore and nextCursor:
{ "content": { "items": [{ "id": "item-1" }] }, "nextCursor": "2", "hasMore": true }A truncated list with no signal is treated as a defect here, not a tradeoff. An agent that silently reasons over half a page is worse than one that errors.
What is actually shipped
Provider | Maturity | Notes |
| experimental | No vendor needed. For smoke tests and local development |
| beta | List, get and create issues, plus repository search. Bring your own OAuth application |
Two providers, honestly labelled. Maturity rides on every tool in the catalog,
so an agent can refuse to call anything below ga if you want it to. Jira,
Google, Notion, Slack and Linear are next, each one landing only when the
conformance suite is green.
If you want one sooner, the adapter contract is small and the suite tells you when you are done. See Writing a provider.
Connecting GitHub
Every provider needs an OAuth application. Self-hosting means bringing your own, because the hosted applications are a cloud convenience and are deliberately not in this repository.
# .env
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...Set the callback in the vendor console to
{PUBLIC_URL}/v1/connections/github/callback, then:
curl -s -X POST localhost:8080/v1/connections/github/authorize \
-H "Authorization: Bearer $SELAT_TOKEN"
# open the authorize_url, approve, doneIts tools appear as github__list_issues and friends. PKCE with S256 is
mandatory, including for vendors that do not require it. The state is stored
server side, single use, and expires in ten minutes.
Your agent's credential is untouched by any of this. That separation is the whole design: humans manage connections, agents hold one bearer, and the two never have to be redeployed together.
Why not just call the APIs directly
You can, and for one agent against one service you probably should. Selat starts paying for itself at the third upstream, or the second agent, or the first time a refresh token rotates at 3am.
Why not a per-vendor MCP server? You end up running one process per service, each with its own auth story, and your agent sees an unbounded tool list. Selat gives you one endpoint, one token, and a catalog you can filter.
Why not the model vendor's built-in connectors? They work well inside that vendor. Selat runs the same tools against any runtime, including the one you wrote yourself, and you can host it on your own hardware with your own OAuth applications.
What about the tool list getting huge? Agents degrade well before any API
limit, so the exposed catalog is capped at 60 tools per workspace and every tool
can be toggled individually. When the cap bites, catalog_truncated says so
instead of quietly shortening the list.
Security
Upstream tokens are sealed with AES-256-GCM under a key from VAULT_KEY, with
the workspace and grant bound into the additional authenticated data, so a
ciphertext copied into another tenant's row will not decrypt.
Gateway credentials are stored as SHA-256 hashes and shown once. The slt_live_
prefix is fixed so the pattern can be registered with GitHub secret scanning,
which turns a leaked token into an automatic revocation instead of an incident.
No token, upstream or gateway, is ever written to a log or returned in a response body. Every query against a tenant table carries a workspace predicate, and a test walks the source to fail the build if one does not. The single exception, deleting an expired OAuth state by its random primary key, has to name itself in the source with the reason, so it lands in review rather than in a quietly loosened rule.
Report a vulnerability privately through GitHub security advisories rather than a public issue.
API
Surface | What it does |
| MCP Streamable HTTP: |
| Namespaced catalog with input schemas. Filter with |
| Invoke a tool. Accepts |
| What is available and what is connected |
| Start an OAuth connect |
| Disconnect and drop the tokens |
| Workspace, plan, connected providers. No secrets |
| Liveness and readiness |
Writing a provider
Implement ProviderAdapter in src/adapters/providers/, then run the
conformance suite against it. The suite checks the things that actually break
agents: honest pagination, unique tool names, object input schemas, errors
mapped to the closed code set, and never leaking the access token into a result.
describe('my provider conformance', () => {
runAdapterConformance(myProvider(), {
pagedTool: 'list_things',
fullPage: { args: { owner: 'o' }, upstream: fakeUpstream([{ match: /things/, body: itemsPage(30) }]) },
lastPage: { args: { owner: 'o' }, upstream: fakeUpstream([{ match: /things/, body: itemsPage(2) }]) },
})
})A contribution merges when that suite is green. No adapter has ever needed a network connection to be tested, and yours should not either.
Development
docker compose up -d db # or a local Postgres on 5432
createdb selat_test
npm install
npm test
npm run devTests run against a real Postgres. TEST_DATABASE_URL defaults to the database
docker compose creates.
License
Apache-2.0. The gateway and every provider adapter are open, and always will be. Billing, organisations, SSO and the hosted OAuth applications live in a separate private repository, which reaches this one over HTTP like any other client.
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 Servers
- Alicense-qualityCmaintenanceEnables AI agents to discover and execute tools via a secure MCP server with JWT authentication, RBAC, rate limiting, and audit logging.1MIT
- Flicense-qualityBmaintenanceA unified gateway for AI agent tools that provides a single MCP stdio endpoint for executing tool calls with unified auth, rate limiting, and observability. Enables agents to interact with multiple external APIs through a standardized interface.1
- Alicense-qualityBmaintenanceEnables AI agents to access a unified catalog of tools from various APIs (OpenAPI, GraphQL, MCP, Google Discovery) through the MCP protocol.MIT
- AlicenseAqualityCmaintenanceA federated MCP gateway that consolidates multiple plain-HTTP backends into a single, OAuth-protected MCP server, enabling agents to access diverse tools through one endpoint with centralized authentication and audit.510MIT
Related MCP Connectors
MCP Hub: AI service discovery, per-user OAuth, and multi-service workflow orchestration
Phone, SMS & email for AI agents — one remote MCP endpoint, OAuth login, zero install.
34 production API tools over one hosted MCP endpoint.
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/fajarhide/selat'
If you have feedback or need assistance with the MCP directory API, please join our Discord server