Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
529 changes: 529 additions & 0 deletions fastmcp-migration/auth-provider-alternatives.md

Large diffs are not rendered by default.

1,456 changes: 1,456 additions & 0 deletions fastmcp-migration/banksy-research/banksy-architecture-research.md

Large diffs are not rendered by default.

178 changes: 178 additions & 0 deletions fastmcp-migration/banksy-research/canvas-mcp-alignment-assessment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# Canvas-MCP Alignment Assessment

Assessment of `tactivos/canvas-mcp` against the banksy FastMCP migration plan (`00-migration-execution-strategy.plan.md`). The goal is to ensure the banksy migration produces a project that can fully absorb canvas-mcp's tools and supporting code — either via `mount()` or direct migration.

**canvas-mcp repo**: `/Users/wkirkham/dev/canvas-mcp/`
**banksy migration plan**: `/Users/wkirkham/dev/willik-notes/fastmcp-migration/execution-strategy-research/00-migration-execution-strategy.plan.md`

---

## 1. Toolchain Comparison

| Category | Banksy Plan | canvas-mcp Actual | Status |
|---|---|---|---|
| Package manager | uv, PEP 621 `pyproject.toml`, hatchling build | uv, PEP 621 `pyproject.toml`, **no `[build-system]`** | Partial match |
| Python version | `3.12` (`.python-version`) | `3.14` (`.python-version`, `pyrightconfig.json`, CI) | **Diverges** |
| Linting/formatting | Ruff (`select = ["E","W","F","I","B","UP","S","ASYNC","RUF"]`, `line-length = 88`, `quote-style = "double"`) | **None** — no Ruff config, no lint step in CI | **Diverges** |
| Type checking | Pyright (standard) + mypy w/ Pydantic plugin | Pyright only (**strict** mode, standalone `pyrightconfig.json`) | Partial match |
| Testing | pytest + pytest-asyncio, `asyncio_mode = "auto"` | pytest only — no pytest-asyncio, no `asyncio_mode` | Partial match |
| HTTP client | httpx + httpx-retries | **None** — no HTTP client | Diverges (n/a currently) |
| Configuration | pydantic-settings `BaseSettings` | **None** — bare `os.environ.get()` for `ENABLE_PROFILING` | **Diverges** |
| Database | SQLAlchemy async + Alembic | **None** | Diverges (n/a currently) |
| Git hooks | pre-commit + pre-commit-uv | **None** | **Diverges** |
| CI | `python.yml`: ruff check, ruff format --check, pyright, pytest | `build.yml`: uv sync, pyright, pytest — **no lint step** | Partial match |

### Key Divergences

- **Python 3.14 vs 3.12**: canvas-mcp targets Python 3.14 (pre-release as of March 2026). Banksy plan specifies 3.12. This is the most significant toolchain divergence — it affects base image availability, dependency compatibility, and CI reproducibility.
- **No Ruff**: canvas-mcp has zero linting or formatting configuration. No `[tool.ruff]` in `pyproject.toml`, no CI lint step.
- **No `[build-system]`**: canvas-mcp's `pyproject.toml` omits the `[build-system]` table entirely. Banksy plan specifies hatchling. This matters for `pip install -e .` and wheel builds.
- **Pyright strict vs standard**: canvas-mcp uses `typeCheckingMode = "strict"` in a standalone `pyrightconfig.json`. Banksy plan uses `"standard"` in `pyproject.toml`. Strict is stricter but may cause friction when absorbing code that only passes standard checks.
- **Dev deps via `[dependency-groups]` vs `[project.optional-dependencies]`**: canvas-mcp uses PEP 735 dependency groups; banksy plan uses the traditional `[project.optional-dependencies].dev` pattern. Both work with uv, but the banksy plan's approach is more portable to non-uv tools.

---

## 2. Project Structure Comparison

| Aspect | Banksy Plan | canvas-mcp |
|---|---|---|
| Layout | `src/banksy/` package (src layout) | Flat — single `main.py` at repo root |
| Server instantiation | `src/banksy/server.py` | `main.py` line ~40 |
| Tool organization | `src/banksy/tools/` directory, one file per tool | All tools inline in `main.py` |
| Config module | `src/banksy/config.py` (pydantic-settings `BaseSettings`) | None — raw `os.environ.get()` |
| Auth | `src/banksy/auth/` package (OAuth, sessions, Mural tokens) | None |
| Database | `src/banksy/db/` package (SQLAlchemy models, session, storage) | None |
| Tests | `tests/` with `conftest.py`, per-feature test modules | `tests/test_health.py` (single file) |
| Entry point | `uv run fastmcp dev src/banksy/server.py` | `uv run fastmcp run main.py --transport http` or `python main.py` |

### Key Observations

