Skip to content

Feat/tools abstraction #96

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ README
.DS_Store
dist
node_modules
*.log
coverage
282 changes: 248 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,52 @@ mcp create <your project name here> --http --port 1337 --cors
mcp add tool price-fetcher
```

### Building and Validation

The framework provides comprehensive validation to ensure your tools are properly documented and functional:

```bash
# Build with automatic validation (recommended)
npm run build

# Build with custom validation settings
MCP_SKIP_TOOL_VALIDATION=false npm run build # Force validation (default)
MCP_SKIP_TOOL_VALIDATION=true npm run build # Skip validation (not recommended)
```

### Validating Tools

```bash
# Validate all tools have proper descriptions (for Zod schemas)
mcp validate
```

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:

- ✅ **During build**: `npm run build` automatically validates tools
- ✅ **Standalone**: `mcp validate` for manual validation
- ✅ **Development**: Use `defineSchema()` helper for immediate feedback
- ✅ **Runtime**: Server validates tools on startup

**Example validation error:**
```bash
❌ Tool validation failed:
❌ PriceFetcher.js: Missing descriptions for fields in price_fetcher: symbol, currency.
All fields must have descriptions when using Zod object schemas.
Use .describe() on each field, e.g., z.string().describe("Field description")
```

**Integrating validation into CI/CD:**
```json
{
"scripts": {
"build": "tsc && mcp-build",
"test": "jest && mcp validate",
"prepack": "npm run build && mcp validate"
}
}
```

### Adding a Prompt

```bash
Expand All @@ -94,38 +140,75 @@ mcp add resource market-data

## Development Workflow

1. Create your project:

```bash
mcp create my-mcp-server
cd my-mcp-server
```

2. Add tools as needed:
1. **Create your project:**
```bash
mcp create my-mcp-server
cd my-mcp-server
```

2. **Add tools:**
```bash
mcp add tool data-fetcher
mcp add tool data-processor
mcp add tool report-generator
```

3. Build:
3. **Define your tool schemas with automatic validation:**
```typescript
// tools/DataFetcher.ts
import { MCPTool, McpInput } from "mcp-framework";
import { z } from "zod";

const DataFetcherSchema = z.object({
// all fields should have .describe()
url: z.string().url().describe("URL to fetch data from"),
timeout: z.number().positive().default(5000).describe("Request timeout in milliseconds").optional()
});

class DataFetcher extends MCPTool {
name = "data_fetcher";
description = "Fetch data from external APIs";
schema = DataFetcherSchema;

async execute(input: McpInput<this>) {
// Fully typed input with autocomplete support
const { url, timeout = 5000 } = input;
// ... implementation
}
}
```

4. **Build with automatic validation:**
```bash
npm run build
npm run build # Automatically validates schemas and compiles
```

5. **Optional: Run standalone validation:**
```bash
mcp validate # Check all tools independently
```

4. Add to MCP Client (Read below for Claude Desktop example)
6. **Test your server:**
```bash
node dist/index.js # Server validates tools on startup
```

7. **Add to MCP Client** (see Claude Desktop example below)

**Pro Tips:**
- Use `defineSchema()` during development for immediate feedback
- Build process automatically catches missing descriptions
- Server startup validates all tools before accepting connections
- Use TypeScript's autocomplete with `McpInput<this>` for better DX

## Using with Claude Desktop

### Local Development

Add this configuration to your Claude Desktop config file:

