Request Flows
End-to-end sequence diagrams for all major MCP request patterns — initialize, read, write, unlink, OAuth, and more
Request Flows
This page documents the end-to-end flows for all major MCP request patterns using sequence diagrams. All flows start at the POST /mcp endpoint.
1. Initialize Handshake
sequenceDiagram
participant C as AI Client
participant E as mcp_endpoint()
participant G as is_gateway_active()
participant A as authenticate()
participant RL as rate_limit
participant H as _handle_initialize()
participant L as audit_log
C->>E: POST /mcp {method: "initialize", params: {clientInfo: {name: "Cursor"}}}
E->>G: is_gateway_active()
G-->>E: True
E->>A: Bearer token or session
A-->>E: user (res.users)
E->>RL: is_rate_limited(user.id)
RL-->>E: False
E->>H: _handle_initialize(request_id)
H->>L: record_auth_status(success=True, client_name="Cursor")
H-->>C: {protocolVersion: "2024-11-05", capabilities: {tools: {}}, serverInfo: {...}}
Rendering diagram…
2. Tools List
sequenceDiagram
participant C as AI Client
participant E as mcp_endpoint()
participant H as _handle_tools_list()
C->>E: POST /mcp {method: "tools/list"}
Note over E: Auth + gateway + rate limit (same as initialize)
E->>H: _handle_tools_list(request_id)
H-->>C: Static array of 16 tool definitions with inputSchema
Rendering diagram…
3. odoo_search_read — Read Path
sequenceDiagram
participant C as AI Client
participant TC as _handle_tools_call()
participant V as clean_and_verify_model()
participant GA as evaluate_gateway_access()
participant ORM as env[model].search_read()
participant L as audit_log
C->>TC: tools/call {name: "odoo_search_read", arguments: {model, domain, fields}}
TC->>V: clean_and_verify_model("sale.order")
V-->>TC: "sale.order"
TC->>GA: evaluate_gateway_access(env, "sale.order", "search_read")
Note over GA: gateway on · model in policy · can_read=True
GA-->>TC: True
TC->>ORM: env["sale.order"].search_read(domain, fields, limit)
ORM-->>TC: [{id:1, name:"SO/001", ...}, ...]
TC->>L: log_model_access(model="sale.order", operation="search_read")
TC-->>C: {content: [{type:"text", text:"{...records...}"}], isError: false}
Rendering diagram…
4. odoo_write — Write Path
sequenceDiagram
participant C as AI Client
participant TC as _handle_tools_call()
participant GA as evaluate_gateway_access()
participant ORM as env[model].browse().write()
participant L as audit_log
C->>TC: tools/call {name: "odoo_write", arguments: {model, ids, action:"update", values}}
TC->>GA: evaluate_gateway_access(env, model, "write")
Note over GA: can_write=True required
GA-->>TC: True
TC->>ORM: env[model].browse(ids).write(values)
ORM-->>TC: True
TC->>L: log_model_access(operation="write", request_data, response_data)
TC-->>C: {content: [{type:"text", text:"{success:true}"}], isError: false}
Rendering diagram…
5. odoo_unlink — Delete + Approval Queue
sequenceDiagram
participant C as AI Client
participant TC as _handle_tools_call()
participant GA as evaluate_gateway_access()
participant APQ as ai_connector.approval
participant HR as Human Reviewer
participant ORM as env[model].browse().unlink()
C->>TC: tools/call {name: "odoo_unlink", arguments: {model, ids, reason}}
TC->>GA: evaluate_gateway_access(env, model, "unlink")
Note over GA: can_delete=True required
GA-->>TC: True
TC->>APQ: Create approval {state: pending, model, ids, user, reason}
APQ-->>TC: approval_id=7
TC-->>C: {content: [{type:"text", text:"{approval_id:7, state:pending}"}], isError:false}
Note over HR: Systray notification + Approval Card in AI client
HR->>APQ: Approve approval #7
APQ->>ORM: env[model].browse(ids).unlink()
ORM-->>APQ: Done
APQ-->>HR: State → approved
Rendering diagram…
6. odoo_action — Workflow Method
sequenceDiagram
participant C as AI Client
participant TC as _handle_tools_call()
participant R as resolve_rpc_method_to_action()
participant GA as evaluate_gateway_access()
participant ORM as getattr(records, action)()
C->>TC: tools/call {name:"odoo_action", arguments:{model, ids, action:"action_confirm"}}
TC->>R: resolve_rpc_method_to_action("action_confirm")
Note over R: "action_*" prefix → "write" policy
R-->>TC: "write"
TC->>GA: evaluate_gateway_access(env, model, "action_confirm")
GA-->>TC: True (can_write)
TC->>ORM: env[model].browse(ids).action_confirm()
ORM-->>TC: {type:"ir.actions.act_window", ...} or True
TC-->>C: {content:[{type:"text", text:"{success:true, result:...}"}], isError:false}
Rendering diagram…
7. odoo_communication — Chatter Post
sequenceDiagram
participant C as AI Client
participant TC as _handle_tools_call()
participant GA as evaluate_gateway_access()
participant ORM as record.message_post()
participant L as audit_log
C->>TC: tools/call {name:"odoo_communication", arguments:{model, res_id, sub_action:"post_note", body}}
Note over TC: sub_action → orm_method = "message_post"
TC->>GA: evaluate_gateway_access(env, model, "message_post")
Note over GA: message_post → "write" policy → can_write
GA-->>TC: True
TC->>ORM: env[model].browse(res_id).message_post(body=body, subtype_xmlid="mail.mt_note")
ORM-->>TC: mail.message record
TC->>L: log_model_access(operation="message_post")
TC-->>C: {content:[{type:"text", text:"{success:true, message_id:...}"}], isError:false}
Rendering diagram…
8. OAuth — Full Authorization Code Flow
sequenceDiagram
participant C as AI Client
participant W as /.well-known/*
participant A as /oauth/authorize
participant T as /oauth/token
participant M as POST /mcp
C->>W: GET /.well-known/oauth-authorization-server
W-->>C: {issuer, authorization_endpoint, token_endpoint, registration_endpoint}
C->>W: GET /.well-known/oauth-protected-resource
W-->>C: {resource, authorization_servers, scopes_supported}
Note over C: Generate code_verifier (random 32 bytes)\ncode_challenge = BASE64URL(SHA256(code_verifier))
C->>A: GET /oauth/authorize?response_type=code&client_id=X&redirect_uri=Y&code_challenge=Z&code_challenge_method=S256&resource=https://odoo.com/mcp&state=S
A-->>C: 302 /web/login?redirect=... (if not logged in)
C->>A: POST /oauth/authorize {csrf_token, action: "authorize"}
A-->>C: 302 redirect_uri?code=RAW_CODE&iss=ISSUER&state=S
C->>T: POST /oauth/token {grant_type=authorization_code, code=RAW_CODE, client_id=X, redirect_uri=Y, code_verifier=VERIFIER, resource=https://odoo.com/mcp}
T-->>C: {access_token, token_type:"Bearer", expires_in:3600, refresh_token, scope:"mcp"}
C->>M: POST /mcp {Authorization: Bearer ACCESS_TOKEN, ...tool call...}
M-->>C: JSON-RPC tool result
Rendering diagram…
9. OAuth — Token Refresh
sequenceDiagram
participant C as AI Client
participant T as /oauth/token
participant DB as odoo_ai_mcp.oauth.token
C->>T: POST /oauth/token {grant_type=refresh_token, refresh_token=RT, client_id=X, resource=RES}
T->>DB: refresh_access_token(refresh_token=RT, client_id=X, resource=RES)
DB->>DB: Invalidate old token row (active=False)
DB->>DB: issue_tokens() → new access_token + refresh_token
DB-->>T: {access_token, expires_in, refresh_token, ...}
T-->>C: New token pair
Rendering diagram…
10. Audit Flow
flowchart TD
CALL["Tool call executes"] --> CHECK{"enable_logging\n= True?"}
CHECK -->|"No"| SKIP["No log written"]
CHECK -->|"Yes"| RO{"readonly\ncursor?"}
RO -->|"Yes"| SKIP
RO -->|"No"| SKIP2{"skip_mcp_logging\nin context?"}
SKIP2 -->|"Yes"| SKIP
SKIP2 -->|"No"| WRITE["record_entry(log_type, **kwargs)\nTruncate text at 10k chars\nWrite ai_connector.audit_log row"]
WRITE --> PURGE["Daily cron: purge_expired_history()\nDelete rows older than log_retention_days"]
Rendering diagram…
11. Rate Limit Flow
flowchart LR
REQ["POST /mcp\n(authenticated user)"] --> ENABLED{"rate_limiting\nenabled?"}
ENABLED -->|"No"| PROCEED["Proceed to handler"]
ENABLED -->|"Yes"| LOOKUP["Look up or create\nrate_limit row\nfor (user_id, minute_window)"]
LOOKUP --> INC["Atomic INCREMENT\ncounter"]
INC --> OVER{"count >\nrequest_limit?"}
OVER -->|"No"| PROCEED
OVER -->|"Yes"| REJECT["Return -32002\nHTTP 429\nLog rate_limit event"]
Rendering diagram…