Skip to content

The Flui Assistant and MCP

Most platforms bolt a chatbot on at the end and hope it doesn’t say anything embarrassing. Flui treats its assistant as a real surface of the product, with the same care the rest of the platform gets: it answers from a compiled knowledge base rather than free-associating, it can actually do things through a shared tool registry, and every action it can take is governed by the same scope model whether a human is driving it in the dashboard or an external agent is calling it over MCP.

This chapter is about the shape of that choice and — more importantly — the safety model underneath it, because the load-bearing part of an assistant that can deploy and uninstall your apps is what stops it doing the wrong one.

Two surfaces, one tool registry

There are two ways into the same capabilities:

  • The in-product assistant — the chat surface inside Flui’s dashboard. You ask a question or a request in natural language; it answers from the docs, and when you ask it to act it runs an agentic tool-use loop on your behalf.
  • The MCP server — a Model Context Protocol endpoint that exposes the same tools to external agents (your own scripts, an IDE agent, another LLM application).

The point of sharing is that a tool is written once. The same registry (ALL_TOOLS in the code) backs both the in-process assistant loop and the MCP server, so there is no second, divergent surface to keep in sync — and, crucially, no second place where the safety checks could drift apart. Both go through the same scope tiers and the same destructive gate, described below.

Both surfaces get their LLM from Flui’s inference layer rather than holding a hard-coded provider — see Inference and bring-your-own-LLM for how that endpoint is resolved.

The assistant answers from a knowledge base, not from memory

The assistant does not reach into a general-purpose model’s training data to answer questions about Flui. Instead, Flui’s documentation is compiled into a knowledge base — a single versioned artifact (kb.json) built by a script from the concept docs, the CLI prose, a generated CLI reference, the flui.yaml schema, and a hand-authored guardrails file. The assistant answers strictly from this corpus.

This grounding is a deliberate choice to keep answers tied to the facts. The guardrails make the contract explicit: the assistant’s knowledge is exactly the corpus it is handed — it is asked not to invent commands, flags, manifest fields, or version numbers, and if the answer isn’t in the corpus it says so and points you at flui <command> --help or the docs rather than guessing. The intent is to keep the assistant inside what it actually knows and to minimise the chance of an answer drifting from the documentation — not to assume it would otherwise be wrong. The knowledge base is also stamped with the CLI/platform/schema versions it was built against, so the assistant can tell you which version it is reasoning about.

This is why keeping these docs accurate matters. The page you are reading is one of the sources that gets compiled into that knowledge base. An inaccurate doc is not just wrong on the website — it becomes a wrong answer the assistant gives with confidence.

A scope gate that also routes

Sending the whole corpus to the model on every message would be slow and expensive (it is a large prefill). Flui avoids that with a single cheap LLM call — a guard step — that does two jobs at once:

  1. Scope gate. It decides whether your message is on-topic for Flui at all. A genuinely off-topic question (the weather, philosophy, general trivia) is refused with a short, warm message in your own language that says the assistant only helps with Flui — it does not answer the off-topic question. The bias is strongly toward answering: greetings, follow-ups, and anything that plausibly concerns running apps on Flui are treated as on-topic.
  2. Router. For an on-topic message, the same call returns the ids of the one-to-four most relevant knowledge sections. Only those sections are injected into the answering prompt, keeping it small.

So one inexpensive model call refuses the irrelevant and narrows the relevant — before the more expensive answering call ever runs.

The agentic tool-use loop

When you ask the assistant to do something, it runs an agentic loop: the model can call tools, see their results, and call more tools, up to a bounded number of iterations, before it writes a final answer. In the dashboard this is streamed over Server-Sent Events, so you watch the text and the individual tool steps appear live rather than waiting for a wall of output.

The loop is built defensively, so it stays reliable even on medium-to-small models rather than depending on a large one. A few of those safeguards:

  • A URL guard. Application endpoint URLs are passed through only when they actually came from a tool result; anything else is stripped before it reaches you. So a link you get is one the platform produced, not one assembled in the answer.
  • Doc-link surfacing. The sections the router grounded the answer in are turned, deterministically, into links to the published docs — the documentation URLs are produced by Flui, not composed in the prompt.
  • Parameter recovery and rate-limit handling. If a tool call comes back malformed, or a provider’s per-minute limit is hit, the loop recovers and retries rather than failing the whole turn.

Taken together, these are also why the assistant does not need a large frontier model. The corpus handed to it is already narrowed by the router, the answers are grounded in that corpus, and the guards above are deterministic rather than left to the model — so a medium-to-small model is enough, which is exactly what Flui’s inference layer recommends by default. See Inference and bring-your-own-LLM.

The tools span app lifecycle, deploy, debugging, the catalog, infrastructure, and GitHub-repo onboarding — the same operations you would otherwise reach for in the CLI or dashboard.

The safety model

Everything above is convenience. This section is the contract.

Scopes, ordered into tiers

Every tool declares a scope, and scopes are ordered into four tiers by blast radius:

read → plan → write → destructive
  • read — list and inspect: applications, the catalog, observability/logs.
  • plan — non-mutating analysis, e.g. validating a flui.yaml spec.
  • write — state-changing operations: install, deploy, scale, restart, stop, connect a repo.
  • destructive — delete and uninstall.

A grant at a tier implies every lower tier, so granting write also grants read and plan.

Default-deny

The system is default-deny. A tool runs only if its scope is in the calling principal’s granted set. An authenticated principal with no explicit grant gets the safe default — read and plan only. To get write or destructive, the scope has to be granted deliberately (on an API key or as an identity-provider role), and an explicit least-privilege grant always wins: a scoped API key stays least-privilege even for an admin.

The MCP tool groups map onto these tiers: application, catalog, observability, spec, infrastructure, and repo.

The destructive gate (the load-bearing rule)

Deleting and uninstalling are special. A destructive tool requires both of these to be true before it can run:

  1. The principal holds a destructive scope, and
  2. A server-wide environment flag, MCP_ALLOW_DESTRUCTIVE, is set to true.

The flag is off by default. When it is off, a destructive operation is refused deterministically — never offered, never retried — with an “an administrator must enable them” style message. This is not the model choosing to behave; it is code refusing before the tool body ever runs, and the refusal is audited.

The same gate exists on both surfaces, but the human checkpoint differs by context:

  • In the interactive assistant, a write or destructive action pauses for explicit human confirmation — the action comes back as a pending step you approve before anything changes. (And if a destructive op is disabled by the flag, the assistant doesn’t even offer the confirmation — it can’t honour it, so it tells you how to enable it instead.)
  • In headless MCP, there is no human in the loop to confirm, so the environment flag is the checkpoint. An operator decides, once, at the server level, whether external agents are allowed to perform destructive operations at all.

There is one more guard worth naming: the loop is idempotent across a conversation. A write or destructive action that already succeeded won’t run a second time if it is re-proposed — so an “install” or a “delete” can’t fire twice from a repeated suggestion.

Why this shape

The thinking is blast-radius first. The most useful thing an assistant can do is also the most dangerous, so rather than rely on every proposal being correct, the design makes a wrong one cheap and recoverable instead of catastrophic — defence in depth, not a verdict on the model. Read and plan are free to flow because they change nothing. Writes need a human’s yes (or a deliberate scope). And the irreversible operations — delete, uninstall — need an operator to have turned them on at the server in addition to everything else. The goal is that no single mistaken token, and no single off turn, can quietly destroy your data.

Where this chapter goes from here