aiyard
v0.3.10
Published
Generate production-ready CLI, SDK, and MCP servers from NestJS controllers
Maintainers
Readme
🏗️ AIYard
Ship 10x faster - Generate production-ready CLIs, SDKs, and MCP servers from your NestJS controllers.
v0.2.8 - FIX: CLI and SDK generators now respect env.baseUrl configuration from aiyard.json!
aiyard generate
✓ CLI tool (Commander.js)
✓ Type-safe SDK (Fetch API + getToken)
✓ MCP server (Claude AI integration)⚡ Quick Start
Install
npm install -g aiyard
# or use directly
npx aiyard generateConfigure
Create aiyard.json in your NestJS project root:
{
"name": "myapi",
"input": {
"pattern": "src/**/*.controller.ts",
"tsconfig": "tsconfig.json"
},
"baseUrl": "http://localhost:3000",
"prefix": "/api/v1",
"outputs": {
"cli": { "enabled": true },
"sdk": { "enabled": true },
"mcp": { "enabled": false }
}
}Generate
aiyard generateUse Generated Code
CLI:
myapi users create --name "Alice" --email "[email protected]"
myapi users list --limit 10
myapi users get user_123SDK:
import { MyAPISDK } from '@myapi/sdk';
const client = new MyAPISDK({
baseUrl: 'http://localhost:3000',
getHeaders: () => ({ 'x-api-key': localStorage.getItem('apiKey') || '' })
});
const users = await client.users.list();
const user = await client.users.create({
name: "Alice",
email: "[email protected]"
});React (with React Query):
import { useQuery, useMutation, QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { MyAPISDK } from '@myapi/sdk';
// Setup client once
const client = new MyAPISDK({
baseUrl: 'http://localhost:3000',
getToken: () => localStorage.getItem('token') || ''
});
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<UsersList />
</QueryClientProvider>
);
}
function UsersList() {
// Query
const { data, isLoading } = useQuery({
queryKey: ['users'],
queryFn: () => client.users.findAll()
});
// Mutation
const { mutate } = useMutation({
mutationFn: (data) => client.users.create(data),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] })
});
if (isLoading) return <div>Loading...</div>;
return (
<div>
<ul>
{data?.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
<button onClick={() => mutate({ name: "Alice", email: "[email protected]" })}>
Add User
</button>
</div>
);
}MCP (Claude Desktop):
{
"mcpServers": {
"myapi": {
"command": "myapi-mcp"
}
}
}✨ Features
🎯 Resource-Based CLI
Intuitive command structure inspired by kubectl and AWS CLI:
myapi users create --name "Alice" --email "[email protected]"- Boolean flags:
--activesets to true - JSON fields:
--metadata '{"role":"admin"}' - Arrays:
--tags "typescript,api,backend" - Enum values shown in help text
📦 Type-Safe SDK
- Full TypeScript type definitions
- Clean interfaces (no Prisma leaks)
- Promise-based API
- Request/response types
- API prefix baked into routes at generation time (no runtime overhead)
- Flexible authentication with
getHeadersandgetToken
⚛️ React Integration
- Works seamlessly with React Query (TanStack Query)
- Works with SWR, Apollo, or any data fetching library
- Type-safe SDK methods for all routes
- Perfect for server state management
- Automatic cache invalidation support
🔐 Flexible Authentication
getHeadersfunction for complete control over request headersgetTokenfunction for Bearer token strategies (deprecated)- Supports any auth scheme: API keys, Bearer, Basic, custom headers
- Works with localStorage, sessionStorage, cookies, or custom logic
- Automatic token/header refresh on each request
- Supports both sync and async retrieval
- No stale tokens - always fresh authentication
🤖 MCP Server
- NestJS-integrated HTTP-based MCP module (drop-in)
- Standalone stdio MCP server for CLI usage
- Native Claude Desktop integration
- AI agents can call your API
- Tool descriptions from JSDoc comments
- Optional API key authentication
🔧 Advanced Type Extraction
- PartialType support for update DTOs
- Enum extraction with value display
- Recursive type definitions
- Clean HTML entities and import paths
📂 Package Strategies
Workspace (recommended):
packages/
├── cli/ (@myapi/cli v1.0.0)
├── sdk/ (@myapi/sdk v1.5.0)
└── mcp/ (@myapi/mcp v2.0.0)Unified:
dist/
├── cli.js
├── sdk.js
└── mcp.js📚 Configuration
Full Example
{
"name": "myapi",
"version": "1.0.0",
"description": "My awesome API",
"input": {
"pattern": ["src/**/*.controller.ts", "src/types/enums.ts"],
"tsconfig": "tsconfig.json",
"exclude": ["**/*.spec.ts"]
},
"baseUrl": "http://localhost:3000",
"prefix": "/api/v1",
"auth": {
"type": "api-key",
"header": "X-API-Key",
"envVar": "API_KEY"
},
"env": {
"baseUrl": "MY_API_URL"
},
"package": {
"strategy": "workspace",
"metadata": {
"author": "Your Name <[email protected]>",
"license": "MIT",
"repository": "https://github.com/username/repo"
},
"build": {
"target": "ES2020",
"formats": ["cjs", "esm"],
"minify": false,
"sourceMap": true
}
},
"outputs": {
"cli": {
"enabled": true,
"path": "packages/cli/src/index.ts",
"options": {
"binName": "myapi"
},
"package": {
"name": "@myapi/cli",
"version": "2.0.0"
}
},
"sdk": {
"enabled": true,
"path": "packages/sdk/src/index.ts",
"options": {
"className": "MyAPISDK"
},
"package": {
"name": "@myapi/sdk",
"version": "1.5.0"
}
},
"mcp": {
"enabled": true,
"path": "packages/mcp/src/index.ts",
"options": {
"serverName": "myapi"
},
"package": {
"name": "@myapi/mcp",
"version": "1.0.0"
}
}
},
"options": {
"includeTypes": true,
"prettify": true,
"verbose": false,
"allowForce": false
}
}Required Fields
name- Project nameinput.pattern- Glob pattern for controllersbaseUrl- API base URL
Optional Fields
version- Default version (default:1.0.0)description- Project descriptionprefix- API path prefix (e.g.,/api/v1) - baked into routes at generation timeauth.type-api-key,bearer,basic,noneenv.baseUrl- Custom environment variable name for base URL (default:BASE_URL)env.apiKey- Custom environment variable name for API key (default:API_KEY)package.strategy-workspaceorunifiedoutputs.*.package.version- Override global version per packageoptions.allowForce- Allow--forceflag to bypass validation (default:false)
Environment Variables
The env field allows you to customize environment variable names used in generated MCP servers:
{
"baseUrl": "http://localhost:3000",
"env": {
"baseUrl": "MY_API_URL"
}
}Generated code without env:
this.baseUrl = process.env.BASE_URL || 'http://localhost:3000';Generated code with env.baseUrl:
this.baseUrl = process.env.MY_API_URL || 'http://localhost:3000';This is useful for:
- Using generic names like
API_URLinstead of app-specific names - Matching existing deployment environment variables
- Standardizing across multiple services
Force Generation
By default, the --force flag is disabled to maintain code quality. This flag allows generation to continue even when validation errors are found, replacing missing types with any.
To enable the --force flag, add to your aiyard.json:
{
"options": {
"allowForce": true
}
}When validation fails without allowForce:
❌ Validation failed. Generation aborted.
The --force flag is disabled by default to maintain code quality.
To enable it, add to your aiyard.json:
{
"options": {
"allowForce": true
}
}
Then you can use:
aiyard generate --forceWith allowForce: true:
# This will now work
aiyard generate --forceWhy is this disabled by default?
- Ensures all types are explicit and properly defined
- Prevents
anytypes from polluting generated code - Encourages following best practices (Response DTOs, Promise return types)
- Makes code generation deterministic and type-safe
See examples/ for more configurations.
🏗️ Best Practices
Use Response DTOs
❌ Bad - Returning Prisma types:
@Get()
findAll(): Promise<User[]> {
return this.prisma.user.findMany();
}✅ Good - Explicit Response DTOs:
// user-response.dto.ts
export class UserResponse {
id: string;
name: string;
email: string;
createdAt: Date;
}
// users.controller.ts
@Get()
findAll(): Promise<UserResponse[]> {
return this.service.findAll();
}Naming Conventions
| Type | Pattern | Example |
|------|---------|---------|
| Input DTO | Create<Entity>Dto | CreateUserDto |
| Update DTO | Update<Entity>Dto | UpdateUserDto |
| Response | <Entity>Response | UserResponse |
File Structure
src/users/
└── dto/
├── create-user.dto.ts
├── update-user.dto.ts
└── user-response.dto.ts🔧 API Prefix Support
AIYard supports API path prefixes like /api/v1 for cleaner project organization. The prefix is baked into generated routes at generation time, not at runtime, for zero overhead.
How It Works
1. Configure prefix in aiyard.json:
{
"baseUrl": "http://localhost:3000",
"prefix": "/api/v1"
}2. Routes generated with prefix baked in:
// Generated SDK code
subjects = {
findAll: async () => {
const url = '/api/v1/subjects'; // ✅ Prefix already included!
return this.request('GET', url);
}
}3. Use SDK with clean config:
const client = new MyAPISDK({
baseUrl: 'http://localhost:3000' // ✅ No prefix needed at runtime!
});
await client.subjects.findAll();
// Calls: http://localhost:3000/api/v1/subjectsBenefits
- Zero Runtime Overhead: Prefix resolved at generation time, not on every request
- Simpler SDK Config: Just
baseUrl, no extra parameters - Explicit Routes: See the full path in generated code
- Easier Debugging: Generated code shows complete URLs
- Type Safety: Routes are constant strings, not dynamic concatenation
🔐 Flexible Authentication
AIYard supports dynamic authentication through the getHeaders and getToken functions, allowing you to implement any authentication strategy without hardcoding credentials.
Why Use getHeaders?
- Complete Control: Set any headers (API keys, Bearer tokens, Basic auth, custom headers)
- Always Fresh: Headers are retrieved before each request
- Flexible Storage: Works with localStorage, sessionStorage, cookies, or any custom logic
- Supports Async: Can await token refresh or async storage
- No Stale Tokens: Perfect for short-lived JWTs
- Multiple Headers: Send multiple authentication headers if needed
SDK Usage
Using getHeaders (Recommended):
import { MyAPISDK } from '@myapi/sdk';
// API Key authentication
const client = new MyAPISDK({
baseUrl: 'http://localhost:3000',
getHeaders: () => ({
'x-api-key': localStorage.getItem('apiKey') || ''
})
});
// Bearer token authentication
const client = new MyAPISDK({
baseUrl: 'http://localhost:3000',
getHeaders: () => ({
'Authorization': `Bearer ${localStorage.getItem('token') || ''}`
})
});
// Multiple headers
const client = new MyAPISDK({
baseUrl: 'http://localhost:3000',
getHeaders: () => ({
'Authorization': `Bearer ${localStorage.getItem('token') || ''}`,
'X-Request-ID': generateRequestId(),
'X-Client-Version': '1.0.0'
})
});
// Async headers
const client = new MyAPISDK({
baseUrl: 'http://localhost:3000',
getHeaders: async () => ({
'Authorization': `Bearer ${await tokenManager.getToken()}`
})
});Using getToken (Deprecated - use getHeaders instead):
import { MyAPISDK } from '@myapi/sdk';
const client = new MyAPISDK({
baseUrl: 'http://localhost:3000',
getToken: () => localStorage.getItem('authToken') || ''
});
// Token retrieved automatically on each request
await client.users.list();Async Token Refresh:
class TokenManager {
private token = '';
private expiresAt = 0;
async getToken(): Promise<string> {
if (Date.now() >= this.expiresAt) {
await this.refreshToken();
}
return this.token;
}
private async refreshToken() {
const response = await fetch('/api/auth/refresh', {
method: 'POST',
credentials: 'include'
});
const data = await response.json();
this.token = data.token;
this.expiresAt = Date.now() + (data.expiresIn * 1000);
}
}
const tokenManager = new TokenManager();
const client = new MyAPISDK({
baseUrl: 'http://localhost:3000',
getToken: () => tokenManager.getToken()
});React Query Usage
Setup (once per app):
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { MyAPISDK } from '@myapi/sdk';
// Create SDK client with token
const client = new MyAPISDK({
baseUrl: 'http://localhost:3000',
getToken: () => localStorage.getItem('authToken') || ''
});
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<Dashboard />
</QueryClientProvider>
);
}Using in Components:
import { useQuery, useMutation } from '@tanstack/react-query';
function UsersList() {
// Query (GET)
const { data: users, isLoading } = useQuery({
queryKey: ['users'],
queryFn: () => client.users.findAll()
});
// Mutation (POST/PUT/PATCH/DELETE)
const createUser = useMutation({
mutationFn: (data) => client.users.create(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] })
}
});
return (
<div>
{isLoading && <p>Loading...</p>}
{users?.map(user => <div key={user.id}>{user.name}</div>)}
<button onClick={() => createUser.mutate({ name: 'Alice', email: '[email protected]' })}>
Add User
</button>
</div>
);
}Common Strategies
| Strategy | Use Case | Example |
|----------|----------|---------|
| API Key | Simple key-based auth | getHeaders: () => ({ 'x-api-key': localStorage.getItem('key') \|\| '' }) |
| Bearer Token | JWT/OAuth tokens | getHeaders: () => ({ 'Authorization': \Bearer ${getToken()}` })|
| **Basic Auth** | Username/password |getHeaders: () => ({ 'Authorization': `Basic ${btoa('user:pass')}` })|
| **localStorage** | Persistent across tabs | Store tokens inlocalStorage.getItem('token')|
| **sessionStorage** | Per-tab, cleared on close | Store tokens insessionStorage.getItem('token')|
| **Cookies** | HttpOnly for security | Custom cookie parser function |
| **Memory** | Fastest, lost on refresh | In-memory token manager |
| **Async Refresh** | Auto-renew expired tokens |getHeaders: async () => ({ 'Authorization': `Bearer ${await refresh()}` })` |
🤖 MCP Server Setup (NestJS Integration)
AIYard generates a drop-in NestJS module that exposes your API as an MCP server over HTTP. This allows AI agents (like Claude) to interact with your NestJS backend directly.
Quick Setup
1. Enable MCP in aiyard.json:
{
"name": "myapi",
"baseUrl": "http://localhost:3000",
"prefix": "/api",
"auth": {
"type": "api-key",
"header": "X-API-Key",
"envVar": "MYAPI_API_KEY"
},
"outputs": {
"mcp": {
"enabled": true,
"path": "src/mcp/mcp.module.ts",
"options": {
"serverName": "myapi"
}
}
}
}2. Generate MCP module:
npx aiyard generate --output .This creates src/mcp/mcp.module.ts - a complete NestJS module.
3. Import MCP module in your app:
// src/app.module.ts
import { Module } from '@nestjs/common';
import { McpModule } from './mcp/mcp.module';
@Module({
imports: [
// ... your existing modules
McpModule, // ✨ Add this
],
})
export class AppModule {}4. (Optional) Customize MCP route prefix:
By default, MCP respects your global prefix. To serve MCP at /mcp instead of /api/mcp:
// src/main.ts
app.setGlobalPrefix('api', {
exclude: ['mcp', 'mcp/health'], // Exclude MCP from global prefix
});Now MCP is at /mcp while REST API stays at /api/*!
5. Start your NestJS app:
npm run startYour MCP server is now running at http://localhost:3000/api/mcp!
Testing MCP Endpoints
List available tools:
curl -X POST http://localhost:3000/api/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}'Call a tool:
curl -X POST http://localhost:3000/api/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "users_findAll",
"arguments": {}
}
}'Health check:
curl -X POST http://localhost:3000/api/mcp/healthAuthentication
If you configure auth in aiyard.json, the generated MCP module will:
- Read the API key from environment variable
- Add the auth header to all backend API calls
- Validate requests before forwarding to your API
Example with auth:
{
"auth": {
"type": "api-key",
"header": "X-API-Key",
"envVar": "MYAPI_API_KEY"
}
}Set environment variable:
export MYAPI_API_KEY=your-secret-keyThe MCP module will automatically include X-API-Key: your-secret-key in all requests to your backend.
How It Works
The generated MCP module:
- Embeds your API schema (routes, parameters, types) at generation time
- Exposes JSON-RPC 2.0 endpoints (
/mcpfor requests,/mcp/healthfor health checks) - Translates MCP tool calls to HTTP requests to your NestJS API
- Includes JSDoc descriptions from your controller methods
- Handles authentication if configured
Environment Variables
Generated module reads these from environment:
{NAME}_URL- Backend API base URL (e.g.,MYAPI_URL){AUTH.ENVVAR}- API key (e.g.,MYAPI_API_KEY) if auth is configured
Override defaults:
export MYAPI_URL=https://api.production.com
export MYAPI_API_KEY=prod-secret-keyClaude Desktop Integration
Add to Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"myapi": {
"command": "curl",
"args": [
"-X", "POST",
"http://localhost:3000/api/mcp",
"-H", "Content-Type: application/json",
"-d", "@-"
]
}
}
}Note: The HTTP-based MCP is best for integrations. For CLI usage, use the standalone stdio MCP server (coming soon).
🔍 What Gets Parsed
AIYard extracts everything from your NestJS controllers:
- ✅
@Controller()- Base route path - ✅
@Get/@Post/@Patch/@Delete/@Put()- HTTP methods - ✅
@Param()- Path parameters - ✅
@Query()- Query parameters - ✅
@Body()- Request body DTOs - ✅ Return types - Response types
- ✅ Enums - With value extraction
- ✅
PartialType()- Mapped type support - ✅ JSDoc comments - For descriptions
🚀 Examples
Check the examples/ directory:
minimal.json- Simplest configurationworkspace.json- Multi-package workspaceunified.json- Single packageversioning.json- Independent version management
💻 Tech Stack
- Parser: ts-morph (TypeScript AST)
- Templates: Handlebars
- CLI: Commander.js
- SDK: Fetch API with TypeScript
- Build: tsup + TypeScript
- Runtime: Node.js 18+
🤝 Contributing
Contributions welcome! Feel free to:
- Open issues for bugs or feature requests
- Submit PRs for improvements
- Share feedback and ideas
📄 License
MIT
⭐ Show Your Support
If AIYard saves you time, give it a star!
Ship faster. Build amazing products.
