* fix(mcp): fix OpenAPI OAuth flow — transport mapping, error messages, and discovery bypass
Three bugs fixed to make the end-to-end OAuth flow work for OpenAPI MCP servers:
1. **Transport mapping in getTemporaryPayload**: `TRANSPORT.OPENAPI` is a UI-only concept;
the backend only accepts `"http"`, `"sse"`, or `"stdio"`. The pre-OAuth temp-session
call was sending `transport: "openapi"` and getting a 422. Fixed by mapping to `"http"`.
2. **deriveErrorMessage handles FastAPI 422 arrays**: FastAPI validation errors return
`detail` as an array of `{loc, msg, type}` objects. The shared error extractor was
returning the array directly, causing `Error: [object Object]`. Fixed to map each
item to its `.msg` field.
3. **Skip OAuth discovery when authorization_url already provided**: `build_mcp_server_from_table`
was unconditionally calling `_descovery_metadata(server_url)` for OAuth servers. For
OpenAPI servers the url is the spec JSON file, not the API base — this caused a timeout
fetching e.g. the GitHub spec (2 MB). Fixed by skipping discovery when `authorization_url`
is already set.
Also: collapsible auth section in MCP server form, "Create OAuth App →" link next to
Client ID when a docs URL is available (e.g. GitHub OAuth App creation page), and
`extractErrorMessage` helper in `useMcpOAuthFlow` for cleaner error display.
* refactor(mcp): extract needs_discovery flag and reduceStaticHeaders helper
* feat(mcp): user OAuth connect flow — OAuthConnectModal, MCPCredentialsTab, useUserMcpOAuthFlow
Adds the user-facing MCP OAuth2 PKCE connect flow:
- OAuthConnectModal: modal that launches the PKCE flow for a user to connect to an MCP server
- MCPCredentialsTab: credentials management tab in the MCP apps panel
- useUserMcpOAuthFlow: hook that handles the full PKCE auth code exchange for user-level connections
- MCPAppsPanel: wires up the new credentials tab and connect modal
- ChatPage: further cleanup after responses-API revert
- db.py / mcp_management_endpoints.py / _types.py: backend support for storing user MCP credentials
* fix(mcp): make client_id optional in /authorize — use server's stored client_id when not provided
* address greptile review feedback
* fix(mcp): narrow bare except to RecordNotFoundError in BYOK credential delete
* refactor(mcp): move inline imports to module level in db.py
* docs(claude): add MCP OAuth, transport mapping, and browser storage patterns
* fix(security): remove accessToken from sessionStorage in OAuth flow state
The LiteLLM API key was being serialised into sessionStorage as part of
StoredFlowState. After the OAuth redirect the component re-mounts with the
same accessToken prop, so it never needed to be stored. Read it from props
in resumeOAuthFlow instead.
* fix(ui): remove duplicate extractErrorMessage, sessionStorage-only in admin OAuth hook, call delete API on disconnect
* fix(ui): guard resumeOAuthFlow against wrong hook instance consuming OAuth result
* fix(ui): separate OAuth result keys per flow, sessionStorage-only, surface revoke errors
* fix(ui): remove dead OAuthConnectModal, revert tsconfig jsx mode to preserve
* fix(mcp): guard BYOK overwrite in oauth credential store, raise clear error when client_id absent
* fix: forward OAuth error params in callback, fix BYOK guard exception handling in db.py
10 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Development Commands
Installation
make install-dev- Install core development dependenciesmake install-proxy-dev- Install proxy development dependencies with full feature setmake install-test-deps- Install all test dependencies
Testing
make test- Run all testsmake test-unit- Run unit tests (tests/test_litellm) with 4 parallel workersmake test-integration- Run integration tests (excludes unit tests)pytest tests/- Direct pytest execution
Code Quality
make lint- Run all linting (Ruff, MyPy, Black, circular imports, import safety)make format- Apply Black code formattingmake lint-ruff- Run Ruff linting onlymake lint-mypy- Run MyPy type checking only
Single Test Files
poetry run pytest tests/path/to/test_file.py -v- Run specific test filepoetry run pytest tests/path/to/test_file.py::test_function -v- Run specific test
Running Scripts
poetry run python script.py- Run Python scripts (use for non-test files)
GitHub Issue & PR Templates
When contributing to the project, use the appropriate templates:
Bug Reports (.github/ISSUE_TEMPLATE/bug_report.yml):
- Describe what happened vs. what you expected
- Include relevant log output
- Specify your LiteLLM version
Feature Requests (.github/ISSUE_TEMPLATE/feature_request.yml):
- Describe the feature clearly
- Explain the motivation and use case
Pull Requests (.github/pull_request_template.md):
- Add at least 1 test in
tests/litellm/ - Ensure
make test-unitpasses
Architecture Overview
LiteLLM is a unified interface for 100+ LLM providers with two main components:
Core Library (litellm/)
- Main entry point:
litellm/main.py- Contains core completion() function - Provider implementations:
litellm/llms/- Each provider has its own subdirectory - Router system:
litellm/router.py+litellm/router_utils/- Load balancing and fallback logic - Type definitions:
litellm/types/- Pydantic models and type hints - Integrations:
litellm/integrations/- Third-party observability, caching, logging - Caching:
litellm/caching/- Multiple cache backends (Redis, in-memory, S3, etc.)
Proxy Server (litellm/proxy/)
- Main server:
proxy_server.py- FastAPI application - Authentication:
auth/- API key management, JWT, OAuth2 - Database:
db/- Prisma ORM with PostgreSQL/SQLite support - Management endpoints:
management_endpoints/- Admin APIs for keys, teams, models - Pass-through endpoints:
pass_through_endpoints/- Provider-specific API forwarding - Guardrails:
guardrails/- Safety and content filtering hooks - UI Dashboard: Served from
_experimental/out/(Next.js build)
Key Patterns
Provider Implementation
- Providers inherit from base classes in
litellm/llms/base.py - Each provider has transformation functions for input/output formatting
- Support both sync and async operations
- Handle streaming responses and function calling
Error Handling
- Provider-specific exceptions mapped to OpenAI-compatible errors
- Fallback logic handled by Router system
- Comprehensive logging through
litellm/_logging.py
Configuration
- YAML config files for proxy server (see
proxy/example_config_yaml/) - Environment variables for API keys and settings
- Database schema managed via Prisma (
proxy/schema.prisma)
Development Notes
Code Style
- Uses Black formatter, Ruff linter, MyPy type checker
- Pydantic v2 for data validation
- Async/await patterns throughout
- Type hints required for all public APIs
- Avoid imports within methods — place all imports at the top of the file (module-level). Inline imports inside functions/methods make dependencies harder to trace and hurt readability. The only exception is avoiding circular imports where absolutely necessary.
Testing Strategy
- Unit tests in
tests/test_litellm/ - Integration tests for each provider in
tests/llm_translation/ - Proxy tests in
tests/proxy_unit_tests/ - Load tests in
tests/load_tests/ - Always add tests when adding new entity types or features — if the existing test file covers other entity types, add corresponding tests for the new one
UI / Backend Consistency
- When wiring a new UI entity type to an existing backend endpoint, verify the backend API contract (single value vs. array, required vs. optional params) and ensure the UI controls match — e.g., use a single-select dropdown when the backend accepts a single value, not a multi-select
MCP OAuth / OpenAPI Transport Mapping
TRANSPORT.OPENAPIis a UI-only concept. The backend only accepts"http","sse", or"stdio". Always map it to"http"before any API call (including pre-OAuth temp-session calls).- FastAPI validation errors return
detailas an array of{loc, msg, type}objects. Error extractors must handle: array (map.msg), string, nested{error: string}, and fallback. - When an MCP server already has
authorization_urlstored, skip OAuth discovery (_discovery_metadata) — the server URL for OpenAPI MCPs is the spec file, not the API base, and fetching it causes timeouts. client_idshould be optional in the/authorizeendpoint — if the server has a storedclient_idin credentials, use that. Never require callers to re-supply it.
MCP Credential Storage
- OAuth credentials and BYOK credentials share the
litellm_mcpusercredentialstable, distinguished by a"type"field in the JSON payload ("oauth2"vs plain string). - When deleting OAuth credentials, check type before deleting to avoid accidentally deleting a BYOK credential for the same
(user_id, server_id)pair. - Always pass the raw
expires_attimestamp to the client — never set it toNonefor expired credentials. Let the frontend compute the "Expired" display state from the timestamp. - Use
RecordNotFoundError(not bareexcept Exception) when catching "already deleted" in credential delete endpoints.
Browser Storage Safety (UI)
- Never write LiteLLM access tokens or API keys to
localStorage— usesessionStorageonly.localStoragesurvives browser close and is readable by any injected script (XSS). - Shared utility functions (e.g.
extractErrorMessage) belong insrc/utils/— never define them inline in hooks or duplicate them across files.
Database Migrations
- Prisma handles schema migrations
- Migration files auto-generated with
prisma migrate dev - Always test migrations against both PostgreSQL and SQLite
Proxy database access
- Do not write raw SQL for proxy DB operations. Use Prisma model methods instead of
execute_raw/query_raw. - Use the generated client:
prisma_client.db.<model>(e.g.litellm_tooltable,litellm_usertable) with.upsert(),.find_many(),.find_unique(),.update(),.update_many()as appropriate. This avoids schema/client drift, keeps code testable with simple mocks, and matches patterns used in spend logs and other proxy code. - No N+1 queries. Never query the DB inside a loop. Batch-fetch with
{"in": ids}and distribute in-memory. - Batch writes. Use
create_many/update_many/delete_manyinstead of individual calls (these return counts only;update_many/delete_manyno-op silently on missing rows). When multiple separate writes target the same table (e.g. inbatch_()), order by primary key to avoid deadlocks. - Push work to the DB. Filter, sort, group, and aggregate in SQL, not Python. Verify Prisma generates the expected SQL — e.g. prefer
group_byoverfind_many(distinct=...)which does client-side processing. - Bound large result sets. Prisma materializes full results in memory. For results over ~10 MB, paginate with
take/skiporcursor/take, always with an explicitorder. Prefer cursor-based pagination (skipis O(n)). Don't paginate naturally small result sets. - Limit fetched columns on wide tables. Use
selectto fetch only needed fields — returns a partial object, so downstream code must not access unselected fields. - Check index coverage. For new or modified queries, check
schema.prismafor a supporting index. Prefer extending an existing index (e.g.@@index([a])→@@index([a, b])) over adding a new one, unless it's a@@unique. Only add indexes for large/frequent queries. - Keep schema files in sync. Apply schema changes to all
schema.prismacopies (schema.prisma,litellm/proxy/,litellm-proxy-extras/,litellm-js/spend-logs/for SpendLogs) with a migration underlitellm-proxy-extras/litellm_proxy_extras/migrations/.
Enterprise Features
- Enterprise-specific code in
enterprise/directory - Optional features enabled via environment variables
- Separate licensing and authentication for enterprise features
HTTP Client Cache Safety
- Never close HTTP/SDK clients on cache eviction.
LLMClientCache._remove_key()must not callclose()/aclose()on evicted clients — they may still be used by in-flight requests. Doing so causesRuntimeError: Cannot send a request, as the client has been closed.after the 1-hour TTL expires. Cleanup happens at shutdown viaclose_litellm_async_clients().
Troubleshooting: DB schema out of sync after proxy restart
litellm-proxy-extras runs prisma migrate deploy on startup using its own bundled migration files, which may lag behind schema changes in the current worktree. Symptoms: Unknown column, Invalid prisma invocation, or missing data on new fields.
Diagnose: Run \d "TableName" in psql and compare against schema.prisma — missing columns confirm the issue.
Fix options:
- Create a Prisma migration (permanent) — run
prisma migrate dev --name <description>in the worktree. The generated file will be picked up byprisma migrate deployon next startup. - Apply manually for local dev —
psql -d litellm -c "ALTER TABLE ... ADD COLUMN IF NOT EXISTS ..."after each proxy start. Fine for dev, not for production. - Update litellm-proxy-extras — if the package is installed from PyPI, its migration directory must include the new file. Either update the package or run the migration manually until the next release ships it.