Skip to content

feat(#3301): add tool lifecycle, kagenti admin, and MCP server routes#3553

Merged
gabemontero merged 3 commits into
mainfrom
agent/3301-tool-kagenti-mcp-routes
Jun 23, 2026
Merged

feat(#3301): add tool lifecycle, kagenti admin, and MCP server routes#3553
gabemontero merged 3 commits into
mainfrom
agent/3301-tool-kagenti-mcp-routes

Conversation

@fullsend-ai-coder

Copy link
Copy Markdown
Contributor

Implement Kagenti tool lifecycle routes (tasks 4.1-4.4), Kagenti infrastructure admin routes (task 5.1), and MCP server registration CRUD routes as specified in issue #3301.

Tool lifecycle routes (boost-backend/src/tools/):

  • PUT /tools/:id/promote — draft→pending (boost.tool.promote)
  • PUT /tools/:id/demote — pending→draft or published→pending
    (boost.tool.demote)
  • PUT /tools/:id/publish — pending→published (boost.tool.publish)
  • PUT /tools/:id/unpublish — published→archived
    (boost.tool.unpublish)
  • ToolLifecycleStore with DB-backed persistence (boost_tools table)
  • Tool lifecycle transition validation

Kagenti admin routes (boost-backend/src/kagenti/):

  • GET /kagenti/status — infrastructure status endpoint gated by
    boost.kagenti.admin with boost.admin fallback

MCP server registration routes (boost-backend/src/mcp/):

  • GET /mcp/servers — list registered MCP servers
  • GET /mcp/servers/:id — get single server
  • POST /mcp/servers — register new server (URL, transport, auth)
  • PUT /mcp/servers/:id — update server config
  • DELETE /mcp/servers/:id — remove server
  • POST /mcp/servers/:id/test — connection test stub
  • McpServerStore with DB-backed persistence (boost_mcp_servers)
  • All routes gated by boost.mcp.manage with boost.admin fallback

Types added to boost-common:

  • ToolRecord — governance record for Kagenti tools
  • McpServerRecord, McpTransport, McpAuthType — MCP server types

All routes follow the existing authorizeLifecycleAction middleware pattern with fine-grained permission check → admin fallback → 403.


Closes #3301

Post-script verification

  • Branch is not main/master (agent/3301-tool-kagenti-mcp-routes)
  • Secret scan passed (gitleaks — 34a23c2a26ffe1a0cee4f85bed67c2d21acccec0..HEAD)
  • Pre-commit hooks passed (authoritative run on runner)
  • Tests ran inside sandbox

Implement Kagenti tool lifecycle routes (tasks 4.1-4.4), Kagenti
infrastructure admin routes (task 5.1), and MCP server registration
CRUD routes as specified in issue #3301.

Tool lifecycle routes (boost-backend/src/tools/):
- PUT /tools/:id/promote — draft→pending (boost.tool.promote)
- PUT /tools/:id/demote — pending→draft or published→pending
  (boost.tool.demote)
- PUT /tools/:id/publish — pending→published (boost.tool.publish)
- PUT /tools/:id/unpublish — published→archived
  (boost.tool.unpublish)
- ToolLifecycleStore with DB-backed persistence (boost_tools table)
- Tool lifecycle transition validation

Kagenti admin routes (boost-backend/src/kagenti/):
- GET /kagenti/status — infrastructure status endpoint gated by
  boost.kagenti.admin with boost.admin fallback

MCP server registration routes (boost-backend/src/mcp/):
- GET /mcp/servers — list registered MCP servers
- GET /mcp/servers/:id — get single server
- POST /mcp/servers — register new server (URL, transport, auth)
- PUT /mcp/servers/:id — update server config
- DELETE /mcp/servers/:id — remove server
- POST /mcp/servers/:id/test — connection test stub
- McpServerStore with DB-backed persistence (boost_mcp_servers)
- All routes gated by boost.mcp.manage with boost.admin fallback

Types added to boost-common:
- ToolRecord — governance record for Kagenti tools
- McpServerRecord, McpTransport, McpAuthType — MCP server types

All routes follow the existing authorizeLifecycleAction middleware
pattern with fine-grained permission check → admin fallback → 403.

Closes #3301
@fullsend-ai-coder fullsend-ai-coder Bot requested review from a team, durandom and gabemontero as code owners June 23, 2026 14:46
@rhdh-gh-app

rhdh-gh-app Bot commented Jun 23, 2026

Copy link
Copy Markdown

Missing Changesets

The following package(s) are changed by this PR but do not have a changeset:

  • @red-hat-developer-hub/backstage-plugin-boost-backend
  • @red-hat-developer-hub/backstage-plugin-boost-common

See CONTRIBUTING.md for more information about how to add changesets.

Changed Packages

Package Name Package Path Changeset Bump Current Version
@red-hat-developer-hub/backstage-plugin-boost-backend workspaces/boost/plugins/boost-backend none v0.1.1
@red-hat-developer-hub/backstage-plugin-boost-common workspaces/boost/plugins/boost-common none v0.1.1

@codecov

codecov Bot commented Jun 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 55.12821% with 175 lines in your changes missing coverage. Please review.
✅ Project coverage is 53.74%. Comparing base (34a23c2) to head (5d4138f).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff            @@
##             main    #3553    +/-   ##
========================================
  Coverage   53.74%   53.74%            
========================================
  Files        2268     2274     +6     
  Lines       86468    86858   +390     
  Branches    24266    24343    +77     
========================================
+ Hits        46468    46683   +215     
- Misses      38519    38666   +147     
- Partials     1481     1509    +28     
Flag Coverage Δ *Carryforward flag
adoption-insights 83.70% <ø> (ø) Carriedforward from 34a23c2
ai-integrations 67.95% <ø> (ø) Carriedforward from 34a23c2
app-defaults 69.79% <ø> (ø) Carriedforward from 34a23c2
augment 46.39% <ø> (ø) Carriedforward from 34a23c2
boost 65.18% <55.12%> (-6.53%) ⬇️
bulk-import 72.46% <ø> (ø) Carriedforward from 34a23c2
cost-management 14.10% <ø> (ø) Carriedforward from 34a23c2
dcm 61.79% <ø> (ø) Carriedforward from 34a23c2
extensions 61.53% <ø> (ø) Carriedforward from 34a23c2
global-floating-action-button 71.18% <ø> (ø) Carriedforward from 34a23c2
global-header 59.71% <ø> (ø) Carriedforward from 34a23c2
homepage 49.84% <ø> (ø) Carriedforward from 34a23c2
install-dynamic-plugins 56.23% <ø> (ø) Carriedforward from 34a23c2
konflux 91.49% <ø> (ø) Carriedforward from 34a23c2
lightspeed 68.57% <ø> (ø) Carriedforward from 34a23c2
mcp-integrations 85.46% <ø> (ø) Carriedforward from 34a23c2
orchestrator 38.02% <ø> (ø) Carriedforward from 34a23c2
quickstart 63.76% <ø> (ø) Carriedforward from 34a23c2
sandbox 79.56% <ø> (ø) Carriedforward from 34a23c2
scorecard 83.96% <ø> (ø) Carriedforward from 34a23c2
theme 61.26% <ø> (ø) Carriedforward from 34a23c2
translations 7.25% <ø> (ø) Carriedforward from 34a23c2
x2a 78.68% <ø> (ø) Carriedforward from 34a23c2

*This pull request uses carry forward flags. Click here to find out more.


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 34a23c2...5d4138f. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 23, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:49 PM UTC · Completed 3:04 PM UTC
Commit: 34a23c2 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review

Findings

Medium

  • [fail-open] workspaces/boost/plugins/boost-backend/src/middleware/security.ts:155 — The authorizeLifecycleAction middleware accepts a _resourceLoader parameter but never calls it. The resource loader created in tools/routes.ts (createStoreToolResourceLoader) correctly looks up the tool and returns createdBy/lifecycleStage, but this data is never used by the middleware. Resource-scoped permissions like boost.tool.promote are evaluated as simple ALLOW/DENY without conditional rule evaluation (IS_OWNER checks cannot be enforced). The code explicitly documents this as deferred, but the gap means any user granted boost.tool.promote can promote any tool, not just their own.
    Remediation: Either implement conditional authorization using the resource loader, or explicitly document that ownership-based access control is not enforced in this release and restrict the permissions to admin-only in the default policy.

  • [input-validation] workspaces/boost/plugins/boost-backend/src/mcp/routes.ts:250 — The POST /mcp/servers/:id/test route does not validate the :id parameter with validateServerId() before passing it to store.get(id). All other :id routes in the same file validate the parameter. While Knex parameterizes queries (mitigating SQL injection), this is an inconsistency in defense-in-depth input validation.
    Remediation: Add validateServerId(id) before calling store.get(id) in the /mcp/servers/:id/test handler.

Low

  • [ssrf] workspaces/boost/plugins/boost-backend/src/mcp/routes.ts:153 — The POST /mcp/servers route accepts an arbitrary url field with no format validation beyond non-empty string check. The test endpoint is currently a stub making no outbound connections, but when MCP client integration is implemented, the stored URL will be used for server-side requests. Consider validating URL format (HTTP/HTTPS scheme) and blocking internal network ranges.

  • [input-validation] workspaces/boost/plugins/boost-backend/src/mcp/routes.ts:194 — The PUT /mcp/servers/:id update route does not validate the url field format when provided, allowing it to be changed to any arbitrary string including empty strings.

  • [null-safety] workspaces/boost/plugins/boost-backend/src/tools/ToolLifecycleStore.ts:191register() uses return record! after this.get() — a non-null assertion that could mask a concurrent-delete edge case. Consider adding an explicit guard.

  • [null-safety] workspaces/boost/plugins/boost-backend/src/mcp/McpServerStore.ts:217 — Same return record! pattern in create(). Consider adding an explicit guard.

  • [race-condition] workspaces/boost/plugins/boost-backend/src/mcp/routes.ts:143 — The POST route performs a redundant check-then-act (store.get then store.create). The store layer already handles duplicate key errors, making the route-level check redundant but harmless.

  • [missing-test] workspaces/boost/plugins/boost-backend/src/tools/routes.ts:1 — Tool routes only expose lifecycle transitions (promote/demote/publish/unpublish). No CRUD routes exist for tool registration, listing, or deletion, despite ToolLifecycleStore having those methods. If tool creation is expected through a different mechanism, consider documenting this.

  • [stale-reference] workspaces/boost/plugins/boost-backend/src/middleware/security.ts:234createToolResourceLoader() in security.ts is still a placeholder returning undefined, while createStoreToolResourceLoader(store) in tools/routes.ts is the real implementation. The public API exports the placeholder, which could confuse consumers. See also: [fail-open] finding at this location.

Info

  • [permission-expansion] workspaces/boost/plugins/boost-common/src/permissions.ts:166 — This PR adds 7 new permissions (5 tool, 1 kagenti admin, 1 MCP manage). The boost.tool.approve permission is defined but not used by any route in this PR. All new permissions use the admin-fallback pattern.

  • [sub-agent-failure] N/A — The intent-coherence sub-agent did not return findings: model claude-sonnet-4-5@20250929 unavailable on this deployment. Sonnet-tier dimension; non-blocking.

  • [sub-agent-failure] N/A — The style-conventions sub-agent did not return findings: model unavailable. Sonnet-tier dimension; non-blocking.

  • [sub-agent-failure] N/A — The cross-repo-contracts sub-agent did not return findings: model unavailable. Sonnet-tier dimension; non-blocking.

Previous run

Review

Findings

Low

  • [race condition / null safety] workspaces/boost/plugins/boost-backend/src/tools/ToolLifecycleStore.ts:163 — In register(), after insert, this.get(tool.id) is followed by record! non-null assertion. If the record were deleted between insert and get, this would silently return undefined, violating the return type. Same pattern in McpServerStore.create().
    Remediation: Replace return record! with an explicit null check and throw.

  • [TOCTOU race condition] workspaces/boost/plugins/boost-backend/src/mcp/routes.ts:181 — POST /mcp/servers checks store.get(id) for conflict before store.create(...). The store also catches unique constraint violations and throws ConflictError. The route-level check is redundant.
    Remediation: Remove the route-level store.get(id) check and rely on the store's ConflictError.

  • [SSRF / Input Validation] workspaces/boost/plugins/boost-backend/src/mcp/routes.ts:148 — The url field is validated only as typeof url === 'string'. No URL format, scheme, or destination validation. Currently no outbound connections are made (test endpoint is a stub), but URL validation should be added before MCP client integration.
    Remediation: When MCP client integration is added, validate URL: parse with new URL(), reject non-http(s) schemes, reject private IP ranges.

  • [Input Validation] workspaces/boost/plugins/boost-backend/src/mcp/routes.ts:148name and description fields have no length validation beyond typeof === 'string'. The description field (TEXT type) is unbounded. Endpoint requires admin permission, limiting attack surface.
    Remediation: Add explicit length validation for name (max 255 chars) and description (max 4096 chars).

  • [Authorization pattern inconsistency] kagenti/routes.ts:48 and mcp/routes.ts:56 — Both requireKagentiAdmin and requireMcpManage manually implement the permission-then-admin-fallback pattern. Since these are admin/CRUD routes (not lifecycle actions), authorizeLifecycleAction would be semantically incorrect. Consider extracting a shared authorizeAdminAction helper to reduce duplication.

  • [edge case / API completeness] workspaces/boost/plugins/boost-backend/src/mcp/routes.ts:218 — PUT /mcp/servers/:id cannot clear the description field back to null once set.
    Remediation: Distinguish between description absent (don't change) and description explicitly null (clear it).

  • [test adequacy] workspaces/boost/plugins/boost-backend/src/tools/routes.test.ts:148 — Test description "returns 400 for invalid transition (published -> pending)" is misleading since published -> pending IS valid via demote.
    Remediation: Rename to "returns 400 when tool is not in draft stage (published)".

  • [Authorization / RBAC] workspaces/boost/plugins/boost-backend/src/mcp/routes.ts:134 — GET /mcp/servers/:id does not call validateServerId(id) unlike PUT and DELETE handlers.
    Remediation: Add validateServerId(id) for consistency.

  • [Inline type import] workspaces/boost/plugins/boost-backend/src/tools/routes.ts:90 — Demote handler uses verbose inline import(...) type assertions for LifecycleStage instead of importing at the top.

  • [Naming convention] workspaces/boost/plugins/boost-backend/src/tools/lifecycle.ts:52 — Tools export isValidToolTransition while agents export isValidTransition (no prefix). Naming asymmetry.

Info

  • [unused parameter] workspaces/boost/plugins/boost-backend/src/tools/routes.ts:65createStoreToolResourceLoader creates a resource loader passed to authorizeLifecycleAction, but the middleware ignores its _resourceLoader parameter. Conditional permission rules (IS_OWNER) are not enforced at runtime.

  • [fail-open analysis] Auth policies use allow: 'user-cookie' consistently. Authorization model is fail-closed. No fail-open issues identified.

  • [secrets handling] MCP server model stores auth_type but not actual credentials. Future MCP client integration should use encrypted admin config for credential storage.

Comment thread workspaces/boost/plugins/boost-backend/src/mcp/McpServerStore.ts
Comment thread workspaces/boost/plugins/boost-backend/src/mcp/routes.ts
Comment thread workspaces/boost/plugins/boost-backend/src/mcp/routes.ts
Comment thread workspaces/boost/plugins/boost-backend/src/mcp/routes.ts
Comment thread workspaces/boost/plugins/boost-backend/src/tools/routes.test.ts
Comment thread workspaces/boost/plugins/boost-backend/src/mcp/routes.ts
Comment thread workspaces/boost/plugins/boost-backend/src/tools/routes.ts
Comment thread workspaces/boost/plugins/boost-backend/src/tools/lifecycle.ts
Comment thread workspaces/boost/plugins/boost-backend/src/tools/routes.ts
@fullsend-ai-review fullsend-ai-review Bot added the ready-for-merge All reviewers approved — ready to merge label Jun 23, 2026
gabemontero and others added 2 commits June 23, 2026 11:13
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add missing validateServerId() call to GET /mcp/servers/:id handler
- Replace inline import() type casts with top-level LifecycleStage import in tools/routes.ts demote handler
- Rename misleading test name to accurately describe what it tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: gabemontero <gmontero@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 23, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 3:30 PM UTC · Completed 3:42 PM UTC
Commit: 34a23c2 · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment and removed ready-for-merge All reviewers approved — ready to merge labels Jun 23, 2026
@gabemontero gabemontero merged commit 5f1b5cf into main Jun 23, 2026
24 of 25 checks passed
@gabemontero gabemontero deleted the agent/3301-tool-kagenti-mcp-routes branch June 23, 2026 15:59
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
5.2% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

requires-manual-review Review requires human judgment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

boost-backend — Kagenti tool lifecycle routes, MCP server registration, and infrastructure admin (issue 5 of 15)

1 participant