Skip to content

Self-host the MCP server over HTTP

Web clients (claude.ai, ChatGPT) and teams need the MCP server at an HTTPS address instead of a local process. ycli mcp start --transport http serves it there. Each MCP client signs its user in through Yandex ID, and every tool call runs with that user's own Yandex token, so everyone keeps their own Tracker, Wiki and Forms permissions.

There is no public ycli instance: you run your own, with your own Yandex OAuth app.

How sign-in works

The MCP authorization spec forbids a server to pass a client's token through to another API, and Yandex ID cannot register MCP clients on the fly. So the server is the OAuth client of Yandex (fastmcp's OAuthProxy):

sequenceDiagram
    participant C as MCP client
    participant S as ycli server
    participant Y as Yandex ID
    C->>S: register, then /authorize
    S->>C: consent page
    S->>Y: redirect to oauth.yandex.ru (PKCE)
    Y->>S: /auth/callback with a code
    S->>Y: exchange the code for the user's Yandex token
    S->>C: the server's own token (not the Yandex one)
    C->>S: tools/call with the server's token
    S->>S: look up the user's Yandex token, check it was issued to this app
    S->>Y: Tracker / Wiki / Forms API as that user

The client only ever holds the server's token. The server accepts a Yandex token only if Yandex ID says it was issued to the server's own OAuth app.

1. Register a Yandex OAuth app

At oauth.yandex.ru, create an app for user authorization (web services), not one "for API access" (that kind has a fixed redirect URI):

Field Value
Redirect URI <base_url>/auth/callback, e.g. https://mcp.example.com/auth/callback
Permissions Tracker, Wiki and Forms, read and write as you need (Yandex allows at most three permission groups per app)

Note the client id and the client secret.

2. Configure

Variable Required Meaning
YCLI__MCP__BASE_URL yes the public HTTPS address clients use, e.g. https://mcp.example.com
YANDEX_ID_ORGANIZATION_ID yes the organization every caller works in
YANDEX_OAUTH_CLIENT_ID, YANDEX_OAUTH_CLIENT_SECRET yes the app from step 1
YCLI__MCP__JWT_SIGNING_KEY recommended signs the server's tokens; derived from the client secret when unset, so rotating the secret signs everyone out
YCLI__MCP__HOST, YCLI__MCP__PORT no where the process listens (default 127.0.0.1:8000); --host / --port override them
YCLI__MCP__TOKEN_CACHE_SECONDS no how long a verified Yandex token is trusted before Yandex ID is asked again (default 300); a revoked token works at most this long
FASTMCP_HOME no where client registrations and tokens are kept, encrypted (default: the user data directory; /data in the Docker image); keep it on a volume

YANDEX_ID_OAUTH_TOKEN is not used over HTTP: a call without a signed-in user is refused, never served with a token from the environment.

3. Run

With Docker (the image's ycli is the entry point):

docker run -d --name ycli-mcp -p 127.0.0.1:8000:8000 \
  -e YCLI__MCP__BASE_URL -e YANDEX_ID_ORGANIZATION_ID \
  -e YANDEX_OAUTH_CLIENT_ID -e YANDEX_OAUTH_CLIENT_SECRET -e YCLI__MCP__JWT_SIGNING_KEY \
  -v ycli-mcp:/data \
  ghcr.io/bim-ba/ycli mcp start --transport http --host 0.0.0.0 --toolsets core

Or directly: uvx --from 'yandex-cli[mcp]' ycli mcp start --transport http. The toolset flags (--toolsets, --read-only, …; see Serve the MCP server) apply as over stdio.

4. Put HTTPS in front

The server speaks plain HTTP and expects a TLS proxy at YCLI__MCP__BASE_URL. With Caddy:

mcp.example.com {
    reverse_proxy 127.0.0.1:8000
}

Both steps with Docker Compose

Steps 3 and 4 as one file: ycli plus Caddy, which gets the certificate for your domain. Put MCP_DOMAIN (the host name, e.g. mcp.example.com) and the variables of step 2 into a .env file next to it, then run docker compose up -d.

compose.yaml
# Self-hosts the ycli MCP server over HTTPS: Caddy gets the certificate for MCP_DOMAIN and proxies to ycli.
services:
  ycli:
    image: ghcr.io/bim-ba/ycli:latest
    command: ["mcp", "start", "--transport", "http", "--host", "0.0.0.0", "--toolsets", "core"]
    environment:
      YCLI__MCP__BASE_URL: https://${MCP_DOMAIN:?set MCP_DOMAIN, e.g. mcp.example.com}
      YANDEX_ID_ORGANIZATION_ID: ${YANDEX_ID_ORGANIZATION_ID:?set YANDEX_ID_ORGANIZATION_ID}
      YANDEX_OAUTH_CLIENT_ID: ${YANDEX_OAUTH_CLIENT_ID:?set YANDEX_OAUTH_CLIENT_ID}
      YANDEX_OAUTH_CLIENT_SECRET: ${YANDEX_OAUTH_CLIENT_SECRET:?set YANDEX_OAUTH_CLIENT_SECRET}
      YCLI__MCP__JWT_SIGNING_KEY: ${YCLI__MCP__JWT_SIGNING_KEY:-}
    volumes:
      - ycli-data:/data
    restart: unless-stopped

  caddy:
    image: caddy:2
    command: caddy reverse-proxy --from ${MCP_DOMAIN} --to ycli:8000
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - caddy-data:/data
    depends_on:
      - ycli
    restart: unless-stopped

volumes:
  ycli-data:
  caddy-data:

Checked on 2026-10-03: docker compose config accepts the file and the ycli service starts and serves its sign-in metadata; Caddy was not run, it needs a real domain.

5. Connect clients

Client How
claude.ai / Claude Desktop Settings → Connectors → Add custom connector → https://mcp.example.com/mcp
ChatGPT developer mode → add a connector with https://mcp.example.com/mcp
Claude Code claude mcp add --transport http yandex-360 https://mcp.example.com/mcp
VS Code, Cursor an http server entry with the same URL

The client opens the sign-in page, shows a consent screen, then Yandex ID.

Limits

  • One organization per server. Run one server per organization.
  • One replica: client registrations live in FASTMCP_HOME. Several replicas need a shared store, which ycli does not configure yet.
  • Requests are stateless, so restarts do not drop sessions; signed-in users stay signed in as long as FASTMCP_HOME and the signing key survive.