Tibi

MCP server

Strands hosts an MCP server for Tibi so Claude Code / Cursor / Continue (and any MCP-aware client) can introspect a Tibi project and call typed tools over JSON-RPC 2.0. Point your client at the URL below — no install, no `tibi-mcp` binary on your machine.

Connect

The hosted endpoint:

https://mcp.tibi.fi

Add it to ~/.config/claude-code/mcp.json (or the equivalent for your client) under mcpServers:

{ "mcpServers": { "tibi": { "url": "https://mcp.tibi.fi", "transport": "http" } } }

If your tenant gates the server behind a bearer token, add it as a header:

{ "mcpServers": { "tibi": { "url": "https://mcp.tibi.fi", "transport": "http", "headers": { "Authorization": "Bearer $TIBI_MCP_TOKEN" } } } }

Once registered, prompts like “list every route in this project” or “validate this .tibi snippet before I save it” fire the tools below instead of guessing.

The endpoint speaks the Streamable HTTP transport (POST / for JSON-RPC, GET / for the SSE channel) on a single root URL, so it also adds cleanly as a Claude Desktop / claude.ai custom connector — paste the URL above as-is, with no /mcp suffix and no OAuth (auth is bearer-token only, and only when your tenant sets one).

Available tools

Every tool is read-only — none write to disk or shell out.

tibi_about

Server identity, version, and the configured public URL. Useful to confirm an LLM is talking to the project you think it is.

tibi_routes

Walks the project's pages/ dir through the same router tibi serve uses. One line per route: <url_pattern> <file>. Flags (404 fallback) / (500 fallback) and dynamic params ({slug}, *rest) inline.

tibi_channels

Walks every .tibi file in pages/ + components/ and surfaces any __channels frontmatter. Returns <name> <interval_ms> <payload> per channel — the same shape tibi serve reads to spawn tick tasks.

tibi_stores

Every [[store]] entry from .tibi-config with its default TTL. Tells the LLM which client-store namespaces exist (and which keys carry which lifetime) without parsing the config itself.

tibi_crons

Every [[cron]] entry: <name> <schedule> <file>. Mirrors the cron table tibi serve spawns.

tibi_plugins

Every script plugin under [server] plugins_dir. Returns <name> <lang> <file> so the client knows what hooks fire at startup.

tibi_packages

Every packaged plugin (the two-file tibi-plugin.toml + tibi-plugin.tpkg bundles) under [server] plugins_dir. Returns <name> <version> <prefix*-from-dir> <path>; the prefix column tells the client what tag namespace the package contributes (e.g. Ti*<-components means every file there resolves as <Ti{Tag}/>).

tibi_file_format

Returns the canonical authoring spec for a .tibi file: required <template> wrapper, optional <style> / <script>, the <script tibi> opt-in for the reactive surface, frontmatter fence rules, and common authoring mistakes. Call this before hand-writing or editing .tibi sources.

tibi_validate

Pass a raw .tibi source string and the server runs the real parser against it. Surfaces structural errors (missing template, unterminated blocks) plus heuristics for the most common LLM-authoring mistake — using reactive helpers (state / effect / onMount / emit) in a plain <script> tag that lacks the tibi attribute.

Authoring rules an LLM should follow

Surface the spec via tibi_file_format before any edit. The high-bit rules:

  • Top-level <template> is mandatory. Bare HTML at the file top isn't valid.
  • Reactivity (state / computed / effect / onMount / emit / $route / template directives like {{expr}} / :attr / t-show / t-bind / t-if / t-for) only exists inside <script tibi>. A plain <script> won't see them.
  • Component tags are CamelCase; lowercase tags are treated as native HTML.
  • Frontmatter fence: ---/---js/---ts/---py/---rs at the very top, closed with ---. result = { ... } exposes top-level keys to the template as {ident} tokens.

tibi_validate catches both parser-level mistakes and the “forgot the tibi attribute” class of bug.

Self-host (optional)

The MCP server ships as the tibi-mcp binary in the standard Tibi CLI install. Run it against a project root if you'd rather host your own (air-gapped, private repo, custom auth in front):

$ TIBI_PROJECT_DIR=/abs/path/to/project tibi-mcp tibi-mcp listening on http://0.0.0.0:3300/ (Streamable HTTP: POST JSON-RPC + GET SSE)

The knobs:

  • TIBI_PROJECT_DIR — absolute path to the project root (where .tibi-config lives). Required.
  • TIBI_MCP_BIND — address to listen on. Defaults to 0.0.0.0:3300.
  • TIBI_MCP_AUTH_TOKEN — optional bearer required on every request. Constant-time compare so probes can't time-leak it. Skip for local dev; set it if you expose the port to anything other than 127.0.0.1.
  • TIBI_MCP_PUBLIC_URL — surfaced in tibi_about + the docs page so clients know the live origin (e.g. https://tibi-mcp.services.strands.gg).

Server is read-only by design — every tool returns derived information; none write to disk or hit the network. If you put it on the public internet, terminate TLS in front and set TIBI_MCP_AUTH_TOKEN.

Embed it in your own service

The transport and tools are also a library, the tibi_mcp crate, so a service can mount the same endpoint with its own sign-in and its own tools. Implement McpHandler: authenticate turns each request into a caller (or returns the response to send back, such as a 401 with your WWW-Authenticate challenge), tools lists what that caller may use, and call_tool runs one. tibi_mcp::router wraps it in the Streamable HTTP endpoint, ready to nest under any path.

use std::sync::Arc; use tibi_mcp::{McpHandler, ProjectTools, ServerInfo}; struct MyHost { tools: ProjectTools } impl McpHandler for MyHost { type Caller = String; // your user id fn authenticate(&self, headers: &HeaderMap) -> Result<String, Response> { my_auth::user_from_bearer(headers) // Err(401) when missing or invalid } fn tools(&self, _user: &String) -> Vec<Value> { let mut tools = ProjectTools::definitions(); tools.extend(my_tools::definitions()); tools } async fn call_tool(&self, user: &String, name: &str, args: &Value) -> anyhow::Result<Value> { match self.tools.call(name, args) { Some(result) => result, None => my_tools::call(user, name, args).await, } } } let info = ServerInfo { name: "my-host".into(), version: "1.0.0".into(), public_url: None }; let app = axum::Router::new().nest("/mcp", tibi_mcp::router(info, Arc::new(host)));

ProjectTools is the read-only tibi_* set above, and BearerToken is the shared-token gate the binary uses, both reusable on their own. The tibi-mcp binary is exactly this, with BearerToken and ProjectTools wired from the environment.