Selat

Selat

Centralizes OAuth connections and tool access for AI agents, providing a single credential to call multiple services via MCP and REST with consistent errors and pagination.

Category
访问服务器

README

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 quickstart
Workspace 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.

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
fake experimental No vendor needed. For smoke tests and local development
github 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, done

Its 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
POST /mcp MCP Streamable HTTP: tools/list, tools/call
GET /v1/tools Namespaced catalog with input schemas. Filter with ?provider=
POST /v1/tools/{name}/call Invoke a tool. Accepts Idempotency-Key on writes
GET /v1/connections What is available and what is connected
POST /v1/connections/{provider}/authorize Start an OAuth connect
DELETE /v1/connections/{provider} Disconnect and drop the tokens
GET /v1/whoami Workspace, plan, connected providers. No secrets
GET /v1/health, GET /v1/ready 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 dev

Tests 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.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选