fastmcp 2.10.1__py3-none-any.whl → 2.10.3__py3-none-any.whl
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/cli/cli.py +117 -225
- fastmcp/cli/install/__init__.py +20 -0
- fastmcp/cli/install/claude_code.py +186 -0
- fastmcp/cli/install/claude_desktop.py +186 -0
- fastmcp/cli/install/cursor.py +196 -0
- fastmcp/cli/install/mcp_config.py +165 -0
- fastmcp/cli/install/shared.py +85 -0
- fastmcp/cli/run.py +17 -4
- fastmcp/client/client.py +230 -124
- fastmcp/client/elicitation.py +5 -0
- fastmcp/client/transports.py +3 -5
- fastmcp/mcp_config.py +282 -0
- fastmcp/prompts/prompt.py +2 -4
- fastmcp/resources/resource.py +2 -2
- fastmcp/resources/template.py +1 -1
- fastmcp/server/auth/providers/bearer.py +15 -6
- fastmcp/server/openapi.py +40 -9
- fastmcp/server/proxy.py +206 -37
- fastmcp/server/server.py +102 -20
- fastmcp/settings.py +19 -1
- fastmcp/tools/tool.py +3 -2
- fastmcp/tools/tool_transform.py +5 -6
- fastmcp/utilities/cli.py +102 -0
- fastmcp/utilities/json_schema.py +14 -3
- fastmcp/utilities/logging.py +3 -0
- fastmcp/utilities/openapi.py +92 -0
- fastmcp/utilities/tests.py +13 -0
- fastmcp/utilities/types.py +4 -3
- {fastmcp-2.10.1.dist-info → fastmcp-2.10.3.dist-info}/METADATA +4 -3
- {fastmcp-2.10.1.dist-info → fastmcp-2.10.3.dist-info}/RECORD +33 -26
- fastmcp/utilities/mcp_config.py +0 -93
- {fastmcp-2.10.1.dist-info → fastmcp-2.10.3.dist-info}/WHEEL +0 -0
- {fastmcp-2.10.1.dist-info → fastmcp-2.10.3.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.10.1.dist-info → fastmcp-2.10.3.dist-info}/licenses/LICENSE +0 -0
fastmcp/cli/cli.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
"""FastMCP CLI tools."""
|
|
1
|
+
"""FastMCP CLI tools using Cyclopts."""
|
|
2
2
|
|
|
3
|
-
import asyncio
|
|
4
3
|
import importlib.metadata
|
|
5
4
|
import importlib.util
|
|
6
5
|
import os
|
|
@@ -8,18 +7,17 @@ import platform
|
|
|
8
7
|
import subprocess
|
|
9
8
|
import sys
|
|
10
9
|
from pathlib import Path
|
|
11
|
-
from typing import Annotated
|
|
10
|
+
from typing import Annotated, Literal
|
|
12
11
|
|
|
13
|
-
import
|
|
14
|
-
import
|
|
12
|
+
import cyclopts
|
|
13
|
+
import pyperclip
|
|
15
14
|
from pydantic import TypeAdapter
|
|
16
15
|
from rich.console import Console
|
|
17
16
|
from rich.table import Table
|
|
18
|
-
from typer import Context, Exit
|
|
19
17
|
|
|
20
18
|
import fastmcp
|
|
21
|
-
from fastmcp.cli import claude
|
|
22
19
|
from fastmcp.cli import run as run_module
|
|
20
|
+
from fastmcp.cli.install import install_app
|
|
23
21
|
from fastmcp.server.server import FastMCP
|
|
24
22
|
from fastmcp.utilities.inspect import FastMCPInfo, inspect_fastmcp
|
|
25
23
|
from fastmcp.utilities.logging import get_logger
|
|
@@ -27,11 +25,10 @@ from fastmcp.utilities.logging import get_logger
|
|
|
27
25
|
logger = get_logger("cli")
|
|
28
26
|
console = Console()
|
|
29
27
|
|
|
30
|
-
app =
|
|
28
|
+
app = cyclopts.App(
|
|
31
29
|
name="fastmcp",
|
|
32
|
-
help="FastMCP
|
|
33
|
-
|
|
34
|
-
no_args_is_help=True, # Show help if no args provided
|
|
30
|
+
help="FastMCP 2.0 - The fast, Pythonic way to build MCP servers and clients.",
|
|
31
|
+
version=fastmcp.__version__,
|
|
35
32
|
)
|
|
36
33
|
|
|
37
34
|
|
|
@@ -64,6 +61,7 @@ def _build_uv_command(
|
|
|
64
61
|
server_spec: str,
|
|
65
62
|
with_editable: Path | None = None,
|
|
66
63
|
with_packages: list[str] | None = None,
|
|
64
|
+
no_banner: bool = False,
|
|
67
65
|
) -> list[str]:
|
|
68
66
|
"""Build the uv run command that runs a MCP server through mcp run."""
|
|
69
67
|
cmd = ["uv"]
|
|
@@ -80,14 +78,26 @@ def _build_uv_command(
|
|
|
80
78
|
|
|
81
79
|
# Add mcp run command
|
|
82
80
|
cmd.extend(["fastmcp", "run", server_spec])
|
|
83
|
-
return cmd
|
|
84
81
|
|
|
82
|
+
if no_banner:
|
|
83
|
+
cmd.append("--no-banner")
|
|
84
|
+
|
|
85
|
+
return cmd
|
|
85
86
|
|
|
86
|
-
@app.command()
|
|
87
|
-
def version(ctx: Context):
|
|
88
|
-
if ctx.resilient_parsing:
|
|
89
|
-
return
|
|
90
87
|
|
|
88
|
+
@app.command
|
|
89
|
+
def version(
|
|
90
|
+
*,
|
|
91
|
+
copy: Annotated[
|
|
92
|
+
bool,
|
|
93
|
+
cyclopts.Parameter(
|
|
94
|
+
"--copy",
|
|
95
|
+
help="Copy version information to clipboard",
|
|
96
|
+
negative=False,
|
|
97
|
+
),
|
|
98
|
+
] = False,
|
|
99
|
+
):
|
|
100
|
+
"""Display version information and platform details."""
|
|
91
101
|
info = {
|
|
92
102
|
"FastMCP version": fastmcp.__version__,
|
|
93
103
|
"MCP version": importlib.metadata.version("mcp"),
|
|
@@ -101,58 +111,64 @@ def version(ctx: Context):
|
|
|
101
111
|
g.add_column(style="cyan", justify="right")
|
|
102
112
|
for k, v in info.items():
|
|
103
113
|
g.add_row(k + ":", str(v).replace("\n", " "))
|
|
104
|
-
console.print(g)
|
|
105
114
|
|
|
106
|
-
|
|
115
|
+
if copy:
|
|
116
|
+
# Use Rich's plain text rendering for copying
|
|
117
|
+
plain_console = Console(file=None, force_terminal=False, legacy_windows=False)
|
|
118
|
+
with plain_console.capture() as capture:
|
|
119
|
+
plain_console.print(g)
|
|
120
|
+
pyperclip.copy(capture.get())
|
|
121
|
+
console.print("[green]✓[/green] Version information copied to clipboard")
|
|
122
|
+
else:
|
|
123
|
+
console.print(g)
|
|
107
124
|
|
|
108
125
|
|
|
109
|
-
@app.command
|
|
126
|
+
@app.command
|
|
110
127
|
def dev(
|
|
111
|
-
server_spec: str
|
|
112
|
-
|
|
113
|
-
help="Python file to run, optionally with :object suffix",
|
|
114
|
-
),
|
|
128
|
+
server_spec: str,
|
|
129
|
+
*,
|
|
115
130
|
with_editable: Annotated[
|
|
116
131
|
Path | None,
|
|
117
|
-
|
|
118
|
-
"--with-editable",
|
|
119
|
-
"-e",
|
|
132
|
+
cyclopts.Parameter(
|
|
133
|
+
name=["--with-editable", "-e"],
|
|
120
134
|
help="Directory containing pyproject.toml to install in editable mode",
|
|
121
|
-
exists=True,
|
|
122
|
-
file_okay=False,
|
|
123
|
-
resolve_path=True,
|
|
124
135
|
),
|
|
125
136
|
] = None,
|
|
126
137
|
with_packages: Annotated[
|
|
127
138
|
list[str],
|
|
128
|
-
|
|
139
|
+
cyclopts.Parameter(
|
|
129
140
|
"--with",
|
|
130
141
|
help="Additional packages to install",
|
|
142
|
+
negative=False,
|
|
131
143
|
),
|
|
132
144
|
] = [],
|
|
133
145
|
inspector_version: Annotated[
|
|
134
146
|
str | None,
|
|
135
|
-
|
|
147
|
+
cyclopts.Parameter(
|
|
136
148
|
"--inspector-version",
|
|
137
149
|
help="Version of the MCP Inspector to use",
|
|
138
150
|
),
|
|
139
151
|
] = None,
|
|
140
152
|
ui_port: Annotated[
|
|
141
153
|
int | None,
|
|
142
|
-
|
|
154
|
+
cyclopts.Parameter(
|
|
143
155
|
"--ui-port",
|
|
144
156
|
help="Port for the MCP Inspector UI",
|
|
145
157
|
),
|
|
146
158
|
] = None,
|
|
147
159
|
server_port: Annotated[
|
|
148
160
|
int | None,
|
|
149
|
-
|
|
161
|
+
cyclopts.Parameter(
|
|
150
162
|
"--server-port",
|
|
151
163
|
help="Port for the MCP Inspector Proxy server",
|
|
152
164
|
),
|
|
153
165
|
] = None,
|
|
154
166
|
) -> None:
|
|
155
|
-
"""Run
|
|
167
|
+
"""Run an MCP server with the MCP Inspector for development.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
server_spec: Python file to run, optionally with :object suffix
|
|
171
|
+
"""
|
|
156
172
|
file, server_object = run_module.parse_file_path(server_spec)
|
|
157
173
|
|
|
158
174
|
logger.debug(
|
|
@@ -192,7 +208,9 @@ def dev(
|
|
|
192
208
|
if inspector_version:
|
|
193
209
|
inspector_cmd += f"@{inspector_version}"
|
|
194
210
|
|
|
195
|
-
uv_cmd = _build_uv_command(
|
|
211
|
+
uv_cmd = _build_uv_command(
|
|
212
|
+
server_spec, with_editable, with_packages, no_banner=True
|
|
213
|
+
)
|
|
196
214
|
|
|
197
215
|
# Run the MCP Inspector command with shell=True on Windows
|
|
198
216
|
shell = sys.platform == "win32"
|
|
@@ -223,59 +241,69 @@ def dev(
|
|
|
223
241
|
sys.exit(1)
|
|
224
242
|
|
|
225
243
|
|
|
226
|
-
@app.command
|
|
244
|
+
@app.command
|
|
227
245
|
def run(
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
...,
|
|
231
|
-
help="Python file, object specification (file:obj), or URL",
|
|
232
|
-
),
|
|
246
|
+
server_spec: str,
|
|
247
|
+
*,
|
|
233
248
|
transport: Annotated[
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
"--transport",
|
|
237
|
-
"
|
|
238
|
-
help="Transport protocol to use (stdio, http, or sse)",
|
|
249
|
+
Literal["stdio", "http", "sse"] | None,
|
|
250
|
+
cyclopts.Parameter(
|
|
251
|
+
name=["--transport", "-t"],
|
|
252
|
+
help="Transport protocol to use",
|
|
239
253
|
),
|
|
240
254
|
] = None,
|
|
241
255
|
host: Annotated[
|
|
242
256
|
str | None,
|
|
243
|
-
|
|
257
|
+
cyclopts.Parameter(
|
|
244
258
|
"--host",
|
|
245
259
|
help="Host to bind to when using http transport (default: 127.0.0.1)",
|
|
246
260
|
),
|
|
247
261
|
] = None,
|
|
248
262
|
port: Annotated[
|
|
249
263
|
int | None,
|
|
250
|
-
|
|
251
|
-
"--port",
|
|
252
|
-
"-p",
|
|
264
|
+
cyclopts.Parameter(
|
|
265
|
+
name=["--port", "-p"],
|
|
253
266
|
help="Port to bind to when using http transport (default: 8000)",
|
|
254
267
|
),
|
|
255
268
|
] = None,
|
|
256
|
-
|
|
269
|
+
path: Annotated[
|
|
257
270
|
str | None,
|
|
258
|
-
|
|
259
|
-
"--
|
|
260
|
-
"
|
|
261
|
-
help="Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)",
|
|
271
|
+
cyclopts.Parameter(
|
|
272
|
+
"--path",
|
|
273
|
+
help="The route path for the server (default: /mcp/ for http transport, /sse/ for sse transport)",
|
|
262
274
|
),
|
|
263
275
|
] = None,
|
|
276
|
+
log_level: Annotated[
|
|
277
|
+
Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None,
|
|
278
|
+
cyclopts.Parameter(
|
|
279
|
+
name=["--log-level", "-l"],
|
|
280
|
+
help="Log level",
|
|
281
|
+
),
|
|
282
|
+
] = None,
|
|
283
|
+
no_banner: Annotated[
|
|
284
|
+
bool,
|
|
285
|
+
cyclopts.Parameter(
|
|
286
|
+
"--no-banner",
|
|
287
|
+
help="Don't show the server banner",
|
|
288
|
+
negative=False,
|
|
289
|
+
),
|
|
290
|
+
] = False,
|
|
264
291
|
) -> None:
|
|
265
|
-
"""Run
|
|
292
|
+
"""Run an MCP server or connect to a remote one.
|
|
266
293
|
|
|
267
294
|
The server can be specified in three ways:
|
|
268
|
-
1. Module approach: server.py - runs the module directly, looking for an object named mcp
|
|
269
|
-
2. Import approach: server.py:app - imports and runs the specified server object
|
|
270
|
-
3. URL approach: http://server-url - connects to a remote server and creates a proxy
|
|
271
|
-
|
|
272
|
-
Note: This command runs the server directly. You are responsible for ensuring
|
|
273
|
-
all dependencies are available.
|
|
295
|
+
1. Module approach: server.py - runs the module directly, looking for an object named 'mcp', 'server', or 'app'
|
|
296
|
+
2. Import approach: server.py:app - imports and runs the specified server object
|
|
297
|
+
3. URL approach: http://server-url - connects to a remote server and creates a proxy
|
|
274
298
|
|
|
275
299
|
Server arguments can be passed after -- :
|
|
276
300
|
fastmcp run server.py -- --config config.json --debug
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
server_spec: Python file, object specification (file:obj), or URL
|
|
277
304
|
"""
|
|
278
|
-
|
|
305
|
+
# TODO: Handle server_args from extra context
|
|
306
|
+
server_args = [] # Will need to handle this with Cyclopts context
|
|
279
307
|
|
|
280
308
|
logger.debug(
|
|
281
309
|
"Running server or client",
|
|
@@ -284,6 +312,7 @@ def run(
|
|
|
284
312
|
"transport": transport,
|
|
285
313
|
"host": host,
|
|
286
314
|
"port": port,
|
|
315
|
+
"path": path,
|
|
287
316
|
"log_level": log_level,
|
|
288
317
|
"server_args": server_args,
|
|
289
318
|
},
|
|
@@ -295,8 +324,10 @@ def run(
|
|
|
295
324
|
transport=transport,
|
|
296
325
|
host=host,
|
|
297
326
|
port=port,
|
|
327
|
+
path=path,
|
|
298
328
|
log_level=log_level,
|
|
299
329
|
server_args=server_args,
|
|
330
|
+
show_banner=not no_banner,
|
|
300
331
|
)
|
|
301
332
|
except Exception as e:
|
|
302
333
|
logger.error(
|
|
@@ -309,166 +340,33 @@ def run(
|
|
|
309
340
|
sys.exit(1)
|
|
310
341
|
|
|
311
342
|
|
|
312
|
-
@app.command
|
|
313
|
-
def
|
|
314
|
-
server_spec: str
|
|
315
|
-
|
|
316
|
-
help="Python file to run, optionally with :object suffix",
|
|
317
|
-
),
|
|
318
|
-
server_name: Annotated[
|
|
319
|
-
str | None,
|
|
320
|
-
typer.Option(
|
|
321
|
-
"--name",
|
|
322
|
-
"-n",
|
|
323
|
-
help="Custom name for the server (defaults to server's name attribute or"
|
|
324
|
-
" file name)",
|
|
325
|
-
),
|
|
326
|
-
] = None,
|
|
327
|
-
with_editable: Annotated[
|
|
328
|
-
Path | None,
|
|
329
|
-
typer.Option(
|
|
330
|
-
"--with-editable",
|
|
331
|
-
"-e",
|
|
332
|
-
help="Directory containing pyproject.toml to install in editable mode",
|
|
333
|
-
exists=True,
|
|
334
|
-
file_okay=False,
|
|
335
|
-
resolve_path=True,
|
|
336
|
-
),
|
|
337
|
-
] = None,
|
|
338
|
-
with_packages: Annotated[
|
|
339
|
-
list[str],
|
|
340
|
-
typer.Option(
|
|
341
|
-
"--with",
|
|
342
|
-
help="Additional packages to install",
|
|
343
|
-
),
|
|
344
|
-
] = [],
|
|
345
|
-
env_vars: Annotated[
|
|
346
|
-
list[str],
|
|
347
|
-
typer.Option(
|
|
348
|
-
"--env-var",
|
|
349
|
-
"-v",
|
|
350
|
-
help="Environment variables in KEY=VALUE format",
|
|
351
|
-
),
|
|
352
|
-
] = [],
|
|
353
|
-
env_file: Annotated[
|
|
354
|
-
Path | None,
|
|
355
|
-
typer.Option(
|
|
356
|
-
"--env-file",
|
|
357
|
-
"-f",
|
|
358
|
-
help="Load environment variables from a .env file",
|
|
359
|
-
exists=True,
|
|
360
|
-
file_okay=True,
|
|
361
|
-
dir_okay=False,
|
|
362
|
-
resolve_path=True,
|
|
363
|
-
),
|
|
364
|
-
] = None,
|
|
365
|
-
) -> None:
|
|
366
|
-
"""Install a MCP server in the Claude desktop app.
|
|
367
|
-
|
|
368
|
-
Environment variables are preserved once added and only updated if new values
|
|
369
|
-
are explicitly provided.
|
|
370
|
-
"""
|
|
371
|
-
file, server_object = run_module.parse_file_path(server_spec)
|
|
372
|
-
|
|
373
|
-
logger.debug(
|
|
374
|
-
"Installing server",
|
|
375
|
-
extra={
|
|
376
|
-
"file": str(file),
|
|
377
|
-
"server_name": server_name,
|
|
378
|
-
"server_object": server_object,
|
|
379
|
-
"with_editable": str(with_editable) if with_editable else None,
|
|
380
|
-
"with_packages": with_packages,
|
|
381
|
-
},
|
|
382
|
-
)
|
|
383
|
-
|
|
384
|
-
if not claude.get_claude_config_path():
|
|
385
|
-
logger.error("Claude app not found")
|
|
386
|
-
sys.exit(1)
|
|
387
|
-
|
|
388
|
-
# Try to import server to get its name, but fall back to file name if dependencies
|
|
389
|
-
# missing
|
|
390
|
-
name = server_name
|
|
391
|
-
server = None
|
|
392
|
-
if not name:
|
|
393
|
-
try:
|
|
394
|
-
server = run_module.import_server(file, server_object)
|
|
395
|
-
name = server.name
|
|
396
|
-
except (ImportError, ModuleNotFoundError) as e:
|
|
397
|
-
logger.debug(
|
|
398
|
-
"Could not import server (likely missing dependencies), using file"
|
|
399
|
-
" name",
|
|
400
|
-
extra={"error": str(e)},
|
|
401
|
-
)
|
|
402
|
-
name = file.stem
|
|
403
|
-
|
|
404
|
-
# Get server dependencies if available
|
|
405
|
-
server_dependencies = getattr(server, "dependencies", []) if server else []
|
|
406
|
-
if server_dependencies:
|
|
407
|
-
with_packages = list(set(with_packages + server_dependencies))
|
|
408
|
-
|
|
409
|
-
# Process environment variables if provided
|
|
410
|
-
env_dict: dict[str, str] | None = None
|
|
411
|
-
if env_file or env_vars:
|
|
412
|
-
env_dict = {}
|
|
413
|
-
# Load from .env file if specified
|
|
414
|
-
if env_file:
|
|
415
|
-
try:
|
|
416
|
-
env_dict |= {
|
|
417
|
-
k: v
|
|
418
|
-
for k, v in dotenv.dotenv_values(env_file).items()
|
|
419
|
-
if v is not None
|
|
420
|
-
}
|
|
421
|
-
except Exception as e:
|
|
422
|
-
logger.error(f"Failed to load .env file: {e}")
|
|
423
|
-
sys.exit(1)
|
|
424
|
-
|
|
425
|
-
# Add command line environment variables
|
|
426
|
-
for env_var in env_vars:
|
|
427
|
-
key, value = _parse_env_var(env_var)
|
|
428
|
-
env_dict[key] = value
|
|
429
|
-
|
|
430
|
-
if claude.update_claude_config(
|
|
431
|
-
server_spec,
|
|
432
|
-
name,
|
|
433
|
-
with_editable=with_editable,
|
|
434
|
-
with_packages=with_packages,
|
|
435
|
-
env_vars=env_dict,
|
|
436
|
-
):
|
|
437
|
-
logger.info(f"Successfully installed {name} in Claude app")
|
|
438
|
-
else:
|
|
439
|
-
logger.error(f"Failed to install {name} in Claude app")
|
|
440
|
-
sys.exit(1)
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
@app.command()
|
|
444
|
-
def inspect(
|
|
445
|
-
server_spec: str = typer.Argument(
|
|
446
|
-
...,
|
|
447
|
-
help="Python file to inspect, optionally with :object suffix",
|
|
448
|
-
),
|
|
343
|
+
@app.command
|
|
344
|
+
async def inspect(
|
|
345
|
+
server_spec: str,
|
|
346
|
+
*,
|
|
449
347
|
output: Annotated[
|
|
450
348
|
Path,
|
|
451
|
-
|
|
452
|
-
"--output",
|
|
453
|
-
"-o",
|
|
349
|
+
cyclopts.Parameter(
|
|
350
|
+
name=["--output", "-o"],
|
|
454
351
|
help="Output file path for the JSON report (default: server-info.json)",
|
|
455
352
|
),
|
|
456
353
|
] = Path("server-info.json"),
|
|
457
354
|
) -> None:
|
|
458
|
-
"""Inspect
|
|
355
|
+
"""Inspect an MCP server and generate a JSON report.
|
|
459
356
|
|
|
460
|
-
This command analyzes
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
and capabilities.
|
|
357
|
+
This command analyzes an MCP server and generates a comprehensive JSON report
|
|
358
|
+
containing information about the server's name, instructions, version, tools,
|
|
359
|
+
prompts, resources, templates, and capabilities.
|
|
464
360
|
|
|
465
361
|
Examples:
|
|
466
362
|
fastmcp inspect server.py
|
|
467
363
|
fastmcp inspect server.py -o report.json
|
|
468
364
|
fastmcp inspect server.py:mcp -o analysis.json
|
|
469
365
|
fastmcp inspect path/to/server.py:app -o /tmp/server-info.json
|
|
470
|
-
"""
|
|
471
366
|
|
|
367
|
+
Args:
|
|
368
|
+
server_spec: Python file to inspect, optionally with :object suffix
|
|
369
|
+
"""
|
|
472
370
|
# Parse the server specification
|
|
473
371
|
file, server_object = run_module.parse_file_path(server_spec)
|
|
474
372
|
|
|
@@ -485,22 +383,8 @@ def inspect(
|
|
|
485
383
|
# Import the server
|
|
486
384
|
server = run_module.import_server(file, server_object)
|
|
487
385
|
|
|
488
|
-
# Get server information
|
|
489
|
-
|
|
490
|
-
return await inspect_fastmcp(server)
|
|
491
|
-
|
|
492
|
-
try:
|
|
493
|
-
# Try to use existing event loop if available
|
|
494
|
-
asyncio.get_running_loop()
|
|
495
|
-
# If there's already a loop running, we need to run in a thread
|
|
496
|
-
import concurrent.futures
|
|
497
|
-
|
|
498
|
-
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
499
|
-
future = executor.submit(asyncio.run, get_info())
|
|
500
|
-
info = future.result()
|
|
501
|
-
except RuntimeError:
|
|
502
|
-
# No running loop, safe to use asyncio.run
|
|
503
|
-
info = asyncio.run(get_info())
|
|
386
|
+
# Get server information - using native async support
|
|
387
|
+
info = await inspect_fastmcp(server)
|
|
504
388
|
|
|
505
389
|
info_json = TypeAdapter(FastMCPInfo).dump_json(info, indent=2)
|
|
506
390
|
|
|
@@ -533,3 +417,11 @@ def inspect(
|
|
|
533
417
|
)
|
|
534
418
|
console.print(f"[bold red]✗[/bold red] Failed to inspect server: {e}")
|
|
535
419
|
sys.exit(1)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
# Add install subcommands using proper Cyclopts pattern
|
|
423
|
+
app.command(install_app)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
if __name__ == "__main__":
|
|
427
|
+
app()
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Install subcommands for FastMCP CLI using Cyclopts."""
|
|
2
|
+
|
|
3
|
+
import cyclopts
|
|
4
|
+
|
|
5
|
+
from .claude_code import claude_code_command
|
|
6
|
+
from .claude_desktop import claude_desktop_command
|
|
7
|
+
from .cursor import cursor_command
|
|
8
|
+
from .mcp_config import mcp_config_command
|
|
9
|
+
|
|
10
|
+
# Create a cyclopts app for install subcommands
|
|
11
|
+
install_app = cyclopts.App(
|
|
12
|
+
name="install",
|
|
13
|
+
help="Install MCP servers in various clients and formats.",
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Register each command from its respective module
|
|
17
|
+
install_app.command(claude_code_command, name="claude-code")
|
|
18
|
+
install_app.command(claude_desktop_command, name="claude-desktop")
|
|
19
|
+
install_app.command(cursor_command, name="cursor")
|
|
20
|
+
install_app.command(mcp_config_command, name="mcp-json")
|