**MacOS**: \`~/Library/Application Support/Claude/claude_desktop_config.json\`
**Windows**: \`%APPDATA%/Claude/claude_desktop_config.json\`
**MacOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
**Windows**: `%APPDATA%/Claude/claude_desktop_config.json`

```json
{
Expand All @@ -142,8 +225,8 @@ Add this configuration to your Claude Desktop config file:

Add this configuration to your Claude Desktop config file:

**MacOS**: \`~/Library/Application Support/Claude/claude_desktop_config.json\`
**Windows**: \`%APPDATA%/Claude/claude_desktop_config.json\`
**MacOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
**Windows**: `%APPDATA%/Claude/claude_desktop_config.json`

```json
{
Expand All @@ -159,7 +242,7 @@ Add this configuration to your Claude Desktop config file:
## Building and Testing

1. Make changes to your tools
2. Run \`npm run build\` to compile
2. Run `npm run build` to compile
3. The server will automatically load your tools on startup

## Environment Variables
Expand All @@ -179,39 +262,170 @@ Example usage:
MCP_ENABLE_FILE_LOGGING=true node dist/index.js

# Specify a custom log directory
MCP_ENABLE_FILE_LOGGING=true MCP_LOG_DIRECTORY=my-logs
MCP_ENABLE_FILE_LOGGING=true MCP_LOG_DIRECTORY=my-logs node dist/index.js

# Enable debug messages in console
MCP_DEBUG_CONSOLE=true```
MCP_DEBUG_CONSOLE=true node dist/index.js
```

## Quick Start

### Creating a Tool
### Defining Tools

MCP Framework uses Zod schemas for defining tool inputs, providing type safety, validation, and automatic documentation:

```typescript
import { MCPTool, McpInput } from "mcp-framework";
import { z } from "zod";

const AddToolSchema = z.object({
a: z.number().describe("First number to add"),
b: z.number().describe("Second number to add"),
});

class AddTool extends MCPTool {
name = "add";
description = "Add tool description";
schema = AddToolSchema;

async execute(input: McpInput<this>) {
const result = input.a + input.b;
return `Result: ${result}`;
}
}

export default AddTool;
```

**Key Benefits:**
- ✅ **Single source of truth** - Define types and validation in one place
- ✅ **Automatic type inference** - TypeScript types are inferred from your schema
- ✅ **Rich validation** - Leverage Zod's powerful validation features
- ✅ **Required descriptions** - Framework enforces documentation
- ✅ **Better IDE support** - Full autocomplete and type checking
- ✅ **Cleaner code** - No duplicate type definitions

### Advanced Zod Schema Features

The framework supports all Zod features:

```typescript
import { MCPTool } from "mcp-framework";
import { MCPTool, McpInput } from "mcp-framework";
import { z } from "zod";

interface ExampleInput {
message: string;
const AdvancedSchema = z.object({
// String constraints and formats
email: z.string().email().describe("User email address"),
name: z.string().min(2).max(50).describe("User name"),
website: z.string().url().optional().describe("Optional website URL"),

// Number constraints
age: z.number().int().positive().max(120).describe("User age"),
rating: z.number().min(1).max(5).describe("Rating from 1 to 5"),

// Arrays and objects
tags: z.array(z.string()).describe("List of tags"),
metadata: z.object({
priority: z.enum(['low', 'medium', 'high']).describe("Task priority"),
dueDate: z.string().optional().describe("Due date in ISO format")
}).describe("Additional metadata"),

// Default values
status: z.string().default('pending').describe("Current status"),

// Unions and enums
category: z.union([
z.literal('personal'),
z.literal('work'),
z.literal('other')
]).describe("Category type")
});

class AdvancedTool extends MCPTool {
name = "advanced_tool";
description = "Tool demonstrating advanced Zod features";
schema = AdvancedSchema;

async execute(input: McpInput<this>) {
// TypeScript automatically knows all the types!
const { email, name, website, age, rating, tags, metadata, status, category } = input;

console.log(input.name.toUpperCase()); // ✅ TypeScript knows this is valid
console.log(input.age.toFixed(2)); // ✅ Number methods available
console.log(input.tags.length); // ✅ Array methods available
console.log(input.website?.includes("https")); // ✅ Optional handling

return `Processed user: ${name}`;
}
}
```

class ExampleTool extends MCPTool<ExampleInput> {
name = "example_tool";
description = "An example tool that processes messages";
### Automatic Type Inference

schema = {
message: {
type: z.string(),
description: "Message to process",
},
};
The `McpInput<this>` type automatically infers the correct input type from your schema, eliminating the need for manual type definitions:

async execute(input: ExampleInput) {
return `Processed: ${input.message}`;
```typescript
class MyTool extends MCPTool {
schema = z.object({
name: z.string().describe("User name"),
age: z.number().optional().describe("User age"),
tags: z.array(z.string()).describe("User tags")
});

async execute(input: McpInput<this>) {
// TypeScript automatically knows:
// input.name is string
// input.age is number | undefined
// input.tags is string[]

console.log(input.name.toUpperCase()); // ✅ TypeScript knows this is valid
console.log(input.age?.toFixed(2)); // ✅ Handles optional correctly
console.log(input.tags.length); // ✅ Array methods available
}
}
```

No more duplicate interfaces or generic type parameters needed!

### Schema Validation & Descriptions

**All schema fields must have descriptions**. This ensures your tools are well-documented and provides better user experience in MCP clients.

The framework validates descriptions at multiple levels:

#### 1. Build-time Validation (Recommended)
```bash
npm run build # Automatically validates during compilation
```

#### 2. Development-time Validation
Use the `defineSchema` helper for immediate feedback:

```typescript
import { defineSchema } from "mcp-framework";

// This will throw an error immediately if descriptions are missing
const MySchema = defineSchema({
name: z.string(), // ❌ Error: Missing description
age: z.number().describe("User age") // ✅ Good
});
```

#### 3. Standalone Validation
```bash
mcp validate # Check all tools for proper descriptions
```

#### 4. Runtime Validation
The server automatically validates tools on startup.

**To skip validation** (not recommended):
```bash
# Skip during build
MCP_SKIP_TOOL_VALIDATION=true npm run build

export default ExampleTool;
# Skip during development
NODE_ENV=production npm run dev
```

### Setting up the Server
Expand Down
25 changes: 25 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/** @type {import('jest').Config} */
export default {
preset: 'ts-jest/presets/default-esm',
testEnvironment: 'node',
extensionsToTreatAsEsm: ['.ts'],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
},
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{
useESM: true,
tsconfig: {
module: 'Node16',
moduleResolution: 'Node16',
},
},
],
},
testMatch: ['**/tests/**/*.test.ts'],
moduleFileExtensions: ['ts', 'js', 'json'],
coverageDirectory: 'coverage',
collectCoverageFrom: ['src/**/*.ts', '!src/**/*.d.ts', '!src/cli/**', '!src/index.ts'],
};
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@
"lint": "eslint",
"lint:fix": "eslint --fix",
"format": "prettier --write \"src/**/*.ts\"",
"prepare": "npm run build"
"dev:pub": "rm -rf dist && npm run build && yalc publish --push"
"prepare": "npm run build",
"dev:pub": "rm -rf dist && npm run build && yalc publish --push",
"test": "NODE_OPTIONS='--experimental-vm-modules' jest",
"test:watch": "NODE_OPTIONS='--experimental-vm-modules' jest --watch",
"test:coverage": "NODE_OPTIONS='--experimental-vm-modules' jest --coverage"
},
"engines": {
"node": ">=18.19.0"
Expand Down
Loading