From typescript-experts
Interactively adds a new tool to an existing FastMCP server by prompting for name, Zod parameters, return type, features like logging and streaming, then generates and integrates the code.
npx claudepluginhub jpoutrin/product-forge --plugin typescript-expertsThis skill uses the workspace's default tool permissions.
Add a new tool to an existing FastMCP server with interactive configuration.
Provides TypeScript patterns for FastMCP MCP servers: basic setup, tools with Zod schemas, logging, progress reporting, streaming output, multiple content types, and image handling.
Scaffolds complete, runnable MCP servers in TypeScript or Python from tool specifications, with transport config, handlers, and test harness. Use for new projects, migrations to MCP, or prototyping tool surfaces.
Generates complete TypeScript MCP server projects with @modelcontextprotocol/sdk, Zod schemas, Express/Stdio transports, tools, error handling, and config.
Share bugs, ideas, or general feedback.
Add a new tool to an existing FastMCP server with interactive configuration.
/add-mcp-tool [tool-name]
[tool-name]: Optional - Name for the new tool (will prompt if not provided)When this command is run:
First, find the FastMCP server in the project:
Looking for FastMCP server...
Found: src/server.ts
Is this the correct server file? (yes/no):
If not found:
I couldn't find a FastMCP server. Please provide the path to your server file:
Let's add a new tool to your MCP server.
Tool name (lowercase, use hyphens):
Example: "fetch-data", "create-file", "query-database"
Tool name:
Tool description (shown to LLM clients):
Example: "Fetches data from the specified API endpoint"
Description:
Does this tool require parameters?
1. No parameters
2. Simple parameters (strings, numbers, booleans)
3. Complex parameters (objects, arrays, enums)
Select (1-3):
If parameters needed:
Let's define the parameters one by one.
Parameter 1:
Name:
Type (string/number/boolean/array/object):
Required? (yes/no):
Description:
Add another parameter? (yes/no):
For enum types:
Enum values (comma-separated):
Example: "json,xml,csv"
Values:
What does this tool return?
1. Simple text response
2. Multiple content items
3. Image content
4. Audio content
5. Resource reference
6. Custom structure
Select (1-6):
Which features do you need?
[ ] Logging (log.info, log.error, etc.)
[ ] Progress reporting (for long operations)
[ ] Streaming output (for text generation)
[ ] Authorization check (canAccess)
[ ] Error handling with UserError
Select features (comma-separated numbers, or 'none'):
Optional annotations for LLM clients:
Read-only hint (tool doesn't modify state)? (yes/no) [default: no]:
Open-world hint (tool accesses external systems)? (yes/no) [default: no]:
Streaming hint (tool produces streaming output)? (yes/no) [default: no]:
server.addTool({
name: "{{tool-name}}",
description: "{{description}}",
execute: async () => {
return "Result here";
},
});
server.addTool({
name: "{{tool-name}}",
description: "{{description}}",
parameters: z.object({
{{#each parameters}}
{{name}}: z.{{type}}(){{#if description}}.describe("{{description}}"){{/if}}{{#unless required}}.optional(){{/unless}},
{{/each}}
}),
execute: async (args) => {
const { {{parameterNames}} } = args;
// TODO: Implement tool logic
return "Processed: " + JSON.stringify(args);
},
});
server.addTool({
name: "{{tool-name}}",
description: "{{description}}",
parameters: z.object({
url: z.string().describe("URL to fetch"),
}),
execute: async (args, { log }) => {
log.info("Starting operation", { url: args.url });
try {
// TODO: Implement logic
const result = await fetchData(args.url);
log.info("Operation completed successfully");
return result;
} catch (error) {
log.error("Operation failed", { error: String(error) });
throw error;
}
},
});
server.addTool({
name: "{{tool-name}}",
description: "{{description}}",
parameters: z.object({
items: z.array(z.string()).describe("Items to process"),
}),
execute: async (args, { reportProgress }) => {
const total = args.items.length;
const results: string[] = [];
for (let i = 0; i < total; i++) {
await reportProgress({ progress: i, total });
// TODO: Process each item
results.push("Processed: " + args.items[i]);
}
await reportProgress({ progress: total, total });
return results.join("\n");
},
});
server.addTool({
name: "{{tool-name}}",
description: "{{description}}",
parameters: z.object({
prompt: z.string().describe("Input prompt"),
}),
annotations: {
streamingHint: true,
},
execute: async (args, { streamContent }) => {
// Stream content progressively
await streamContent({ type: "text", text: "Processing...\n" });
// TODO: Generate content
const chunks = ["First ", "part, ", "second ", "part."];
for (const chunk of chunks) {
await streamContent({ type: "text", text: chunk });
await new Promise(r => setTimeout(r, 100));
}
return; // Return undefined when using streaming
},
});
server.addTool({
name: "{{tool-name}}",
description: "{{description}}",
canAccess: (auth) => {
// Return true if user is authorized
return auth?.role === "admin";
},
execute: async (args, { session }) => {
// Tool is only executed if canAccess returns true
return "Welcome, " + session.id;
},
});
import { UserError } from "fastmcp";
server.addTool({
name: "{{tool-name}}",
description: "{{description}}",
parameters: z.object({
id: z.string().describe("Resource ID"),
}),
execute: async (args) => {
// Validate input
if (!args.id.match(/^[a-z0-9-]+$/)) {
throw new UserError("Invalid ID format. Use lowercase letters, numbers, and hyphens only.");
}
// Check resource exists
const resource = await findResource(args.id);
if (!resource) {
throw new UserError(`Resource not found: ${args.id}`);
}
return JSON.stringify(resource);
},
});
import { imageContent } from "fastmcp";
server.addTool({
name: "{{tool-name}}",
description: "{{description}}",
parameters: z.object({
imageId: z.string().describe("Image identifier"),
}),
execute: async (args) => {
// Return image from URL
return imageContent({
url: `https://example.com/images/${args.imageId}.png`,
});
// Or from file path:
// return imageContent({ path: `/path/to/${args.imageId}.png` });
// Or from buffer:
// return imageContent({ buffer: imageBuffer });
},
});
server.addTool({
name: "{{tool-name}}",
description: "{{description}}",
execute: async () => {
return {
content: [
{ type: "text", text: "Here's the analysis:" },
{ type: "text", text: "Line 1: Found 5 issues" },
{ type: "text", text: "Line 2: 3 warnings" },
],
};
},
});
Where should the tool code be added?
1. Inline in server.ts (simple projects)
2. New file in tools/ directory (recommended for organization)
3. Existing tools file (specify which)
Select (1-3):