feat(mcp-actions): Add the ability to configure different plugins for different servers (#33235)

* feat: split MCP actions into per-plugin servers

Add mcpActions.servers config to create multiple MCP server endpoints
scoped by plugin source, with per-server include/exclude filtering.
Add mcpActions.tools for global tool description overrides.

Signed-off-by: benjdlambert <ben@blam.sh>

* feat: namespace tool names, use filter rules for server scoping

- Tool names now use action ID (plugin:name) by default, opt out
  via mcpActions.namespacedToolNames
- Removed pluginSources from server config, use filter.include
  with id glob patterns instead
- Removed tool description overrides (deferred to followup)
- Added server key validation for route safety

Signed-off-by: benjdlambert <ben@blam.sh>

* docs: update README for filter-based server scoping

Signed-off-by: benjdlambert <ben@blam.sh>

* feat: drop SSE routes for split servers

Signed-off-by: benjdlambert <ben@blam.sh>

* fix: handle empty servers config, fix test name

Signed-off-by: benjdlambert <ben@blam.sh>

---------

Signed-off-by: benjdlambert <ben@blam.sh>
This commit is contained in:
Ben Lambert
2026-03-10 11:28:31 +01:00
committed by GitHub
parent 64073a4290
commit c74b69788e
12 changed files with 881 additions and 39 deletions
+86 -3
View File
@@ -71,6 +71,64 @@ export const myPlugin = createBackendPlugin({
});
```
### Namespaced Tool Names
By default, MCP tool names include the plugin ID prefix to avoid collisions across plugins. For example, an action registered as `greet-user` by `my-custom-plugin` is exposed as `my-custom-plugin:greet-user`.
You can disable this if you need the short names for backward compatibility:
```yaml
mcpActions:
namespacedToolNames: false
```
### Multiple MCP Servers
By default, the plugin serves a single MCP server at `/api/mcp-actions/v1` that exposes all available actions. You can split actions into multiple focused servers by configuring `mcpActions.servers`, where each key becomes a separate MCP server endpoint.
```yaml
mcpActions:
servers:
catalog:
name: 'Backstage Catalog'
description: 'Tools for interacting with the software catalog'
filter:
include:
- id: 'catalog:*'
scaffolder:
name: 'Backstage Scaffolder'
description: 'Tools for creating new software from templates'
filter:
include:
- id: 'scaffolder:*'
```
This creates two MCP server endpoints:
- `http://localhost:7007/api/mcp-actions/v1/catalog`
- `http://localhost:7007/api/mcp-actions/v1/scaffolder`
Each server uses include filter rules with glob patterns on action IDs to control which actions are exposed. For example, `id: 'catalog:*'` matches all actions registered by the catalog plugin.
When `mcpActions.servers` is not configured, the plugin behaves exactly as before with a single server at `/api/mcp-actions/v1`.
#### Filter Rules
Include and exclude filter rules support glob patterns on action IDs and attribute matching. Exclude rules take precedence over include rules. When include rules are specified, actions must match at least one include rule to be exposed.
```yaml
mcpActions:
servers:
catalog:
name: 'Backstage Catalog'
filter:
include:
- id: 'catalog:*'
exclude:
- attributes:
destructive: true
```
### Error Handling
When errors are thrown from MCP actions, the backend will handle and surface error message for any error from `@backstage/errors`. Unknown errors will be handled by `@modelcontextprotocol/sdk`'s default error handling, which may result in a generic `500 Server Error` being returned. As a result, we recommend using errors from `@backstage/errors` when applicable.
@@ -159,16 +217,15 @@ The MCP server supports both Server-Sent Events (SSE) and Streamable HTTP protoc
The SSE protocol is deprecated, and should be avoided as it will be removed in a future release.
### Single Server (default)
- `Streamable HTTP`: `http://localhost:7007/api/mcp-actions/v1`
- `SSE`: `http://localhost:7007/api/mcp-actions/v1/sse`
There's a few different ways to configure MCP tools, but here's a snippet of the most common.
```json
{
"mcpServers": {
"backstage-actions": {
// you can also replace this with the public / internal URL of the deployed backend.
"url": "http://localhost:7007/api/mcp-actions/v1",
"headers": {
"Authorization": "Bearer ${MCP_TOKEN}"
@@ -178,6 +235,32 @@ There's a few different ways to configure MCP tools, but here's a snippet of the
}
```
### Multiple Servers
When `mcpActions.servers` is configured, each server key becomes part of the URL. For example, with servers named `catalog` and `scaffolder`:
- `http://localhost:7007/api/mcp-actions/v1/catalog`
- `http://localhost:7007/api/mcp-actions/v1/scaffolder`
```json
{
"mcpServers": {
"backstage-catalog": {
"url": "http://localhost:7007/api/mcp-actions/v1/catalog",
"headers": {
"Authorization": "Bearer ${MCP_TOKEN}"
}
},
"backstage-scaffolder": {
"url": "http://localhost:7007/api/mcp-actions/v1/scaffolder",
"headers": {
"Authorization": "Bearer ${MCP_TOKEN}"
}
}
}
}
```
## Metrics
The MCP Actions Backend emits metrics for the following operations: