type + more codex docs
This commit is contained in:
@@ -1,24 +1,6 @@
|
|||||||
# talk w/ me
|
|
||||||
|
|
||||||
help me understand. i am tired, be clear
|
|
||||||
|
|
||||||
# pitfalls
|
# pitfalls
|
||||||
|
|
||||||
no: assert dict == dict
|
no: assert dict == dict
|
||||||
yes: assert dict['field'] == dict['field'] unless we know better
|
yes: assert dict['field'] == dict['field'] unless we know better
|
||||||
|
|
||||||
more later
|
more later
|
||||||
|
|
||||||
# when you move through code, add docstrings / comment weird/complicated logic
|
|
||||||
|
|
||||||
# use available MCP tools/skills
|
|
||||||
|
|
||||||
- serena mcp: symbol discovery. Has some of LSP stuff
|
|
||||||
- context7: docs
|
|
||||||
|
|
||||||
- skills: outside of workspace, request commands. Use when appropiate
|
|
||||||
|
|
||||||
# docs/ pair with `superpowers` and other skills
|
|
||||||
|
|
||||||
superpowers, a Codex-cli plugin, is a set of skills. plugin provided skills live in nonstandard paths. check paths given by Codex
|
|
||||||
if not found, might be stale hash, try find new hash
|
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
# FastMCP Proxy ResourceLink Issue Guide
|
||||||
|
|
||||||
|
Use this as a copy/rewrite starting point for an upstream FastMCP issue. Keep
|
||||||
|
the code blocks and concrete examples; rewrite the prose however you want.
|
||||||
|
|
||||||
|
## Suggested Title
|
||||||
|
|
||||||
|
```text
|
||||||
|
Namespace transform rewrites listed resource URIs but not ResourceLink URIs returned from proxied tools
|
||||||
|
```
|
||||||
|
|
||||||
|
## Short Summary
|
||||||
|
|
||||||
|
```text
|
||||||
|
When a FastMCP proxy has a Namespace transform, listed resources and resource
|
||||||
|
templates are correctly namespaced, and namespaced resource reads work.
|
||||||
|
However, ResourceLink content returned inside a proxied tools/call result keeps
|
||||||
|
the raw upstream URI instead of the downstream namespaced URI.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Why This Looks Like A Proxy Bug
|
||||||
|
|
||||||
|
```text
|
||||||
|
The proxy is already doing the hard part correctly for normal resource surfaces:
|
||||||
|
|
||||||
|
- resources/list returns namespaced URIs
|
||||||
|
- resource_templates/list returns namespaced URI templates
|
||||||
|
- resources/read accepts the namespaced URI and reverses it upstream
|
||||||
|
|
||||||
|
Only ResourceLink content inside tools/call results escapes with the upstream
|
||||||
|
URI unchanged. That makes the returned link unusable from the downstream client
|
||||||
|
even though the same resource is available through the proxy under its
|
||||||
|
namespaced URI.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Minimal Example
|
||||||
|
|
||||||
|
```python
|
||||||
|
from fastmcp.server import create_proxy
|
||||||
|
from fastmcp.server.transforms import Namespace
|
||||||
|
|
||||||
|
proxy = create_proxy(upstream_client, name="Proxy-everything.default")
|
||||||
|
proxy.add_transform(Namespace("everything.default"))
|
||||||
|
```
|
||||||
|
|
||||||
|
Given an upstream resource:
|
||||||
|
|
||||||
|
```text
|
||||||
|
demo://resource/dynamic/text/2
|
||||||
|
```
|
||||||
|
|
||||||
|
The proxy correctly exposes it as:
|
||||||
|
|
||||||
|
```text
|
||||||
|
demo://everything.default/resource/dynamic/text/2
|
||||||
|
```
|
||||||
|
|
||||||
|
But if an upstream tool returns:
|
||||||
|
|
||||||
|
```python
|
||||||
|
mcp.types.ResourceLink(
|
||||||
|
type="resource_link",
|
||||||
|
name="dynamic-text",
|
||||||
|
uri="demo://resource/dynamic/text/2",
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
the proxied tool result still contains:
|
||||||
|
|
||||||
|
```text
|
||||||
|
demo://resource/dynamic/text/2
|
||||||
|
```
|
||||||
|
|
||||||
|
instead of:
|
||||||
|
|
||||||
|
```text
|
||||||
|
demo://everything.default/resource/dynamic/text/2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Observed Behavior
|
||||||
|
|
||||||
|
```text
|
||||||
|
resources/list:
|
||||||
|
upstream: demo://resource/dynamic/text/2
|
||||||
|
proxied: demo://everything.default/resource/dynamic/text/2
|
||||||
|
|
||||||
|
resources/read:
|
||||||
|
demo://everything.default/resource/dynamic/text/2
|
||||||
|
-> works
|
||||||
|
|
||||||
|
tools/call result content:
|
||||||
|
ResourceLink.uri == demo://resource/dynamic/text/2
|
||||||
|
-> raw upstream URI leaks through unchanged
|
||||||
|
```
|
||||||
|
|
||||||
|
## Expected Behavior
|
||||||
|
|
||||||
|
```text
|
||||||
|
If a proxy transform rewrites a resource URI for resources/list and
|
||||||
|
resources/read, ResourceLink content emitted by proxied tools should expose the
|
||||||
|
same downstream-facing URI.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Relevant FastMCP Code Paths
|
||||||
|
|
||||||
|
`Namespace` already owns the URI mapping:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Namespace(Transform):
|
||||||
|
async def list_resources(...):
|
||||||
|
...
|
||||||
|
|
||||||
|
async def get_resource(...):
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
`ProxyTool.run(...)` currently returns upstream tool content unchanged:
|
||||||
|
|
||||||
|
```python
|
||||||
|
result = await client.call_tool_mcp(
|
||||||
|
name=backend_name,
|
||||||
|
arguments=arguments,
|
||||||
|
meta=meta,
|
||||||
|
)
|
||||||
|
|
||||||
|
return ToolResult(
|
||||||
|
content=result.content,
|
||||||
|
structured_content=result.structuredContent,
|
||||||
|
meta=result.meta,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
FastMCP's `Transform` base class has hooks for listing/getting tools,
|
||||||
|
resources, templates, and prompts, but there does not appear to be a hook for
|
||||||
|
transforming tool results.
|
||||||
|
|
||||||
|
## Suggested Direction
|
||||||
|
|
||||||
|
```text
|
||||||
|
Prefer a general result-transform hook over special-casing Namespace logic in
|
||||||
|
ProxyTool.
|
||||||
|
```
|
||||||
|
|
||||||
|
For example, something in this shape:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Transform:
|
||||||
|
async def tool_result(self, result: ToolResult) -> ToolResult:
|
||||||
|
return result
|
||||||
|
```
|
||||||
|
|
||||||
|
Then `Namespace` could rewrite only typed MCP resource-link content:
|
||||||
|
|
||||||
|
```python
|
||||||
|
if isinstance(content, mcp.types.ResourceLink):
|
||||||
|
content = content.model_copy(
|
||||||
|
update={"uri": self._transform_uri(str(content.uri))}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
That would keep URI rewriting logic in `Namespace`, where the forward and
|
||||||
|
reverse resource URI mapping already lives.
|
||||||
|
|
||||||
|
## Important Boundary
|
||||||
|
|
||||||
|
```text
|
||||||
|
This is specifically about mcp.types.ResourceLink content inside tools/call
|
||||||
|
results. Normal resources/list, resource_templates/list, and resources/read
|
||||||
|
already behave correctly through the proxy.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Extra Note
|
||||||
|
|
||||||
|
```text
|
||||||
|
Session-scoped resources returned by some tools may have additional lifecycle
|
||||||
|
constraints beyond URI rewriting. This issue is about ordinary ResourceLink URI
|
||||||
|
projection only.
|
||||||
|
```
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
---
|
||||||
|
title: Namespace transform does not rewrite ResourceLink URIs returned from proxied tools
|
||||||
|
---
|
||||||
|
|
||||||
|
## summary
|
||||||
|
|
||||||
|
With a `Namespace` transform on a proxy, listed resources are namespaced
|
||||||
|
correctly.
|
||||||
|
However, `ResourceLink.uri` values returned from a proxied
|
||||||
|
`tools/call` result keep the upstream URI instead of the proxied URI.
|
||||||
|
|
||||||
|
Tested on FastMCP `3.3.0`.
|
||||||
|
|
||||||
|
## minimal example
|
||||||
|
|
||||||
|
```python
|
||||||
|
from fastmcp.server import create_proxy
|
||||||
|
from fastmcp.server.transforms import Namespace
|
||||||
|
|
||||||
|
proxy = create_proxy(upstream_client, name="Proxy-everything")
|
||||||
|
proxy.add_transform(Namespace("everything"))
|
||||||
|
```
|
||||||
|
|
||||||
|
An upstream resource looking like this:
|
||||||
|
|
||||||
|
```text
|
||||||
|
demo://resource/dynamic/text/2
|
||||||
|
```
|
||||||
|
|
||||||
|
will be exposed correctly as:
|
||||||
|
|
||||||
|
```text
|
||||||
|
demo://everything/resource/dynamic/text/2
|
||||||
|
```
|
||||||
|
|
||||||
|
But if an upstream tool returns:
|
||||||
|
|
||||||
|
```python
|
||||||
|
mcp.types.ResourceLink(
|
||||||
|
type="resource_link",
|
||||||
|
name="dynamic-text",
|
||||||
|
uri="demo://resource/dynamic/text/2",
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
the proxied tool result still contains:
|
||||||
|
|
||||||
|
```text
|
||||||
|
demo://resource/dynamic/text/2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Observed Behavior
|
||||||
|
|
||||||
|
```text
|
||||||
|
resources/list:
|
||||||
|
upstream: demo://resource/dynamic/text/2
|
||||||
|
proxied: demo://everything/resource/dynamic/text/2
|
||||||
|
|
||||||
|
resources/read:
|
||||||
|
demo://everything/resource/dynamic/text/2
|
||||||
|
-> works
|
||||||
|
|
||||||
|
tools/call result content:
|
||||||
|
ResourceLink.uri == demo://resource/dynamic/text/2
|
||||||
|
-> raw upstream URI leaks through unchanged
|
||||||
|
```
|
||||||
|
|
||||||
|
## expected behavior
|
||||||
|
|
||||||
|
`ResourceLink` content returned by proxied tools should expose the same proxied
|
||||||
|
URI as `resources/list` and `resources/read`.
|
||||||
|
|
||||||
|
## scope of this issue
|
||||||
|
|
||||||
|
This is specifically about `mcp.types.ResourceLink` content inside `tools/call`
|
||||||
|
results. Normal `resources/list`, `resource_templates/list`, and
|
||||||
|
`resources/read` already behave correctly through the proxy.
|
||||||
|
|
||||||
|
Session-scoped resources returned by some tools may have additional lifecycle
|
||||||
|
constraints beyond URI rewriting. This issue is about ordinary ResourceLink URI
|
||||||
|
projection only.
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
# Proxy Mount Lifecycle Design
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Reduce avoidable proxy churn during `ProxyRuntime.reload()` without pretending
|
||||||
|
that FastMCP's missing unmount lifecycle is solved.
|
||||||
|
|
||||||
|
The next step is not "fully correct hot reload." The next step is to introduce a
|
||||||
|
small mount registry that can reuse unchanged enabled upstream proxy mounts
|
||||||
|
across reloads, while keeping the current best-effort remount behavior for
|
||||||
|
changed or removed connections.
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
|
||||||
|
`ProxyRuntime.reload()` currently:
|
||||||
|
|
||||||
|
1. reloads config
|
||||||
|
2. resets the server provider list to `local_provider`
|
||||||
|
3. recreates the admin mount
|
||||||
|
4. recreates a `Client`, `FastMCPProxy`, and `Namespace(connection.id)` mount for
|
||||||
|
every enabled connection
|
||||||
|
|
||||||
|
This is simple and currently works, but it churns every upstream proxy mount on
|
||||||
|
every reload even when a connection is unchanged.
|
||||||
|
|
||||||
|
FastMCP does not currently expose a complete official mount/unmount lifecycle
|
||||||
|
that we can rely on for safe per-connection teardown. The codebase should keep
|
||||||
|
treating reload as best-effort until that exists.
|
||||||
|
|
||||||
|
## Non-Goals
|
||||||
|
|
||||||
|
- Do not implement a custom general-purpose unmount system.
|
||||||
|
- Do not claim long-lived subscriptions survive reload.
|
||||||
|
- Do not wire proxy-result `ResourceLink` rewriting into runtime as part of this
|
||||||
|
work.
|
||||||
|
- Do not replace FastMCP's own proxy/provider internals.
|
||||||
|
- Do not change public MCP naming or config formats.
|
||||||
|
|
||||||
|
## Recommended Design
|
||||||
|
|
||||||
|
Add a dedicated module:
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/wf_mcp/transparent_proxy/mounts.py
|
||||||
|
```
|
||||||
|
|
||||||
|
with two internal models:
|
||||||
|
|
||||||
|
```python
|
||||||
|
ProxyMount
|
||||||
|
ProxyMountRegistry
|
||||||
|
```
|
||||||
|
|
||||||
|
### `ProxyMount`
|
||||||
|
|
||||||
|
Owns the mount objects created for one enabled connection:
|
||||||
|
|
||||||
|
- `connection_id`
|
||||||
|
- a stable config fingerprint
|
||||||
|
- the FastMCP proxy object returned by `create_proxy(...)`
|
||||||
|
- any future lifecycle metadata we need
|
||||||
|
|
||||||
|
It should not initially promise ownership of client shutdown. FastMCP owns too
|
||||||
|
much of that behavior today through its proxy/client factory internals.
|
||||||
|
|
||||||
|
### `ProxyMountRegistry`
|
||||||
|
|
||||||
|
Owns reusable mounts keyed by `connection_id`.
|
||||||
|
|
||||||
|
Minimal responsibilities:
|
||||||
|
|
||||||
|
- return an existing mount when the enabled connection fingerprint is unchanged
|
||||||
|
- create a new mount when the connection is new or materially changed
|
||||||
|
- report which cached mounts are no longer active after a reload
|
||||||
|
- keep the stale/retired concept explicit so later cleanup can be added in one
|
||||||
|
place when FastMCP exposes a safe lifecycle API
|
||||||
|
|
||||||
|
Initial API shape:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class ProxyMountRegistry:
|
||||||
|
def get_or_create(self, connection: ConnectionConfig, *, store_root: Path) -> ProxyMount:
|
||||||
|
...
|
||||||
|
|
||||||
|
def active_mounts_for(self, config: BrokerConfig) -> list[ProxyMount]:
|
||||||
|
...
|
||||||
|
|
||||||
|
def retired_connection_ids(self, active_connection_ids: set[str]) -> set[str]:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
The exact API can shrink during implementation; the boundary is more important
|
||||||
|
than these method names.
|
||||||
|
|
||||||
|
## Reload Flow
|
||||||
|
|
||||||
|
Target flow:
|
||||||
|
|
||||||
|
```text
|
||||||
|
reload config
|
||||||
|
validate config
|
||||||
|
reset mounted providers to local_provider
|
||||||
|
mount admin surface
|
||||||
|
ask ProxyMountRegistry for active enabled mounts
|
||||||
|
mount each active proxy
|
||||||
|
publish reload events
|
||||||
|
return ProxyReloadResult
|
||||||
|
```
|
||||||
|
|
||||||
|
For an unchanged enabled connection, the same proxy mount object should be
|
||||||
|
reused across reloads.
|
||||||
|
|
||||||
|
For a changed connection, create a new mount using the new config fingerprint.
|
||||||
|
|
||||||
|
For a disabled or removed connection, stop mounting it and mark its cached mount
|
||||||
|
retired. Do not invent unsafe teardown yet.
|
||||||
|
|
||||||
|
## Fingerprint
|
||||||
|
|
||||||
|
Reuse should be based on the connection fields that affect upstream transport or
|
||||||
|
identity:
|
||||||
|
|
||||||
|
- `id`
|
||||||
|
- `server`
|
||||||
|
- `account`
|
||||||
|
- `enabled`
|
||||||
|
- `metadata`
|
||||||
|
|
||||||
|
The fingerprint must be deterministic and easy to test. It does not need to be a
|
||||||
|
cryptographic API contract; it is an internal reuse key.
|
||||||
|
|
||||||
|
## Why Not Just A Dict In `reload()`
|
||||||
|
|
||||||
|
A raw dictionary inside `ProxyRuntime.reload()` would work for the first happy
|
||||||
|
path, but it would bury lifecycle decisions in the loop:
|
||||||
|
|
||||||
|
- when a cached proxy is still valid
|
||||||
|
- when changed config invalidates it
|
||||||
|
- what "retired" means
|
||||||
|
- where future close/unmount logic belongs
|
||||||
|
|
||||||
|
That is exactly the logic likely to grow once FastMCP exposes a real lifecycle
|
||||||
|
API. A small registry keeps the future deletion/replacement localized.
|
||||||
|
|
||||||
|
## Relationship To FastMCP
|
||||||
|
|
||||||
|
FastMCP already gives us:
|
||||||
|
|
||||||
|
- `create_proxy(...)`
|
||||||
|
- `ProxyProvider`
|
||||||
|
- component-list caching
|
||||||
|
- forwarded advanced protocol behavior
|
||||||
|
|
||||||
|
We should keep using those pieces. This work should not subclass or monkeypatch
|
||||||
|
FastMCP.
|
||||||
|
|
||||||
|
If FastMCP later ships official dynamic provider unmount or proxy lifecycle
|
||||||
|
support, the registry should become thinner or disappear.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Add focused tests before implementation:
|
||||||
|
|
||||||
|
1. unchanged enabled connection reuses the same proxy mount across reloads
|
||||||
|
2. changed connection metadata produces a new mount
|
||||||
|
3. disabled or removed connection is not remounted
|
||||||
|
4. reload public payload remains unchanged
|
||||||
|
5. existing list-changed event/notification behavior remains unchanged
|
||||||
|
|
||||||
|
Do not write tests that imply retired mounts are safely closed until that is
|
||||||
|
actually implemented.
|
||||||
|
|
||||||
|
## Deferred Work
|
||||||
|
|
||||||
|
- safe close/unmount of retired mounts
|
||||||
|
- preserving long-lived subscriptions across reload
|
||||||
|
- forwarding upstream notifications through mount lifecycle changes
|
||||||
|
- wiring `wf_mcp.proxy_results` helpers into real tool-result handling
|
||||||
|
- replacing FastMCP behavior that should be fixed upstream instead
|
||||||
|
|
||||||
|
## Open Question
|
||||||
|
|
||||||
|
Whether reused mounts should keep FastMCP `ProxyProvider` component caches across
|
||||||
|
reload is not yet a promise. Reuse likely does preserve those caches. If that
|
||||||
|
causes stale catalog behavior, the registry should either invalidate the provider
|
||||||
|
cache explicitly when supported or treat reload as a reason to rebuild mounts
|
||||||
|
for affected connections.
|
||||||
@@ -4,6 +4,8 @@ from collections.abc import Sequence
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from fastmcp.tools import Tool
|
||||||
|
|
||||||
from ..shared.names import is_admin_tool_name, parse_namespaced_tool_name
|
from ..shared.names import is_admin_tool_name, parse_namespaced_tool_name
|
||||||
from ..shared.pagination import paginate_items
|
from ..shared.pagination import paginate_items
|
||||||
|
|
||||||
@@ -61,7 +63,7 @@ def proxy_tool_payload(
|
|||||||
proxy_name: str,
|
proxy_name: str,
|
||||||
connection_id: str,
|
connection_id: str,
|
||||||
local_name: str,
|
local_name: str,
|
||||||
tool: Any,
|
tool: Tool,
|
||||||
include_schema: bool,
|
include_schema: bool,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Return the admin-facing metadata payload for one proxied tool."""
|
"""Return the admin-facing metadata payload for one proxied tool."""
|
||||||
@@ -82,7 +84,7 @@ def proxy_tool_payload(
|
|||||||
|
|
||||||
def collect_proxy_tools(
|
def collect_proxy_tools(
|
||||||
*,
|
*,
|
||||||
tools: Sequence[Any],
|
tools: Sequence[Tool],
|
||||||
connection_ids: set[str],
|
connection_ids: set[str],
|
||||||
) -> list[ProxyToolPayload]:
|
) -> list[ProxyToolPayload]:
|
||||||
"""Collect visible upstream tool metadata from FastMCP's listed tools."""
|
"""Collect visible upstream tool metadata from FastMCP's listed tools."""
|
||||||
@@ -113,7 +115,7 @@ def collect_proxy_tools(
|
|||||||
|
|
||||||
def collect_proxy_tool_payloads(
|
def collect_proxy_tool_payloads(
|
||||||
*,
|
*,
|
||||||
tools: Sequence[Any],
|
tools: Sequence[Tool],
|
||||||
connection_ids: set[str],
|
connection_ids: set[str],
|
||||||
include_schema: bool,
|
include_schema: bool,
|
||||||
) -> list[dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
|
|||||||
@@ -279,9 +279,43 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastmcp"
|
name = "fastmcp"
|
||||||
version = "3.2.4"
|
version = "3.3.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
{ name = "fastmcp-slim", extra = ["client", "server"] },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/1a/b8/aff9378edc9438916ce5f06ac31bc1d60dd99e1a82411e186f1c38e20a8b/fastmcp-3.3.0.tar.gz", hash = "sha256:48c7fffdb6865cb9658ac02c2ff589caaaa3cd68e2cc37ed51402fa46e46c2eb", size = 28804871, upload-time = "2026-05-15T02:04:59.357Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ff/bd/4f21d58764e1f1e1237e29bc58bb1b5863fde2297796c8e96301d334fe2d/fastmcp-3.3.0-py3-none-any.whl", hash = "sha256:e6b1dc391a9fcc4b6a7f0bb7c2481d1aac8d03eefe818908c69909795a39d51b", size = 7903, upload-time = "2026-05-15T02:05:02.24Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastmcp-slim"
|
||||||
|
version = "3.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "platformdirs" },
|
||||||
|
{ name = "pydantic", extra = ["email"] },
|
||||||
|
{ name = "pydantic-settings" },
|
||||||
|
{ name = "python-dotenv" },
|
||||||
|
{ name = "rich" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/db/6e/f4ba7d4f5d586ee85b7e2dd4feff89112de0e4cedc6a5fda794adb6b99c2/fastmcp_slim-3.3.0.tar.gz", hash = "sha256:56fd0077226b8dbf0bba253f9baaad5d97a3f74eb62750d6997128486b91b0ba", size = 567972, upload-time = "2026-05-15T02:04:34.015Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/96/36/fa34dd253426e03e4f68918ca700758a22f3600fb6d4dc5a260b3d152c82/fastmcp_slim-3.3.0-py3-none-any.whl", hash = "sha256:7478c5220e06e5ff4f9cb5270e46c6b6d0f44ec0d79372a572b0a71195baa69a", size = 739366, upload-time = "2026-05-15T02:04:32.528Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
client = [
|
||||||
|
{ name = "authlib" },
|
||||||
|
{ name = "exceptiongroup" },
|
||||||
|
{ name = "httpx" },
|
||||||
|
{ name = "mcp" },
|
||||||
|
{ name = "opentelemetry-api" },
|
||||||
|
{ name = "py-key-value-aio", extra = ["filetree", "keyring", "memory"] },
|
||||||
|
]
|
||||||
|
server = [
|
||||||
{ name = "authlib" },
|
{ name = "authlib" },
|
||||||
{ name = "cyclopts" },
|
{ name = "cyclopts" },
|
||||||
{ name = "exceptiongroup" },
|
{ name = "exceptiongroup" },
|
||||||
@@ -293,22 +327,15 @@ dependencies = [
|
|||||||
{ name = "openapi-pydantic" },
|
{ name = "openapi-pydantic" },
|
||||||
{ name = "opentelemetry-api" },
|
{ name = "opentelemetry-api" },
|
||||||
{ name = "packaging" },
|
{ name = "packaging" },
|
||||||
{ name = "platformdirs" },
|
|
||||||
{ name = "py-key-value-aio", extra = ["filetree", "keyring", "memory"] },
|
{ name = "py-key-value-aio", extra = ["filetree", "keyring", "memory"] },
|
||||||
{ name = "pydantic", extra = ["email"] },
|
|
||||||
{ name = "pyperclip" },
|
{ name = "pyperclip" },
|
||||||
{ name = "python-dotenv" },
|
{ name = "python-multipart" },
|
||||||
{ name = "pyyaml" },
|
{ name = "pyyaml" },
|
||||||
{ name = "rich" },
|
|
||||||
{ name = "uncalled-for" },
|
{ name = "uncalled-for" },
|
||||||
{ name = "uvicorn" },
|
{ name = "uvicorn" },
|
||||||
{ name = "watchfiles" },
|
{ name = "watchfiles" },
|
||||||
{ name = "websockets" },
|
{ name = "websockets" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/9c/13/29544fbc6dfe45ea38046af0067311e0bad7acc7d1f2ad38bb08f2409fe2/fastmcp-3.2.4.tar.gz", hash = "sha256:083ecb75b44a4169e7fc0f632f94b781bdb0ff877c6b35b9877cbb566fd4d4d1", size = 28746127, upload-time = "2026-04-14T01:42:24.174Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cf/76/b310d52fa0e30d39bd937eb58ec2c1f1ea1b5f519f0575e9dd9612f01deb/fastmcp-3.2.4-py3-none-any.whl", hash = "sha256:e6c9c429171041455e47ab94bb3f83c4657622a0ec28922f6940053959bd58a9", size = 728599, upload-time = "2026-04-14T01:42:26.85Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "griffelib"
|
name = "griffelib"
|
||||||
@@ -535,7 +562,7 @@ dev = [
|
|||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "fastmcp", specifier = ">=3.2.4" },
|
{ name = "fastmcp", specifier = ">=3.2.4" },
|
||||||
{ name = "jsonschema", specifier = ">=4.26.0" },
|
{ name = "jsonschema", specifier = ">=4.26" },
|
||||||
{ name = "mcp", extras = ["cli", "rich"], specifier = ">=1" },
|
{ name = "mcp", extras = ["cli", "rich"], specifier = ">=1" },
|
||||||
{ name = "pydantic", specifier = ">=2" },
|
{ name = "pydantic", specifier = ">=2" },
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user