Skip to content

Commit

Permalink
Merge pull request #19 from jlowin/cli
Browse files Browse the repository at this point in the history
Update cli file spec
  • Loading branch information
jlowin authored Dec 1, 2024
2 parents 8cd94ff + 8dc7254 commit ba94bf6
Show file tree
Hide file tree
Showing 6 changed files with 42 additions and 47 deletions.
4 changes: 0 additions & 4 deletions examples/desktop.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,3 @@ def desktop() -> list[str]:
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b


if __name__ == "__main__":
mcp.run()
11 changes: 6 additions & 5 deletions examples/echo.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,17 @@ def echo_tool(text: str) -> str:
return text


@mcp.resource("echo://static")
def echo_resource() -> str:
return "Echo!"


@mcp.resource("echo://{text}")
def echo_resource(text: str) -> str:
def echo_template(text: str) -> str:
"""Echo the input text"""
return f"Echo: {text}"


@mcp.prompt("echo")
def echo_prompt(text: str) -> str:
return text


if __name__ == "__main__":
mcp.run()
4 changes: 0 additions & 4 deletions examples/screenshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,3 @@ def take_screenshot() -> Image:
# if the file exceeds ~1MB, it will be rejected by Claude
screenshot.convert("RGB").save(buffer, format="JPEG", quality=60, optimize=True)
return Image(data=buffer.getvalue(), format="jpeg")


if __name__ == "__main__":
mcp.run()
4 changes: 0 additions & 4 deletions examples/simple_echo.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,3 @@
def echo(text: str) -> str:
"""Echo the input text"""
return text


if __name__ == "__main__":
mcp.run()
38 changes: 20 additions & 18 deletions src/fastmcp/cli/claude.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ def get_claude_config_path() -> Path | None:


def update_claude_config(
file: Path,
server_name: Optional[str] = None,
file_spec: str,
server_name: str,
*,
with_editable: Optional[Path] = None,
with_packages: Optional[list[str]] = None,
Expand All @@ -35,9 +35,8 @@ def update_claude_config(
"""Add the MCP server to Claude's configuration.
Args:
file: Path to the server file
server_name: Optional custom name for the server. If not provided,
defaults to the file stem
file_spec: Path to the server file, optionally with :object suffix
server_name: Name for the server in Claude's config
with_editable: Optional directory to install in editable mode
with_packages: Optional list of additional packages to install
force: If True, replace existing server with same name
Expand All @@ -55,46 +54,49 @@ def update_claude_config(
if "mcpServers" not in config:
config["mcpServers"] = {}

# Use provided server_name or fall back to file stem
name = server_name or file.stem
if name in config["mcpServers"]:
if server_name in config["mcpServers"]:
if not force:
logger.warning(
f"Server '{name}' already exists in Claude config. "
f"Server '{server_name}' already exists in Claude config. "
"Use `--force` to replace.",
extra={"config_file": str(config_file)},
)
return False
logger.info(
f"Replacing existing server '{name}' in Claude config",
f"Replacing existing server '{server_name}' in Claude config",
extra={"config_file": str(config_file)},
)

# Build uv run command
args = ["run"]
args = ["run", "--with", "fastmcp"]

if with_editable:
args.extend(["--with-editable", str(with_editable)])

# Always include fastmcp
args.extend(["--with", "fastmcp"])

# Add additional packages
if with_packages:
for pkg in with_packages:
if pkg:
args.extend(["--with", pkg])

args.append(str(file))
# Convert file path to absolute before adding to command
# Split off any :object suffix first
if ":" in file_spec:
file_path, server_object = file_spec.rsplit(":", 1)
file_spec = f"{Path(file_path).resolve()}:{server_object}"
else:
file_spec = str(Path(file_spec).resolve())

# Add fastmcp run command
args.extend(["fastmcp", "run", file_spec])

config["mcpServers"][name] = {
config["mcpServers"][server_name] = {
"command": "uv",
"args": args,
}

config_file.write_text(json.dumps(config, indent=2))
logger.info(
f"Added server '{name}' to Claude config",
f"Added server '{server_name}' to Claude config",
extra={"config_file": str(config_file)},
)
return True
Expand Down
28 changes: 16 additions & 12 deletions src/fastmcp/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@


def _build_uv_command(
file: Path,
file_spec: str,
with_editable: Optional[Path] = None,
with_packages: Optional[list[str]] = None,
) -> list[str]:
"""Build the uv run command."""
"""Build the uv run command that runs a FastMCP server through fastmcp run."""
cmd = ["uv"]

cmd.extend(["run", "--with", "fastmcp"])
Expand All @@ -41,7 +41,8 @@ def _build_uv_command(
if pkg:
cmd.extend(["--with", pkg])

cmd.append(str(file))
# Add fastmcp run command
cmd.extend(["fastmcp", "run", file_spec])
return cmd


Expand Down Expand Up @@ -89,15 +90,17 @@ def _import_server(file: Path, server_object: Optional[str] = None):
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)

# If no object specified, try __main__ block
# If no object specified, try common server names
if not server_object:
# Look for the most common server object names
for name in ["mcp", "server", "app"]:
if hasattr(module, name):
return getattr(module, name)

logger.error(
f"No server object found in {file}. Please specify the object name with file:object syntax.",
f"No server object found in {file}. Please either:\n"
"1. Use a standard variable name (mcp, server, or app)\n"
"2. Specify the object name with file:object syntax",
extra={"file": str(file)},
)
sys.exit(1)
Expand Down Expand Up @@ -178,7 +181,7 @@ def dev(
)

try:
uv_cmd = _build_uv_command(file, with_editable, with_packages)
uv_cmd = _build_uv_command(file_spec, with_editable, with_packages)
# Run the MCP Inspector command
process = subprocess.run(
["npx", "@modelcontextprotocol/inspector"] + uv_cmd,
Expand Down Expand Up @@ -229,7 +232,12 @@ def run(
),
] = None,
) -> None:
"""Run a FastMCP server."""
"""Run a FastMCP server.
The server can be specified in two ways:
1. Module approach: server.py - runs the module directly, expecting a server.run() call
2. Import approach: server.py:app - imports and runs the specified server object
"""
file, server_object = _parse_file_path(file_spec)

logger.debug(
Expand Down Expand Up @@ -338,7 +346,7 @@ def install(
name = file.stem

if claude.update_claude_config(
file,
file_spec,
name,
with_editable=with_editable,
with_packages=with_packages,
Expand All @@ -348,7 +356,3 @@ def install(
else:
print(f"Failed to install {name} in Claude app")
sys.exit(1)


if __name__ == "__main__":
app()

0 comments on commit ba94bf6

Please sign in to comment.