Jeremiah Lowin commited on
Commit
bfdc923
·
1 Parent(s): 1d32a06

Update cli file spec

Browse files
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 echo_resource(text: str) -> str:
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
- file: Path,
29
- server_name: Optional[str] = None,
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
- file: Path to the server file
39
- server_name: Optional custom name for the server. If not provided,
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
- # Use provided server_name or fall back to file stem
59
- name = server_name or file.stem
60
- if name in config["mcpServers"]:
61
  if not force:
62
  logger.warning(
63
- f"Server '{name}' already exists in Claude config. "
64
  "Use `--force` to replace.",
65
  extra={"config_file": str(config_file)},
66
  )
67
  return False
68
  logger.info(
69
- f"Replacing existing server '{name}' in Claude config",
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
- args.append(str(file))
 
 
 
 
 
 
 
 
 
89
 
90
- config["mcpServers"][name] = {
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 '{name}' to Claude config",
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
- file: Path,
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
- cmd.append(str(file))
 
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 __main__ block
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 specify the object name with file:object syntax.",
 
 
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(file, with_editable, with_packages)
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
- file,
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)