fastmcp-remote 3.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastmcp_remote-3.4.0/.gitignore +80 -0
- fastmcp_remote-3.4.0/PKG-INFO +109 -0
- fastmcp_remote-3.4.0/README.md +86 -0
- fastmcp_remote-3.4.0/fastmcp_remote/__init__.py +10 -0
- fastmcp_remote-3.4.0/fastmcp_remote/cli.py +253 -0
- fastmcp_remote-3.4.0/fastmcp_remote/py.typed +1 -0
- fastmcp_remote-3.4.0/pyproject.toml +62 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
build/
|
|
6
|
+
dist/
|
|
7
|
+
wheels/
|
|
8
|
+
*.egg-info/
|
|
9
|
+
*.egg
|
|
10
|
+
MANIFEST
|
|
11
|
+
.pytest_cache/
|
|
12
|
+
.loq_cache
|
|
13
|
+
.coverage
|
|
14
|
+
htmlcov/
|
|
15
|
+
.tox/
|
|
16
|
+
nosetests.xml
|
|
17
|
+
coverage.xml
|
|
18
|
+
*.cover
|
|
19
|
+
|
|
20
|
+
# Virtual environments
|
|
21
|
+
.venv
|
|
22
|
+
venv/
|
|
23
|
+
env/
|
|
24
|
+
ENV/
|
|
25
|
+
.env
|
|
26
|
+
|
|
27
|
+
# System files
|
|
28
|
+
.DS_Store
|
|
29
|
+
|
|
30
|
+
# Version file
|
|
31
|
+
src/fastmcp/_version.py
|
|
32
|
+
|
|
33
|
+
# Editors and IDEs
|
|
34
|
+
.cursorrules
|
|
35
|
+
.vscode/
|
|
36
|
+
.idea/
|
|
37
|
+
*.swp
|
|
38
|
+
*.swo
|
|
39
|
+
*~
|
|
40
|
+
.project
|
|
41
|
+
.pydevproject
|
|
42
|
+
.settings/
|
|
43
|
+
|
|
44
|
+
# Jupyter Notebook
|
|
45
|
+
.ipynb_checkpoints
|
|
46
|
+
|
|
47
|
+
# Type checking
|
|
48
|
+
.mypy_cache/
|
|
49
|
+
.dmypy.json
|
|
50
|
+
dmypy.json
|
|
51
|
+
.pyre/
|
|
52
|
+
.pytype/
|
|
53
|
+
|
|
54
|
+
# Local development
|
|
55
|
+
.python-version
|
|
56
|
+
.envrc
|
|
57
|
+
.envrc.private
|
|
58
|
+
.direnv/
|
|
59
|
+
|
|
60
|
+
# Logs and databases
|
|
61
|
+
*.log
|
|
62
|
+
*.sqlite
|
|
63
|
+
*.db
|
|
64
|
+
*.ddb
|
|
65
|
+
|
|
66
|
+
# Claude worktree management
|
|
67
|
+
.claude-wt/worktrees
|
|
68
|
+
.claude/worktrees/
|
|
69
|
+
|
|
70
|
+
# Agents
|
|
71
|
+
/PLAN.md
|
|
72
|
+
/TODO.md
|
|
73
|
+
/STATUS.md
|
|
74
|
+
plans/
|
|
75
|
+
|
|
76
|
+
# Common FastMCP test files
|
|
77
|
+
/test.py
|
|
78
|
+
/server.py
|
|
79
|
+
/client.py
|
|
80
|
+
/test.json
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastmcp-remote
|
|
3
|
+
Version: 3.4.0
|
|
4
|
+
Summary: A Python stdio bridge for remote MCP servers, powered by FastMCP.
|
|
5
|
+
Project-URL: Homepage, https://gofastmcp.com
|
|
6
|
+
Project-URL: Repository, https://github.com/PrefectHQ/fastmcp
|
|
7
|
+
Project-URL: Documentation, https://gofastmcp.com
|
|
8
|
+
Project-URL: Original npm project, https://github.com/geelen/mcp-remote
|
|
9
|
+
Author: Jeremiah Lowin
|
|
10
|
+
License-Expression: Apache-2.0
|
|
11
|
+
Keywords: fastmcp,fastmcp remote,mcp,mcp remote,model context protocol,oauth,stdio
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: fastmcp-slim[client,server]==3.4.0
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# fastmcp-remote
|
|
25
|
+
|
|
26
|
+
`fastmcp-remote` is FastMCP's standalone Python stdio bridge for remote MCP servers. It lets MCP clients that launch local stdio processes connect to MCP servers hosted over Streamable HTTP or SSE.
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"linear": {
|
|
32
|
+
"command": "uvx",
|
|
33
|
+
"args": ["fastmcp-remote", "https://mcp.linear.app/mcp"]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The CLI is powered by [FastMCP](https://gofastmcp.com). Its command shape is inspired by the original [`mcp-remote`](https://github.com/geelen/mcp-remote) npm project, which established the stdio-to-remote bridge pattern used across the MCP ecosystem.
|
|
40
|
+
|
|
41
|
+
`fastmcp-remote` is intentionally smaller than the general FastMCP CLI. It does not load Python files, discover local MCP configs, prepare project environments, or run development reload loops. It builds one FastMCP client for the URL you provide, exposes that client as a local stdio proxy, and leaves the rest alone.
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
Run a remote MCP server through a local stdio bridge:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
uvx fastmcp-remote https://example.com/mcp
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Use the full MCP endpoint URL for the remote server. Many FastMCP HTTP servers expose MCP at `/mcp`, so a local development server may need `http://localhost:8000/mcp` rather than `http://localhost:8000`.
|
|
52
|
+
|
|
53
|
+
`fastmcp-remote` starts a local stdio bridge, then connects to the upstream server when the MCP host initializes that bridge. If the upstream server is unavailable, the URL does not point to an MCP endpoint, or authentication cannot complete, initialization fails and the host should report the remote server as failed.
|
|
54
|
+
|
|
55
|
+
For authenticated MCP servers, OAuth is enabled automatically. To pass a bearer token or other custom header instead, provide a header. The header name ends at the first colon, so values can contain additional colons. Quote the header when the value contains spaces, just like any other shell argument:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
uvx fastmcp-remote https://example.com/mcp \
|
|
59
|
+
--header "Authorization: Bearer <token>"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Repeat `--header` to send multiple headers. Header values use `Name: Value` format:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
uvx fastmcp-remote https://example.com/mcp \
|
|
66
|
+
--header "Authorization: Bearer <token>" \
|
|
67
|
+
--header "X-Workspace: production" \
|
|
68
|
+
--header "X-Client-Name: My MCP Host" \
|
|
69
|
+
--header "X-Callback-Url: https://example.com/oauth/callback"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Some MCP hosts on Windows have trouble preserving spaces inside command arguments. Put the spaced value in an environment variable and reference it from the header value:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"mcpServers": {
|
|
77
|
+
"remote-api": {
|
|
78
|
+
"command": "uvx",
|
|
79
|
+
"args": [
|
|
80
|
+
"fastmcp-remote",
|
|
81
|
+
"https://example.com/mcp",
|
|
82
|
+
"--header",
|
|
83
|
+
"Authorization:${AUTH_HEADER}"
|
|
84
|
+
],
|
|
85
|
+
"env": {
|
|
86
|
+
"AUTH_HEADER": "Bearer <token>"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Use `--auth none` for unauthenticated development servers:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
uvx fastmcp-remote http://localhost:8000/mcp --auth none
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Options
|
|
100
|
+
|
|
101
|
+
- `--transport`: Choose `http` or `sse`. Defaults to `http`.
|
|
102
|
+
- `--header`: Add a header to upstream requests, for example `--header "Authorization: Bearer <token>"`. Values may contain colons. Quote headers whose values contain spaces. Use `${VAR}` to expand environment variables inside values. Repeat for multiple headers.
|
|
103
|
+
- `--resource`: Isolate OAuth token storage for a named remote resource.
|
|
104
|
+
- `--host`: Set the OAuth callback hostname. Defaults to `localhost`.
|
|
105
|
+
- `--auth-timeout`: Set how long to wait for the OAuth callback. Defaults to 300 seconds.
|
|
106
|
+
- `--ignore-tool`: Hide tools whose names match a glob pattern.
|
|
107
|
+
- `--auth`: Choose `oauth` or `none`. The default uses OAuth unless an `Authorization` header is provided.
|
|
108
|
+
|
|
109
|
+
OAuth tokens are stored under `~/.fastmcp/remote` by default. Set `FASTMCP_REMOTE_CONFIG_DIR` to use another directory.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# fastmcp-remote
|
|
2
|
+
|
|
3
|
+
`fastmcp-remote` is FastMCP's standalone Python stdio bridge for remote MCP servers. It lets MCP clients that launch local stdio processes connect to MCP servers hosted over Streamable HTTP or SSE.
|
|
4
|
+
|
|
5
|
+
```json
|
|
6
|
+
{
|
|
7
|
+
"mcpServers": {
|
|
8
|
+
"linear": {
|
|
9
|
+
"command": "uvx",
|
|
10
|
+
"args": ["fastmcp-remote", "https://mcp.linear.app/mcp"]
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The CLI is powered by [FastMCP](https://gofastmcp.com). Its command shape is inspired by the original [`mcp-remote`](https://github.com/geelen/mcp-remote) npm project, which established the stdio-to-remote bridge pattern used across the MCP ecosystem.
|
|
17
|
+
|
|
18
|
+
`fastmcp-remote` is intentionally smaller than the general FastMCP CLI. It does not load Python files, discover local MCP configs, prepare project environments, or run development reload loops. It builds one FastMCP client for the URL you provide, exposes that client as a local stdio proxy, and leaves the rest alone.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
Run a remote MCP server through a local stdio bridge:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
uvx fastmcp-remote https://example.com/mcp
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Use the full MCP endpoint URL for the remote server. Many FastMCP HTTP servers expose MCP at `/mcp`, so a local development server may need `http://localhost:8000/mcp` rather than `http://localhost:8000`.
|
|
29
|
+
|
|
30
|
+
`fastmcp-remote` starts a local stdio bridge, then connects to the upstream server when the MCP host initializes that bridge. If the upstream server is unavailable, the URL does not point to an MCP endpoint, or authentication cannot complete, initialization fails and the host should report the remote server as failed.
|
|
31
|
+
|
|
32
|
+
For authenticated MCP servers, OAuth is enabled automatically. To pass a bearer token or other custom header instead, provide a header. The header name ends at the first colon, so values can contain additional colons. Quote the header when the value contains spaces, just like any other shell argument:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
uvx fastmcp-remote https://example.com/mcp \
|
|
36
|
+
--header "Authorization: Bearer <token>"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Repeat `--header` to send multiple headers. Header values use `Name: Value` format:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uvx fastmcp-remote https://example.com/mcp \
|
|
43
|
+
--header "Authorization: Bearer <token>" \
|
|
44
|
+
--header "X-Workspace: production" \
|
|
45
|
+
--header "X-Client-Name: My MCP Host" \
|
|
46
|
+
--header "X-Callback-Url: https://example.com/oauth/callback"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Some MCP hosts on Windows have trouble preserving spaces inside command arguments. Put the spaced value in an environment variable and reference it from the header value:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"mcpServers": {
|
|
54
|
+
"remote-api": {
|
|
55
|
+
"command": "uvx",
|
|
56
|
+
"args": [
|
|
57
|
+
"fastmcp-remote",
|
|
58
|
+
"https://example.com/mcp",
|
|
59
|
+
"--header",
|
|
60
|
+
"Authorization:${AUTH_HEADER}"
|
|
61
|
+
],
|
|
62
|
+
"env": {
|
|
63
|
+
"AUTH_HEADER": "Bearer <token>"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Use `--auth none` for unauthenticated development servers:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
uvx fastmcp-remote http://localhost:8000/mcp --auth none
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Options
|
|
77
|
+
|
|
78
|
+
- `--transport`: Choose `http` or `sse`. Defaults to `http`.
|
|
79
|
+
- `--header`: Add a header to upstream requests, for example `--header "Authorization: Bearer <token>"`. Values may contain colons. Quote headers whose values contain spaces. Use `${VAR}` to expand environment variables inside values. Repeat for multiple headers.
|
|
80
|
+
- `--resource`: Isolate OAuth token storage for a named remote resource.
|
|
81
|
+
- `--host`: Set the OAuth callback hostname. Defaults to `localhost`.
|
|
82
|
+
- `--auth-timeout`: Set how long to wait for the OAuth callback. Defaults to 300 seconds.
|
|
83
|
+
- `--ignore-tool`: Hide tools whose names match a glob pattern.
|
|
84
|
+
- `--auth`: Choose `oauth` or `none`. The default uses OAuth unless an `Authorization` header is provided.
|
|
85
|
+
|
|
86
|
+
OAuth tokens are stored under `~/.fastmcp/remote` by default. Set `FASTMCP_REMOTE_CONFIG_DIR` to use another directory.
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import fnmatch
|
|
5
|
+
import hashlib
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
from collections.abc import Sequence
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Literal
|
|
12
|
+
from urllib.parse import urlparse
|
|
13
|
+
|
|
14
|
+
import anyio
|
|
15
|
+
from key_value.aio.protocols import AsyncKeyValue
|
|
16
|
+
from key_value.aio.stores.filetree import (
|
|
17
|
+
FileTreeStore,
|
|
18
|
+
FileTreeV1CollectionSanitizationStrategy,
|
|
19
|
+
FileTreeV1KeySanitizationStrategy,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from fastmcp import Client
|
|
23
|
+
from fastmcp.client.auth import OAuth
|
|
24
|
+
from fastmcp.client.transports import SSETransport, StreamableHttpTransport
|
|
25
|
+
from fastmcp.server import create_proxy
|
|
26
|
+
from fastmcp.server.transforms import GetToolNext, Transform
|
|
27
|
+
from fastmcp.tools import Tool
|
|
28
|
+
from fastmcp.utilities.versions import VersionSpec
|
|
29
|
+
|
|
30
|
+
RemoteTransport = Literal["http", "sse"]
|
|
31
|
+
AuthMode = Literal["oauth", "none"]
|
|
32
|
+
ENV_VAR_PATTERN = re.compile(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class RemoteConfig:
|
|
37
|
+
url: str
|
|
38
|
+
headers: dict[str, str]
|
|
39
|
+
transport: RemoteTransport
|
|
40
|
+
auth: AuthMode | None
|
|
41
|
+
callback_port: int | None
|
|
42
|
+
callback_host: str
|
|
43
|
+
callback_timeout: float
|
|
44
|
+
storage_dir: Path
|
|
45
|
+
ignore_tools: tuple[str, ...]
|
|
46
|
+
show_banner: bool
|
|
47
|
+
log_level: str | None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class IgnoreTools(Transform):
|
|
51
|
+
def __init__(self, patterns: Sequence[str]) -> None:
|
|
52
|
+
self.patterns = tuple(patterns)
|
|
53
|
+
|
|
54
|
+
def _matches(self, name: str) -> bool:
|
|
55
|
+
return any(fnmatch.fnmatchcase(name, pattern) for pattern in self.patterns)
|
|
56
|
+
|
|
57
|
+
async def list_tools(self, tools: Sequence[Tool]) -> Sequence[Tool]:
|
|
58
|
+
return [tool for tool in tools if not self._matches(tool.name)]
|
|
59
|
+
|
|
60
|
+
async def get_tool(
|
|
61
|
+
self,
|
|
62
|
+
name: str,
|
|
63
|
+
call_next: GetToolNext,
|
|
64
|
+
*,
|
|
65
|
+
version: VersionSpec | None = None,
|
|
66
|
+
) -> Tool | None:
|
|
67
|
+
if self._matches(name):
|
|
68
|
+
return None
|
|
69
|
+
return await call_next(name, version=version)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def parse_header(value: str) -> tuple[str, str]:
|
|
73
|
+
name, separator, header_value = value.partition(":")
|
|
74
|
+
if not separator or not name.strip():
|
|
75
|
+
raise argparse.ArgumentTypeError("Headers must use the format 'Name: Value'.")
|
|
76
|
+
try:
|
|
77
|
+
expanded_value = ENV_VAR_PATTERN.sub(
|
|
78
|
+
lambda match: os.environ[match.group(1)], header_value
|
|
79
|
+
)
|
|
80
|
+
except KeyError as exc:
|
|
81
|
+
raise argparse.ArgumentTypeError(
|
|
82
|
+
f"Environment variable {exc.args[0]} is not set."
|
|
83
|
+
) from exc
|
|
84
|
+
return name.strip(), expanded_value.strip()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def default_storage_dir(resource: str | None = None) -> Path:
|
|
88
|
+
if config_dir := os.environ.get("FASTMCP_REMOTE_CONFIG_DIR"):
|
|
89
|
+
base = Path(config_dir).expanduser()
|
|
90
|
+
else:
|
|
91
|
+
base = Path.home() / ".fastmcp" / "remote"
|
|
92
|
+
if resource is None:
|
|
93
|
+
return base
|
|
94
|
+
digest = hashlib.sha256(resource.encode()).hexdigest()[:16]
|
|
95
|
+
return base / "resources" / digest
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
99
|
+
parser = argparse.ArgumentParser(
|
|
100
|
+
prog="fastmcp-remote",
|
|
101
|
+
description="Bridge a remote MCP server to a local stdio MCP process.",
|
|
102
|
+
)
|
|
103
|
+
parser.add_argument("url", help="Remote MCP server URL.")
|
|
104
|
+
parser.add_argument(
|
|
105
|
+
"callback_port",
|
|
106
|
+
nargs="?",
|
|
107
|
+
type=int,
|
|
108
|
+
help="OAuth callback port. Defaults to an available local port.",
|
|
109
|
+
)
|
|
110
|
+
parser.add_argument(
|
|
111
|
+
"--transport",
|
|
112
|
+
choices=["http", "sse"],
|
|
113
|
+
default="http",
|
|
114
|
+
help="Remote transport. Defaults to http.",
|
|
115
|
+
)
|
|
116
|
+
parser.add_argument(
|
|
117
|
+
"--header",
|
|
118
|
+
action="append",
|
|
119
|
+
default=[],
|
|
120
|
+
type=parse_header,
|
|
121
|
+
help="Header to send upstream, in 'Name: Value' form. Repeat for multiple headers.",
|
|
122
|
+
)
|
|
123
|
+
parser.add_argument(
|
|
124
|
+
"--auth",
|
|
125
|
+
choices=["oauth", "none"],
|
|
126
|
+
default=None,
|
|
127
|
+
help="Authentication mode. Defaults to OAuth unless Authorization is provided.",
|
|
128
|
+
)
|
|
129
|
+
parser.add_argument(
|
|
130
|
+
"--resource",
|
|
131
|
+
help="Resource identifier used to isolate OAuth token storage.",
|
|
132
|
+
)
|
|
133
|
+
parser.add_argument(
|
|
134
|
+
"--host",
|
|
135
|
+
default="localhost",
|
|
136
|
+
help="OAuth callback hostname. Defaults to localhost.",
|
|
137
|
+
)
|
|
138
|
+
parser.add_argument(
|
|
139
|
+
"--auth-timeout",
|
|
140
|
+
type=float,
|
|
141
|
+
default=300.0,
|
|
142
|
+
help="Seconds to wait for the OAuth callback. Defaults to 300.",
|
|
143
|
+
)
|
|
144
|
+
parser.add_argument(
|
|
145
|
+
"--ignore-tool",
|
|
146
|
+
action="append",
|
|
147
|
+
default=[],
|
|
148
|
+
help="Hide tools matching this glob pattern. Repeat for multiple patterns.",
|
|
149
|
+
)
|
|
150
|
+
parser.add_argument(
|
|
151
|
+
"--debug",
|
|
152
|
+
action="store_true",
|
|
153
|
+
help="Enable debug logging.",
|
|
154
|
+
)
|
|
155
|
+
parser.add_argument(
|
|
156
|
+
"--silent",
|
|
157
|
+
action="store_true",
|
|
158
|
+
help="Suppress non-critical logs.",
|
|
159
|
+
)
|
|
160
|
+
return parser
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def parse_args(argv: Sequence[str] | None = None) -> RemoteConfig:
|
|
164
|
+
parser = build_parser()
|
|
165
|
+
args = parser.parse_args(argv)
|
|
166
|
+
|
|
167
|
+
parsed_url = urlparse(args.url)
|
|
168
|
+
if parsed_url.scheme not in {"http", "https"}:
|
|
169
|
+
parser.error("The remote MCP server URL must start with http:// or https://.")
|
|
170
|
+
|
|
171
|
+
headers = dict(args.header)
|
|
172
|
+
if args.silent and args.debug:
|
|
173
|
+
parser.error("--silent and --debug cannot be used together.")
|
|
174
|
+
if args.auth_timeout <= 0:
|
|
175
|
+
parser.error("--auth-timeout must be greater than 0.")
|
|
176
|
+
|
|
177
|
+
log_level = "DEBUG" if args.debug else None
|
|
178
|
+
if args.silent:
|
|
179
|
+
log_level = "CRITICAL"
|
|
180
|
+
|
|
181
|
+
return RemoteConfig(
|
|
182
|
+
url=args.url,
|
|
183
|
+
headers=headers,
|
|
184
|
+
transport=args.transport,
|
|
185
|
+
auth=args.auth,
|
|
186
|
+
callback_port=args.callback_port,
|
|
187
|
+
callback_host=args.host,
|
|
188
|
+
callback_timeout=args.auth_timeout,
|
|
189
|
+
storage_dir=default_storage_dir(args.resource),
|
|
190
|
+
ignore_tools=tuple(args.ignore_tool),
|
|
191
|
+
show_banner=not args.silent,
|
|
192
|
+
log_level=log_level,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def build_token_storage(storage_dir: Path) -> AsyncKeyValue:
|
|
197
|
+
storage_dir.mkdir(parents=True, exist_ok=True)
|
|
198
|
+
return FileTreeStore(
|
|
199
|
+
data_directory=storage_dir,
|
|
200
|
+
key_sanitization_strategy=FileTreeV1KeySanitizationStrategy(storage_dir),
|
|
201
|
+
collection_sanitization_strategy=FileTreeV1CollectionSanitizationStrategy(
|
|
202
|
+
storage_dir
|
|
203
|
+
),
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def resolve_auth(config: RemoteConfig) -> OAuth | None:
|
|
208
|
+
authorization_header = any(
|
|
209
|
+
name.lower() == "authorization" for name in config.headers
|
|
210
|
+
)
|
|
211
|
+
auth_mode = config.auth
|
|
212
|
+
if auth_mode is None and authorization_header:
|
|
213
|
+
auth_mode = "none"
|
|
214
|
+
elif auth_mode is None:
|
|
215
|
+
auth_mode = "oauth"
|
|
216
|
+
|
|
217
|
+
if auth_mode == "none":
|
|
218
|
+
return None
|
|
219
|
+
|
|
220
|
+
return OAuth(
|
|
221
|
+
token_storage=build_token_storage(config.storage_dir),
|
|
222
|
+
callback_port=config.callback_port,
|
|
223
|
+
callback_host=config.callback_host,
|
|
224
|
+
callback_timeout=config.callback_timeout,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def build_transport(config: RemoteConfig) -> SSETransport | StreamableHttpTransport:
|
|
229
|
+
auth = resolve_auth(config)
|
|
230
|
+
if config.transport == "sse":
|
|
231
|
+
return SSETransport(config.url, headers=config.headers, auth=auth)
|
|
232
|
+
return StreamableHttpTransport(config.url, headers=config.headers, auth=auth)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
async def run(config: RemoteConfig) -> None:
|
|
236
|
+
client = Client(build_transport(config))
|
|
237
|
+
server = create_proxy(
|
|
238
|
+
client,
|
|
239
|
+
name="fastmcp-remote",
|
|
240
|
+
provider_error_strategy="raise",
|
|
241
|
+
)
|
|
242
|
+
if config.ignore_tools:
|
|
243
|
+
server.add_transform(IgnoreTools(config.ignore_tools))
|
|
244
|
+
await server.run_async(
|
|
245
|
+
transport="stdio",
|
|
246
|
+
show_banner=config.show_banner,
|
|
247
|
+
log_level=config.log_level,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def main(argv: Sequence[str] | None = None) -> None:
|
|
252
|
+
config = parse_args(argv)
|
|
253
|
+
anyio.run(run, config)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "fastmcp-remote"
|
|
3
|
+
dynamic = ["version", "dependencies"]
|
|
4
|
+
description = "A Python stdio bridge for remote MCP servers, powered by FastMCP."
|
|
5
|
+
authors = [{ name = "Jeremiah Lowin" }]
|
|
6
|
+
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
license = "Apache-2.0"
|
|
10
|
+
|
|
11
|
+
keywords = [
|
|
12
|
+
"mcp",
|
|
13
|
+
"fastmcp remote",
|
|
14
|
+
"mcp remote",
|
|
15
|
+
"model context protocol",
|
|
16
|
+
"fastmcp",
|
|
17
|
+
"stdio",
|
|
18
|
+
"oauth",
|
|
19
|
+
]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"License :: OSI Approved :: Apache Software License",
|
|
23
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"Typing :: Typed",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://gofastmcp.com"
|
|
33
|
+
Repository = "https://github.com/PrefectHQ/fastmcp"
|
|
34
|
+
Documentation = "https://gofastmcp.com"
|
|
35
|
+
"Original npm project" = "https://github.com/geelen/mcp-remote"
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
fastmcp-remote = "fastmcp_remote.cli:main"
|
|
39
|
+
|
|
40
|
+
[build-system]
|
|
41
|
+
requires = ["hatchling", "uv-dynamic-versioning>=0.7.0"]
|
|
42
|
+
build-backend = "hatchling.build"
|
|
43
|
+
|
|
44
|
+
[tool.hatch.version]
|
|
45
|
+
source = "uv-dynamic-versioning"
|
|
46
|
+
|
|
47
|
+
[tool.hatch.metadata]
|
|
48
|
+
allow-direct-references = true
|
|
49
|
+
|
|
50
|
+
[tool.hatch.build.targets.wheel]
|
|
51
|
+
packages = ["fastmcp_remote"]
|
|
52
|
+
|
|
53
|
+
[tool.uv-dynamic-versioning]
|
|
54
|
+
vcs = "git"
|
|
55
|
+
style = "pep440"
|
|
56
|
+
bump = true
|
|
57
|
+
fallback-version = "0.0.0"
|
|
58
|
+
|
|
59
|
+
[tool.hatch.metadata.hooks.uv-dynamic-versioning]
|
|
60
|
+
dependencies = [
|
|
61
|
+
"fastmcp-slim[client,server]=={{ version }}",
|
|
62
|
+
]
|