Commit graph

12 commits

Author SHA1 Message Date
ryan-crabbe-berri
c3bcb6f64f
test(mcp): drain the logging worker after each test so queued callbacks cannot leak into the next test (#38228)
LoggingWorker now carries still-queued coroutines onto the next event loop (12a34a10d8). Under xdist,
a success-logging coroutine queued by test_acompletion_mcp_respects_manual_approval ran nine seconds
later inside test_mcp_tool_call_hook on the same worker, resolved litellm.callbacks at run time and
overwrote that test's captured payload with a gpt-4o-mini completion (assert 1.35e-05 == 1.42).

Run clear_queue() in the suite's autouse teardown so every coroutine a test enqueues finishes before the
next test registers its callbacks, and add a subprocess regression test that runs the real conftest
against a stopped worker with work still queued.
2026-08-25 10:50:35 -07:00
Anmol Jaiswal
bb27bfd9a7
fix(http_handler): dispose aiohttp session when AsyncHTTPHandler is finalized without a running loop (#36670)
* fix(http_handler): dispose aiohttp session when finalized without a running loop

AsyncHTTPHandler.__del__ can only schedule an async close when a running
event loop exists at finalization time; in any other context (worker
threads whose loop has closed, sync contexts, interpreter shutdown) the
RuntimeError from get_running_loop() is swallowed and the underlying
aiohttp ClientSession is abandoned to GC, emitting 'Unclosed client
session' / 'Unclosed connector' warnings.

This is the disposal gap left after the recycle-time fix: clients created
for short-lived event loops (the loop-id-keyed LLM client cache mints one
handler per loop) are never recycled - they live and die with their loop,
and their finalization is precisely the loop-less case.

Fix:
- no running loop: fall back to the connector's synchronous teardown via
  LiteLLMAiohttpTransport._mark_connector_closed - the same finalizer-safe
  path used for dead-loop recycles - honoring _owns_session so a shared
  session is never closed.
- running loop: keep the async close, but hold a strong reference to the
  scheduled task until it completes (a bare create_task() result may be
  collected before running), mirroring _background_close_tasks.

Tests: loop-less finalization closes a dead-loop session; running-loop
finalization registers and drains the close task; the sync fallback
respects session ownership. All three fail without the fix.

* lint: conform new finalizer code to the type-discipline budget

Final on the five never-rebound locals (LIT010); the class-level task
registry keeps its mutable set with the sanctioned mutable-ok reason,
mirroring the aiohttp transport's registry (LIT001).

* lint: reasoned pyright ignore on the cross-class teardown call

The handler deliberately reuses the transport's finalizer-safe connector
teardown; no public seam exists and an async close can never run at
loop-less finalization. Clears the net-new reportPrivateUsage the
basedpyright budget gate flagged once the LIT stage passed.

* fix(http_handler): retrieve exceptions from finalizer close tasks

A bare discard done-callback dropped the task without consuming its
exception, so a failing aclose() emitted "Task exception was never
retrieved" at GC, the same noise class this path exists to remove.
Mirror the transport's _on_close_task_done: discard, early-return on
cancellation, retrieve and debug-log the exception.

* fix(http_handler): dispose foreign-loop sessions instead of scheduling aclose on the live loop

GC on a live loop (e.g. the app's) of a handler whose session belongs to
another, possibly dead, loop scheduled aclose() on the current loop, the
cross-loop path the transport refuses. Route both that case and the
loop-less case through the transport's lifecycle-aware
_close_recycled_session, which picks async close on the session's own
loop, threadsafe handoff, or the synchronous connector teardown.

Regression test: a dead-loop session collected while another loop runs
is disposed without scheduling anything on that loop.

* chore: retrigger CI (test_mcp_logging payload-order flake, also failed on litellm_spendlogs_fallback_metadata minutes earlier)

* test(mcp): select the MCP tool-call payload instead of the last-delivered one

TestMCPLogger kept a single last-writer slot; an async success event from
another call (a mocked acompletion whose log task lands late) races the
MCP event for it, so the cost assertions intermittently read the wrong
payload. This PR's finalizer change shifts task interleaving on the loop
and tips that latent race over (also seen on an unrelated PR minutes
earlier). Collect call_type=call_mcp_tool payloads in their own list and
assert on those.

* test(mcp): MCPLoggerHook inherits the order-independent payload capture

It duplicated TestMCPLogger's init and success handler verbatim; the
hook test reads the same MCP payload selection, so subclass instead.
2026-08-25 08:12:10 -07:00
yuneng-jiang
6a0d03914c
test: drop the cwd-relative sys.path.insert calls from the test suite (#37802)
* test: drop the cwd-relative sys.path.insert calls from the test suite

TQ003 stands at 1,077 across 1,058 files, and 1,015 of them are the same shape:
sys.path.insert(0, os.path.abspath("../..")) and its deeper siblings. The
argument resolves against the working directory rather than the file, so from
the repo root, where every job runs pytest, it inserts the directory two levels
above the checkout. It has never pointed at litellm. The package is installed
into the environment anyway, which is what actually makes the import work, and
what the rule's message has said all along.

Removing them leaves 1,634 imports of sys and os with no remaining reference,
and those go too, except where another test module imports the name back out of
the file. The rest of TQ003 is 62 call sites that resolve against __file__ or a
variable, which are a different question and are left alone.

Collection is identical either way: 45,871 tests and the same 51 pre-existing
collection errors before and after, and ruff reports no new undefined name.

* test: drop the duplicate imports the sys.path sweep exposed to F811

* test(pre-call-utils): restore the os import the new bedrock tests need
2026-08-22 09:25:58 -07:00
Ishaan Jaffer
e8461b5b97
style: run black formatter on files from main merge 2026-04-17 13:02:59 -07:00
Julio Quinteros Pro
5bcd3b53c9 fix: import LiteLLM_ObjectPermissionTable from _types instead of proxy_server
Per maintainer feedback, FastAPI should always be available in proxy code.
The issue was that MCP tests were importing from proxy_server unnecessarily,
pulling in all proxy dependencies including policy_resolve_endpoints.

Fix:
- Revert policy_resolve_endpoints.py to use direct FastAPI imports
- Update MCP tests to import LiteLLM_ObjectPermissionTable from litellm.proxy._types
  instead of litellm.proxy.proxy_server

This avoids importing the entire proxy_server module with all its dependencies
when tests only need specific types.

Addresses: https://github.com/BerriAI/litellm/pull/21075/changes#r2802201174
2026-02-13 14:46:03 -03:00
Yuta Saito
df37770a70 test: add permission test 2026-01-14 07:58:21 +09:00
YutaSaito
b5133c4c7d
Feat/mcp preserve tool metadata calltoolresult (#17561)
* feat(mcp): preserve tool metadata and full CallToolResult in MCP gateway

This PR fixes two issues that prevented ChatGPT from rendering MCP UI widgets
when proxied through LiteLLM:

1. Preserve Tool Metadata in tools/list
   - Modified _create_prefixed_tools() to mutate tools in place instead of
     reconstructing them, preserving all fields including metadata/_meta
   - This ensures ChatGPT can see 'openai/outputTemplate' URIs in tools/list
     and will call resources/read to fetch widgets

2. Preserve Full CallToolResult (structuredContent + metadata)
   - Changed call_mcp_tool() and _handle_managed_mcp_tool() to return full
     CallToolResult objects instead of just content
   - Updated error handlers to return CallToolResult with isError flag
   - Wrapped local tool results in CallToolResult objects
   - This preserves structuredContent and metadata fields needed for widget rendering

Files changed:
- litellm/proxy/_experimental/mcp_server/mcp_server_manager.py
- litellm/proxy/_experimental/mcp_server/server.py

Fixes issues where ChatGPT could not render MCP UI widgets when using
LiteLLM as an MCP gateway.

* feat(mcp): Preserve tool metadata and return full CallToolResult for ChatGPT UI widgets

- Preserve metadata and _meta fields when creating prefixed tools
- Return full CallToolResult instead of just content list
- Ensures ChatGPT can discover and render UI widgets via openai/outputTemplate
- Fixes metadata stripping that prevented widget rendering in ChatGPT

Changes:
- mcp_server_manager.py: Mutate tools in place to preserve all fields including metadata
- server.py: Return CallToolResult with structuredContent and metadata preserved
- Added test to verify metadata preservation

* fix: guard cost calculator when BaseModel lacks _hidden_params

---------

Co-authored-by: Afroz Ahmad <aahmad@Afrozs-MacBook-Pro.local>
Co-authored-by: Afroz Ahmad <aahmad@KNDMCPTMZH3.sephoraus.com>
2025-12-05 16:15:22 -08:00
Ishaan Jaffer
6530749c89 test fixes 2025-11-26 11:42:12 -08:00
YutaSaito
0b586d26fc
refactor: drop MCPClient.connect and use run_with_session lifecycle (#16696)
Surface detailed connection errors by handling HTTP failures
2025-11-15 17:54:27 -08:00
YutaSaito
f487f4e3a9
feat: add dynamic OAuth2 metadata discovery for MCP servers (#16676)
* feat: add dynamic OAuth2 metadata discovery for MCP servers

* fix: lint error
2025-11-14 18:14:43 -08:00
Ishaan Jaff
35bfa7c9ee
[MCP Gateway] - Allow using custom post call MCP hook for cost tracking (#12469)
* add async_post_mcp_tool_call_hook to custom logger

* instrument call_mcp_tool

* add async_post_mcp_tool_call_hook

* fixes for MCP tests

* test fix MCP tests

* allow adding custom cost for MCP response

* fix LiteLLM_MCPTextContent

* add MCPPostCallResponseObject

* fix _handle_local_mcp_tool

* async_post_mcp_tool_call_hook

* fix typing loc

* fix call_mcp_tool

* fix server.py

* fix MCPPostCallResponseObject

* async_post_mcp_tool_call_hook

* docs mcp cost hook

* fix import path
2025-07-09 18:41:08 -07:00
Ishaan Jaff
5cad0dd94b
[Feat] Add MCP Cost Tracking (#12385)
* fix MCP_TOOL_NAME_PREFIX

* test_mcp_cost_tracking

* init MCPCostCalculator

* add call_mcp_tool

* calculate_mcp_tool_call_cost

* add MCPServerCostInfo

* add mcp_server_cost_info

* add mcp_server_cost_info

* pass through litellm_logging_obj

* logged_standard_logging_payload

* fix logging MCP tool call

* test_mcp_cost_tracking_per_tool

* fix NewMCPServerRequest

* fix add_update_server

* add MCP info to schema.prisma

* fix create_mcp_server

* working custom cost per call

* fix mcp server cost

* fix MCPCostCalculator

* TestMCPCostCalculator
2025-07-07 21:08:10 -07:00