- canvas-mcp is a **single-file prototype** (`main.py` ~97 lines of code). Banksy's target is a multi-package project. The structural gap is large but expected — canvas-mcp has 2 placeholder tools, banksy will have 50+.
- canvas-mcp uses a flat layout (`pythonpath = ["."]` in pytest config), which conflicts with banksy's `src/` layout. When absorbed, all imports (`from main import ...`) break.
- No separation of concerns: server instantiation, tool definitions, middleware, config, and health endpoint all live in one file.

---

## 3. FastMCP Usage Patterns

| Pattern | Banksy Plan | canvas-mcp |
|---|---|---|
| `FastMCP.from_openapi()` | Core pattern — generates all Mural API tools | Not used |
| Tool definition | `@server.tool()` decorators in separate files | `@mcp.tool()` decorators inline in `main.py` |
| `mount()` sub-servers | OpenAPI sub-server mounted onto main server | Not used |
| Custom routes | Not mentioned in plan | `@mcp.custom_route("/health")` for health endpoint |
| Middleware | Not mentioned in plan | `ProfilingMiddleware` (Pyinstrument-based, opt-in via `ENABLE_PROFILING`) |
| Server variable name | `server` (implied by `server.py`) | `mcp` |
| Tool metadata | Not specified | Uses `tags={}` and `meta={}` kwargs |
| Entry point | `uv run fastmcp dev src/banksy/server.py` | `mcp.run(transport="http")` or `fastmcp run main.py` |

### Features canvas-mcp uses that banksy plan doesn't account for

1. **`mcp.custom_route()`** — canvas-mcp adds a `GET /health` route for load balancer probes. Banksy will likely need this too. The plan should add a health endpoint.
2. **`Middleware` class** — canvas-mcp uses `fastmcp.server.middleware.Middleware` for profiling. The banksy plan doesn't mention middleware, but this is a clean pattern for cross-cutting concerns (logging, auth context, etc.).
3. **Tool `tags` and `meta`** — canvas-mcp annotates tools with tags and metadata. Banksy's PR3 (tool curation) mentions "tag-based tool visibility scaffolding" but doesn't specify the mechanism.

### Patterns that would conflict

- **Server variable naming**: canvas-mcp uses `mcp`, banksy plan implies `server` or `app`. Minor but relevant for merge.
- **Flat imports**: canvas-mcp tools import from `main`, which won't work in banksy's `src/banksy/` package structure.

---

## 4. Dependency Delta

### In canvas-mcp but NOT in banksy plan

| Dependency | Version | Group | Notes |
|---|---|---|---|
| `memray` | `>=1.19.0` | dev | Memory profiler. |
| `pyinstrument` | `>=5.1.0` | dev | CPU profiler used by `ProfilingMiddleware`. |

### In banksy plan but NOT in canvas-mcp

| Dependency | Version | Group | Notes |
|---|---|---|---|
| `httpx` | `>=0.27` | prod | Required for `from_openapi()`. canvas-mcp has no HTTP calls. |
| `httpx-retries` | `>=0.1` | prod | Retry wrapper. |
| `pydantic` | `>=2.0` | prod | Explicit dep. FastMCP bundles it transitively but banksy pins it. |
| `pydantic-settings` | `>=2.0` | prod | Config management. |
| `sqlalchemy[asyncio]` | `>=2.0` | prod | ORM + async. |
| `asyncpg` | `>=0.30` | prod | PostgreSQL driver. |
| `alembic` | `>=1.15` | prod | Migrations. |
| `pytest-asyncio` | `>=1.0` | dev | Async test support. |
| `pytest-cov` | `>=6.0` | dev | Coverage. |
| `inline-snapshot` | `>=0.15` | dev | Snapshot testing. |
| `dirty-equals` | `>=0.8` | dev | Flexible assertions. |
| `ruff` | `>=0.9` | dev | Linter/formatter. |
| `mypy` | `>=1.14` | dev | Type checker (w/ Pydantic plugin). |
| `pydantic[mypy]` | `>=2.0` | dev | mypy plugin support. |

### Shared dependencies with version range differences

| Dependency | Banksy Plan | canvas-mcp | Delta |
|---|---|---|---|
| `fastmcp` | `>=3.1` | `>=3.1.0` | Equivalent (`.0` is implicit) |
| `pyright` | `>=1.1` | `>=1.1.0` | Equivalent |
| `pytest` | `>=8.0` | `>=8.0.0` | Equivalent |

No actual version conflicts in shared deps.

---

## 5. Absorption Feasibility

### Option A: Mount as FastMCP sub-server via `mount()`

**Feasibility: Medium-Low in current state**

