Skip to content

MPT-20734: add plug router metadata#186

Merged
d3rky merged 1 commit into
mainfrom
codex/MPT-20734-plugrouter
May 19, 2026
Merged

MPT-20734: add plug router metadata#186
d3rky merged 1 commit into
mainfrom
codex/MPT-20734-plugrouter

Conversation

@svazquezco
Copy link
Copy Markdown
Contributor

@svazquezco svazquezco commented May 18, 2026

Closes MPT-20734

  • Add Plug dataclass to model extension UI plug metadata with automatic static asset path normalization (normalizes href and optional icon under /static/)
  • Introduce PlugRouter.register() for provider-based plug registration, replacing the previous decorator-based plug()
  • Add PlugMetadataBuilder to build MetaPlug entries from registered plug providers and enforce unique plug IDs
  • Extend MetaConfig with optional plugs field so plug definitions are included in generated metadata
  • Mount /static on the runtime FastAPI app to expose plug static assets; runtime skips registering plug routes
  • Add validate_plug_static_assets() in CLI and call it from mpt-ext meta validate; validation checks /static/ prefix, prevents path traversal, verifies files exist, and causes meta.generated.yaml to be written on failure
  • Export Plug, PlugRouteCallback, and STATIC_PATH_PREFIX from mpt_extension_sdk.routing
  • Update docs to clarify: plug routes are declarative and emitted into metadata; event/API routes are mounted by the runtime; schedule routes are SDK-only and not mounted/emitted
  • Add tests covering plug dataclass normalization/validation, router registration, CLI validation behavior, metadata generation, and runtime mounting

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 2026

📝 Walkthrough

Walkthrough

Adds declarative UI plug support: a frozen Plug dataclass with /static path normalization and validation, provider-based PlugRouter registration, MetaPlug schema and builder, ExtensionApp/meta integration, runtime /static mounting, CLI validation that writes meta.generated.yaml on failure, docs, and tests.

Plug Metadata and Static Assets

