Summary
The three actions in workspaces/mcp-integrations/plugins/techdocs-mcp-extras (fetch-techdocs, retrieve-techdocs-content, analyze-techdocs-coverage) ignore the credentials provided by the actions registry (action: async ({ input }) =>) and call the catalog and the techdocs backend with auth.getOwnServiceCredentials() (src/service.ts, around lines 209, 289, 346, 463 and 532 on main).
Service principals are always allowed by the permission framework, so with permission.enabled: true any authenticated MCP user can list entities and read TechDocs content that catalog.entity.read would deny them in the UI. The techdocs backend itself gates /static/docs on the caller's catalog read permission (plugins/techdocs-backend/src/service/router.ts in backstage/backstage), so the MCP path bypasses a check the rest of the product enforces. Read-only, but it breaks the assumption that MCP tools inherit the caller's RBAC.
Tasks
Acceptance Criteria
- With
permission.enabled: true, an MCP caller denied catalog.entity.read on an entity gets no entity metadata and no TechDocs content for it from any of the three actions
- Catalog and techdocs backend requests made by the plugin carry the caller's identity (
onBehalfOf: credentials), not the plugin's own service credentials
- No credentials object is written to the log
- Behaviour for callers that are allowed is unchanged
Context
The sibling plugins software-catalog-mcp-extras and scaffolder-mcp-extras in the same workspace already forward the caller's credentials, and the workspace's own .cursor/rules/port-mcp-tool.mdc requires "credentials from action callback".
Version: @red-hat-developer-hub/backstage-plugin-techdocs-mcp-extras 0.2.6 (main); the pattern dates back to the initial import of the plugin (#1491) and is unchanged since.
Summary
The three actions in
workspaces/mcp-integrations/plugins/techdocs-mcp-extras(fetch-techdocs,retrieve-techdocs-content,analyze-techdocs-coverage) ignore thecredentialsprovided by the actions registry (action: async ({ input }) =>) and call the catalog and the techdocs backend withauth.getOwnServiceCredentials()(src/service.ts, around lines 209, 289, 346, 463 and 532 onmain).Service principals are always allowed by the permission framework, so with
permission.enabled: trueany authenticated MCP user can list entities and read TechDocs content thatcatalog.entity.readwould deny them in the UI. The techdocs backend itself gates/static/docson the caller's catalog read permission (plugins/techdocs-backend/src/service/router.tsin backstage/backstage), so the MCP path bypasses a check the rest of the product enforces. Read-only, but it breaks the assumption that MCP tools inherit the caller's RBAC.Tasks
credentialsfrom the action callback (action: async ({ input, credentials }) =>) insrc/actions/*.tsinto theTechDocsServicemethods{ credentials }for catalog calls andonBehalfOf: credentialswhen obtaining the techdocs plugin token insrc/service.ts, replacing theauth.getOwnServiceCredentials()calls (around lines 209, 289, 346, 463, 532)this.logger.info(credentials)insrc/service.ts(around line 290), which logs the credentials object at info levelsrc/service.test.ts/src/plugin.integration.test.tsto assert the caller's credentials are forwardedpatchchangeset for@red-hat-developer-hub/backstage-plugin-techdocs-mcp-extrasAcceptance Criteria
permission.enabled: true, an MCP caller deniedcatalog.entity.readon an entity gets no entity metadata and no TechDocs content for it from any of the three actionsonBehalfOf: credentials), not the plugin's own service credentialsContext
The sibling plugins
software-catalog-mcp-extrasandscaffolder-mcp-extrasin the same workspace already forward the caller's credentials, and the workspace's own.cursor/rules/port-mcp-tool.mdcrequires "credentials from action callback".Version:
@red-hat-developer-hub/backstage-plugin-techdocs-mcp-extras0.2.6 (main); the pattern dates back to the initial import of the plugin (#1491) and is unchanged since.