rasuvaeff/yii3-mcp-rbac-bridge
GitHub: rasuvaeff/yii3-mcp-rbac-bridge
为 Yii3 MCP 服务器提供基于 RBAC 的用户级授权,在工具调用和列表过滤中实施细粒度权限控制并绑定会话身份。
Stars: 0 | Forks: 0
# rasuvaeff/yii3-mcp-rbac-bridge
[](https://packagist.org/packages/rasuvaeff/yii3-mcp-rbac-bridge)
[](https://packagist.org/packages/rasuvaeff/yii3-mcp-rbac-bridge)
[](https://github.com/rasuvaeff/yii3-mcp-rbac-bridge/actions)
[](https://github.com/rasuvaeff/yii3-mcp-rbac-bridge/actions)
[](https://github.com/rasuvaeff/yii3-mcp-rbac-bridge/blob/master/psalm.xml)
[](https://packagist.org/packages/rasuvaeff/yii3-mcp-rbac-bridge)
[](LICENSE.md)
[俄语版本](README.ru.md)
基于 Yii3 auth stack 的 [rasuvaeff/yii3-mcp](https://github.com/rasuvaeff/yii3-mcp) 服务器用户级授权 ——
这是面向应用的 OAuth 2.1 替代方案:在每次 `tools/call` 上执行 RBAC 权限校验,支持基于权限的
`tools/list` 过滤,以及针对会话劫持的会话身份绑定。
## 环境要求
| 需求 | 版本 |
|-------------|---------|
| PHP | 8.3 – 8.5 |
| `rasuvaeff/yii3-mcp` | `^1.1 \|\| ^2.0` |
| `yiisoft/access` | `^2.0` (将 `AccessCheckerInterface` 绑定到你的 RBAC 管理器) |
| `yiisoft/user` | `^2.0` (当前请求的身份) |
## 安装说明
```
composer require rasuvaeff/yii3-mcp-rbac-bridge
```
## 模型:两个授权层
`SharedSecretMiddleware` (属于 yii3-mcp)会被保留 —— 它是**机器授权**:即该 MCP 客户端是否完全有权与该端点进行通信。此 bridge 增加了**用户授权**:即发起调用的已认证用户实际上能做什么。两层同时运行;添加 RBAC 并不意味着要移除共享密钥。
```
// config/routes.php — secret first (cheap fail-closed), then identity
Route::methods(['POST', 'GET', 'DELETE', 'OPTIONS'], '/mcp')
->middleware(SharedSecretMiddleware::class)
->middleware(Authentication::class) // yiisoft/auth: token -> CurrentUser
->action(McpAction::class),
```
## 用法
### 1. 在工具上声明权限
```
use Rasuvaeff\Yii3McpRbacBridge\RequiredPermission;
final readonly class OrderTools
{
#[McpTool(name: 'order.status')]
#[RequiredPermission('orders.view')]
public function status(string $orderId): string { ... }
#[McpTool(name: 'ping')] // no attribute = unrestricted
public function ping(): string { ... }
}
```
限制是显式且针对单个工具的:没有声明权限的工具保持开放(在共享密钥保护之后)。在没有 `#[McpTool]` 的方法上使用 `#[RequiredPermission]` 会导致构建失败 —— 永远不会被强制执行的权限是一个 bug,而不是默认行为。通过属性将一个工具名称映射到两个不同的权限也会导致构建失败(因为静默的“最后生效”规则会强制执行任意一个权限);根据设计,显式覆盖优先。
### 工具名称:map 键必须是什么
`PermissionMap` 的键是工具名称,并且 bridge 会完全按照 yii3-mcp 注册它们的方式来推导每一个键 —— 这样 list 和 call 就永远不会基于不同的名称:
| 工具声明 | 注册的名称 = map 的键 |
|---|---|
| `#[McpTool(name: 'order.status')]` | `order.status` —— 显式名称优先 |
| `public function status()` 上的 `#[McpTool]` | `status` —— 方法名 |
| `public function __invoke()` 上的 `#[McpTool]` | **类的短名称**(例如 `RefundTool`),**而不是** `__invoke` |
`fromToolClasses()` 会为你计算这些键。只有**显式** map (`$overrides` 参数或 `new PermissionMap([...])`)才需要你正确指定键 —— 对于可调用工具(invokable tool),该键是类的短名称:
```
new PermissionMap(['RefundTool' => 'orders.refund']); // invokable RefundTool::__invoke
```
与任何已注册工具都不匹配的键将不起作用(该工具保持不受限制),因此请保持显式键与上面的工具名称同步。
### 2. 连接 bridge
```
// config/common/di/mcp-rbac.php
use Rasuvaeff\Yii3McpRbacBridge\ {
CurrentUserIdentitySource, IdentitySourceInterface, PermissionMap,
StaticIdentitySource,
};
return [
IdentitySourceInterface::class => CurrentUserIdentitySource::class,
PermissionMap::class => static fn () => PermissionMap::fromToolClasses(
[OrderTools::class], // same list as the `tools` params
// ['order.status' => 'orders.admin'], // optional explicit overrides
),
];
```
根据入口点拆分身份连接;stdio 没有 HTTP `CurrentUser`:
```
// config/web/di.php
return [IdentitySourceInterface::class => CurrentUserIdentitySource::class];
// config/console/di.php
return [
IdentitySourceInterface::class => static fn () => new StaticIdentitySource(
getenv('MCP_USER_ID') ?: null,
),
];
```
```
// config/params.php
'rasuvaeff/yii3-mcp' => [
'tools' => [OrderTools::class],
'interceptors' => [
SessionIdentityInterceptor::class, // outermost: binding before anything trusts the session
RbacToolCallInterceptor::class,
],
'tool_visibility' => RbacToolVisibility::class,
],
```
`AccessCheckerInterface` 来自你的 RBAC 设置(带有 `rbac-php`/`rbac-db` 存储的 `yiisoft/rbac` 管理器 —— 参见 `suggest`)。
### 各组件作用
| 类 | 角色 |
|---|---|
| `RbacToolCallInterceptor` | 拒绝没有映射权限的 `tools/call`(常规 MCP 工具错误,对访客实行 fail-closed) |
| `RbacToolVisibility` | 在 `tools/list` 中隐藏相同的工具 —— list 和 call 永远不会产生分歧(同一个 `PermissionMap`) |
| `SessionIdentityInterceptor` | 将 MCP 会话绑定到其第一个身份;如果泄露的 `Mcp-Session-Id` 搭配了其他用户的 token 将被拒绝 |
| `PermissionMap` | 工具名称 -> 权限:扫描 `#[RequiredPermission]` + 显式覆盖 |
| `CurrentUserIdentitySource` | 来自 yiisoft/user `CurrentUser` 的身份 ID(null = 访客);针对 stdio/自定义设置实现 `IdentitySourceInterface` |
| `StaticIdentitySource` | 用于控制台/stdio 的固定配置/环境身份;`null` 表示访客 |
## 安全说明
- **两个会话绑定,两个层级。** yii3-mcp 2.0 将每个会话绑定到创建它的 **MCP 客户端**(在 `initialize` 时打上不可变所有者标记);此 bridge 的 `SessionIdentityInterceptor` 在第一次 `tools/call` 时将此会话绑定到**应用程序用户**(yii3-mcp 拦截器链在 `initialize` 时不可见)。这两个层级互为补充:在 `initialize` 和第一次调用之间,会话仍然不带任何用户身份 —— 在该时间窗口内没有任何内容被授权,因此没有任何内容被暴露 —— 但在核心 2.0 上,外部的 MCP 客户端无法再利用泄露的 `Mcp-Session-Id` 潜入该窗口;仅剩下同一客户端内的竞争条件。在核心 1.x 上,客户端所有者层并不存在,首次调用的用户绑定是唯一的会话保护措施。
- **会话所有权拒绝将绕过此 bridge。** yii3-mcp 2.0 会在拦截器链运行之前拒绝外部或无所有者的会话(来自 `McpAction` 的 SDK 形态的 404,来自 `InterceptingReferenceHandler` 的 `SessionOwnershipException`) —— 此类拒绝永远不会到达此 bridge 的 RBAC 拦截器或可见性过滤器。因此,被核心阻止的劫持尝试仅在应用程序/Web 服务器日志中可见,而不在此 bridge 观察到的任何内容中。
- 访客是一等公民:访客会以访客身份绑定会话,并且在每个映射了权限的工具上被拒绝(`AccessCheckerInterface` 接收到 `null`)。内部的访客标记无法通过字面量 `"guest"` 用户 ID 伪造。
- 权限撤销将在下一次调用时应用(fail-closed)。此版本不包含撤销时的实时 `notifications/tools/list_changed` —— SDK 仅在带有事件分发器时才暴露此功能,而 yii3-mcp 的工厂尚未携带该功能。
- 对于 stdio(`mcp:serve`),没有 HTTP 请求:将 `IdentitySourceInterface` 绑定到 `StaticIdentitySource`,或者将共享密钥保留为唯一的(机器)身份。
- 访客加上 `RbacToolVisibility` 在合法情况下可能会产生一个空的 `tools/list`。这是一种授权结果,而不是核心的 `mcp:list` 失败;在将零可见工具视为注册表 bug 之前,请验证控制台的身份绑定。
## 示例
请参阅 [examples/](examples/) —— 可离线运行。
| 脚本 | 展示内容 | 需要服务器? |
|--------|-------|:-------------:|
| [`rbac.php`](examples/rbac.php) | 过滤后的列表、允许/拒绝的调用、会话绑定 | 否 |
### 依赖分析器
此叶子包由根应用程序通过 config-plugin 选择,并且在自动加载的源代码目录中可能合法地没有类引用。请保留直接依赖关系:由应用程序(而不是核心包)选择后端或 bridge。将 Composer Dependency Analyser 的异常范围限定在此包:
```
use ShipMonk\ComposerDependencyAnalyser\Config\Configuration;
use ShipMonk\ComposerDependencyAnalyser\Config\ErrorType;
return (new Configuration())->ignoreErrorsOnPackage(
'rasuvaeff/yii3-mcp-rbac-bridge',
[ErrorType::UNUSED_DEPENDENCY],
);
```
`composer-require-checker` 检测的是已使用但未声明的符号,而不是未使用的包,因此这种仅配置的依赖关系不需要进行 require-checker 抑制。
## 开发说明
主机上没有 PHP/Composer —— 通过 `composer:2` 镜像在 Docker 中运行:
```
docker run --rm -v "$PWD":/app -w /app composer:2 composer build
```
或者使用 Make:`make build`、`make cs-fix`、`make psalm`、`make test`。
## 许可证
BSD-3-Clause。请参阅 [LICENSE.md](LICENSE.md)。
标签:ffuf, MCP, OpenVAS, PHP, RBAC, Yii3, 中间件, 权限控制