Layer / File(s) Summary
Docs updates
AGENTS.md, docs/architecture.md, docs/usage.md
Document plug behavior, register_plugs usage, static asset normalization, and meta validate changes.
Public exports
mpt_extension_sdk/__init__.py, mpt_extension_sdk/routing/__init__.py
Export Plug, STATIC_PATH_PREFIX, and PlugRouteCallback at package and routing module level.
Plug dataclass and static path normalization
mpt_extension_sdk/routing/plugs.py
Add frozen Plug dataclass with required fields, normalize_static_path(), validation of path parts, and enforced /static/-prefixed normalized asset paths.
Routing types and models
mpt_extension_sdk/routing/types.py, mpt_extension_sdk/routing/models.py
Use TYPE_CHECKING guarded import and forward refs; type PlugRouteDefinition.callback as PlugRouteCallback.
PlugRouter registration API
mpt_extension_sdk/routing/routers/plug.py
Replace decorator-based plug() with register() returning provider decorator that derives name/path from provider __name__ and registers RouteType.PLUG.
ExtensionApp integration
mpt_extension_sdk/extension_app.py
Add _build_meta_plugs() using PlugMetadataBuilder and populate MetaConfig.plugs (or None).
Runtime MetaPlug and builder
mpt_extension_sdk/runtime/models.py, mpt_extension_sdk/runtime/builders.py
Add MetaPlug Pydantic model, MetaConfig.plugs field, and PlugMetadataBuilder to aggregate/validate plug provider outputs and enforce unique IDs.
Runtime app changes
mpt_extension_sdk/runtime/app.py
Mount /static via StaticFiles and update extension route registration to skip plug definitions while registering event/api routes.
CLI validation
mpt_extension_sdk/cli/main.py
Add validate_plug_static_assets() and _validate_static_asset() to pre-validate plug assets; on failure write meta.generated.yaml, print error, and exit 1.
Plug & router tests
tests/routing/routers/test_plug.py
Add tests for normalization, traversal/static-root rejections, and provider-based router registration assertions.
CLI tests
tests/cli/test_main.py
Add plug_meta fixture and tests verifying meta validate succeeds when static files exist and fails (writes meta.generated.yaml) when missing; unit tests for rejection cases.
Runtime & builder tests
tests/runtime/*, tests/test_extension_app.py
Add runtime tests for /static mount and plug ignoring, MetaConfig plug parsing tests, builder idempotency, and ExtensionApp plug inclusion/duplicate/invalid-type tests.
Test cleanup
tests/api/*, others
Consolidate imports, add asyncio yield in async test handler, and minor pagination/response test adjustments.

Estimated code review effort:
🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs:

📝 Walkthrough

Adds declarative UI plug support: a frozen Plug dataclass with /static path normalization and validation, provider-based PlugRouter registration, MetaPlug schema and builder, ExtensionApp/meta integration, runtime /static mounting, CLI validation that writes meta.generated.yaml on failure, docs, and tests.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Jira Issue Key In Title ✅ Passed ✅ Found Jira issue key in the title: MPT-20734
Test Coverage Required ✅ Passed PR modifies 14 code files and includes 9 test files. Test coverage is provided alongside code changes.
Single Commit Required ✅ Passed The PR contains exactly 1 commit: "feature: MPT-20734 add plug router metadata". Single commit requirement is satisfied.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@svazquezco svazquezco marked this pull request as ready for review May 18, 2026 13:42
@svazquezco svazquezco requested a review from a team as a code owner May 18, 2026 13:42
@svazquezco svazquezco requested review from d3rky and robcsegal and removed request for a team May 18, 2026 13:42
Base automatically changed from codex/MPT-20733-api-router-runtime to main May 18, 2026 13:44
@svazquezco svazquezco force-pushed the codex/MPT-20734-plugrouter branch from 0fe7926 to 432a9c6 Compare May 18, 2026 13:45
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
docs/usage.md (2)

143-172: ⚡ Quick win

Consider adding router inclusion to the example.

The example shows how to define plug_router and register plug providers, but doesn't demonstrate how to include the router in the extension app. Adding this would make the example more complete for first-time users.

📝 Suggested addition

After the example code block, consider adding:

# In your app.py
ext_app = ExtensionApp(prefix="/api/v2")
ext_app.include_router(plug_router)

Or incorporate it into the main example to show the full flow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/usage.md` around lines 143 - 172, The example defines PlugRouter and
register_plugs but omits showing how to attach that router to an extension app;
update the docs to include usage of ExtensionApp and include_router so readers
see the full flow: after creating plug_router and register_plugs, instantiate
ExtensionApp (e.g., ExtensionApp(prefix="...")) and call
ext_app.include_router(plug_router) to register the plug providers with the app;
reference the PlugRouter, plug_router, register_plugs, ExtensionApp, and
include_router symbols in the added text.

170-172: 💤 Low value

Consider clarifying the input format for static paths.

The explanation states that href and icon are "paths relative to the local static/ folder" and that "The SDK normalizes them under /static/". While technically correct, an explicit example showing the transformation might help users understand the expected input format.

📝 Suggested clarification

Consider adding an example transformation:

`href` and `icon` are paths relative to the local `static/` folder. For example, 
if you have `static/main-menu.js` in your extension package, specify `href="main-menu.js"` 
in the Plug definition. The SDK normalizes them under `/static/` in generated metadata 
(e.g., `/static/main-menu.js`), and `mpt-ext meta validate` checks that every referenced 
file exists locally.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/usage.md` around lines 170 - 172, Add a short explicit example showing
how to specify static paths for `href` and `icon` in the docs: explain that
these fields should be given as filenames or paths relative to the local static/
folder (e.g., `main-menu.js` or `images/icon.png`), and state that the SDK will
normalize them to `/static/<filename>` in generated metadata; reference the
`href` and `icon` fields and the `mpt-ext meta validate` command so readers know
the validator checks the referenced files exist locally and see the before/after
transformation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mpt_extension_sdk/routing/routers/plug.py`:
- Around line 17-23: The code in decorator (function decorator) assumes
plug_provider has __name__, which will fail for callables like
functools.partial; update the name extraction when building PlugRouteDefinition
to guard against missing __name__ by using a safe fallback: try
getattr(plug_provider, "__name__", None), then if None check for a wrapped
function (e.g., getattr(plug_provider, "func", None) and its __name__), and
finally fall back to plug_provider.__class__.__name__ or str(plug_provider) to
produce a stable route name; apply this change where PlugRouteDefinition is
constructed in decorator so name= uses the guarded value.

In `@mpt_extension_sdk/runtime/builders.py`:
- Around line 12-21: The instance field _plug_ids is never cleared, so repeated
calls to PlugMetadataBuilder.build() will treat previously seen IDs as
duplicates; at the start of the build method reset/clear self._plug_ids (e.g.,
self._plug_ids.clear() or reassign a new set) before iterating, then proceed to
call self._iter_plugs(), self._validate_plug(...) and
self._create_meta_plug(...). Apply the same reset to the other build
implementation mentioned (the similar build method around lines 50-54) to ensure
each build run starts with a fresh seen-ID set.

---

Nitpick comments:
In `@docs/usage.md`:
- Around line 143-172: The example defines PlugRouter and register_plugs but
omits showing how to attach that router to an extension app; update the docs to
include usage of ExtensionApp and include_router so readers see the full flow:
after creating plug_router and register_plugs, instantiate ExtensionApp (e.g.,
ExtensionApp(prefix="...")) and call ext_app.include_router(plug_router) to
register the plug providers with the app; reference the PlugRouter, plug_router,
register_plugs, ExtensionApp, and include_router symbols in the added text.
- Around line 170-172: Add a short explicit example showing how to specify
static paths for `href` and `icon` in the docs: explain that these fields should
be given as filenames or paths relative to the local static/ folder (e.g.,
`main-menu.js` or `images/icon.png`), and state that the SDK will normalize them
to `/static/<filename>` in generated metadata; reference the `href` and `icon`
fields and the `mpt-ext meta validate` command so readers know the validator
checks the referenced files exist locally and see the before/after
transformation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 6f7338aa-bbe8-4d0a-ae66-e79299231b6b

📥 Commits

Reviewing files that changed from the base of the PR and between 232c898 and 432a9c6.

📒 Files selected for processing (21)
  • docs/architecture.md
  • docs/usage.md
  • mpt_extension_sdk/__init__.py
  • mpt_extension_sdk/cli/main.py
  • mpt_extension_sdk/extension_app.py
  • mpt_extension_sdk/routing/__init__.py
  • mpt_extension_sdk/routing/models.py
  • mpt_extension_sdk/routing/plugs.py
  • mpt_extension_sdk/routing/routers/plug.py
  • mpt_extension_sdk/routing/types.py
  • mpt_extension_sdk/runtime/app.py
  • mpt_extension_sdk/runtime/builders.py
  • mpt_extension_sdk/runtime/models.py
  • tests/api/builders/test_api.py
  • tests/api/test_pagination.py
  • tests/api/test_responses.py
  • tests/cli/test_main.py
  • tests/routing/routers/test_plug.py
  • tests/runtime/test_app.py
  • tests/runtime/test_models.py
  • tests/test_extension_app.py

Comment thread mpt_extension_sdk/routing/routers/plug.py
Comment thread mpt_extension_sdk/runtime/builders.py
@svazquezco svazquezco force-pushed the codex/MPT-20734-plugrouter branch 2 times, most recently from 0bd70ee to 49f537c Compare May 18, 2026 14:49
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
mpt_extension_sdk/runtime/app.py (1)

72-76: ⚡ Quick win

Use the shared static prefix constant for mount path.

This keeps runtime mount wiring aligned with plug path normalization/validation if the prefix ever changes.

♻️ Proposed refactor
@@
 from fastapi import FastAPI, Request, Response
 from fastapi.staticfiles import StaticFiles
@@
 from mpt_extension_sdk.observability.bootstrap import ObservabilityBootstrap
 from mpt_extension_sdk.observability.config import ObservabilityConfig
+from mpt_extension_sdk.routing.plugs import STATIC_PATH_PREFIX
@@
     app.mount(
-        "/static",
+        STATIC_PATH_PREFIX.removesuffix("/"),
         StaticFiles(directory=Path.cwd() / "static", check_dir=False),
         name="static",
     )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mpt_extension_sdk/runtime/app.py` around lines 72 - 76, The mount path is
hardcoded as "/static" in the app.mount call; replace that literal with the
shared static prefix constant used across the project (e.g., STATIC_PREFIX or
the module constant that normalizes plug paths) so runtime routing uses the same
canonical prefix. Update the app.mount invocation that references
StaticFiles(directory=Path.cwd() / "static", check_dir=False) to use the shared
constant instead of the literal string and ensure any import or reference points
to the module-exported constant the rest of the codebase uses for static/plug
path normalization.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mpt_extension_sdk/routing/plugs.py`:
- Around line 33-40: The branch that computes relative_path mishandles the bare
mount path (e.g. "/static" or "/static/") and causes incorrect normalization;
update the logic in the block that uses STATIC_PATH_PREFIX and cleaned_path so
that if cleaned_path exactly equals the mount prefix (with or without a trailing
slash) you treat it as invalid (raise ValueError) or normalize to an empty
relative path consistently, otherwise compute relative_path by removing the
prefix and stripping any leading slash (use
cleaned_path.removeprefix(STATIC_PATH_PREFIX).lstrip("/")). Ensure you still
validate path_parts via PurePosixPath(relative_path).parts and
cls._has_invalid_path_parts.

---

Nitpick comments:
In `@mpt_extension_sdk/runtime/app.py`:
- Around line 72-76: The mount path is hardcoded as "/static" in the app.mount
call; replace that literal with the shared static prefix constant used across
the project (e.g., STATIC_PREFIX or the module constant that normalizes plug
paths) so runtime routing uses the same canonical prefix. Update the app.mount
invocation that references StaticFiles(directory=Path.cwd() / "static",
check_dir=False) to use the shared constant instead of the literal string and
ensure any import or reference points to the module-exported constant the rest
of the codebase uses for static/plug path normalization.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 46ef2941-92c0-4393-a6ec-77192cfea782

📥 Commits

Reviewing files that changed from the base of the PR and between 0bd70ee and 49f537c.

📒 Files selected for processing (23)
  • AGENTS.md
  • docs/architecture.md
  • docs/usage.md
  • mpt_extension_sdk/__init__.py
  • mpt_extension_sdk/cli/main.py
  • mpt_extension_sdk/extension_app.py
  • mpt_extension_sdk/routing/__init__.py
  • mpt_extension_sdk/routing/models.py
  • mpt_extension_sdk/routing/plugs.py
  • mpt_extension_sdk/routing/routers/plug.py
  • mpt_extension_sdk/routing/types.py
  • mpt_extension_sdk/runtime/app.py
  • mpt_extension_sdk/runtime/builders.py
  • mpt_extension_sdk/runtime/models.py
  • tests/api/builders/test_api.py
  • tests/api/test_pagination.py
  • tests/api/test_responses.py
  • tests/cli/test_main.py
  • tests/routing/routers/test_plug.py
  • tests/runtime/test_app.py
  • tests/runtime/test_builders.py
  • tests/runtime/test_models.py
  • tests/test_extension_app.py
✅ Files skipped from review due to trivial changes (5)
  • AGENTS.md
  • mpt_extension_sdk/routing/models.py
  • tests/api/test_responses.py
  • docs/usage.md
  • tests/api/test_pagination.py
🚧 Files skipped from review as they are similar to previous changes (12)
  • tests/runtime/test_builders.py
  • mpt_extension_sdk/routing/types.py
  • mpt_extension_sdk/runtime/models.py
  • tests/api/builders/test_api.py
  • tests/routing/routers/test_plug.py
  • mpt_extension_sdk/runtime/builders.py
  • tests/test_extension_app.py
  • tests/cli/test_main.py
  • mpt_extension_sdk/routing/routers/plug.py
  • mpt_extension_sdk/extension_app.py
  • tests/runtime/test_models.py
  • tests/runtime/test_app.py

Comment thread mpt_extension_sdk/routing/plugs.py
@svazquezco svazquezco force-pushed the codex/MPT-20734-plugrouter branch from 49f537c to 1d19561 Compare May 18, 2026 15:43
@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
mpt_extension_sdk/cli/main.py (1)

73-79: 💤 Low value

Consider direct attribute access instead of getattr.

Since MetaConfig.plugs is a typed attribute (per the builder layer), you could simplify the iteration to use direct access:

-    for plug in getattr(meta_config, "plugs", None) or []:
+    for plug in meta_config.plugs or []:

This preserves the same None-coalescing behavior while being more explicit about the expected type.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mpt_extension_sdk/cli/main.py` around lines 73 - 79, The loop uses
getattr(meta_config, "plugs", None) but MetaConfig defines plugs directly;
change the iteration in validate_plug_static_assets to use direct attribute
access (meta_config.plugs or []) and keep the None-coalescing behavior, calling
_validate_static_asset(plug.href, asset_root) and
_validate_static_asset(plug.icon, asset_root) as before so the function still
validates plug.href and optional plug.icon.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@mpt_extension_sdk/cli/main.py`:
- Around line 73-79: The loop uses getattr(meta_config, "plugs", None) but
MetaConfig defines plugs directly; change the iteration in
validate_plug_static_assets to use direct attribute access (meta_config.plugs or
[]) and keep the None-coalescing behavior, calling
_validate_static_asset(plug.href, asset_root) and
_validate_static_asset(plug.icon, asset_root) as before so the function still
validates plug.href and optional plug.icon.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: b6e1b7eb-c40c-4e33-a1cc-df7d71209632

📥 Commits

Reviewing files that changed from the base of the PR and between 49f537c and 1d19561.

📒 Files selected for processing (23)
  • AGENTS.md
  • docs/architecture.md
  • docs/usage.md
  • mpt_extension_sdk/__init__.py
  • mpt_extension_sdk/cli/main.py
  • mpt_extension_sdk/extension_app.py
  • mpt_extension_sdk/routing/__init__.py
  • mpt_extension_sdk/routing/models.py
  • mpt_extension_sdk/routing/plugs.py
  • mpt_extension_sdk/routing/routers/plug.py
  • mpt_extension_sdk/routing/types.py
  • mpt_extension_sdk/runtime/app.py
  • mpt_extension_sdk/runtime/builders.py
  • mpt_extension_sdk/runtime/models.py
  • tests/api/builders/test_api.py
  • tests/api/test_pagination.py
  • tests/api/test_responses.py
  • tests/cli/test_main.py
  • tests/routing/routers/test_plug.py
  • tests/runtime/test_app.py
  • tests/runtime/test_builders.py
  • tests/runtime/test_models.py
  • tests/test_extension_app.py
✅ Files skipped from review due to trivial changes (2)
  • tests/api/test_responses.py
  • docs/usage.md
🚧 Files skipped from review as they are similar to previous changes (16)
  • AGENTS.md
  • tests/runtime/test_builders.py
  • tests/runtime/test_models.py
  • mpt_extension_sdk/runtime/models.py
  • tests/api/builders/test_api.py
  • mpt_extension_sdk/routing/models.py
  • tests/routing/routers/test_plug.py
  • mpt_extension_sdk/extension_app.py
  • mpt_extension_sdk/routing/routers/plug.py
  • tests/cli/test_main.py
  • mpt_extension_sdk/routing/plugs.py
  • mpt_extension_sdk/runtime/builders.py
  • mpt_extension_sdk/routing/types.py
  • tests/api/test_pagination.py
  • tests/runtime/test_app.py
  • tests/test_extension_app.py

@d3rky d3rky merged commit c80cf7f into main May 19, 2026
4 checks passed
@d3rky d3rky deleted the codex/MPT-20734-plugrouter branch May 19, 2026 14:22
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.

2 participants