Skip to content

Commit b12fd17

Browse files
committed
feat: add new tool abstraction
1 parent d8dc314 commit b12fd17

File tree

10 files changed

+1499
-376
lines changed

10 files changed

+1499
-376
lines changed

README.md

Lines changed: 247 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,52 @@ mcp create <your project name here> --http --port 1337 --cors
7878
mcp add tool price-fetcher
7979
```
8080
81+
### Building and Validation
82+
83+
The framework provides comprehensive validation to ensure your tools are properly documented and functional:
84+
85+
```bash
86+
# Build with automatic validation (recommended)
87+
npm run build
88+
89+
# Build with custom validation settings
90+
MCP_SKIP_TOOL_VALIDATION=false npm run build # Force validation (default)
91+
MCP_SKIP_TOOL_VALIDATION=true npm run build # Skip validation (not recommended)
92+
```
93+
94+
### Validating Tools
95+
96+
```bash
97+
# Validate all tools have proper descriptions (for Zod schemas)
98+
mcp validate
99+
```
100+
101+
This command checks that all tools using Zod schemas have descriptions for every field. The validation runs automatically during build, but you can also run it standalone:
102+
103+
- ✅ **During build**: `npm run build` automatically validates tools
104+
- ✅ **Standalone**: `mcp validate` for manual validation
105+
- ✅ **Development**: Use `defineSchema()` helper for immediate feedback
106+
- ✅ **Runtime**: Server validates tools on startup
107+
108+
**Example validation error:**
109+
```bash
110+
❌ Tool validation failed:
111+
❌ PriceFetcher.js: Missing descriptions for fields in price_fetcher: symbol, currency.
112+
All fields must have descriptions when using Zod object schemas.
113+
Use .describe() on each field, e.g., z.string().describe("Field description")
114+
```
115+
116+
**Integrating validation into CI/CD:**
117+
```json
118+
{
119+
"scripts": {
120+
"build": "tsc && mcp-build",
121+
"test": "jest && mcp validate",
122+
"prepack": "npm run build && mcp validate"
123+
}
124+
}
125+
```
126+
81127
### Adding a Prompt
82128
83129
```bash
@@ -94,38 +140,74 @@ mcp add resource market-data
94140
95141
## Development Workflow
96142
97-
1. Create your project:
98-
99-
```bash
100-
mcp create my-mcp-server
101-
cd my-mcp-server
102-
```
103-
104-
2. Add tools as needed:
143+
1. **Create your project:**
144+
```bash
145+
mcp create my-mcp-server
146+
cd my-mcp-server
147+
```
105148
149+
2. **Add tools:**
106150
```bash
107151
mcp add tool data-fetcher
108152
mcp add tool data-processor
109153
mcp add tool report-generator
110154
```
111155
112-
3. Build:
156+
3. **Define your tool schemas with automatic validation:**
157+
```typescript
158+
// tools/DataFetcher.ts
159+
import { MCPTool, McpInput } from "mcp-framework";
160+
import { z } from "zod";
161+
162+
const DataFetcherSchema = z.object({
163+
url: z.string().url().describe("URL to fetch data from"),
164+
timeout: z.number().positive().default(5000).describe("Request timeout in milliseconds").optional()
165+
});
166+
167+
class DataFetcher extends MCPTool {
168+
name = "data_fetcher";
169+
description = "Fetch data from external APIs";
170+
schema = DataFetcherSchema;
171+
172+
async execute(input: McpInput<this>) {
173+
// Fully typed input with autocomplete support
174+
const { url, timeout = 5000 } = input;
175+
// ... implementation
176+
}
177+
}
178+
```
113179
180+
4. **Build with automatic validation:**
114181
```bash
115-
npm run build
182+
npm run build # Automatically validates schemas and compiles
183+
```
116184
185+
5. **Optional: Run standalone validation:**
186+
```bash
187+
mcp validate # Check all tools independently
117188
```
118189
119-
4. Add to MCP Client (Read below for Claude Desktop example)
190+
6. **Test your server:**
191+
```bash
192+
node dist/index.js # Server validates tools on startup
193+
```
194+
195+
7. **Add to MCP Client** (see Claude Desktop example below)
196+
197+
**Pro Tips:**
198+
- Use `defineSchema()` during development for immediate feedback
199+
- Build process automatically catches missing descriptions
200+
- Server startup validates all tools before accepting connections
201+
- Use TypeScript's autocomplete with `McpInput<this>` for better DX
120202
121203
## Using with Claude Desktop
122204
123205
### Local Development
124206
125207
Add this configuration to your Claude Desktop config file:
126208
127-
**MacOS**: \`~/Library/Application Support/Claude/claude_desktop_config.json\`
128-
**Windows**: \`%APPDATA%/Claude/claude_desktop_config.json\`
209+
**MacOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
210+
**Windows**: `%APPDATA%/Claude/claude_desktop_config.json`
129211
130212
```json
131213
{
@@ -142,8 +224,8 @@ Add this configuration to your Claude Desktop config file:
142224
143225
Add this configuration to your Claude Desktop config file:
144226
145-
**MacOS**: \`~/Library/Application Support/Claude/claude_desktop_config.json\`
146-
**Windows**: \`%APPDATA%/Claude/claude_desktop_config.json\`
227+
**MacOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
228+
**Windows**: `%APPDATA%/Claude/claude_desktop_config.json`
147229
148230
```json
149231
{
@@ -159,7 +241,7 @@ Add this configuration to your Claude Desktop config file:
159241
## Building and Testing
160242
161243
1. Make changes to your tools
162-
2. Run \`npm run build\` to compile
244+
2. Run `npm run build` to compile
163245
3. The server will automatically load your tools on startup
164246
165247
## Environment Variables
@@ -179,39 +261,170 @@ Example usage:
179261
MCP_ENABLE_FILE_LOGGING=true node dist/index.js
180262
181263
# Specify a custom log directory
182-
MCP_ENABLE_FILE_LOGGING=true MCP_LOG_DIRECTORY=my-logs
264+
MCP_ENABLE_FILE_LOGGING=true MCP_LOG_DIRECTORY=my-logs node dist/index.js
265+
183266
# Enable debug messages in console
184-
MCP_DEBUG_CONSOLE=true```
267+
MCP_DEBUG_CONSOLE=true node dist/index.js
268+
```
185269
186270
## Quick Start
187271
188-
### Creating a Tool
272+
### Defining Tools
273+
274+
MCP Framework uses Zod schemas for defining tool inputs, providing type safety, validation, and automatic documentation:
275+
276+
```typescript
277+
import { MCPTool, McpInput } from "mcp-framework";
278+
import { z } from "zod";
279+
280+
const AddToolSchema = z.object({
281+
a: z.number().describe("First number to add"),
282+
b: z.number().describe("Second number to add"),
283+
});
284+
285+
class AddTool extends MCPTool {
286+
name = "add";
287+
description = "Add tool description";
288+
schema = AddToolSchema;
289+
290+
async execute(input: McpInput<this>) {
291+
const result = input.a + input.b;
292+
return `Result: ${result}`;
293+
}
294+
}
295+
296+
export default AddTool;
297+
```
298+
299+
**Key Benefits:**
300+
- ✅ **Single source of truth** - Define types and validation in one place
301+
- ✅ **Automatic type inference** - TypeScript types are inferred from your schema
302+
- ✅ **Rich validation** - Leverage Zod's powerful validation features
303+
- ✅ **Required descriptions** - Framework enforces documentation
304+
- ✅ **Better IDE support** - Full autocomplete and type checking
305+
- ✅ **Cleaner code** - No duplicate type definitions
306+
307+
### Advanced Zod Schema Features
308+
309+
The framework supports all Zod features:
189310
190311
```typescript
191-
import { MCPTool } from "mcp-framework";
312+
import { MCPTool, McpInput } from "mcp-framework";
192313
import { z } from "zod";
193314

194-
interface ExampleInput {
195-
message: string;
315+
const AdvancedSchema = z.object({
316+
// String constraints and formats
317+
email: z.string().email().describe("User email address"),
318+
name: z.string().min(2).max(50).describe("User name"),
319+
website: z.string().url().optional().describe("Optional website URL"),
320+
321+
// Number constraints
322+
age: z.number().int().positive().max(120).describe("User age"),
323+
rating: z.number().min(1).max(5).describe("Rating from 1 to 5"),
324+
325+
// Arrays and objects
326+
tags: z.array(z.string()).describe("List of tags"),
327+
metadata: z.object({
328+
priority: z.enum(['low', 'medium', 'high']).describe("Task priority"),
329+
dueDate: z.string().optional().describe("Due date in ISO format")
330+
}).describe("Additional metadata"),
331+
332+
// Default values
333+
status: z.string().default('pending').describe("Current status"),
334+
335+
// Unions and enums
336+
category: z.union([
337+
z.literal('personal'),
338+
z.literal('work'),
339+
z.literal('other')
340+
]).describe("Category type")
341+
});
342+
343+
class AdvancedTool extends MCPTool {
344+
name = "advanced_tool";
345+
description = "Tool demonstrating advanced Zod features";
346+
schema = AdvancedSchema;
347+
348+
async execute(input: McpInput<this>) {
349+
// TypeScript automatically knows all the types!
350+
const { email, name, website, age, rating, tags, metadata, status, category } = input;
351+
352+
console.log(input.name.toUpperCase()); // ✅ TypeScript knows this is valid
353+
console.log(input.age.toFixed(2)); // ✅ Number methods available
354+
console.log(input.tags.length); // ✅ Array methods available
355+
console.log(input.website?.includes("https")); // ✅ Optional handling
356+
357+
return `Processed user: ${name}`;
358+
}
196359
}
360+
```
197361
198-
class ExampleTool extends MCPTool<ExampleInput> {
199-
name = "example_tool";
200-
description = "An example tool that processes messages";
362+
### Automatic Type Inference
201363
202-
schema = {
203-
message: {
204-
type: z.string(),
205-
description: "Message to process",
206-
},
207-
};
364+
The `McpInput<this>` type automatically infers the correct input type from your schema, eliminating the need for manual type definitions:
208365
209-
async execute(input: ExampleInput) {
210-
return `Processed: ${input.message}`;
366+
```typescript
367+
class MyTool extends MCPTool {
368+
schema = z.object({
369+
name: z.string().describe("User name"),
370+
age: z.number().optional().describe("User age"),
371+
tags: z.array(z.string()).describe("User tags")
372+
});
373+
374+
async execute(input: McpInput<this>) {
375+
// TypeScript automatically knows:
376+
// input.name is string
377+
// input.age is number | undefined
378+
// input.tags is string[]
379+
380+
console.log(input.name.toUpperCase()); // ✅ TypeScript knows this is valid
381+
console.log(input.age?.toFixed(2)); // ✅ Handles optional correctly
382+
console.log(input.tags.length); // ✅ Array methods available
211383
}
212384
}
385+
```
386+
387+
No more duplicate interfaces or generic type parameters needed!
388+
389+
### Schema Validation & Descriptions
390+
391+
**All schema fields must have descriptions**. This ensures your tools are well-documented and provides better user experience in MCP clients.
392+
393+
The framework validates descriptions at multiple levels:
394+
395+
#### 1. Build-time Validation (Recommended)
396+
```bash
397+
npm run build # Automatically validates during compilation
398+
```
399+
400+
#### 2. Development-time Validation
401+
Use the `defineSchema` helper for immediate feedback:
402+
403+
```typescript
404+
import { defineSchema } from "mcp-framework";
405+
406+
// This will throw an error immediately if descriptions are missing
407+
const MySchema = defineSchema({
408+
name: z.string(), // ❌ Error: Missing description
409+
age: z.number().describe("User age") // ✅ Good
410+
});
411+
```
412+
413+
#### 3. Standalone Validation
414+
```bash
415+
mcp validate # Check all tools for proper descriptions
416+
```
417+
418+
#### 4. Runtime Validation
419+
The server automatically validates tools on startup.
420+
421+
**To skip validation** (not recommended):
422+
```bash
423+
# Skip during build
424+
MCP_SKIP_TOOL_VALIDATION=true npm run build
213425

214-
export default ExampleTool;
426+
# Skip during development
427+
NODE_ENV=production npm run dev
215428
```
216429
217430
### Setting up the Server

examples/mcpinput-pattern.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { MCPTool, McpInput } from '../src/index.js';
2+
import { z } from 'zod';
3+
4+
// Define your schema using Zod with descriptions
5+
const AddToolSchema = z.object({
6+
a: z.number().describe('First number to add'),
7+
b: z.number().describe('Second number to add'),
8+
});
9+
10+
// Create the tool - no generic parameters needed!
11+
class AddTool extends MCPTool {
12+
name = 'add';
13+
description = 'Add two numbers';
14+
schema = AddToolSchema;
15+
16+
// McpInput<this> automatically infers types from schema
17+
async execute(input: McpInput<this>) {
18+
const result = input.a + input.b; // Full type safety!
19+
return `Result: ${result}`;
20+
}
21+
}
22+
23+
// Alternative: You can also let TypeScript infer the parameter type
24+
class SubtractTool extends MCPTool {
25+
name = 'subtract';
26+
description = 'Subtract two numbers';
27+
28+
schema = z.object({
29+
x: z.number().describe('Number to subtract from'),
30+
y: z.number().describe('Number to subtract'),
31+
});
32+
33+
// Parameter type is automatically inferred
34+
async execute(input) {
35+
const result = input.x - input.y; // Still type safe!
36+
return `Result: ${result}`;
37+
}
38+
}
39+
40+
export { AddTool, SubtractTool };
41+
export default AddTool;

0 commit comments

Comments
 (0)