@beshkenadze/kubb-plugin-fastmcp
v0.1.0
Published
Kubb plugin to generate FastMCP servers and tools from OpenAPI specifications
Maintainers
Readme
@beshkenadze/kubb-plugin-fastmcp
Swagger/OpenAPI integration to create FastMCP servers and tools.
Features
- Generate FastMCP tool handlers from OpenAPI operations
- Create FastMCP server setup with automatic tool registration
- Group handlers by OpenAPI tags
- TypeScript and Zod schema integration
- Customizable output paths and client configuration
Installation
bun add -D @beshkenadze/kubb-plugin-fastmcpQuick Start
1. Configure Kubb
Create kubb.config.ts:
import { defineConfig } from '@kubb/core'
import { pluginOas } from '@kubb/plugin-oas'
import { pluginTs } from '@kubb/plugin-ts'
import { pluginZod } from '@kubb/plugin-zod'
import { pluginFastMCP } from '@beshkenadze/kubb-plugin-fastmcp'
export default defineConfig({
input: {
path: './petstore.yaml',
},
output: {
path: './src/gen',
},
plugins: [
pluginOas(),
pluginTs(),
pluginZod(),
pluginFastMCP({
output: {
path: './fastmcp',
barrelType: 'named',
},
client: {
baseURL: 'https://petstore.swagger.io/v2',
},
group: {
type: 'tag',
name: ({ group }) => `${group}Handlers`,
},
}),
],
})2. Generate Code
kubb generateThis will generate:
- TypeScript types and Zod schemas
- FastMCP handler functions for each API operation
- FastMCP server setup with all tools registered
- Grouped handler files by OpenAPI tags
3. Generated Output
The plugin generates:
src/gen/fastmcp/server.ts
export const server: FastMCPServer = new FastMCPServer({
name: "OpenAPI Petstore",
version: "3.0.0",
tools: [
{ name: "addPet", description: "Add a new pet to the store", handler: addPetHandler },
{ name: "getPetById", description: "Find pet by ID", handler: getPetByIdHandler },
// ... more operations
],
})
server.start({ transportType: "httpStream", httpStream: { port: 8080 } })src/gen/fastmcp/petHandlers/addPet.ts
import { CallToolResult } from 'fastmcp/types'
import fetch from 'fastmcp/client'
export const addPetHandler = async (params: AddPetRequest): Promise<CallToolResult> => {
const res = await fetch<AddPet200Response>('/pet', {
method: 'POST',
params,
// ... generated client code
})
return {
content: [
{
type: 'text',
text: JSON.stringify(res.data)
}
]
}
}tsconfig.json Integration
The plugin integrates with your project's tsconfig.json to provide intelligent import path resolution and extension handling for generated code. This ensures compatibility with your TypeScript configuration and bundler setup.
Features
- Path Alias Resolution: Automatically resolves
@/*and other path mappings fromcompilerOptions.paths - Import Style Detection: Detects whether to append
.jsor.tsextensions based on your module system (ESM vs CJS) - Dynamic Extension Handling: Generated imports adapt to your configuration (bundler mode, Node.js ESM, etc.)
- Fallback Detection: Uses file existence checks when automatic detection is ambiguous
How It Works
- tsconfig Loading: The plugin uses get-tsconfig to load and parse your
tsconfig.json - Import Style Detection: Based on your
compilerOptions:moduleResolution: "node16" | "nodenext"+module: "node16" | "nodenext"→'needs-js-extension'(ESM requires.jsextensions)allowImportingTsExtensions: true→'ts-extensions-allowed'(allows.tsimports for bundlers)- Default bundler mode →
'no-extension-ok'(extensions optional for Webpack/Vite/esbuild)
- Path Resolution: Uses
createPathsMatcherto resolve aliases like@utils→./src/utils - Extension Appending: The
resolveImportPathutility appends appropriate extensions based on detected style - Plugin Option Override: You can manually set
importStyleto override automatic detection
Example tsconfig.json Configurations
1. ESM with Extensions (Node.js 16+)
{
"compilerOptions": {
"module": "node16",
"moduleResolution": "node16",
"verbatimModuleSyntax": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}Generated imports:
// @utils → ./src/utils.js (needs-js-extension)
import { helper } from '@utils';
// ./local → ./local.js
import { localFn } from './local';2. Bundler Mode with TS Extensions
{
"compilerOptions": {
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@components/*": ["./src/components/*"]
}
}
}Generated imports:
// @components/Button → ./src/components/Button.ts (ts-extensions-allowed)
import { Button } from '@components/Button';
// ./utils → ./utils.ts
import { utilFn } from './utils';
// ./Componentx → ./Componentx.tsx (React JSX detection)
import { Componentx } from './Componentx';3. CommonJS / Legacy Mode
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"baseUrl": "."
}
}Generated imports:
// No extensions appended (no-extension-ok)
import { helper } from '@utils';
import { localFn } from './local';Plugin Configuration
You can override automatic detection with the importStyle option:
pluginFastMCP({
importStyle: 'ts-extensions-allowed', // Force .ts extensions
// or
importStyle: 'no-extension-ok', // No extensions for bundler
output: { path: './fastmcp' },
})Available values:
'auto'(default): Detect from tsconfig.json'needs-js-extension': Always append.js(Node.js ESM)'ts-extensions-allowed': Append.ts/.tsx(bundler with TS extensions)'no-extension-ok': No extensions (CommonJS/bundler)
Testing the Integration
The plugin includes tests for different tsconfig configurations. Run tests to verify:
bun testTests cover:
- Alias resolution with paths mapping
- Extension appending for ESM vs CJS
- React JSX (.tsx) detection
- File existence fallback
Troubleshooting
- Path aliases not resolving: Ensure
baseUrlandpathsare defined in compilerOptions - Extension errors at runtime: Check your bundler configuration matches the detected importStyle
- No tsconfig.json found: The plugin falls back to relative paths without extensions
- Testing with path mappings: Use
vite-tsconfig-pathsin your Vitest config for test resolution
For more details, see the get-tsconfig documentation and TypeScript module resolution docs.
Configuration Options
Basic Configuration
pluginFastMCP({
output: {
path: './fastmcp', // Output directory
barrelType: 'named', // 'named', 'all', or false
},
})Advanced Configuration
pluginFastMCP({
output: {
path: './fastmcp',
barrelType: 'named',
banner: '/* Generated FastMCP Server */',
},
client: {
baseURL: 'https://api.example.com',
dataReturnType: 'data', // 'data' or 'full'
importPath: 'fastmcp/client',
},
group: {
type: 'tag',
name: ({ group }) => `${group}Service`,
},
exclude: [
{ type: 'tag', pattern: 'internal' },
],
include: [
{ type: 'operationId', pattern: 'public*' },
],
transformers: {
name: (name, type) => `${name}FastMCP`,
},
})Grouping Options
- By Tag (default): Groups handlers by OpenAPI tags
- By Path: Groups by path segments
- Custom: Use custom grouping logic
group: {
type: 'tag',
output: './handlers/{{tag}}', // For file grouping
name: ({ group }) => `${group}API`, // For service names
}Demo
1. Setup
Clone the repo and install dependencies:
git clone https://github.com/beshkenadze/kubb-plugin-fastmcp
cd kubb-plugin-fastmcp
bun install2. Download Petstore API
curl -o petstore.yaml https://raw.githubusercontent.com/openapitools/openapi-generator/master/modules/openapi-generator/src/test/resources/3_0/petstore.yaml3. Generate FastMCP Server
kubb generate --config kubb.config.ts4. Run the Server
The generated src/gen/fastmcp/server.ts can be run directly:
cd src/gen/fastmcp
npx tsx server.tsThis starts a FastMCP server on port 8080 with tools for all Petstore API operations.
Integration with FastMCP Clients
The generated handlers use the FastMCP client pattern:
import { FastMCPClient } from 'fastmcp/client'
import { server } from './server'
const client = new FastMCPClient({
server: server,
tools: ['addPet', 'getPetById', 'placeOrder']
})
const result = await client.callTool('addPet', {
// pet data
})Troubleshooting
Module Resolution Errors
Ensure all Kubb packages are compatible versions:
bun add -D @kubb/[email protected] @kubb/[email protected] @kubb/[email protected] @kubb/[email protected]Testing with Path Mappings
For advanced testing with tsconfig path mappings, add jonaskello/tsconfig-paths:
bun add -D jonaskello/tsconfig-pathsUpdate vitest.config.ts:
import { defineConfig } from 'vitest/config'
import tsconfigPaths from 'vite-tsconfig-paths'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react(), tsconfigPaths()],
test: {
environment: 'node',
globals: true,
include: ['**/*.{test,spec}.{js,ts,jsx,tsx}'],
extension: ['.ts', '.tsx'],
},
})This enables automatic .js/.ts extension resolution based on your tsconfig.json settings.
Generation Errors
- Check OpenAPI spec validity with
kubb validate - Ensure all required plugins are included (OAS, TS, Zod)
- Verify output paths exist and are writable
Custom FastMCP SDK
If using a custom FastMCP implementation, update the import paths:
pluginFastMCP({
client: {
importPath: 'your-fastmcp/client',
},
})Contributing
- Fork the repo
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
License
MIT
