tino.mcp

MCP (Model Context Protocol) server exposing TINO as tools for AI agents.

The server is mounted into the main FastAPI app as a Streamable HTTP ASGI sub-application at /mcp. It reuses TINO’s existing services and enforces the same per-bucket role model as the REST API.

Authentication follows the MCP OAuth 2.0 specification: TINO acts as a Resource Server and delegates authentication to the configured OIDC provider (it still enforces its own per-bucket authorization). MCP clients identify themselves to that provider with a Client ID Metadata Document (CIMD) — their client_id is an HTTPS URL the provider dereferences, so there is no Dynamic Client Registration — obtain an access token, and present it to TINO as a Bearer token. TINO validates the token against the provider’s JWKS and runs each tool as that user, with their real group memberships driving bucket access.

tino.mcp.server.MCP_MAX_RESULTS = 50

Maximum number of file results the search tool returns across all buckets.

tino.mcp.server.MCP_MIN_QUERY_LEN = 2

Search queries shorter than this are ignored (the tool returns an empty list).

class tino.mcp.server.OIDCTokenVerifier(discovery_url: str, groups_claim: str)

Validate provider-issued JWT access tokens against the OIDC JWKS.

On success the token’s claims are turned into a User (username + groups) and bound to the request context for the tools to use.

async verify_token(token: str) AccessToken | None

Validate the JWT and bind the resolved user to the request context.

async tino.mcp.server.commit(bucket: str, message: str, files: list[str] | None = None) dict

Commit changes in a bucket. Requires the committer role.

If files is omitted, all changed files in the working tree are committed.

tino.mcp.server.compile_typst(bucket: str, path: str) dict

Compile a Typst file to verify it is valid.

Returns {"success": true, "pages": N} when compilation succeeds, or {"success": false, "error": "..."} with the compiler error otherwise. Use this after editing to check your work before committing.

async tino.mcp.server.create_dir(bucket: str, path: str) dict

Create an empty directory in a bucket. Requires the editor role.

Fails if the directory already exists or the path is invalid. Note that git does not track empty directories, so a created directory only persists once it contains a committed file.

async tino.mcp.server.delete_dir(bucket: str, path: str) dict

Delete a directory and all its contents. Requires the editor role.

async tino.mcp.server.delete_file(bucket: str, path: str) dict

Delete a file from a bucket. Requires the editor role.

tino.mcp.server.git_log(bucket: str, path: str | None = None, max_count: int = 20) list[dict]

Return commit history for a bucket, optionally filtered to a single file.

tino.mcp.server.git_status(bucket: str) list[dict]

Return the working-tree status (modified, untracked, deleted) of a bucket.

tino.mcp.server.list_buckets() list[dict]

List all buckets the current user can access, with their role on each.

Buckets may include an instructions field with specific guidance for working with that bucket’s content. Always read and follow these instructions before making changes.

tino.mcp.server.list_files(bucket: str) list[dict]

List all files and directories in a bucket.

tino.mcp.server.read_file(bucket: str, path: str) str

Read the text content of a file in a bucket.

async tino.mcp.server.rename_dir(bucket: str, old_path: str, new_path: str) dict

Rename or move a directory and all its contents. Requires the editor role.

async tino.mcp.server.rename_file(bucket: str, old_path: str, new_path: str) dict

Rename or move a file within a bucket. Requires the editor role.

Fails if the source is missing, the destination already exists, or the path is invalid.

tino.mcp.server.search(query: str, bucket: str | None = None) list[dict]

Search file names and contents across the buckets you can access.

Pass bucket to limit the search to a single bucket, or omit it to search every accessible bucket. Each result is a file with name_match and any matching content snippets (line number + text). Prefer this over listing every bucket and reading files one by one when locating a document.

async tino.mcp.server.write_file(bucket: str, path: str, content: str) dict

Create or overwrite a text file in a bucket. Requires the editor role.

Server-level MCP instructions, shared by the MCP server and the REST API.

Kept free of the MCP SDK so the bucket settings dialog can fetch the full instruction text (built-in plus the TINO_MCP_INSTRUCTIONS override) without pulling in the MCP server machinery.

tino.mcp.instructions.BASE_INSTRUCTIONS = 'TINO is a collaborative Typst document platform.\nDocuments live in *buckets* (git repositories).\nUse these tools to list, read, write, compile, and commit Typst source files.\nAlways compile after editing to verify the document is valid.\nEach bucket may have specific instructions.\nAlways list buckets first and follow any per-bucket guidance.\nTo locate a document by file name or content, use the ``search`` tool instead of\nlisting and reading every file.\n'

Built-in instructions describing TINO and how an agent should use the tools.

tino.mcp.instructions.server_instructions() str

Return the full server-level instructions an MCP agent receives.

This is BASE_INSTRUCTIONS plus the optional TINO_MCP_INSTRUCTIONS global override. Per-bucket instructions are appended separately (see the list_buckets tool).