Jeremiah Lowin commited on
Commit
9adbb54
·
unverified ·
1 Parent(s): 2ecf7ae

Add MCP json output from CLI (#1056)

Browse files
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/contrib"
 
 
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 + FastMCP
3
  sidebarTitle: Anthropic API
4
  description: Call FastMCP servers from the Anthropic API
5
- icon: message-smile
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 + FastMCP
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 + FastMCP
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
- 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.
12
 
13
- <Note>
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 + 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
 
 
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 + FastMCP
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
- 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.
12
 
13
- <Note>
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 + FastMCP
3
  sidebarTitle: Eunomia Auth
4
  description: Add policy-based authorization to your FastMCP servers
5
- icon: layer-group
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 + FastMCP
3
  sidebarTitle: Gemini SDK
4
  description: Call FastMCP servers from the Google Gemini SDK
5
- icon: message-smile
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 + FastMCP
3
  sidebarTitle: OpenAI API
4
  description: Call FastMCP servers from the OpenAI API
5
- icon: message-smile
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
- <Warning>
168
- The `install` command currently only sets up servers for STDIO transport. When installed in MCP client applications, your server will be run using STDIO regardless of any transport configuration in your code.
169
- </Warning>
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.install import 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 command directly
327
- app.command()(install)
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 module for FastMCP CLI."""
2
 
3
- from .install import install
4
 
5
- __all__ = ["install"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- """Main install logic for FastMCP CLI."""
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
- class Client(str, Enum):
25
- """Supported MCP clients."""
26
-
27
- CLAUDE_CODE = "claude-code"
28
- CLAUDE_DESKTOP = "claude-desktop"
29
- CURSOR = "cursor"
30
-
31
-
32
- def install(
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 = _parse_env_var(env_var)
144
  env_dict[key] = value
145
 
146
- # Route to appropriate installer
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" },