Skip to content

KYTE-#325 PR B: MCP schema-management tools + schema scope#109

Merged
kennethphough merged 1 commit into
masterfrom
feature/325-mcp-schema-tools
Jun 30, 2026
Merged

KYTE-#325 PR B: MCP schema-management tools + schema scope#109
kennethphough merged 1 commit into
masterfrom
feature/325-mcp-schema-tools

Conversation

@kennethphough

Copy link
Copy Markdown
Member

KYTE-#325 — PR B (MCP schema-management tools)

Builds on PR A (#108). Lets a schema-scoped MCP token create and migrate data models from Claude, where each change applies a real DDL migration via DataModelController/ModelAttributeController in internal mode (mirrors the #322 SiteTools pattern).

New scope

  • schema added to KyteMCPTokenController::VALID_SCOPES, held separately from provision (model-migration rights independent of site spin-up/tear-down). Not hierarchical.

New tools (ModelTools, all #[RequiresScope('schema')])

tool DDL notes
create_model CREATE TABLE base table w/ PK + audit columns
add_attribute ADD COLUMN decimal needs precision+scale; varchar needs size; FK via foreign_key_model
update_attribute CHANGE COLUMN name required (full column def rewritten)
rename_model RENAME TABLE loads app model constants first (see below)
remove_attribute DROP COLUMN destructive — requires confirm_destructive=true
delete_model DROP TABLE destructive — requires confirm_destructive=true

Every caller-supplied model/attribute id is re-scoped to the token's account; foreign ids are rejected. read_model/list_models already existed.

Why rename_model calls loadAppModels

DataModelController::update guards on defined($o->name) — the model must be registered as a runtime constant. The HTTP pipeline does this via Api::loadAppModels at routing, but the MCP path bypasses routing, so the tool registers the app's models first (same as AppModelWrapperController).

Model-layer hardening (surfaced by partial MCP payloads)

  • DataModelController::prepareModelDef reads optional flags/defaults defensively — no undefined-property warning / null-to-strlen deprecation when a caller omits protected/password/defaults/unsigned.
  • Api::loadAppModels skips rows with a missing/corrupt model_definition (instead of define()-ing a null name) and no longer re-defines an already-defined constant.

Tests

McpModelToolsTest extended with write coverage: create / add / update / rename, decimal precision/scale reaching the physical column, destructive-op confirm gating, FK-dependency block, and cross-account isolation — driving real DDL against the test DB via a dedicated password-bearing app user (Api::dbappconnect rejects empty passwords).

Verification

  • PHPStan level 1 (php:8.2-cli, -d memory_limit=512M) — green.
  • Unit suite (256 tests) — green against MariaDB 10.5.

Follow-ups (not in this PR)

🤖 Generated with Claude Code

…odels) — PR B

KYTE-#325 PR B. Lets a `schema`-scoped MCP token create and migrate data
models from Claude, where each change applies a real DDL migration via the
DataModel/ModelAttribute controllers in internal mode (mirrors the #322
SiteTools pattern).

- New `schema` scope in KyteMCPTokenController::VALID_SCOPES, separate from
  `provision` (model-migration rights independent of site spin-up/tear-down).
- ModelTools write tools (all #[RequiresScope('schema')]):
    create_model, add_attribute, update_attribute, rename_model;
    remove_attribute (DROP COLUMN) and delete_model (DROP TABLE) behind a
    required confirm_destructive flag.
  Every caller-supplied model/attribute id is re-scoped to the token's
  account before use; foreign ids are rejected.
- rename_model registers the app's model constants via Api::loadAppModels
  first — DataModelController::update guards on defined($o->name), which the
  HTTP pipeline satisfies at routing but the MCP path (routing-bypassed) does
  not.
- Model-layer hardening surfaced by the partial MCP payloads:
    * prepareModelDef reads optional flags/defaults defensively (no
      undefined-property warning / null-to-strlen deprecation when a caller
      omits protected/password/defaults/unsigned).
    * loadAppModels skips rows with a missing/corrupt model_definition
      instead of define()-ing a null model name, and no longer re-defines an
      already-defined constant.

Tests: McpModelToolsTest extended with write coverage (create/add/update/
rename, decimal precision/scale reaching the physical column, destructive-op
confirm gating, FK-dependency block, and cross-account isolation), driving
real DDL against the test DB via a dedicated password-bearing app user.

PHPStan level 1 (php:8.2-cli, 512M) green; unit suite 256/256 green vs
MariaDB 10.5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kennethphough
kennethphough merged commit 29c46b8 into master Jun 30, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant