Spaces:
Running
Running
Jeremiah Lowin commited on
Commit ·
bfdc923
1
Parent(s): 1d32a06
Update cli file spec
Browse files- examples/desktop.py +0 -4
- examples/echo.py +6 -5
- examples/screenshot.py +0 -4
- examples/simple_echo.py +0 -4
- src/fastmcp/cli/claude.py +20 -18
- src/fastmcp/cli/cli.py +16 -12
examples/desktop.py
CHANGED
|
@@ -23,7 +23,3 @@ def desktop() -> list[str]:
|
|
| 23 |
def add(a: int, b: int) -> int:
|
| 24 |
"""Add two numbers"""
|
| 25 |
return a + b
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
if __name__ == "__main__":
|
| 29 |
-
mcp.run()
|
|
|
|
| 23 |
def add(a: int, b: int) -> int:
|
| 24 |
"""Add two numbers"""
|
| 25 |
return a + b
|
|
|
|
|
|
|
|
|
|
|
|
examples/echo.py
CHANGED
|
@@ -15,8 +15,13 @@ def echo_tool(text: str) -> str:
|
|
| 15 |
return text
|
| 16 |
|
| 17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
@mcp.resource("echo://{text}")
|
| 19 |
-
def
|
| 20 |
"""Echo the input text"""
|
| 21 |
return f"Echo: {text}"
|
| 22 |
|
|
@@ -24,7 +29,3 @@ def echo_resource(text: str) -> str:
|
|
| 24 |
@mcp.prompt("echo")
|
| 25 |
def echo_prompt(text: str) -> str:
|
| 26 |
return text
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
if __name__ == "__main__":
|
| 30 |
-
mcp.run()
|
|
|
|
| 15 |
return text
|
| 16 |
|
| 17 |
|
| 18 |
+
@mcp.resource("echo://static")
|
| 19 |
+
def echo_resource() -> str:
|
| 20 |
+
return "Echo!"
|
| 21 |
+
|
| 22 |
+
|
| 23 |
@mcp.resource("echo://{text}")
|
| 24 |
+
def echo_template(text: str) -> str:
|
| 25 |
"""Echo the input text"""
|
| 26 |
return f"Echo: {text}"
|
| 27 |
|
|
|
|
| 29 |
@mcp.prompt("echo")
|
| 30 |
def echo_prompt(text: str) -> str:
|
| 31 |
return text
|
|
|
|
|
|
|
|
|
|
|
|
examples/screenshot.py
CHANGED
|
@@ -26,7 +26,3 @@ def take_screenshot() -> Image:
|
|
| 26 |
# if the file exceeds ~1MB, it will be rejected by Claude
|
| 27 |
screenshot.convert("RGB").save(buffer, format="JPEG", quality=60, optimize=True)
|
| 28 |
return Image(data=buffer.getvalue(), format="jpeg")
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
if __name__ == "__main__":
|
| 32 |
-
mcp.run()
|
|
|
|
| 26 |
# if the file exceeds ~1MB, it will be rejected by Claude
|
| 27 |
screenshot.convert("RGB").save(buffer, format="JPEG", quality=60, optimize=True)
|
| 28 |
return Image(data=buffer.getvalue(), format="jpeg")
|
|
|
|
|
|
|
|
|
|
|
|
examples/simple_echo.py
CHANGED
|
@@ -13,7 +13,3 @@ mcp = FastMCP("Echo Server")
|
|
| 13 |
def echo(text: str) -> str:
|
| 14 |
"""Echo the input text"""
|
| 15 |
return text
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
if __name__ == "__main__":
|
| 19 |
-
mcp.run()
|
|
|
|
| 13 |
def echo(text: str) -> str:
|
| 14 |
"""Echo the input text"""
|
| 15 |
return text
|
|
|
|
|
|
|
|
|
|
|
|
src/fastmcp/cli/claude.py
CHANGED
|
@@ -25,8 +25,8 @@ def get_claude_config_path() -> Path | None:
|
|
| 25 |
|
| 26 |
|
| 27 |
def update_claude_config(
|
| 28 |
-
|
| 29 |
-
server_name:
|
| 30 |
*,
|
| 31 |
with_editable: Optional[Path] = None,
|
| 32 |
with_packages: Optional[list[str]] = None,
|
|
@@ -35,9 +35,8 @@ def update_claude_config(
|
|
| 35 |
"""Add the MCP server to Claude's configuration.
|
| 36 |
|
| 37 |
Args:
|
| 38 |
-
|
| 39 |
-
server_name:
|
| 40 |
-
defaults to the file stem
|
| 41 |
with_editable: Optional directory to install in editable mode
|
| 42 |
with_packages: Optional list of additional packages to install
|
| 43 |
force: If True, replace existing server with same name
|
|
@@ -55,46 +54,49 @@ def update_claude_config(
|
|
| 55 |
if "mcpServers" not in config:
|
| 56 |
config["mcpServers"] = {}
|
| 57 |
|
| 58 |
-
|
| 59 |
-
name = server_name or file.stem
|
| 60 |
-
if name in config["mcpServers"]:
|
| 61 |
if not force:
|
| 62 |
logger.warning(
|
| 63 |
-
f"Server '{
|
| 64 |
"Use `--force` to replace.",
|
| 65 |
extra={"config_file": str(config_file)},
|
| 66 |
)
|
| 67 |
return False
|
| 68 |
logger.info(
|
| 69 |
-
f"Replacing existing server '{
|
| 70 |
extra={"config_file": str(config_file)},
|
| 71 |
)
|
| 72 |
|
| 73 |
# Build uv run command
|
| 74 |
-
args = ["run"]
|
| 75 |
|
| 76 |
if with_editable:
|
| 77 |
args.extend(["--with-editable", str(with_editable)])
|
| 78 |
|
| 79 |
-
# Always include fastmcp
|
| 80 |
-
args.extend(["--with", "fastmcp"])
|
| 81 |
-
|
| 82 |
-
# Add additional packages
|
| 83 |
if with_packages:
|
| 84 |
for pkg in with_packages:
|
| 85 |
if pkg:
|
| 86 |
args.extend(["--with", pkg])
|
| 87 |
|
| 88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
-
config["mcpServers"][
|
| 91 |
"command": "uv",
|
| 92 |
"args": args,
|
| 93 |
}
|
| 94 |
|
| 95 |
config_file.write_text(json.dumps(config, indent=2))
|
| 96 |
logger.info(
|
| 97 |
-
f"Added server '{
|
| 98 |
extra={"config_file": str(config_file)},
|
| 99 |
)
|
| 100 |
return True
|
|
|
|
| 25 |
|
| 26 |
|
| 27 |
def update_claude_config(
|
| 28 |
+
file_spec: str,
|
| 29 |
+
server_name: str,
|
| 30 |
*,
|
| 31 |
with_editable: Optional[Path] = None,
|
| 32 |
with_packages: Optional[list[str]] = None,
|
|
|
|
| 35 |
"""Add the MCP server to Claude's configuration.
|
| 36 |
|
| 37 |
Args:
|
| 38 |
+
file_spec: Path to the server file, optionally with :object suffix
|
| 39 |
+
server_name: Name for the server in Claude's config
|
|
|
|
| 40 |
with_editable: Optional directory to install in editable mode
|
| 41 |
with_packages: Optional list of additional packages to install
|
| 42 |
force: If True, replace existing server with same name
|
|
|
|
| 54 |
if "mcpServers" not in config:
|
| 55 |
config["mcpServers"] = {}
|
| 56 |
|
| 57 |
+
if server_name in config["mcpServers"]:
|
|
|
|
|
|
|
| 58 |
if not force:
|
| 59 |
logger.warning(
|
| 60 |
+
f"Server '{server_name}' already exists in Claude config. "
|
| 61 |
"Use `--force` to replace.",
|
| 62 |
extra={"config_file": str(config_file)},
|
| 63 |
)
|
| 64 |
return False
|
| 65 |
logger.info(
|
| 66 |
+
f"Replacing existing server '{server_name}' in Claude config",
|
| 67 |
extra={"config_file": str(config_file)},
|
| 68 |
)
|
| 69 |
|
| 70 |
# Build uv run command
|
| 71 |
+
args = ["run", "--with", "fastmcp"]
|
| 72 |
|
| 73 |
if with_editable:
|
| 74 |
args.extend(["--with-editable", str(with_editable)])
|
| 75 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
if with_packages:
|
| 77 |
for pkg in with_packages:
|
| 78 |
if pkg:
|
| 79 |
args.extend(["--with", pkg])
|
| 80 |
|
| 81 |
+
# Convert file path to absolute before adding to command
|
| 82 |
+
# Split off any :object suffix first
|
| 83 |
+
if ":" in file_spec:
|
| 84 |
+
file_path, server_object = file_spec.rsplit(":", 1)
|
| 85 |
+
file_spec = f"{Path(file_path).resolve()}:{server_object}"
|
| 86 |
+
else:
|
| 87 |
+
file_spec = str(Path(file_spec).resolve())
|
| 88 |
+
|
| 89 |
+
# Add fastmcp run command
|
| 90 |
+
args.extend(["fastmcp", "run", file_spec])
|
| 91 |
|
| 92 |
+
config["mcpServers"][server_name] = {
|
| 93 |
"command": "uv",
|
| 94 |
"args": args,
|
| 95 |
}
|
| 96 |
|
| 97 |
config_file.write_text(json.dumps(config, indent=2))
|
| 98 |
logger.info(
|
| 99 |
+
f"Added server '{server_name}' to Claude config",
|
| 100 |
extra={"config_file": str(config_file)},
|
| 101 |
)
|
| 102 |
return True
|
src/fastmcp/cli/cli.py
CHANGED
|
@@ -24,11 +24,11 @@ app = typer.Typer(
|
|
| 24 |
|
| 25 |
|
| 26 |
def _build_uv_command(
|
| 27 |
-
|
| 28 |
with_editable: Optional[Path] = None,
|
| 29 |
with_packages: Optional[list[str]] = None,
|
| 30 |
) -> list[str]:
|
| 31 |
-
"""Build the uv run command."""
|
| 32 |
cmd = ["uv"]
|
| 33 |
|
| 34 |
cmd.extend(["run", "--with", "fastmcp"])
|
|
@@ -41,7 +41,8 @@ def _build_uv_command(
|
|
| 41 |
if pkg:
|
| 42 |
cmd.extend(["--with", pkg])
|
| 43 |
|
| 44 |
-
|
|
|
|
| 45 |
return cmd
|
| 46 |
|
| 47 |
|
|
@@ -89,7 +90,7 @@ def _import_server(file: Path, server_object: Optional[str] = None):
|
|
| 89 |
module = importlib.util.module_from_spec(spec)
|
| 90 |
spec.loader.exec_module(module)
|
| 91 |
|
| 92 |
-
# If no object specified, try
|
| 93 |
if not server_object:
|
| 94 |
# Look for the most common server object names
|
| 95 |
for name in ["mcp", "server", "app"]:
|
|
@@ -97,7 +98,9 @@ def _import_server(file: Path, server_object: Optional[str] = None):
|
|
| 97 |
return getattr(module, name)
|
| 98 |
|
| 99 |
logger.error(
|
| 100 |
-
f"No server object found in {file}. Please
|
|
|
|
|
|
|
| 101 |
extra={"file": str(file)},
|
| 102 |
)
|
| 103 |
sys.exit(1)
|
|
@@ -178,7 +181,7 @@ def dev(
|
|
| 178 |
)
|
| 179 |
|
| 180 |
try:
|
| 181 |
-
uv_cmd = _build_uv_command(
|
| 182 |
# Run the MCP Inspector command
|
| 183 |
process = subprocess.run(
|
| 184 |
["npx", "@modelcontextprotocol/inspector"] + uv_cmd,
|
|
@@ -229,7 +232,12 @@ def run(
|
|
| 229 |
),
|
| 230 |
] = None,
|
| 231 |
) -> None:
|
| 232 |
-
"""Run a FastMCP server.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
file, server_object = _parse_file_path(file_spec)
|
| 234 |
|
| 235 |
logger.debug(
|
|
@@ -338,7 +346,7 @@ def install(
|
|
| 338 |
name = file.stem
|
| 339 |
|
| 340 |
if claude.update_claude_config(
|
| 341 |
-
|
| 342 |
name,
|
| 343 |
with_editable=with_editable,
|
| 344 |
with_packages=with_packages,
|
|
@@ -348,7 +356,3 @@ def install(
|
|
| 348 |
else:
|
| 349 |
print(f"Failed to install {name} in Claude app")
|
| 350 |
sys.exit(1)
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
if __name__ == "__main__":
|
| 354 |
-
app()
|
|
|
|
| 24 |
|
| 25 |
|
| 26 |
def _build_uv_command(
|
| 27 |
+
file_spec: str,
|
| 28 |
with_editable: Optional[Path] = None,
|
| 29 |
with_packages: Optional[list[str]] = None,
|
| 30 |
) -> list[str]:
|
| 31 |
+
"""Build the uv run command that runs a FastMCP server through fastmcp run."""
|
| 32 |
cmd = ["uv"]
|
| 33 |
|
| 34 |
cmd.extend(["run", "--with", "fastmcp"])
|
|
|
|
| 41 |
if pkg:
|
| 42 |
cmd.extend(["--with", pkg])
|
| 43 |
|
| 44 |
+
# Add fastmcp run command
|
| 45 |
+
cmd.extend(["fastmcp", "run", file_spec])
|
| 46 |
return cmd
|
| 47 |
|
| 48 |
|
|
|
|
| 90 |
module = importlib.util.module_from_spec(spec)
|
| 91 |
spec.loader.exec_module(module)
|
| 92 |
|
| 93 |
+
# If no object specified, try common server names
|
| 94 |
if not server_object:
|
| 95 |
# Look for the most common server object names
|
| 96 |
for name in ["mcp", "server", "app"]:
|
|
|
|
| 98 |
return getattr(module, name)
|
| 99 |
|
| 100 |
logger.error(
|
| 101 |
+
f"No server object found in {file}. Please either:\n"
|
| 102 |
+
"1. Use a standard variable name (mcp, server, or app)\n"
|
| 103 |
+
"2. Specify the object name with file:object syntax",
|
| 104 |
extra={"file": str(file)},
|
| 105 |
)
|
| 106 |
sys.exit(1)
|
|
|
|
| 181 |
)
|
| 182 |
|
| 183 |
try:
|
| 184 |
+
uv_cmd = _build_uv_command(file_spec, with_editable, with_packages)
|
| 185 |
# Run the MCP Inspector command
|
| 186 |
process = subprocess.run(
|
| 187 |
["npx", "@modelcontextprotocol/inspector"] + uv_cmd,
|
|
|
|
| 232 |
),
|
| 233 |
] = None,
|
| 234 |
) -> None:
|
| 235 |
+
"""Run a FastMCP server.
|
| 236 |
+
|
| 237 |
+
The server can be specified in two ways:
|
| 238 |
+
1. Module approach: server.py - runs the module directly, expecting a server.run() call
|
| 239 |
+
2. Import approach: server.py:app - imports and runs the specified server object
|
| 240 |
+
"""
|
| 241 |
file, server_object = _parse_file_path(file_spec)
|
| 242 |
|
| 243 |
logger.debug(
|
|
|
|
| 346 |
name = file.stem
|
| 347 |
|
| 348 |
if claude.update_claude_config(
|
| 349 |
+
file_spec,
|
| 350 |
name,
|
| 351 |
with_editable=with_editable,
|
| 352 |
with_packages=with_packages,
|
|
|
|
| 356 |
else:
|
| 357 |
print(f"Failed to install {name} in Claude app")
|
| 358 |
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|