- canvas-mcp's tools are trivial placeholders (health check, haiku). There's no real business logic to mount.
- The `mcp.custom_route("/health")` pattern is useful but custom routes don't compose via `mount()` — they'd need to be re-registered on the parent server.
- The `ProfilingMiddleware` is a reusable pattern but middleware also doesn't compose automatically via `mount()`.
- **Verdict**: Not worth mounting as a sub-server. The tools are too simple and the infrastructure patterns should be adopted directly.

### Option B: Copy tools directly into `src/banksy/tools/`

**Feasibility: High (trivial)**

- The two tools (`check_health`, `canvas_haiku`) are self-contained functions with no external dependencies. Copying them into `src/banksy/tools/health.py` and registering them on the banksy server is straightforward.
- The `_health_payload()` helper and `/health` custom route could be adopted into banksy's server setup.

### Shared concerns that need unification

| Concern | canvas-mcp pattern | Banksy plan pattern | Unification needed |
|---|---|---|---|
| Config | `os.environ.get()` | pydantic-settings `BaseSettings` | Yes — canvas-mcp config must migrate to BaseSettings |
| HTTP client | None | httpx.AsyncClient (lifespan-managed) | N/A until canvas-mcp adds Mural API calls |
| Auth | None | OAuth + Mural token mgmt | N/A until canvas-mcp adds auth |
| Server instance | `mcp = FastMCP(...)` in `main.py` | `server` in `src/banksy/server.py` | Naming convention alignment |
| Health endpoint | `@mcp.custom_route("/health")` | Not in plan | Banksy should adopt this pattern |

### Architectural conflicts

- **None that are blocking.** canvas-mcp is small enough that all patterns can be adapted.
- The only structural friction is the flat layout vs src layout, and the `[dependency-groups]` vs `[project.optional-dependencies]` pattern.

### Refactoring needed at absorption time

1. Extract tools from `main.py` into individual files under banksy's `src/banksy/tools/` structure.
2. Replace `os.environ.get()` config with pydantic-settings.
3. Change all imports from flat `main` module to `banksy.*` package paths.
4. Adopt banksy's `[build-system]` (hatchling) and `[project.optional-dependencies]` pattern.
5. Align Python version requirement.

---

## 6. Decisions Needed

Each divergence must be resolved in the banksy migration plan so the resulting project can absorb canvas-mcp cleanly. For each item below, the decision determines whether banksy adapts, canvas-mcp adapts at absorption time, or both meet in the middle.

| # | Topic | Options | Affects | Status |
|---|---|---|---|---|
| 1 | Python version (banksy 3.12 vs canvas-mcp 3.14) | (a) Stay 3.12 — canvas-mcp downgrades at absorption (b) Move banksy to 3.13 — both meet in the middle (c) Move banksy to 3.14 — adopt canvas-mcp's version | PR1, canvas-mcp | Resolved -- 3.14. See [Python 3.14 compatibility research](python-314-compatibility-research.md) |
| 2 | Pyright mode (banksy standard vs canvas-mcp strict) | (a) Stay standard — canvas-mcp relaxes at absorption (b) Adopt strict in banksy | PR1, canvas-mcp | Resolved -- strict. See [Pyright strict dependency typing research](pyright-strict-dependency-typing-research.md) |
| 3 | mypy (banksy has it, canvas-mcp doesn't) | (a) Keep mypy + Pydantic plugin in banksy (b) Drop mypy, go Pyright-only (aligns with canvas-mcp) | PR1 | Resolved -- drop mypy, Pyright strict only |
| 4 | Dev dep format (banksy `[project.optional-dependencies]` vs canvas-mcp `[dependency-groups]`) | (a) Stay `optional-dependencies` — canvas-mcp switches at absorption (b) Switch banksy to PEP 735 `dependency-groups` | PR1, canvas-mcp | Resolved -- PEP 735 `[dependency-groups]` |
| 5 | Health endpoint (`GET /health` custom route) | (a) Add to banksy in PR1 (alongside echo tool) (b) Add to banksy in PR2 (alongside OpenAPI tools) (c) Skip — add only at absorption time | PR1 or PR2 | Resolved -- incorporated into plan |
| 6 | Profiling middleware (canvas-mcp `ProfilingMiddleware`) | (a) Adopt in banksy PR1 as opt-in dev tool (b) Skip — add only at absorption time | PR1 | Resolved -- skip, absorb with canvas-mcp |
| 7 | Profiling dev deps (`pyinstrument`, `memray`) | (a) Add to banksy dev deps (b) Skip — canvas-mcp-only concern | PR1 | Resolved -- skip, absorb with canvas-mcp |
| 8 | Tool tags and meta (`tags={}`, `meta={}` kwargs) | (a) Specify as the mechanism for tag-based visibility in PR3 (b) Leave PR3 unspecified — decide during implementation | PR3 | Open |
Loading