Skip to content

Commit 540b031

Browse files
Add flet-mcp: MCP server for LLM agents (#6624)
* Add flet-mcp package and integrate CLI/CI Introduce a new flet-mcp Python package (pyproject, README, package code and data) providing an MCP server for LLM agents, including ApiStore and bundled icons/docs data. Integrate flet-mcp into the CLI by registering an optional "mcp" subcommand only when the package is installed and add an optional dependency entry in flet-cli's pyproject. Update GitHub Actions to build, package, and publish flet-mcp (new build_flet_mcp job and inclusion in the publish step). Also add .gitignore for generated MCP artifacts and include various minor Python package updates. * Add MCP build modules and indexing tools Introduce a new flet_mcp build subpackage and tooling to generate MCP data. Adds api.py (API reference builder using Griffe, injects icon enums and extracts CLI help), docs.py (mkdocs search_index -> SQLite FTS5 indexer), examples.py (pyproject.toml-based examples indexer), and indexer.py (orchestrator that runs examples/docs/API pipelines and writes mcp.db/api.json). Update .gitignore to un-ignore the build subpackage and tweak icons.yml comment/content. * feat(flet-mcp): gate tool groups behind FLET_MCP_ENABLE_* env vars Defaults: API and ICONS groups on, EXAMPLES/DOCS/CLI off, so the hallucination-reduction starter set is what consumers get out of the box. Server instructions enumerate the active groups so the model sees only relevant guidance. * docs(flet-mcp): document FLET_MCP_ENABLE_* tool-group gating Adds a 'Toggling groups' section to the package README explaining which tool groups are on by default, the env var per group, and how the active set is surfaced to MCP clients via the server's initialize instructions. * docs(flet-mcp): document mcp-build group and SDK-workspace build * feat(flet-mcp): merge get_control_api+get_type_api into get_api, mark async methods, classify @value as dataclass - Single get_api(name) tool replaces get_control_api + get_type_api. Looks across controls, services, dataclass types, and events and tags every match with a 'kind' discriminator ('control'/'service'/'type'/'event'). One tool means one lookup instead of multiple guess-the-bucket attempts. - Methods declared async def are now marked '"async": true' in the api.json methods list. The get_api tool docstring and the FastMCP server instructions both call this out, so callers know to await them (e.g. page.window.center() is async — handler must be async def). - Treat Flet's @value decorator as a dataclass in the API builder. Most styling/value classes (ButtonStyle, Padding, ...) use @value, so prior builds were missing 70% of them. Type count: 28 -> 199. * fix(flet-mcp): index Page by treating BasePage as a control base Page extends BasePage, which extends BaseControl, but the classifier only matched direct inheritance from BaseControl/LayoutControl/ AdaptiveControl/Service. Adding BasePage to the known control bases catches Page itself (the most-used class in Flet). Controls: 166 -> 168 (Page + anything else inheriting from BasePage). * feat(flet-mcp): expose pip package name on every API entry get_api responses now carry a 'package' field naming the pip-installable package the class lives in. Derived from the first segment of 'module' with underscores swapped for hyphens — so flet_video -> flet-video, flet_audio -> flet-audio, etc. Core flet classes stay 'flet'. Agents can now route 'use Video' to 'first add flet-video to deps' without having to guess the distribution name from the import path. The tool docstring, FastMCP server instructions, and README all call this out so consumers know to surface it to the user. * feat(flet-mcp): walk inheritance recursively, fold enums into get_api, mark deprecated classes Three related changes to make get_api the true universal verifier: 1. _is_control_class now walks the inheritance chain through a global class-name registry built in a pre-pass over all loaded packages. Catches transitive subclasses of BaseControl/LayoutControl/ AdaptiveControl/Service through intermediate bases (DialogControl, BasePage, ...) without hardcoding every link. AlertDialog, BottomSheet, SnackBar, Banner, Page, and ~80 other previously-missed controls now resolve. Controls indexed: 168 -> 248. BasePage drops out of the hardcoded base set since it's no longer needed. 2. api_store.get() now falls through to enums after controls/types/events. Calling get_api('MainAxisAlignment') returns the enum directly instead of 'not found' (it used to force a separate get_enum round-trip). Large enums (Icons, CupertinoIcons) come back truncated, same as get_enum. 3. Deprecated classes are kept in the index but marked with a 'deprecated' object carrying reason/version/delete_version parsed from @deprecated_class. ElevatedButton now answers with 'deprecated: {reason: "Use Button instead."}' — the model sees both the verdict and the replacement in one shot. * Annotate Gradient colors with ColorValue for Gradient, list[ColorValue] instead of list[str] * Clarify colors cookbook usage and examples * feat(flet-mcp): defer docs tools, guard unindexed queries, document in-process use - Add table guards to search_docs/get_doc so they return empty results instead of crashing when no docs index is built - Mark the docs index as deferred (mkdocs search_index.json no longer produced after the Docusaurus + Algolia migration) in README and the --docs CLI help - Document in-process FastMCP usage via fastmcp.Client(mcp); fix stale get_control_api reference (now get_api) and bump the Pydantic AI model * docs(cli): add flet mcp command page to fix broken docs link The CLI docs index auto-lists every registered command (including the new 'mcp' command), so the Docusaurus build failed on a broken link to a missing flet-mcp page. Add the cli/flet-mcp.md stub (importing the generated cli-mcp.mdx partial) and the sidebar entry, matching the other commands. * docs(cookbook): add Flet MCP server article Explain how to install and run the flet-mcp server, configure it in AI clients, toggle tool groups, and consume it from a custom agent via Pydantic AI or an in-process FastMCP client. * docs(flet-mcp): use single backticks in docstrings * docs(breaking-changes): remove orphaned duplicate guides Drop the unprefixed data-channel-protocol-upgrade.md, default-bundled-python-3-14.md, and removed-pyodide-version-export.md — identical, unreferenced leftovers of the v0-86-0-* rename. Only the v0-86-0-prefixed files are linked from the sidebar. --------- Co-authored-by: InesaFitsner <inesa@appveyor.com>
1 parent 5bf63e1 commit 540b031

34 files changed

Lines changed: 4430 additions & 299 deletions

.github/workflows/ci.yml

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,48 @@ jobs:
739739
sdk/python/dist/*.whl
740740
sdk/python/dist/*.tar.gz
741741
742+
# ==============
743+
# ==============
744+
# Build flet-mcp
745+
# ==============
746+
build_flet_mcp:
747+
name: Build flet-mcp Python package
748+
runs-on: ubuntu-latest
749+
env:
750+
PYPI_VER: ${{ needs.build_flet_package.outputs.PYPI_VER }}
751+
needs:
752+
- python_tests
753+
- build_flet_package
754+
steps:
755+
- name: Checkout repository
756+
uses: actions/checkout@v4
757+
758+
- name: Setup uv
759+
uses: astral-sh/setup-uv@v6
760+
761+
- name: Build MCP data
762+
working-directory: ${{ env.SDK_PYTHON }}
763+
run: |
764+
uv run --group mcp-build flet mcp build \
765+
--examples examples \
766+
--output packages/flet-mcp/src/flet_mcp/data/
767+
768+
- name: Build Python package
769+
working-directory: ${{ env.SDK_PYTHON }}
770+
run: |
771+
source "$SCRIPTS/common.sh"
772+
patch_python_package_versions
773+
uv build --package flet-mcp
774+
775+
- name: Upload artifacts
776+
uses: actions/upload-artifact@v4
777+
with:
778+
name: flet-mcp-python-distribution
779+
if-no-files-found: error
780+
path: |
781+
sdk/python/dist/*.whl
782+
sdk/python/dist/*.tar.gz
783+
742784
# ==============
743785
# GitHub Release
744786
# ==============
@@ -756,6 +798,7 @@ jobs:
756798
- build_templates
757799
- build_flet_extensions
758800
- build_flet_cli_desktop
801+
- build_flet_mcp
759802
if: ${{ startsWith(github.ref, 'refs/tags/') }}
760803
steps:
761804
- name: Detect pre-release
@@ -839,6 +882,7 @@ jobs:
839882
flet_secure_storage \
840883
flet_spinkit \
841884
flet_video \
842-
flet_webview; do
885+
flet_webview \
886+
flet_mcp; do
843887
uv publish dist/**/${pkg}-*
844888
done

sdk/python/packages/flet-cli/pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ dependencies = [
1818
"rich >=13.0.0"
1919
]
2020

21+
[project.optional-dependencies]
22+
mcp = ["flet-mcp"]
23+
2124
[project.urls]
2225
Homepage = "https://flet.dev"
2326
Repository = "https://github.com/flet-dev/flet"

sdk/python/packages/flet-cli/src/flet_cli/cli.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,16 @@ def get_parser() -> argparse.ArgumentParser:
122122
flet_cli.commands.devices.Command.register_to(sp, "devices")
123123
flet_cli.commands.doctor.Command.register_to(sp, "doctor")
124124

125+
# Register MCP command only if flet-mcp is installed
126+
try:
127+
from importlib import import_module
128+
129+
import_module("flet_mcp")
130+
mcp_cmd = import_module("flet_cli.commands.mcp")
131+
mcp_cmd.Command.register_to(sp, "mcp")
132+
except ImportError:
133+
pass
134+
125135
# set "run" as the default subparser
126136
set_default_subparser(parser, name="run", index=1)
127137

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import argparse
2+
from pathlib import Path
3+
4+
from flet_cli.commands.base import BaseCommand
5+
6+
7+
class Command(BaseCommand):
8+
"""Flet MCP server for LLM agents."""
9+
10+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
11+
sub = parser.add_subparsers(dest="mcp_command")
12+
13+
# `flet mcp build` sub-subcommand
14+
build_parser = sub.add_parser("build", help="Build MCP index data")
15+
build_parser.add_argument(
16+
"--examples",
17+
type=Path,
18+
help="Path to examples directory",
19+
)
20+
build_parser.add_argument(
21+
"--docs",
22+
type=Path,
23+
help="Path to mkdocs search_index.json "
24+
"(deferred — pending Docusaurus rework)",
25+
)
26+
build_parser.add_argument(
27+
"--output",
28+
type=Path,
29+
help="Output directory (default: flet_mcp/data/)",
30+
)
31+
32+
# `flet mcp` (serve) options — applied to the mcp parser itself
33+
parser.add_argument(
34+
"--transport",
35+
choices=["stdio", "streamable-http"],
36+
default="stdio",
37+
help="MCP transport mode",
38+
)
39+
parser.add_argument(
40+
"--port",
41+
type=int,
42+
default=8000,
43+
help="Port for HTTP transport mode",
44+
)
45+
46+
def handle(self, options: argparse.Namespace) -> None:
47+
if options.mcp_command == "build":
48+
self._handle_build(options)
49+
else:
50+
self._handle_serve(options)
51+
52+
def _handle_serve(self, options: argparse.Namespace) -> None:
53+
from flet_mcp.server import mcp
54+
55+
if options.transport == "streamable-http":
56+
mcp.run(transport="streamable-http", port=options.port)
57+
else:
58+
mcp.run(transport="stdio")
59+
60+
def _handle_build(self, options: argparse.Namespace) -> None:
61+
from flet_mcp.build.indexer import build_all
62+
63+
build_all(
64+
examples_dir=options.examples,
65+
docs_index=options.docs,
66+
output_dir=options.output,
67+
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Build artifacts (generated by `flet mcp build`)
2+
src/flet_mcp/data/api.json
3+
src/flet_mcp/data/mcp.db
4+
5+
# Un-ignore the build subpackage (matched by sdk/python/.gitignore "build/" rule)
6+
!src/flet_mcp/build/
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# flet-mcp
2+
3+
MCP (Model Context Protocol) server that gives LLM agents access to Flet examples, documentation, and API reference.
4+
5+
## Installation
6+
7+
```bash
8+
pip install flet-mcp
9+
```
10+
11+
## Building the data files
12+
13+
The MCP server reads a Griffe-introspected `api.json` and (optionally) a
14+
SQLite index of examples and docs. Build them from inside the flet SDK
15+
workspace so every Flet extension package (`flet-audio`, `flet-map`, …) is
16+
importable — the workspace declares them all as members, but you need the
17+
`mcp-build` dependency group to pick up the build-time deps (`markdownify`,
18+
`griffe`):
19+
20+
```bash
21+
cd sdk/python
22+
uv sync --group mcp-build
23+
uv run flet mcp build # api.json only
24+
uv run flet mcp build --examples ./examples # add examples index
25+
```
26+
27+
> **Docs index is currently deferred.** The `--docs` flag still expects a mkdocs
28+
> `search_index.json`, which the site no longer produces after the migration to
29+
> Docusaurus + Algolia. The `DOCS` tool group stays off by default; rebuilding
30+
> docs search against Docusaurus is tracked as follow-up work.
31+
32+
Running the build from elsewhere (a downstream project's venv that only
33+
installs core `flet`) works too, but the resulting `api.json` will be missing
34+
controls from every extension package not installed in that venv — the
35+
indexer logs a "Failed to load" line per missing package and skips it.
36+
37+
## Usage
38+
39+
### Start the MCP server
40+
41+
```bash
42+
# stdio transport (default, for use with Claude Desktop, Cursor, etc.)
43+
flet mcp
44+
45+
# HTTP transport
46+
flet mcp --transport streamable-http --port 8000
47+
```
48+
49+
### Configure in Claude Desktop
50+
51+
Add to your `claude_desktop_config.json`:
52+
53+
```json
54+
{
55+
"mcpServers": {
56+
"flet": {
57+
"command": "flet",
58+
"args": ["mcp"]
59+
}
60+
}
61+
}
62+
```
63+
64+
### List available tools
65+
66+
```bash
67+
fastmcp list packages/flet-mcp/src/flet_mcp/server.py
68+
```
69+
70+
### Call tools from the command line
71+
72+
```bash
73+
# Search examples
74+
fastmcp call packages/flet-mcp/src/flet_mcp/server.py search_examples '{"query": "dropdown"}'
75+
76+
# Get full example code
77+
fastmcp call packages/flet-mcp/src/flet_mcp/server.py get_example '{"example_id": "controls_dropdown_styled"}'
78+
79+
# Get API reference for any symbol (control, service, type, event, enum)
80+
fastmcp call packages/flet-mcp/src/flet_mcp/server.py get_api '{"name": "TextField"}'
81+
82+
# Find an icon
83+
fastmcp call packages/flet-mcp/src/flet_mcp/server.py find_icon '{"query": "settings"}'
84+
85+
# Search large enums
86+
fastmcp call packages/flet-mcp/src/flet_mcp/server.py search_enum_members '{"name": "Icons", "query": "arrow"}'
87+
88+
# Get CLI help
89+
fastmcp call packages/flet-mcp/src/flet_mcp/server.py get_cli_help '{"command": "run"}'
90+
```
91+
92+
### Use with Pydantic AI
93+
94+
```python
95+
from pydantic_ai import Agent
96+
from pydantic_ai.toolsets import MCPToolset
97+
from flet_mcp import mcp
98+
99+
agent = Agent("anthropic:claude-sonnet-4-6", toolsets=[MCPToolset(mcp)])
100+
result = agent.run_sync("Create a Flet app with a login form")
101+
```
102+
103+
### Use in-process via a FastMCP client
104+
105+
The exported `mcp` is a `FastMCP` instance, so a custom agent can talk to it
106+
in-process (no subprocess, no transport) by handing it to a `fastmcp.Client`.
107+
Set the `FLET_MCP_ENABLE_*` env vars before importing `flet_mcp` so the desired
108+
tool groups register. The client deserializes structured results onto `.data`:
109+
110+
```python
111+
import asyncio
112+
from fastmcp import Client
113+
from flet_mcp import mcp
114+
115+
async def main():
116+
async with Client(mcp) as client:
117+
api = (await client.call_tool("get_api", {"name": "TextField"})).data
118+
print(api["kind"], api["package"], len(api["properties"]))
119+
120+
asyncio.run(main())
121+
```
122+
123+
## Tools
124+
125+
Tools are organized into groups that can be toggled at server startup. Defaults
126+
focus on the hallucination-reduction starter set: **API** and **icons** are on;
127+
**examples**, **docs**, and **CLI** are off.
128+
129+
| Group | Default | Tools |
130+
|---|---|---|
131+
| `API` | on | `list_controls`, `get_api`, `get_enum`, `search_enum_members`, `enum_has_member` |
132+
| `ICONS` | on | `find_icon` |
133+
| `EXAMPLES` | off | `search_examples`, `get_example` |
134+
| `DOCS` | off | `search_docs`, `get_doc` |
135+
| `CLI` | off | `get_cli_help` |
136+
137+
| Tool | Description |
138+
|------|-------------|
139+
| `list_controls` | Browse controls and services, with optional filtering |
140+
| `get_api` | Get the API reference for a class by name — looks across controls, services, dataclass types (ButtonStyle, Padding, ...), and event classes. Async methods are marked `"async": true`; every entry carries a `"package"` field naming the pip-installable source package (`"flet"` = core, anything else needs adding to deps). |
141+
| `get_enum` | Get enum members |
142+
| `search_enum_members` | Search large enums (Icons, CupertinoIcons) |
143+
| `enum_has_member` | Check if an enum value exists |
144+
| `find_icon` | Search Material and Cupertino icons by keyword |
145+
| `search_examples` | Search example projects by keyword |
146+
| `get_example` | Get full source code for an example |
147+
| `search_docs` | Search documentation by keyword |
148+
| `get_doc` | Get full content of a doc section |
149+
| `get_cli_help` | Get structured CLI command options |
150+
151+
### Toggling groups
152+
153+
Each group is gated by an environment variable read at server startup:
154+
155+
| Variable | Default | Effect |
156+
|---|---|---|
157+
| `FLET_MCP_ENABLE_API` | `1` | Register the API tool group |
158+
| `FLET_MCP_ENABLE_ICONS` | `1` | Register `find_icon` |
159+
| `FLET_MCP_ENABLE_EXAMPLES` | `0` | Register example search/get tools |
160+
| `FLET_MCP_ENABLE_DOCS` | `0` | Register docs search/get tools |
161+
| `FLET_MCP_ENABLE_CLI` | `0` | Register `get_cli_help` |
162+
163+
Accepted truthy values: `1`, `true`, `yes` (case-insensitive). The active
164+
groups are also surfaced in the server's `initialize` instructions, so MCP
165+
clients that forward those instructions to the model (e.g. Pydantic AI's
166+
`MCPToolset(..., include_instructions=True)`) keep the model's guidance
167+
in sync with what's actually registered.
168+
169+
Examples:
170+
171+
```bash
172+
# Default starter surface (API + icons only)
173+
flet mcp
174+
175+
# Add examples and docs once their indexes have been built
176+
FLET_MCP_ENABLE_EXAMPLES=1 FLET_MCP_ENABLE_DOCS=1 flet mcp
177+
178+
# Narrow further: API tools only, drop icons
179+
FLET_MCP_ENABLE_ICONS=0 flet mcp
180+
```
181+
182+
Note: enabling `EXAMPLES` only registers the tools — you also need to populate
183+
the SQLite index by running `flet mcp build --examples <path>`. Without an index
184+
the tools register cleanly but return empty results. `DOCS` is deferred (see
185+
"Building the data files" above): the tools register and degrade gracefully to
186+
empty results, but no docs index is built yet.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
[project]
2+
name = "flet-mcp"
3+
version = "0.1.0"
4+
description = "Flet MCP server for LLM agents"
5+
authors = [{ name = "Appveyor Systems Inc.", email = "hello@flet.dev" }]
6+
license = "Apache-2.0"
7+
readme = "README.md"
8+
requires-python = ">=3.10"
9+
dependencies = [
10+
"fastmcp >=2.0.0",
11+
"pyyaml >=6.0",
12+
]
13+
14+
[project.optional-dependencies]
15+
build = [
16+
"markdownify >=0.14.1",
17+
"griffe >=1.6.2",
18+
"flet",
19+
"flet-cli",
20+
]
21+
22+
[project.urls]
23+
Homepage = "https://flet.dev"
24+
Repository = "https://github.com/flet-dev/flet"
25+
Documentation = "https://flet.dev/docs"
26+
27+
[build-system]
28+
requires = ["setuptools"]
29+
build-backend = "setuptools.build_meta"
30+
31+
[tool.setuptools.package-data]
32+
flet_mcp = ["data/*"]
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from flet_mcp.server import mcp
2+
3+
__all__ = ["mcp"]

0 commit comments

Comments
 (0)