fastmcp 3.20.1 → 3.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,191 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { execa } from "execa";
4
- import yargs from "yargs";
5
- import { hideBin } from "yargs/helpers";
6
-
7
- await yargs(hideBin(process.argv))
8
- .scriptName("fastmcp")
9
- .command(
10
- "dev <file>",
11
- "Start a development server",
12
- (yargs) => {
13
- return yargs
14
- .positional("file", {
15
- demandOption: true,
16
- describe: "The path to the server file",
17
- type: "string",
18
- })
19
-
20
- .option("watch", {
21
- alias: "w",
22
- default: false,
23
- describe: "Watch for file changes and restart server",
24
- type: "boolean",
25
- })
26
-
27
- .option("verbose", {
28
- alias: "v",
29
- default: false,
30
- describe: "Enable verbose logging",
31
- type: "boolean",
32
- });
33
- },
34
-
35
- async (argv) => {
36
- try {
37
- const command = argv.watch
38
- ? `npx @wong2/mcp-cli npx tsx --watch ${argv.file}`
39
- : `npx @wong2/mcp-cli npx tsx ${argv.file}`;
40
-
41
- if (argv.verbose) {
42
- console.log(`[FastMCP] Starting server: ${command}`);
43
- console.log(`[FastMCP] File: ${argv.file}`);
44
- console.log(
45
- `[FastMCP] Watch mode: ${argv.watch ? "enabled" : "disabled"}`,
46
- );
47
- }
48
-
49
- await execa({
50
- shell: true,
51
- stderr: "inherit",
52
- stdin: "inherit",
53
- stdout: "inherit",
54
- })`${command}`;
55
- } catch (error) {
56
- console.error(
57
- "[FastMCP Error] Failed to start development server:",
58
- error instanceof Error ? error.message : String(error),
59
- );
60
-
61
- if (argv.verbose && error instanceof Error && error.stack) {
62
- console.error("[FastMCP Debug] Stack trace:", error.stack);
63
- }
64
-
65
- process.exit(1);
66
- }
67
- },
68
- )
69
-
70
- .command(
71
- "inspect <file>",
72
- "Inspect a server file",
73
- (yargs) => {
74
- return yargs.positional("file", {
75
- demandOption: true,
76
- describe: "The path to the server file",
77
- type: "string",
78
- });
79
- },
80
-
81
- async (argv) => {
82
- try {
83
- await execa({
84
- stderr: "inherit",
85
- stdout: "inherit",
86
- })`npx @modelcontextprotocol/inspector npx tsx ${argv.file}`;
87
- } catch (error) {
88
- console.error(
89
- "[FastMCP Error] Failed to inspect server:",
90
- error instanceof Error ? error.message : String(error),
91
- );
92
-
93
- process.exit(1);
94
- }
95
- },
96
- )
97
-
98
- .command(
99
- "validate <file>",
100
- "Validate a FastMCP server file for syntax and basic structure",
101
- (yargs) => {
102
- return yargs
103
- .positional("file", {
104
- demandOption: true,
105
- describe: "The path to the server file",
106
- type: "string",
107
- })
108
-
109
- .option("strict", {
110
- alias: "s",
111
- default: false,
112
- describe: "Enable strict validation (type checking)",
113
- type: "boolean",
114
- });
115
- },
116
-
117
- async (argv) => {
118
- try {
119
- const { existsSync } = await import("fs");
120
- const { resolve } = await import("path");
121
- const filePath = resolve(argv.file);
122
-
123
- if (!existsSync(filePath)) {
124
- console.error(`[FastMCP Error] File not found: ${filePath}`);
125
- process.exit(1);
126
- }
127
-
128
- console.log(`[FastMCP] Validating server file: ${filePath}`);
129
-
130
- const command = argv.strict
131
- ? `npx tsc --noEmit --strict ${filePath}`
132
- : `npx tsc --noEmit ${filePath}`;
133
-
134
- try {
135
- await execa({
136
- shell: true,
137
- stderr: "pipe",
138
- stdout: "pipe",
139
- })`${command}`;
140
-
141
- console.log("[FastMCP] ✓ TypeScript compilation successful");
142
- } catch (tsError) {
143
- console.error("[FastMCP] ✗ TypeScript compilation failed");
144
-
145
- if (tsError instanceof Error && "stderr" in tsError) {
146
- console.error(tsError.stderr);
147
- }
148
-
149
- process.exit(1);
150
- }
151
-
152
- try {
153
- await execa({
154
- shell: true,
155
- stderr: "pipe",
156
- stdout: "pipe",
157
- })`node -e "
158
- (async () => {
159
- try {
160
- const { FastMCP } = await import('fastmcp');
161
- await import('file://${filePath}');
162
- console.log('[FastMCP] ✓ Server structure validation passed');
163
- } catch (error) {
164
- console.error('[FastMCP] ✗ Server structure validation failed:', error.message);
165
- process.exit(1);
166
- }
167
- })();
168
- "`;
169
- } catch {
170
- console.error("[FastMCP] ✗ Server structure validation failed");
171
- console.error("Make sure the file properly imports and uses FastMCP");
172
-
173
- process.exit(1);
174
- }
175
-
176
- console.log(
177
- "[FastMCP] ✓ All validations passed! Server file looks good.",
178
- );
179
- } catch (error) {
180
- console.error(
181
- "[FastMCP Error] Validation failed:",
182
- error instanceof Error ? error.message : String(error),
183
- );
184
-
185
- process.exit(1);
186
- }
187
- },
188
- )
189
-
190
- .help()
191
- .parseAsync();
@@ -1,333 +0,0 @@
1
- /**
2
- * Example FastMCP server demonstrating core functionality plus streaming output.
3
- *
4
- * Features demonstrated:
5
- * - Basic tool with type-safe parameters
6
- * - Streaming-enabled tool for incremental output
7
- * - Advanced tool annotations
8
- *
9
- * For a complete project template, see https://github.com/punkpeye/fastmcp-boilerplate
10
- */
11
- import { type } from "arktype";
12
- import * as v from "valibot";
13
- import { z } from "zod";
14
-
15
- import { FastMCP } from "../FastMCP.js";
16
-
17
- const server = new FastMCP({
18
- name: "Addition",
19
- ping: {
20
- // enabled: undefined,
21
- // Automatically enabled/disabled based on transport type
22
- // Using a longer interval to reduce log noise
23
- intervalMs: 10000, // default is 5000ms
24
- // Reduce log verbosity
25
- logLevel: "debug", // default
26
- },
27
- roots: {
28
- // You can explicitly disable roots support if needed
29
- // enabled: false,
30
- },
31
- version: "1.0.0",
32
- });
33
-
34
- // --- Zod Example ---
35
- const AddParamsZod = z.object({
36
- a: z.number().describe("The first number"),
37
- b: z.number().describe("The second number"),
38
- });
39
-
40
- server.addTool({
41
- annotations: {
42
- openWorldHint: false, // This tool doesn't interact with external systems
43
- readOnlyHint: true, // This tool doesn't modify anything
44
- title: "Addition (Zod)",
45
- },
46
- description: "Add two numbers (using Zod schema)",
47
- execute: async (args) => {
48
- // args is typed as { a: number, b: number }
49
- console.log(`[Zod] Adding ${args.a} and ${args.b}`);
50
- return String(args.a + args.b);
51
- },
52
- name: "add-zod",
53
- parameters: AddParamsZod,
54
- });
55
-
56
- // --- ArkType Example ---
57
- const AddParamsArkType = type({
58
- a: "number",
59
- b: "number",
60
- });
61
-
62
- server.addTool({
63
- annotations: {
64
- destructiveHint: true, // This would perform destructive operations
65
- idempotentHint: true, // But operations can be repeated safely
66
- openWorldHint: true, // Interacts with external systems
67
- readOnlyHint: false, // Example showing a modifying tool
68
- title: "Addition (ArkType)",
69
- },
70
- description: "Add two numbers (using ArkType schema)",
71
- execute: async (args, { log }) => {
72
- // args is typed as { a: number, b: number } based on AddParamsArkType.infer
73
- console.log(`[ArkType] Adding ${args.a} and ${args.b}`);
74
-
75
- // Demonstrate long-running operation that might need a timeout
76
- log.info("Starting calculation with potential delay...");
77
-
78
- // Simulate a complex calculation process
79
- if (args.a > 1000 || args.b > 1000) {
80
- log.warn("Large numbers detected, operation might take longer");
81
- // In a real implementation, this delay might be a slow operation
82
- await new Promise((resolve) => setTimeout(resolve, 3000));
83
- }
84
-
85
- return String(args.a + args.b);
86
- },
87
- name: "add-arktype",
88
- parameters: AddParamsArkType,
89
- // Will abort execution after 2s
90
- timeoutMs: 2000,
91
- });
92
-
93
- // --- Valibot Example ---
94
- const AddParamsValibot = v.object({
95
- a: v.number("The first number"),
96
- b: v.number("The second number"),
97
- });
98
-
99
- server.addTool({
100
- annotations: {
101
- openWorldHint: false,
102
- readOnlyHint: true,
103
- title: "Addition (Valibot)",
104
- },
105
- description: "Add two numbers (using Valibot schema)",
106
- execute: async (args) => {
107
- console.log(`[Valibot] Adding ${args.a} and ${args.b}`);
108
- return String(args.a + args.b);
109
- },
110
- name: "add-valibot",
111
- parameters: AddParamsValibot,
112
- });
113
-
114
- server.addResource({
115
- async load() {
116
- return {
117
- text: "Example log content",
118
- };
119
- },
120
- mimeType: "text/plain",
121
- name: "Application Logs",
122
- uri: "file:///logs/app.log",
123
- });
124
-
125
- server.addTool({
126
- annotations: {
127
- openWorldHint: false,
128
- readOnlyHint: true,
129
- streamingHint: true,
130
- },
131
- description: "Generate a poem line by line with streaming output",
132
- execute: async (args, context) => {
133
- const { theme } = args;
134
- const lines = [
135
- `Poem about ${theme} - line 1`,
136
- `Poem about ${theme} - line 2`,
137
- `Poem about ${theme} - line 3`,
138
- `Poem about ${theme} - line 4`,
139
- ];
140
-
141
- for (const line of lines) {
142
- await context.streamContent({
143
- text: line,
144
- type: "text",
145
- });
146
-
147
- await new Promise((resolve) => setTimeout(resolve, 1000));
148
- }
149
-
150
- return;
151
- },
152
- name: "stream-poem",
153
- parameters: z.object({
154
- theme: z.string().describe("Theme for the poem"),
155
- }),
156
- });
157
-
158
- server.addTool({
159
- annotations: {
160
- openWorldHint: false,
161
- readOnlyHint: false,
162
- },
163
- description: "Test progress reporting without buffering delays",
164
- execute: async (args, { reportProgress }) => {
165
- console.log("Testing progress reporting fix for HTTP Stream buffering...");
166
-
167
- await reportProgress({ progress: 0, total: 100 });
168
- await new Promise((resolve) => setTimeout(resolve, 500));
169
-
170
- await reportProgress({ progress: 25, total: 100 });
171
- await new Promise((resolve) => setTimeout(resolve, 500));
172
-
173
- await reportProgress({ progress: 75, total: 100 });
174
- await new Promise((resolve) => setTimeout(resolve, 500));
175
-
176
- // This progress should be received immediately
177
- await reportProgress({ progress: 100, total: 100 });
178
-
179
- return `Buffering test completed for ${args.testCase}`;
180
- },
181
- name: "test-buffering-fix",
182
- parameters: z.object({
183
- testCase: z.string().describe("Test case description"),
184
- }),
185
- });
186
-
187
- server.addPrompt({
188
- arguments: [
189
- {
190
- description: "Git diff or description of changes",
191
- name: "changes",
192
- required: true,
193
- },
194
- ],
195
- description: "Generate a Git commit message",
196
- load: async (args) => {
197
- return `Generate a concise but descriptive commit message for these changes:\n\n${args.changes}`;
198
- },
199
- name: "git-commit",
200
- });
201
-
202
- server.addResourceTemplate({
203
- arguments: [
204
- {
205
- description: "Documentation section to retrieve",
206
- name: "section",
207
- required: true,
208
- },
209
- ],
210
- description: "Get project documentation",
211
- load: async (args) => {
212
- const docs = {
213
- "api-reference":
214
- "# API Reference\n\n## Authentication\nAll API requests require a valid API key in the Authorization header.\n\n## Endpoints\n- GET /users - List all users\n- POST /users - Create new user",
215
- deployment:
216
- "# Deployment Guide\n\nTo deploy this application:\n\n1. Build the project: `npm run build`\n2. Set environment variables\n3. Deploy to your hosting platform",
217
- "getting-started":
218
- "# Getting Started\n\nWelcome to our project! Follow these steps to set up your development environment:\n\n1. Clone the repository\n2. Install dependencies with `npm install`\n3. Run `npm start` to begin",
219
- };
220
-
221
- return {
222
- text:
223
- docs[args.section as keyof typeof docs] ||
224
- "Documentation section not found",
225
- };
226
- },
227
- mimeType: "text/markdown",
228
- name: "Project Documentation",
229
- uriTemplate: "docs://project/{section}",
230
- });
231
-
232
- server.addTool({
233
- annotations: {
234
- openWorldHint: false,
235
- readOnlyHint: true,
236
- title: "Get Documentation (Embedded)",
237
- },
238
- description:
239
- "Retrieve project documentation using embedded resources - demonstrates the new embedded() feature",
240
- execute: async (args) => {
241
- return {
242
- content: [
243
- {
244
- resource: await server.embedded(`docs://project/${args.section}`),
245
- type: "resource",
246
- },
247
- ],
248
- };
249
- },
250
- name: "get-documentation",
251
- parameters: z.object({
252
- section: z
253
- .enum(["getting-started", "api-reference", "deployment"])
254
- .describe("Documentation section to retrieve"),
255
- }),
256
- });
257
-
258
- // Select transport type based on command line arguments
259
- const transportType = process.argv.includes("--http-stream")
260
- ? "httpStream"
261
- : "stdio";
262
-
263
- if (transportType === "httpStream") {
264
- // Start with HTTP streaming transport
265
- const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 8080;
266
-
267
- server.start({
268
- httpStream: {
269
- port: PORT,
270
- },
271
- transportType: "httpStream",
272
- });
273
-
274
- console.log(
275
- `HTTP Stream MCP server is running at http://localhost:${PORT}/mcp`,
276
- );
277
- console.log("Use StreamableHTTPClientTransport to connect to this server");
278
- console.log("For example:");
279
- console.log(`
280
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
281
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
282
-
283
- const client = new Client(
284
- {
285
- name: "example-client",
286
- version: "1.0.0",
287
- },
288
- {
289
- capabilities: {},
290
- },
291
- );
292
-
293
- const transport = new StreamableHTTPClientTransport(
294
- new URL("http://localhost:${PORT}/mcp"),
295
- );
296
-
297
- await client.connect(transport);
298
- `);
299
- } else if (process.argv.includes("--explicit-ping-config")) {
300
- server.start({
301
- transportType: "stdio",
302
- });
303
-
304
- console.log(
305
- "Started stdio transport with explicit ping configuration from server options",
306
- );
307
- } else if (process.argv.includes("--disable-roots")) {
308
- // Example of disabling roots at runtime
309
- const serverWithDisabledRoots = new FastMCP({
310
- name: "Addition (No Roots)",
311
- ping: {
312
- intervalMs: 10000,
313
- logLevel: "debug",
314
- },
315
- roots: {
316
- enabled: false,
317
- },
318
- version: "1.0.0",
319
- });
320
-
321
- serverWithDisabledRoots.start({
322
- transportType: "stdio",
323
- });
324
-
325
- console.log("Started stdio transport with roots support disabled");
326
- } else {
327
- // Disable by default for:
328
- server.start({
329
- transportType: "stdio",
330
- });
331
-
332
- console.log("Started stdio transport with ping disabled by default");
333
- }