πŸ› οΈ Development setupΒΆ

PrerequisitesΒΆ

Project setupΒΆ

Clone the repository and create a virtual environment:

git clone https://github.com/confirm/tino.git
cd tino
make venv
source .venv/bin/activate
make develop

make develop installs the Python package in editable mode (with dev dependencies) and the Node.js dependencies.

Running the dev serverΒΆ

Start the development server with auto-reload:

make server

This launches Uvicorn on http://localhost:8000 with hot-reload enabled. For convenience, make server sets dev-friendly defaults so it runs without any further configuration:

  • TINO_AUTH_DISABLED=true

  • TINO_BASE_URL=http://localhost:8000

  • TINO_SECRET_KEY=develop (which keeps you logged in across reloads)

These defaults are overridable from your shell or the command line β€” for example, to run with authentication enabled (see πŸš€ Deployment):

make server \
    TINO_AUTH_DISABLED=false \
    TINO_OIDC_DISCOVERY_URL=https://<sso-host>/realms/<realm>/.well-known/openid-configuration \
    TINO_OIDC_CLIENT_ID=tino \
    TINO_OIDC_CLIENT_SECRET=<secret>

Developing the MCP serverΒΆ

MCP OAuth requires a public HTTPS URL. localhost is not enough because the MCP client needs to reach TINO’s well-known endpoints and the OIDC provider needs to redirect back to a real origin.

The easiest way to get one during development is a Cloudflare Tunnel (cloudflared):

# In one terminal β€” start TINO with auth enabled:
TINO_AUTH_DISABLED=false \
TINO_BASE_URL=https://<tunnel-host> \
TINO_MCP_ENABLED=true \
TINO_OIDC_DISCOVERY_URL=https://<sso-host>/realms/<realm>/.well-known/openid-configuration \
TINO_OIDC_CLIENT_ID=tino \
TINO_OIDC_CLIENT_SECRET=<secret> \
make server

# In another terminal β€” expose localhost via the tunnel:
cloudflared tunnel --url http://localhost:8000

Note

cloudflared prints the temporary public URL (e.g. https://random-words.trycloudflare.com). Use that as TINO_BASE_URL.

Important

The tunnel hostname must be registered as a valid redirect URI in your OIDC provider (e.g. as a Valid redirect URIs entry in Keycloak). For a persistent hostname, create a named tunnel instead of the quick --url shortcut.

Once the tunnel is up, point an MCP client at it:

$ claude mcp add --transport http tino https://<tunnel-host>/mcp

Vendored assetsΒΆ

The CodeMirror editor is bundled into tino/static/js/vendor/codemirror.js. After modifying tino/static/js/codemirror-entry.js or updating Node dependencies, rebuild with:

make vendor-js

The colours CSS is fetched separately:

make vendor-css

LintingΒΆ

Run all linters at once:

make test

Or run them individually:

make test-pycodestyle   # PEP 8
make test-pylint        # Pylint
make test-isort         # import order
make test-eslint        # JavaScript
make test-stylelint     # CSS

Building the docsΒΆ

Build the documentation once:

make docs

Or start a live-reloading preview:

make autodocs

This opens the docs in your browser on http://localhost:8888 and watches for changes.

Building the Docker imageΒΆ

make package            # creates a wheel in ``build/``
make docker-image       # copies wheel from ``build/`` into Docker image.

Project layoutΒΆ

tino/
β”œβ”€β”€ app.py              FastAPI application factory
β”œβ”€β”€ auth.py             Authentication & OIDC
β”œβ”€β”€ collab.py           Real-time collaboration (Yjs/WebSocket)
β”œβ”€β”€ config.py           Environment-based configuration
β”œβ”€β”€ dependencies.py     FastAPI dependency injection
β”œβ”€β”€ mcp/                MCP server, OAuth resource server & instructions
β”œβ”€β”€ middleware.py        ASGI middleware stack
β”œβ”€β”€ models.py           Pydantic request/response schemas
β”œβ”€β”€ notifier.py         File-change notifications
β”œβ”€β”€ routers/            API endpoint modules
β”œβ”€β”€ services/           Business logic (bucket, file, git, compiler, …)
└── static/             SPA frontend
    β”œβ”€β”€ css/
    β”œβ”€β”€ js/             ES modules (no framework, vanilla JS)
    └── index.html