Spaces:
Running
Running
Jeremiah Lowin commited on
Add MCP json output from CLI (#1056)
Browse files- docs/css/style.css +1 -0
- docs/docs.json +5 -4
- docs/integrations/anthropic.mdx +2 -2
- docs/integrations/chatgpt.mdx +1 -1
- docs/integrations/claude-code.mdx +4 -5
- docs/integrations/claude-desktop.mdx +3 -1
- docs/integrations/cursor.mdx +22 -5
- docs/integrations/eunomia-authorization.mdx +2 -2
- docs/integrations/gemini.mdx +2 -2
- docs/integrations/mcp-json-configuration.mdx +347 -0
- docs/integrations/openai.mdx +2 -2
- docs/patterns/cli.mdx +49 -3
- docs/{integrations → patterns}/contrib.mdx +0 -0
- docs/patterns/tool-transformation.mdx +0 -1
- docs/snippets/local-focus.mdx +7 -0
- pyproject.toml +1 -0
- src/fastmcp/cli/cli.py +3 -3
- src/fastmcp/cli/install/__init__.py +25 -3
- src/fastmcp/cli/install/claude_code.py +75 -0
- src/fastmcp/cli/install/claude_desktop.py +74 -0
- src/fastmcp/cli/install/cursor.py +71 -0
- src/fastmcp/cli/install/mcp_config.py +179 -0
- src/fastmcp/cli/install/{install.py → shared.py} +20 -131
- tests/cli/test_mcp_config.py +199 -0
- uv.lock +2 -0
docs/css/style.css
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
/* Code highlighting -- target only inline code elements, not code blocks */
|
| 2 |
p code:not(pre code),
|
| 3 |
table code:not(pre code),
|
|
|
|
| 4 |
li code:not(pre code),
|
| 5 |
h1 code:not(pre code),
|
| 6 |
h2 code:not(pre code),
|
|
|
|
| 1 |
/* Code highlighting -- target only inline code elements, not code blocks */
|
| 2 |
p code:not(pre code),
|
| 3 |
table code:not(pre code),
|
| 4 |
+
.prose code:not(pre code),
|
| 5 |
li code:not(pre code),
|
| 6 |
h1 code:not(pre code),
|
| 7 |
h2 code:not(pre code),
|
docs/docs.json
CHANGED
|
@@ -140,10 +140,10 @@
|
|
| 140 |
"integrations/claude-code",
|
| 141 |
"integrations/claude-desktop",
|
| 142 |
"integrations/cursor",
|
| 143 |
-
"integrations/gemini",
|
| 144 |
-
"integrations/openai",
|
| 145 |
"integrations/eunomia-authorization",
|
| 146 |
-
"integrations/
|
|
|
|
|
|
|
| 147 |
]
|
| 148 |
},
|
| 149 |
{
|
|
@@ -153,7 +153,8 @@
|
|
| 153 |
"patterns/decorating-methods",
|
| 154 |
"patterns/http-requests",
|
| 155 |
"patterns/testing",
|
| 156 |
-
"patterns/cli"
|
|
|
|
| 157 |
]
|
| 158 |
},
|
| 159 |
{
|
|
|
|
| 140 |
"integrations/claude-code",
|
| 141 |
"integrations/claude-desktop",
|
| 142 |
"integrations/cursor",
|
|
|
|
|
|
|
| 143 |
"integrations/eunomia-authorization",
|
| 144 |
+
"integrations/gemini",
|
| 145 |
+
"integrations/mcp-json-configuration",
|
| 146 |
+
"integrations/openai"
|
| 147 |
]
|
| 148 |
},
|
| 149 |
{
|
|
|
|
| 153 |
"patterns/decorating-methods",
|
| 154 |
"patterns/http-requests",
|
| 155 |
"patterns/testing",
|
| 156 |
+
"patterns/cli",
|
| 157 |
+
"patterns/contrib"
|
| 158 |
]
|
| 159 |
},
|
| 160 |
{
|
docs/integrations/anthropic.mdx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
---
|
| 2 |
-
title: Anthropic API
|
| 3 |
sidebarTitle: Anthropic API
|
| 4 |
description: Call FastMCP servers from the Anthropic API
|
| 5 |
-
icon: message-
|
| 6 |
---
|
| 7 |
|
| 8 |
import { VersionBadge } from "/snippets/version-badge.mdx"
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Anthropic API 🤝 FastMCP
|
| 3 |
sidebarTitle: Anthropic API
|
| 4 |
description: Call FastMCP servers from the Anthropic API
|
| 5 |
+
icon: message-code
|
| 6 |
---
|
| 7 |
|
| 8 |
import { VersionBadge } from "/snippets/version-badge.mdx"
|
docs/integrations/chatgpt.mdx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
---
|
| 2 |
-
title: ChatGPT
|
| 3 |
sidebarTitle: ChatGPT
|
| 4 |
description: Connect FastMCP servers to ChatGPT Deep Research
|
| 5 |
icon: message-smile
|
|
|
|
| 1 |
---
|
| 2 |
+
title: ChatGPT 🤝 FastMCP
|
| 3 |
sidebarTitle: ChatGPT
|
| 4 |
description: Connect FastMCP servers to ChatGPT Deep Research
|
| 5 |
icon: message-smile
|
docs/integrations/claude-code.mdx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
---
|
| 2 |
-
title: Claude Code
|
| 3 |
sidebarTitle: Claude Code
|
| 4 |
description: Install and use FastMCP servers in Claude Code
|
| 5 |
icon: message-smile
|
|
@@ -7,12 +7,11 @@ tag: NEW
|
|
| 7 |
---
|
| 8 |
|
| 9 |
import { VersionBadge } from "/snippets/version-badge.mdx"
|
|
|
|
| 10 |
|
| 11 |
-
|
| 12 |
|
| 13 |
-
|
| 14 |
-
This guide focuses specifically on installing local FastMCP server files directly into Claude Code using STDIO transport. For deploying remote servers using SSE or HTTP transports, see the [Claude Code MCP documentation](https://docs.anthropic.com/en/docs/claude-code/mcp).
|
| 15 |
-
</Note>
|
| 16 |
|
| 17 |
## Requirements
|
| 18 |
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Claude Code 🤝 FastMCP
|
| 3 |
sidebarTitle: Claude Code
|
| 4 |
description: Install and use FastMCP servers in Claude Code
|
| 5 |
icon: message-smile
|
|
|
|
| 7 |
---
|
| 8 |
|
| 9 |
import { VersionBadge } from "/snippets/version-badge.mdx"
|
| 10 |
+
import { LocalFocusTip } from "/snippets/local-focus.mdx"
|
| 11 |
|
| 12 |
+
<LocalFocusTip />
|
| 13 |
|
| 14 |
+
Claude Code supports MCP servers through multiple transport methods including STDIO, SSE, and HTTP, allowing you to extend Claude's capabilities with custom tools, resources, and prompts from your FastMCP servers.
|
|
|
|
|
|
|
| 15 |
|
| 16 |
## Requirements
|
| 17 |
|
docs/integrations/claude-desktop.mdx
CHANGED
|
@@ -1,12 +1,14 @@
|
|
| 1 |
---
|
| 2 |
-
title: Claude Desktop
|
| 3 |
sidebarTitle: Claude Desktop
|
| 4 |
description: Call FastMCP servers from Claude Desktop
|
| 5 |
icon: message-smile
|
| 6 |
---
|
| 7 |
|
| 8 |
import { VersionBadge } from "/snippets/version-badge.mdx"
|
|
|
|
| 9 |
|
|
|
|
| 10 |
|
| 11 |
Claude Desktop supports MCP servers through local STDIO connections and remote servers (beta), allowing you to extend Claude's capabilities with custom tools, resources, and prompts from your FastMCP servers.
|
| 12 |
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Claude Desktop 🤝 FastMCP
|
| 3 |
sidebarTitle: Claude Desktop
|
| 4 |
description: Call FastMCP servers from Claude Desktop
|
| 5 |
icon: message-smile
|
| 6 |
---
|
| 7 |
|
| 8 |
import { VersionBadge } from "/snippets/version-badge.mdx"
|
| 9 |
+
import { LocalFocusTip } from "/snippets/local-focus.mdx"
|
| 10 |
|
| 11 |
+
<LocalFocusTip />
|
| 12 |
|
| 13 |
Claude Desktop supports MCP servers through local STDIO connections and remote servers (beta), allowing you to extend Claude's capabilities with custom tools, resources, and prompts from your FastMCP servers.
|
| 14 |
|
docs/integrations/cursor.mdx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
---
|
| 2 |
-
title: Cursor
|
| 3 |
sidebarTitle: Cursor
|
| 4 |
description: Install and use FastMCP servers in Cursor
|
| 5 |
icon: message-smile
|
|
@@ -7,12 +7,11 @@ tag: NEW
|
|
| 7 |
---
|
| 8 |
|
| 9 |
import { VersionBadge } from "/snippets/version-badge.mdx"
|
|
|
|
| 10 |
|
| 11 |
-
|
| 12 |
|
| 13 |
-
|
| 14 |
-
This guide focuses specifically on installing local FastMCP server files directly into Cursor using STDIO transport. For deploying remote servers using SSE or HTTP transports, see the [Cursor MCP documentation](https://docs.cursor.com/context/mcp).
|
| 15 |
-
</Note>
|
| 16 |
|
| 17 |
## Requirements
|
| 18 |
|
|
@@ -106,6 +105,24 @@ fastmcp install cursor server.py --name "Weather Server" --env-file .env
|
|
| 106 |
**`uv` must be installed and available in your system PATH**. Cursor runs in its own isolated environment and needs `uv` to manage dependencies.
|
| 107 |
</Warning>
|
| 108 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
### Manual Configuration
|
| 110 |
|
| 111 |
For more control over the configuration, you can manually edit Cursor's configuration file. The configuration file is located at:
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Cursor 🤝 FastMCP
|
| 3 |
sidebarTitle: Cursor
|
| 4 |
description: Install and use FastMCP servers in Cursor
|
| 5 |
icon: message-smile
|
|
|
|
| 7 |
---
|
| 8 |
|
| 9 |
import { VersionBadge } from "/snippets/version-badge.mdx"
|
| 10 |
+
import { LocalFocusTip } from "/snippets/local-focus.mdx"
|
| 11 |
|
| 12 |
+
<LocalFocusTip />
|
| 13 |
|
| 14 |
+
Cursor supports MCP servers through multiple transport methods including STDIO, SSE, and Streamable HTTP, allowing you to extend Cursor's AI assistant with custom tools, resources, and prompts from your FastMCP servers.
|
|
|
|
|
|
|
| 15 |
|
| 16 |
## Requirements
|
| 17 |
|
|
|
|
| 105 |
**`uv` must be installed and available in your system PATH**. Cursor runs in its own isolated environment and needs `uv` to manage dependencies.
|
| 106 |
</Warning>
|
| 107 |
|
| 108 |
+
### Generate MCP JSON
|
| 109 |
+
|
| 110 |
+
<Note>
|
| 111 |
+
**Use the first-class integration above for the best experience.** The MCP JSON generation is useful for advanced use cases, manual configuration, or integration with other tools.
|
| 112 |
+
</Note>
|
| 113 |
+
|
| 114 |
+
You can generate MCP JSON configuration for manual use:
|
| 115 |
+
|
| 116 |
+
```bash
|
| 117 |
+
# Generate configuration and output to stdout
|
| 118 |
+
fastmcp install mcp-json server.py --name "Dice Roller" --with pandas
|
| 119 |
+
|
| 120 |
+
# Copy configuration to clipboard for easy pasting
|
| 121 |
+
fastmcp install mcp-json server.py --name "Dice Roller" --copy
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
This generates the standard `mcpServers` configuration format that can be used with any MCP-compatible client.
|
| 125 |
+
|
| 126 |
### Manual Configuration
|
| 127 |
|
| 128 |
For more control over the configuration, you can manually edit Cursor's configuration file. The configuration file is located at:
|
docs/integrations/eunomia-authorization.mdx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
---
|
| 2 |
-
title: Eunomia Authorization
|
| 3 |
sidebarTitle: Eunomia Auth
|
| 4 |
description: Add policy-based authorization to your FastMCP servers
|
| 5 |
-
icon:
|
| 6 |
tag: NEW
|
| 7 |
---
|
| 8 |
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Eunomia Authorization 🤝 FastMCP
|
| 3 |
sidebarTitle: Eunomia Auth
|
| 4 |
description: Add policy-based authorization to your FastMCP servers
|
| 5 |
+
icon: shield-check
|
| 6 |
tag: NEW
|
| 7 |
---
|
| 8 |
|
docs/integrations/gemini.mdx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
---
|
| 2 |
-
title: Gemini SDK
|
| 3 |
sidebarTitle: Gemini SDK
|
| 4 |
description: Call FastMCP servers from the Google Gemini SDK
|
| 5 |
-
icon: message-
|
| 6 |
---
|
| 7 |
|
| 8 |
import { VersionBadge } from "/snippets/version-badge.mdx"
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Gemini SDK 🤝 FastMCP
|
| 3 |
sidebarTitle: Gemini SDK
|
| 4 |
description: Call FastMCP servers from the Google Gemini SDK
|
| 5 |
+
icon: message-code
|
| 6 |
---
|
| 7 |
|
| 8 |
import { VersionBadge } from "/snippets/version-badge.mdx"
|
docs/integrations/mcp-json-configuration.mdx
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: MCP JSON Configuration 🤝 FastMCP
|
| 3 |
+
sidebarTitle: MCP JSON
|
| 4 |
+
description: Generate standard MCP configuration files for any compatible client
|
| 5 |
+
icon: brackets-curly
|
| 6 |
+
tag: NEW
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
import { VersionBadge } from "/snippets/version-badge.mdx"
|
| 10 |
+
|
| 11 |
+
<VersionBadge version="2.10.3" />
|
| 12 |
+
|
| 13 |
+
FastMCP can generate standard MCP JSON configuration files that work with any MCP-compatible client including Claude Desktop, VS Code, Cursor, and other applications that support the Model Context Protocol.
|
| 14 |
+
|
| 15 |
+
## MCP JSON Configuration Standard
|
| 16 |
+
|
| 17 |
+
The MCP JSON configuration format is an **emergent standard** that has developed across the MCP ecosystem. This format defines how MCP clients should configure and launch MCP servers, providing a consistent way to specify server commands, arguments, and environment variables.
|
| 18 |
+
|
| 19 |
+
### Configuration Structure
|
| 20 |
+
|
| 21 |
+
The standard uses a `mcpServers` object where each key represents a server name and the value contains the server's configuration:
|
| 22 |
+
|
| 23 |
+
```json
|
| 24 |
+
{
|
| 25 |
+
"mcpServers": {
|
| 26 |
+
"server-name": {
|
| 27 |
+
"command": "executable",
|
| 28 |
+
"args": ["arg1", "arg2"],
|
| 29 |
+
"env": {
|
| 30 |
+
"VAR": "value"
|
| 31 |
+
}
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
### Server Configuration Fields
|
| 38 |
+
|
| 39 |
+
#### `command` (required)
|
| 40 |
+
The executable command to run the MCP server. This should be an absolute path or a command available in the system PATH.
|
| 41 |
+
|
| 42 |
+
```json
|
| 43 |
+
{
|
| 44 |
+
"command": "python"
|
| 45 |
+
}
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
#### `args` (optional)
|
| 49 |
+
An array of command-line arguments passed to the server executable. Arguments are passed in order.
|
| 50 |
+
|
| 51 |
+
```json
|
| 52 |
+
{
|
| 53 |
+
"args": ["server.py", "--verbose", "--port", "8080"]
|
| 54 |
+
}
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
#### `env` (optional)
|
| 58 |
+
An object containing environment variables to set when launching the server. All values must be strings.
|
| 59 |
+
|
| 60 |
+
```json
|
| 61 |
+
{
|
| 62 |
+
"env": {
|
| 63 |
+
"API_KEY": "secret-key",
|
| 64 |
+
"DEBUG": "true",
|
| 65 |
+
"PORT": "8080"
|
| 66 |
+
}
|
| 67 |
+
}
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
### Client Adoption
|
| 71 |
+
|
| 72 |
+
This format is widely adopted across the MCP ecosystem:
|
| 73 |
+
|
| 74 |
+
- **Claude Desktop**: Uses `~/.claude/claude_desktop_config.json`
|
| 75 |
+
- **Cursor**: Uses `~/.cursor/mcp.json`
|
| 76 |
+
- **VS Code**: Uses workspace `.vscode/mcp.json`
|
| 77 |
+
- **Other clients**: Many MCP-compatible applications follow this standard
|
| 78 |
+
|
| 79 |
+
## Overview
|
| 80 |
+
|
| 81 |
+
<Note>
|
| 82 |
+
**For the best experience, use FastMCP's first-class integrations:** [`fastmcp install claude-code`](/integrations/claude-code), [`fastmcp install claude-desktop`](/integrations/claude-desktop), or [`fastmcp install cursor`](/integrations/cursor). Use MCP JSON generation for advanced use cases and unsupported clients.
|
| 83 |
+
</Note>
|
| 84 |
+
|
| 85 |
+
The `fastmcp install mcp-json` command generates configuration in the standard `mcpServers` format used across the MCP ecosystem. This is useful when:
|
| 86 |
+
|
| 87 |
+
- **Working with unsupported clients** - Any MCP client not directly integrated with FastMCP
|
| 88 |
+
- **CI/CD environments** - Automated configuration generation for deployments
|
| 89 |
+
- **Configuration sharing** - Easy distribution of server setups to team members
|
| 90 |
+
- **Custom tooling** - Integration with your own MCP management tools
|
| 91 |
+
- **Manual setup** - When you prefer to manually configure your MCP client
|
| 92 |
+
|
| 93 |
+
## Basic Usage
|
| 94 |
+
|
| 95 |
+
Generate configuration and output to stdout (useful for piping):
|
| 96 |
+
|
| 97 |
+
```bash
|
| 98 |
+
fastmcp install mcp-json server.py
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
This outputs the server configuration JSON that you add to the `mcpServers` object:
|
| 102 |
+
|
| 103 |
+
```json
|
| 104 |
+
{
|
| 105 |
+
"command": "uv",
|
| 106 |
+
"args": [
|
| 107 |
+
"run",
|
| 108 |
+
"--with",
|
| 109 |
+
"fastmcp",
|
| 110 |
+
"fastmcp",
|
| 111 |
+
"run",
|
| 112 |
+
"/absolute/path/to/server.py"
|
| 113 |
+
]
|
| 114 |
+
}
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
To use this in a client configuration file, add it under a server name in the `mcpServers` object:
|
| 118 |
+
|
| 119 |
+
```json
|
| 120 |
+
{
|
| 121 |
+
"mcpServers": {
|
| 122 |
+
"My Server": {
|
| 123 |
+
"command": "uv",
|
| 124 |
+
"args": [
|
| 125 |
+
"run",
|
| 126 |
+
"--with",
|
| 127 |
+
"fastmcp",
|
| 128 |
+
"fastmcp",
|
| 129 |
+
"run",
|
| 130 |
+
"/absolute/path/to/server.py"
|
| 131 |
+
]
|
| 132 |
+
}
|
| 133 |
+
}
|
| 134 |
+
}
|
| 135 |
+
```
|
| 136 |
+
|
| 137 |
+
## Configuration Options
|
| 138 |
+
|
| 139 |
+
### Server Naming
|
| 140 |
+
|
| 141 |
+
```bash
|
| 142 |
+
# Use server's built-in name (from FastMCP constructor)
|
| 143 |
+
fastmcp install mcp-json server.py
|
| 144 |
+
|
| 145 |
+
# Override with custom name
|
| 146 |
+
fastmcp install mcp-json server.py --name "Custom Server Name"
|
| 147 |
+
```
|
| 148 |
+
|
| 149 |
+
### Dependencies
|
| 150 |
+
|
| 151 |
+
Add Python packages your server needs:
|
| 152 |
+
|
| 153 |
+
```bash
|
| 154 |
+
# Single package
|
| 155 |
+
fastmcp install mcp-json server.py --with pandas
|
| 156 |
+
|
| 157 |
+
# Multiple packages
|
| 158 |
+
fastmcp install mcp-json server.py --with pandas --with requests --with httpx
|
| 159 |
+
|
| 160 |
+
# Editable local package
|
| 161 |
+
fastmcp install mcp-json server.py --with-editable ./my-package
|
| 162 |
+
```
|
| 163 |
+
|
| 164 |
+
You can also specify dependencies directly in your server code:
|
| 165 |
+
|
| 166 |
+
```python server.py
|
| 167 |
+
from fastmcp import FastMCP
|
| 168 |
+
|
| 169 |
+
mcp = FastMCP(
|
| 170 |
+
name="Data Analysis Server",
|
| 171 |
+
dependencies=["pandas", "matplotlib", "seaborn"]
|
| 172 |
+
)
|
| 173 |
+
```
|
| 174 |
+
|
| 175 |
+
### Environment Variables
|
| 176 |
+
|
| 177 |
+
```bash
|
| 178 |
+
# Individual environment variables
|
| 179 |
+
fastmcp install mcp-json server.py \
|
| 180 |
+
--env-var API_KEY=your-secret-key \
|
| 181 |
+
--env-var DEBUG=true
|
| 182 |
+
|
| 183 |
+
# Load from .env file
|
| 184 |
+
fastmcp install mcp-json server.py --env-file .env
|
| 185 |
+
```
|
| 186 |
+
|
| 187 |
+
### Server Object Selection
|
| 188 |
+
|
| 189 |
+
Use the same `file.py:object` notation as other FastMCP commands:
|
| 190 |
+
|
| 191 |
+
```bash
|
| 192 |
+
# Auto-detects server object (looks for 'mcp', 'server', or 'app')
|
| 193 |
+
fastmcp install mcp-json server.py
|
| 194 |
+
|
| 195 |
+
# Explicit server object
|
| 196 |
+
fastmcp install mcp-json server.py:my_custom_server
|
| 197 |
+
```
|
| 198 |
+
|
| 199 |
+
## Clipboard Integration
|
| 200 |
+
|
| 201 |
+
Copy configuration directly to your clipboard for easy pasting:
|
| 202 |
+
|
| 203 |
+
```bash
|
| 204 |
+
fastmcp install mcp-json server.py --copy
|
| 205 |
+
```
|
| 206 |
+
|
| 207 |
+
<Note>
|
| 208 |
+
The `--copy` flag requires the `pyperclip` Python package. If not installed, you'll see an error message with installation instructions.
|
| 209 |
+
</Note>
|
| 210 |
+
|
| 211 |
+
## Usage Examples
|
| 212 |
+
|
| 213 |
+
### Basic Server
|
| 214 |
+
|
| 215 |
+
```bash
|
| 216 |
+
fastmcp install mcp-json dice_server.py
|
| 217 |
+
```
|
| 218 |
+
|
| 219 |
+
Output:
|
| 220 |
+
```json
|
| 221 |
+
{
|
| 222 |
+
"command": "uv",
|
| 223 |
+
"args": [
|
| 224 |
+
"run",
|
| 225 |
+
"--with",
|
| 226 |
+
"fastmcp",
|
| 227 |
+
"fastmcp",
|
| 228 |
+
"run",
|
| 229 |
+
"/home/user/dice_server.py"
|
| 230 |
+
]
|
| 231 |
+
}
|
| 232 |
+
```
|
| 233 |
+
|
| 234 |
+
### Production Server with Dependencies
|
| 235 |
+
|
| 236 |
+
```bash
|
| 237 |
+
fastmcp install mcp-json api_server.py \
|
| 238 |
+
--name "Production API Server" \
|
| 239 |
+
--with requests \
|
| 240 |
+
--with python-dotenv \
|
| 241 |
+
--env-var API_BASE_URL=https://api.example.com \
|
| 242 |
+
--env-var TIMEOUT=30
|
| 243 |
+
```
|
| 244 |
+
|
| 245 |
+
Output:
|
| 246 |
+
```json
|
| 247 |
+
{
|
| 248 |
+
"command": "uv",
|
| 249 |
+
"args": [
|
| 250 |
+
"run",
|
| 251 |
+
"--with",
|
| 252 |
+
"fastmcp",
|
| 253 |
+
"--with",
|
| 254 |
+
"python-dotenv",
|
| 255 |
+
"--with",
|
| 256 |
+
"requests",
|
| 257 |
+
"fastmcp",
|
| 258 |
+
"run",
|
| 259 |
+
"/home/user/api_server.py"
|
| 260 |
+
],
|
| 261 |
+
"env": {
|
| 262 |
+
"API_BASE_URL": "https://api.example.com",
|
| 263 |
+
"TIMEOUT": "30"
|
| 264 |
+
}
|
| 265 |
+
}
|
| 266 |
+
```
|
| 267 |
+
|
| 268 |
+
### Pipeline Usage
|
| 269 |
+
|
| 270 |
+
Save configuration to file:
|
| 271 |
+
|
| 272 |
+
```bash
|
| 273 |
+
fastmcp install mcp-json server.py > mcp-config.json
|
| 274 |
+
```
|
| 275 |
+
|
| 276 |
+
Use in shell scripts:
|
| 277 |
+
|
| 278 |
+
```bash
|
| 279 |
+
#!/bin/bash
|
| 280 |
+
CONFIG=$(fastmcp install mcp-json server.py --name "CI Server")
|
| 281 |
+
echo "$CONFIG" | jq '.command'
|
| 282 |
+
# Output: "uv"
|
| 283 |
+
```
|
| 284 |
+
|
| 285 |
+
## Integration with MCP Clients
|
| 286 |
+
|
| 287 |
+
The generated configuration works with any MCP-compatible application:
|
| 288 |
+
|
| 289 |
+
### Claude Desktop
|
| 290 |
+
<Note>
|
| 291 |
+
**Prefer [`fastmcp install claude-desktop`](/integrations/claude-desktop)** for automatic installation. Use MCP JSON for advanced configuration needs.
|
| 292 |
+
</Note>
|
| 293 |
+
Copy the `mcpServers` object into `~/.claude/claude_desktop_config.json`
|
| 294 |
+
|
| 295 |
+
### Cursor
|
| 296 |
+
<Note>
|
| 297 |
+
**Prefer [`fastmcp install cursor`](/integrations/cursor)** for automatic installation. Use MCP JSON for advanced configuration needs.
|
| 298 |
+
</Note>
|
| 299 |
+
Add to `~/.cursor/mcp.json`
|
| 300 |
+
|
| 301 |
+
### VS Code
|
| 302 |
+
Add to your workspace's `.vscode/mcp.json` file
|
| 303 |
+
|
| 304 |
+
### Custom Applications
|
| 305 |
+
Use the JSON configuration with any application that supports the MCP protocol
|
| 306 |
+
|
| 307 |
+
## Configuration Format
|
| 308 |
+
|
| 309 |
+
The generated configuration follows the standard MCP server specification:
|
| 310 |
+
|
| 311 |
+
```json
|
| 312 |
+
{
|
| 313 |
+
"mcpServers": {
|
| 314 |
+
"<server-name>": {
|
| 315 |
+
"command": "<executable>",
|
| 316 |
+
"args": ["<arg1>", "<arg2>", "..."],
|
| 317 |
+
"env": {
|
| 318 |
+
"<ENV_VAR>": "<value>"
|
| 319 |
+
}
|
| 320 |
+
}
|
| 321 |
+
}
|
| 322 |
+
}
|
| 323 |
+
```
|
| 324 |
+
|
| 325 |
+
**Fields:**
|
| 326 |
+
- `command`: The executable to run (always `uv` for FastMCP servers)
|
| 327 |
+
- `args`: Command-line arguments including dependencies and server path
|
| 328 |
+
- `env`: Environment variables (only included if specified)
|
| 329 |
+
|
| 330 |
+
<Warning>
|
| 331 |
+
**All file paths in the generated configuration are absolute paths**. This ensures the configuration works regardless of the working directory when the MCP client starts the server.
|
| 332 |
+
</Warning>
|
| 333 |
+
|
| 334 |
+
## Requirements
|
| 335 |
+
|
| 336 |
+
- **uv**: Must be installed and available in your system PATH
|
| 337 |
+
- **pyperclip** (optional): Required only for `--copy` functionality
|
| 338 |
+
|
| 339 |
+
Install uv if not already available:
|
| 340 |
+
|
| 341 |
+
```bash
|
| 342 |
+
# macOS
|
| 343 |
+
brew install uv
|
| 344 |
+
|
| 345 |
+
# Linux/Windows
|
| 346 |
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
| 347 |
+
```
|
docs/integrations/openai.mdx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
---
|
| 2 |
-
title: OpenAI API
|
| 3 |
sidebarTitle: OpenAI API
|
| 4 |
description: Call FastMCP servers from the OpenAI API
|
| 5 |
-
icon: message-
|
| 6 |
tag: NEW
|
| 7 |
---
|
| 8 |
|
|
|
|
| 1 |
---
|
| 2 |
+
title: OpenAI API 🤝 FastMCP
|
| 3 |
sidebarTitle: OpenAI API
|
| 4 |
description: Call FastMCP servers from the OpenAI API
|
| 5 |
+
icon: message-code
|
| 6 |
tag: NEW
|
| 7 |
---
|
| 8 |
|
docs/patterns/cli.mdx
CHANGED
|
@@ -151,11 +151,13 @@ Install a MCP server in MCP client applications. FastMCP currently supports the
|
|
| 151 |
- **Claude Code** - Installs via Claude Code's built-in MCP management system
|
| 152 |
- **Claude Desktop** - Installs via direct configuration file modification
|
| 153 |
- **Cursor** - Installs via deeplink that opens Cursor for user confirmation
|
|
|
|
| 154 |
|
| 155 |
```bash
|
| 156 |
fastmcp install claude-code server.py
|
| 157 |
fastmcp install claude-desktop server.py
|
| 158 |
fastmcp install cursor server.py
|
|
|
|
| 159 |
```
|
| 160 |
|
| 161 |
Note that for security reasons, MCP clients run every server in a completely isolated environment. Therefore, all dependencies must be explicitly specified using the `--with` and/or `--with-editable` options (following `uv` conventions) or by attaching them to your server in code via the `dependencies` parameter.
|
|
@@ -164,9 +166,9 @@ Note that for security reasons, MCP clients run every server in a completely iso
|
|
| 164 |
**`uv` must be installed and available in your system PATH**. Both Claude Desktop and Cursor run in isolated environments and need `uv` to manage dependencies. On macOS, install `uv` globally with Homebrew for Claude Desktop compatibility: `brew install uv`.
|
| 165 |
</Warning>
|
| 166 |
|
| 167 |
-
<
|
| 168 |
-
|
| 169 |
-
</
|
| 170 |
|
| 171 |
#### Server Specification
|
| 172 |
|
|
@@ -205,8 +207,52 @@ fastmcp install cursor server.py --env-var API_KEY=secret --env-var DEBUG=true
|
|
| 205 |
|
| 206 |
# Install with environment file
|
| 207 |
fastmcp install cursor server.py --env-file .env
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
```
|
| 209 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
### `inspect`
|
| 211 |
|
| 212 |
<VersionBadge version="2.9.0" />
|
|
|
|
| 151 |
- **Claude Code** - Installs via Claude Code's built-in MCP management system
|
| 152 |
- **Claude Desktop** - Installs via direct configuration file modification
|
| 153 |
- **Cursor** - Installs via deeplink that opens Cursor for user confirmation
|
| 154 |
+
- **MCP JSON** - Generates standard MCP JSON configuration for manual use
|
| 155 |
|
| 156 |
```bash
|
| 157 |
fastmcp install claude-code server.py
|
| 158 |
fastmcp install claude-desktop server.py
|
| 159 |
fastmcp install cursor server.py
|
| 160 |
+
fastmcp install mcp-json server.py
|
| 161 |
```
|
| 162 |
|
| 163 |
Note that for security reasons, MCP clients run every server in a completely isolated environment. Therefore, all dependencies must be explicitly specified using the `--with` and/or `--with-editable` options (following `uv` conventions) or by attaching them to your server in code via the `dependencies` parameter.
|
|
|
|
| 166 |
**`uv` must be installed and available in your system PATH**. Both Claude Desktop and Cursor run in isolated environments and need `uv` to manage dependencies. On macOS, install `uv` globally with Homebrew for Claude Desktop compatibility: `brew install uv`.
|
| 167 |
</Warning>
|
| 168 |
|
| 169 |
+
<Tip>
|
| 170 |
+
**FastMCP `install` commands focus on local server files with STDIO transport.** For remote servers running with HTTP or SSE transport, use your client's native configuration - FastMCP's value is simplifying the complex local setup with dependencies and `uv` commands.
|
| 171 |
+
</Tip>
|
| 172 |
|
| 173 |
#### Server Specification
|
| 174 |
|
|
|
|
| 207 |
|
| 208 |
# Install with environment file
|
| 209 |
fastmcp install cursor server.py --env-file .env
|
| 210 |
+
|
| 211 |
+
# Generate MCP JSON configuration
|
| 212 |
+
fastmcp install mcp-json server.py --name "My Server" --with pandas
|
| 213 |
+
|
| 214 |
+
# Copy JSON configuration to clipboard
|
| 215 |
+
fastmcp install mcp-json server.py --copy
|
| 216 |
```
|
| 217 |
|
| 218 |
+
#### MCP JSON Generation
|
| 219 |
+
|
| 220 |
+
The `mcp-json` subcommand generates standard MCP JSON configuration that can be used with any MCP-compatible client. This is useful when:
|
| 221 |
+
|
| 222 |
+
- Working with MCP clients not directly supported by FastMCP
|
| 223 |
+
- Creating configuration for CI/CD environments
|
| 224 |
+
- Sharing server configurations with others
|
| 225 |
+
- Integration with custom tooling
|
| 226 |
+
|
| 227 |
+
The generated JSON follows the standard `mcpServers` format used by Claude Desktop, VS Code, Cursor, and other MCP clients:
|
| 228 |
+
|
| 229 |
+
```json
|
| 230 |
+
{
|
| 231 |
+
"mcpServers": {
|
| 232 |
+
"server-name": {
|
| 233 |
+
"command": "uv",
|
| 234 |
+
"args": [
|
| 235 |
+
"run",
|
| 236 |
+
"--with",
|
| 237 |
+
"fastmcp",
|
| 238 |
+
"fastmcp",
|
| 239 |
+
"run",
|
| 240 |
+
"/path/to/server.py"
|
| 241 |
+
],
|
| 242 |
+
"env": {
|
| 243 |
+
"API_KEY": "value"
|
| 244 |
+
}
|
| 245 |
+
}
|
| 246 |
+
}
|
| 247 |
+
}
|
| 248 |
+
```
|
| 249 |
+
|
| 250 |
+
**Options specific to mcp-json:**
|
| 251 |
+
|
| 252 |
+
| Option | Flag | Description |
|
| 253 |
+
| ------ | ---- | ----------- |
|
| 254 |
+
| Copy to Clipboard | `--copy` | Copy configuration to clipboard instead of printing to stdout |
|
| 255 |
+
|
| 256 |
### `inspect`
|
| 257 |
|
| 258 |
<VersionBadge version="2.9.0" />
|
docs/{integrations → patterns}/contrib.mdx
RENAMED
|
File without changes
|
docs/patterns/tool-transformation.mdx
CHANGED
|
@@ -3,7 +3,6 @@ title: Tool Transformation
|
|
| 3 |
sidebarTitle: Tool Transformation
|
| 4 |
description: Create enhanced tool variants with modified schemas, argument mappings, and custom behavior.
|
| 5 |
icon: wand-magic-sparkles
|
| 6 |
-
tag: NEW
|
| 7 |
---
|
| 8 |
|
| 9 |
import { VersionBadge } from '/snippets/version-badge.mdx'
|
|
|
|
| 3 |
sidebarTitle: Tool Transformation
|
| 4 |
description: Create enhanced tool variants with modified schemas, argument mappings, and custom behavior.
|
| 5 |
icon: wand-magic-sparkles
|
|
|
|
| 6 |
---
|
| 7 |
|
| 8 |
import { VersionBadge } from '/snippets/version-badge.mdx'
|
docs/snippets/local-focus.mdx
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export const LocalFocusTip = () => {
|
| 2 |
+
return (
|
| 3 |
+
<Tip>
|
| 4 |
+
<strong>This integration focuses on running local FastMCP server files with STDIO transport.</strong> For remote servers running with HTTP or SSE transport, use your client's native configuration - FastMCP's integrations focus on simplifying the complex local setup with dependencies and <code>uv</code> commands.
|
| 5 |
+
</Tip>
|
| 6 |
+
);
|
| 7 |
+
};
|
pyproject.toml
CHANGED
|
@@ -46,6 +46,7 @@ dev = [
|
|
| 46 |
"pdbpp>=0.10.3",
|
| 47 |
"pre-commit",
|
| 48 |
"pyinstrument>=5.0.2",
|
|
|
|
| 49 |
"pyright>=1.1.389",
|
| 50 |
"pytest>=8.3.3",
|
| 51 |
"pytest-asyncio>=0.23.5",
|
|
|
|
| 46 |
"pdbpp>=0.10.3",
|
| 47 |
"pre-commit",
|
| 48 |
"pyinstrument>=5.0.2",
|
| 49 |
+
"pyperclip>=1.9.0",
|
| 50 |
"pyright>=1.1.389",
|
| 51 |
"pytest>=8.3.3",
|
| 52 |
"pytest-asyncio>=0.23.5",
|
src/fastmcp/cli/cli.py
CHANGED
|
@@ -18,7 +18,7 @@ from typer import Context, Exit
|
|
| 18 |
|
| 19 |
import fastmcp
|
| 20 |
from fastmcp.cli import run as run_module
|
| 21 |
-
from fastmcp.cli.install
|
| 22 |
from fastmcp.server.server import FastMCP
|
| 23 |
from fastmcp.utilities.inspect import FastMCPInfo, inspect_fastmcp
|
| 24 |
from fastmcp.utilities.logging import get_logger
|
|
@@ -323,8 +323,8 @@ def run(
|
|
| 323 |
sys.exit(1)
|
| 324 |
|
| 325 |
|
| 326 |
-
# Add install
|
| 327 |
-
app.
|
| 328 |
|
| 329 |
|
| 330 |
@app.command()
|
|
|
|
| 18 |
|
| 19 |
import fastmcp
|
| 20 |
from fastmcp.cli import run as run_module
|
| 21 |
+
from fastmcp.cli.install import install_app
|
| 22 |
from fastmcp.server.server import FastMCP
|
| 23 |
from fastmcp.utilities.inspect import FastMCPInfo, inspect_fastmcp
|
| 24 |
from fastmcp.utilities.logging import get_logger
|
|
|
|
| 323 |
sys.exit(1)
|
| 324 |
|
| 325 |
|
| 326 |
+
# Add install subcommands
|
| 327 |
+
app.add_typer(install_app)
|
| 328 |
|
| 329 |
|
| 330 |
@app.command()
|
src/fastmcp/cli/install/__init__.py
CHANGED
|
@@ -1,5 +1,27 @@
|
|
| 1 |
-
"""Install
|
| 2 |
|
| 3 |
-
|
| 4 |
|
| 5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Install subcommands for FastMCP CLI."""
|
| 2 |
|
| 3 |
+
import typer
|
| 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 typer app for install subcommands
|
| 11 |
+
install_app = typer.Typer(
|
| 12 |
+
name="install",
|
| 13 |
+
help="Install MCP servers in various clients and formats",
|
| 14 |
+
no_args_is_help=True,
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
# Register each command from its respective module
|
| 18 |
+
install_app.command("claude-code", help="Install a MCP server in Claude Code")(
|
| 19 |
+
claude_code_command
|
| 20 |
+
)
|
| 21 |
+
install_app.command("claude-desktop", help="Install a MCP server in Claude Desktop")(
|
| 22 |
+
claude_desktop_command
|
| 23 |
+
)
|
| 24 |
+
install_app.command("cursor", help="Install a MCP server in Cursor")(cursor_command)
|
| 25 |
+
install_app.command(
|
| 26 |
+
"mcp-json", help="Generate MCP JSON configuration for manual installation"
|
| 27 |
+
)(mcp_config_command)
|
src/fastmcp/cli/install/claude_code.py
CHANGED
|
@@ -3,12 +3,17 @@
|
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
import subprocess
|
|
|
|
| 6 |
from pathlib import Path
|
|
|
|
| 7 |
|
|
|
|
| 8 |
from rich import print
|
| 9 |
|
| 10 |
from fastmcp.utilities.logging import get_logger
|
| 11 |
|
|
|
|
|
|
|
| 12 |
logger = get_logger(__name__)
|
| 13 |
|
| 14 |
|
|
@@ -116,3 +121,73 @@ def install_claude_code(
|
|
| 116 |
except Exception as e:
|
| 117 |
print(f"[red]Failed to install '[bold]{name}[/bold]' in Claude Code: {e}[/red]")
|
| 118 |
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
import subprocess
|
| 6 |
+
import sys
|
| 7 |
from pathlib import Path
|
| 8 |
+
from typing import Annotated
|
| 9 |
|
| 10 |
+
import typer
|
| 11 |
from rich import print
|
| 12 |
|
| 13 |
from fastmcp.utilities.logging import get_logger
|
| 14 |
|
| 15 |
+
from .shared import process_common_args
|
| 16 |
+
|
| 17 |
logger = get_logger(__name__)
|
| 18 |
|
| 19 |
|
|
|
|
| 121 |
except Exception as e:
|
| 122 |
print(f"[red]Failed to install '[bold]{name}[/bold]' in Claude Code: {e}[/red]")
|
| 123 |
return False
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def claude_code_command(
|
| 127 |
+
server_spec: Annotated[
|
| 128 |
+
str, typer.Argument(help="Python file to run, optionally with :object suffix")
|
| 129 |
+
],
|
| 130 |
+
server_name: Annotated[
|
| 131 |
+
str | None,
|
| 132 |
+
typer.Option(
|
| 133 |
+
"--name",
|
| 134 |
+
"-n",
|
| 135 |
+
help="Custom name for the server (defaults to server's name attribute or file name)",
|
| 136 |
+
),
|
| 137 |
+
] = None,
|
| 138 |
+
with_editable: Annotated[
|
| 139 |
+
Path | None,
|
| 140 |
+
typer.Option(
|
| 141 |
+
"--with-editable",
|
| 142 |
+
"-e",
|
| 143 |
+
help="Directory containing pyproject.toml to install in editable mode",
|
| 144 |
+
exists=True,
|
| 145 |
+
file_okay=False,
|
| 146 |
+
resolve_path=True,
|
| 147 |
+
),
|
| 148 |
+
] = None,
|
| 149 |
+
with_packages: Annotated[
|
| 150 |
+
list[str],
|
| 151 |
+
typer.Option(
|
| 152 |
+
"--with", help="Additional packages to install, in PEP 508 format"
|
| 153 |
+
),
|
| 154 |
+
] = [],
|
| 155 |
+
env_vars: Annotated[
|
| 156 |
+
list[str],
|
| 157 |
+
typer.Option(
|
| 158 |
+
"--env-var", "-v", help="Environment variables in KEY=VALUE format"
|
| 159 |
+
),
|
| 160 |
+
] = [],
|
| 161 |
+
env_file: Annotated[
|
| 162 |
+
Path | None,
|
| 163 |
+
typer.Option(
|
| 164 |
+
"--env-file",
|
| 165 |
+
"-f",
|
| 166 |
+
help="Load environment variables from a .env file",
|
| 167 |
+
exists=True,
|
| 168 |
+
file_okay=True,
|
| 169 |
+
dir_okay=False,
|
| 170 |
+
resolve_path=True,
|
| 171 |
+
),
|
| 172 |
+
] = None,
|
| 173 |
+
) -> None:
|
| 174 |
+
"""Install a MCP server in Claude Code."""
|
| 175 |
+
file, server_object, name, packages, env_dict = process_common_args(
|
| 176 |
+
server_spec, server_name, with_packages, env_vars, env_file
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
success = install_claude_code(
|
| 180 |
+
file=file,
|
| 181 |
+
server_object=server_object,
|
| 182 |
+
name=name,
|
| 183 |
+
with_editable=with_editable,
|
| 184 |
+
with_packages=packages,
|
| 185 |
+
env_vars=env_dict,
|
| 186 |
+
)
|
| 187 |
+
|
| 188 |
+
if success:
|
| 189 |
+
print(
|
| 190 |
+
f"[green bold]Successfully installed '[bold]{name}[/bold]' in Claude Code[/green bold]"
|
| 191 |
+
)
|
| 192 |
+
else:
|
| 193 |
+
sys.exit(1)
|
src/fastmcp/cli/install/claude_desktop.py
CHANGED
|
@@ -5,12 +5,16 @@ from __future__ import annotations
|
|
| 5 |
import os
|
| 6 |
import sys
|
| 7 |
from pathlib import Path
|
|
|
|
| 8 |
|
|
|
|
| 9 |
from rich import print
|
| 10 |
|
| 11 |
from fastmcp.mcp_config import StdioMCPServer, update_config_file
|
| 12 |
from fastmcp.utilities.logging import get_logger
|
| 13 |
|
|
|
|
|
|
|
| 14 |
logger = get_logger(__name__)
|
| 15 |
|
| 16 |
|
|
@@ -119,3 +123,73 @@ def install_claude_desktop(
|
|
| 119 |
f"[red]Failed to install '[bold]{name}[/bold]' in Claude Desktop: {e}[/red]"
|
| 120 |
)
|
| 121 |
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
import os
|
| 6 |
import sys
|
| 7 |
from pathlib import Path
|
| 8 |
+
from typing import Annotated
|
| 9 |
|
| 10 |
+
import typer
|
| 11 |
from rich import print
|
| 12 |
|
| 13 |
from fastmcp.mcp_config import StdioMCPServer, update_config_file
|
| 14 |
from fastmcp.utilities.logging import get_logger
|
| 15 |
|
| 16 |
+
from .shared import process_common_args
|
| 17 |
+
|
| 18 |
logger = get_logger(__name__)
|
| 19 |
|
| 20 |
|
|
|
|
| 123 |
f"[red]Failed to install '[bold]{name}[/bold]' in Claude Desktop: {e}[/red]"
|
| 124 |
)
|
| 125 |
return False
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def claude_desktop_command(
|
| 129 |
+
server_spec: Annotated[
|
| 130 |
+
str, typer.Argument(help="Python file to run, optionally with :object suffix")
|
| 131 |
+
],
|
| 132 |
+
server_name: Annotated[
|
| 133 |
+
str | None,
|
| 134 |
+
typer.Option(
|
| 135 |
+
"--name",
|
| 136 |
+
"-n",
|
| 137 |
+
help="Custom name for the server (defaults to server's name attribute or file name)",
|
| 138 |
+
),
|
| 139 |
+
] = None,
|
| 140 |
+
with_editable: Annotated[
|
| 141 |
+
Path | None,
|
| 142 |
+
typer.Option(
|
| 143 |
+
"--with-editable",
|
| 144 |
+
"-e",
|
| 145 |
+
help="Directory containing pyproject.toml to install in editable mode",
|
| 146 |
+
exists=True,
|
| 147 |
+
file_okay=False,
|
| 148 |
+
resolve_path=True,
|
| 149 |
+
),
|
| 150 |
+
] = None,
|
| 151 |
+
with_packages: Annotated[
|
| 152 |
+
list[str],
|
| 153 |
+
typer.Option(
|
| 154 |
+
"--with", help="Additional packages to install, in PEP 508 format"
|
| 155 |
+
),
|
| 156 |
+
] = [],
|
| 157 |
+
env_vars: Annotated[
|
| 158 |
+
list[str],
|
| 159 |
+
typer.Option(
|
| 160 |
+
"--env-var", "-v", help="Environment variables in KEY=VALUE format"
|
| 161 |
+
),
|
| 162 |
+
] = [],
|
| 163 |
+
env_file: Annotated[
|
| 164 |
+
Path | None,
|
| 165 |
+
typer.Option(
|
| 166 |
+
"--env-file",
|
| 167 |
+
"-f",
|
| 168 |
+
help="Load environment variables from a .env file",
|
| 169 |
+
exists=True,
|
| 170 |
+
file_okay=True,
|
| 171 |
+
dir_okay=False,
|
| 172 |
+
resolve_path=True,
|
| 173 |
+
),
|
| 174 |
+
] = None,
|
| 175 |
+
) -> None:
|
| 176 |
+
"""Install a MCP server in Claude Desktop."""
|
| 177 |
+
file, server_object, name, packages, env_dict = process_common_args(
|
| 178 |
+
server_spec, server_name, with_packages, env_vars, env_file
|
| 179 |
+
)
|
| 180 |
+
|
| 181 |
+
success = install_claude_desktop(
|
| 182 |
+
file=file,
|
| 183 |
+
server_object=server_object,
|
| 184 |
+
name=name,
|
| 185 |
+
with_editable=with_editable,
|
| 186 |
+
with_packages=packages,
|
| 187 |
+
env_vars=env_dict,
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
if success:
|
| 191 |
+
print(
|
| 192 |
+
f"[green bold]Successfully installed '[bold]{name}[/bold]' in Claude Desktop[/green bold]"
|
| 193 |
+
)
|
| 194 |
+
else:
|
| 195 |
+
sys.exit(1)
|
src/fastmcp/cli/install/cursor.py
CHANGED
|
@@ -6,12 +6,16 @@ import base64
|
|
| 6 |
import subprocess
|
| 7 |
import sys
|
| 8 |
from pathlib import Path
|
|
|
|
| 9 |
|
|
|
|
| 10 |
from rich import print
|
| 11 |
|
| 12 |
from fastmcp.mcp_config import StdioMCPServer
|
| 13 |
from fastmcp.utilities.logging import get_logger
|
| 14 |
|
|
|
|
|
|
|
| 15 |
logger = get_logger(__name__)
|
| 16 |
|
| 17 |
|
|
@@ -133,3 +137,70 @@ def install_cursor(
|
|
| 133 |
except Exception as e:
|
| 134 |
print(f"[red]Failed to generate Cursor deeplink: {e}[/red]")
|
| 135 |
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
import subprocess
|
| 7 |
import sys
|
| 8 |
from pathlib import Path
|
| 9 |
+
from typing import Annotated
|
| 10 |
|
| 11 |
+
import typer
|
| 12 |
from rich import print
|
| 13 |
|
| 14 |
from fastmcp.mcp_config import StdioMCPServer
|
| 15 |
from fastmcp.utilities.logging import get_logger
|
| 16 |
|
| 17 |
+
from .shared import process_common_args
|
| 18 |
+
|
| 19 |
logger = get_logger(__name__)
|
| 20 |
|
| 21 |
|
|
|
|
| 137 |
except Exception as e:
|
| 138 |
print(f"[red]Failed to generate Cursor deeplink: {e}[/red]")
|
| 139 |
return False
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def cursor_command(
|
| 143 |
+
server_spec: Annotated[
|
| 144 |
+
str, typer.Argument(help="Python file to run, optionally with :object suffix")
|
| 145 |
+
],
|
| 146 |
+
server_name: Annotated[
|
| 147 |
+
str | None,
|
| 148 |
+
typer.Option(
|
| 149 |
+
"--name",
|
| 150 |
+
"-n",
|
| 151 |
+
help="Custom name for the server (defaults to server's name attribute or file name)",
|
| 152 |
+
),
|
| 153 |
+
] = None,
|
| 154 |
+
with_editable: Annotated[
|
| 155 |
+
Path | None,
|
| 156 |
+
typer.Option(
|
| 157 |
+
"--with-editable",
|
| 158 |
+
"-e",
|
| 159 |
+
help="Directory containing pyproject.toml to install in editable mode",
|
| 160 |
+
exists=True,
|
| 161 |
+
file_okay=False,
|
| 162 |
+
resolve_path=True,
|
| 163 |
+
),
|
| 164 |
+
] = None,
|
| 165 |
+
with_packages: Annotated[
|
| 166 |
+
list[str],
|
| 167 |
+
typer.Option(
|
| 168 |
+
"--with", help="Additional packages to install, in PEP 508 format"
|
| 169 |
+
),
|
| 170 |
+
] = [],
|
| 171 |
+
env_vars: Annotated[
|
| 172 |
+
list[str],
|
| 173 |
+
typer.Option(
|
| 174 |
+
"--env-var", "-v", help="Environment variables in KEY=VALUE format"
|
| 175 |
+
),
|
| 176 |
+
] = [],
|
| 177 |
+
env_file: Annotated[
|
| 178 |
+
Path | None,
|
| 179 |
+
typer.Option(
|
| 180 |
+
"--env-file",
|
| 181 |
+
"-f",
|
| 182 |
+
help="Load environment variables from a .env file",
|
| 183 |
+
exists=True,
|
| 184 |
+
file_okay=True,
|
| 185 |
+
dir_okay=False,
|
| 186 |
+
resolve_path=True,
|
| 187 |
+
),
|
| 188 |
+
] = None,
|
| 189 |
+
) -> None:
|
| 190 |
+
"""Install a MCP server in Cursor."""
|
| 191 |
+
file, server_object, name, packages, env_dict = process_common_args(
|
| 192 |
+
server_spec, server_name, with_packages, env_vars, env_file
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
success = install_cursor(
|
| 196 |
+
file=file,
|
| 197 |
+
server_object=server_object,
|
| 198 |
+
name=name,
|
| 199 |
+
with_editable=with_editable,
|
| 200 |
+
with_packages=packages,
|
| 201 |
+
env_vars=env_dict,
|
| 202 |
+
)
|
| 203 |
+
|
| 204 |
+
# Cursor handles its own messaging, no generic success message needed
|
| 205 |
+
if not success:
|
| 206 |
+
sys.exit(1)
|
src/fastmcp/cli/install/mcp_config.py
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""MCP configuration JSON generation for FastMCP install."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
import sys
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from typing import Annotated
|
| 9 |
+
|
| 10 |
+
import typer
|
| 11 |
+
from rich import print
|
| 12 |
+
|
| 13 |
+
from fastmcp.utilities.logging import get_logger
|
| 14 |
+
|
| 15 |
+
from .shared import process_common_args
|
| 16 |
+
|
| 17 |
+
logger = get_logger(__name__)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def install_mcp_config(
|
| 21 |
+
file: Path,
|
| 22 |
+
server_object: str | None,
|
| 23 |
+
name: str,
|
| 24 |
+
*,
|
| 25 |
+
with_editable: Path | None = None,
|
| 26 |
+
with_packages: list[str] | None = None,
|
| 27 |
+
env_vars: dict[str, str] | None = None,
|
| 28 |
+
copy: bool = False,
|
| 29 |
+
) -> bool:
|
| 30 |
+
"""Generate MCP configuration JSON for manual installation.
|
| 31 |
+
|
| 32 |
+
Args:
|
| 33 |
+
file: Path to the server file
|
| 34 |
+
server_object: Optional server object name (for :object suffix)
|
| 35 |
+
name: Name for the server in MCP config
|
| 36 |
+
with_editable: Optional directory to install in editable mode
|
| 37 |
+
with_packages: Optional list of additional packages to install
|
| 38 |
+
env_vars: Optional dictionary of environment variables
|
| 39 |
+
copy: If True, copy to clipboard instead of printing to stdout
|
| 40 |
+
|
| 41 |
+
Returns:
|
| 42 |
+
True if generation was successful, False otherwise
|
| 43 |
+
"""
|
| 44 |
+
try:
|
| 45 |
+
# Build uv run command
|
| 46 |
+
args = ["run"]
|
| 47 |
+
|
| 48 |
+
# Collect all packages in a set to deduplicate
|
| 49 |
+
packages = {"fastmcp"}
|
| 50 |
+
if with_packages:
|
| 51 |
+
packages.update(pkg for pkg in with_packages if pkg)
|
| 52 |
+
|
| 53 |
+
# Add all packages with --with
|
| 54 |
+
for pkg in sorted(packages):
|
| 55 |
+
args.extend(["--with", pkg])
|
| 56 |
+
|
| 57 |
+
if with_editable:
|
| 58 |
+
args.extend(["--with-editable", str(with_editable)])
|
| 59 |
+
|
| 60 |
+
# Build server spec from parsed components
|
| 61 |
+
if server_object:
|
| 62 |
+
server_spec = f"{file.resolve()}:{server_object}"
|
| 63 |
+
else:
|
| 64 |
+
server_spec = str(file.resolve())
|
| 65 |
+
|
| 66 |
+
# Add fastmcp run command
|
| 67 |
+
args.extend(["fastmcp", "run", server_spec])
|
| 68 |
+
|
| 69 |
+
# Build MCP server configuration (just the server object, not the wrapper)
|
| 70 |
+
config = {
|
| 71 |
+
"command": "uv",
|
| 72 |
+
"args": args,
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
# Add environment variables if provided
|
| 76 |
+
if env_vars:
|
| 77 |
+
config["env"] = env_vars
|
| 78 |
+
|
| 79 |
+
# Convert to JSON
|
| 80 |
+
json_output = json.dumps(config, indent=2)
|
| 81 |
+
|
| 82 |
+
# Handle output
|
| 83 |
+
if copy:
|
| 84 |
+
try:
|
| 85 |
+
import pyperclip
|
| 86 |
+
|
| 87 |
+
pyperclip.copy(json_output)
|
| 88 |
+
print(
|
| 89 |
+
f"[green]MCP configuration for '[bold]{name}[/bold]' copied to clipboard[/green]"
|
| 90 |
+
)
|
| 91 |
+
except ImportError:
|
| 92 |
+
print(
|
| 93 |
+
"[red]The `--copy` flag requires pyperclip. Please install pyperclip and try again: `pip install pyperclip`[/red]"
|
| 94 |
+
)
|
| 95 |
+
return False
|
| 96 |
+
else:
|
| 97 |
+
# Print to stdout (for piping)
|
| 98 |
+
print(json_output)
|
| 99 |
+
|
| 100 |
+
return True
|
| 101 |
+
|
| 102 |
+
except Exception as e:
|
| 103 |
+
print(f"[red]❌ Failed to generate MCP configuration: {e}[/red]")
|
| 104 |
+
return False
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def mcp_config_command(
|
| 108 |
+
server_spec: Annotated[
|
| 109 |
+
str, typer.Argument(help="Python file to run, optionally with :object suffix")
|
| 110 |
+
],
|
| 111 |
+
server_name: Annotated[
|
| 112 |
+
str | None,
|
| 113 |
+
typer.Option(
|
| 114 |
+
"--name",
|
| 115 |
+
"-n",
|
| 116 |
+
help="Custom name for the server (defaults to server's name attribute or file name)",
|
| 117 |
+
),
|
| 118 |
+
] = None,
|
| 119 |
+
with_editable: Annotated[
|
| 120 |
+
Path | None,
|
| 121 |
+
typer.Option(
|
| 122 |
+
"--with-editable",
|
| 123 |
+
"-e",
|
| 124 |
+
help="Directory containing pyproject.toml to install in editable mode",
|
| 125 |
+
exists=True,
|
| 126 |
+
file_okay=False,
|
| 127 |
+
resolve_path=True,
|
| 128 |
+
),
|
| 129 |
+
] = None,
|
| 130 |
+
with_packages: Annotated[
|
| 131 |
+
list[str],
|
| 132 |
+
typer.Option(
|
| 133 |
+
"--with", help="Additional packages to install, in PEP 508 format"
|
| 134 |
+
),
|
| 135 |
+
] = [],
|
| 136 |
+
env_vars: Annotated[
|
| 137 |
+
list[str],
|
| 138 |
+
typer.Option(
|
| 139 |
+
"--env-var", "-v", help="Environment variables in KEY=VALUE format"
|
| 140 |
+
),
|
| 141 |
+
] = [],
|
| 142 |
+
env_file: Annotated[
|
| 143 |
+
Path | None,
|
| 144 |
+
typer.Option(
|
| 145 |
+
"--env-file",
|
| 146 |
+
"-f",
|
| 147 |
+
help="Load environment variables from a .env file",
|
| 148 |
+
exists=True,
|
| 149 |
+
file_okay=True,
|
| 150 |
+
dir_okay=False,
|
| 151 |
+
resolve_path=True,
|
| 152 |
+
),
|
| 153 |
+
] = None,
|
| 154 |
+
copy: Annotated[
|
| 155 |
+
bool,
|
| 156 |
+
typer.Option(
|
| 157 |
+
"--copy",
|
| 158 |
+
help="Copy configuration to clipboard instead of printing to stdout",
|
| 159 |
+
),
|
| 160 |
+
] = False,
|
| 161 |
+
) -> None:
|
| 162 |
+
"""Generate MCP configuration JSON for manual installation."""
|
| 163 |
+
file, server_object, name, packages, env_dict = process_common_args(
|
| 164 |
+
server_spec, server_name, with_packages, env_vars, env_file
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
success = install_mcp_config(
|
| 168 |
+
file=file,
|
| 169 |
+
server_object=server_object,
|
| 170 |
+
name=name,
|
| 171 |
+
with_editable=with_editable,
|
| 172 |
+
with_packages=packages,
|
| 173 |
+
env_vars=env_dict,
|
| 174 |
+
copy=copy,
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
+
# mcp-config handles its own messaging, no generic success message needed
|
| 178 |
+
if not success:
|
| 179 |
+
sys.exit(1)
|
src/fastmcp/cli/install/{install.py → shared.py}
RENAMED
|
@@ -1,106 +1,47 @@
|
|
| 1 |
-
"""
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
import sys
|
| 6 |
-
from enum import Enum
|
| 7 |
from pathlib import Path
|
| 8 |
-
from typing import Annotated
|
| 9 |
|
| 10 |
-
import typer
|
| 11 |
from dotenv import dotenv_values
|
| 12 |
from rich import print
|
| 13 |
|
| 14 |
from fastmcp.cli.run import import_server, parse_file_path
|
| 15 |
from fastmcp.utilities.logging import get_logger
|
| 16 |
|
| 17 |
-
from .claude_code import install_claude_code
|
| 18 |
-
from .claude_desktop import install_claude_desktop
|
| 19 |
-
from .cursor import install_cursor
|
| 20 |
-
|
| 21 |
logger = get_logger(__name__)
|
| 22 |
|
| 23 |
|
| 24 |
-
|
| 25 |
-
"""
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
client: Annotated[
|
| 34 |
-
Client,
|
| 35 |
-
typer.Argument(help="MCP client to install the server into"),
|
| 36 |
-
],
|
| 37 |
-
server_spec: Annotated[
|
| 38 |
-
str, typer.Argument(help="Python file to run, optionally with :object suffix")
|
| 39 |
-
],
|
| 40 |
-
server_name: Annotated[
|
| 41 |
-
str | None,
|
| 42 |
-
typer.Option(
|
| 43 |
-
"--name",
|
| 44 |
-
"-n",
|
| 45 |
-
help="Custom name for the server (defaults to server's name attribute or file name)",
|
| 46 |
-
),
|
| 47 |
-
] = None,
|
| 48 |
-
with_editable: Annotated[
|
| 49 |
-
Path | None,
|
| 50 |
-
typer.Option(
|
| 51 |
-
"--with-editable",
|
| 52 |
-
"-e",
|
| 53 |
-
help="Directory containing pyproject.toml to install in editable mode. Use this to include local packages that are not available on PyPI.",
|
| 54 |
-
exists=True,
|
| 55 |
-
file_okay=False,
|
| 56 |
-
resolve_path=True,
|
| 57 |
-
),
|
| 58 |
-
] = None,
|
| 59 |
-
with_packages: Annotated[
|
| 60 |
-
list[str],
|
| 61 |
-
typer.Option(
|
| 62 |
-
"--with",
|
| 63 |
-
help="Additional packages to install, in PEP 508 format (e.g. 'httpx>=0.25.2')",
|
| 64 |
-
),
|
| 65 |
-
] = [],
|
| 66 |
-
env_vars: Annotated[
|
| 67 |
-
list[str],
|
| 68 |
-
typer.Option(
|
| 69 |
-
"--env-var",
|
| 70 |
-
"-v",
|
| 71 |
-
help="Environment variables in KEY=VALUE format",
|
| 72 |
-
),
|
| 73 |
-
] = [],
|
| 74 |
-
env_file: Annotated[
|
| 75 |
-
Path | None,
|
| 76 |
-
typer.Option(
|
| 77 |
-
"--env-file",
|
| 78 |
-
"-f",
|
| 79 |
-
help="Load environment variables from a .env file",
|
| 80 |
-
exists=True,
|
| 81 |
-
file_okay=True,
|
| 82 |
-
dir_okay=False,
|
| 83 |
-
resolve_path=True,
|
| 84 |
-
),
|
| 85 |
-
] = None,
|
| 86 |
-
) -> None:
|
| 87 |
-
"""Install a MCP server in the specified target application.
|
| 88 |
|
| 89 |
-
Environment variables are preserved once added and only updated if new values
|
| 90 |
-
are explicitly provided.
|
| 91 |
-
"""
|
| 92 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
# Parse server spec
|
| 94 |
file, server_object = parse_file_path(server_spec)
|
| 95 |
|
| 96 |
logger.debug(
|
| 97 |
"Installing server",
|
| 98 |
extra={
|
| 99 |
-
"client": client,
|
| 100 |
"file": str(file),
|
| 101 |
"server_name": server_name,
|
| 102 |
"server_object": server_object,
|
| 103 |
-
"with_editable": str(with_editable) if with_editable else None,
|
| 104 |
"with_packages": with_packages,
|
| 105 |
},
|
| 106 |
)
|
|
@@ -140,59 +81,7 @@ def install(
|
|
| 140 |
|
| 141 |
# Add command line environment variables
|
| 142 |
for env_var in env_vars:
|
| 143 |
-
key, value =
|
| 144 |
env_dict[key] = value
|
| 145 |
|
| 146 |
-
|
| 147 |
-
if client == Client.CLAUDE_CODE:
|
| 148 |
-
success = install_claude_code(
|
| 149 |
-
file=file,
|
| 150 |
-
server_object=server_object,
|
| 151 |
-
name=name,
|
| 152 |
-
with_editable=with_editable,
|
| 153 |
-
with_packages=with_packages,
|
| 154 |
-
env_vars=env_dict,
|
| 155 |
-
)
|
| 156 |
-
elif client == Client.CLAUDE_DESKTOP:
|
| 157 |
-
success = install_claude_desktop(
|
| 158 |
-
file=file,
|
| 159 |
-
server_object=server_object,
|
| 160 |
-
name=name,
|
| 161 |
-
with_editable=with_editable,
|
| 162 |
-
with_packages=with_packages,
|
| 163 |
-
env_vars=env_dict,
|
| 164 |
-
)
|
| 165 |
-
elif client == Client.CURSOR:
|
| 166 |
-
success = install_cursor(
|
| 167 |
-
file=file,
|
| 168 |
-
server_object=server_object,
|
| 169 |
-
name=name,
|
| 170 |
-
with_editable=with_editable,
|
| 171 |
-
with_packages=with_packages,
|
| 172 |
-
env_vars=env_dict,
|
| 173 |
-
)
|
| 174 |
-
else:
|
| 175 |
-
print(
|
| 176 |
-
f"[red bold]Unknown client: {client!r}[/red bold]. Supported clients: [bold]{Client.CLAUDE_CODE}[/bold], [bold]{Client.CLAUDE_DESKTOP}[/bold], [bold]{Client.CURSOR}[/bold]"
|
| 177 |
-
)
|
| 178 |
-
raise typer.Exit(1)
|
| 179 |
-
|
| 180 |
-
if success:
|
| 181 |
-
# Only show generic success message for clients that don't have their own messaging
|
| 182 |
-
if client != Client.CURSOR:
|
| 183 |
-
print(
|
| 184 |
-
f"[green bold]Successfully installed '[bold]{name}[/bold]' in {client.value}[/green bold]"
|
| 185 |
-
)
|
| 186 |
-
else:
|
| 187 |
-
sys.exit(1)
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
def _parse_env_var(env_var: str) -> tuple[str, str]:
|
| 191 |
-
"""Parse environment variable string in format KEY=VALUE."""
|
| 192 |
-
if "=" not in env_var:
|
| 193 |
-
print(
|
| 194 |
-
f"[red]❌ Invalid environment variable format: '[bold]{env_var}[/bold]'. Must be KEY=VALUE[/red]"
|
| 195 |
-
)
|
| 196 |
-
sys.exit(1)
|
| 197 |
-
key, value = env_var.split("=", 1)
|
| 198 |
-
return key.strip(), value.strip()
|
|
|
|
| 1 |
+
"""Shared utilities for install commands."""
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
import sys
|
|
|
|
| 6 |
from pathlib import Path
|
|
|
|
| 7 |
|
|
|
|
| 8 |
from dotenv import dotenv_values
|
| 9 |
from rich import print
|
| 10 |
|
| 11 |
from fastmcp.cli.run import import_server, parse_file_path
|
| 12 |
from fastmcp.utilities.logging import get_logger
|
| 13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
logger = get_logger(__name__)
|
| 15 |
|
| 16 |
|
| 17 |
+
def parse_env_var(env_var: str) -> tuple[str, str]:
|
| 18 |
+
"""Parse environment variable string in format KEY=VALUE."""
|
| 19 |
+
if "=" not in env_var:
|
| 20 |
+
print(
|
| 21 |
+
f"[red]❌ Invalid environment variable format: '[bold]{env_var}[/bold]'. Must be KEY=VALUE[/red]"
|
| 22 |
+
)
|
| 23 |
+
sys.exit(1)
|
| 24 |
+
key, value = env_var.split("=", 1)
|
| 25 |
+
return key.strip(), value.strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
+
def process_common_args(
|
| 29 |
+
server_spec: str,
|
| 30 |
+
server_name: str | None,
|
| 31 |
+
with_packages: list[str],
|
| 32 |
+
env_vars: list[str],
|
| 33 |
+
env_file: Path | None,
|
| 34 |
+
) -> tuple[Path, str | None, str, list[str], dict[str, str] | None]:
|
| 35 |
+
"""Process common arguments shared by all install commands."""
|
| 36 |
# Parse server spec
|
| 37 |
file, server_object = parse_file_path(server_spec)
|
| 38 |
|
| 39 |
logger.debug(
|
| 40 |
"Installing server",
|
| 41 |
extra={
|
|
|
|
| 42 |
"file": str(file),
|
| 43 |
"server_name": server_name,
|
| 44 |
"server_object": server_object,
|
|
|
|
| 45 |
"with_packages": with_packages,
|
| 46 |
},
|
| 47 |
)
|
|
|
|
| 81 |
|
| 82 |
# Add command line environment variables
|
| 83 |
for env_var in env_vars:
|
| 84 |
+
key, value = parse_env_var(env_var)
|
| 85 |
env_dict[key] = value
|
| 86 |
|
| 87 |
+
return file, server_object, name, with_packages, env_dict
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tests/cli/test_mcp_config.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tests for MCP configuration JSON generation."""
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from unittest.mock import MagicMock, patch
|
| 6 |
+
|
| 7 |
+
from fastmcp.cli.install.mcp_config import install_mcp_config
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class TestInstallMcpConfig:
|
| 11 |
+
"""Test install_mcp_config function."""
|
| 12 |
+
|
| 13 |
+
def test_generates_basic_config(self):
|
| 14 |
+
"""Should generate basic MCP configuration with minimal options."""
|
| 15 |
+
result = install_mcp_config(
|
| 16 |
+
file=Path("server.py"),
|
| 17 |
+
server_object=None,
|
| 18 |
+
name="test-server",
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
assert result is True
|
| 22 |
+
|
| 23 |
+
@patch("fastmcp.cli.install.mcp_config.print")
|
| 24 |
+
def test_generates_config_with_all_options(self, mock_print):
|
| 25 |
+
"""Should generate MCP configuration with all options."""
|
| 26 |
+
result = install_mcp_config(
|
| 27 |
+
file=Path("server.py"),
|
| 28 |
+
server_object="custom_server",
|
| 29 |
+
name="test-server",
|
| 30 |
+
with_editable=Path("/path/to/editable"),
|
| 31 |
+
with_packages=["pandas", "requests"],
|
| 32 |
+
env_vars={"API_KEY": "secret", "DEBUG": "true"},
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
assert result is True
|
| 36 |
+
mock_print.assert_called_once()
|
| 37 |
+
|
| 38 |
+
# Get the JSON output from print call
|
| 39 |
+
json_output = mock_print.call_args[0][0]
|
| 40 |
+
config = json.loads(json_output)
|
| 41 |
+
|
| 42 |
+
# Verify structure (should be just the server config, not wrapped in mcpServers)
|
| 43 |
+
server_config = config
|
| 44 |
+
|
| 45 |
+
# Verify command and args
|
| 46 |
+
assert server_config["command"] == "uv"
|
| 47 |
+
assert "run" in server_config["args"]
|
| 48 |
+
assert "--with" in server_config["args"]
|
| 49 |
+
assert "fastmcp" in server_config["args"]
|
| 50 |
+
assert "pandas" in server_config["args"]
|
| 51 |
+
assert "requests" in server_config["args"]
|
| 52 |
+
assert "--with-editable" in server_config["args"]
|
| 53 |
+
assert str(Path("/path/to/editable")) in server_config["args"]
|
| 54 |
+
|
| 55 |
+
# Verify server spec with object
|
| 56 |
+
server_spec_in_args = None
|
| 57 |
+
for i, arg in enumerate(server_config["args"]):
|
| 58 |
+
if (
|
| 59 |
+
arg == "fastmcp"
|
| 60 |
+
and i + 2 < len(server_config["args"])
|
| 61 |
+
and server_config["args"][i + 1] == "run"
|
| 62 |
+
):
|
| 63 |
+
server_spec_in_args = server_config["args"][i + 2]
|
| 64 |
+
break
|
| 65 |
+
|
| 66 |
+
assert server_spec_in_args is not None
|
| 67 |
+
assert ":custom_server" in server_spec_in_args
|
| 68 |
+
|
| 69 |
+
# Verify environment variables
|
| 70 |
+
assert server_config["env"] == {"API_KEY": "secret", "DEBUG": "true"}
|
| 71 |
+
|
| 72 |
+
@patch("fastmcp.cli.install.mcp_config.print")
|
| 73 |
+
def test_generates_config_without_env_vars(self, mock_print):
|
| 74 |
+
"""Should generate MCP configuration without env section when no env vars."""
|
| 75 |
+
result = install_mcp_config(
|
| 76 |
+
file=Path("server.py"),
|
| 77 |
+
server_object=None,
|
| 78 |
+
name="test-server",
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
assert result is True
|
| 82 |
+
json_output = mock_print.call_args[0][0]
|
| 83 |
+
config = json.loads(json_output)
|
| 84 |
+
|
| 85 |
+
# Should not have env section
|
| 86 |
+
assert "env" not in config
|
| 87 |
+
|
| 88 |
+
@patch("fastmcp.cli.install.mcp_config.print")
|
| 89 |
+
def test_deduplicates_packages(self, mock_print):
|
| 90 |
+
"""Should deduplicate packages including fastmcp."""
|
| 91 |
+
result = install_mcp_config(
|
| 92 |
+
file=Path("server.py"),
|
| 93 |
+
server_object=None,
|
| 94 |
+
name="test-server",
|
| 95 |
+
with_packages=["pandas", "fastmcp", "pandas"], # duplicates
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
assert result is True
|
| 99 |
+
json_output = mock_print.call_args[0][0]
|
| 100 |
+
config = json.loads(json_output)
|
| 101 |
+
|
| 102 |
+
args = config["args"]
|
| 103 |
+
|
| 104 |
+
# Count occurrences of packages
|
| 105 |
+
pandas_count = sum(1 for arg in args if arg == "pandas")
|
| 106 |
+
fastmcp_count = sum(1 for arg in args if arg == "fastmcp")
|
| 107 |
+
|
| 108 |
+
# Should only appear once each for the package (fastmcp appears twice: once as package, once as command)
|
| 109 |
+
assert pandas_count == 1
|
| 110 |
+
assert fastmcp_count == 2 # Once in --with fastmcp, once in fastmcp run
|
| 111 |
+
|
| 112 |
+
@patch("fastmcp.cli.install.mcp_config.print")
|
| 113 |
+
def test_resolves_absolute_paths(self, mock_print):
|
| 114 |
+
"""Should resolve server file to absolute path."""
|
| 115 |
+
result = install_mcp_config(
|
| 116 |
+
file=Path("server.py"),
|
| 117 |
+
server_object=None,
|
| 118 |
+
name="test-server",
|
| 119 |
+
)
|
| 120 |
+
|
| 121 |
+
assert result is True
|
| 122 |
+
json_output = mock_print.call_args[0][0]
|
| 123 |
+
config = json.loads(json_output)
|
| 124 |
+
|
| 125 |
+
args = config["args"]
|
| 126 |
+
|
| 127 |
+
# Find the server spec after "fastmcp run"
|
| 128 |
+
server_spec_in_args = None
|
| 129 |
+
for i, arg in enumerate(args):
|
| 130 |
+
if arg == "fastmcp" and i + 2 < len(args) and args[i + 1] == "run":
|
| 131 |
+
server_spec_in_args = args[i + 2]
|
| 132 |
+
break
|
| 133 |
+
|
| 134 |
+
assert server_spec_in_args is not None
|
| 135 |
+
assert str(Path("server.py").resolve()) in server_spec_in_args
|
| 136 |
+
|
| 137 |
+
@patch("fastmcp.cli.install.mcp_config.print")
|
| 138 |
+
def test_copy_to_clipboard_success(self, mock_print):
|
| 139 |
+
"""Should copy configuration to clipboard when copy=True."""
|
| 140 |
+
# Mock the pyperclip module at import time
|
| 141 |
+
mock_pyperclip = MagicMock()
|
| 142 |
+
mock_copy = MagicMock()
|
| 143 |
+
mock_pyperclip.copy = mock_copy
|
| 144 |
+
|
| 145 |
+
with patch.dict("sys.modules", {"pyperclip": mock_pyperclip}):
|
| 146 |
+
result = install_mcp_config(
|
| 147 |
+
file=Path("server.py"),
|
| 148 |
+
server_object=None,
|
| 149 |
+
name="test-server",
|
| 150 |
+
copy=True,
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
assert result is True
|
| 154 |
+
mock_copy.assert_called_once()
|
| 155 |
+
|
| 156 |
+
# Verify clipboard content is valid JSON
|
| 157 |
+
clipboard_content = mock_copy.call_args[0][0]
|
| 158 |
+
config = json.loads(clipboard_content) # Should not raise
|
| 159 |
+
assert "command" in config # Should be server config, not wrapped
|
| 160 |
+
|
| 161 |
+
# Should print success message
|
| 162 |
+
mock_print.assert_called_once()
|
| 163 |
+
assert "copied to clipboard" in str(mock_print.call_args)
|
| 164 |
+
|
| 165 |
+
@patch("fastmcp.cli.install.mcp_config.print")
|
| 166 |
+
def test_copy_to_clipboard_import_error(self, mock_print):
|
| 167 |
+
"""Should handle pyperclip import error gracefully."""
|
| 168 |
+
with patch(
|
| 169 |
+
"builtins.__import__",
|
| 170 |
+
side_effect=ImportError("No module named 'pyperclip'"),
|
| 171 |
+
):
|
| 172 |
+
result = install_mcp_config(
|
| 173 |
+
file=Path("server.py"),
|
| 174 |
+
server_object=None,
|
| 175 |
+
name="test-server",
|
| 176 |
+
copy=True,
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
assert result is False
|
| 180 |
+
|
| 181 |
+
# Should print error message
|
| 182 |
+
mock_print.assert_called_once()
|
| 183 |
+
error_call = str(mock_print.call_args)
|
| 184 |
+
assert "copy` flag requires pyperclip" in error_call
|
| 185 |
+
assert "pip install pyperclip" in error_call
|
| 186 |
+
|
| 187 |
+
@patch("fastmcp.cli.install.mcp_config.print")
|
| 188 |
+
def test_handles_exception_gracefully(self, mock_print):
|
| 189 |
+
"""Should handle unexpected exceptions gracefully."""
|
| 190 |
+
with patch("json.dumps", side_effect=Exception("JSON error")):
|
| 191 |
+
result = install_mcp_config(
|
| 192 |
+
file=Path("server.py"),
|
| 193 |
+
server_object=None,
|
| 194 |
+
name="test-server",
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
assert result is False
|
| 198 |
+
mock_print.assert_called_once()
|
| 199 |
+
assert "Failed to generate MCP configuration" in str(mock_print.call_args)
|
uv.lock
CHANGED
|
@@ -487,6 +487,7 @@ dev = [
|
|
| 487 |
{ name = "pdbpp" },
|
| 488 |
{ name = "pre-commit" },
|
| 489 |
{ name = "pyinstrument" },
|
|
|
|
| 490 |
{ name = "pyright" },
|
| 491 |
{ name = "pytest" },
|
| 492 |
{ name = "pytest-asyncio" },
|
|
@@ -524,6 +525,7 @@ dev = [
|
|
| 524 |
{ name = "pdbpp", specifier = ">=0.10.3" },
|
| 525 |
{ name = "pre-commit" },
|
| 526 |
{ name = "pyinstrument", specifier = ">=5.0.2" },
|
|
|
|
| 527 |
{ name = "pyright", specifier = ">=1.1.389" },
|
| 528 |
{ name = "pytest", specifier = ">=8.3.3" },
|
| 529 |
{ name = "pytest-asyncio", specifier = ">=0.23.5" },
|
|
|
|
| 487 |
{ name = "pdbpp" },
|
| 488 |
{ name = "pre-commit" },
|
| 489 |
{ name = "pyinstrument" },
|
| 490 |
+
{ name = "pyperclip" },
|
| 491 |
{ name = "pyright" },
|
| 492 |
{ name = "pytest" },
|
| 493 |
{ name = "pytest-asyncio" },
|
|
|
|
| 525 |
{ name = "pdbpp", specifier = ">=0.10.3" },
|
| 526 |
{ name = "pre-commit" },
|
| 527 |
{ name = "pyinstrument", specifier = ">=5.0.2" },
|
| 528 |
+
{ name = "pyperclip", specifier = ">=1.9.0" },
|
| 529 |
{ name = "pyright", specifier = ">=1.1.389" },
|
| 530 |
{ name = "pytest", specifier = ">=8.3.3" },
|
| 531 |
{ name = "pytest-asyncio", specifier = ">=0.23.5" },
|