MCP clients

One endpoint, one header. Selat is a Streamable HTTP MCP server, so any client that speaks that transport can use it without a bridge process.

endpoint  https://api.selat.weekndlabs.com/mcp
header    Authorization: Bearer slt_live_...

There is no stdio wrapper and nothing to install. The endpoint is stateless per request, so a client may reconnect whenever it likes.

Claude Code

claude mcp add --transport http selat https://api.selat.weekndlabs.com/mcp \
  --header "Authorization: Bearer slt_live_..."

Cursor

In ~/.cursor/mcp.json, or the project’s .cursor/mcp.json:

{
  "mcpServers": {
    "selat": {
      "url": "https://api.selat.weekndlabs.com/mcp",
      "headers": { "Authorization": "Bearer slt_live_..." }
    }
  }
}

Any other client

Clients that build their own MCP transport take a URL and a header map. This is the shape, using the MCP TypeScript SDK directly:

import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'

const transport = new StreamableHTTPClientTransport(
  new URL('https://api.selat.weekndlabs.com/mcp'),
  { requestInit: { headers: { Authorization: `Bearer ${token}` } } },
)
const client = new Client({ name: 'my-agent', version: '1.0.0' })
await client.connect(transport)

const { tools } = await client.listTools()
const result = await client.callTool({ name: 'github__list_issues', arguments: { owner: 'o', repo: 'r' } })

Agent platforms that store MCP servers as records usually want the same two fields under an http transport: a url and a headers object. Nothing else is needed.

Tool names

Every tool is namespaced by its provider, so github__list_issues and gcal__list_calendars can never collide. Some clients prefix the name again with the server label they gave you, which is why naming the server selat produces selat__selat_search_tools. Name it after what it holds, for example apps, and the result reads better.

Giving an agent less than everything

A credential does not have to reach the whole workspace. Mint one with a scope and the agent holding it sees only what that scope allows:

curl -s -X POST $SELAT_URL/v1/admin/workspaces/$WORKSPACE/credentials \
  -H "Authorization: Bearer $SELAT_SERVICE_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"name":"drive reader","scope":{"providers":["gdrive"],"readOnly":true}}'

providers limits which prefixes it can reach, and null means all of them. readOnly refuses every tool declared as a write.

Both apply to the tool list, not only to the call. The credential above is offered six Drive tools rather than fourteen, and never learns that anything else is connected. A tool it can see is a tool it can call, so a model does not spend a turn discovering that half its list was never available.

This is what to reach for before worrying about the cap below. An agent given one job rarely needs sixty tools, and a narrow credential is also the thing you can hand to somebody else’s agent without handing over the rest of your workspace.

When the catalog is large

Selat serves at most 60 tools to a client, because agents degrade well before that. When a workspace has more, the list is capped and a meta tool appears:

selat__search_tools
  query   free text, for example "create a jira issue"

It searches everything the workspace has enabled, including what the capped list left out, and returns names and input schemas ready to pass straight to tools/call. Calling it is free and is never metered.