# Architecture Flow Diagram
## π Complete Request Flow
```
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CLIENT (Laravel Frontend) β
β β
β User clicks "Ask AI" β Sends message + Bearer Token β
ββββββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
β POST /chat
β Authorization: Bearer <token>
β {"message": "Show my contracts"}
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β WEB CHAT API (FastAPI) β
β src/web_chat/main.py β
β β
β 1. Extract Bearer token from header β
β 2. Call get_user_from_token(token) β
β ββ Check cache (15 min TTL) β
β ββ If cached: return user data (10ms) β
β ββ If not: call Laravel API (150ms) β
β β
β 3. Laravel API: GET /api/get-login-user-data β
β Returns: β
β { β
β "user_id": 123, β
β "role_id": 10, // Dealership Admin β
β "organization_id": 1, β
β "dealership_id": 5, β
β "email": "user@example.com", β
β "permissions": ["contract.index", "contract.create", ...] β
β } β
β β
β 4. Create UserContext object β
β UserContext( β
β user_id=123, β
β role=Role.DEALERSHIP_ADMIN, β
β bearer_token=token, β
β organization_id=1, β
β dealership_id=5, β
β permissions=[...] β
β ) β
ββββββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
β process_query(message, user_context)
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ORCHESTRATOR (Query Processor) β
β src/orchestrator/processor.py β
β β
β 1. Connect to MCP Server β
β 2. Get available tools from MCP β
β 3. Filter tools by RBAC β
β ββ User role: DEALERSHIP_ADMIN β
β ββ Available: DEALERSHIP_TOOLS + AUTHENTICATED_TOOLS β
β ββ Filtered: ["get_dealership_contracts", "get_user_profile", ...] β
β β
β 4. Call LLM with filtered tools β
β ββ Provider: OpenAI/Claude/Gemini β
β ββ Message: "Show my contracts" β
β ββ Tools: [filtered tool list] β
β β
β 5. LLM decides to call: get_dealership_contracts() β
β β
β 6. Execute tool via MCP β
β ββ Inject user context into tool arguments β
β ββ args["user_id"] = 123 β
β ββ args["organization_id"] = 1 β
β ββ args["dealership_id"] = 5 β
β ββ args["bearer_token"] = token β
β ββ args["permissions"] = [...] β
ββββββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
β call_tool("get_dealership_contracts", args)
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MCP SERVER (FastMCP) β
β src/mcp_server/server.py β
β β
β 1. Receive tool call with injected context β
β 2. Route to registered tool β
β 3. Call tool function with arguments β
ββββββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
β get_dealership_contracts(user_context, ...)
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β TOOL (Your Implementation) β
β src/mcp_server/tools/contracts/get_contracts.py β
β β
β 1. Receive UserContext β
β user_context = UserContext( β
β user_id=123, β
β role=DEALERSHIP_ADMIN, β
β bearer_token=token, β
β dealership_id=5, β
β permissions=["contract.index", ...] β
β ) β
β β
β 2. Check permissions β
β if "contract.index" not in user_context.permissions: β
β return error("Missing permission") β
β β
β 3. Call Laravel API β
β GET /api/contracts β
β Headers: {"Authorization": f"Bearer {user_context.bearer_token}"} β
β β
β 4. Laravel returns contracts β
β { β
β "data": [ β
β {"id": 1, "vendor": "ABC", "dealership_id": 5, ...}, β
β {"id": 2, "vendor": "XYZ", "dealership_id": 5, ...}, β
β {"id": 3, "vendor": "DEF", "dealership_id": 8, ...} β Other β
β ] β
β } β
β β
β 5. Filter by hierarchy β
β filtered = RBAC.filter_data_by_hierarchy( β
β data=contracts, β
β user_context=user_context β
β ) β
β Result: Only contracts where dealership_id=5 β
β β
β 6. Return envelope response β
β wrap_response( β
β tool_name="get_dealership_contracts", β
β data={"contracts": filtered, "count": 2} β
β ) β
ββββββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
β Return tool result
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ORCHESTRATOR (Continued) β
β β
β 7. Receive tool result β
β 8. Send result back to LLM β
β 9. LLM generates natural language response β
β "You have 2 contracts: β
β 1. ABC Corp - expires 2024-12-31 β
β 2. XYZ Inc - expires 2025-06-30" β
ββββββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
β Return final answer
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β WEB CHAT API (Response) β
β β
β 10. Save conversation history β
β 11. Return response to client β
β { β
β "success": true, β
β "response": "You have 2 contracts: ...", β
β "history": [...] β
β } β
ββββββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
β HTTP Response
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CLIENT (Display Response) β
β β
β User sees: "You have 2 contracts: ..." β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
```
---
## π RBAC Filtering at Different Stages
```
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β RBAC FILTERING STAGES β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Stage 1: TOOL VISIBILITY (Orchestrator)
ββ Input: All MCP tools
ββ Filter: RBAC.is_tool_allowed(user_role, tool_name)
ββ Output: Only tools user's role can access
ββ Effect: LLM only sees allowed tools
Example:
Dealership Admin sees:
β
get_dealership_contracts
β
get_user_profile
β
upload_contract
β get_organization_contracts (org-level only)
β manage_organizations (admin only)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Stage 2: PERMISSION CHECK (Tool Level)
ββ Input: Tool called by LLM
ββ Filter: Check user_context.permissions
ββ Output: Allow/deny tool execution
ββ Effect: Fine-grained permission control
Example:
Tool: upload_contract
Check: "contract.create" in user_context.permissions
Dealership Admin: β
Has permission β Proceed
Dealership Viewer: β No permission β Return error
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Stage 3: DATA FILTERING (Tool Level)
ββ Input: Data from Laravel API
ββ Filter: RBAC.filter_data_by_hierarchy(data, user_context)
ββ Output: Only data user can see
ββ Effect: Hierarchy-aware data access
Example:
Laravel returns 100 contracts:
- 30 from Organization A, Dealership 1
- 40 from Organization A, Dealership 2
- 30 from Organization B, Dealership 3
Org Admin (Org A): Sees 70 contracts (Dealership 1 + 2)
Platform Admin (Platform with Dealership 1): Sees 30 contracts
Dealership Admin (Dealership 1): Sees 30 contracts
Dealership Admin (Dealership 2): Sees 40 contracts
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Stage 4: ACTION FILTERING (Tool Level)
ββ Input: User action (create/update/delete)
ββ Filter: user_context.can_write / can_manage
ββ Output: Allow/deny action
ββ Effect: Prevent viewers from modifying data
Example:
Action: Delete contract
Dealership Admin: β
can_manage=True β Proceed
Dealership User: β
can_write=True β Check permission
Dealership Viewer: β can_write=False β Return error
```
---
## π― Permission Check Decision Tree
```
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SHOULD I CHECK PERMISSIONS? β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Tool Type: READ-ONLY (list, get, view)
ββ RBAC already filtered tool visibility β
ββ No write/delete actions
ββ Decision: NO permission check needed
ββ Just filter data by hierarchy
Example: get_dealership_contracts, get_user_profile
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Tool Type: CREATE/UPDATE
ββ User might be a viewer (read-only)
ββ Need to prevent modifications
ββ Decision: YES, check can_write
ββ if not user_context.can_write: return error
Example: create_contract, update_contract
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Tool Type: DELETE
ββ Sensitive action
ββ Only managers/admins should delete
ββ Decision: YES, check can_manage + specific permission
ββ if not user_context.can_manage: return error
ββ if "resource.delete" not in permissions: return error
Example: delete_contract, delete_vendor
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Tool Type: ADMIN ACTION
ββ System-wide changes
ββ Only admins should access
ββ Decision: YES, check is_global_admin or is_organization_level
ββ if not user_context.is_global_admin: return error
Example: manage_organizations, system_configuration
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Tool Type: SPECIFIC FEATURE
ββ Laravel has specific permission for this
ββ Need to check exact permission
ββ Decision: YES, check specific Laravel permission
ββ if "specific.permission" not in permissions: return error
Example: contracts.renewal, invoice.paid, users.global_filters
```
---
## π Data Flow Example: "Show my contracts"
```
User Message: "Show my contracts"
User Role: Dealership Admin (role_id=10)
Dealership: ID 5
Organization: ID 1
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Step 1: Authentication
ββ Bearer token validated
ββ User data cached
ββ UserContext created
ββ user_id: 123
ββ role: DEALERSHIP_ADMIN (10)
ββ organization_id: 1
ββ dealership_id: 5
ββ permissions: ["contract.index", "contract.show", ...]
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Step 2: Tool Filtering (RBAC)
ββ All MCP tools: 20 tools
ββ Filter by role: DEALERSHIP_ADMIN
ββ Allowed tools: 8 tools
ββ β
get_dealership_contracts
ββ β
get_dealership_vendors
ββ β
upload_contract
ββ β
get_user_profile
ββ β get_platform_contracts (platform-level)
ββ β get_organization_contracts (org-level)
ββ β manage_organizations (admin-only)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Step 3: LLM Decision
ββ Query: "Show my contracts"
ββ Available tools: [8 filtered tools]
ββ LLM chooses: get_dealership_contracts()
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Step 4: Tool Execution
ββ Tool: get_dealership_contracts
ββ Injected context:
β ββ user_id: 123
β ββ organization_id: 1
β ββ dealership_id: 5
β ββ bearer_token: <token>
β ββ permissions: [...]
ββ Tool logic:
ββ Check permission: "contract.index" β
ββ Call Laravel: GET /api/contracts
ββ Laravel returns: 100 contracts (all orgs/dealerships)
ββ Filter by hierarchy:
ββ Keep: organization_id=1 AND dealership_id=5
ββ Result: 12 contracts
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Step 5: LLM Response
ββ Tool result: {"contracts": [12 items], "count": 12}
ββ LLM generates: "You have 12 contracts for your dealership..."
ββ Return to user
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Total Time: ~200ms
ββ Auth (cached): 10ms
ββ LLM call: 100ms
ββ Tool execution: 50ms
ββ Response: 40ms
```
---
## π Comparison: Laravel vs MCP RBAC
```
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β LARAVEL APPROACH β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Route::group(['middleware' => ['auth:sanctum', 'permission']], function () {
Route::get('/api/contracts', [ContractController::class, 'index'])
->middleware('permission:contract.index');
Route::post('/api/contracts', [ContractController::class, 'store'])
->middleware('permission:contract.create');
Route::delete('/api/contracts/{id}', [ContractController::class, 'destroy'])
->middleware('permission:contract.delete');
});
Flow:
1. Request hits route
2. auth:sanctum validates token
3. permission middleware checks Laravel permission
4. If allowed β Controller executes
5. If denied β 403 Forbidden
Pros:
β
Simple and explicit
β
Enforced at route level
β
Standard Laravel pattern
Cons:
β Fixed per endpoint
β No dynamic tool filtering
β LLM sees all endpoints (can't filter)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MCP APPROACH β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# RBAC at orchestrator level
allowed_tools = RBAC.get_allowed_tools(user_context.role)
# Tool receives full context
@mcp.tool()
async def get_contracts(user_context: UserContext):
# Check permissions
if "contract.index" not in user_context.permissions:
return error("Missing permission")
# Call Laravel API
response = await call_laravel_api(user_context.bearer_token)
# Filter by hierarchy
filtered = RBAC.filter_data_by_hierarchy(data, user_context)
return filtered
Flow:
1. Bearer token validated at API entry
2. RBAC filters tools by role
3. LLM only sees allowed tools
4. Tool checks permissions + filters data
5. Return filtered result
Pros:
β
Dynamic tool filtering (LLM-friendly)
β
Hierarchy-aware data filtering
β
Flexible permission checks
β
Single auth check (cached)
β
Context-aware tools
Cons:
β More complex than middleware
β Permission checks in tool code
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
VERDICT: MCP approach is BETTER for AI agents because:
1. LLM only sees tools user can actually use
2. Tools can make intelligent decisions based on context
3. Hierarchy filtering is automatic
4. More flexible than fixed routes
5. Better user experience (no "permission denied" errors)
```
---
## π Key Takeaways
1. **Authentication happens once** at API entry (Bearer token)
2. **RBAC filters tools** before LLM sees them
3. **Tools receive full user context** automatically
4. **Permission checks** happen at tool level (flexible)
5. **Data filtering** happens at tool level (hierarchy-aware)
6. **No middleware needed** - MCP architecture is better for AI
**Your current implementation is excellent!** π