diff --git a/docs/openapi.json b/docs/openapi.json index 6034b91a91..eb202e44e0 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -91194,8 +91194,7 @@ "type": "string", "enum": [ "organization", - "team", - "agent" + "team" ] }, "in": "query", @@ -91209,19 +91208,6 @@ "in": "query", "name": "entityId", "required": false - }, - { - "schema": { - "type": "string", - "enum": [ - "token_cost", - "mcp_server_calls", - "tool_calls" - ] - }, - "in": "query", - "name": "limitType", - "required": false } ], "responses": { @@ -91243,21 +91229,12 @@ "type": "string", "enum": [ "organization", - "team", - "agent" + "team" ] }, "entityId": { "type": "string" }, - "limitType": { - "type": "string", - "enum": [ - "token_cost", - "mcp_server_calls", - "tool_calls" - ] - }, "limitValue": { "type": "integer", "minimum": -2147483648, @@ -91325,7 +91302,6 @@ "id", "entityType", "entityId", - "limitType", "limitValue", "mcpServerName", "toolName", @@ -91567,21 +91543,12 @@ "type": "string", "enum": [ "organization", - "team", - "agent" + "team" ] }, "entityId": { "type": "string" }, - "limitType": { - "type": "string", - "enum": [ - "token_cost", - "mcp_server_calls", - "tool_calls" - ] - }, "limitValue": { "type": "integer", "minimum": -2147483648, @@ -91611,7 +91578,6 @@ "required": [ "entityType", "entityId", - "limitType", "limitValue" ] } @@ -91636,21 +91602,12 @@ "type": "string", "enum": [ "organization", - "team", - "agent" + "team" ] }, "entityId": { "type": "string" }, - "limitType": { - "type": "string", - "enum": [ - "token_cost", - "mcp_server_calls", - "tool_calls" - ] - }, "limitValue": { "type": "integer", "minimum": -2147483648, @@ -91691,7 +91648,6 @@ "id", "entityType", "entityId", - "limitType", "limitValue", "mcpServerName", "toolName", @@ -91953,21 +91909,12 @@ "type": "string", "enum": [ "organization", - "team", - "agent" + "team" ] }, "entityId": { "type": "string" }, - "limitType": { - "type": "string", - "enum": [ - "token_cost", - "mcp_server_calls", - "tool_calls" - ] - }, "limitValue": { "type": "integer", "minimum": -2147483648, @@ -92008,7 +91955,6 @@ "id", "entityType", "entityId", - "limitType", "limitValue", "mcpServerName", "toolName", @@ -92249,21 +92195,12 @@ "type": "string", "enum": [ "organization", - "team", - "agent" + "team" ] }, "entityId": { "type": "string" }, - "limitType": { - "type": "string", - "enum": [ - "token_cost", - "mcp_server_calls", - "tool_calls" - ] - }, "limitValue": { "type": "integer", "minimum": -2147483648, @@ -92323,21 +92260,12 @@ "type": "string", "enum": [ "organization", - "team", - "agent" + "team" ] }, "entityId": { "type": "string" }, - "limitType": { - "type": "string", - "enum": [ - "token_cost", - "mcp_server_calls", - "tool_calls" - ] - }, "limitValue": { "type": "integer", "minimum": -2147483648, @@ -92378,7 +92306,6 @@ "id", "entityType", "entityId", - "limitType", "limitValue", "mcpServerName", "toolName", @@ -93001,6 +92928,1547 @@ } } }, + "/api/mcp-rate-limits": { + "get": { + "operationId": "getMcpRateLimits", + "tags": [ + "MCP Rate Limits" + ], + "description": "Get all MCP rate limits with optional filtering and live usage", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "agentId", + "required": false + }, + { + "schema": { + "type": "string", + "enum": [ + "mcp_server_calls", + "tool_calls" + ] + }, + "in": "query", + "name": "limitType", + "required": false + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" + }, + "agentId": { + "type": "string", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" + }, + "limitType": { + "type": "string", + "enum": [ + "mcp_server_calls", + "tool_calls" + ] + }, + "mcpServerName": { + "type": "string", + "maxLength": 255 + }, + "toolName": { + "nullable": true, + "type": "string", + "maxLength": 255 + }, + "maxCalls": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "windowSeconds": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "mcpUsage": { + "type": "number" + } + }, + "required": [ + "id", + "agentId", + "limitType", + "mcpServerName", + "toolName", + "maxCalls", + "windowSeconds", + "createdAt", + "updatedAt" + ], + "additionalProperties": false + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_validation_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_authentication_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_authorization_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_not_found_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "409": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_conflict_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "500": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_internal_server_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + } + } + }, + "post": { + "operationId": "createMcpRateLimit", + "tags": [ + "MCP Rate Limits" + ], + "description": "Create a new MCP rate limit", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "agentId": { + "type": "string", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" + }, + "limitType": { + "type": "string", + "enum": [ + "mcp_server_calls", + "tool_calls" + ] + }, + "mcpServerName": { + "type": "string", + "maxLength": 255 + }, + "toolName": { + "nullable": true, + "type": "string", + "maxLength": 255 + }, + "maxCalls": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "windowSeconds": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + } + }, + "required": [ + "agentId", + "limitType", + "mcpServerName", + "maxCalls", + "windowSeconds" + ] + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" + }, + "agentId": { + "type": "string", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" + }, + "limitType": { + "type": "string", + "enum": [ + "mcp_server_calls", + "tool_calls" + ] + }, + "mcpServerName": { + "type": "string", + "maxLength": 255 + }, + "toolName": { + "nullable": true, + "type": "string", + "maxLength": 255 + }, + "maxCalls": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "windowSeconds": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "id", + "agentId", + "limitType", + "mcpServerName", + "toolName", + "maxCalls", + "windowSeconds", + "createdAt", + "updatedAt" + ], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_validation_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_authentication_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_authorization_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_not_found_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "409": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_conflict_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "500": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_internal_server_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + } + } + } + }, + "/api/mcp-rate-limits/{id}": { + "get": { + "operationId": "getMcpRateLimit", + "tags": [ + "MCP Rate Limits" + ], + "description": "Get an MCP rate limit by ID", + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" + }, + "agentId": { + "type": "string", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" + }, + "limitType": { + "type": "string", + "enum": [ + "mcp_server_calls", + "tool_calls" + ] + }, + "mcpServerName": { + "type": "string", + "maxLength": 255 + }, + "toolName": { + "nullable": true, + "type": "string", + "maxLength": 255 + }, + "maxCalls": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "windowSeconds": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "id", + "agentId", + "limitType", + "mcpServerName", + "toolName", + "maxCalls", + "windowSeconds", + "createdAt", + "updatedAt" + ], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_validation_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_authentication_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_authorization_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_not_found_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "409": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_conflict_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "500": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_internal_server_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + } + } + }, + "patch": { + "operationId": "updateMcpRateLimit", + "tags": [ + "MCP Rate Limits" + ], + "description": "Update an MCP rate limit", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "limitType": { + "type": "string", + "enum": [ + "mcp_server_calls", + "tool_calls" + ] + }, + "mcpServerName": { + "type": "string", + "maxLength": 255 + }, + "toolName": { + "nullable": true, + "type": "string", + "maxLength": 255 + }, + "maxCalls": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "windowSeconds": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + } + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" + }, + "agentId": { + "type": "string", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" + }, + "limitType": { + "type": "string", + "enum": [ + "mcp_server_calls", + "tool_calls" + ] + }, + "mcpServerName": { + "type": "string", + "maxLength": 255 + }, + "toolName": { + "nullable": true, + "type": "string", + "maxLength": 255 + }, + "maxCalls": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "windowSeconds": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "id", + "agentId", + "limitType", + "mcpServerName", + "toolName", + "maxCalls", + "windowSeconds", + "createdAt", + "updatedAt" + ], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_validation_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_authentication_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_authorization_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_not_found_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "409": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_conflict_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "500": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_internal_server_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + } + } + }, + "delete": { + "operationId": "deleteMcpRateLimit", + "tags": [ + "MCP Rate Limits" + ], + "description": "Delete an MCP rate limit", + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + }, + "required": [ + "success" + ], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_validation_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_authentication_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_authorization_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_not_found_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "409": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_conflict_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + }, + "500": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "api_internal_server_error" + ] + } + }, + "required": [ + "message", + "type" + ], + "additionalProperties": false + } + }, + "required": [ + "error" + ], + "additionalProperties": false + } + } + } + } + } + } + }, "/api/mcp_server_installation_requests": { "get": { "operationId": "getMcpServerInstallationRequests", diff --git a/docs/pages/platform-access-control.md b/docs/pages/platform-access-control.md index f7b04dc2a4..379cd8e8a1 100644 --- a/docs/pages/platform-access-control.md +++ b/docs/pages/platform-access-control.md @@ -3,7 +3,7 @@ title: "Access Control" category: Archestra Platform description: "Role-based access control (RBAC) system for managing user permissions in Archestra" order: 4 -lastUpdated: 2026-02-27 +lastUpdated: 2026-03-03 --- statement-breakpoint +DROP INDEX "limits_type_idx";--> statement-breakpoint +ALTER TABLE "mcp_rate_limits" ADD CONSTRAINT "mcp_rate_limits_agent_id_agents_id_fk" FOREIGN KEY ("agent_id") REFERENCES "public"."agents"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "mcp_rate_limits_agent_idx" ON "mcp_rate_limits" USING btree ("agent_id");--> statement-breakpoint +CREATE INDEX "mcp_rate_limits_agent_server_idx" ON "mcp_rate_limits" USING btree ("agent_id","mcp_server_name");--> statement-breakpoint +ALTER TABLE "limits" DROP COLUMN "limit_type"; + +-- RBAC resource rename: "limit" -> "llmTokenLimit" in custom roles +-- Also copy "limit" permissions to "mcpRateLimit" for backward compatibility +UPDATE "organization_role" +SET "permission" = ( + ("permission"::jsonb - 'limit') + || jsonb_build_object('llmTokenLimit', "permission"::jsonb->'limit') + || jsonb_build_object('mcpRateLimit', "permission"::jsonb->'limit') +)::text +WHERE "permission"::text LIKE '%"limit"%'; \ No newline at end of file diff --git a/platform/backend/src/database/migrations/meta/0160_snapshot.json b/platform/backend/src/database/migrations/meta/0160_snapshot.json new file mode 100644 index 0000000000..ddc1c7a353 --- /dev/null +++ b/platform/backend/src/database/migrations/meta/0160_snapshot.json @@ -0,0 +1,6372 @@ +{ + "id": "7bf3f1c0-3751-4715-9ac0-b542fcd4acb6", + "prevId": "1b7f0cd9-22b7-4cab-8b25-71169ca345a2", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_labels": { + "name": "agent_labels", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key_id": { + "name": "key_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "value_id": { + "name": "value_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "agent_labels_agent_id_agents_id_fk": { + "name": "agent_labels_agent_id_agents_id_fk", + "tableFrom": "agent_labels", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_labels_key_id_label_keys_id_fk": { + "name": "agent_labels_key_id_label_keys_id_fk", + "tableFrom": "agent_labels", + "tableTo": "label_keys", + "columnsFrom": [ + "key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_labels_value_id_label_values_id_fk": { + "name": "agent_labels_value_id_label_values_id_fk", + "tableFrom": "agent_labels", + "tableTo": "label_values", + "columnsFrom": [ + "value_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "agent_labels_agent_id_key_id_pk": { + "name": "agent_labels_agent_id_key_id_pk", + "columns": [ + "agent_id", + "key_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_team": { + "name": "agent_team", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "agent_team_agent_id_agents_id_fk": { + "name": "agent_team_agent_id_agents_id_fk", + "tableFrom": "agent_team", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_team_team_id_team_id_fk": { + "name": "agent_team_team_id_team_id_fk", + "tableFrom": "agent_team", + "tableTo": "team", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "agent_team_agent_id_team_id_pk": { + "name": "agent_team_agent_id_team_id_pk", + "columns": [ + "agent_id", + "team_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_tools": { + "name": "agent_tools", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "tool_id": { + "name": "tool_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "response_modifier_template": { + "name": "response_modifier_template", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_source_mcp_server_id": { + "name": "credential_source_mcp_server_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_source_mcp_server_id": { + "name": "execution_source_mcp_server_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "use_dynamic_team_credential": { + "name": "use_dynamic_team_credential", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "agent_tools_agent_id_agents_id_fk": { + "name": "agent_tools_agent_id_agents_id_fk", + "tableFrom": "agent_tools", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_tools_tool_id_tools_id_fk": { + "name": "agent_tools_tool_id_tools_id_fk", + "tableFrom": "agent_tools", + "tableTo": "tools", + "columnsFrom": [ + "tool_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_tools_credential_source_mcp_server_id_mcp_server_id_fk": { + "name": "agent_tools_credential_source_mcp_server_id_mcp_server_id_fk", + "tableFrom": "agent_tools", + "tableTo": "mcp_server", + "columnsFrom": [ + "credential_source_mcp_server_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "agent_tools_execution_source_mcp_server_id_mcp_server_id_fk": { + "name": "agent_tools_execution_source_mcp_server_id_mcp_server_id_fk", + "tableFrom": "agent_tools", + "tableTo": "mcp_server", + "columnsFrom": [ + "execution_source_mcp_server_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "agent_tools_agent_id_tool_id_unique": { + "name": "agent_tools_agent_id_tool_id_unique", + "nullsNotDistinct": false, + "columns": [ + "agent_id", + "tool_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "agent_scope", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'personal'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_demo": { + "name": "is_demo", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "consider_context_untrusted": { + "name": "consider_context_untrusted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "agent_type": { + "name": "agent_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'mcp_gateway'" + }, + "system_prompt": { + "name": "system_prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_prompt": { + "name": "user_prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt_version": { + "name": "prompt_version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "prompt_history": { + "name": "prompt_history", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "incoming_email_enabled": { + "name": "incoming_email_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "incoming_email_security_mode": { + "name": "incoming_email_security_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'private'" + }, + "incoming_email_allowed_domain": { + "name": "incoming_email_allowed_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "llm_api_key_id": { + "name": "llm_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "llm_model": { + "name": "llm_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "identity_provider_id": { + "name": "identity_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "built_in_agent_config": { + "name": "built_in_agent_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "built_in": { + "name": "built_in", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "\"agents\".\"built_in_agent_config\" IS NOT NULL", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_organization_id_idx": { + "name": "agents_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_agent_type_idx": { + "name": "agents_agent_type_idx", + "columns": [ + { + "expression": "agent_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_identity_provider_id_idx": { + "name": "agents_identity_provider_id_idx", + "columns": [ + { + "expression": "identity_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_author_id_idx": { + "name": "agents_author_id_idx", + "columns": [ + { + "expression": "author_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_scope_idx": { + "name": "agents_scope_idx", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_author_id_user_id_fk": { + "name": "agents_author_id_user_id_fk", + "tableFrom": "agents", + "tableTo": "user", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "agents_llm_api_key_id_chat_api_keys_id_fk": { + "name": "agents_llm_api_key_id_chat_api_keys_id_fk", + "tableFrom": "agents", + "tableTo": "chat_api_keys", + "columnsFrom": [ + "llm_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "agents_identity_provider_id_identity_provider_id_fk": { + "name": "agents_identity_provider_id_identity_provider_id_fk", + "tableFrom": "agents", + "tableTo": "identity_provider", + "columnsFrom": [ + "identity_provider_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_key_models": { + "name": "api_key_models", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "api_key_id": { + "name": "api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "model_id": { + "name": "model_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "is_fastest": { + "name": "is_fastest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_best": { + "name": "is_best", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "api_key_models_api_key_id_idx": { + "name": "api_key_models_api_key_id_idx", + "columns": [ + { + "expression": "api_key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "api_key_models_model_id_idx": { + "name": "api_key_models_model_id_idx", + "columns": [ + { + "expression": "model_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "api_key_models_api_key_id_chat_api_keys_id_fk": { + "name": "api_key_models_api_key_id_chat_api_keys_id_fk", + "tableFrom": "api_key_models", + "tableTo": "chat_api_keys", + "columnsFrom": [ + "api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_models_model_id_models_id_fk": { + "name": "api_key_models_model_id_models_id_fk", + "tableFrom": "api_key_models", + "tableTo": "models", + "columnsFrom": [ + "model_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_key_models_unique": { + "name": "api_key_models_unique", + "nullsNotDistinct": false, + "columns": [ + "api_key_id", + "model_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.apikey": { + "name": "apikey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start": { + "name": "start", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refill_interval": { + "name": "refill_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "refill_amount": { + "name": "refill_amount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "rate_limit_enabled": { + "name": "rate_limit_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "rate_limit_time_window": { + "name": "rate_limit_time_window", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 86400000 + }, + "rate_limit_max": { + "name": "rate_limit_max", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_request": { + "name": "last_request", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "apikey_user_id_user_id_fk": { + "name": "apikey_user_id_user_id_fk", + "tableFrom": "apikey", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.browser_tab_states": { + "name": "browser_tab_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "isolation_key": { + "name": "isolation_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tab_index": { + "name": "tab_index", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "browser_tab_states_agent_user_isolation_idx": { + "name": "browser_tab_states_agent_user_isolation_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "isolation_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "browser_tab_states_user_id_idx": { + "name": "browser_tab_states_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "browser_tab_states_agent_id_agents_id_fk": { + "name": "browser_tab_states_agent_id_agents_id_fk", + "tableFrom": "browser_tab_states", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "browser_tab_states_user_id_user_id_fk": { + "name": "browser_tab_states_user_id_user_id_fk", + "tableFrom": "browser_tab_states", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_api_keys": { + "name": "chat_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'personal'" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_system": { + "name": "is_system", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_api_keys_organization_id_idx": { + "name": "chat_api_keys_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_api_keys_org_provider_idx": { + "name": "chat_api_keys_org_provider_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_api_keys_system_unique": { + "name": "chat_api_keys_system_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_api_keys\".\"is_system\" = true", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_api_keys_primary_personal_unique": { + "name": "chat_api_keys_primary_personal_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_api_keys\".\"is_primary\" = true AND \"chat_api_keys\".\"scope\" = 'personal'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_api_keys_primary_team_unique": { + "name": "chat_api_keys_primary_team_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_api_keys\".\"is_primary\" = true AND \"chat_api_keys\".\"scope\" = 'team'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_api_keys_primary_org_wide_unique": { + "name": "chat_api_keys_primary_org_wide_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_api_keys\".\"is_primary\" = true AND \"chat_api_keys\".\"scope\" = 'org_wide'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_api_keys_secret_id_secret_id_fk": { + "name": "chat_api_keys_secret_id_secret_id_fk", + "tableFrom": "chat_api_keys", + "tableTo": "secret", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_api_keys_user_id_user_id_fk": { + "name": "chat_api_keys_user_id_user_id_fk", + "tableFrom": "chat_api_keys", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_api_keys_team_id_team_id_fk": { + "name": "chat_api_keys_team_id_team_id_fk", + "tableFrom": "chat_api_keys", + "tableTo": "team", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chatops_channel_binding": { + "name": "chatops_channel_binding", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "channel_name": { + "name": "channel_name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "workspace_name": { + "name": "workspace_name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "is_dm": { + "name": "is_dm", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dm_owner_email": { + "name": "dm_owner_email", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chatops_channel_binding_provider_channel_workspace_idx": { + "name": "chatops_channel_binding_provider_channel_workspace_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chatops_channel_binding_organization_id_idx": { + "name": "chatops_channel_binding_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chatops_channel_binding_agent_id_idx": { + "name": "chatops_channel_binding_agent_id_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chatops_channel_binding_agent_id_agents_id_fk": { + "name": "chatops_channel_binding_agent_id_agents_id_fk", + "tableFrom": "chatops_channel_binding", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chatops_processed_message": { + "name": "chatops_processed_message", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "message_id": { + "name": "message_id", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chatops_processed_message_processed_at_idx": { + "name": "chatops_processed_message_processed_at_idx", + "columns": [ + { + "expression": "processed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chatops_processed_message_message_id_unique": { + "name": "chatops_processed_message_message_id_unique", + "nullsNotDistinct": false, + "columns": [ + "message_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.conversation_enabled_tools": { + "name": "conversation_enabled_tools", + "schema": "", + "columns": { + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "tool_id": { + "name": "tool_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "conversation_enabled_tools_conversation_id_conversations_id_fk": { + "name": "conversation_enabled_tools_conversation_id_conversations_id_fk", + "tableFrom": "conversation_enabled_tools", + "tableTo": "conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "conversation_enabled_tools_tool_id_tools_id_fk": { + "name": "conversation_enabled_tools_tool_id_tools_id_fk", + "tableFrom": "conversation_enabled_tools", + "tableTo": "tools", + "columnsFrom": [ + "tool_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "conversation_enabled_tools_conversation_id_tool_id_pk": { + "name": "conversation_enabled_tools_conversation_id_tool_id_pk", + "columns": [ + "conversation_id", + "tool_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.conversations": { + "name": "conversations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chat_api_key_id": { + "name": "chat_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "selected_model": { + "name": "selected_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'gpt-4o'" + }, + "selected_provider": { + "name": "selected_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_custom_tool_selection": { + "name": "has_custom_tool_selection", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "todo_list": { + "name": "todo_list", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "artifact": { + "name": "artifact", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pinned_at": { + "name": "pinned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "conversations_agent_id_agents_id_fk": { + "name": "conversations_agent_id_agents_id_fk", + "tableFrom": "conversations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "conversations_chat_api_key_id_chat_api_keys_id_fk": { + "name": "conversations_chat_api_key_id_chat_api_keys_id_fk", + "tableFrom": "conversations", + "tableTo": "chat_api_keys", + "columnsFrom": [ + "chat_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dual_llm_config": { + "name": "dual_llm_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "main_agent_prompt": { + "name": "main_agent_prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "quarantined_agent_prompt": { + "name": "quarantined_agent_prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary_prompt": { + "name": "summary_prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "max_rounds": { + "name": "max_rounds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dual_llm_results": { + "name": "dual_llm_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversations": { + "name": "conversations", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "dual_llm_results_agent_id_idx": { + "name": "dual_llm_results_agent_id_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dual_llm_results_agent_id_agents_id_fk": { + "name": "dual_llm_results_agent_id_agents_id_fk", + "tableFrom": "dual_llm_results", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.identity_provider": { + "name": "identity_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_config": { + "name": "oidc_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "saml_config": { + "name": "saml_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role_mapping": { + "name": "role_mapping", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_sync_config": { + "name": "team_sync_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain_verified": { + "name": "domain_verified", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "identity_provider_user_id_user_id_fk": { + "name": "identity_provider_user_id_user_id_fk", + "tableFrom": "identity_provider", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "identity_provider_provider_id_unique": { + "name": "identity_provider_provider_id_unique", + "nullsNotDistinct": false, + "columns": [ + "provider_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.incoming_email_subscription": { + "name": "incoming_email_subscription", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subscription_id": { + "name": "subscription_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "webhook_url": { + "name": "webhook_url", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": true + }, + "client_state": { + "name": "client_state", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.interactions": { + "name": "interactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "external_agent_id": { + "name": "external_agent_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "session_source": { + "name": "session_source", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "request": { + "name": "request", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "processed_request": { + "name": "processed_request", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "response": { + "name": "response", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "baseline_model": { + "name": "baseline_model", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "baseline_cost": { + "name": "baseline_cost", + "type": "numeric(13, 10)", + "primaryKey": false, + "notNull": false + }, + "cost": { + "name": "cost", + "type": "numeric(13, 10)", + "primaryKey": false, + "notNull": false + }, + "toon_tokens_before": { + "name": "toon_tokens_before", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "toon_tokens_after": { + "name": "toon_tokens_after", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "toon_cost_savings": { + "name": "toon_cost_savings", + "type": "numeric(13, 10)", + "primaryKey": false, + "notNull": false + }, + "toon_skip_reason": { + "name": "toon_skip_reason", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "interactions_agent_id_idx": { + "name": "interactions_agent_id_idx", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "interactions_external_agent_id_idx": { + "name": "interactions_external_agent_id_idx", + "columns": [ + { + "expression": "external_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "interactions_execution_id_idx": { + "name": "interactions_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "interactions_user_id_idx": { + "name": "interactions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "interactions_session_id_idx": { + "name": "interactions_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "interactions_created_at_idx": { + "name": "interactions_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "interactions_profile_created_at_idx": { + "name": "interactions_profile_created_at_idx", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "interactions_session_created_at_idx": { + "name": "interactions_session_created_at_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "interactions_profile_id_agents_id_fk": { + "name": "interactions_profile_id_agents_id_fk", + "tableFrom": "interactions", + "tableTo": "agents", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "interactions_user_id_user_id_fk": { + "name": "interactions_user_id_user_id_fk", + "tableFrom": "interactions", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.internal_mcp_catalog": { + "name": "internal_mcp_catalog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "instructions": { + "name": "instructions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installation_command": { + "name": "installation_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requires_auth": { + "name": "requires_auth", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "auth_description": { + "name": "auth_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_fields": { + "name": "auth_fields", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "server_type": { + "name": "server_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "server_url": { + "name": "server_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "docs_url": { + "name": "docs_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_secret_id": { + "name": "client_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "local_config_secret_id": { + "name": "local_config_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "local_config": { + "name": "local_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "deployment_spec_yaml": { + "name": "deployment_spec_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_config": { + "name": "user_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "oauth_config": { + "name": "oauth_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "internal_mcp_catalog_client_secret_id_secret_id_fk": { + "name": "internal_mcp_catalog_client_secret_id_secret_id_fk", + "tableFrom": "internal_mcp_catalog", + "tableTo": "secret", + "columnsFrom": [ + "client_secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "internal_mcp_catalog_local_config_secret_id_secret_id_fk": { + "name": "internal_mcp_catalog_local_config_secret_id_secret_id_fk", + "tableFrom": "internal_mcp_catalog", + "tableTo": "secret", + "columnsFrom": [ + "local_config_secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.jwks": { + "name": "jwks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.label_keys": { + "name": "label_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "label_keys_key_unique": { + "name": "label_keys_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.label_values": { + "name": "label_values", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "label_values_value_unique": { + "name": "label_values_value_unique", + "nullsNotDistinct": false, + "columns": [ + "value" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.limit_model_usage": { + "name": "limit_model_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "limit_id": { + "name": "limit_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "current_usage_tokens_in": { + "name": "current_usage_tokens_in", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "current_usage_tokens_out": { + "name": "current_usage_tokens_out", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "limit_model_usage_limit_id_idx": { + "name": "limit_model_usage_limit_id_idx", + "columns": [ + { + "expression": "limit_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "limit_model_usage_limit_model_idx": { + "name": "limit_model_usage_limit_model_idx", + "columns": [ + { + "expression": "limit_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "limit_model_usage_limit_id_limits_id_fk": { + "name": "limit_model_usage_limit_id_limits_id_fk", + "tableFrom": "limit_model_usage", + "tableTo": "limits", + "columnsFrom": [ + "limit_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.limits": { + "name": "limits", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "entity_type": { + "name": "entity_type", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "limit_value": { + "name": "limit_value", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "mcp_server_name": { + "name": "mcp_server_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_cleanup": { + "name": "last_cleanup", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "limits_entity_idx": { + "name": "limits_entity_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_catalog_labels": { + "name": "mcp_catalog_labels", + "schema": "", + "columns": { + "catalog_id": { + "name": "catalog_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key_id": { + "name": "key_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "value_id": { + "name": "value_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mcp_catalog_labels_catalog_id_internal_mcp_catalog_id_fk": { + "name": "mcp_catalog_labels_catalog_id_internal_mcp_catalog_id_fk", + "tableFrom": "mcp_catalog_labels", + "tableTo": "internal_mcp_catalog", + "columnsFrom": [ + "catalog_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_catalog_labels_key_id_label_keys_id_fk": { + "name": "mcp_catalog_labels_key_id_label_keys_id_fk", + "tableFrom": "mcp_catalog_labels", + "tableTo": "label_keys", + "columnsFrom": [ + "key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_catalog_labels_value_id_label_values_id_fk": { + "name": "mcp_catalog_labels_value_id_label_values_id_fk", + "tableFrom": "mcp_catalog_labels", + "tableTo": "label_values", + "columnsFrom": [ + "value_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "mcp_catalog_labels_catalog_id_key_id_pk": { + "name": "mcp_catalog_labels_catalog_id_key_id_pk", + "columns": [ + "catalog_id", + "key_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_http_sessions": { + "name": "mcp_http_sessions", + "schema": "", + "columns": { + "connection_key": { + "name": "connection_key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_endpoint_url": { + "name": "session_endpoint_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_endpoint_pod_name": { + "name": "session_endpoint_pod_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_rate_limits": { + "name": "mcp_rate_limits", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "limit_type": { + "name": "limit_type", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "mcp_server_name": { + "name": "mcp_server_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "max_calls": { + "name": "max_calls", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "window_seconds": { + "name": "window_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_rate_limits_agent_idx": { + "name": "mcp_rate_limits_agent_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_rate_limits_agent_server_idx": { + "name": "mcp_rate_limits_agent_server_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mcp_server_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_rate_limits_agent_id_agents_id_fk": { + "name": "mcp_rate_limits_agent_id_agents_id_fk", + "tableFrom": "mcp_rate_limits", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_server_installation_request": { + "name": "mcp_server_installation_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "external_catalog_id": { + "name": "external_catalog_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by": { + "name": "requested_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "request_reason": { + "name": "request_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "custom_server_config": { + "name": "custom_server_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "admin_response": { + "name": "admin_response", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reviewed_by": { + "name": "reviewed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reviewed_at": { + "name": "reviewed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mcp_server_installation_request_requested_by_user_id_fk": { + "name": "mcp_server_installation_request_requested_by_user_id_fk", + "tableFrom": "mcp_server_installation_request", + "tableTo": "user", + "columnsFrom": [ + "requested_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_server_installation_request_reviewed_by_user_id_fk": { + "name": "mcp_server_installation_request_reviewed_by_user_id_fk", + "tableFrom": "mcp_server_installation_request", + "tableTo": "user", + "columnsFrom": [ + "reviewed_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_server_user": { + "name": "mcp_server_user", + "schema": "", + "columns": { + "mcp_server_id": { + "name": "mcp_server_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mcp_server_user_mcp_server_id_mcp_server_id_fk": { + "name": "mcp_server_user_mcp_server_id_mcp_server_id_fk", + "tableFrom": "mcp_server_user", + "tableTo": "mcp_server", + "columnsFrom": [ + "mcp_server_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_server_user_user_id_user_id_fk": { + "name": "mcp_server_user_user_id_user_id_fk", + "tableFrom": "mcp_server_user", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "mcp_server_user_mcp_server_id_user_id_pk": { + "name": "mcp_server_user_mcp_server_id_user_id_pk", + "columns": [ + "mcp_server_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_server": { + "name": "mcp_server", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "catalog_id": { + "name": "catalog_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "server_type": { + "name": "server_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reinstall_required": { + "name": "reinstall_required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "local_installation_status": { + "name": "local_installation_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "local_installation_error": { + "name": "local_installation_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_refresh_error": { + "name": "oauth_refresh_error", + "type": "oauth_refresh_error_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "oauth_refresh_failed_at": { + "name": "oauth_refresh_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mcp_server_catalog_id_internal_mcp_catalog_id_fk": { + "name": "mcp_server_catalog_id_internal_mcp_catalog_id_fk", + "tableFrom": "mcp_server", + "tableTo": "internal_mcp_catalog", + "columnsFrom": [ + "catalog_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mcp_server_secret_id_secret_id_fk": { + "name": "mcp_server_secret_id_secret_id_fk", + "tableFrom": "mcp_server", + "tableTo": "secret", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mcp_server_owner_id_user_id_fk": { + "name": "mcp_server_owner_id_user_id_fk", + "tableFrom": "mcp_server", + "tableTo": "user", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mcp_server_team_id_team_id_fk": { + "name": "mcp_server_team_id_team_id_fk", + "tableFrom": "mcp_server", + "tableTo": "team", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_tool_calls": { + "name": "mcp_tool_calls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mcp_server_name": { + "name": "mcp_server_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "tool_call": { + "name": "tool_call", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tool_result": { + "name": "tool_result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_method": { + "name": "auth_method", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_tool_calls_agent_id_idx": { + "name": "mcp_tool_calls_agent_id_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_tool_calls_created_at_idx": { + "name": "mcp_tool_calls_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_tool_calls_agent_id_agents_id_fk": { + "name": "mcp_tool_calls_agent_id_agents_id_fk", + "tableFrom": "mcp_tool_calls", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mcp_tool_calls_user_id_user_id_fk": { + "name": "mcp_tool_calls_user_id_user_id_fk", + "tableFrom": "mcp_tool_calls", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages": { + "name": "messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "messages_conversation_id_idx": { + "name": "messages_conversation_id_idx", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "messages_conversation_id_conversations_id_fk": { + "name": "messages_conversation_id_conversations_id_fk", + "tableFrom": "messages", + "tableTo": "conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.models": { + "name": "models", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context_length": { + "name": "context_length", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "input_modalities": { + "name": "input_modalities", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "output_modalities": { + "name": "output_modalities", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "supports_tool_calling": { + "name": "supports_tool_calling", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "prompt_price_per_token": { + "name": "prompt_price_per_token", + "type": "numeric(20, 12)", + "primaryKey": false, + "notNull": false + }, + "completion_price_per_token": { + "name": "completion_price_per_token", + "type": "numeric(20, 12)", + "primaryKey": false, + "notNull": false + }, + "custom_price_per_million_input": { + "name": "custom_price_per_million_input", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "custom_price_per_million_output": { + "name": "custom_price_per_million_output", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "models_provider_model_idx": { + "name": "models_provider_model_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "models_external_id_idx": { + "name": "models_external_id_idx", + "columns": [ + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "models_provider_model_unique": { + "name": "models_provider_model_unique", + "nullsNotDistinct": false, + "columns": [ + "provider", + "model_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_access_token": { + "name": "oauth_access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_id": { + "name": "refresh_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_access_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_access_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_session_id_session_id_fk": { + "name": "oauth_access_token_session_id_session_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_access_token_user_id_user_id_fk": { + "name": "oauth_access_token_user_id_user_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_refresh_id_oauth_refresh_token_id_fk": { + "name": "oauth_access_token_refresh_id_oauth_refresh_token_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_refresh_token", + "columnsFrom": [ + "refresh_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_access_token_token_unique": { + "name": "oauth_access_token_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_client": { + "name": "oauth_client", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contacts": { + "name": "contacts", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "tos": { + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_id": { + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_statement": { + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "skip_consent": { + "name": "skip_consent", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enable_end_session": { + "name": "enable_end_session", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_types": { + "name": "grant_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "response_types": { + "name": "response_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_client_user_id_user_id_fk": { + "name": "oauth_client_user_id_user_id_fk", + "tableFrom": "oauth_client", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_client_client_id_unique": { + "name": "oauth_client_client_id_unique", + "nullsNotDistinct": false, + "columns": [ + "client_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_consent": { + "name": "oauth_consent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_consent_client_id_oauth_client_client_id_fk": { + "name": "oauth_consent_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_consent_user_id_user_id_fk": { + "name": "oauth_consent_user_id_user_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_refresh_token": { + "name": "oauth_refresh_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked": { + "name": "revoked", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_refresh_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_refresh_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_refresh_token_session_id_session_id_fk": { + "name": "oauth_refresh_token_session_id_session_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_refresh_token_user_id_user_id_fk": { + "name": "oauth_refresh_token_user_id_user_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.optimization_rules": { + "name": "optimization_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "entity_type": { + "name": "entity_type", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_model": { + "name": "target_model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "optimization_rules_entity_idx": { + "name": "optimization_rules_entity_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_role": { + "name": "organization_role", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "organization_role_organization_id_organization_id_fk": { + "name": "organization_role_organization_id_organization_id_fk", + "tableFrom": "organization_role", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_role_organization_id_role_unique": { + "name": "organization_role_organization_id_role_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "role" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "limit_cleanup_interval": { + "name": "limit_cleanup_interval", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'1h'" + }, + "onboarding_complete": { + "name": "onboarding_complete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'cosmic-night'" + }, + "custom_font": { + "name": "custom_font", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'lato'" + }, + "convert_tool_results_to_toon": { + "name": "convert_tool_results_to_toon", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "compression_scope": { + "name": "compression_scope", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'organization'" + }, + "global_tool_policy": { + "name": "global_tool_policy", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'permissive'" + }, + "allow_chat_file_uploads": { + "name": "allow_chat_file_uploads", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_slug_unique": { + "name": "organization_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.processed_email": { + "name": "processed_email", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "message_id": { + "name": "message_id", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "processed_email_processed_at_idx": { + "name": "processed_email_processed_at_idx", + "columns": [ + { + "expression": "processed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "processed_email_message_id_unique": { + "name": "processed_email_message_id_unique", + "nullsNotDistinct": false, + "columns": [ + "message_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secret": { + "name": "secret", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true, + "default": "'secret'" + }, + "secret": { + "name": "secret", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "is_vault": { + "name": "is_vault", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_byos_vault": { + "name": "is_byos_vault", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_external_group": { + "name": "team_external_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_identifier": { + "name": "group_identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_external_group_team_id_team_id_fk": { + "name": "team_external_group_team_id_team_id_fk", + "tableFrom": "team_external_group", + "tableTo": "team", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_external_group_team_group_unique": { + "name": "team_external_group_team_group_unique", + "nullsNotDistinct": false, + "columns": [ + "team_id", + "group_identifier" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_member": { + "name": "team_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "synced_from_sso": { + "name": "synced_from_sso", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "team_member_team_id_team_id_fk": { + "name": "team_member_team_id_team_id_fk", + "tableFrom": "team_member", + "tableTo": "team", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_member_user_id_user_id_fk": { + "name": "team_member_user_id_user_id_fk", + "tableFrom": "team_member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_token": { + "name": "team_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_organization_token": { + "name": "is_organization_token", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "token_start": { + "name": "token_start", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_team_token_org_id": { + "name": "idx_team_token_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_team_token_team_id": { + "name": "idx_team_token_team_id", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_token_organization_id_organization_id_fk": { + "name": "team_token_organization_id_organization_id_fk", + "tableFrom": "team_token", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_token_team_id_team_id_fk": { + "name": "team_token_team_id_team_id_fk", + "tableFrom": "team_token", + "tableTo": "team", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_token_secret_id_secret_id_fk": { + "name": "team_token_secret_id_secret_id_fk", + "tableFrom": "team_token", + "tableTo": "secret", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_token_organization_id_team_id_unique": { + "name": "team_token_organization_id_team_id_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "team_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_vault_folder": { + "name": "team_vault_folder", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vault_path": { + "name": "vault_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "team_vault_folder_team_id_team_id_fk": { + "name": "team_vault_folder_team_id_team_id_fk", + "tableFrom": "team_vault_folder", + "tableTo": "team", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_vault_folder_team_id_unique": { + "name": "team_vault_folder_team_id_unique", + "nullsNotDistinct": false, + "columns": [ + "team_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team": { + "name": "team", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "convert_tool_results_to_toon": { + "name": "convert_tool_results_to_toon", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "team_organization_id_organization_id_fk": { + "name": "team_organization_id_organization_id_fk", + "tableFrom": "team", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_created_by_user_id_fk": { + "name": "team_created_by_user_id_fk", + "tableFrom": "team", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_invocation_policies": { + "name": "tool_invocation_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tool_id": { + "name": "tool_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "tool_invocation_policies_tool_id_tools_id_fk": { + "name": "tool_invocation_policies_tool_id_tools_id_fk", + "tableFrom": "tool_invocation_policies", + "tableTo": "tools", + "columnsFrom": [ + "tool_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tools": { + "name": "tools", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_id": { + "name": "catalog_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mcp_server_id": { + "name": "mcp_server_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "delegate_to_agent_id": { + "name": "delegate_to_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parameters": { + "name": "parameters", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policies_auto_configured_at": { + "name": "policies_auto_configured_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "policies_auto_configuring_started_at": { + "name": "policies_auto_configuring_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "policies_auto_configured_reasoning": { + "name": "policies_auto_configured_reasoning", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policies_auto_configured_model": { + "name": "policies_auto_configured_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tools_delegate_to_agent_id_idx": { + "name": "tools_delegate_to_agent_id_idx", + "columns": [ + { + "expression": "delegate_to_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tools_agent_id_agents_id_fk": { + "name": "tools_agent_id_agents_id_fk", + "tableFrom": "tools", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tools_catalog_id_internal_mcp_catalog_id_fk": { + "name": "tools_catalog_id_internal_mcp_catalog_id_fk", + "tableFrom": "tools", + "tableTo": "internal_mcp_catalog", + "columnsFrom": [ + "catalog_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tools_mcp_server_id_mcp_server_id_fk": { + "name": "tools_mcp_server_id_mcp_server_id_fk", + "tableFrom": "tools", + "tableTo": "mcp_server", + "columnsFrom": [ + "mcp_server_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tools_delegate_to_agent_id_agents_id_fk": { + "name": "tools_delegate_to_agent_id_agents_id_fk", + "tableFrom": "tools", + "tableTo": "agents", + "columnsFrom": [ + "delegate_to_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tools_catalog_id_name_agent_id_delegate_to_agent_id_unique": { + "name": "tools_catalog_id_name_agent_id_delegate_to_agent_id_unique", + "nullsNotDistinct": false, + "columns": [ + "catalog_id", + "name", + "agent_id", + "delegate_to_agent_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.trusted_data_policies": { + "name": "trusted_data_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tool_id": { + "name": "tool_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'mark_as_trusted'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "trusted_data_policies_tool_id_tools_id_fk": { + "name": "trusted_data_policies_tool_id_tools_id_fk", + "tableFrom": "trusted_data_policies", + "tableTo": "tools", + "columnsFrom": [ + "tool_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "two_factor_user_id_user_id_fk": { + "name": "two_factor_user_id_user_id_fk", + "tableFrom": "two_factor", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_token": { + "name": "user_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "token_start": { + "name": "token_start", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_user_token_org_id": { + "name": "idx_user_token_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_user_token_user_id": { + "name": "idx_user_token_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_token_organization_id_organization_id_fk": { + "name": "user_token_organization_id_organization_id_fk", + "tableFrom": "user_token", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_token_user_id_user_id_fk": { + "name": "user_token_user_id_user_id_fk", + "tableFrom": "user_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_token_secret_id_secret_id_fk": { + "name": "user_token_secret_id_secret_id_fk", + "tableFrom": "user_token", + "tableTo": "secret", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_token_organization_id_user_id_unique": { + "name": "user_token_organization_id_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.virtual_api_keys": { + "name": "virtual_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_api_key_id": { + "name": "chat_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "token_start": { + "name": "token_start", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_virtual_api_key_chat_api_key_id": { + "name": "idx_virtual_api_key_chat_api_key_id", + "columns": [ + { + "expression": "chat_api_key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_virtual_api_key_token_start": { + "name": "idx_virtual_api_key_token_start", + "columns": [ + { + "expression": "token_start", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "virtual_api_keys_chat_api_key_id_chat_api_keys_id_fk": { + "name": "virtual_api_keys_chat_api_key_id_chat_api_keys_id_fk", + "tableFrom": "virtual_api_keys", + "tableTo": "chat_api_keys", + "columnsFrom": [ + "chat_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "virtual_api_keys_secret_id_secret_id_fk": { + "name": "virtual_api_keys_secret_id_secret_id_fk", + "tableFrom": "virtual_api_keys", + "tableTo": "secret", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.agent_scope": { + "name": "agent_scope", + "schema": "public", + "values": [ + "personal", + "team", + "org" + ] + }, + "public.oauth_refresh_error_enum": { + "name": "oauth_refresh_error_enum", + "schema": "public", + "values": [ + "refresh_failed", + "no_refresh_token" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/platform/backend/src/database/migrations/meta/_journal.json b/platform/backend/src/database/migrations/meta/_journal.json index d52f410980..d9c7673201 100644 --- a/platform/backend/src/database/migrations/meta/_journal.json +++ b/platform/backend/src/database/migrations/meta/_journal.json @@ -1121,6 +1121,13 @@ "when": 1772281943516, "tag": "0159_conscious_thanos", "breakpoints": true + }, + { + "idx": 160, + "version": "7", + "when": 1772499585378, + "tag": "0160_dark_piledriver", + "breakpoints": true } ] } \ No newline at end of file diff --git a/platform/backend/src/database/schemas/index.ts b/platform/backend/src/database/schemas/index.ts index 5d40016939..f064005aa2 100644 --- a/platform/backend/src/database/schemas/index.ts +++ b/platform/backend/src/database/schemas/index.ts @@ -28,6 +28,7 @@ export { default as limitsTable } from "./limit"; export { default as limitModelUsageTable } from "./limit-model-usage"; export { default as mcpCatalogLabelsTable } from "./mcp-catalog-label"; export { default as mcpHttpSessionsTable } from "./mcp-http-session"; +export { default as mcpRateLimitsTable } from "./mcp-rate-limit"; export { default as mcpServersTable } from "./mcp-server"; export { default as mcpServerInstallationRequestsTable } from "./mcp-server-installation-request"; export { default as mcpServerUsersTable } from "./mcp-server-user"; diff --git a/platform/backend/src/database/schemas/limit.ts b/platform/backend/src/database/schemas/limit.ts index 17a6b70855..454425e456 100644 --- a/platform/backend/src/database/schemas/limit.ts +++ b/platform/backend/src/database/schemas/limit.ts @@ -8,7 +8,7 @@ import { uuid, varchar, } from "drizzle-orm/pg-core"; -import type { LimitEntityType, LimitType } from "@/types"; +import type { LimitEntityType } from "@/types"; const limitsTable = pgTable( "limits", @@ -16,7 +16,6 @@ const limitsTable = pgTable( id: uuid("id").primaryKey().defaultRandom(), entityType: varchar("entity_type").$type().notNull(), entityId: text("entity_id").notNull(), - limitType: varchar("limit_type").$type().notNull(), limitValue: integer("limit_value").notNull(), mcpServerName: varchar("mcp_server_name", { length: 255 }), toolName: varchar("tool_name", { length: 255 }), @@ -35,7 +34,6 @@ const limitsTable = pgTable( }, (table) => ({ entityIdx: index("limits_entity_idx").on(table.entityType, table.entityId), - limitTypeIdx: index("limits_type_idx").on(table.limitType), }), ); diff --git a/platform/backend/src/database/schemas/mcp-rate-limit.ts b/platform/backend/src/database/schemas/mcp-rate-limit.ts new file mode 100644 index 0000000000..5744259f6d --- /dev/null +++ b/platform/backend/src/database/schemas/mcp-rate-limit.ts @@ -0,0 +1,39 @@ +import { + index, + integer, + pgTable, + timestamp, + uuid, + varchar, +} from "drizzle-orm/pg-core"; +import type { McpRateLimitType } from "@/types/mcp-rate-limit"; +import agentsTable from "./agent"; + +const mcpRateLimitsTable = pgTable( + "mcp_rate_limits", + { + id: uuid("id").primaryKey().defaultRandom(), + agentId: uuid("agent_id") + .notNull() + .references(() => agentsTable.id, { onDelete: "cascade" }), + limitType: varchar("limit_type").$type().notNull(), + mcpServerName: varchar("mcp_server_name", { length: 255 }).notNull(), + toolName: varchar("tool_name", { length: 255 }), + maxCalls: integer("max_calls").notNull(), + windowSeconds: integer("window_seconds").notNull(), + createdAt: timestamp("created_at", { mode: "date" }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { mode: "date" }) + .notNull() + .defaultNow() + .$onUpdate(() => new Date()), + }, + (table) => ({ + agentIdx: index("mcp_rate_limits_agent_idx").on(table.agentId), + agentServerIdx: index("mcp_rate_limits_agent_server_idx").on( + table.agentId, + table.mcpServerName, + ), + }), +); + +export default mcpRateLimitsTable; diff --git a/platform/backend/src/models/index.ts b/platform/backend/src/models/index.ts index be643e89a9..366063a78e 100644 --- a/platform/backend/src/models/index.ts +++ b/platform/backend/src/models/index.ts @@ -21,6 +21,7 @@ export { default as InvitationModel } from "./invitation"; export { default as LimitModel, LimitValidationService } from "./limit"; export { default as McpCatalogLabelModel } from "./mcp-catalog-label"; export { default as McpHttpSessionModel } from "./mcp-http-session"; +export { default as McpRateLimitModel } from "./mcp-rate-limit"; export { default as McpServerModel } from "./mcp-server"; export { default as McpServerInstallationRequestModel } from "./mcp-server-installation-request"; export { default as McpServerUserModel } from "./mcp-server-user"; diff --git a/platform/backend/src/models/interaction.ts b/platform/backend/src/models/interaction.ts index f80ead40d0..0ba4bcd843 100644 --- a/platform/backend/src/models/interaction.ts +++ b/platform/backend/src/models/interaction.ts @@ -734,17 +734,6 @@ class InteractionModel { } } - // Update profile-level token cost limits (if any exist) - updatePromises.push( - LimitModel.updateTokenLimitUsage( - "agent", - interaction.profileId, - model, - inputTokens, - outputTokens, - ), - ); - // Execute all updates in parallel await Promise.all(updatePromises); } catch (error) { diff --git a/platform/backend/src/models/limit.test.ts b/platform/backend/src/models/limit.test.ts index cb271ca634..2621d4d244 100644 --- a/platform/backend/src/models/limit.test.ts +++ b/platform/backend/src/models/limit.test.ts @@ -3,23 +3,19 @@ import LimitModel, { LimitValidationService } from "./limit"; describe("LimitModel", () => { describe("create", () => { - test("can create a token_cost limit for an agent", async ({ - makeAgent, - }) => { + test("can create a token_cost limit for a team", async ({ makeAgent }) => { const agent = await makeAgent({ name: "Test Agent" }); const limit = await LimitModel.create({ - entityType: "agent", + entityType: "team", entityId: agent.id, - limitType: "token_cost", limitValue: 1000000, model: ["claude-3-5-sonnet-20241022"], }); expect(limit.id).toBeDefined(); - expect(limit.entityType).toBe("agent"); + expect(limit.entityType).toBe("team"); expect(limit.entityId).toBe(agent.id); - expect(limit.limitType).toBe("token_cost"); expect(limit.limitValue).toBe(1000000); expect(limit.model).toEqual(["claude-3-5-sonnet-20241022"]); }); @@ -36,7 +32,6 @@ describe("LimitModel", () => { const limit = await LimitModel.create({ entityType: "team", entityId: team.id, - limitType: "token_cost", limitValue: 5000000, model: ["gpt-4"], }); @@ -54,7 +49,6 @@ describe("LimitModel", () => { const limit = await LimitModel.create({ entityType: "organization", entityId: org.id, - limitType: "token_cost", limitValue: 10000000, model: ["claude-3-5-sonnet-20241022"], }); @@ -70,9 +64,8 @@ describe("LimitModel", () => { const agent = await makeAgent({ name: "Test Agent" }); const limit = await LimitModel.create({ - entityType: "agent", + entityType: "team", entityId: agent.id, - limitType: "token_cost", limitValue: 1000000, model: ["gpt-4o", "claude-3-5-sonnet-20241022", "gemini-pro"], }); @@ -107,17 +100,15 @@ describe("LimitModel", () => { const agent2 = await makeAgent({ name: "Agent 2" }); await LimitModel.create({ - entityType: "agent", + entityType: "team", entityId: agent1.id, - limitType: "token_cost", limitValue: 1000000, model: ["claude-3-5-sonnet-20241022"], }); await LimitModel.create({ - entityType: "agent", + entityType: "team", entityId: agent2.id, - limitType: "token_cost", limitValue: 2000000, model: ["gpt-4"], }); @@ -134,9 +125,8 @@ describe("LimitModel", () => { const org = await makeOrganization(); await LimitModel.create({ - entityType: "agent", + entityType: "team", entityId: agent.id, - limitType: "token_cost", limitValue: 1000000, model: ["claude-3-5-sonnet-20241022"], }); @@ -144,14 +134,13 @@ describe("LimitModel", () => { await LimitModel.create({ entityType: "organization", entityId: org.id, - limitType: "token_cost", limitValue: 10000000, model: ["claude-3-5-sonnet-20241022"], }); - const agentLimits = await LimitModel.findAll("agent"); + const agentLimits = await LimitModel.findAll("team"); expect(agentLimits).toHaveLength(1); - expect(agentLimits[0].entityType).toBe("agent"); + expect(agentLimits[0].entityType).toBe("team"); const orgLimits = await LimitModel.findAll("organization"); expect(orgLimits).toHaveLength(1); @@ -163,17 +152,15 @@ describe("LimitModel", () => { const agent2 = await makeAgent({ name: "Agent 2" }); await LimitModel.create({ - entityType: "agent", + entityType: "team", entityId: agent1.id, - limitType: "token_cost", limitValue: 1000000, model: ["claude-3-5-sonnet-20241022"], }); await LimitModel.create({ - entityType: "agent", + entityType: "team", entityId: agent2.id, - limitType: "token_cost", limitValue: 2000000, model: ["gpt-4"], }); @@ -191,9 +178,8 @@ describe("LimitModel", () => { const org = await makeOrganization(); await LimitModel.create({ - entityType: "agent", + entityType: "team", entityId: agent.id, - limitType: "token_cost", limitValue: 1000000, model: ["claude-3-5-sonnet-20241022"], }); @@ -201,14 +187,13 @@ describe("LimitModel", () => { await LimitModel.create({ entityType: "organization", entityId: org.id, - limitType: "token_cost", limitValue: 10000000, model: ["claude-3-5-sonnet-20241022"], }); - const agentLimits = await LimitModel.findAll("agent", agent.id); + const agentLimits = await LimitModel.findAll("team", agent.id); expect(agentLimits).toHaveLength(1); - expect(agentLimits[0].entityType).toBe("agent"); + expect(agentLimits[0].entityType).toBe("team"); expect(agentLimits[0].entityId).toBe(agent.id); }); }); @@ -218,9 +203,8 @@ describe("LimitModel", () => { const agent = await makeAgent({ name: "Test Agent" }); const created = await LimitModel.create({ - entityType: "agent", + entityType: "team", entityId: agent.id, - limitType: "token_cost", limitValue: 1000000, model: ["claude-3-5-sonnet-20241022"], }); @@ -244,9 +228,8 @@ describe("LimitModel", () => { const agent = await makeAgent({ name: "Test Agent" }); const limit = await LimitModel.create({ - entityType: "agent", + entityType: "team", entityId: agent.id, - limitType: "token_cost", limitValue: 1000000, model: ["claude-3-5-sonnet-20241022"], }); @@ -276,9 +259,8 @@ describe("LimitModel", () => { const agent = await makeAgent({ name: "Test Agent" }); const limit = await LimitModel.create({ - entityType: "agent", + entityType: "team", entityId: agent.id, - limitType: "token_cost", limitValue: 1000000, model: ["claude-3-5-sonnet-20241022"], }); @@ -352,15 +334,14 @@ describe("LimitModel", () => { const agent = await makeAgent({ name: "Test Agent" }); const limit = await LimitModel.create({ - entityType: "agent", + entityType: "team", entityId: agent.id, - limitType: "token_cost", limitValue: 1000000, model: ["claude-3-5-sonnet-20241022"], }); await LimitModel.updateTokenLimitUsage( - "agent", + "team", agent.id, "claude-3-5-sonnet-20241022", 100, @@ -381,22 +362,21 @@ describe("LimitModel", () => { const agent = await makeAgent({ name: "Test Agent" }); const limit = await LimitModel.create({ - entityType: "agent", + entityType: "team", entityId: agent.id, - limitType: "token_cost", limitValue: 1000000, model: ["claude-3-5-sonnet-20241022"], }); await LimitModel.updateTokenLimitUsage( - "agent", + "team", agent.id, "claude-3-5-sonnet-20241022", 100, 200, ); await LimitModel.updateTokenLimitUsage( - "agent", + "team", agent.id, "claude-3-5-sonnet-20241022", 50, @@ -418,16 +398,15 @@ describe("LimitModel", () => { // Create limit with multiple models const limit = await LimitModel.create({ - entityType: "agent", + entityType: "team", entityId: agent.id, - limitType: "token_cost", limitValue: 1000000, model: ["gpt-4o", "claude-3-5-sonnet-20241022"], }); // Update usage for gpt-4o only await LimitModel.updateTokenLimitUsage( - "agent", + "team", agent.id, "gpt-4o", 100, @@ -457,24 +436,22 @@ describe("LimitModel", () => { // Create two limits, both containing gpt-4o const limit1 = await LimitModel.create({ - entityType: "agent", + entityType: "team", entityId: agent.id, - limitType: "token_cost", limitValue: 1000000, model: ["gpt-4o", "claude-3-5-sonnet-20241022"], }); const limit2 = await LimitModel.create({ - entityType: "agent", + entityType: "team", entityId: agent.id, - limitType: "token_cost", limitValue: 500000, model: ["gpt-4o", "gemini-pro"], }); // Update usage for gpt-4o await LimitModel.updateTokenLimitUsage( - "agent", + "team", agent.id, "gpt-4o", 100, @@ -502,9 +479,8 @@ describe("LimitModel", () => { const agent = await makeAgent({ name: "Test Agent" }); const limit = await LimitModel.create({ - entityType: "agent", + entityType: "team", entityId: agent.id, - limitType: "token_cost", limitValue: 1000000, model: ["gpt-4o"], }); @@ -524,23 +500,22 @@ describe("LimitModel", () => { const agent = await makeAgent({ name: "Test Agent" }); const limit = await LimitModel.create({ - entityType: "agent", + entityType: "team", entityId: agent.id, - limitType: "token_cost", limitValue: 1000000, model: ["gpt-4o", "claude-3-5-sonnet-20241022"], }); // Add usage for both models await LimitModel.updateTokenLimitUsage( - "agent", + "team", agent.id, "gpt-4o", 100000, 50000, ); await LimitModel.updateTokenLimitUsage( - "agent", + "team", agent.id, "claude-3-5-sonnet-20241022", 200000, @@ -581,7 +556,6 @@ describe("LimitModel", () => { await LimitModel.create({ entityType: "organization", entityId: org.id, - limitType: "token_cost", limitValue: 1000000, model: ["claude-3-5-sonnet-20241022"], }); @@ -604,7 +578,6 @@ describe("LimitModel", () => { const limit = await LimitModel.create({ entityType: "organization", entityId: org.id, - limitType: "token_cost", limitValue: 1000000, model: ["claude-3-5-sonnet-20241022"], }); @@ -631,7 +604,6 @@ describe("LimitModel", () => { const limit = await LimitModel.create({ entityType: "organization", entityId: org.id, - limitType: "token_cost", limitValue: 1000000, model: ["claude-3-5-sonnet-20241022"], }); @@ -658,16 +630,15 @@ describe("LimitModel", () => { const agent = await makeAgent({ name: "Test Agent" }); const limit = await LimitModel.create({ - entityType: "agent", + entityType: "team", entityId: agent.id, - limitType: "token_cost", limitValue: 1000000, model: ["claude-3-5-sonnet-20241022"], }); // Add some usage await LimitModel.updateTokenLimitUsage( - "agent", + "team", agent.id, "claude-3-5-sonnet-20241022", 100, @@ -694,21 +665,15 @@ describe("LimitModel", () => { const agent = await makeAgent({ name: "Test Agent" }); await LimitModel.create({ - entityType: "agent", + entityType: "team", entityId: agent.id, - limitType: "token_cost", limitValue: 1000000, model: ["claude-3-5-sonnet-20241022"], }); - const limits = await LimitModel.findLimitsForValidation( - "agent", - agent.id, - "token_cost", - ); + const limits = await LimitModel.findLimitsForValidation("team", agent.id); expect(limits).toHaveLength(1); - expect(limits[0].limitType).toBe("token_cost"); }); test("should not find limits for other entity types", async ({ @@ -719,9 +684,8 @@ describe("LimitModel", () => { const org = await makeOrganization(); await LimitModel.create({ - entityType: "agent", + entityType: "team", entityId: agent.id, - limitType: "token_cost", limitValue: 1000000, model: ["claude-3-5-sonnet-20241022"], }); @@ -729,7 +693,6 @@ describe("LimitModel", () => { const limits = await LimitModel.findLimitsForValidation( "organization", org.id, - "token_cost", ); expect(limits).toHaveLength(0); @@ -752,16 +715,6 @@ describe("LimitValidationService", () => { expect(result).toBeNull(); }); - test("should return refusal message when agent-level limit is exceeded", async () => { - // TODO: Set up test data with agent limit of 1000 tokens and current usage of 1000+ - const result = - await LimitValidationService.checkLimitsBeforeRequest("agent-123"); - - // For now, this will return null since no test data is set up - // Once test data is added, update this expectation - expect(result).toBeNull(); - }); - test("should return refusal message when team-level limit is exceeded", async () => { // TODO: Set up test data with team limit exceeded const result = @@ -776,14 +729,6 @@ describe("LimitValidationService", () => { expect(result).toBeNull(); }); - test("should check agent limits first (highest priority)", async () => { - // TODO: Set up conflicting limits where agent allows but team/org forbids - // Should return null (allowed) because agent limit takes priority - const result = - await LimitValidationService.checkLimitsBeforeRequest("agent-123"); - expect(result).toBeNull(); - }); - test("should return properly formatted refusal message", async () => { // TODO: Set up test data to trigger a limit violation // Then verify the format matches tool call blocking pattern diff --git a/platform/backend/src/models/limit.ts b/platform/backend/src/models/limit.ts index 686358520d..84739fcc72 100644 --- a/platform/backend/src/models/limit.ts +++ b/platform/backend/src/models/limit.ts @@ -1,13 +1,7 @@ import { and, eq, inArray, isNull, lt, or, type SQL, sql } from "drizzle-orm"; import db, { schema } from "@/database"; import logger from "@/logging"; -import type { - CreateLimit, - Limit, - LimitEntityType, - LimitType, - UpdateLimit, -} from "@/types"; +import type { CreateLimit, Limit, LimitEntityType, UpdateLimit } from "@/types"; import AgentTeamModel from "./agent-team"; import ModelModel from "./model"; @@ -21,12 +15,8 @@ class LimitModel { .values(data) .returning(); - // For token_cost limits, initialize model usage records - if ( - limit.limitType === "token_cost" && - limit.model && - Array.isArray(limit.model) - ) { + // Initialize model usage records + if (limit.model && Array.isArray(limit.model)) { await LimitModel.initializeModelUsageRecords(limit.id, limit.model); } @@ -60,12 +50,11 @@ class LimitModel { } /** - * Find all limits, optionally filtered by entity type, entity ID, and/or limit type + * Find all limits, optionally filtered by entity type and/or entity ID */ static async findAll( entityType?: LimitEntityType, entityId?: string, - limitType?: LimitType, ): Promise { const whereConditions: SQL[] = []; @@ -77,10 +66,6 @@ class LimitModel { whereConditions.push(eq(schema.limitsTable.entityId, entityId)); } - if (limitType) { - whereConditions.push(eq(schema.limitsTable.limitType, limitType)); - } - const whereClause = whereConditions.length > 0 ? and(...whereConditions) : undefined; @@ -247,7 +232,7 @@ class LimitModel { "[LimitModel] Update token limit usage", ); try { - // Find all token_cost limits for this entity that include this model + // Find all limits for this entity that include this model const limits = await db .select({ id: schema.limitsTable.id }) .from(schema.limitsTable) @@ -255,7 +240,6 @@ class LimitModel { and( eq(schema.limitsTable.entityType, entityType), eq(schema.limitsTable.entityId, entityId), - eq(schema.limitsTable.limitType, "token_cost"), // Check if model is in the JSONB array sql`${schema.limitsTable.model} ? ${model}`, ), @@ -344,8 +328,8 @@ class LimitModel { .where(eq(schema.limitsTable.id, id)) .returning(); - // Reset model usage records for token_cost limits - if (limit && limit.limitType === "token_cost") { + // Reset model usage records + if (limit) { await db .update(schema.limitModelUsageTable) .set({ @@ -366,7 +350,6 @@ class LimitModel { static async findLimitsForValidation( entityType: LimitEntityType, entityId: string, - limitType: LimitType = "token_cost", ): Promise { const limits = await db .select() @@ -375,7 +358,6 @@ class LimitModel { and( eq(schema.limitsTable.entityType, entityType), eq(schema.limitsTable.entityId, entityId), - eq(schema.limitsTable.limitType, limitType), ), ); @@ -455,7 +437,7 @@ class LimitModel { if (limitsToCleanup.length > 0) { logger.info( - `[LimitsCleanup] Limits to cleanup: ${limitsToCleanup.map((l) => `${l.id}(${l.limitType}:${l.lastCleanup ? l.lastCleanup.toISOString() : "never"})`).join(", ")}`, + `[LimitsCleanup] Limits to cleanup: ${limitsToCleanup.map((l) => `${l.id}(${l.lastCleanup ? l.lastCleanup.toISOString() : "never"})`).join(", ")}`, ); } @@ -463,7 +445,7 @@ class LimitModel { if (limitsToCleanup.length > 0) { for (const limit of limitsToCleanup) { logger.info( - `[LimitsCleanup] Cleaning up limit ${limit.id}: ${limit.limitType}, lastCleanup=${limit.lastCleanup ? limit.lastCleanup.toISOString() : "never"}`, + `[LimitsCleanup] Cleaning up limit ${limit.id}, lastCleanup=${limit.lastCleanup ? limit.lastCleanup.toISOString() : "never"}`, ); await LimitModel.resetLimitUsage(limit.id); @@ -544,20 +526,6 @@ export class LimitValidationService { await LimitModel.cleanupLimitsIfNeeded(organizationId); } - // Check agent-level limits first (highest priority) - logger.info( - `[LimitValidation] Checking agent-level limits for: ${agentId}`, - ); - const agentLimitViolation = - await LimitValidationService.checkEntityLimits("agent", agentId); - if (agentLimitViolation) { - logger.info( - `[LimitValidation] BLOCKED by agent-level limit for: ${agentId}`, - ); - return agentLimitViolation; - } - logger.info(`[LimitValidation] Agent-level limits OK for: ${agentId}`); - // Check team-level limits if (agentTeamIds.length > 0) { logger.info( @@ -659,7 +627,7 @@ export class LimitValidationService { * Check if current token cost usage has exceeded limits for a specific entity */ private static async checkEntityLimits( - entityType: "organization" | "team" | "agent", + entityType: LimitEntityType, entityId: string, ): Promise { try { @@ -669,16 +637,15 @@ export class LimitValidationService { const limits = await LimitModel.findLimitsForValidation( entityType, entityId, - "token_cost", ); logger.info( - `[LimitValidation] Found ${limits.length} token_cost limits for ${entityType} ${entityId}`, + `[LimitValidation] Found ${limits.length} limits for ${entityType} ${entityId}`, ); if (limits.length === 0) { logger.info( - `[LimitValidation] No token_cost limits found for ${entityType} ${entityId} - allowing`, + `[LimitValidation] No limits found for ${entityType} ${entityId} - allowing`, ); return null; } @@ -688,83 +655,73 @@ export class LimitValidationService { `[LimitValidation] Checking limit ${limit.id} for ${entityType} ${entityId}`, ); - // For token_cost limits, convert tokens to actual cost using token prices let comparisonValue = 0; - let limitDescription = "tokens"; let totalTokensIn = 0; let totalTokensOut = 0; - if (limit.limitType === "token_cost") { - try { - // Get per-model usage from limit_model_usage table - const modelUsages = await db - .select() - .from(schema.limitModelUsageTable) - .where(eq(schema.limitModelUsageTable.limitId, limit.id)); - - if (modelUsages.length === 0) { - logger.warn( - `[LimitValidation] No model usage records found for limit ${limit.id}`, + try { + // Get per-model usage from limit_model_usage table + const modelUsages = await db + .select() + .from(schema.limitModelUsageTable) + .where(eq(schema.limitModelUsageTable.limitId, limit.id)); + + if (modelUsages.length === 0) { + logger.warn( + `[LimitValidation] No model usage records found for limit ${limit.id}`, + ); + } else { + let totalCost = 0; + + for (const usage of modelUsages) { + totalTokensIn += usage.currentUsageTokensIn; + totalTokensOut += usage.currentUsageTokensOut; + + const modelEntry = await ModelModel.findByModelIdOnly( + usage.model, + ); + const pricing = ModelModel.getEffectivePricing( + modelEntry, + usage.model, ); - comparisonValue = 0; - } else { - let totalCost = 0; - - for (const usage of modelUsages) { - // Track total tokens for metadata - totalTokensIn += usage.currentUsageTokensIn; - totalTokensOut += usage.currentUsageTokensOut; - - // Look up model by modelId only — limit usage records don't store provider - const modelEntry = await ModelModel.findByModelIdOnly( - usage.model, - ); - const pricing = ModelModel.getEffectivePricing( - modelEntry, - usage.model, - ); - - const inputCost = - (usage.currentUsageTokensIn * - parseFloat(pricing.pricePerMillionInput)) / - 1000000; - const outputCost = - (usage.currentUsageTokensOut * - parseFloat(pricing.pricePerMillionOutput)) / - 1000000; - const modelCost = inputCost + outputCost; - - totalCost += modelCost; - - logger.debug( - `[LimitValidation] Model ${usage.model}: ${usage.currentUsageTokensIn} in + ${usage.currentUsageTokensOut} out = $${modelCost.toFixed(2)}`, - ); - } - - comparisonValue = totalCost; - limitDescription = "cost_dollars"; + + const inputCost = + (usage.currentUsageTokensIn * + parseFloat(pricing.pricePerMillionInput)) / + 1000000; + const outputCost = + (usage.currentUsageTokensOut * + parseFloat(pricing.pricePerMillionOutput)) / + 1000000; + const modelCost = inputCost + outputCost; + + totalCost += modelCost; logger.debug( - `[LimitValidation] Total cost for limit ${limit.id}: $${totalCost.toFixed(2)} across ${modelUsages.length} models`, + `[LimitValidation] Model ${usage.model}: ${usage.currentUsageTokensIn} in + ${usage.currentUsageTokensOut} out = $${modelCost.toFixed(2)}`, ); } - } catch (error) { - logger.error( - `[LimitValidation] Error calculating cost for limit ${limit.id}: ${error}`, + + comparisonValue = totalCost; + + logger.debug( + `[LimitValidation] Total cost for limit ${limit.id}: $${totalCost.toFixed(2)} across ${modelUsages.length} models`, ); } + } catch (error) { + logger.error( + `[LimitValidation] Error calculating cost for limit ${limit.id}: ${error}`, + ); } if (comparisonValue >= limit.limitValue) { logger.info( - `[LimitValidation] LIMIT EXCEEDED for ${entityType} ${entityId}: ${comparisonValue} ${limitDescription} >= ${limit.limitValue}`, + `[LimitValidation] LIMIT EXCEEDED for ${entityType} ${entityId}: $${comparisonValue.toFixed(2)} >= $${limit.limitValue}`, ); - // Calculate remaining based on the comparison type (tokens vs dollars) const remaining = Math.max(0, limit.limitValue - comparisonValue); const totalTokens = totalTokensIn + totalTokensOut; - // For metadata, use token counts for programmatic access const archestraMetadata = ` token_cost ${entityType} @@ -773,10 +730,7 @@ export class LimitValidationService { ${limit.limitValue} ${Math.max(0, limit.limitValue - totalTokens)}`; - // For user message, use appropriate units based on limit type - let contentMessage: string; - if (limitDescription === "cost_dollars") { - contentMessage = ` + const contentMessage = ` I cannot process this request because the ${entityType}-level token cost limit has been exceeded. Current usage: $${comparisonValue.toFixed(2)} @@ -784,26 +738,15 @@ Limit: $${limit.limitValue.toFixed(2)} Remaining: $${remaining.toFixed(2)} Please contact your administrator to increase the limit or wait for the usage to reset.`; - } else { - contentMessage = ` -I cannot process this request because the ${entityType}-level token cost limit has been exceeded. - -Current usage: ${totalTokens.toLocaleString()} tokens -Limit: ${limit.limitValue.toLocaleString()} tokens -Remaining: ${Math.max(0, limit.limitValue - totalTokens).toLocaleString()} tokens - -Please contact your administrator to increase the limit or wait for the usage to reset.`; - } const refusalMessage = `${archestraMetadata} ${contentMessage}`; return [refusalMessage, contentMessage]; - } else { - logger.info( - `[LimitValidation] Limit OK for ${entityType} ${entityId}: ${comparisonValue} < ${limit.limitValue}`, - ); } + logger.info( + `[LimitValidation] Limit OK for ${entityType} ${entityId}: $${comparisonValue.toFixed(2)} < $${limit.limitValue}`, + ); } logger.info( diff --git a/platform/backend/src/models/mcp-rate-limit.test.ts b/platform/backend/src/models/mcp-rate-limit.test.ts new file mode 100644 index 0000000000..53d3e8459e --- /dev/null +++ b/platform/backend/src/models/mcp-rate-limit.test.ts @@ -0,0 +1,359 @@ +import { describe, expect, test } from "@/test"; +import { CreateMcpRateLimitSchema } from "@/types"; +import McpRateLimitModel from "./mcp-rate-limit"; + +describe("McpRateLimitModel", () => { + describe("create", () => { + test("creates an mcp_server_calls limit for an agent", async ({ + makeAgent, + }) => { + const agent = await makeAgent({ name: "Rate Limit Agent" }); + + const limit = await McpRateLimitModel.create({ + agentId: agent.id, + limitType: "mcp_server_calls", + mcpServerName: "test-server", + maxCalls: 100, + windowSeconds: 3600, + }); + + expect(limit).toBeDefined(); + expect(limit.id).toBeDefined(); + expect(limit.agentId).toBe(agent.id); + expect(limit.limitType).toBe("mcp_server_calls"); + expect(limit.mcpServerName).toBe("test-server"); + expect(limit.toolName).toBeNull(); + expect(limit.maxCalls).toBe(100); + expect(limit.windowSeconds).toBe(3600); + expect(limit.createdAt).toBeInstanceOf(Date); + expect(limit.updatedAt).toBeInstanceOf(Date); + }); + + test("creates a tool_calls limit for an agent", async ({ makeAgent }) => { + const agent = await makeAgent({ name: "Tool Limit Agent" }); + + const limit = await McpRateLimitModel.create({ + agentId: agent.id, + limitType: "tool_calls", + mcpServerName: "test-server", + toolName: "dangerous-tool", + maxCalls: 10, + windowSeconds: 60, + }); + + expect(limit).toBeDefined(); + expect(limit.limitType).toBe("tool_calls"); + expect(limit.mcpServerName).toBe("test-server"); + expect(limit.toolName).toBe("dangerous-tool"); + expect(limit.maxCalls).toBe(10); + expect(limit.windowSeconds).toBe(60); + }); + + test("creates limits with different window presets", async ({ + makeAgent, + }) => { + const agent = await makeAgent({ name: "Window Test Agent" }); + const windows = [60, 3600, 86400, 604800, 2592000]; + + for (const windowSeconds of windows) { + const limit = await McpRateLimitModel.create({ + agentId: agent.id, + limitType: "mcp_server_calls", + mcpServerName: `server-${windowSeconds}`, + maxCalls: 100, + windowSeconds, + }); + + expect(limit.windowSeconds).toBe(windowSeconds); + } + }); + }); + + describe("findByAgentId", () => { + test("returns only limits for the specified agent", async ({ + makeAgent, + }) => { + const agent1 = await makeAgent({ name: "Agent 1" }); + const agent2 = await makeAgent({ name: "Agent 2" }); + + await McpRateLimitModel.create({ + agentId: agent1.id, + limitType: "mcp_server_calls", + mcpServerName: "server-1", + maxCalls: 100, + windowSeconds: 3600, + }); + await McpRateLimitModel.create({ + agentId: agent2.id, + limitType: "mcp_server_calls", + mcpServerName: "server-2", + maxCalls: 200, + windowSeconds: 3600, + }); + + const agent1Limits = await McpRateLimitModel.findByAgentId(agent1.id); + expect(agent1Limits).toHaveLength(1); + expect(agent1Limits[0].agentId).toBe(agent1.id); + expect(agent1Limits[0].mcpServerName).toBe("server-1"); + }); + + test("returns empty array for agent with no limits", async ({ + makeAgent, + }) => { + const agent = await makeAgent({ name: "No Limits Agent" }); + const limits = await McpRateLimitModel.findByAgentId(agent.id); + expect(limits).toHaveLength(0); + }); + }); + + describe("findAll", () => { + test("returns all limits with no filter", async ({ makeAgent }) => { + const agent = await makeAgent({ name: "Find All Agent" }); + + await McpRateLimitModel.create({ + agentId: agent.id, + limitType: "mcp_server_calls", + mcpServerName: "server-a", + maxCalls: 100, + windowSeconds: 3600, + }); + await McpRateLimitModel.create({ + agentId: agent.id, + limitType: "tool_calls", + mcpServerName: "server-a", + toolName: "tool-b", + maxCalls: 50, + windowSeconds: 60, + }); + + const limits = await McpRateLimitModel.findAll(); + expect(limits.length).toBeGreaterThanOrEqual(2); + }); + + test("filters by agentId", async ({ makeAgent }) => { + const agent1 = await makeAgent({ name: "Filter Agent 1" }); + const agent2 = await makeAgent({ name: "Filter Agent 2" }); + + await McpRateLimitModel.create({ + agentId: agent1.id, + limitType: "mcp_server_calls", + mcpServerName: "server", + maxCalls: 100, + windowSeconds: 3600, + }); + await McpRateLimitModel.create({ + agentId: agent2.id, + limitType: "mcp_server_calls", + mcpServerName: "server", + maxCalls: 200, + windowSeconds: 3600, + }); + + const filtered = await McpRateLimitModel.findAll({ + agentId: agent1.id, + }); + expect(filtered).toHaveLength(1); + expect(filtered[0].agentId).toBe(agent1.id); + }); + + test("filters by limitType", async ({ makeAgent }) => { + const agent = await makeAgent({ name: "Type Filter Agent" }); + + await McpRateLimitModel.create({ + agentId: agent.id, + limitType: "mcp_server_calls", + mcpServerName: "server", + maxCalls: 100, + windowSeconds: 3600, + }); + await McpRateLimitModel.create({ + agentId: agent.id, + limitType: "tool_calls", + mcpServerName: "server", + toolName: "tool", + maxCalls: 50, + windowSeconds: 60, + }); + + const filtered = await McpRateLimitModel.findAll({ + agentId: agent.id, + limitType: "tool_calls", + }); + expect(filtered).toHaveLength(1); + expect(filtered[0].limitType).toBe("tool_calls"); + }); + }); + + describe("findById", () => { + test("returns the limit when found", async ({ makeAgent }) => { + const agent = await makeAgent({ name: "Find By Id Agent" }); + + const created = await McpRateLimitModel.create({ + agentId: agent.id, + limitType: "mcp_server_calls", + mcpServerName: "server", + maxCalls: 100, + windowSeconds: 3600, + }); + + const found = await McpRateLimitModel.findById(created.id); + expect(found).not.toBeNull(); + expect(found?.id).toBe(created.id); + expect(found?.maxCalls).toBe(100); + }); + + test("returns null for non-existent ID", async () => { + const found = await McpRateLimitModel.findById( + "00000000-0000-0000-0000-000000000000", + ); + expect(found).toBeNull(); + }); + }); + + describe("patch", () => { + test("updates maxCalls", async ({ makeAgent }) => { + const agent = await makeAgent({ name: "Patch Agent" }); + + const limit = await McpRateLimitModel.create({ + agentId: agent.id, + limitType: "mcp_server_calls", + mcpServerName: "server", + maxCalls: 100, + windowSeconds: 3600, + }); + + const updated = await McpRateLimitModel.patch(limit.id, { + maxCalls: 200, + }); + expect(updated).not.toBeNull(); + expect(updated?.maxCalls).toBe(200); + }); + + test("updates windowSeconds", async ({ makeAgent }) => { + const agent = await makeAgent({ name: "Patch Window Agent" }); + + const limit = await McpRateLimitModel.create({ + agentId: agent.id, + limitType: "mcp_server_calls", + mcpServerName: "server", + maxCalls: 100, + windowSeconds: 3600, + }); + + const updated = await McpRateLimitModel.patch(limit.id, { + windowSeconds: 86400, + }); + expect(updated).not.toBeNull(); + expect(updated?.windowSeconds).toBe(86400); + }); + }); + + describe("delete", () => { + test("removes the limit and returns true", async ({ makeAgent }) => { + const agent = await makeAgent({ name: "Delete Agent" }); + + const limit = await McpRateLimitModel.create({ + agentId: agent.id, + limitType: "mcp_server_calls", + mcpServerName: "server", + maxCalls: 100, + windowSeconds: 3600, + }); + + const deleted = await McpRateLimitModel.delete(limit.id); + expect(deleted).toBe(true); + + const found = await McpRateLimitModel.findById(limit.id); + expect(found).toBeNull(); + }); + + test("returns false for non-existent ID", async () => { + const deleted = await McpRateLimitModel.delete( + "00000000-0000-0000-0000-000000000000", + ); + expect(deleted).toBe(false); + }); + }); + + describe("cascade delete", () => { + test("deleting the agent cascades to delete its limits", async ({ + makeAgent, + }) => { + const agent = await makeAgent({ name: "Cascade Agent" }); + + const limit = await McpRateLimitModel.create({ + agentId: agent.id, + limitType: "mcp_server_calls", + mcpServerName: "server", + maxCalls: 100, + windowSeconds: 3600, + }); + + // Delete the agent (using AgentModel) + const { AgentModel } = await import("@/models"); + await AgentModel.delete(agent.id); + + // The limit should be gone too + const found = await McpRateLimitModel.findById(limit.id); + expect(found).toBeNull(); + }); + }); +}); + +describe("CreateMcpRateLimitSchema validation", () => { + test("rejects tool_calls without toolName", () => { + const result = CreateMcpRateLimitSchema.safeParse({ + agentId: "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", + limitType: "tool_calls", + mcpServerName: "server", + maxCalls: 100, + windowSeconds: 3600, + }); + expect(result.success).toBe(false); + }); + + test("rejects maxCalls <= 0", () => { + const result = CreateMcpRateLimitSchema.safeParse({ + agentId: "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", + limitType: "mcp_server_calls", + mcpServerName: "server", + maxCalls: 0, + windowSeconds: 3600, + }); + expect(result.success).toBe(false); + }); + + test("rejects windowSeconds <= 0", () => { + const result = CreateMcpRateLimitSchema.safeParse({ + agentId: "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", + limitType: "mcp_server_calls", + mcpServerName: "server", + maxCalls: 100, + windowSeconds: 0, + }); + expect(result.success).toBe(false); + }); + + test("accepts valid mcp_server_calls config", () => { + const result = CreateMcpRateLimitSchema.safeParse({ + agentId: "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", + limitType: "mcp_server_calls", + mcpServerName: "server", + maxCalls: 100, + windowSeconds: 3600, + }); + expect(result.success).toBe(true); + }); + + test("accepts valid tool_calls config", () => { + const result = CreateMcpRateLimitSchema.safeParse({ + agentId: "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", + limitType: "tool_calls", + mcpServerName: "server", + toolName: "my-tool", + maxCalls: 50, + windowSeconds: 60, + }); + expect(result.success).toBe(true); + }); +}); diff --git a/platform/backend/src/models/mcp-rate-limit.ts b/platform/backend/src/models/mcp-rate-limit.ts new file mode 100644 index 0000000000..f95d89ddf8 --- /dev/null +++ b/platform/backend/src/models/mcp-rate-limit.ts @@ -0,0 +1,75 @@ +import { and, eq } from "drizzle-orm"; +import { getDb, schema } from "@/database"; +import type { + CreateMcpRateLimit, + McpRateLimitType, + UpdateMcpRateLimit, +} from "@/types"; + +export default class McpRateLimitModel { + static async create(data: CreateMcpRateLimit) { + const db = getDb(); + const [result] = await db + .insert(schema.mcpRateLimitsTable) + .values(data) + .returning(); + return result; + } + + static async findByAgentId(agentId: string) { + const db = getDb(); + return db + .select() + .from(schema.mcpRateLimitsTable) + .where(eq(schema.mcpRateLimitsTable.agentId, agentId)); + } + + static async findAll(filters?: { + agentId?: string; + limitType?: McpRateLimitType; + }) { + const db = getDb(); + const conditions = []; + if (filters?.agentId) { + conditions.push(eq(schema.mcpRateLimitsTable.agentId, filters.agentId)); + } + if (filters?.limitType) { + conditions.push( + eq(schema.mcpRateLimitsTable.limitType, filters.limitType), + ); + } + + return db + .select() + .from(schema.mcpRateLimitsTable) + .where(conditions.length > 0 ? and(...conditions) : undefined); + } + + static async findById(id: string) { + const db = getDb(); + const [result] = await db + .select() + .from(schema.mcpRateLimitsTable) + .where(eq(schema.mcpRateLimitsTable.id, id)); + return result ?? null; + } + + static async patch(id: string, data: Partial) { + const db = getDb(); + const [result] = await db + .update(schema.mcpRateLimitsTable) + .set(data) + .where(eq(schema.mcpRateLimitsTable.id, id)) + .returning(); + return result ?? null; + } + + static async delete(id: string) { + const db = getDb(); + const [result] = await db + .delete(schema.mcpRateLimitsTable) + .where(eq(schema.mcpRateLimitsTable.id, id)) + .returning({ id: schema.mcpRateLimitsTable.id }); + return !!result; + } +} diff --git a/platform/backend/src/observability/metrics/mcp.ts b/platform/backend/src/observability/metrics/mcp.ts index b0f31043eb..939f85429f 100644 --- a/platform/backend/src/observability/metrics/mcp.ts +++ b/platform/backend/src/observability/metrics/mcp.ts @@ -17,6 +17,7 @@ let mcpToolCallDuration: client.Histogram; let mcpToolCallsTotal: client.Counter; let mcpRequestSizeBytes: client.Histogram; let mcpResponseSizeBytes: client.Histogram; +let mcpRateLimitRejectionsTotal: client.Counter; // Deployment status gauge — one series per (server_name, state) combination. // Each server has exactly one active state at a time (value=1), with all other @@ -41,7 +42,8 @@ export function initializeMcpMetrics(labelKeys: string[]): void { mcpToolCallDuration && mcpToolCallsTotal && mcpRequestSizeBytes && - mcpResponseSizeBytes + mcpResponseSizeBytes && + mcpRateLimitRejectionsTotal ) { return; } @@ -62,6 +64,9 @@ export function initializeMcpMetrics(labelKeys: string[]): void { if (mcpResponseSizeBytes) { client.register.removeSingleMetric("mcp_response_size_bytes"); } + if (mcpRateLimitRejectionsTotal) { + client.register.removeSingleMetric("mcp_rate_limit_rejections_total"); + } } catch (_error) { // Ignore errors if metrics don't exist } @@ -106,6 +111,19 @@ export function initializeMcpMetrics(labelKeys: string[]): void { enableExemplars: true, }); + mcpRateLimitRejectionsTotal = new client.Counter({ + name: "mcp_rate_limit_rejections_total", + help: "Total MCP tool call rate limit rejections", + labelNames: [ + "agent_id", + "agent_name", + "mcp_server_name", + "tool_name", + "limit_type", + "entity_type", + ], + }); + logger.info( `MCP metrics initialized with ${nextLabelKeys.length} agent label keys: ${nextLabelKeys.join(", ")}`, ); @@ -199,6 +217,34 @@ export function reportMcpToolCall(params: { } } +/** + * Reports an MCP rate limit rejection + */ +export function reportMcpRateLimitRejection(params: { + agentId: string; + agentName: string; + mcpServerName: string; + toolName: string; + limitType: string; + entityType: string; +}): void { + if (!mcpRateLimitRejectionsTotal) { + logger.warn( + "MCP metrics not initialized, skipping rate limit rejection reporting", + ); + return; + } + + mcpRateLimitRejectionsTotal.inc({ + agent_id: params.agentId, + agent_name: params.agentName, + mcp_server_name: params.mcpServerName, + tool_name: params.toolName, + limit_type: params.limitType, + entity_type: params.entityType, + }); +} + /** * Update the mcp_server_deployment_status gauge from a map of server statuses. * Each server gets value=1 for its current state and value=0 for all other states. diff --git a/platform/backend/src/routes/index.ts b/platform/backend/src/routes/index.ts index dc75f51250..d7e27961c9 100644 --- a/platform/backend/src/routes/index.ts +++ b/platform/backend/src/routes/index.ts @@ -36,6 +36,7 @@ export { default as internalMcpCatalogRoutes } from "./internal-mcp-catalog"; export { default as invitationRoutes } from "./invitation"; export { default as limitsRoutes } from "./limits"; export { mcpGatewayRoutes } from "./mcp-gateway"; +export { default as mcpRateLimitsRoutes } from "./mcp-rate-limits"; export { default as mcpServerRoutes } from "./mcp-server"; export { default as mcpServerInstallationRequestRoutes } from "./mcp-server-installation-requests"; export { default as mcpToolCallRoutes } from "./mcp-tool-call"; diff --git a/platform/backend/src/routes/limits.ts b/platform/backend/src/routes/limits.ts index dbc8869974..ab11e44793 100644 --- a/platform/backend/src/routes/limits.ts +++ b/platform/backend/src/routes/limits.ts @@ -8,7 +8,6 @@ import { constructResponseSchema, DeleteObjectResponseSchema, LimitEntityTypeSchema, - LimitTypeSchema, LimitWithUsageSchema, SelectLimitSchema, UpdateLimitSchema, @@ -27,15 +26,11 @@ const limitsRoutes: FastifyPluginAsyncZod = async (fastify) => { querystring: z.object({ entityType: LimitEntityTypeSchema.optional(), entityId: z.string().optional(), - limitType: LimitTypeSchema.optional(), }), response: constructResponseSchema(z.array(LimitWithUsageSchema)), }, }, - async ( - { query: { entityType, entityId, limitType }, organizationId }, - reply, - ) => { + async ({ query: { entityType, entityId }, organizationId }, reply) => { // Cleanup limits if needed before fetching if (organizationId) { await LimitModel.cleanupLimitsIfNeeded(organizationId); @@ -48,18 +43,13 @@ const limitsRoutes: FastifyPluginAsyncZod = async (fastify) => { ); } - const limits = await LimitModel.findAll(entityType, entityId, limitType); + const limits = await LimitModel.findAll(entityType, entityId); - // Add per-model usage breakdown for token_cost limits + // Add per-model usage breakdown const limitsWithUsage = await Promise.all( limits.map(async (limit) => { - if (limit.limitType === "token_cost") { - const modelUsage = await LimitModel.getModelUsageBreakdown( - limit.id, - ); - return { ...limit, modelUsage }; - } - return limit; + const modelUsage = await LimitModel.getModelUsageBreakdown(limit.id); + return { ...limit, modelUsage }; }), ); diff --git a/platform/backend/src/routes/mcp-rate-limits.ts b/platform/backend/src/routes/mcp-rate-limits.ts new file mode 100644 index 0000000000..327a144ae5 --- /dev/null +++ b/platform/backend/src/routes/mcp-rate-limits.ts @@ -0,0 +1,138 @@ +import { RouteId } from "@shared"; +import type { FastifyPluginAsyncZod } from "fastify-type-provider-zod"; +import { z } from "zod"; +import { getMcpUsageForLimit } from "@/clients/mcp-rate-limit"; +import { McpRateLimitModel } from "@/models"; +import { + ApiError, + CreateMcpRateLimitSchema, + constructResponseSchema, + DeleteObjectResponseSchema, + McpRateLimitTypeSchema, + McpRateLimitWithUsageSchema, + SelectMcpRateLimitSchema, + UpdateMcpRateLimitSchema, + UuidIdSchema, +} from "@/types"; + +const mcpRateLimitsRoutes: FastifyPluginAsyncZod = async (fastify) => { + fastify.get( + "/api/mcp-rate-limits", + { + schema: { + operationId: RouteId.GetMcpRateLimits, + description: + "Get all MCP rate limits with optional filtering and live usage", + tags: ["MCP Rate Limits"], + querystring: z.object({ + agentId: z.string().optional(), + limitType: McpRateLimitTypeSchema.optional(), + }), + response: constructResponseSchema(z.array(McpRateLimitWithUsageSchema)), + }, + }, + async ({ query: { agentId, limitType } }, reply) => { + const limits = await McpRateLimitModel.findAll({ agentId, limitType }); + + const limitsWithUsage = await Promise.all( + limits.map(async (limit) => { + const mcpUsage = await getMcpUsageForLimit(limit.id); + return { ...limit, mcpUsage }; + }), + ); + + return reply.send(limitsWithUsage); + }, + ); + + fastify.post( + "/api/mcp-rate-limits", + { + schema: { + operationId: RouteId.CreateMcpRateLimit, + description: "Create a new MCP rate limit", + tags: ["MCP Rate Limits"], + body: CreateMcpRateLimitSchema, + response: constructResponseSchema(SelectMcpRateLimitSchema), + }, + }, + async ({ body }, reply) => { + return reply.send(await McpRateLimitModel.create(body)); + }, + ); + + fastify.get( + "/api/mcp-rate-limits/:id", + { + schema: { + operationId: RouteId.GetMcpRateLimit, + description: "Get an MCP rate limit by ID", + tags: ["MCP Rate Limits"], + params: z.object({ + id: UuidIdSchema, + }), + response: constructResponseSchema(SelectMcpRateLimitSchema), + }, + }, + async ({ params: { id } }, reply) => { + const limit = await McpRateLimitModel.findById(id); + + if (!limit) { + throw new ApiError(404, "MCP rate limit not found"); + } + + return reply.send(limit); + }, + ); + + fastify.patch( + "/api/mcp-rate-limits/:id", + { + schema: { + operationId: RouteId.UpdateMcpRateLimit, + description: "Update an MCP rate limit", + tags: ["MCP Rate Limits"], + params: z.object({ + id: UuidIdSchema, + }), + body: UpdateMcpRateLimitSchema.partial(), + response: constructResponseSchema(SelectMcpRateLimitSchema), + }, + }, + async ({ params: { id }, body }, reply) => { + const limit = await McpRateLimitModel.patch(id, body); + + if (!limit) { + throw new ApiError(404, "MCP rate limit not found"); + } + + return reply.send(limit); + }, + ); + + fastify.delete( + "/api/mcp-rate-limits/:id", + { + schema: { + operationId: RouteId.DeleteMcpRateLimit, + description: "Delete an MCP rate limit", + tags: ["MCP Rate Limits"], + params: z.object({ + id: UuidIdSchema, + }), + response: constructResponseSchema(DeleteObjectResponseSchema), + }, + }, + async ({ params: { id } }, reply) => { + const deleted = await McpRateLimitModel.delete(id); + + if (!deleted) { + throw new ApiError(404, "MCP rate limit not found"); + } + + return reply.send({ success: true }); + }, + ); +}; + +export default mcpRateLimitsRoutes; diff --git a/platform/backend/src/standalone-scripts/codegen-access-control-docs.ts b/platform/backend/src/standalone-scripts/codegen-access-control-docs.ts index 912bf6796b..cb58651341 100644 --- a/platform/backend/src/standalone-scripts/codegen-access-control-docs.ts +++ b/platform/backend/src/standalone-scripts/codegen-access-control-docs.ts @@ -31,7 +31,8 @@ function getResourceDescription(resource: Resource): string { mcpToolCall: "Tool execution logs and results", team: "Teams for organizing members and access control", conversation: "Chat conversations with automation experts", - limit: "Usage limits and quotas", + llmTokenLimit: "LLM token usage limits and quotas", + mcpRateLimit: "MCP rate limits for tool calls", llmModels: "LLM models and pricing configuration", chatSettings: "Chat feature configuration and settings", ac: "RBAC roles", @@ -88,8 +89,7 @@ function generateCustomRolesPermissionsTable(): string { for (const action of actions) { const permission = `${resource}:${action}`; const actionDesc = getActionDescription(action); - // don't lowercase "RBAC roles" - const fullDescription = `${actionDesc} ${resource === "ac" ? description : description.toLowerCase()}`; + const fullDescription = `${actionDesc} ${description}`; table += `| \`${permission}\` | ${fullDescription} |\n`; } diff --git a/platform/backend/src/types/index.ts b/platform/backend/src/types/index.ts index b6eaa5f1c8..15512a712a 100644 --- a/platform/backend/src/types/index.ts +++ b/platform/backend/src/types/index.ts @@ -18,6 +18,7 @@ export * from "./limit"; export * from "./llm-provider"; export * from "./llm-providers"; export * from "./mcp-catalog"; +export * from "./mcp-rate-limit"; export * from "./mcp-server"; export * from "./mcp-server-installation-request"; export * from "./mcp-tool-call"; diff --git a/platform/backend/src/types/limit.ts b/platform/backend/src/types/limit.ts index 223896eeef..58c3132d2d 100644 --- a/platform/backend/src/types/limit.ts +++ b/platform/backend/src/types/limit.ts @@ -9,36 +9,22 @@ import { schema } from "@/database"; /** * Entity types that can have limits applied */ -// TODO: need to make a database migration to migrate agent -> profile -export const LimitEntityTypeSchema = z.enum(["organization", "team", "agent"]); +export const LimitEntityTypeSchema = z.enum(["organization", "team"]); export type LimitEntityType = z.infer; -/** - * Types of limits that can be applied - */ -export const LimitTypeSchema = z.enum([ - "token_cost", - "mcp_server_calls", - "tool_calls", -]); -export type LimitType = z.infer; - /** * Base database schema derived from Drizzle */ export const SelectLimitSchema = createSelectSchema(schema.limitsTable, { entityType: LimitEntityTypeSchema, - limitType: LimitTypeSchema, model: z.array(z.string()).nullable().optional(), }); export const InsertLimitSchema = createInsertSchema(schema.limitsTable, { entityType: LimitEntityTypeSchema, - limitType: LimitTypeSchema, model: z.array(z.string()).nullable().optional(), }); export const UpdateLimitSchema = createUpdateSchema(schema.limitsTable, { entityType: LimitEntityTypeSchema, - limitType: LimitTypeSchema, model: z.array(z.string()).nullable().optional(), }).omit({ id: true, @@ -55,41 +41,18 @@ export const CreateLimitSchema = InsertLimitSchema.omit({ updatedAt: true, }).refine( (data) => { - // Validation: mcp_server_calls requires mcpServerName and should not have model - if (data.limitType === "mcp_server_calls") { - if (!data.mcpServerName) { - return false; - } - if (data.model) { - return false; - } - } - // Validation: tool_calls requires both mcpServerName and toolName and should not have model - if (data.limitType === "tool_calls") { - if (!data.mcpServerName || !data.toolName) { - return false; - } - if (data.model) { - return false; - } + // Requires non-empty model array and should not have mcp or tool specificity + if (!data.model || !Array.isArray(data.model) || data.model.length === 0) { + return false; } - // Validation: token_cost requires non-empty model array and should not have mcp or tool specificity - if (data.limitType === "token_cost") { - if ( - !data.model || - !Array.isArray(data.model) || - data.model.length === 0 - ) { - return false; - } - if (data.mcpServerName || data.toolName) { - return false; - } + if (data.mcpServerName || data.toolName) { + return false; } return true; }, { - message: "Invalid limit configuration for the specified limit type", + message: + "A non-empty model array is required. mcpServerName and toolName are not allowed.", }, ); diff --git a/platform/backend/src/types/mcp-rate-limit.ts b/platform/backend/src/types/mcp-rate-limit.ts new file mode 100644 index 0000000000..40abc9ab83 --- /dev/null +++ b/platform/backend/src/types/mcp-rate-limit.ts @@ -0,0 +1,83 @@ +import { + createInsertSchema, + createSelectSchema, + createUpdateSchema, +} from "drizzle-zod"; +import { z } from "zod"; +import { schema } from "@/database"; + +export const McpRateLimitTypeSchema = z.enum([ + "mcp_server_calls", + "tool_calls", +]); +export type McpRateLimitType = z.infer; + +/** + * Common rate limit window presets in seconds + */ +export const RateLimitWindowSeconds = { + OneMinute: 60, + OneHour: 3_600, + OneDay: 86_400, + OneWeek: 604_800, + OneMonth: 2_592_000, +} as const; + +export const SelectMcpRateLimitSchema = createSelectSchema( + schema.mcpRateLimitsTable, + { + limitType: McpRateLimitTypeSchema, + }, +); + +export const InsertMcpRateLimitSchema = createInsertSchema( + schema.mcpRateLimitsTable, + { + limitType: McpRateLimitTypeSchema, + }, +); + +export const UpdateMcpRateLimitSchema = createUpdateSchema( + schema.mcpRateLimitsTable, + { + limitType: McpRateLimitTypeSchema, + }, +).omit({ + id: true, + agentId: true, + createdAt: true, + updatedAt: true, +}); + +export const CreateMcpRateLimitSchema = InsertMcpRateLimitSchema.omit({ + id: true, + createdAt: true, + updatedAt: true, +}).refine( + (data) => { + if (data.limitType === "tool_calls" && !data.toolName) { + return false; + } + if (data.maxCalls <= 0) { + return false; + } + if (data.windowSeconds <= 0) { + return false; + } + return true; + }, + { + message: + "Invalid MCP rate limit configuration: tool_calls requires toolName, maxCalls and windowSeconds must be > 0", + }, +); + +export const McpRateLimitWithUsageSchema = SelectMcpRateLimitSchema.extend({ + mcpUsage: z.number().optional(), +}); + +export type McpRateLimit = z.infer; +export type InsertMcpRateLimit = z.infer; +export type CreateMcpRateLimit = z.infer; +export type UpdateMcpRateLimit = z.infer; +export type McpRateLimitWithUsage = z.infer; diff --git a/platform/dev/grafana/dashboards/mcp-monitoring.json b/platform/dev/grafana/dashboards/mcp-monitoring.json index 94814ab2cf..b47cd3e474 100644 --- a/platform/dev/grafana/dashboards/mcp-monitoring.json +++ b/platform/dev/grafana/dashboards/mcp-monitoring.json @@ -2247,6 +2247,201 @@ ], "transparent": true, "type": "table" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 65 + }, + "id": 105, + "panels": [], + "title": "Rate Limits", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics_datasource}" + }, + "description": "Rate of MCP tool calls rejected by rate limits, grouped by MCP server", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 66 + }, + "id": 106, + "options": { + "legend": { + "calcs": ["lastNotNull"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics_datasource}" + }, + "expr": "sum by (mcp_server_name) (rate(mcp_rate_limit_rejections_total{agent_name=~\"$agent_name\", mcp_server_name=~\"$mcp_server_name\", tool_name=~\"$tool_name\"}[$__rate_interval]))", + "legendFormat": "{{mcp_server_name}}", + "refId": "A" + } + ], + "title": "Rate Limit Rejections by MCP Server", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics_datasource}" + }, + "description": "Rate of MCP tool calls rejected by rate limits, grouped by tool name", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 66 + }, + "id": 107, + "options": { + "legend": { + "calcs": ["lastNotNull"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics_datasource}" + }, + "expr": "sum by (tool_name) (rate(mcp_rate_limit_rejections_total{agent_name=~\"$agent_name\", mcp_server_name=~\"$mcp_server_name\", tool_name=~\"$tool_name\"}[$__rate_interval]))", + "legendFormat": "{{tool_name}}", + "refId": "A" + } + ], + "title": "Rate Limit Rejections by Tool", + "type": "timeseries" } ], "preload": false, diff --git a/platform/e2e-tests/tests/api/fixtures.ts b/platform/e2e-tests/tests/api/fixtures.ts index a309e4efac..3c8e048912 100644 --- a/platform/e2e-tests/tests/api/fixtures.ts +++ b/platform/e2e-tests/tests/api/fixtures.ts @@ -719,7 +719,7 @@ const deleteOptimizationRule = async ( }); /** - * Create a limit (token cost, mcp_server_calls, or tool_calls) + * Create an LLM token limit * (authnz is handled by the authenticated session) */ const createLimit = async ( @@ -727,11 +727,8 @@ const createLimit = async ( limit: { entityType: "organization" | "team" | "agent"; entityId: string; - limitType: "token_cost" | "mcp_server_calls" | "tool_calls"; limitValue: number; model?: string[]; - mcpServerName?: string; - toolName?: string; }, ) => makeApiRequest({ diff --git a/platform/e2e-tests/tests/api/llm-proxy/token-cost-limits.spec.ts b/platform/e2e-tests/tests/api/llm-proxy/token-cost-limits.spec.ts index 1c88a67019..c7d4fa6a4a 100644 --- a/platform/e2e-tests/tests/api/llm-proxy/token-cost-limits.spec.ts +++ b/platform/e2e-tests/tests/api/llm-proxy/token-cost-limits.spec.ts @@ -446,7 +446,6 @@ for (const config of testConfigs) { const limitResponse = await createLimit(request, { entityType: "agent", entityId: profileId, - limitType: "token_cost", limitValue: 2, model: [config.modelName], }); @@ -604,7 +603,6 @@ for (const config of testConfigs) { const limitResponse = await createLimit(request, { entityType: "agent", entityId: profileId, - limitType: "token_cost", limitValue: 1000, model: [config.modelName], }); diff --git a/platform/e2e-tests/tests/api/mcp-gateway-rate-limits.spec.ts b/platform/e2e-tests/tests/api/mcp-gateway-rate-limits.spec.ts new file mode 100644 index 0000000000..f6446e8856 --- /dev/null +++ b/platform/e2e-tests/tests/api/mcp-gateway-rate-limits.spec.ts @@ -0,0 +1,544 @@ +import crypto from "node:crypto"; +import { + MCP_GATEWAY_URL_SUFFIX, + MCP_SERVER_TOOL_NAME_SEPARATOR, +} from "../../consts"; +import { expect, test } from "./fixtures"; +import { + assignArchestraToolsToProfile, + callMcpTool, + getOrgTokenForProfile, + initializeMcpSession, + makeApiRequest, +} from "./mcp-gateway-utils"; + +/** + * MCP Gateway Rate Limit Tests + * + * Tests that MCP rate limits (mcp_server_calls and tool_calls) are enforced + * when calling tools via the MCP Gateway. + * + * Uses the built-in archestra__whoami tool which requires no external dependencies. + */ + +const ARCHESTRA_MCP_SERVER_NAME = "archestra"; +const WHOAMI_TOOL_NAME = `${ARCHESTRA_MCP_SERVER_NAME}${MCP_SERVER_TOOL_NAME_SEPARATOR}whoami`; + +test.describe("MCP Gateway - Rate Limits", () => { + let profileId: string; + let archestraToken: string; + + test.beforeAll(async ({ request, createAgent }) => { + // Create a dedicated profile for rate limit testing + const uniqueSuffix = crypto.randomUUID().slice(0, 8); + const createResponse = await createAgent( + request, + `MCP Rate Limit Test ${uniqueSuffix}`, + ); + const profile = await createResponse.json(); + profileId = profile.id; + + // Assign Archestra tools to the profile (required for tools/call to work) + await assignArchestraToolsToProfile(request, profileId); + + // Get org token for MCP Gateway authentication + archestraToken = await getOrgTokenForProfile(request); + + // Initialize MCP session (stateless, but verifies profile is accessible) + await initializeMcpSession(request, { + profileId, + token: archestraToken, + }); + }); + + test.afterAll(async ({ request, deleteAgent }) => { + await deleteAgent(request, profileId); + }); + + test("mcp_server_calls limit blocks calls after threshold is exceeded", async ({ + request, + }) => { + const limitThreshold = 2; + const windowSeconds = 60; // 1 minute + + // Step 1: Create an mcp_server_calls rate limit with a low threshold + const createLimitResponse = await makeApiRequest({ + request, + method: "post", + urlSuffix: "/api/mcp-rate-limits", + data: { + agentId: profileId, + limitType: "mcp_server_calls", + maxCalls: limitThreshold, + mcpServerName: ARCHESTRA_MCP_SERVER_NAME, + windowSeconds, + }, + }); + expect(createLimitResponse.status()).toBe(200); + const limit = await createLimitResponse.json(); + const limitId = limit.id; + + try { + // Step 2: Make calls within the limit - these should succeed + for (let i = 0; i < limitThreshold; i++) { + const result = await callMcpTool(request, { + profileId, + token: archestraToken, + toolName: WHOAMI_TOOL_NAME, + }); + + expect(result.content).toBeDefined(); + expect(result.content.length).toBeGreaterThan(0); + const textContent = result.content.find( + // biome-ignore lint/suspicious/noExplicitAny: for a test it's okay.. + (c: any) => c.type === "text", + ); + expect(textContent).toBeDefined(); + expect(textContent?.text).toContain(profileId); + } + + // Step 3: The next call should be rate-limited + // callMcpTool throws on JSON-RPC errors, but rate limit errors are returned + // as tool results with isError: true, so we need to use the raw API + const rateLimitedResponse = await makeApiRequest({ + request, + method: "post", + urlSuffix: `${MCP_GATEWAY_URL_SUFFIX}/${profileId}`, + headers: { + Authorization: `Bearer ${archestraToken}`, + "Content-Type": "application/json", + Accept: "application/json, text/event-stream", + }, + data: { + jsonrpc: "2.0", + id: 3, + method: "tools/call", + params: { + name: WHOAMI_TOOL_NAME, + arguments: {}, + }, + }, + }); + + expect(rateLimitedResponse.status()).toBe(200); + const rateLimitedResult = await rateLimitedResponse.json(); + + // The rate limit error is returned as a tool result with isError: true + expect(rateLimitedResult.result).toBeDefined(); + expect(rateLimitedResult.result.content).toBeDefined(); + expect(rateLimitedResult.result.isError).toBe(true); + + const errorContent = rateLimitedResult.result.content.find( + // biome-ignore lint/suspicious/noExplicitAny: for a test it's okay.. + (c: any) => c.type === "text", + ); + expect(errorContent).toBeDefined(); + expect(errorContent.text).toContain("Rate limit exceeded"); + expect(errorContent.text).toContain(ARCHESTRA_MCP_SERVER_NAME); + expect(errorContent.text).toContain(`${limitThreshold} calls per`); + expect(errorContent.text).toContain("Try again in approximately"); + } finally { + // Step 4: Clean up the rate limit + await makeApiRequest({ + request, + method: "delete", + urlSuffix: `/api/mcp-rate-limits/${limitId}`, + }); + } + }); + + test("tool_calls limit blocks calls after threshold for a specific tool", async ({ + request, + }) => { + const limitThreshold = 2; + const windowSeconds = 60; // 1 minute + + // Step 1: Create a tool_calls rate limit targeting the specific whoami tool + const createLimitResponse = await makeApiRequest({ + request, + method: "post", + urlSuffix: "/api/mcp-rate-limits", + data: { + agentId: profileId, + limitType: "tool_calls", + maxCalls: limitThreshold, + mcpServerName: ARCHESTRA_MCP_SERVER_NAME, + toolName: WHOAMI_TOOL_NAME, + windowSeconds, + }, + }); + expect(createLimitResponse.status()).toBe(200); + const limit = await createLimitResponse.json(); + const limitId = limit.id; + + try { + // Step 2: Make calls within the limit - these should succeed + for (let i = 0; i < limitThreshold; i++) { + const result = await callMcpTool(request, { + profileId, + token: archestraToken, + toolName: WHOAMI_TOOL_NAME, + }); + + expect(result.content).toBeDefined(); + expect(result.content.length).toBeGreaterThan(0); + const textContent = result.content.find( + // biome-ignore lint/suspicious/noExplicitAny: for a test it's okay.. + (c: any) => c.type === "text", + ); + expect(textContent).toBeDefined(); + expect(textContent?.text).toContain(profileId); + } + + // Step 3: The next call should be rate-limited + const rateLimitedResponse = await makeApiRequest({ + request, + method: "post", + urlSuffix: `${MCP_GATEWAY_URL_SUFFIX}/${profileId}`, + headers: { + Authorization: `Bearer ${archestraToken}`, + "Content-Type": "application/json", + Accept: "application/json, text/event-stream", + }, + data: { + jsonrpc: "2.0", + id: 3, + method: "tools/call", + params: { + name: WHOAMI_TOOL_NAME, + arguments: {}, + }, + }, + }); + + expect(rateLimitedResponse.status()).toBe(200); + const rateLimitedResult = await rateLimitedResponse.json(); + + // Verify the rate limit error for tool_calls includes the tool name + expect(rateLimitedResult.result).toBeDefined(); + expect(rateLimitedResult.result.isError).toBe(true); + + const errorContent = rateLimitedResult.result.content.find( + // biome-ignore lint/suspicious/noExplicitAny: for a test it's okay.. + (c: any) => c.type === "text", + ); + expect(errorContent).toBeDefined(); + expect(errorContent.text).toContain("Rate limit exceeded"); + expect(errorContent.text).toContain(`tool '${WHOAMI_TOOL_NAME}'`); + expect(errorContent.text).toContain(ARCHESTRA_MCP_SERVER_NAME); + } finally { + // Step 4: Clean up the rate limit + await makeApiRequest({ + request, + method: "delete", + urlSuffix: `/api/mcp-rate-limits/${limitId}`, + }); + } + }); + + test("calls succeed normally when no rate limit is configured", async ({ + request, + }) => { + // Verify that without any rate limit, the tool call succeeds + const result = await callMcpTool(request, { + profileId, + token: archestraToken, + toolName: WHOAMI_TOOL_NAME, + }); + + expect(result.content).toBeDefined(); + expect(result.content.length).toBeGreaterThan(0); + const textContent = result.content.find( + // biome-ignore lint/suspicious/noExplicitAny: for a test it's okay.. + (c: any) => c.type === "text", + ); + expect(textContent).toBeDefined(); + expect(textContent?.text).toContain(profileId); + }); + + test("rate limit usage is reflected in the MCP rate limits API response", async ({ + request, + }) => { + const limitThreshold = 5; + const windowSeconds = 60; // 1 minute + + // Step 1: Create an mcp_server_calls rate limit + const createLimitResponse = await makeApiRequest({ + request, + method: "post", + urlSuffix: "/api/mcp-rate-limits", + data: { + agentId: profileId, + limitType: "mcp_server_calls", + maxCalls: limitThreshold, + mcpServerName: ARCHESTRA_MCP_SERVER_NAME, + windowSeconds, + }, + }); + expect(createLimitResponse.status()).toBe(200); + const limit = await createLimitResponse.json(); + const limitId = limit.id; + + try { + // Step 2: Make a single call to increment usage + await callMcpTool(request, { + profileId, + token: archestraToken, + toolName: WHOAMI_TOOL_NAME, + }); + + // Step 3: Check the MCP rate limits API to verify usage is tracked + const limitsResponse = await makeApiRequest({ + request, + method: "get", + urlSuffix: `/api/mcp-rate-limits?agentId=${profileId}`, + }); + expect(limitsResponse.status()).toBe(200); + const limits = await limitsResponse.json(); + + const createdLimit = limits.find( + // biome-ignore lint/suspicious/noExplicitAny: for a test it's okay.. + (l: any) => l.id === limitId, + ); + expect(createdLimit).toBeDefined(); + expect(createdLimit.limitType).toBe("mcp_server_calls"); + expect(createdLimit.mcpServerName).toBe(ARCHESTRA_MCP_SERVER_NAME); + // mcpUsage should be at least 1 (may be higher if other tests ran in parallel) + expect(createdLimit.mcpUsage).toBeGreaterThanOrEqual(1); + } finally { + // Step 4: Clean up the rate limit + await makeApiRequest({ + request, + method: "delete", + urlSuffix: `/api/mcp-rate-limits/${limitId}`, + }); + } + }); +}); + +test.describe("MCP Rate Limits - CRUD API", () => { + let profileId: string; + + test.beforeAll(async ({ request, createAgent }) => { + const uniqueSuffix = crypto.randomUUID().slice(0, 8); + const createResponse = await createAgent( + request, + `MCP Rate Limit CRUD Test ${uniqueSuffix}`, + ); + const profile = await createResponse.json(); + profileId = profile.id; + }); + + test.afterAll(async ({ request, deleteAgent }) => { + await deleteAgent(request, profileId); + }); + + test("creates an mcp_server_calls limit", async ({ request }) => { + const response = await makeApiRequest({ + request, + method: "post", + urlSuffix: "/api/mcp-rate-limits", + data: { + agentId: profileId, + limitType: "mcp_server_calls", + maxCalls: 100, + mcpServerName: "test-server", + windowSeconds: 3600, + }, + }); + expect(response.status()).toBe(200); + const limit = await response.json(); + expect(limit.id).toBeDefined(); + expect(limit.agentId).toBe(profileId); + expect(limit.limitType).toBe("mcp_server_calls"); + expect(limit.maxCalls).toBe(100); + expect(limit.mcpServerName).toBe("test-server"); + expect(limit.windowSeconds).toBe(3600); + + // Cleanup + await makeApiRequest({ + request, + method: "delete", + urlSuffix: `/api/mcp-rate-limits/${limit.id}`, + }); + }); + + test("creates a tool_calls limit with toolName", async ({ request }) => { + const response = await makeApiRequest({ + request, + method: "post", + urlSuffix: "/api/mcp-rate-limits", + data: { + agentId: profileId, + limitType: "tool_calls", + maxCalls: 50, + mcpServerName: "test-server", + toolName: "my-tool", + windowSeconds: 60, + }, + }); + expect(response.status()).toBe(200); + const limit = await response.json(); + expect(limit.toolName).toBe("my-tool"); + expect(limit.limitType).toBe("tool_calls"); + + // Cleanup + await makeApiRequest({ + request, + method: "delete", + urlSuffix: `/api/mcp-rate-limits/${limit.id}`, + }); + }); + + test("lists limits filtered by agentId with mcpUsage field", async ({ + request, + }) => { + // Create two limits + const limit1Resp = await makeApiRequest({ + request, + method: "post", + urlSuffix: "/api/mcp-rate-limits", + data: { + agentId: profileId, + limitType: "mcp_server_calls", + maxCalls: 100, + mcpServerName: "server-a", + windowSeconds: 3600, + }, + }); + const limit1 = await limit1Resp.json(); + + const limit2Resp = await makeApiRequest({ + request, + method: "post", + urlSuffix: "/api/mcp-rate-limits", + data: { + agentId: profileId, + limitType: "tool_calls", + maxCalls: 50, + mcpServerName: "server-a", + toolName: "tool-a", + windowSeconds: 60, + }, + }); + const limit2 = await limit2Resp.json(); + + try { + const listResponse = await makeApiRequest({ + request, + method: "get", + urlSuffix: `/api/mcp-rate-limits?agentId=${profileId}`, + }); + expect(listResponse.status()).toBe(200); + const limits = await listResponse.json(); + + expect(limits.length).toBeGreaterThanOrEqual(2); + // biome-ignore lint/suspicious/noExplicitAny: for a test it's okay.. + const ids = limits.map((l: any) => l.id); + expect(ids).toContain(limit1.id); + expect(ids).toContain(limit2.id); + + // All limits should have mcpUsage field + // biome-ignore lint/suspicious/noExplicitAny: for a test it's okay.. + for (const limit of limits as any[]) { + expect(limit.mcpUsage).toBeDefined(); + expect(typeof limit.mcpUsage).toBe("number"); + } + } finally { + await makeApiRequest({ + request, + method: "delete", + urlSuffix: `/api/mcp-rate-limits/${limit1.id}`, + }); + await makeApiRequest({ + request, + method: "delete", + urlSuffix: `/api/mcp-rate-limits/${limit2.id}`, + }); + } + }); + + test("updates a limit via PATCH", async ({ request }) => { + const createResp = await makeApiRequest({ + request, + method: "post", + urlSuffix: "/api/mcp-rate-limits", + data: { + agentId: profileId, + limitType: "mcp_server_calls", + maxCalls: 100, + mcpServerName: "update-server", + windowSeconds: 3600, + }, + }); + const limit = await createResp.json(); + + try { + const updateResp = await makeApiRequest({ + request, + method: "patch", + urlSuffix: `/api/mcp-rate-limits/${limit.id}`, + data: { maxCalls: 200, windowSeconds: 86400 }, + }); + expect(updateResp.status()).toBe(200); + const updated = await updateResp.json(); + expect(updated.maxCalls).toBe(200); + expect(updated.windowSeconds).toBe(86400); + } finally { + await makeApiRequest({ + request, + method: "delete", + urlSuffix: `/api/mcp-rate-limits/${limit.id}`, + }); + } + }); + + test("deletes a limit and GET returns 404", async ({ request }) => { + const createResp = await makeApiRequest({ + request, + method: "post", + urlSuffix: "/api/mcp-rate-limits", + data: { + agentId: profileId, + limitType: "mcp_server_calls", + maxCalls: 100, + mcpServerName: "delete-server", + windowSeconds: 3600, + }, + }); + const limit = await createResp.json(); + + const deleteResp = await makeApiRequest({ + request, + method: "delete", + urlSuffix: `/api/mcp-rate-limits/${limit.id}`, + }); + expect(deleteResp.status()).toBe(200); + + const getResp = await makeApiRequest({ + request, + method: "get", + urlSuffix: `/api/mcp-rate-limits/${limit.id}`, + ignoreStatusCheck: true, + }); + expect(getResp.status()).toBe(404); + }); + + test("rejects tool_calls without toolName", async ({ request }) => { + const response = await makeApiRequest({ + request, + method: "post", + urlSuffix: "/api/mcp-rate-limits", + data: { + agentId: profileId, + limitType: "tool_calls", + maxCalls: 50, + mcpServerName: "test-server", + windowSeconds: 60, + // Missing toolName + }, + ignoreStatusCheck: true, + }); + expect(response.status()).toBe(400); + }); +}); diff --git a/platform/e2e-tests/tests/ui/mcp-rate-limits.spec.ts b/platform/e2e-tests/tests/ui/mcp-rate-limits.spec.ts new file mode 100644 index 0000000000..23f8774f6d --- /dev/null +++ b/platform/e2e-tests/tests/ui/mcp-rate-limits.spec.ts @@ -0,0 +1,361 @@ +import { archestraApiSdk } from "@shared"; +import { expect, test } from "../../fixtures"; + +test.describe("MCP Rate Limits", () => { + let agentId: string; + let cookieHeaders: string; + + test.beforeAll(async ({ browser }) => { + const context = await browser.newContext({ + storageState: "e2e-tests/playwright/.auth/admin.json", + }); + const page = await context.newPage(); + cookieHeaders = (await context.cookies()) + .map((c) => `${c.name}=${c.value}`) + .join("; "); + + // Create a dedicated agent for rate limit tests + const createResult = await archestraApiSdk.createAgent({ + headers: { Cookie: cookieHeaders }, + body: { name: "E2E Rate Limit Agent", teams: [], scope: "org" }, + }); + + if (createResult.error || !createResult.data) { + throw new Error( + `Failed to create test agent: ${JSON.stringify(createResult.error)}`, + ); + } + agentId = createResult.data.id; + await context.close(); + }); + + test.afterAll(async ({ browser }) => { + if (!agentId) return; + const context = await browser.newContext({ + storageState: "e2e-tests/playwright/.auth/admin.json", + }); + cookieHeaders = (await context.cookies()) + .map((c) => `${c.name}=${c.value}`) + .join("; "); + + await archestraApiSdk.deleteAgent({ + headers: { Cookie: cookieHeaders }, + path: { id: agentId }, + }); + await context.close(); + }); + + test("shows empty state when no rate limits exist", async ({ + page, + goToPage, + }) => { + test.setTimeout(60_000); + await goToPage(page, "/mcp/rate-limits"); + await page.waitForLoadState("domcontentloaded"); + + await expect(page.getByText("No MCP rate limits configured")).toBeVisible({ + timeout: 15_000, + }); + }); + + test("can create a per-server rate limit", async ({ + page, + goToPage, + extractCookieHeaders, + }) => { + test.setTimeout(120_000); + await goToPage(page, "/mcp/rate-limits"); + await page.waitForLoadState("domcontentloaded"); + + // Click "Add Rate Limit" + await page.getByRole("button", { name: "Add Rate Limit" }).click(); + await expect(page.getByRole("dialog")).toBeVisible({ timeout: 10_000 }); + + // Select agent + await page.getByLabel("Agent / MCP Gateway").click(); + await page + .getByRole("option", { name: "E2E Rate Limit Agent" }) + .click(); + + // Type defaults to "Per Server" + await expect(page.getByLabel("Type")).toHaveText(/Per Server/); + + // Select MCP server name - enter manually since there may be no catalog items + // The MCP Server select may be empty. We need to fill mcpServerName. + // Since the UI uses a Select, we need a server to exist. Let's use a known server name. + // Actually, the select dropdown uses catalog items. If none exist, we can't select one. + // Let's check if there are options and handle both cases. + await page.getByLabel("MCP Server").click(); + // Select the first available option, or skip if none + const serverOptions = page.getByRole("option"); + const serverCount = await serverOptions.count(); + if (serverCount > 0) { + await serverOptions.first().click(); + } else { + // Close the dropdown and skip - no servers available to test with + await page.keyboard.press("Escape"); + test.skip(true, "No MCP servers available in catalog"); + return; + } + + // Set window to "1 hour" (default) + // Window defaults to 1 hour, no change needed + + // Set max calls + await page.getByLabel("Max Calls").fill("500"); + + // Submit + await page.getByRole("button", { name: "Create Rate Limit" }).click(); + + // Wait for dialog to close + await expect(page.getByRole("dialog")).not.toBeVisible({ timeout: 15_000 }); + + // Verify the row appears in the table + await expect(page.getByText("E2E Rate Limit Agent")).toBeVisible({ + timeout: 10_000, + }); + await expect(page.getByText("Per Server")).toBeVisible(); + await expect(page.getByText("500 calls")).toBeVisible(); + + // Clean up: delete the rate limit via API + const cookies = await extractCookieHeaders(page); + const limitsResult = await archestraApiSdk.getMcpRateLimits({ + headers: { Cookie: cookies }, + }); + if (limitsResult.data) { + const limit = limitsResult.data.find((l) => l.agentId === agentId); + if (limit) { + await archestraApiSdk.deleteMcpRateLimit({ + headers: { Cookie: cookies }, + path: { id: limit.id }, + }); + } + } + }); + + test("can create a per-tool rate limit", async ({ + page, + goToPage, + extractCookieHeaders, + }) => { + test.setTimeout(120_000); + await goToPage(page, "/mcp/rate-limits"); + await page.waitForLoadState("domcontentloaded"); + + // Click "Add Rate Limit" + await page.getByRole("button", { name: "Add Rate Limit" }).click(); + await expect(page.getByRole("dialog")).toBeVisible({ timeout: 10_000 }); + + // Select agent + await page.getByLabel("Agent / MCP Gateway").click(); + await page + .getByRole("option", { name: "E2E Rate Limit Agent" }) + .click(); + + // Change type to "Per Tool" + await page.getByLabel("Type").click(); + await page.getByRole("option", { name: "Per Tool" }).click(); + + // Select MCP server + await page.getByLabel("MCP Server").click(); + const serverOptions = page.getByRole("option"); + const serverCount = await serverOptions.count(); + if (serverCount > 0) { + await serverOptions.first().click(); + } else { + await page.keyboard.press("Escape"); + test.skip(true, "No MCP servers available in catalog"); + return; + } + + // Enter tool name (manual input since agent likely has no tools assigned) + await page.getByPlaceholder("Enter tool name manually").fill("test-tool"); + + // Set max calls + await page.getByLabel("Max Calls").fill("100"); + + // Submit + await page.getByRole("button", { name: "Create Rate Limit" }).click(); + + // Wait for dialog to close + await expect(page.getByRole("dialog")).not.toBeVisible({ timeout: 15_000 }); + + // Verify the row appears + await expect(page.getByText("Per Tool")).toBeVisible({ timeout: 10_000 }); + await expect(page.getByText("test-tool")).toBeVisible(); + + // Clean up via API + const cookies = await extractCookieHeaders(page); + const limitsResult = await archestraApiSdk.getMcpRateLimits({ + headers: { Cookie: cookies }, + }); + if (limitsResult.data) { + for (const limit of limitsResult.data.filter( + (l) => l.agentId === agentId, + )) { + await archestraApiSdk.deleteMcpRateLimit({ + headers: { Cookie: cookies }, + path: { id: limit.id }, + }); + } + } + }); + + test("can edit a rate limit", async ({ + page, + goToPage, + extractCookieHeaders, + }) => { + test.setTimeout(120_000); + + // Create a rate limit via API + const cookies = await extractCookieHeaders(page); + + // We need a valid MCP server name. Get the first catalog item. + const catalogResult = await archestraApiSdk.getInternalMcpCatalog({ + headers: { Cookie: cookies }, + }); + if ( + !catalogResult.data || + catalogResult.data.length === 0 + ) { + test.skip(true, "No MCP servers available in catalog"); + return; + } + const serverName = catalogResult.data[0].name; + + const createResult = await archestraApiSdk.createMcpRateLimit({ + headers: { Cookie: cookies }, + body: { + agentId, + limitType: "mcp_server_calls", + mcpServerName: serverName, + maxCalls: 200, + windowSeconds: 3600, + }, + }); + if (createResult.error || !createResult.data) { + throw new Error( + `Failed to create test rate limit: ${JSON.stringify(createResult.error)}`, + ); + } + const limitId = createResult.data.id; + + try { + await goToPage(page, "/mcp/rate-limits"); + await page.waitForLoadState("domcontentloaded"); + + // Wait for the table to show + await expect(page.getByText("E2E Rate Limit Agent")).toBeVisible({ + timeout: 15_000, + }); + + // Click the edit button (pencil icon) on the row + const row = page.getByRole("row").filter({ + hasText: "E2E Rate Limit Agent", + }); + await row.getByRole("button").first().click(); + + // Wait for the edit dialog to appear + await expect(page.getByText("Edit Rate Limit")).toBeVisible({ + timeout: 10_000, + }); + + // Update max calls + const maxCallsInput = page.getByLabel("Max Calls"); + await maxCallsInput.clear(); + await maxCallsInput.fill("999"); + + // Save changes + await page.getByRole("button", { name: "Save Changes" }).click(); + + // Wait for dialog to close + await expect(page.getByRole("dialog")).not.toBeVisible({ + timeout: 15_000, + }); + + // Verify updated value appears in the table + await expect(page.getByText("999 calls")).toBeVisible({ + timeout: 10_000, + }); + } finally { + // Clean up + await archestraApiSdk.deleteMcpRateLimit({ + headers: { Cookie: cookies }, + path: { id: limitId }, + }); + } + }); + + test("can delete a rate limit", async ({ + page, + goToPage, + extractCookieHeaders, + }) => { + test.setTimeout(120_000); + + // Create a rate limit via API + const cookies = await extractCookieHeaders(page); + + const catalogResult = await archestraApiSdk.getInternalMcpCatalog({ + headers: { Cookie: cookies }, + }); + if ( + !catalogResult.data || + catalogResult.data.length === 0 + ) { + test.skip(true, "No MCP servers available in catalog"); + return; + } + const serverName = catalogResult.data[0].name; + + await archestraApiSdk.createMcpRateLimit({ + headers: { Cookie: cookies }, + body: { + agentId, + limitType: "mcp_server_calls", + mcpServerName: serverName, + maxCalls: 300, + windowSeconds: 3600, + }, + }); + + await goToPage(page, "/mcp/rate-limits"); + await page.waitForLoadState("domcontentloaded"); + + // Wait for the table to show + await expect(page.getByText("E2E Rate Limit Agent")).toBeVisible({ + timeout: 15_000, + }); + + // Click the delete button (trash icon) on the row + const row = page.getByRole("row").filter({ + hasText: "E2E Rate Limit Agent", + }); + // The delete button is the second button in the actions column (after edit) + const deleteButton = row + .getByRole("button") + .filter({ has: page.locator("svg.text-destructive, .text-destructive") }) + .first(); + await deleteButton.click(); + + // Confirm deletion in the alert dialog + await expect(page.getByText("Delete MCP Rate Limit")).toBeVisible({ + timeout: 10_000, + }); + await page + .getByRole("button", { name: "Delete" }) + .filter({ hasNotText: "Delete MCP Rate Limit" }) + .click(); + + // Verify the row is removed + await expect(page.getByText("E2E Rate Limit Agent")).not.toBeVisible({ + timeout: 15_000, + }); + + // Verify empty state is shown + await expect(page.getByText("No MCP rate limits configured")).toBeVisible({ + timeout: 10_000, + }); + }); +}); diff --git a/platform/frontend/src/app/_parts/sidebar.tsx b/platform/frontend/src/app/_parts/sidebar.tsx index 54ab5c1601..e9d0c18231 100644 --- a/platform/frontend/src/app/_parts/sidebar.tsx +++ b/platform/frontend/src/app/_parts/sidebar.tsx @@ -130,6 +130,12 @@ const contentNavGroups: NavGroup[] = [ customIsActive: (pathname: string) => pathname.startsWith("/mcp/tool-policies"), }, + { + title: "Rate Limits", + url: "/mcp/rate-limits", + customIsActive: (pathname: string) => + pathname.startsWith("/mcp/rate-limits"), + }, ], }, ], diff --git a/platform/frontend/src/app/llm/cost/limits/page.tsx b/platform/frontend/src/app/llm/cost/limits/page.tsx index 21f6acf1c9..52f6a3975c 100644 --- a/platform/frontend/src/app/llm/cost/limits/page.tsx +++ b/platform/frontend/src/app/llm/cost/limits/page.tsx @@ -11,7 +11,6 @@ import { X, } from "lucide-react"; import { useCallback, useState } from "react"; -import type { CatalogItem } from "@/app/mcp/registry/_parts/mcp-server-card"; import { WithPermissions } from "@/components/roles/with-permissions"; import { AlertDialog, @@ -60,7 +59,6 @@ import { } from "@/components/ui/table"; import { TooltipProvider } from "@/components/ui/tooltip"; import { useModelsWithApiKeys } from "@/lib/chat-models.query"; -import { useInternalMcpCatalog } from "@/lib/internal-mcp-catalog.query"; import { useCreateLimit, useDeleteLimit, @@ -83,9 +81,6 @@ type TokenPriceData = { }; type TeamData = archestraApiTypes.GetTeamsResponses["200"][number]; type UsageStatus = "safe" | "warning" | "danger"; -type LimitType = Pick["limitType"]; -type TokenCostLimitType = Extract; -type McpServerCallsLimitType = Extract; // Loading skeleton component function LoadingSkeleton({ count, prefix }: { count: number; prefix: string }) { @@ -106,36 +101,27 @@ function LoadingSkeleton({ count, prefix }: { count: number; prefix: string }) { // Inline Form Component for adding/editing limits function LimitInlineForm({ initialData, - limitType, onSave, onCancel, teams, - mcpServers, tokenPrices, - hasOrganizationMcpLimit, - getTeamsWithMcpLimits, organizationId, }: { initialData?: LimitData; - limitType: TokenCostLimitType | McpServerCallsLimitType; onSave: (data: archestraApiTypes.CreateLimitData["body"]) => void; onCancel: () => void; teams: TeamData[]; - mcpServers: CatalogItem[]; tokenPrices: TokenPriceData[]; - hasOrganizationMcpLimit?: (mcpServerName?: string) => boolean; - getTeamsWithMcpLimits?: (mcpServerName?: string) => string[]; organizationId: string; }) { const [formData, setFormData] = useState<{ - entityType: "organization" | "team" | "agent"; + entityType: "organization" | "team"; entityId: string; mcpServerName: string; limitValue: string; model: string[]; }>({ - entityType: - (initialData?.entityType as "organization" | "team" | "agent") || "team", + entityType: (initialData?.entityType as "organization" | "team") || "team", entityId: initialData?.entityId || "", mcpServerName: initialData?.mcpServerName || "", limitValue: initialData?.limitValue?.toString() || "", @@ -147,7 +133,6 @@ function LimitInlineForm({ e.preventDefault(); onSave({ ...formData, - limitType, limitValue: parseInt(formData.limitValue, 10), entityId: formData.entityType === "organization" @@ -155,15 +140,14 @@ function LimitInlineForm({ : formData.entityId, }); }, - [formData, onSave, limitType, organizationId], + [formData, onSave, organizationId], ); const isValid = formData.limitValue && (formData.entityType === "organization" || formData.entityId) && - (limitType === "token_cost" - ? Array.isArray(formData.model) && formData.model.length > 0 - : formData.mcpServerName); + Array.isArray(formData.model) && + formData.model.length > 0; return ( @@ -182,7 +166,7 @@ function LimitInlineForm({ onValueChange={(value) => setFormData({ ...formData, - entityType: value as "agent" | "organization" | "team", + entityType: value as "organization" | "team", entityId: "", }) } @@ -230,62 +214,7 @@ function LimitInlineForm({ )} - {limitType !== "token_cost" && ( -
- - -
- )} - - {limitType === "token_cost" && ( + {
)} - )} + }
void; onDelete: () => void; teams: TeamData[]; - mcpServers: CatalogItem[]; tokenPrices: TokenPriceData[]; getEntityName: (limit: LimitData) => string; getUsageStatus: ( limitValue: number, - limitType: string, modelUsage?: Array<{ model: string; tokensIn: number; @@ -465,24 +387,16 @@ function LimitRow({ actualUsage: number; actualLimit: number; }; - hasOrganizationMcpLimit?: (mcpServerName?: string) => boolean; - getTeamsWithMcpLimits?: (mcpServerName?: string) => string[]; organizationId: string; }) { if (isEditing) { return ( ); @@ -490,7 +404,6 @@ function LimitRow({ const { percentage, status, actualUsage, actualLimit } = getUsageStatus( limit.limitValue, - limit.limitType, limit.modelUsage, ); @@ -518,32 +431,24 @@ function LimitRow({ {getEntityName(limit)} - {limit.limitType === "token_cost" ? ( - -
- {Array.isArray(limit.model) && limit.model.length > 0 ? ( - (limit.model as string[]).map((modelName: string) => ( - - {modelName} - - )) - ) : ( - - - )} -
- - ) : ( - - {limit.mcpServerName || "-"} - - )} + +
+ {Array.isArray(limit.model) && limit.model.length > 0 ? ( + (limit.model as string[]).map((modelName: string) => ( + + {modelName} + + )) + ) : ( + - + )} +
+
- {limit.limitType === "token_cost" - ? `$${actualUsage.toFixed(2)} / $${actualLimit.toFixed(2)}` - : `${actualUsage.toLocaleString()} / ${limit.limitValue.toLocaleString()} calls`} + {`$${actualUsage.toFixed(2)} / $${actualLimit.toFixed(2)}`} {percentage.toFixed(1)}%
@@ -562,7 +467,7 @@ function LimitRow({
(null); const [isAddingLlmLimit, setIsAddingLlmLimit] = useState(false); - const [isAddingMcpLimit, setIsAddingMcpLimit] = useState(false); // Data fetching hooks const { data: limits = [], isLoading: limitsLoading } = useLimits(); - const { data: mcpServers = [] } = useInternalMcpCatalog(); const { data: teams = [] } = useTeams(); const { data: organizationDetails } = useOrganization(); const { data: modelsWithApiKeys = [] } = useModelsWithApiKeys(); @@ -631,44 +534,7 @@ export default function LimitsPage() { const createLimit = useCreateLimit(); const updateLimit = useUpdateLimit(); - // Filter limits by type - const llmLimits = limits.filter((limit) => limit.limitType === "token_cost"); - const mcpLimits = limits.filter( - (limit) => limit.limitType === "mcp_server_calls", - ); - - // Helper functions for MCP limit validation only - const hasOrganizationMcpLimit = useCallback( - (mcpServerName?: string) => { - return limits.some((limit) => { - if ( - limit.limitType !== "mcp_server_calls" || - limit.entityType !== "organization" - ) { - return false; - } - return limit.mcpServerName === mcpServerName; - }); - }, - [limits], - ); - - const getTeamsWithMcpLimits = useCallback( - (mcpServerName?: string) => { - return limits - .filter((limit) => { - if ( - limit.limitType !== "mcp_server_calls" || - limit.entityType !== "team" - ) { - return false; - } - return limit.mcpServerName === mcpServerName; - }) - .map((limit) => limit.entityId); - }, - [limits], - ); + const llmLimits = limits; // Helper function to get entity name const getEntityName = useCallback( @@ -689,7 +555,6 @@ export default function LimitsPage() { const getUsageStatus = useCallback( ( limitValue: number, - limitType: string, modelUsage?: Array<{ model: string; tokensIn: number; @@ -697,21 +562,12 @@ export default function LimitsPage() { cost: number; }>, ) => { - let actualUsage: number; const actualLimit = limitValue; - - if (limitType === "token_cost") { - // Use precise per-model cost sum from backend - if (modelUsage && modelUsage.length > 0) { - actualUsage = modelUsage.reduce((sum, usage) => sum + usage.cost, 0); - } else { - // No usage tracked yet - actualUsage = 0; - } - } else { - // MCP server calls and tool calls limits are not currently tracked - actualUsage = 0; - } + // Use precise per-model cost sum from backend + const actualUsage = + modelUsage && modelUsage.length > 0 + ? modelUsage.reduce((sum, usage) => sum + usage.cost, 0) + : 0; const percentage = (actualUsage / actualLimit) * 100; let status: UsageStatus = "safe"; @@ -736,7 +592,6 @@ export default function LimitsPage() { try { await createLimit.mutateAsync(data); setIsAddingLlmLimit(false); - setIsAddingMcpLimit(false); } catch (error) { console.error("Failed to create limit:", error); } @@ -759,7 +614,6 @@ export default function LimitsPage() { const handleCancelEdit = useCallback(() => { setEditingLimitId(null); setIsAddingLlmLimit(false); - setIsAddingMcpLimit(false); }, []); return ( @@ -769,7 +623,7 @@ export default function LimitsPage() {
Auto-cleanup interval {({ hasPermission }) => ( @@ -811,7 +665,7 @@ export default function LimitsPage() {
setIsAddingLlmLimit(true)} size="sm" disabled={isAddingLlmLimit || editingLimitId !== null} @@ -838,14 +692,10 @@ export default function LimitsPage() { {isAddingLlmLimit && ( )} @@ -873,12 +723,9 @@ export default function LimitsPage() { onCancel={handleCancelEdit} onDelete={() => handleDeleteLimit(limit.id)} teams={teams} - mcpServers={mcpServers} tokenPrices={tokenPrices} getEntityName={getEntityName} getUsageStatus={getUsageStatus} - hasOrganizationMcpLimit={hasOrganizationMcpLimit} - getTeamsWithMcpLimits={getTeamsWithMcpLimits} organizationId={organizationDetails?.id || ""} /> )) @@ -888,104 +735,6 @@ export default function LimitsPage() { )} - - - -
-
- MCP Limits - - MCP server and tool call limits across teams and organization - -
- setIsAddingMcpLimit(true)} - size="sm" - disabled={true} - > - - Add MCP Limit - -
-
- -
-
-

- Coming soon -

-
-
- -
- {limitsLoading ? ( - - ) : ( - - - - Status - Applied to - MCP Server - Usage - Actions - - - - {isAddingMcpLimit && ( - - )} - {mcpLimits.length === 0 && !isAddingMcpLimit ? ( - - - -

No MCP limits configured

-

- Click "Add MCP Limit" to get started -

-
-
- ) : ( - mcpLimits.map((limit) => ( - setEditingLimitId(limit.id)} - onSave={(data) => handleUpdateLimit(limit.id, data)} - onCancel={handleCancelEdit} - onDelete={() => handleDeleteLimit(limit.id)} - teams={teams} - mcpServers={mcpServers} - tokenPrices={tokenPrices} - getEntityName={getEntityName} - getUsageStatus={getUsageStatus} - hasOrganizationMcpLimit={hasOrganizationMcpLimit} - getTeamsWithMcpLimits={getTeamsWithMcpLimits} - organizationId={organizationDetails?.id || ""} - /> - )) - )} -
-
- )} -
-
-
); } diff --git a/platform/frontend/src/app/llm/cost/optimization-rules/_parts/rule.tsx b/platform/frontend/src/app/llm/cost/optimization-rules/_parts/rule.tsx index 56c7deaecf..d3e4c2a771 100644 --- a/platform/frontend/src/app/llm/cost/optimization-rules/_parts/rule.tsx +++ b/platform/frontend/src/app/llm/cost/optimization-rules/_parts/rule.tsx @@ -458,7 +458,7 @@ export function Rule({ return (
{({ hasPermission }) => ( diff --git a/platform/frontend/src/app/llm/cost/optimization-rules/page.tsx b/platform/frontend/src/app/llm/cost/optimization-rules/page.tsx index 5d581bb157..65fbcf6b14 100644 --- a/platform/frontend/src/app/llm/cost/optimization-rules/page.tsx +++ b/platform/frontend/src/app/llm/cost/optimization-rules/page.tsx @@ -87,7 +87,7 @@ function DeleteRuleConfirmation({
{ if (editingRuleId !== null) { setEditingRuleId(null); @@ -401,7 +401,7 @@ export default function OptimizationRulesPage() { ) : ( <> { diff --git a/platform/frontend/src/app/mcp/rate-limits/page.client.tsx b/platform/frontend/src/app/mcp/rate-limits/page.client.tsx new file mode 100644 index 0000000000..a79e5430b2 --- /dev/null +++ b/platform/frontend/src/app/mcp/rate-limits/page.client.tsx @@ -0,0 +1,814 @@ +"use client"; + +import type { archestraApiTypes } from "@shared"; +import type { ColumnDef, SortingState } from "@tanstack/react-table"; +import { + Check, + ChevronDown, + ChevronsUpDown, + ChevronUp, + Edit, + Plus, + Settings, + Trash2, +} from "lucide-react"; +import { useCallback, useMemo, useState } from "react"; +import type { CatalogItem } from "@/app/mcp/registry/_parts/mcp-server-card"; +import { PageLayout } from "@/components/page-layout"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { DataTable } from "@/components/ui/data-table"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogForm, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { PermissionButton } from "@/components/ui/permission-button"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { Progress } from "@/components/ui/progress"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { useProfiles } from "@/lib/agent.query"; +import { useInternalMcpCatalog } from "@/lib/internal-mcp-catalog.query"; +import { + useCreateMcpRateLimit, + useDeleteMcpRateLimit, + useMcpRateLimits, + useUpdateMcpRateLimit, +} from "@/lib/mcp-rate-limits.query"; +import { cn } from "@/lib/utils"; + +type AgentData = archestraApiTypes.GetAllAgentsResponses["200"][number]; +type McpRateLimitData = + archestraApiTypes.GetMcpRateLimitsResponses["200"][number]; +type UsageStatus = "safe" | "warning" | "danger"; +type McpLimitType = "mcp_server_calls" | "tool_calls"; + +const WINDOW_PRESETS = [ + { label: "1 minute", value: 60 }, + { label: "1 hour", value: 3_600 }, + { label: "1 day", value: 86_400 }, + { label: "1 week", value: 604_800 }, + { label: "1 month", value: 2_592_000 }, +] as const; + +export default function McpRateLimitsClient() { + const [dialogOpen, setDialogOpen] = useState(false); + const [editingLimit, setEditingLimit] = useState( + null, + ); + const [sorting, setSorting] = useState([]); + + const { data: limits = [], isLoading } = useMcpRateLimits(); + const { data: mcpServers = [] } = useInternalMcpCatalog(); + const { data: agents = [] } = useProfiles({ + filters: { agentTypes: ["mcp_gateway", "agent", "profile"] }, + }); + + const deleteMcpRateLimit = useDeleteMcpRateLimit(); + const createMcpRateLimit = useCreateMcpRateLimit(); + const updateMcpRateLimit = useUpdateMcpRateLimit(); + + const getAgentName = useCallback( + (limit: McpRateLimitData) => { + const agent = agents.find((a) => a.id === limit.agentId); + return agent?.name || limit.agentId; + }, + [agents], + ); + + const handleCreate = useCallback( + async (data: archestraApiTypes.CreateMcpRateLimitData["body"]) => { + try { + await createMcpRateLimit.mutateAsync(data); + setDialogOpen(false); + } catch { + // Error handled by mutation hook + } + }, + [createMcpRateLimit], + ); + + const handleUpdate = useCallback( + async ( + id: string, + data: archestraApiTypes.CreateMcpRateLimitData["body"], + ) => { + try { + await updateMcpRateLimit.mutateAsync({ id, ...data }); + setEditingLimit(null); + } catch { + // Error handled by mutation hook + } + }, + [updateMcpRateLimit], + ); + + const handleDelete = useCallback( + async (id: string) => { + await deleteMcpRateLimit.mutateAsync({ id }); + }, + [deleteMcpRateLimit], + ); + + const columns: ColumnDef[] = [ + { + id: "status", + header: "Status", + cell: ({ row }) => { + const limit = row.original; + const mcpUsage = (limit.mcpUsage as number | undefined) ?? 0; + const percentage = (mcpUsage / limit.maxCalls) * 100; + const status = getUsageStatus(percentage); + return ( + + {status === "danger" + ? "Exceeded" + : status === "warning" + ? "Near Limit" + : "Safe"} + + ); + }, + }, + { + id: "agent", + accessorFn: (row) => getAgentName(row), + header: ({ column }) => ( + + ), + cell: ({ row }) => ( + + {getAgentName(row.original)} + + ), + }, + { + id: "type", + header: "Type", + cell: ({ row }) => ( + + {row.original.limitType === "tool_calls" ? "Per Tool" : "Per Server"} + + ), + }, + { + id: "target", + header: "Target", + cell: ({ row }) => { + const limit = row.original; + const target = + limit.limitType === "tool_calls" && limit.toolName + ? limit.toolName + : limit.mcpServerName || "-"; + return ( + + {target} + + ); + }, + }, + { + id: "window", + accessorFn: (row) => row.windowSeconds, + header: ({ column }) => ( + + ), + cell: ({ row }) => ( + + {formatWindowSeconds(row.original.windowSeconds)} + + ), + }, + { + id: "usage", + header: "Usage", + size: 200, + cell: ({ row }) => { + const limit = row.original; + const mcpUsage = (limit.mcpUsage as number | undefined) ?? 0; + const percentage = (mcpUsage / limit.maxCalls) * 100; + const status = getUsageStatus(percentage); + return ( +
+
+ + {mcpUsage.toLocaleString()} / {limit.maxCalls.toLocaleString()}{" "} + calls + + {percentage.toFixed(1)}% +
+ +
+ ); + }, + }, + { + id: "actions", + header: "Actions", + enableHiding: false, + cell: ({ row }) => { + const limit = row.original; + return ( +
+ setEditingLimit(limit)} + > + + + handleDelete(limit.id)} /> +
+ ); + }, + }, + ]; + + return ( + setDialogOpen(true)} + > + + Add Rate Limit +
+ } + > + {isLoading ? ( +
+ {[0, 1, 2].map((i) => ( +
+ ))} +
+ ) : limits.length === 0 ? ( +
+ +

No MCP rate limits configured

+

+ Click "Add Rate Limit" to get started +

+
+ ) : ( + + )} + + + + !open && setEditingLimit(null)} + agents={agents} + mcpServers={mcpServers} + initialData={editingLimit} + onSave={(data) => + editingLimit ? handleUpdate(editingLimit.id, data) : undefined + } + isSaving={updateMcpRateLimit.isPending} + /> + + ); +} + +function SortIcon({ isSorted }: { isSorted: false | "asc" | "desc" }) { + const upArrow = ; + const downArrow = ; + if (isSorted === "asc") return upArrow; + if (isSorted === "desc") return downArrow; + return ( +
+ {upArrow} + {downArrow} +
+ ); +} + +function getUsageStatus(percentage: number): UsageStatus { + if (percentage >= 90) return "danger"; + if (percentage >= 75) return "warning"; + return "safe"; +} + +function formatWindowSeconds(windowSeconds: number): string { + const preset = WINDOW_PRESETS.find((p) => p.value === windowSeconds); + if (preset) return preset.label; + if (windowSeconds < 60) return `${windowSeconds}s`; + if (windowSeconds < 3600) return `${Math.floor(windowSeconds / 60)}m`; + return `${Math.floor(windowSeconds / 3600)}h`; +} + +function DeleteLimitConfirmation({ onDelete }: { onDelete: () => void }) { + return ( + + + + + + + + + Delete MCP Rate Limit + + Are you sure you want to delete this rate limit? This action cannot + be undone. + + + + Cancel + + Delete + + + + + ); +} + +function ToolSearchCombobox({ + value, + onChange, + tools, +}: { + value: string; + onChange: (value: string) => void; + tools: Array<{ name: string; description: string | null }>; +}) { + const [open, setOpen] = useState(false); + + const selectedTool = tools.find((t) => t.name === value); + + return ( + + + + + + + + + No tools found. + + {tools.map((tool) => ( + { + onChange(tool.name); + setOpen(false); + }} + > + +
+ + {tool.name} + + {tool.description && ( + + {tool.description} + + )} +
+
+ ))} +
+
+
+
+
+ ); +} + +function McpRateLimitDialog({ + open, + onOpenChange, + agents, + mcpServers, + initialData, + onSave, + isSaving, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; + agents: AgentData[]; + mcpServers: CatalogItem[]; + initialData?: McpRateLimitData | null; + onSave: (data: archestraApiTypes.CreateMcpRateLimitData["body"]) => void; + isSaving: boolean; +}) { + const [agentId, setAgentId] = useState(initialData?.agentId || ""); + const [limitType, setLimitType] = useState( + (initialData?.limitType as McpLimitType) || "mcp_server_calls", + ); + const [mcpServerName, setMcpServerName] = useState( + initialData?.mcpServerName || "", + ); + const [toolName, setToolName] = useState(initialData?.toolName || ""); + const [maxCalls, setMaxCalls] = useState( + initialData?.maxCalls?.toString() || "", + ); + const [windowSeconds, setWindowSeconds] = useState( + initialData?.windowSeconds?.toString() || "3600", + ); + + // Get tools for the selected agent + const selectedAgent = agents.find((a) => a.id === agentId); + const agentTools = useMemo(() => { + if (!selectedAgent) return []; + return selectedAgent.tools + .filter((t) => !t.delegateToAgentId) + .map((t) => ({ name: t.name, description: t.description })); + }, [selectedAgent]); + + // Group agents by type for the select dropdown + const mcpGateways = useMemo( + () => agents.filter((a) => a.agentType === "mcp_gateway"), + [agents], + ); + const agentTypeAgents = useMemo( + () => agents.filter((a) => a.agentType === "agent"), + [agents], + ); + const profiles = useMemo( + () => agents.filter((a) => a.agentType === "profile"), + [agents], + ); + + // Reset form when dialog opens/closes or initialData changes + const resetForm = useCallback(() => { + setAgentId(initialData?.agentId || ""); + setLimitType( + (initialData?.limitType as McpLimitType) || "mcp_server_calls", + ); + setMcpServerName(initialData?.mcpServerName || ""); + setToolName(initialData?.toolName || ""); + setMaxCalls(initialData?.maxCalls?.toString() || ""); + setWindowSeconds(initialData?.windowSeconds?.toString() || "3600"); + }, [initialData]); + + // Reset when dialog opens + const handleOpenChange = useCallback( + (newOpen: boolean) => { + if (newOpen) { + resetForm(); + } + onOpenChange(newOpen); + }, + [onOpenChange, resetForm], + ); + + const handleSubmit = useCallback( + (e: React.FormEvent) => { + e.preventDefault(); + const parsedMaxCalls = Number.parseInt(maxCalls, 10); + const parsedWindowSeconds = Number.parseInt(windowSeconds, 10); + if (Number.isNaN(parsedMaxCalls) || parsedMaxCalls <= 0) return; + if (Number.isNaN(parsedWindowSeconds) || parsedWindowSeconds <= 0) return; + + onSave({ + agentId, + limitType, + maxCalls: parsedMaxCalls, + mcpServerName, + ...(limitType === "tool_calls" && { toolName }), + windowSeconds: parsedWindowSeconds, + }); + }, + [ + agentId, + limitType, + mcpServerName, + toolName, + maxCalls, + windowSeconds, + onSave, + ], + ); + + const isValid = + maxCalls && + Number.parseInt(maxCalls, 10) > 0 && + !Number.isNaN(Number.parseInt(maxCalls, 10)) && + mcpServerName && + windowSeconds && + agentId && + (limitType === "mcp_server_calls" || toolName); + + const isEdit = !!initialData; + + return ( + + + + + {isEdit ? "Edit Rate Limit" : "Add Rate Limit"} + + + {isEdit + ? "Update the rate limit configuration." + : "Configure a new rate limit for MCP tool calls."} + + + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ + {limitType === "tool_calls" && ( +
+ +

+ Shows tools assigned to the selected agent / MCP gateway. + {agentId && agentTools.length === 0 && ( + <> + {" "} + No tools are currently assigned — you can enter a tool + name manually, or{" "} + + assign tools + {" "} + first. + + )} +

+ {agentTools.length > 0 ? ( + + ) : ( + setToolName(e.target.value)} + placeholder={ + agentId + ? "Enter tool name manually" + : "Select an agent / MCP gateway first" + } + className="w-full" + /> + )} +
+ )} + +
+ + +
+ +
+ + { + const value = e.target.value.replace(/[^0-9]/g, ""); + setMaxCalls(value); + }} + placeholder="e.g. 1,000" + min="1" + required + className="w-full" + /> +
+
+ + + + +
+
+
+ ); +} diff --git a/platform/frontend/src/app/mcp/rate-limits/page.tsx b/platform/frontend/src/app/mcp/rate-limits/page.tsx new file mode 100644 index 0000000000..b7931b024d --- /dev/null +++ b/platform/frontend/src/app/mcp/rate-limits/page.tsx @@ -0,0 +1,7 @@ +import McpRateLimitsClient from "./page.client"; + +export const dynamic = "force-dynamic"; + +export default function McpRateLimitsPage() { + return ; +} diff --git a/platform/frontend/src/components/roles/role-permission-builder.ee.tsx b/platform/frontend/src/components/roles/role-permission-builder.ee.tsx index 4881d50d2d..3bbd97744e 100644 --- a/platform/frontend/src/components/roles/role-permission-builder.ee.tsx +++ b/platform/frontend/src/components/roles/role-permission-builder.ee.tsx @@ -44,7 +44,8 @@ const resourceCategories: Record = { "ac", "team", "invitation", - "limit", + "llmTokenLimit", + "mcpRateLimit", "llmModels", "chatSettings", "identityProvider", diff --git a/platform/frontend/src/components/settings/role-permissions-card.tsx b/platform/frontend/src/components/settings/role-permissions-card.tsx index 6fc915fb75..97fbc59c26 100644 --- a/platform/frontend/src/components/settings/role-permissions-card.tsx +++ b/platform/frontend/src/components/settings/role-permissions-card.tsx @@ -47,7 +47,8 @@ const resourceCategories: Record = { "ac", "team", "invitation", - "limit", + "llmTokenLimit", + "mcpRateLimit", "llmModels", "chatSettings", "identityProvider", diff --git a/platform/frontend/src/lib/limits.query.ts b/platform/frontend/src/lib/limits.query.ts index c59cce4800..c31dc295d0 100644 --- a/platform/frontend/src/lib/limits.query.ts +++ b/platform/frontend/src/lib/limits.query.ts @@ -5,11 +5,7 @@ import { toast } from "sonner"; const { getLimits, createLimit, getLimit, updateLimit, deleteLimit } = archestraApiSdk; -export function useLimits(params?: { - entityType?: "team" | "organization" | "agent"; - entityId?: string; - limitType?: "token_cost" | "mcp_server_calls" | "tool_calls"; -}) { +export function useLimits(params?: archestraApiTypes.GetLimitsData["query"]) { return useQuery({ queryKey: ["limits", params], queryFn: async () => { @@ -18,7 +14,6 @@ export function useLimits(params?: { ? { ...(params.entityType && { entityType: params.entityType }), ...(params.entityId && { entityId: params.entityId }), - ...(params.limitType && { limitType: params.limitType }), } : undefined, }); diff --git a/platform/frontend/src/lib/mcp-rate-limits.query.ts b/platform/frontend/src/lib/mcp-rate-limits.query.ts new file mode 100644 index 0000000000..99ac45e628 --- /dev/null +++ b/platform/frontend/src/lib/mcp-rate-limits.query.ts @@ -0,0 +1,113 @@ +import { archestraApiSdk, type archestraApiTypes } from "@shared"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { toast } from "sonner"; + +const { + getMcpRateLimits, + createMcpRateLimit, + getMcpRateLimit, + updateMcpRateLimit, + deleteMcpRateLimit, +} = archestraApiSdk; + +export function useMcpRateLimits( + params?: archestraApiTypes.GetMcpRateLimitsData["query"], +) { + return useQuery({ + queryKey: ["mcpRateLimits", params], + queryFn: async () => { + const response = await getMcpRateLimits({ + query: params + ? { + ...(params.agentId && { agentId: params.agentId }), + ...(params.limitType && { limitType: params.limitType }), + } + : undefined, + }); + return response.data ?? []; + }, + refetchInterval: 5000, + refetchOnWindowFocus: true, + }); +} + +export function useMcpRateLimit(id: string) { + return useQuery({ + queryKey: ["mcpRateLimits", id], + queryFn: async () => { + const response = await getMcpRateLimit({ path: { id } }); + return response.data; + }, + enabled: !!id, + }); +} + +export function useCreateMcpRateLimit() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async ( + data: archestraApiTypes.CreateMcpRateLimitData["body"], + ) => { + const response = await createMcpRateLimit({ body: data }); + return response.data; + }, + onSuccess: async () => { + await queryClient.invalidateQueries({ queryKey: ["mcpRateLimits"] }); + toast.success("MCP rate limit created successfully"); + }, + onError: (error) => { + console.error("Create MCP rate limit error:", error); + toast.error("Failed to create MCP rate limit"); + }, + }); +} + +export function useUpdateMcpRateLimit() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async ({ + id, + ...data + }: { + id: string; + } & Partial) => { + const response = await updateMcpRateLimit({ + path: { id }, + body: data, + }); + return response.data; + }, + onSuccess: async (_, variables) => { + await queryClient.invalidateQueries({ queryKey: ["mcpRateLimits"] }); + await queryClient.invalidateQueries({ + queryKey: ["mcpRateLimits", variables.id], + }); + toast.success("MCP rate limit updated successfully"); + }, + onError: (error) => { + console.error("Update MCP rate limit error:", error); + toast.error("Failed to update MCP rate limit"); + }, + }); +} + +export function useDeleteMcpRateLimit() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async ({ id }: { id: string }) => { + const response = await deleteMcpRateLimit({ path: { id } }); + return response.data; + }, + onSuccess: async (_, variables) => { + await queryClient.invalidateQueries({ queryKey: ["mcpRateLimits"] }); + queryClient.removeQueries({ + queryKey: ["mcpRateLimits", variables.id], + }); + toast.success("MCP rate limit deleted successfully"); + }, + onError: (error) => { + console.error("Delete MCP rate limit error:", error); + toast.error("Failed to delete MCP rate limit"); + }, + }); +} diff --git a/platform/shared/access-control.ts b/platform/shared/access-control.ts index 92fea48294..215a6fc6e8 100644 --- a/platform/shared/access-control.ts +++ b/platform/shared/access-control.ts @@ -34,7 +34,8 @@ export const allAvailableActions: Record = { team: ["create", "read", "update", "delete", "admin"], mcpToolCall: ["read"], conversation: ["create", "read", "update", "delete"], - limit: ["create", "read", "update", "delete"], + llmTokenLimit: ["create", "read", "update", "delete"], + mcpRateLimit: ["create", "read", "update", "delete"], llmModels: ["create", "read", "update", "delete"], chatSettings: ["create", "read", "update", "delete"], /** @@ -63,7 +64,8 @@ export const editorPermissions: Record = { team: ["read"], mcpToolCall: ["read"], conversation: ["create", "read", "update", "delete"], - limit: ["create", "read", "update", "delete"], + llmTokenLimit: ["create", "read", "update", "delete"], + mcpRateLimit: ["create", "read", "update", "delete"], llmModels: ["create", "read", "update", "delete"], chatSettings: ["create", "read", "update", "delete"], // Empty arrays required for Record type compatibility @@ -89,7 +91,8 @@ export const memberPermissions: Record = { team: ["read"], mcpToolCall: ["read"], conversation: ["create", "read", "update", "delete"], - limit: ["read"], + llmTokenLimit: ["read"], + mcpRateLimit: ["read"], llmModels: ["read"], chatSettings: ["read"], // Empty arrays required for Record type compatibility @@ -518,19 +521,34 @@ export const requiredEndpointPermissionsMap: Partial< [RouteId.DeleteAgentDelegation]: {}, [RouteId.GetAllDelegationConnections]: {}, [RouteId.GetLimits]: { - limit: ["read"], + llmTokenLimit: ["read"], }, [RouteId.CreateLimit]: { - limit: ["create"], + llmTokenLimit: ["create"], }, [RouteId.GetLimit]: { - limit: ["read"], + llmTokenLimit: ["read"], }, [RouteId.UpdateLimit]: { - limit: ["update"], + llmTokenLimit: ["update"], }, [RouteId.DeleteLimit]: { - limit: ["delete"], + llmTokenLimit: ["delete"], + }, + [RouteId.GetMcpRateLimits]: { + mcpRateLimit: ["read"], + }, + [RouteId.CreateMcpRateLimit]: { + mcpRateLimit: ["create"], + }, + [RouteId.GetMcpRateLimit]: { + mcpRateLimit: ["read"], + }, + [RouteId.UpdateMcpRateLimit]: { + mcpRateLimit: ["update"], + }, + [RouteId.DeleteMcpRateLimit]: { + mcpRateLimit: ["delete"], }, [RouteId.GetOrganization]: { organization: ["read"], @@ -700,6 +718,10 @@ export const requiredPagePermissionsMap: Record = { policy: ["read"], }, + "/mcp/rate-limits": { + mcpRateLimit: ["read"], + }, + "/mcp/registry": { internalMcpCatalog: ["read"], }, @@ -765,7 +787,7 @@ export const requiredPagePermissionsMap: Record = { interaction: ["read"], }, "/llm/cost/limits": { - limit: ["read"], + llmTokenLimit: ["read"], }, "/llm/cost/optimization-rules": { llmProxy: ["read"], diff --git a/platform/shared/hey-api/clients/api/index.ts b/platform/shared/hey-api/clients/api/index.ts index 7012b317db..e6181d546f 100644 --- a/platform/shared/hey-api/clients/api/index.ts +++ b/platform/shared/hey-api/clients/api/index.ts @@ -34,6 +34,7 @@ export { createIdentityProvider, createInternalMcpCatalogItem, createLimit, + createMcpRateLimit, createMcpServerInstallationRequest, createOptimizationRule, createRole, @@ -56,6 +57,7 @@ export { deleteInternalMcpCatalogItem, deleteInternalMcpCatalogItemByName, deleteLimit, + deleteMcpRateLimit, deleteMcpServer, deleteMcpServerInstallationRequest, deleteOptimizationRule, @@ -119,6 +121,8 @@ export { getLabelValues, getLimit, getLimits, + getMcpRateLimit, + getMcpRateLimits, getMcpServer, getMcpServerInstallationRequest, getMcpServerInstallationRequests, @@ -232,6 +236,7 @@ export { updateIdentityProvider, updateInternalMcpCatalogItem, updateLimit, + updateMcpRateLimit, updateMcpServerInstallationRequest, updateModelPricing, updateOptimizationRule, @@ -419,6 +424,11 @@ export type { CreateLimitErrors, CreateLimitResponse, CreateLimitResponses, + CreateMcpRateLimitData, + CreateMcpRateLimitError, + CreateMcpRateLimitErrors, + CreateMcpRateLimitResponse, + CreateMcpRateLimitResponses, CreateMcpServerInstallationRequestData, CreateMcpServerInstallationRequestError, CreateMcpServerInstallationRequestErrors, @@ -533,6 +543,11 @@ export type { DeleteLimitErrors, DeleteLimitResponse, DeleteLimitResponses, + DeleteMcpRateLimitData, + DeleteMcpRateLimitError, + DeleteMcpRateLimitErrors, + DeleteMcpRateLimitResponse, + DeleteMcpRateLimitResponses, DeleteMcpServerData, DeleteMcpServerError, DeleteMcpServerErrors, @@ -842,6 +857,16 @@ export type { GetLimitsErrors, GetLimitsResponse, GetLimitsResponses, + GetMcpRateLimitData, + GetMcpRateLimitError, + GetMcpRateLimitErrors, + GetMcpRateLimitResponse, + GetMcpRateLimitResponses, + GetMcpRateLimitsData, + GetMcpRateLimitsError, + GetMcpRateLimitsErrors, + GetMcpRateLimitsResponse, + GetMcpRateLimitsResponses, GetMcpServerData, GetMcpServerError, GetMcpServerErrors, @@ -1396,6 +1421,11 @@ export type { UpdateLimitErrors, UpdateLimitResponse, UpdateLimitResponses, + UpdateMcpRateLimitData, + UpdateMcpRateLimitError, + UpdateMcpRateLimitErrors, + UpdateMcpRateLimitResponse, + UpdateMcpRateLimitResponses, UpdateMcpServerInstallationRequestData, UpdateMcpServerInstallationRequestError, UpdateMcpServerInstallationRequestErrors, diff --git a/platform/shared/hey-api/clients/api/sdk.gen.ts b/platform/shared/hey-api/clients/api/sdk.gen.ts index 6390ddb7a1..7ce5da60d2 100644 --- a/platform/shared/hey-api/clients/api/sdk.gen.ts +++ b/platform/shared/hey-api/clients/api/sdk.gen.ts @@ -2,7 +2,7 @@ import type { Client, Options as Options2, TDataShape } from './client'; import { client } from './client.gen'; -import type { AddMcpServerInstallationRequestNoteData, AddMcpServerInstallationRequestNoteErrors, AddMcpServerInstallationRequestNoteResponses, AddTeamExternalGroupData, AddTeamExternalGroupErrors, AddTeamExternalGroupResponses, AddTeamMemberData, AddTeamMemberErrors, AddTeamMemberResponses, AnthropicMessagesWithAgentData, AnthropicMessagesWithAgentErrors, AnthropicMessagesWithAgentResponses, AnthropicMessagesWithDefaultAgentData, AnthropicMessagesWithDefaultAgentErrors, AnthropicMessagesWithDefaultAgentResponses, ApproveMcpServerInstallationRequestData, ApproveMcpServerInstallationRequestErrors, ApproveMcpServerInstallationRequestResponses, AssignToolToAgentData, AssignToolToAgentErrors, AssignToolToAgentResponses, AutoConfigureAgentToolPoliciesData, AutoConfigureAgentToolPoliciesErrors, AutoConfigureAgentToolPoliciesResponses, BedrockConverseStreamWithAgentAndModelData, BedrockConverseStreamWithAgentAndModelResponses, BedrockConverseStreamWithAgentData, BedrockConverseStreamWithAgentResponses, BedrockConverseStreamWithDefaultAgentData, BedrockConverseStreamWithDefaultAgentResponses, BedrockConverseWithAgentAndModelData, BedrockConverseWithAgentAndModelErrors, BedrockConverseWithAgentAndModelResponses, BedrockConverseWithAgentData, BedrockConverseWithAgentErrors, BedrockConverseWithAgentResponses, BedrockConverseWithDefaultAgentData, BedrockConverseWithDefaultAgentErrors, BedrockConverseWithDefaultAgentResponses, BulkAssignToolsData, BulkAssignToolsErrors, BulkAssignToolsResponses, BulkUpdateChatOpsBindingsData, BulkUpdateChatOpsBindingsErrors, BulkUpdateChatOpsBindingsResponses, BulkUpsertDefaultCallPolicyData, BulkUpsertDefaultCallPolicyErrors, BulkUpsertDefaultCallPolicyResponses, BulkUpsertDefaultResultPolicyData, BulkUpsertDefaultResultPolicyErrors, BulkUpsertDefaultResultPolicyResponses, CerebrasChatCompletionsWithAgentData, CerebrasChatCompletionsWithAgentErrors, CerebrasChatCompletionsWithAgentResponses, CerebrasChatCompletionsWithDefaultAgentData, CerebrasChatCompletionsWithDefaultAgentErrors, CerebrasChatCompletionsWithDefaultAgentResponses, CheckInvitationData, CheckInvitationErrors, CheckInvitationResponses, CheckSecretsConnectivityData, CheckSecretsConnectivityErrors, CheckSecretsConnectivityResponses, CheckTeamVaultFolderConnectivityData, CheckTeamVaultFolderConnectivityErrors, CheckTeamVaultFolderConnectivityResponses, CohereChatWithAgentData, CohereChatWithAgentErrors, CohereChatWithAgentResponses, CohereChatWithDefaultAgentData, CohereChatWithDefaultAgentErrors, CohereChatWithDefaultAgentResponses, CreateAgentData, CreateAgentErrors, CreateAgentResponses, CreateChatApiKeyData, CreateChatApiKeyErrors, CreateChatApiKeyResponses, CreateChatConversationData, CreateChatConversationErrors, CreateChatConversationResponses, CreateChatOpsDmBindingData, CreateChatOpsDmBindingErrors, CreateChatOpsDmBindingResponses, CreateDualLlmConfigData, CreateDualLlmConfigErrors, CreateDualLlmConfigResponses, CreateIdentityProviderData, CreateIdentityProviderErrors, CreateIdentityProviderResponses, CreateInternalMcpCatalogItemData, CreateInternalMcpCatalogItemErrors, CreateInternalMcpCatalogItemResponses, CreateLimitData, CreateLimitErrors, CreateLimitResponses, CreateMcpServerInstallationRequestData, CreateMcpServerInstallationRequestErrors, CreateMcpServerInstallationRequestResponses, CreateOptimizationRuleData, CreateOptimizationRuleErrors, CreateOptimizationRuleResponses, CreateRoleData, CreateRoleErrors, CreateRoleResponses, CreateTeamData, CreateTeamErrors, CreateTeamResponses, CreateToolInvocationPolicyData, CreateToolInvocationPolicyErrors, CreateToolInvocationPolicyResponses, CreateTrustedDataPolicyData, CreateTrustedDataPolicyErrors, CreateTrustedDataPolicyResponses, CreateVirtualApiKeyData, CreateVirtualApiKeyErrors, CreateVirtualApiKeyResponses, DeclineMcpServerInstallationRequestData, DeclineMcpServerInstallationRequestErrors, DeclineMcpServerInstallationRequestResponses, DeepseekChatCompletionsWithAgentData, DeepseekChatCompletionsWithAgentErrors, DeepseekChatCompletionsWithAgentResponses, DeepseekChatCompletionsWithDefaultAgentData, DeepseekChatCompletionsWithDefaultAgentErrors, DeepseekChatCompletionsWithDefaultAgentResponses, DeleteAgentData, DeleteAgentDelegationData, DeleteAgentDelegationErrors, DeleteAgentDelegationResponses, DeleteAgentErrors, DeleteAgentResponses, DeleteChatApiKeyData, DeleteChatApiKeyErrors, DeleteChatApiKeyResponses, DeleteChatConversationData, DeleteChatConversationErrors, DeleteChatConversationResponses, DeleteChatOpsBindingData, DeleteChatOpsBindingErrors, DeleteChatOpsBindingResponses, DeleteConversationEnabledToolsData, DeleteConversationEnabledToolsErrors, DeleteConversationEnabledToolsResponses, DeleteDualLlmConfigData, DeleteDualLlmConfigErrors, DeleteDualLlmConfigResponses, DeleteIdentityProviderData, DeleteIdentityProviderErrors, DeleteIdentityProviderResponses, DeleteIncomingEmailSubscriptionData, DeleteIncomingEmailSubscriptionErrors, DeleteIncomingEmailSubscriptionResponses, DeleteInternalMcpCatalogItemByNameData, DeleteInternalMcpCatalogItemByNameErrors, DeleteInternalMcpCatalogItemByNameResponses, DeleteInternalMcpCatalogItemData, DeleteInternalMcpCatalogItemErrors, DeleteInternalMcpCatalogItemResponses, DeleteLimitData, DeleteLimitErrors, DeleteLimitResponses, DeleteMcpServerData, DeleteMcpServerErrors, DeleteMcpServerInstallationRequestData, DeleteMcpServerInstallationRequestErrors, DeleteMcpServerInstallationRequestResponses, DeleteMcpServerResponses, DeleteOptimizationRuleData, DeleteOptimizationRuleErrors, DeleteOptimizationRuleResponses, DeletePendingSignupMemberData, DeletePendingSignupMemberErrors, DeletePendingSignupMemberResponses, DeleteRoleData, DeleteRoleErrors, DeleteRoleResponses, DeleteTeamData, DeleteTeamErrors, DeleteTeamResponses, DeleteTeamVaultFolderData, DeleteTeamVaultFolderErrors, DeleteTeamVaultFolderResponses, DeleteToolData, DeleteToolErrors, DeleteToolInvocationPolicyData, DeleteToolInvocationPolicyErrors, DeleteToolInvocationPolicyResponses, DeleteToolResponses, DeleteTrustedDataPolicyData, DeleteTrustedDataPolicyErrors, DeleteTrustedDataPolicyResponses, DeleteVirtualApiKeyData, DeleteVirtualApiKeyErrors, DeleteVirtualApiKeyResponses, GenerateChatConversationTitleData, GenerateChatConversationTitleErrors, GenerateChatConversationTitleResponses, GetAgentData, GetAgentDelegationsData, GetAgentDelegationsErrors, GetAgentDelegationsResponses, GetAgentEmailAddressData, GetAgentEmailAddressErrors, GetAgentEmailAddressResponses, GetAgentErrors, GetAgentResponses, GetAgentsData, GetAgentsErrors, GetAgentsResponses, GetAgentStatisticsData, GetAgentStatisticsErrors, GetAgentStatisticsResponses, GetAgentToolsData, GetAgentToolsErrors, GetAgentToolsResponses, GetAgentVersionsData, GetAgentVersionsErrors, GetAgentVersionsResponses, GetAllAgentsData, GetAllAgentsErrors, GetAllAgentsResponses, GetAllAgentToolsData, GetAllAgentToolsErrors, GetAllAgentToolsResponses, GetAllDelegationConnectionsData, GetAllDelegationConnectionsErrors, GetAllDelegationConnectionsResponses, GetAllVirtualApiKeysData, GetAllVirtualApiKeysErrors, GetAllVirtualApiKeysResponses, GetApiAuthBy__Data, GetApiAuthBy__Responses, GetApiAuthOauth2AuthorizeData, GetApiAuthOauth2AuthorizeResponses, GetAvailableChatApiKeysData, GetAvailableChatApiKeysErrors, GetAvailableChatApiKeysResponses, GetChatAgentMcpToolsData, GetChatAgentMcpToolsErrors, GetChatAgentMcpToolsResponses, GetChatApiKeyData, GetChatApiKeyErrors, GetChatApiKeyResponses, GetChatApiKeysData, GetChatApiKeysErrors, GetChatApiKeysResponses, GetChatConversationData, GetChatConversationErrors, GetChatConversationResponses, GetChatConversationsData, GetChatConversationsErrors, GetChatConversationsResponses, GetChatModelsData, GetChatModelsErrors, GetChatModelsResponses, GetChatOpsStatusData, GetChatOpsStatusErrors, GetChatOpsStatusResponses, GetConfigData, GetConfigResponses, GetConversationEnabledToolsData, GetConversationEnabledToolsErrors, GetConversationEnabledToolsResponses, GetCostSavingsStatisticsData, GetCostSavingsStatisticsErrors, GetCostSavingsStatisticsResponses, GetDefaultCredentialsStatusData, GetDefaultCredentialsStatusErrors, GetDefaultCredentialsStatusResponses, GetDefaultDualLlmConfigData, GetDefaultDualLlmConfigErrors, GetDefaultDualLlmConfigResponses, GetDefaultLlmProxyData, GetDefaultLlmProxyErrors, GetDefaultLlmProxyResponses, GetDefaultMcpGatewayData, GetDefaultMcpGatewayErrors, GetDefaultMcpGatewayResponses, GetDeploymentYamlPreviewData, GetDeploymentYamlPreviewErrors, GetDeploymentYamlPreviewResponses, GetDualLlmConfigData, GetDualLlmConfigErrors, GetDualLlmConfigResponses, GetDualLlmConfigsData, GetDualLlmConfigsErrors, GetDualLlmConfigsResponses, GetDualLlmResultByToolCallIdData, GetDualLlmResultByToolCallIdErrors, GetDualLlmResultByToolCallIdResponses, GetDualLlmResultsByInteractionData, GetDualLlmResultsByInteractionErrors, GetDualLlmResultsByInteractionResponses, GetHealthData, GetHealthResponses, GetIdentityProviderData, GetIdentityProviderErrors, GetIdentityProviderIdpLogoutUrlData, GetIdentityProviderIdpLogoutUrlErrors, GetIdentityProviderIdpLogoutUrlResponses, GetIdentityProviderResponses, GetIdentityProvidersData, GetIdentityProvidersErrors, GetIdentityProvidersResponses, GetIncomingEmailStatusData, GetIncomingEmailStatusErrors, GetIncomingEmailStatusResponses, GetInteractionData, GetInteractionErrors, GetInteractionResponses, GetInteractionsData, GetInteractionsErrors, GetInteractionSessionsData, GetInteractionSessionsErrors, GetInteractionSessionsResponses, GetInteractionsResponses, GetInternalMcpCatalogData, GetInternalMcpCatalogErrors, GetInternalMcpCatalogItemData, GetInternalMcpCatalogItemErrors, GetInternalMcpCatalogItemResponses, GetInternalMcpCatalogLabelKeysData, GetInternalMcpCatalogLabelKeysErrors, GetInternalMcpCatalogLabelKeysResponses, GetInternalMcpCatalogLabelValuesData, GetInternalMcpCatalogLabelValuesErrors, GetInternalMcpCatalogLabelValuesResponses, GetInternalMcpCatalogResponses, GetInternalMcpCatalogToolsData, GetInternalMcpCatalogToolsErrors, GetInternalMcpCatalogToolsResponses, GetK8sImagePullSecretsData, GetK8sImagePullSecretsErrors, GetK8sImagePullSecretsResponses, GetLabelKeysData, GetLabelKeysErrors, GetLabelKeysResponses, GetLabelValuesData, GetLabelValuesErrors, GetLabelValuesResponses, GetLimitData, GetLimitErrors, GetLimitResponses, GetLimitsData, GetLimitsErrors, GetLimitsResponses, GetMcpServerData, GetMcpServerErrors, GetMcpServerInstallationRequestData, GetMcpServerInstallationRequestErrors, GetMcpServerInstallationRequestResponses, GetMcpServerInstallationRequestsData, GetMcpServerInstallationRequestsErrors, GetMcpServerInstallationRequestsResponses, GetMcpServerInstallationStatusData, GetMcpServerInstallationStatusErrors, GetMcpServerInstallationStatusResponses, GetMcpServerResponses, GetMcpServersData, GetMcpServersErrors, GetMcpServersResponses, GetMcpServerToolsData, GetMcpServerToolsErrors, GetMcpServerToolsResponses, GetMcpToolCallData, GetMcpToolCallErrors, GetMcpToolCallResponses, GetMcpToolCallsData, GetMcpToolCallsErrors, GetMcpToolCallsResponses, GetMemberSignupStatusData, GetMemberSignupStatusErrors, GetMemberSignupStatusResponses, GetModelStatisticsData, GetModelStatisticsErrors, GetModelStatisticsResponses, GetModelsWithApiKeysData, GetModelsWithApiKeysErrors, GetModelsWithApiKeysResponses, GetOAuthClientInfoData, GetOAuthClientInfoResponses, GetOnboardingStatusData, GetOnboardingStatusErrors, GetOnboardingStatusResponses, GetOperatorsData, GetOperatorsErrors, GetOperatorsResponses, GetOptimizationRulesData, GetOptimizationRulesErrors, GetOptimizationRulesResponses, GetOrganizationData, GetOrganizationErrors, GetOrganizationResponses, GetOverviewStatisticsData, GetOverviewStatisticsErrors, GetOverviewStatisticsResponses, GetPublicAppearanceData, GetPublicAppearanceErrors, GetPublicAppearanceResponses, GetPublicIdentityProvidersData, GetPublicIdentityProvidersErrors, GetPublicIdentityProvidersResponses, GetRoleData, GetRoleErrors, GetRoleResponses, GetRolesData, GetRolesErrors, GetRolesResponses, GetSecretData, GetSecretErrors, GetSecretResponses, GetSecretsTypeData, GetSecretsTypeErrors, GetSecretsTypeResponses, GetTeamData, GetTeamErrors, GetTeamExternalGroupsData, GetTeamExternalGroupsErrors, GetTeamExternalGroupsResponses, GetTeamMembersData, GetTeamMembersErrors, GetTeamMembersResponses, GetTeamResponses, GetTeamsData, GetTeamsErrors, GetTeamsResponses, GetTeamStatisticsData, GetTeamStatisticsErrors, GetTeamStatisticsResponses, GetTeamVaultFolderData, GetTeamVaultFolderErrors, GetTeamVaultFolderResponses, GetTeamVaultSecretKeysData, GetTeamVaultSecretKeysErrors, GetTeamVaultSecretKeysResponses, GetTokensData, GetTokensErrors, GetTokensResponses, GetTokenValueData, GetTokenValueErrors, GetTokenValueResponses, GetToolInvocationPoliciesData, GetToolInvocationPoliciesErrors, GetToolInvocationPoliciesResponses, GetToolInvocationPolicyData, GetToolInvocationPolicyErrors, GetToolInvocationPolicyResponses, GetToolsData, GetToolsErrors, GetToolsResponses, GetToolsWithAssignmentsData, GetToolsWithAssignmentsErrors, GetToolsWithAssignmentsResponses, GetTrustedDataPoliciesData, GetTrustedDataPoliciesErrors, GetTrustedDataPoliciesResponses, GetTrustedDataPolicyData, GetTrustedDataPolicyErrors, GetTrustedDataPolicyResponses, GetUniqueExternalAgentIdsData, GetUniqueExternalAgentIdsErrors, GetUniqueExternalAgentIdsResponses, GetUniqueUserIdsData, GetUniqueUserIdsErrors, GetUniqueUserIdsResponses, GetUserPermissionsData, GetUserPermissionsErrors, GetUserPermissionsResponses, GetUserTokenData, GetUserTokenErrors, GetUserTokenResponses, GetUserTokenValueData, GetUserTokenValueErrors, GetUserTokenValueResponses, GetV1A2aByAgentIdWellKnownAgentJsonData, GetV1A2aByAgentIdWellKnownAgentJsonResponses, GetV1McpByProfileIdData, GetV1McpByProfileIdErrors, GetV1McpByProfileIdResponses, GetVirtualApiKeysData, GetVirtualApiKeysErrors, GetVirtualApiKeysResponses, GetWellKnownOauthAuthorizationServerData, GetWellKnownOauthAuthorizationServerResponses, GetWellKnownOauthProtectedResourceBy__Data, GetWellKnownOauthProtectedResourceBy__Responses, GroqChatCompletionsWithAgentData, GroqChatCompletionsWithAgentErrors, GroqChatCompletionsWithAgentResponses, GroqChatCompletionsWithDefaultAgentData, GroqChatCompletionsWithDefaultAgentErrors, GroqChatCompletionsWithDefaultAgentResponses, HandleOAuthCallbackData, HandleOAuthCallbackErrors, HandleOAuthCallbackResponses, InitiateOAuthData, InitiateOAuthErrors, InitiateOAuthResponses, InstallMcpServerData, InstallMcpServerErrors, InstallMcpServerResponses, ListChatOpsBindingsData, ListChatOpsBindingsErrors, ListChatOpsBindingsResponses, ListTeamVaultFolderSecretsData, ListTeamVaultFolderSecretsErrors, ListTeamVaultFolderSecretsResponses, MinimaxChatCompletionsWithAgentData, MinimaxChatCompletionsWithAgentErrors, MinimaxChatCompletionsWithAgentResponses, MinimaxChatCompletionsWithDefaultAgentData, MinimaxChatCompletionsWithDefaultAgentErrors, MinimaxChatCompletionsWithDefaultAgentResponses, MistralChatCompletionsWithAgentData, MistralChatCompletionsWithAgentErrors, MistralChatCompletionsWithAgentResponses, MistralChatCompletionsWithDefaultAgentData, MistralChatCompletionsWithDefaultAgentErrors, MistralChatCompletionsWithDefaultAgentResponses, OllamaChatCompletionsWithAgentData, OllamaChatCompletionsWithAgentErrors, OllamaChatCompletionsWithAgentResponses, OllamaChatCompletionsWithDefaultAgentData, OllamaChatCompletionsWithDefaultAgentErrors, OllamaChatCompletionsWithDefaultAgentResponses, OpenAiChatCompletionsWithAgentData, OpenAiChatCompletionsWithAgentErrors, OpenAiChatCompletionsWithAgentResponses, OpenAiChatCompletionsWithDefaultAgentData, OpenAiChatCompletionsWithDefaultAgentErrors, OpenAiChatCompletionsWithDefaultAgentResponses, OpenrouterChatCompletionsWithAgentData, OpenrouterChatCompletionsWithAgentErrors, OpenrouterChatCompletionsWithAgentResponses, OpenrouterChatCompletionsWithDefaultAgentData, OpenrouterChatCompletionsWithDefaultAgentErrors, OpenrouterChatCompletionsWithDefaultAgentResponses, PerplexityChatCompletionsWithAgentData, PerplexityChatCompletionsWithAgentErrors, PerplexityChatCompletionsWithAgentResponses, PerplexityChatCompletionsWithDefaultAgentData, PerplexityChatCompletionsWithDefaultAgentErrors, PerplexityChatCompletionsWithDefaultAgentResponses, PostApiAuthBy__Data, PostApiAuthBy__Responses, PostApiAuthOauth2RegisterData, PostApiAuthOauth2RegisterResponses, PostApiAuthOauth2TokenData, PostApiAuthOauth2TokenResponses, PostApiAuthOrganizationRemoveMemberData, PostApiAuthOrganizationRemoveMemberResponses, PostApiWebhooksChatopsMsTeamsData, PostApiWebhooksChatopsMsTeamsErrors, PostApiWebhooksChatopsMsTeamsResponses, PostApiWebhooksChatopsSlackData, PostApiWebhooksChatopsSlackErrors, PostApiWebhooksChatopsSlackInteractiveData, PostApiWebhooksChatopsSlackInteractiveErrors, PostApiWebhooksChatopsSlackInteractiveResponses, PostApiWebhooksChatopsSlackResponses, PostApiWebhooksChatopsSlackSlashCommandData, PostApiWebhooksChatopsSlackSlashCommandErrors, PostApiWebhooksChatopsSlackSlashCommandResponses, PostApiWebhooksIncomingEmailData, PostApiWebhooksIncomingEmailErrors, PostApiWebhooksIncomingEmailResponses, PostV1A2aByAgentIdData, PostV1A2aByAgentIdResponses, PostV1GeminiByAgentIdV1BetaModelsByModelGenerateContentData, PostV1GeminiByAgentIdV1BetaModelsByModelGenerateContentErrors, PostV1GeminiByAgentIdV1BetaModelsByModelGenerateContentResponses, PostV1GeminiByAgentIdV1BetaModelsByModelStreamGenerateContentData, PostV1GeminiByAgentIdV1BetaModelsByModelStreamGenerateContentErrors, PostV1GeminiV1BetaModelsByModelGenerateContentData, PostV1GeminiV1BetaModelsByModelGenerateContentErrors, PostV1GeminiV1BetaModelsByModelGenerateContentResponses, PostV1GeminiV1BetaModelsByModelStreamGenerateContentData, PostV1GeminiV1BetaModelsByModelStreamGenerateContentErrors, PostV1McpByProfileIdData, PostV1McpByProfileIdResponses, ReauthenticateMcpServerData, ReauthenticateMcpServerErrors, ReauthenticateMcpServerResponses, RefreshChatOpsChannelDiscoveryData, RefreshChatOpsChannelDiscoveryErrors, RefreshChatOpsChannelDiscoveryResponses, ReinstallMcpServerData, ReinstallMcpServerErrors, ReinstallMcpServerResponses, RemoveTeamExternalGroupData, RemoveTeamExternalGroupErrors, RemoveTeamExternalGroupResponses, RemoveTeamMemberData, RemoveTeamMemberErrors, RemoveTeamMemberResponses, RenewIncomingEmailSubscriptionData, RenewIncomingEmailSubscriptionErrors, RenewIncomingEmailSubscriptionResponses, ResetDeploymentYamlData, ResetDeploymentYamlErrors, ResetDeploymentYamlResponses, RollbackAgentData, RollbackAgentErrors, RollbackAgentResponses, RotateTokenData, RotateTokenErrors, RotateTokenResponses, RotateUserTokenData, RotateUserTokenErrors, RotateUserTokenResponses, SetTeamVaultFolderData, SetTeamVaultFolderErrors, SetTeamVaultFolderResponses, SetupIncomingEmailWebhookData, SetupIncomingEmailWebhookErrors, SetupIncomingEmailWebhookResponses, StopChatStreamData, StopChatStreamErrors, StopChatStreamResponses, StreamChatData, StreamChatErrors, SubmitOAuthConsentData, SubmitOAuthConsentResponses, SyncAgentDelegationsData, SyncAgentDelegationsErrors, SyncAgentDelegationsResponses, SyncChatModelsData, SyncChatModelsErrors, SyncChatModelsResponses, UnassignToolFromAgentData, UnassignToolFromAgentErrors, UnassignToolFromAgentResponses, UpdateAgentData, UpdateAgentErrors, UpdateAgentResponses, UpdateAgentToolData, UpdateAgentToolErrors, UpdateAgentToolResponses, UpdateChatApiKeyData, UpdateChatApiKeyErrors, UpdateChatApiKeyResponses, UpdateChatConversationData, UpdateChatConversationErrors, UpdateChatConversationResponses, UpdateChatMessageData, UpdateChatMessageErrors, UpdateChatMessageResponses, UpdateChatOpsBindingData, UpdateChatOpsBindingErrors, UpdateChatOpsBindingResponses, UpdateChatOpsConfigInQuickstartData, UpdateChatOpsConfigInQuickstartErrors, UpdateChatOpsConfigInQuickstartResponses, UpdateConversationEnabledToolsData, UpdateConversationEnabledToolsErrors, UpdateConversationEnabledToolsResponses, UpdateDualLlmConfigData, UpdateDualLlmConfigErrors, UpdateDualLlmConfigResponses, UpdateIdentityProviderData, UpdateIdentityProviderErrors, UpdateIdentityProviderResponses, UpdateInternalMcpCatalogItemData, UpdateInternalMcpCatalogItemErrors, UpdateInternalMcpCatalogItemResponses, UpdateLimitData, UpdateLimitErrors, UpdateLimitResponses, UpdateMcpServerInstallationRequestData, UpdateMcpServerInstallationRequestErrors, UpdateMcpServerInstallationRequestResponses, UpdateModelPricingData, UpdateModelPricingErrors, UpdateModelPricingResponses, UpdateOptimizationRuleData, UpdateOptimizationRuleErrors, UpdateOptimizationRuleResponses, UpdateOrganizationData, UpdateOrganizationErrors, UpdateOrganizationResponses, UpdateRoleData, UpdateRoleErrors, UpdateRoleResponses, UpdateSlackChatOpsConfigData, UpdateSlackChatOpsConfigErrors, UpdateSlackChatOpsConfigResponses, UpdateTeamData, UpdateTeamErrors, UpdateTeamResponses, UpdateToolInvocationPolicyData, UpdateToolInvocationPolicyErrors, UpdateToolInvocationPolicyResponses, UpdateTrustedDataPolicyData, UpdateTrustedDataPolicyErrors, UpdateTrustedDataPolicyResponses, ValidateDeploymentYamlData, ValidateDeploymentYamlErrors, ValidateDeploymentYamlResponses, VllmChatCompletionsWithAgentData, VllmChatCompletionsWithAgentErrors, VllmChatCompletionsWithAgentResponses, VllmChatCompletionsWithDefaultAgentData, VllmChatCompletionsWithDefaultAgentErrors, VllmChatCompletionsWithDefaultAgentResponses, XaiChatCompletionsWithAgentData, XaiChatCompletionsWithAgentErrors, XaiChatCompletionsWithAgentResponses, XaiChatCompletionsWithDefaultAgentData, XaiChatCompletionsWithDefaultAgentErrors, XaiChatCompletionsWithDefaultAgentResponses, ZhipuaiChatCompletionsWithAgentData, ZhipuaiChatCompletionsWithAgentErrors, ZhipuaiChatCompletionsWithAgentResponses, ZhipuaiChatCompletionsWithDefaultAgentData, ZhipuaiChatCompletionsWithDefaultAgentErrors, ZhipuaiChatCompletionsWithDefaultAgentResponses } from './types.gen'; +import type { AddMcpServerInstallationRequestNoteData, AddMcpServerInstallationRequestNoteErrors, AddMcpServerInstallationRequestNoteResponses, AddTeamExternalGroupData, AddTeamExternalGroupErrors, AddTeamExternalGroupResponses, AddTeamMemberData, AddTeamMemberErrors, AddTeamMemberResponses, AnthropicMessagesWithAgentData, AnthropicMessagesWithAgentErrors, AnthropicMessagesWithAgentResponses, AnthropicMessagesWithDefaultAgentData, AnthropicMessagesWithDefaultAgentErrors, AnthropicMessagesWithDefaultAgentResponses, ApproveMcpServerInstallationRequestData, ApproveMcpServerInstallationRequestErrors, ApproveMcpServerInstallationRequestResponses, AssignToolToAgentData, AssignToolToAgentErrors, AssignToolToAgentResponses, AutoConfigureAgentToolPoliciesData, AutoConfigureAgentToolPoliciesErrors, AutoConfigureAgentToolPoliciesResponses, BedrockConverseStreamWithAgentAndModelData, BedrockConverseStreamWithAgentAndModelResponses, BedrockConverseStreamWithAgentData, BedrockConverseStreamWithAgentResponses, BedrockConverseStreamWithDefaultAgentData, BedrockConverseStreamWithDefaultAgentResponses, BedrockConverseWithAgentAndModelData, BedrockConverseWithAgentAndModelErrors, BedrockConverseWithAgentAndModelResponses, BedrockConverseWithAgentData, BedrockConverseWithAgentErrors, BedrockConverseWithAgentResponses, BedrockConverseWithDefaultAgentData, BedrockConverseWithDefaultAgentErrors, BedrockConverseWithDefaultAgentResponses, BulkAssignToolsData, BulkAssignToolsErrors, BulkAssignToolsResponses, BulkUpdateChatOpsBindingsData, BulkUpdateChatOpsBindingsErrors, BulkUpdateChatOpsBindingsResponses, BulkUpsertDefaultCallPolicyData, BulkUpsertDefaultCallPolicyErrors, BulkUpsertDefaultCallPolicyResponses, BulkUpsertDefaultResultPolicyData, BulkUpsertDefaultResultPolicyErrors, BulkUpsertDefaultResultPolicyResponses, CerebrasChatCompletionsWithAgentData, CerebrasChatCompletionsWithAgentErrors, CerebrasChatCompletionsWithAgentResponses, CerebrasChatCompletionsWithDefaultAgentData, CerebrasChatCompletionsWithDefaultAgentErrors, CerebrasChatCompletionsWithDefaultAgentResponses, CheckInvitationData, CheckInvitationErrors, CheckInvitationResponses, CheckSecretsConnectivityData, CheckSecretsConnectivityErrors, CheckSecretsConnectivityResponses, CheckTeamVaultFolderConnectivityData, CheckTeamVaultFolderConnectivityErrors, CheckTeamVaultFolderConnectivityResponses, CohereChatWithAgentData, CohereChatWithAgentErrors, CohereChatWithAgentResponses, CohereChatWithDefaultAgentData, CohereChatWithDefaultAgentErrors, CohereChatWithDefaultAgentResponses, CreateAgentData, CreateAgentErrors, CreateAgentResponses, CreateChatApiKeyData, CreateChatApiKeyErrors, CreateChatApiKeyResponses, CreateChatConversationData, CreateChatConversationErrors, CreateChatConversationResponses, CreateChatOpsDmBindingData, CreateChatOpsDmBindingErrors, CreateChatOpsDmBindingResponses, CreateDualLlmConfigData, CreateDualLlmConfigErrors, CreateDualLlmConfigResponses, CreateIdentityProviderData, CreateIdentityProviderErrors, CreateIdentityProviderResponses, CreateInternalMcpCatalogItemData, CreateInternalMcpCatalogItemErrors, CreateInternalMcpCatalogItemResponses, CreateLimitData, CreateLimitErrors, CreateLimitResponses, CreateMcpRateLimitData, CreateMcpRateLimitErrors, CreateMcpRateLimitResponses, CreateMcpServerInstallationRequestData, CreateMcpServerInstallationRequestErrors, CreateMcpServerInstallationRequestResponses, CreateOptimizationRuleData, CreateOptimizationRuleErrors, CreateOptimizationRuleResponses, CreateRoleData, CreateRoleErrors, CreateRoleResponses, CreateTeamData, CreateTeamErrors, CreateTeamResponses, CreateToolInvocationPolicyData, CreateToolInvocationPolicyErrors, CreateToolInvocationPolicyResponses, CreateTrustedDataPolicyData, CreateTrustedDataPolicyErrors, CreateTrustedDataPolicyResponses, CreateVirtualApiKeyData, CreateVirtualApiKeyErrors, CreateVirtualApiKeyResponses, DeclineMcpServerInstallationRequestData, DeclineMcpServerInstallationRequestErrors, DeclineMcpServerInstallationRequestResponses, DeepseekChatCompletionsWithAgentData, DeepseekChatCompletionsWithAgentErrors, DeepseekChatCompletionsWithAgentResponses, DeepseekChatCompletionsWithDefaultAgentData, DeepseekChatCompletionsWithDefaultAgentErrors, DeepseekChatCompletionsWithDefaultAgentResponses, DeleteAgentData, DeleteAgentDelegationData, DeleteAgentDelegationErrors, DeleteAgentDelegationResponses, DeleteAgentErrors, DeleteAgentResponses, DeleteChatApiKeyData, DeleteChatApiKeyErrors, DeleteChatApiKeyResponses, DeleteChatConversationData, DeleteChatConversationErrors, DeleteChatConversationResponses, DeleteChatOpsBindingData, DeleteChatOpsBindingErrors, DeleteChatOpsBindingResponses, DeleteConversationEnabledToolsData, DeleteConversationEnabledToolsErrors, DeleteConversationEnabledToolsResponses, DeleteDualLlmConfigData, DeleteDualLlmConfigErrors, DeleteDualLlmConfigResponses, DeleteIdentityProviderData, DeleteIdentityProviderErrors, DeleteIdentityProviderResponses, DeleteIncomingEmailSubscriptionData, DeleteIncomingEmailSubscriptionErrors, DeleteIncomingEmailSubscriptionResponses, DeleteInternalMcpCatalogItemByNameData, DeleteInternalMcpCatalogItemByNameErrors, DeleteInternalMcpCatalogItemByNameResponses, DeleteInternalMcpCatalogItemData, DeleteInternalMcpCatalogItemErrors, DeleteInternalMcpCatalogItemResponses, DeleteLimitData, DeleteLimitErrors, DeleteLimitResponses, DeleteMcpRateLimitData, DeleteMcpRateLimitErrors, DeleteMcpRateLimitResponses, DeleteMcpServerData, DeleteMcpServerErrors, DeleteMcpServerInstallationRequestData, DeleteMcpServerInstallationRequestErrors, DeleteMcpServerInstallationRequestResponses, DeleteMcpServerResponses, DeleteOptimizationRuleData, DeleteOptimizationRuleErrors, DeleteOptimizationRuleResponses, DeletePendingSignupMemberData, DeletePendingSignupMemberErrors, DeletePendingSignupMemberResponses, DeleteRoleData, DeleteRoleErrors, DeleteRoleResponses, DeleteTeamData, DeleteTeamErrors, DeleteTeamResponses, DeleteTeamVaultFolderData, DeleteTeamVaultFolderErrors, DeleteTeamVaultFolderResponses, DeleteToolData, DeleteToolErrors, DeleteToolInvocationPolicyData, DeleteToolInvocationPolicyErrors, DeleteToolInvocationPolicyResponses, DeleteToolResponses, DeleteTrustedDataPolicyData, DeleteTrustedDataPolicyErrors, DeleteTrustedDataPolicyResponses, DeleteVirtualApiKeyData, DeleteVirtualApiKeyErrors, DeleteVirtualApiKeyResponses, GenerateChatConversationTitleData, GenerateChatConversationTitleErrors, GenerateChatConversationTitleResponses, GetAgentData, GetAgentDelegationsData, GetAgentDelegationsErrors, GetAgentDelegationsResponses, GetAgentEmailAddressData, GetAgentEmailAddressErrors, GetAgentEmailAddressResponses, GetAgentErrors, GetAgentResponses, GetAgentsData, GetAgentsErrors, GetAgentsResponses, GetAgentStatisticsData, GetAgentStatisticsErrors, GetAgentStatisticsResponses, GetAgentToolsData, GetAgentToolsErrors, GetAgentToolsResponses, GetAgentVersionsData, GetAgentVersionsErrors, GetAgentVersionsResponses, GetAllAgentsData, GetAllAgentsErrors, GetAllAgentsResponses, GetAllAgentToolsData, GetAllAgentToolsErrors, GetAllAgentToolsResponses, GetAllDelegationConnectionsData, GetAllDelegationConnectionsErrors, GetAllDelegationConnectionsResponses, GetAllVirtualApiKeysData, GetAllVirtualApiKeysErrors, GetAllVirtualApiKeysResponses, GetApiAuthBy__Data, GetApiAuthBy__Responses, GetApiAuthOauth2AuthorizeData, GetApiAuthOauth2AuthorizeResponses, GetAvailableChatApiKeysData, GetAvailableChatApiKeysErrors, GetAvailableChatApiKeysResponses, GetChatAgentMcpToolsData, GetChatAgentMcpToolsErrors, GetChatAgentMcpToolsResponses, GetChatApiKeyData, GetChatApiKeyErrors, GetChatApiKeyResponses, GetChatApiKeysData, GetChatApiKeysErrors, GetChatApiKeysResponses, GetChatConversationData, GetChatConversationErrors, GetChatConversationResponses, GetChatConversationsData, GetChatConversationsErrors, GetChatConversationsResponses, GetChatModelsData, GetChatModelsErrors, GetChatModelsResponses, GetChatOpsStatusData, GetChatOpsStatusErrors, GetChatOpsStatusResponses, GetConfigData, GetConfigResponses, GetConversationEnabledToolsData, GetConversationEnabledToolsErrors, GetConversationEnabledToolsResponses, GetCostSavingsStatisticsData, GetCostSavingsStatisticsErrors, GetCostSavingsStatisticsResponses, GetDefaultCredentialsStatusData, GetDefaultCredentialsStatusErrors, GetDefaultCredentialsStatusResponses, GetDefaultDualLlmConfigData, GetDefaultDualLlmConfigErrors, GetDefaultDualLlmConfigResponses, GetDefaultLlmProxyData, GetDefaultLlmProxyErrors, GetDefaultLlmProxyResponses, GetDefaultMcpGatewayData, GetDefaultMcpGatewayErrors, GetDefaultMcpGatewayResponses, GetDeploymentYamlPreviewData, GetDeploymentYamlPreviewErrors, GetDeploymentYamlPreviewResponses, GetDualLlmConfigData, GetDualLlmConfigErrors, GetDualLlmConfigResponses, GetDualLlmConfigsData, GetDualLlmConfigsErrors, GetDualLlmConfigsResponses, GetDualLlmResultByToolCallIdData, GetDualLlmResultByToolCallIdErrors, GetDualLlmResultByToolCallIdResponses, GetDualLlmResultsByInteractionData, GetDualLlmResultsByInteractionErrors, GetDualLlmResultsByInteractionResponses, GetHealthData, GetHealthResponses, GetIdentityProviderData, GetIdentityProviderErrors, GetIdentityProviderIdpLogoutUrlData, GetIdentityProviderIdpLogoutUrlErrors, GetIdentityProviderIdpLogoutUrlResponses, GetIdentityProviderResponses, GetIdentityProvidersData, GetIdentityProvidersErrors, GetIdentityProvidersResponses, GetIncomingEmailStatusData, GetIncomingEmailStatusErrors, GetIncomingEmailStatusResponses, GetInteractionData, GetInteractionErrors, GetInteractionResponses, GetInteractionsData, GetInteractionsErrors, GetInteractionSessionsData, GetInteractionSessionsErrors, GetInteractionSessionsResponses, GetInteractionsResponses, GetInternalMcpCatalogData, GetInternalMcpCatalogErrors, GetInternalMcpCatalogItemData, GetInternalMcpCatalogItemErrors, GetInternalMcpCatalogItemResponses, GetInternalMcpCatalogLabelKeysData, GetInternalMcpCatalogLabelKeysErrors, GetInternalMcpCatalogLabelKeysResponses, GetInternalMcpCatalogLabelValuesData, GetInternalMcpCatalogLabelValuesErrors, GetInternalMcpCatalogLabelValuesResponses, GetInternalMcpCatalogResponses, GetInternalMcpCatalogToolsData, GetInternalMcpCatalogToolsErrors, GetInternalMcpCatalogToolsResponses, GetK8sImagePullSecretsData, GetK8sImagePullSecretsErrors, GetK8sImagePullSecretsResponses, GetLabelKeysData, GetLabelKeysErrors, GetLabelKeysResponses, GetLabelValuesData, GetLabelValuesErrors, GetLabelValuesResponses, GetLimitData, GetLimitErrors, GetLimitResponses, GetLimitsData, GetLimitsErrors, GetLimitsResponses, GetMcpRateLimitData, GetMcpRateLimitErrors, GetMcpRateLimitResponses, GetMcpRateLimitsData, GetMcpRateLimitsErrors, GetMcpRateLimitsResponses, GetMcpServerData, GetMcpServerErrors, GetMcpServerInstallationRequestData, GetMcpServerInstallationRequestErrors, GetMcpServerInstallationRequestResponses, GetMcpServerInstallationRequestsData, GetMcpServerInstallationRequestsErrors, GetMcpServerInstallationRequestsResponses, GetMcpServerInstallationStatusData, GetMcpServerInstallationStatusErrors, GetMcpServerInstallationStatusResponses, GetMcpServerResponses, GetMcpServersData, GetMcpServersErrors, GetMcpServersResponses, GetMcpServerToolsData, GetMcpServerToolsErrors, GetMcpServerToolsResponses, GetMcpToolCallData, GetMcpToolCallErrors, GetMcpToolCallResponses, GetMcpToolCallsData, GetMcpToolCallsErrors, GetMcpToolCallsResponses, GetMemberSignupStatusData, GetMemberSignupStatusErrors, GetMemberSignupStatusResponses, GetModelStatisticsData, GetModelStatisticsErrors, GetModelStatisticsResponses, GetModelsWithApiKeysData, GetModelsWithApiKeysErrors, GetModelsWithApiKeysResponses, GetOAuthClientInfoData, GetOAuthClientInfoResponses, GetOnboardingStatusData, GetOnboardingStatusErrors, GetOnboardingStatusResponses, GetOperatorsData, GetOperatorsErrors, GetOperatorsResponses, GetOptimizationRulesData, GetOptimizationRulesErrors, GetOptimizationRulesResponses, GetOrganizationData, GetOrganizationErrors, GetOrganizationResponses, GetOverviewStatisticsData, GetOverviewStatisticsErrors, GetOverviewStatisticsResponses, GetPublicAppearanceData, GetPublicAppearanceErrors, GetPublicAppearanceResponses, GetPublicIdentityProvidersData, GetPublicIdentityProvidersErrors, GetPublicIdentityProvidersResponses, GetRoleData, GetRoleErrors, GetRoleResponses, GetRolesData, GetRolesErrors, GetRolesResponses, GetSecretData, GetSecretErrors, GetSecretResponses, GetSecretsTypeData, GetSecretsTypeErrors, GetSecretsTypeResponses, GetTeamData, GetTeamErrors, GetTeamExternalGroupsData, GetTeamExternalGroupsErrors, GetTeamExternalGroupsResponses, GetTeamMembersData, GetTeamMembersErrors, GetTeamMembersResponses, GetTeamResponses, GetTeamsData, GetTeamsErrors, GetTeamsResponses, GetTeamStatisticsData, GetTeamStatisticsErrors, GetTeamStatisticsResponses, GetTeamVaultFolderData, GetTeamVaultFolderErrors, GetTeamVaultFolderResponses, GetTeamVaultSecretKeysData, GetTeamVaultSecretKeysErrors, GetTeamVaultSecretKeysResponses, GetTokensData, GetTokensErrors, GetTokensResponses, GetTokenValueData, GetTokenValueErrors, GetTokenValueResponses, GetToolInvocationPoliciesData, GetToolInvocationPoliciesErrors, GetToolInvocationPoliciesResponses, GetToolInvocationPolicyData, GetToolInvocationPolicyErrors, GetToolInvocationPolicyResponses, GetToolsData, GetToolsErrors, GetToolsResponses, GetToolsWithAssignmentsData, GetToolsWithAssignmentsErrors, GetToolsWithAssignmentsResponses, GetTrustedDataPoliciesData, GetTrustedDataPoliciesErrors, GetTrustedDataPoliciesResponses, GetTrustedDataPolicyData, GetTrustedDataPolicyErrors, GetTrustedDataPolicyResponses, GetUniqueExternalAgentIdsData, GetUniqueExternalAgentIdsErrors, GetUniqueExternalAgentIdsResponses, GetUniqueUserIdsData, GetUniqueUserIdsErrors, GetUniqueUserIdsResponses, GetUserPermissionsData, GetUserPermissionsErrors, GetUserPermissionsResponses, GetUserTokenData, GetUserTokenErrors, GetUserTokenResponses, GetUserTokenValueData, GetUserTokenValueErrors, GetUserTokenValueResponses, GetV1A2aByAgentIdWellKnownAgentJsonData, GetV1A2aByAgentIdWellKnownAgentJsonResponses, GetV1McpByProfileIdData, GetV1McpByProfileIdErrors, GetV1McpByProfileIdResponses, GetVirtualApiKeysData, GetVirtualApiKeysErrors, GetVirtualApiKeysResponses, GetWellKnownOauthAuthorizationServerData, GetWellKnownOauthAuthorizationServerResponses, GetWellKnownOauthProtectedResourceBy__Data, GetWellKnownOauthProtectedResourceBy__Responses, GroqChatCompletionsWithAgentData, GroqChatCompletionsWithAgentErrors, GroqChatCompletionsWithAgentResponses, GroqChatCompletionsWithDefaultAgentData, GroqChatCompletionsWithDefaultAgentErrors, GroqChatCompletionsWithDefaultAgentResponses, HandleOAuthCallbackData, HandleOAuthCallbackErrors, HandleOAuthCallbackResponses, InitiateOAuthData, InitiateOAuthErrors, InitiateOAuthResponses, InstallMcpServerData, InstallMcpServerErrors, InstallMcpServerResponses, ListChatOpsBindingsData, ListChatOpsBindingsErrors, ListChatOpsBindingsResponses, ListTeamVaultFolderSecretsData, ListTeamVaultFolderSecretsErrors, ListTeamVaultFolderSecretsResponses, MinimaxChatCompletionsWithAgentData, MinimaxChatCompletionsWithAgentErrors, MinimaxChatCompletionsWithAgentResponses, MinimaxChatCompletionsWithDefaultAgentData, MinimaxChatCompletionsWithDefaultAgentErrors, MinimaxChatCompletionsWithDefaultAgentResponses, MistralChatCompletionsWithAgentData, MistralChatCompletionsWithAgentErrors, MistralChatCompletionsWithAgentResponses, MistralChatCompletionsWithDefaultAgentData, MistralChatCompletionsWithDefaultAgentErrors, MistralChatCompletionsWithDefaultAgentResponses, OllamaChatCompletionsWithAgentData, OllamaChatCompletionsWithAgentErrors, OllamaChatCompletionsWithAgentResponses, OllamaChatCompletionsWithDefaultAgentData, OllamaChatCompletionsWithDefaultAgentErrors, OllamaChatCompletionsWithDefaultAgentResponses, OpenAiChatCompletionsWithAgentData, OpenAiChatCompletionsWithAgentErrors, OpenAiChatCompletionsWithAgentResponses, OpenAiChatCompletionsWithDefaultAgentData, OpenAiChatCompletionsWithDefaultAgentErrors, OpenAiChatCompletionsWithDefaultAgentResponses, OpenrouterChatCompletionsWithAgentData, OpenrouterChatCompletionsWithAgentErrors, OpenrouterChatCompletionsWithAgentResponses, OpenrouterChatCompletionsWithDefaultAgentData, OpenrouterChatCompletionsWithDefaultAgentErrors, OpenrouterChatCompletionsWithDefaultAgentResponses, PerplexityChatCompletionsWithAgentData, PerplexityChatCompletionsWithAgentErrors, PerplexityChatCompletionsWithAgentResponses, PerplexityChatCompletionsWithDefaultAgentData, PerplexityChatCompletionsWithDefaultAgentErrors, PerplexityChatCompletionsWithDefaultAgentResponses, PostApiAuthBy__Data, PostApiAuthBy__Responses, PostApiAuthOauth2RegisterData, PostApiAuthOauth2RegisterResponses, PostApiAuthOauth2TokenData, PostApiAuthOauth2TokenResponses, PostApiAuthOrganizationRemoveMemberData, PostApiAuthOrganizationRemoveMemberResponses, PostApiWebhooksChatopsMsTeamsData, PostApiWebhooksChatopsMsTeamsErrors, PostApiWebhooksChatopsMsTeamsResponses, PostApiWebhooksChatopsSlackData, PostApiWebhooksChatopsSlackErrors, PostApiWebhooksChatopsSlackInteractiveData, PostApiWebhooksChatopsSlackInteractiveErrors, PostApiWebhooksChatopsSlackInteractiveResponses, PostApiWebhooksChatopsSlackResponses, PostApiWebhooksChatopsSlackSlashCommandData, PostApiWebhooksChatopsSlackSlashCommandErrors, PostApiWebhooksChatopsSlackSlashCommandResponses, PostApiWebhooksIncomingEmailData, PostApiWebhooksIncomingEmailErrors, PostApiWebhooksIncomingEmailResponses, PostV1A2aByAgentIdData, PostV1A2aByAgentIdResponses, PostV1GeminiByAgentIdV1BetaModelsByModelGenerateContentData, PostV1GeminiByAgentIdV1BetaModelsByModelGenerateContentErrors, PostV1GeminiByAgentIdV1BetaModelsByModelGenerateContentResponses, PostV1GeminiByAgentIdV1BetaModelsByModelStreamGenerateContentData, PostV1GeminiByAgentIdV1BetaModelsByModelStreamGenerateContentErrors, PostV1GeminiV1BetaModelsByModelGenerateContentData, PostV1GeminiV1BetaModelsByModelGenerateContentErrors, PostV1GeminiV1BetaModelsByModelGenerateContentResponses, PostV1GeminiV1BetaModelsByModelStreamGenerateContentData, PostV1GeminiV1BetaModelsByModelStreamGenerateContentErrors, PostV1McpByProfileIdData, PostV1McpByProfileIdResponses, ReauthenticateMcpServerData, ReauthenticateMcpServerErrors, ReauthenticateMcpServerResponses, RefreshChatOpsChannelDiscoveryData, RefreshChatOpsChannelDiscoveryErrors, RefreshChatOpsChannelDiscoveryResponses, ReinstallMcpServerData, ReinstallMcpServerErrors, ReinstallMcpServerResponses, RemoveTeamExternalGroupData, RemoveTeamExternalGroupErrors, RemoveTeamExternalGroupResponses, RemoveTeamMemberData, RemoveTeamMemberErrors, RemoveTeamMemberResponses, RenewIncomingEmailSubscriptionData, RenewIncomingEmailSubscriptionErrors, RenewIncomingEmailSubscriptionResponses, ResetDeploymentYamlData, ResetDeploymentYamlErrors, ResetDeploymentYamlResponses, RollbackAgentData, RollbackAgentErrors, RollbackAgentResponses, RotateTokenData, RotateTokenErrors, RotateTokenResponses, RotateUserTokenData, RotateUserTokenErrors, RotateUserTokenResponses, SetTeamVaultFolderData, SetTeamVaultFolderErrors, SetTeamVaultFolderResponses, SetupIncomingEmailWebhookData, SetupIncomingEmailWebhookErrors, SetupIncomingEmailWebhookResponses, StopChatStreamData, StopChatStreamErrors, StopChatStreamResponses, StreamChatData, StreamChatErrors, SubmitOAuthConsentData, SubmitOAuthConsentResponses, SyncAgentDelegationsData, SyncAgentDelegationsErrors, SyncAgentDelegationsResponses, SyncChatModelsData, SyncChatModelsErrors, SyncChatModelsResponses, UnassignToolFromAgentData, UnassignToolFromAgentErrors, UnassignToolFromAgentResponses, UpdateAgentData, UpdateAgentErrors, UpdateAgentResponses, UpdateAgentToolData, UpdateAgentToolErrors, UpdateAgentToolResponses, UpdateChatApiKeyData, UpdateChatApiKeyErrors, UpdateChatApiKeyResponses, UpdateChatConversationData, UpdateChatConversationErrors, UpdateChatConversationResponses, UpdateChatMessageData, UpdateChatMessageErrors, UpdateChatMessageResponses, UpdateChatOpsBindingData, UpdateChatOpsBindingErrors, UpdateChatOpsBindingResponses, UpdateChatOpsConfigInQuickstartData, UpdateChatOpsConfigInQuickstartErrors, UpdateChatOpsConfigInQuickstartResponses, UpdateConversationEnabledToolsData, UpdateConversationEnabledToolsErrors, UpdateConversationEnabledToolsResponses, UpdateDualLlmConfigData, UpdateDualLlmConfigErrors, UpdateDualLlmConfigResponses, UpdateIdentityProviderData, UpdateIdentityProviderErrors, UpdateIdentityProviderResponses, UpdateInternalMcpCatalogItemData, UpdateInternalMcpCatalogItemErrors, UpdateInternalMcpCatalogItemResponses, UpdateLimitData, UpdateLimitErrors, UpdateLimitResponses, UpdateMcpRateLimitData, UpdateMcpRateLimitErrors, UpdateMcpRateLimitResponses, UpdateMcpServerInstallationRequestData, UpdateMcpServerInstallationRequestErrors, UpdateMcpServerInstallationRequestResponses, UpdateModelPricingData, UpdateModelPricingErrors, UpdateModelPricingResponses, UpdateOptimizationRuleData, UpdateOptimizationRuleErrors, UpdateOptimizationRuleResponses, UpdateOrganizationData, UpdateOrganizationErrors, UpdateOrganizationResponses, UpdateRoleData, UpdateRoleErrors, UpdateRoleResponses, UpdateSlackChatOpsConfigData, UpdateSlackChatOpsConfigErrors, UpdateSlackChatOpsConfigResponses, UpdateTeamData, UpdateTeamErrors, UpdateTeamResponses, UpdateToolInvocationPolicyData, UpdateToolInvocationPolicyErrors, UpdateToolInvocationPolicyResponses, UpdateTrustedDataPolicyData, UpdateTrustedDataPolicyErrors, UpdateTrustedDataPolicyResponses, ValidateDeploymentYamlData, ValidateDeploymentYamlErrors, ValidateDeploymentYamlResponses, VllmChatCompletionsWithAgentData, VllmChatCompletionsWithAgentErrors, VllmChatCompletionsWithAgentResponses, VllmChatCompletionsWithDefaultAgentData, VllmChatCompletionsWithDefaultAgentErrors, VllmChatCompletionsWithDefaultAgentResponses, XaiChatCompletionsWithAgentData, XaiChatCompletionsWithAgentErrors, XaiChatCompletionsWithAgentResponses, XaiChatCompletionsWithDefaultAgentData, XaiChatCompletionsWithDefaultAgentErrors, XaiChatCompletionsWithDefaultAgentResponses, ZhipuaiChatCompletionsWithAgentData, ZhipuaiChatCompletionsWithAgentErrors, ZhipuaiChatCompletionsWithAgentResponses, ZhipuaiChatCompletionsWithDefaultAgentData, ZhipuaiChatCompletionsWithDefaultAgentErrors, ZhipuaiChatCompletionsWithDefaultAgentResponses } from './types.gen'; export type Options = Options2 & { /** @@ -1186,6 +1186,45 @@ export const postV1McpByProfileId = (optio } }); +/** + * Get all MCP rate limits with optional filtering and live usage + */ +export const getMcpRateLimits = (options?: Options) => (options?.client ?? client).get({ url: '/api/mcp-rate-limits', ...options }); + +/** + * Create a new MCP rate limit + */ +export const createMcpRateLimit = (options: Options) => (options.client ?? client).post({ + url: '/api/mcp-rate-limits', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Delete an MCP rate limit + */ +export const deleteMcpRateLimit = (options: Options) => (options.client ?? client).delete({ url: '/api/mcp-rate-limits/{id}', ...options }); + +/** + * Get an MCP rate limit by ID + */ +export const getMcpRateLimit = (options: Options) => (options.client ?? client).get({ url: '/api/mcp-rate-limits/{id}', ...options }); + +/** + * Update an MCP rate limit + */ +export const updateMcpRateLimit = (options: Options) => (options.client ?? client).patch({ + url: '/api/mcp-rate-limits/{id}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + /** * Get all MCP server installation requests */ diff --git a/platform/shared/hey-api/clients/api/types.gen.ts b/platform/shared/hey-api/clients/api/types.gen.ts index 535b7ceb1d..d8546279c3 100644 --- a/platform/shared/hey-api/clients/api/types.gen.ts +++ b/platform/shared/hey-api/clients/api/types.gen.ts @@ -26235,9 +26235,8 @@ export type GetLimitsData = { body?: never; path?: never; query?: { - entityType?: 'organization' | 'team' | 'agent'; + entityType?: 'organization' | 'team'; entityId?: string; - limitType?: 'token_cost' | 'mcp_server_calls' | 'tool_calls'; }; url: '/api/limits'; }; @@ -26307,9 +26306,8 @@ export type GetLimitsResponses = { */ 200: Array<{ id: string; - entityType: 'organization' | 'team' | 'agent'; + entityType: 'organization' | 'team'; entityId: string; - limitType: 'token_cost' | 'mcp_server_calls' | 'tool_calls'; limitValue: number; mcpServerName: string | null; toolName: string | null; @@ -26330,9 +26328,8 @@ export type GetLimitsResponse = GetLimitsResponses[keyof GetLimitsResponses]; export type CreateLimitData = { body: { - entityType: 'organization' | 'team' | 'agent'; + entityType: 'organization' | 'team'; entityId: string; - limitType: 'token_cost' | 'mcp_server_calls' | 'tool_calls'; limitValue: number; mcpServerName?: string | null; toolName?: string | null; @@ -26409,9 +26406,8 @@ export type CreateLimitResponses = { */ 200: { id: string; - entityType: 'organization' | 'team' | 'agent'; + entityType: 'organization' | 'team'; entityId: string; - limitType: 'token_cost' | 'mcp_server_calls' | 'tool_calls'; limitValue: number; mcpServerName: string | null; toolName: string | null; @@ -26577,9 +26573,8 @@ export type GetLimitResponses = { */ 200: { id: string; - entityType: 'organization' | 'team' | 'agent'; + entityType: 'organization' | 'team'; entityId: string; - limitType: 'token_cost' | 'mcp_server_calls' | 'tool_calls'; limitValue: number; mcpServerName: string | null; toolName: string | null; @@ -26594,9 +26589,8 @@ export type GetLimitResponse = GetLimitResponses[keyof GetLimitResponses]; export type UpdateLimitData = { body?: { - entityType?: 'organization' | 'team' | 'agent'; + entityType?: 'organization' | 'team'; entityId?: string; - limitType?: 'token_cost' | 'mcp_server_calls' | 'tool_calls'; limitValue?: number; mcpServerName?: string | null; toolName?: string | null; @@ -26675,9 +26669,8 @@ export type UpdateLimitResponses = { */ 200: { id: string; - entityType: 'organization' | 'team' | 'agent'; + entityType: 'organization' | 'team'; entityId: string; - limitType: 'token_cost' | 'mcp_server_calls' | 'tool_calls'; limitValue: number; mcpServerName: string | null; toolName: string | null; @@ -26753,6 +26746,446 @@ export type PostV1McpByProfileIdResponses = { 200: unknown; }; +export type GetMcpRateLimitsData = { + body?: never; + path?: never; + query?: { + agentId?: string; + limitType?: 'mcp_server_calls' | 'tool_calls'; + }; + url: '/api/mcp-rate-limits'; +}; + +export type GetMcpRateLimitsErrors = { + /** + * Default Response + */ + 400: { + error: { + message: string; + type: 'api_validation_error'; + }; + }; + /** + * Default Response + */ + 401: { + error: { + message: string; + type: 'api_authentication_error'; + }; + }; + /** + * Default Response + */ + 403: { + error: { + message: string; + type: 'api_authorization_error'; + }; + }; + /** + * Default Response + */ + 404: { + error: { + message: string; + type: 'api_not_found_error'; + }; + }; + /** + * Default Response + */ + 409: { + error: { + message: string; + type: 'api_conflict_error'; + }; + }; + /** + * Default Response + */ + 500: { + error: { + message: string; + type: 'api_internal_server_error'; + }; + }; +}; + +export type GetMcpRateLimitsError = GetMcpRateLimitsErrors[keyof GetMcpRateLimitsErrors]; + +export type GetMcpRateLimitsResponses = { + /** + * Default Response + */ + 200: Array<{ + id: string; + agentId: string; + limitType: 'mcp_server_calls' | 'tool_calls'; + mcpServerName: string; + toolName: string | null; + maxCalls: number; + windowSeconds: number; + createdAt: string; + updatedAt: string; + mcpUsage?: number; + }>; +}; + +export type GetMcpRateLimitsResponse = GetMcpRateLimitsResponses[keyof GetMcpRateLimitsResponses]; + +export type CreateMcpRateLimitData = { + body: { + agentId: string; + limitType: 'mcp_server_calls' | 'tool_calls'; + mcpServerName: string; + toolName?: string | null; + maxCalls: number; + windowSeconds: number; + }; + path?: never; + query?: never; + url: '/api/mcp-rate-limits'; +}; + +export type CreateMcpRateLimitErrors = { + /** + * Default Response + */ + 400: { + error: { + message: string; + type: 'api_validation_error'; + }; + }; + /** + * Default Response + */ + 401: { + error: { + message: string; + type: 'api_authentication_error'; + }; + }; + /** + * Default Response + */ + 403: { + error: { + message: string; + type: 'api_authorization_error'; + }; + }; + /** + * Default Response + */ + 404: { + error: { + message: string; + type: 'api_not_found_error'; + }; + }; + /** + * Default Response + */ + 409: { + error: { + message: string; + type: 'api_conflict_error'; + }; + }; + /** + * Default Response + */ + 500: { + error: { + message: string; + type: 'api_internal_server_error'; + }; + }; +}; + +export type CreateMcpRateLimitError = CreateMcpRateLimitErrors[keyof CreateMcpRateLimitErrors]; + +export type CreateMcpRateLimitResponses = { + /** + * Default Response + */ + 200: { + id: string; + agentId: string; + limitType: 'mcp_server_calls' | 'tool_calls'; + mcpServerName: string; + toolName: string | null; + maxCalls: number; + windowSeconds: number; + createdAt: string; + updatedAt: string; + }; +}; + +export type CreateMcpRateLimitResponse = CreateMcpRateLimitResponses[keyof CreateMcpRateLimitResponses]; + +export type DeleteMcpRateLimitData = { + body?: never; + path: { + id: string; + }; + query?: never; + url: '/api/mcp-rate-limits/{id}'; +}; + +export type DeleteMcpRateLimitErrors = { + /** + * Default Response + */ + 400: { + error: { + message: string; + type: 'api_validation_error'; + }; + }; + /** + * Default Response + */ + 401: { + error: { + message: string; + type: 'api_authentication_error'; + }; + }; + /** + * Default Response + */ + 403: { + error: { + message: string; + type: 'api_authorization_error'; + }; + }; + /** + * Default Response + */ + 404: { + error: { + message: string; + type: 'api_not_found_error'; + }; + }; + /** + * Default Response + */ + 409: { + error: { + message: string; + type: 'api_conflict_error'; + }; + }; + /** + * Default Response + */ + 500: { + error: { + message: string; + type: 'api_internal_server_error'; + }; + }; +}; + +export type DeleteMcpRateLimitError = DeleteMcpRateLimitErrors[keyof DeleteMcpRateLimitErrors]; + +export type DeleteMcpRateLimitResponses = { + /** + * Default Response + */ + 200: { + success: boolean; + }; +}; + +export type DeleteMcpRateLimitResponse = DeleteMcpRateLimitResponses[keyof DeleteMcpRateLimitResponses]; + +export type GetMcpRateLimitData = { + body?: never; + path: { + id: string; + }; + query?: never; + url: '/api/mcp-rate-limits/{id}'; +}; + +export type GetMcpRateLimitErrors = { + /** + * Default Response + */ + 400: { + error: { + message: string; + type: 'api_validation_error'; + }; + }; + /** + * Default Response + */ + 401: { + error: { + message: string; + type: 'api_authentication_error'; + }; + }; + /** + * Default Response + */ + 403: { + error: { + message: string; + type: 'api_authorization_error'; + }; + }; + /** + * Default Response + */ + 404: { + error: { + message: string; + type: 'api_not_found_error'; + }; + }; + /** + * Default Response + */ + 409: { + error: { + message: string; + type: 'api_conflict_error'; + }; + }; + /** + * Default Response + */ + 500: { + error: { + message: string; + type: 'api_internal_server_error'; + }; + }; +}; + +export type GetMcpRateLimitError = GetMcpRateLimitErrors[keyof GetMcpRateLimitErrors]; + +export type GetMcpRateLimitResponses = { + /** + * Default Response + */ + 200: { + id: string; + agentId: string; + limitType: 'mcp_server_calls' | 'tool_calls'; + mcpServerName: string; + toolName: string | null; + maxCalls: number; + windowSeconds: number; + createdAt: string; + updatedAt: string; + }; +}; + +export type GetMcpRateLimitResponse = GetMcpRateLimitResponses[keyof GetMcpRateLimitResponses]; + +export type UpdateMcpRateLimitData = { + body?: { + limitType?: 'mcp_server_calls' | 'tool_calls'; + mcpServerName?: string; + toolName?: string | null; + maxCalls?: number; + windowSeconds?: number; + }; + path: { + id: string; + }; + query?: never; + url: '/api/mcp-rate-limits/{id}'; +}; + +export type UpdateMcpRateLimitErrors = { + /** + * Default Response + */ + 400: { + error: { + message: string; + type: 'api_validation_error'; + }; + }; + /** + * Default Response + */ + 401: { + error: { + message: string; + type: 'api_authentication_error'; + }; + }; + /** + * Default Response + */ + 403: { + error: { + message: string; + type: 'api_authorization_error'; + }; + }; + /** + * Default Response + */ + 404: { + error: { + message: string; + type: 'api_not_found_error'; + }; + }; + /** + * Default Response + */ + 409: { + error: { + message: string; + type: 'api_conflict_error'; + }; + }; + /** + * Default Response + */ + 500: { + error: { + message: string; + type: 'api_internal_server_error'; + }; + }; +}; + +export type UpdateMcpRateLimitError = UpdateMcpRateLimitErrors[keyof UpdateMcpRateLimitErrors]; + +export type UpdateMcpRateLimitResponses = { + /** + * Default Response + */ + 200: { + id: string; + agentId: string; + limitType: 'mcp_server_calls' | 'tool_calls'; + mcpServerName: string; + toolName: string | null; + maxCalls: number; + windowSeconds: number; + createdAt: string; + updatedAt: string; + }; +}; + +export type UpdateMcpRateLimitResponse = UpdateMcpRateLimitResponses[keyof UpdateMcpRateLimitResponses]; + export type GetMcpServerInstallationRequestsData = { body?: never; path?: never; diff --git a/platform/shared/permission.types.ts b/platform/shared/permission.types.ts index b305076f0b..82f44812c9 100644 --- a/platform/shared/permission.types.ts +++ b/platform/shared/permission.types.ts @@ -38,7 +38,8 @@ export const resources = [ "mcpToolCall", "team", "conversation", - "limit", + "llmTokenLimit", + "mcpRateLimit", "llmModels", "chatSettings", /** @@ -72,7 +73,8 @@ export const resourceLabels: Record = { team: "Teams", ac: "Access Control", conversation: "Conversations", - limit: "Limits", + llmTokenLimit: "LLM Token Limits", + mcpRateLimit: "MCP Rate Limits", llmModels: "LLM Models", chatSettings: "Chat Settings", }; diff --git a/platform/shared/routes.ts b/platform/shared/routes.ts index c09d617954..93c991949d 100644 --- a/platform/shared/routes.ts +++ b/platform/shared/routes.ts @@ -268,6 +268,13 @@ export const RouteId = { UpdateLimit: "updateLimit", DeleteLimit: "deleteLimit", + // MCP Rate Limit Routes + GetMcpRateLimits: "getMcpRateLimits", + CreateMcpRateLimit: "createMcpRateLimit", + GetMcpRateLimit: "getMcpRateLimit", + UpdateMcpRateLimit: "updateMcpRateLimit", + DeleteMcpRateLimit: "deleteMcpRateLimit", + // Organization Routes GetOrganization: "getOrganization", UpdateOrganization: "updateOrganization",