# RBAC Implementation Analysis & Tool Development Guide
## š Executive Summary
I've analyzed your complete RBAC system, Laravel permissions, and MCP tool architecture. Here's what you have and how to proceed with building new tools.
---
## šļø Current Architecture Overview
### 1. **Authentication Flow** (Bearer Token)
```
Client Request ā Bearer Token ā Laravel API (/api/get-login-user-data)
ā
User Cache (15 min TTL)
ā
UserContext Created
ā
Tools Receive Context
```
**Key Files:**
- `src/web_chat/main.py` - Extracts Bearer token, validates, creates UserContext
- `src/web_chat/user_cache.py` - Caches user data (90%+ hit rate)
- `src/core/security.py` - UserContext & RBAC logic
### 2. **Role Hierarchy** (3-Tier System)
```
Organization (Top Level)
āāā Platform (Middle Level)
ā āāā Dealership (Bottom Level)
āāā Dealership (Direct, no platform)
```
**15 Roles Across 4 Levels:**
| Level | Roles | Access Scope |
|-------|-------|--------------|
| **Global** | GLOBAL_ADMIN (1) | Everything |
| **Organization** | ORG_ADMIN (2), ORG_MANAGER (7), ORG_USER (14), ORG_VIEWER (6), ORG_USERS (5) | All platforms & dealerships in org |
| **Platform** | PLATFORM_ADMIN (4), PLATFORM_MANAGER (3), PLATFORM_USER (8), PLATFORM_VIEWER (9), TEST_PLATFORM_ADMIN (15) | Their platform + dealerships under it |
| **Dealership** | DEALERSHIP_ADMIN (10), DEALERSHIP_MANAGER (11), DEALERSHIP_USERS (12), DEALERSHIP_VIEWER (13) | Only their dealership |
### 3. **Tool Access Control** (RBAC)
Tools are organized into **5 categories**:
```python
# src/core/security.py
PUBLIC_TOOLS = {"get_system_info", "health_check"}
AUTHENTICATED_TOOLS = {"echo", "search_knowledge_base", "get_user_profile"}
DEALERSHIP_TOOLS = {"get_dealership_contracts", "get_dealership_vendors", "upload_contract"}
PLATFORM_TOOLS = {"get_platform_contracts", "get_platform_vendors", "get_platform_dealerships"}
ORGANIZATION_TOOLS = {"get_organization_contracts", "get_organization_vendors", "get_all_platforms"}
ADMIN_TOOLS = {"manage_organizations", "system_configuration", "audit_logs"}
```
**Access Rules:**
- **Global Admin** ā All tools
- **Org-level users** ā Org + Platform + Dealership tools
- **Platform-level users** ā Platform + Dealership tools
- **Dealership-level users** ā Dealership tools only
### 4. **User Context Injection**
When tools are called, the orchestrator **automatically injects** user context:
```python
# src/orchestrator/processor.py (lines 129-147)
args = dict(tool_call.arguments)
args["user_id"] = user_context.user_id
args["organization_id"] = user_context.organization_id
args["role"] = user_context.role.value
args["bearer_token"] = user_context.bearer_token # For Laravel API calls
args["platform_id"] = user_context.platform_id
args["dealership_id"] = user_context.dealership_id
args["email"] = user_context.email
args["name"] = user_context.name
args["permissions"] = user_context.permissions # Laravel permissions array
```
**This means:**
- Tools receive ALL user context automatically
- Tools can call Laravel APIs using `bearer_token`
- Tools can check Laravel permissions from `permissions` array
---
## š Laravel Permissions Mapping
From your database export, here are the **actual permissions** per role:
### Permission Categories
| Category | Permissions |
|----------|-------------|
| **Contracts** | `contract.index`, `contract.show`, `contract.create`, `contract.update`, `contract.delete`, `contract.clauses`, `contract.data`, `contract.dates`, `contract.files`, `contract.owner`, `contract.summary`, `contracts.renewal` |
| **Vendors** | (Implied by submodule permissions) |
| **Invoices** | `invoice.index`, `invoice.show`, `invoice.create`, `invoice.update`, `invoice.delete`, `invoice.paid`, `invoice.approval`, `invoice.self_update` |
| **Approvals** | `approval.index`, `approval.show`, `approval.create`, `approval.update`, `approval.delete` |
| **Users** | `users.index`, `users.show`, `users.create`, `users.update`, `users.delete`, `users.set_filters`, `users.org_filters`, `users.global_filters` |
| **Organizations** | `organization.index`, `organization.show`, `organization.create`, `organization.update`, `organization.delete` |
| **Submodules** | `submodule.index`, `submodule.show`, `submodule.create`, `submodule.update`, `submodule.delete` (Platforms/Dealerships) |
| **Data** | `data.index`, `data.show`, `data.create`, `data.update`, `data.delete` |
### Key Differences by Role
**Viewers** (ORG_VIEWER, PLATFORM_VIEWER, DEALERSHIP_VIEWER):
- ā No `create`, `update`, `delete` permissions
- ā
Only `index` and `show` (read-only)
**Users** (ORG_USER, PLATFORM_USER, DEALERSHIP_USERS):
- ā
Can create/update most resources
- ā Limited delete permissions
- ā No user management
**Managers** (ORG_MANAGER, PLATFORM_MANAGER, DEALERSHIP_MANAGER):
- ā
Full CRUD on contracts, invoices, approvals
- ā
Can manage users (limited)
- ā Cannot delete organizations
**Admins** (ORG_ADMIN, PLATFORM_ADMIN, DEALERSHIP_ADMIN):
- ā
Full CRUD on everything at their level
- ā
Can manage users
- ā
Can manage organization/platform/dealership settings
**Global Admin**:
- ā
Everything + `users.global_filters`
---
## š ļø How to Build New Tools
### Step 1: Understand the Tool Pattern
Every tool follows this structure:
```python
# src/mcp_server/tools/your_tool/tool.py
from src.core import UserContext, RBAC, wrap_response
from src.observability import get_logger
logger = get_logger(__name__)
async def your_tool_logic(
user_context: UserContext,
your_param: str,
trace_id: str = "unknown"
) -> dict:
"""
Your tool implementation.
Args:
user_context: Automatically injected by orchestrator
your_param: Tool-specific parameter
trace_id: Request trace ID
Returns:
Envelope response with data
"""
logger.info("your_tool_called", user_id=user_context.user_id)
try:
# 1. Check permissions (optional, RBAC already filters tools)
if not user_context.can_write:
return wrap_response(
tool_name="your_tool",
trace_id=trace_id,
data={},
missing_reason="Viewer roles cannot perform this action"
)
# 2. Call Laravel API using bearer_token
import httpx
async with httpx.AsyncClient() as client:
response = await client.get(
f"{settings.laravel_api_url}/api/your-endpoint",
headers={"Authorization": f"Bearer {user_context.bearer_token}"}
)
data = response.json()
# 3. Filter data by hierarchy (if needed)
filtered_data = RBAC.filter_data_by_hierarchy(
data=data["items"],
user_context=user_context,
org_field="organization_id",
platform_field="platform_id",
dealership_field="dealership_id"
)
# 4. Return in envelope format
return wrap_response(
tool_name="your_tool",
trace_id=trace_id,
data={"items": filtered_data, "count": len(filtered_data)}
)
except Exception as e:
logger.error("your_tool_failed", error=str(e))
return wrap_response(
tool_name="your_tool",
trace_id=trace_id,
data={},
missing_reason=f"Failed: {str(e)}"
)
# Tool registration
def register(mcp):
"""Register tool with MCP server."""
@mcp.tool(
name="your_tool",
description="What your tool does. Be specific for LLM."
)
async def your_tool_wrapper(
your_param: str,
user_id: int = 0,
organization_id: int = 0,
role: int = 1,
bearer_token: str = "",
platform_id: int = None,
dealership_id: int = None,
email: str = "",
name: str = "",
permissions: list = None,
trace_id: str = "unknown",
) -> dict:
"""
MCP tool wrapper - receives injected context.
"""
from src.core import Role
user_context = UserContext(
user_id=user_id,
role=Role(role),
bearer_token=bearer_token,
organization_id=organization_id,
platform_id=platform_id,
dealership_id=dealership_id,
email=email,
name=name,
permissions=permissions or [],
)
return await your_tool_logic(user_context, your_param, trace_id)
```
### Step 2: Add Tool to RBAC
Update `src/core/security.py`:
```python
class RBAC:
# Add your tool to appropriate category
DEALERSHIP_TOOLS: Set[str] = {
"get_dealership_contracts",
"get_dealership_vendors",
"upload_contract",
"your_new_tool", # ā Add here
}
```
### Step 3: Create Tool Directory
```bash
mkdir -p src/mcp_server/tools/your_tool
touch src/mcp_server/tools/your_tool/__init__.py
touch src/mcp_server/tools/your_tool/tool.py
```
### Step 4: Auto-Discovery
The tool is **automatically registered** - no manual registration needed!
The `src/mcp_server/tools/__init__.py` auto-discovers any module with a `register()` function.
---
## šÆ Permission Checking Strategies
### Strategy 1: RBAC Tool-Level (Recommended)
Let RBAC handle tool access, then check write permissions:
```python
# Tool is already filtered by RBAC
# Just check if user can write
if not user_context.can_write:
return wrap_response(..., missing_reason="Read-only access")
```
### Strategy 2: Laravel Permission Check
Check specific Laravel permissions:
```python
def has_permission(user_context: UserContext, permission: str) -> bool:
"""Check if user has specific Laravel permission."""
return permission in (user_context.permissions or [])
# Usage
if not has_permission(user_context, "contract.create"):
return wrap_response(..., missing_reason="Missing contract.create permission")
```
### Strategy 3: Hybrid (Most Flexible)
Combine RBAC + Laravel permissions:
```python
# RBAC ensures tool is accessible
# Laravel permissions for fine-grained control
if action == "create":
if not has_permission(user_context, "contract.create"):
return wrap_response(..., missing_reason="Cannot create contracts")
elif action == "delete":
if not has_permission(user_context, "contract.delete"):
return wrap_response(..., missing_reason="Cannot delete contracts")
```
---
## š MCP RBAC vs Laravel Middleware
### Laravel Approach
```php
Route::group(['middleware' => ['auth:sanctum', 'permission', 'check.impersonation.expiration']], function () {
Route::post('create-vendor', [VendorV2Controller::class, 'createVendor'])->name('submodule.create');
});
```
**How it works:**
1. `auth:sanctum` - Validates Bearer token
2. `permission` - Checks Laravel permissions
3. Route is blocked if user lacks permission
### MCP Approach (Current Implementation)
```python
# 1. Bearer token validated at API level (src/web_chat/main.py)
# 2. User context created with permissions
# 3. RBAC filters tools at orchestrator level (src/orchestrator/processor.py)
# 4. Tools receive full context and can check permissions
```
**Key Differences:**
| Aspect | Laravel | MCP (Current) |
|--------|---------|---------------|
| **Auth** | Middleware per route | Bearer token at API entry |
| **Permission Check** | Middleware blocks route | RBAC filters tools + tool-level checks |
| **Granularity** | Per endpoint | Per tool + per action |
| **Flexibility** | Fixed per route | Dynamic based on user context |
### Should You Implement Laravel-Style Middleware?
**NO - Current approach is BETTER because:**
1. **Tool-Level Filtering**: RBAC already filters tools before LLM sees them
2. **Context-Aware**: Tools can make intelligent decisions based on full user context
3. **Flexible**: Same tool can behave differently based on role/permissions
4. **Efficient**: Single auth check at API entry, cached for 15 minutes
5. **LLM-Friendly**: LLM only sees tools user can actually use
**Laravel middleware is for REST APIs with fixed endpoints. MCP is for dynamic tool calling.**
---
## š Recommended Approach for New Tools
### ā
DO THIS:
1. **Let RBAC handle tool visibility**
- Add tool to appropriate category in `RBAC` class
- Orchestrator automatically filters tools
2. **Use UserContext properties for common checks**
```python
if not user_context.can_write: # Viewer check
if not user_context.can_manage: # Manager/Admin check
if user_context.is_organization_level: # Level check
```
3. **Check Laravel permissions for specific actions**
```python
if "contract.delete" not in user_context.permissions:
return error_response("Cannot delete contracts")
```
4. **Filter data by hierarchy**
```python
filtered = RBAC.filter_data_by_hierarchy(data, user_context)
```
5. **Call Laravel APIs with bearer_token**
```python
headers = {"Authorization": f"Bearer {user_context.bearer_token}"}
```
### ā DON'T DO THIS:
1. ā Don't create middleware-style permission decorators
2. ā Don't hardcode role checks (use `user_context.is_*` properties)
3. ā Don't bypass RBAC filtering
4. ā Don't expose sensitive data without filtering
5. ā Don't make tools that work for all roles but return different data (split into separate tools instead)
---
## š Next Steps
### When You Provide Laravel Endpoints:
**Tell me:**
1. **Endpoint URL**: e.g., `/api/contracts`
2. **HTTP Method**: GET, POST, PUT, DELETE
3. **Request Parameters**: What data to send
4. **Response Format**: What data comes back
5. **Required Permission**: e.g., `contract.index`
6. **Hierarchy Level**: Organization, Platform, or Dealership level
**Example:**
```
Endpoint: GET /api/contracts
Permission: contract.index
Level: Dealership (filtered by dealership_id)
Response: {
"data": [
{
"id": 1,
"vendor_name": "ABC Corp",
"organization_id": 1,
"dealership_id": 5,
...
}
]
}
```
### I Will Create:
1. **Tool file** in `src/mcp_server/tools/contracts/get_contracts.py`
2. **RBAC entry** in `src/core/security.py`
3. **Proper permission checks** using Laravel permissions
4. **Hierarchy filtering** using `RBAC.filter_data_by_hierarchy()`
5. **Error handling** with proper logging
6. **Response envelope** with `wrap_response()`
---
## š Reference Files
| File | Purpose |
|------|---------|
| `src/core/security.py` | RBAC logic, UserContext, Role enum |
| `RBAC_GUIDE.md` | Role hierarchy, access rules, examples |
| `src/mcp_server/tools/user_profile/tool.py` | Example tool with permission filtering |
| `src/mcp_server/tools/example_tool.py` | Simple tool template |
| `src/orchestrator/processor.py` | User context injection logic |
| `src/web_chat/main.py` | Bearer token authentication |
---
## š Summary
Your current implementation is **excellent** and follows best practices:
ā
Bearer token authentication with caching
ā
Role-based tool filtering at orchestrator level
ā
User context automatically injected into tools
ā
Laravel permissions available in tools
ā
Hierarchy-aware data filtering
ā
Flexible permission checking (RBAC + Laravel)
**You don't need Laravel-style middleware.** Your MCP architecture is more flexible and LLM-friendly.
**Ready to build tools!** Just provide Laravel endpoints and I'll create the tools following this pattern.