@leanmcp/core 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +555 -555
- package/dist/index.d.mts +50 -44
- package/dist/index.d.ts +50 -44
- package/dist/index.js +247 -42
- package/dist/index.mjs +246 -41
- package/package.json +71 -71
package/README.md
CHANGED
|
@@ -1,555 +1,555 @@
|
|
|
1
|
-
# @leanmcp/core
|
|
2
|
-
|
|
3
|
-
Core library for building Model Context Protocol (MCP) servers with TypeScript decorators and declarative schema definition.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **Type-safe decorators** - `@Tool`, `@Prompt`, `@Resource` with full TypeScript support
|
|
8
|
-
- **Schema generation** - Define JSON Schema declaratively using `@SchemaConstraint` decorators on class properties
|
|
9
|
-
- **Streamable HTTP transport** - Production-ready HTTP server with session management
|
|
10
|
-
- **Input validation** - Built-in AJV validation for all inputs
|
|
11
|
-
- **Clean API** - Function names automatically become tool/prompt/resource names
|
|
12
|
-
- **MCP compliant** - Built on official `@modelcontextprotocol/sdk`
|
|
13
|
-
|
|
14
|
-
## Installation
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
npm install @leanmcp/core
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
### Peer Dependencies
|
|
21
|
-
|
|
22
|
-
For HTTP server support:
|
|
23
|
-
```bash
|
|
24
|
-
npm install express cors
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Quick Start
|
|
28
|
-
|
|
29
|
-
### 1. Define Your Service with Class-Based Schema
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
import { Tool, SchemaConstraint, Optional } from "@leanmcp/core";
|
|
33
|
-
|
|
34
|
-
// Define input schema as a class
|
|
35
|
-
class AnalyzeSentimentInput {
|
|
36
|
-
@SchemaConstraint({
|
|
37
|
-
description: 'Text to analyze',
|
|
38
|
-
minLength: 1
|
|
39
|
-
})
|
|
40
|
-
text!: string;
|
|
41
|
-
|
|
42
|
-
@Optional()
|
|
43
|
-
@SchemaConstraint({
|
|
44
|
-
description: 'Language code',
|
|
45
|
-
enum: ['en', 'es', 'fr'],
|
|
46
|
-
default: 'en'
|
|
47
|
-
})
|
|
48
|
-
language?: string;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Define output schema
|
|
52
|
-
class AnalyzeSentimentOutput {
|
|
53
|
-
@SchemaConstraint({ enum: ['positive', 'negative', 'neutral'] })
|
|
54
|
-
sentiment!: string;
|
|
55
|
-
|
|
56
|
-
@SchemaConstraint({ minimum: -1, maximum: 1 })
|
|
57
|
-
score!: number;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export class SentimentService {
|
|
61
|
-
@Tool({
|
|
62
|
-
description: 'Analyze sentiment of text',
|
|
63
|
-
inputClass: AnalyzeSentimentInput
|
|
64
|
-
})
|
|
65
|
-
async analyzeSentiment(input: AnalyzeSentimentInput): Promise<AnalyzeSentimentOutput> {
|
|
66
|
-
// Your implementation
|
|
67
|
-
return {
|
|
68
|
-
sentiment: 'positive',
|
|
69
|
-
score: 0.8,
|
|
70
|
-
confidence: 0.95
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### 2. Create and Start Server
|
|
77
|
-
|
|
78
|
-
#### Option A: Zero-Config Auto-Discovery (Recommended)
|
|
79
|
-
|
|
80
|
-
The simplest way to create an HTTP server with auto-discovery:
|
|
81
|
-
|
|
82
|
-
```typescript
|
|
83
|
-
import { createHTTPServer } from "@leanmcp/core";
|
|
84
|
-
|
|
85
|
-
// Create and start HTTP server with auto-discovery
|
|
86
|
-
await createHTTPServer({
|
|
87
|
-
name: "my-mcp-server",
|
|
88
|
-
version: "1.0.0",
|
|
89
|
-
port: 3000,
|
|
90
|
-
cors: true,
|
|
91
|
-
logging: true
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
console.log('\nMCP Server running');
|
|
95
|
-
console.log('HTTP endpoint: http://localhost:3000/mcp');
|
|
96
|
-
console.log('Health check: http://localhost:3000/health');
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
**What happens automatically:**
|
|
100
|
-
- Services are discovered from `./mcp` directory
|
|
101
|
-
- HTTP server is created and started
|
|
102
|
-
- Session management is configured
|
|
103
|
-
- CORS is enabled (if specified)
|
|
104
|
-
|
|
105
|
-
**Directory Structure:**
|
|
106
|
-
```
|
|
107
|
-
your-project/
|
|
108
|
-
├── main.ts
|
|
109
|
-
└── mcp/
|
|
110
|
-
├── sentiment/
|
|
111
|
-
│ └── index.ts # export class SentimentService
|
|
112
|
-
├── weather/
|
|
113
|
-
│ └── index.ts # export class WeatherService
|
|
114
|
-
└── database/
|
|
115
|
-
└── index.ts # export class DatabaseService
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
#### Option B: Factory Pattern (Advanced)
|
|
119
|
-
|
|
120
|
-
For advanced use cases requiring manual service registration or custom configuration:
|
|
121
|
-
|
|
122
|
-
```typescript
|
|
123
|
-
import { createHTTPServer, MCPServer } from "@leanmcp/core";
|
|
124
|
-
import { SentimentService } from "./services/sentiment";
|
|
125
|
-
|
|
126
|
-
// Create MCP server with factory function
|
|
127
|
-
const serverFactory = async () => {
|
|
128
|
-
const server = new MCPServer({
|
|
129
|
-
name: "my-mcp-server",
|
|
130
|
-
version: "1.0.0",
|
|
131
|
-
logging: true,
|
|
132
|
-
autoDiscover: false // Disable auto-discovery for manual registration
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// Register services manually
|
|
136
|
-
server.registerService(new SentimentService());
|
|
137
|
-
|
|
138
|
-
return server.getServer();
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
// Start HTTP server with factory
|
|
142
|
-
await createHTTPServer(serverFactory, {
|
|
143
|
-
port: 3000,
|
|
144
|
-
cors: true,
|
|
145
|
-
logging: true
|
|
146
|
-
});
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
## Decorators
|
|
150
|
-
|
|
151
|
-
### @Tool
|
|
152
|
-
|
|
153
|
-
Marks a method as an MCP tool (callable function). Use `inputClass` to specify the input schema class.
|
|
154
|
-
|
|
155
|
-
```typescript
|
|
156
|
-
class CalculateInput {
|
|
157
|
-
@SchemaConstraint({ description: 'First number' })
|
|
158
|
-
a!: number;
|
|
159
|
-
|
|
160
|
-
@SchemaConstraint({ description: 'Second number' })
|
|
161
|
-
b!: number;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
@Tool({
|
|
165
|
-
description: 'Calculate sum of two numbers',
|
|
166
|
-
inputClass: CalculateInput
|
|
167
|
-
})
|
|
168
|
-
async calculate(input: CalculateInput) {
|
|
169
|
-
return { result: input.a + input.b };
|
|
170
|
-
}
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
### @Prompt
|
|
174
|
-
|
|
175
|
-
Marks a method as an MCP prompt template. Input schema is automatically inferred from parameter type.
|
|
176
|
-
|
|
177
|
-
```typescript
|
|
178
|
-
class CodeReviewInput {
|
|
179
|
-
@SchemaConstraint({ description: 'Code to review' })
|
|
180
|
-
code!: string;
|
|
181
|
-
|
|
182
|
-
@SchemaConstraint({ description: 'Programming language' })
|
|
183
|
-
language!: string;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
@Prompt({ description: 'Generate code review prompt' })
|
|
187
|
-
codeReview(input: CodeReviewInput) {
|
|
188
|
-
return {
|
|
189
|
-
messages: [{
|
|
190
|
-
role: "user",
|
|
191
|
-
content: {
|
|
192
|
-
type: "text",
|
|
193
|
-
text: `Review this ${input.language} code:\n\n${input.code}`
|
|
194
|
-
}
|
|
195
|
-
}]
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
### @Resource
|
|
201
|
-
|
|
202
|
-
Marks a method as an MCP resource (data source).
|
|
203
|
-
|
|
204
|
-
```typescript
|
|
205
|
-
@Resource({ description: 'Get system configuration', mimeType: 'application/json' })
|
|
206
|
-
async getConfig() {
|
|
207
|
-
return {
|
|
208
|
-
version: "1.0.0",
|
|
209
|
-
environment: process.env.NODE_ENV
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
### @SchemaConstraint
|
|
215
|
-
|
|
216
|
-
Add validation constraints to class properties for automatic schema generation.
|
|
217
|
-
|
|
218
|
-
```typescript
|
|
219
|
-
class UserInput {
|
|
220
|
-
@SchemaConstraint({
|
|
221
|
-
description: 'User email',
|
|
222
|
-
format: 'email',
|
|
223
|
-
minLength: 5,
|
|
224
|
-
maxLength: 100
|
|
225
|
-
})
|
|
226
|
-
email!: string;
|
|
227
|
-
|
|
228
|
-
@SchemaConstraint({
|
|
229
|
-
description: 'User age',
|
|
230
|
-
minimum: 18,
|
|
231
|
-
maximum: 120
|
|
232
|
-
})
|
|
233
|
-
age!: number;
|
|
234
|
-
|
|
235
|
-
@Optional()
|
|
236
|
-
@SchemaConstraint({
|
|
237
|
-
description: 'User role',
|
|
238
|
-
enum: ['admin', 'user', 'guest'],
|
|
239
|
-
default: 'user'
|
|
240
|
-
})
|
|
241
|
-
role?: string;
|
|
242
|
-
}
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
### @Optional
|
|
246
|
-
|
|
247
|
-
Marks a property as optional in the schema.
|
|
248
|
-
|
|
249
|
-
```typescript
|
|
250
|
-
class SearchInput {
|
|
251
|
-
@SchemaConstraint({ description: 'Search query' })
|
|
252
|
-
query!: string;
|
|
253
|
-
|
|
254
|
-
@Optional()
|
|
255
|
-
@SchemaConstraint({ description: 'Max results', default: 10 })
|
|
256
|
-
limit?: number;
|
|
257
|
-
}
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
## API Reference
|
|
261
|
-
|
|
262
|
-
### MCPServer
|
|
263
|
-
|
|
264
|
-
Main server class for registering services.
|
|
265
|
-
|
|
266
|
-
```typescript
|
|
267
|
-
const server = new MCPServer({
|
|
268
|
-
name: string; // Server name
|
|
269
|
-
version: string; // Server version
|
|
270
|
-
logging?: boolean; // Enable logging (default: false)
|
|
271
|
-
debug?: boolean; // Enable verbose debug logs (default: false)
|
|
272
|
-
autoDiscover?: boolean; // Enable auto-discovery (default: true)
|
|
273
|
-
mcpDir?: string; // Custom mcp directory path (optional)
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
// Manual registration
|
|
277
|
-
server.registerService(instance: any): void;
|
|
278
|
-
|
|
279
|
-
// Get underlying MCP SDK server
|
|
280
|
-
server.getServer(): Server;
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
**Options:**
|
|
284
|
-
|
|
285
|
-
- **`logging`**: Enable basic logging for server operations
|
|
286
|
-
- **`debug`**: Enable verbose debug logs showing detailed service registration (requires `logging: true`)
|
|
287
|
-
- **`autoDiscover`**: Automatically discover and register services from `./mcp` directory (default: `true`)
|
|
288
|
-
- **`mcpDir`**: Custom path to the mcp directory (default: auto-detected `./mcp`)
|
|
289
|
-
|
|
290
|
-
#### Zero-Config Auto-Discovery
|
|
291
|
-
|
|
292
|
-
Services are automatically discovered and registered from the `./mcp` directory when the server is created:
|
|
293
|
-
|
|
294
|
-
**Basic Usage (Simplified API):**
|
|
295
|
-
```typescript
|
|
296
|
-
import { createHTTPServer } from "@leanmcp/core";
|
|
297
|
-
|
|
298
|
-
await createHTTPServer({
|
|
299
|
-
name: "my-server",
|
|
300
|
-
version: "1.0.0",
|
|
301
|
-
port: 3000,
|
|
302
|
-
logging: true // Enable logging
|
|
303
|
-
});
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
**With Debug Logging:**
|
|
307
|
-
```typescript
|
|
308
|
-
await createHTTPServer({
|
|
309
|
-
name: "my-server",
|
|
310
|
-
version: "1.0.0",
|
|
311
|
-
port: 3000,
|
|
312
|
-
logging: true,
|
|
313
|
-
debug: true // Show detailed service registration logs
|
|
314
|
-
});
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
**With Shared Dependencies:**
|
|
318
|
-
|
|
319
|
-
For services that need shared dependencies, create a `config.ts` (example) file in your `mcp` directory:
|
|
320
|
-
|
|
321
|
-
```typescript
|
|
322
|
-
// mcp/config.ts
|
|
323
|
-
import { AuthProvider } from "@leanmcp/auth";
|
|
324
|
-
|
|
325
|
-
if (!process.env.COGNITO_USER_POOL_ID || !process.env.COGNITO_CLIENT_ID) {
|
|
326
|
-
throw new Error('Missing required Cognito configuration');
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
export const authProvider = new AuthProvider('cognito', {
|
|
330
|
-
region: process.env.AWS_REGION || 'us-east-1',
|
|
331
|
-
userPoolId: process.env.COGNITO_USER_POOL_ID,
|
|
332
|
-
clientId: process.env.COGNITO_CLIENT_ID,
|
|
333
|
-
clientSecret: process.env.COGNITO_CLIENT_SECRET
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
await authProvider.init();
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
Then import in your services:
|
|
340
|
-
|
|
341
|
-
```typescript
|
|
342
|
-
// mcp/slack/index.ts
|
|
343
|
-
import { Tool } from "@leanmcp/core";
|
|
344
|
-
import { Authenticated } from "@leanmcp/auth";
|
|
345
|
-
import { authProvider } from "../config.js";
|
|
346
|
-
|
|
347
|
-
@Authenticated(authProvider)
|
|
348
|
-
export class SlackService {
|
|
349
|
-
constructor() {
|
|
350
|
-
// No parameters needed - use environment or imported config
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
@Tool({ description: 'Send a message' })
|
|
354
|
-
async sendMessage(args: any) {
|
|
355
|
-
// Implementation
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
Your main file stays clean:
|
|
361
|
-
|
|
362
|
-
```typescript
|
|
363
|
-
import { createHTTPServer } from "@leanmcp/core";
|
|
364
|
-
|
|
365
|
-
await createHTTPServer({
|
|
366
|
-
name: "my-server",
|
|
367
|
-
version: "1.0.0",
|
|
368
|
-
port: 3000,
|
|
369
|
-
logging: true
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
// Services are automatically discovered and registered
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
**How It Works:**
|
|
376
|
-
- Automatically discovers and registers services from the `./mcp` directory during server initialization
|
|
377
|
-
- Recursively scans for `index.ts` or `index.js` files
|
|
378
|
-
- Dynamically imports each file and looks for exported classes
|
|
379
|
-
- Instantiates services with no-args constructors
|
|
380
|
-
- Registers all discovered services with their decorated methods
|
|
381
|
-
|
|
382
|
-
**Directory Structure:**
|
|
383
|
-
```
|
|
384
|
-
your-project/
|
|
385
|
-
├── main.ts
|
|
386
|
-
└── mcp/
|
|
387
|
-
├── config.ts # Optional: shared dependencies
|
|
388
|
-
├── slack/
|
|
389
|
-
│ └── index.ts # export class SlackService
|
|
390
|
-
├── database/
|
|
391
|
-
│ └── index.ts # export class DatabaseService
|
|
392
|
-
└── auth/
|
|
393
|
-
└── index.ts # export class AuthService
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
### createHTTPServer
|
|
397
|
-
|
|
398
|
-
Create and start an HTTP server with streamable transport.
|
|
399
|
-
|
|
400
|
-
**Simplified API (Recommended):**
|
|
401
|
-
```typescript
|
|
402
|
-
await createHTTPServer({
|
|
403
|
-
name: string; // Server name (required)
|
|
404
|
-
version: string; // Server version (required)
|
|
405
|
-
port?: number; // Port number (default: 3001)
|
|
406
|
-
cors?: boolean | object; // Enable CORS (default: false)
|
|
407
|
-
logging?: boolean; // Enable logging (default: false)
|
|
408
|
-
debug?: boolean; // Enable debug logs (default: false)
|
|
409
|
-
autoDiscover?: boolean; // Auto-discover services (default: true)
|
|
410
|
-
mcpDir?: string; // Custom mcp directory path (optional)
|
|
411
|
-
sessionTimeout?: number; // Session timeout in ms (optional)
|
|
412
|
-
});
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
**Factory Pattern (Advanced):**
|
|
416
|
-
```typescript
|
|
417
|
-
await createHTTPServer(
|
|
418
|
-
serverFactory: () => Server | Promise<Server>,
|
|
419
|
-
options: {
|
|
420
|
-
port?: number; // Port number (default: 3001)
|
|
421
|
-
cors?: boolean | object; // Enable CORS (default: false)
|
|
422
|
-
logging?: boolean; // Enable HTTP request logging (default: false)
|
|
423
|
-
sessionTimeout?: number; // Session timeout in ms (optional)
|
|
424
|
-
}
|
|
425
|
-
);
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
**CORS Configuration:**
|
|
429
|
-
```typescript
|
|
430
|
-
// Simple CORS (allow all origins - not recommended for production)
|
|
431
|
-
await createHTTPServer({
|
|
432
|
-
name: "my-server",
|
|
433
|
-
version: "1.0.0",
|
|
434
|
-
cors: true
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
// Advanced CORS configuration
|
|
438
|
-
await createHTTPServer({
|
|
439
|
-
name: "my-server",
|
|
440
|
-
version: "1.0.0",
|
|
441
|
-
cors: {
|
|
442
|
-
origin: 'https://example.com', // Specific origin
|
|
443
|
-
credentials: true // Allow credentials
|
|
444
|
-
}
|
|
445
|
-
});
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
### Schema Generation
|
|
449
|
-
|
|
450
|
-
Generate JSON Schema from TypeScript classes:
|
|
451
|
-
|
|
452
|
-
```typescript
|
|
453
|
-
import { classToJsonSchemaWithConstraints } from "@leanmcp/core";
|
|
454
|
-
|
|
455
|
-
const schema = classToJsonSchemaWithConstraints(MyInputClass);
|
|
456
|
-
```
|
|
457
|
-
|
|
458
|
-
## HTTP Endpoints
|
|
459
|
-
|
|
460
|
-
When using `createHTTPServer`, the following endpoints are available:
|
|
461
|
-
|
|
462
|
-
- `POST /mcp` - MCP protocol endpoint (accepts JSON-RPC 2.0 messages)
|
|
463
|
-
- `GET /health` - Health check endpoint
|
|
464
|
-
- `GET /` - Welcome message
|
|
465
|
-
|
|
466
|
-
## Environment Variables
|
|
467
|
-
|
|
468
|
-
```bash
|
|
469
|
-
PORT=3000 # Server port (optional)
|
|
470
|
-
NODE_ENV=production # Environment (optional)
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
## Error Handling
|
|
474
|
-
|
|
475
|
-
All tools automatically handle errors and return them in MCP format:
|
|
476
|
-
|
|
477
|
-
```typescript
|
|
478
|
-
class DivideInput {
|
|
479
|
-
@SchemaConstraint({ description: 'Numerator' })
|
|
480
|
-
a!: number;
|
|
481
|
-
|
|
482
|
-
@SchemaConstraint({ description: 'Denominator' })
|
|
483
|
-
b!: number;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
@Tool({
|
|
487
|
-
description: 'Divide numbers',
|
|
488
|
-
inputClass: DivideInput
|
|
489
|
-
})
|
|
490
|
-
async divide(input: DivideInput) {
|
|
491
|
-
if (input.b === 0) {
|
|
492
|
-
throw new Error("Division by zero");
|
|
493
|
-
}
|
|
494
|
-
return { result: input.a / input.b };
|
|
495
|
-
}
|
|
496
|
-
```
|
|
497
|
-
|
|
498
|
-
Errors are returned as:
|
|
499
|
-
```json
|
|
500
|
-
{
|
|
501
|
-
"content": [{"type": "text", "text": "Error: Division by zero"}],
|
|
502
|
-
"isError": true
|
|
503
|
-
}
|
|
504
|
-
```
|
|
505
|
-
|
|
506
|
-
## TypeScript Support
|
|
507
|
-
|
|
508
|
-
Full TypeScript support with type inference:
|
|
509
|
-
|
|
510
|
-
```typescript
|
|
511
|
-
class MyInput {
|
|
512
|
-
@SchemaConstraint({ description: 'Input field' })
|
|
513
|
-
field!: string;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
class MyOutput {
|
|
517
|
-
result!: string;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
// Input schema defined via inputClass, output type inferred from return type
|
|
521
|
-
@Tool({
|
|
522
|
-
description: 'My tool',
|
|
523
|
-
inputClass: MyInput
|
|
524
|
-
})
|
|
525
|
-
async myTool(input: MyInput): Promise<MyOutput> {
|
|
526
|
-
// TypeScript knows the exact types
|
|
527
|
-
const result: MyOutput = {
|
|
528
|
-
result: input.field.toUpperCase()
|
|
529
|
-
// Full autocomplete and type checking
|
|
530
|
-
};
|
|
531
|
-
return result;
|
|
532
|
-
}
|
|
533
|
-
```
|
|
534
|
-
|
|
535
|
-
**Key Points:**
|
|
536
|
-
- Input schema is defined using `inputClass` in the `@Tool` decorator
|
|
537
|
-
- Output schema is inferred from the return type
|
|
538
|
-
- For tools with no input parameters, omit the `inputClass` option
|
|
539
|
-
- Use `@SchemaConstraint` decorators to add validation and documentation to your input classes
|
|
540
|
-
|
|
541
|
-
## License
|
|
542
|
-
|
|
543
|
-
MIT
|
|
544
|
-
|
|
545
|
-
## Related Packages
|
|
546
|
-
|
|
547
|
-
- [@leanmcp/cli](../cli) - CLI tool for creating new projects
|
|
548
|
-
- [@leanmcp/auth](../auth) - Authentication decorators and providers
|
|
549
|
-
- [@leanmcp/utils](../utils) - Utility functions
|
|
550
|
-
|
|
551
|
-
## Links
|
|
552
|
-
|
|
553
|
-
- [GitHub Repository](https://github.com/LeanMCP/leanmcp-sdk)
|
|
554
|
-
- [MCP Specification](https://spec.modelcontextprotocol.io/)
|
|
555
|
-
- [Documentation](https://github.com/LeanMCP/leanmcp-sdk#readme)
|
|
1
|
+
# @leanmcp/core
|
|
2
|
+
|
|
3
|
+
Core library for building Model Context Protocol (MCP) servers with TypeScript decorators and declarative schema definition.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Type-safe decorators** - `@Tool`, `@Prompt`, `@Resource` with full TypeScript support
|
|
8
|
+
- **Schema generation** - Define JSON Schema declaratively using `@SchemaConstraint` decorators on class properties
|
|
9
|
+
- **Streamable HTTP transport** - Production-ready HTTP server with session management
|
|
10
|
+
- **Input validation** - Built-in AJV validation for all inputs
|
|
11
|
+
- **Clean API** - Function names automatically become tool/prompt/resource names
|
|
12
|
+
- **MCP compliant** - Built on official `@modelcontextprotocol/sdk`
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @leanmcp/core
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Peer Dependencies
|
|
21
|
+
|
|
22
|
+
For HTTP server support:
|
|
23
|
+
```bash
|
|
24
|
+
npm install express cors
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### 1. Define Your Service with Class-Based Schema
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { Tool, SchemaConstraint, Optional } from "@leanmcp/core";
|
|
33
|
+
|
|
34
|
+
// Define input schema as a class
|
|
35
|
+
class AnalyzeSentimentInput {
|
|
36
|
+
@SchemaConstraint({
|
|
37
|
+
description: 'Text to analyze',
|
|
38
|
+
minLength: 1
|
|
39
|
+
})
|
|
40
|
+
text!: string;
|
|
41
|
+
|
|
42
|
+
@Optional()
|
|
43
|
+
@SchemaConstraint({
|
|
44
|
+
description: 'Language code',
|
|
45
|
+
enum: ['en', 'es', 'fr'],
|
|
46
|
+
default: 'en'
|
|
47
|
+
})
|
|
48
|
+
language?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Define output schema
|
|
52
|
+
class AnalyzeSentimentOutput {
|
|
53
|
+
@SchemaConstraint({ enum: ['positive', 'negative', 'neutral'] })
|
|
54
|
+
sentiment!: string;
|
|
55
|
+
|
|
56
|
+
@SchemaConstraint({ minimum: -1, maximum: 1 })
|
|
57
|
+
score!: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export class SentimentService {
|
|
61
|
+
@Tool({
|
|
62
|
+
description: 'Analyze sentiment of text',
|
|
63
|
+
inputClass: AnalyzeSentimentInput
|
|
64
|
+
})
|
|
65
|
+
async analyzeSentiment(input: AnalyzeSentimentInput): Promise<AnalyzeSentimentOutput> {
|
|
66
|
+
// Your implementation
|
|
67
|
+
return {
|
|
68
|
+
sentiment: 'positive',
|
|
69
|
+
score: 0.8,
|
|
70
|
+
confidence: 0.95
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 2. Create and Start Server
|
|
77
|
+
|
|
78
|
+
#### Option A: Zero-Config Auto-Discovery (Recommended)
|
|
79
|
+
|
|
80
|
+
The simplest way to create an HTTP server with auto-discovery:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import { createHTTPServer } from "@leanmcp/core";
|
|
84
|
+
|
|
85
|
+
// Create and start HTTP server with auto-discovery
|
|
86
|
+
await createHTTPServer({
|
|
87
|
+
name: "my-mcp-server",
|
|
88
|
+
version: "1.0.0",
|
|
89
|
+
port: 3000,
|
|
90
|
+
cors: true,
|
|
91
|
+
logging: true
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
console.log('\nMCP Server running');
|
|
95
|
+
console.log('HTTP endpoint: http://localhost:3000/mcp');
|
|
96
|
+
console.log('Health check: http://localhost:3000/health');
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**What happens automatically:**
|
|
100
|
+
- Services are discovered from `./mcp` directory
|
|
101
|
+
- HTTP server is created and started
|
|
102
|
+
- Session management is configured
|
|
103
|
+
- CORS is enabled (if specified)
|
|
104
|
+
|
|
105
|
+
**Directory Structure:**
|
|
106
|
+
```
|
|
107
|
+
your-project/
|
|
108
|
+
├── main.ts
|
|
109
|
+
└── mcp/
|
|
110
|
+
├── sentiment/
|
|
111
|
+
│ └── index.ts # export class SentimentService
|
|
112
|
+
├── weather/
|
|
113
|
+
│ └── index.ts # export class WeatherService
|
|
114
|
+
└── database/
|
|
115
|
+
└── index.ts # export class DatabaseService
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### Option B: Factory Pattern (Advanced)
|
|
119
|
+
|
|
120
|
+
For advanced use cases requiring manual service registration or custom configuration:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { createHTTPServer, MCPServer } from "@leanmcp/core";
|
|
124
|
+
import { SentimentService } from "./services/sentiment";
|
|
125
|
+
|
|
126
|
+
// Create MCP server with factory function
|
|
127
|
+
const serverFactory = async () => {
|
|
128
|
+
const server = new MCPServer({
|
|
129
|
+
name: "my-mcp-server",
|
|
130
|
+
version: "1.0.0",
|
|
131
|
+
logging: true,
|
|
132
|
+
autoDiscover: false // Disable auto-discovery for manual registration
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Register services manually
|
|
136
|
+
server.registerService(new SentimentService());
|
|
137
|
+
|
|
138
|
+
return server.getServer();
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Start HTTP server with factory
|
|
142
|
+
await createHTTPServer(serverFactory, {
|
|
143
|
+
port: 3000,
|
|
144
|
+
cors: true,
|
|
145
|
+
logging: true
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Decorators
|
|
150
|
+
|
|
151
|
+
### @Tool
|
|
152
|
+
|
|
153
|
+
Marks a method as an MCP tool (callable function). Use `inputClass` to specify the input schema class.
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
class CalculateInput {
|
|
157
|
+
@SchemaConstraint({ description: 'First number' })
|
|
158
|
+
a!: number;
|
|
159
|
+
|
|
160
|
+
@SchemaConstraint({ description: 'Second number' })
|
|
161
|
+
b!: number;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@Tool({
|
|
165
|
+
description: 'Calculate sum of two numbers',
|
|
166
|
+
inputClass: CalculateInput
|
|
167
|
+
})
|
|
168
|
+
async calculate(input: CalculateInput) {
|
|
169
|
+
return { result: input.a + input.b };
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### @Prompt
|
|
174
|
+
|
|
175
|
+
Marks a method as an MCP prompt template. Input schema is automatically inferred from parameter type.
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
class CodeReviewInput {
|
|
179
|
+
@SchemaConstraint({ description: 'Code to review' })
|
|
180
|
+
code!: string;
|
|
181
|
+
|
|
182
|
+
@SchemaConstraint({ description: 'Programming language' })
|
|
183
|
+
language!: string;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@Prompt({ description: 'Generate code review prompt' })
|
|
187
|
+
codeReview(input: CodeReviewInput) {
|
|
188
|
+
return {
|
|
189
|
+
messages: [{
|
|
190
|
+
role: "user",
|
|
191
|
+
content: {
|
|
192
|
+
type: "text",
|
|
193
|
+
text: `Review this ${input.language} code:\n\n${input.code}`
|
|
194
|
+
}
|
|
195
|
+
}]
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### @Resource
|
|
201
|
+
|
|
202
|
+
Marks a method as an MCP resource (data source).
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
@Resource({ description: 'Get system configuration', mimeType: 'application/json' })
|
|
206
|
+
async getConfig() {
|
|
207
|
+
return {
|
|
208
|
+
version: "1.0.0",
|
|
209
|
+
environment: process.env.NODE_ENV
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### @SchemaConstraint
|
|
215
|
+
|
|
216
|
+
Add validation constraints to class properties for automatic schema generation.
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
class UserInput {
|
|
220
|
+
@SchemaConstraint({
|
|
221
|
+
description: 'User email',
|
|
222
|
+
format: 'email',
|
|
223
|
+
minLength: 5,
|
|
224
|
+
maxLength: 100
|
|
225
|
+
})
|
|
226
|
+
email!: string;
|
|
227
|
+
|
|
228
|
+
@SchemaConstraint({
|
|
229
|
+
description: 'User age',
|
|
230
|
+
minimum: 18,
|
|
231
|
+
maximum: 120
|
|
232
|
+
})
|
|
233
|
+
age!: number;
|
|
234
|
+
|
|
235
|
+
@Optional()
|
|
236
|
+
@SchemaConstraint({
|
|
237
|
+
description: 'User role',
|
|
238
|
+
enum: ['admin', 'user', 'guest'],
|
|
239
|
+
default: 'user'
|
|
240
|
+
})
|
|
241
|
+
role?: string;
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### @Optional
|
|
246
|
+
|
|
247
|
+
Marks a property as optional in the schema.
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
class SearchInput {
|
|
251
|
+
@SchemaConstraint({ description: 'Search query' })
|
|
252
|
+
query!: string;
|
|
253
|
+
|
|
254
|
+
@Optional()
|
|
255
|
+
@SchemaConstraint({ description: 'Max results', default: 10 })
|
|
256
|
+
limit?: number;
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## API Reference
|
|
261
|
+
|
|
262
|
+
### MCPServer
|
|
263
|
+
|
|
264
|
+
Main server class for registering services.
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
const server = new MCPServer({
|
|
268
|
+
name: string; // Server name
|
|
269
|
+
version: string; // Server version
|
|
270
|
+
logging?: boolean; // Enable logging (default: false)
|
|
271
|
+
debug?: boolean; // Enable verbose debug logs (default: false)
|
|
272
|
+
autoDiscover?: boolean; // Enable auto-discovery (default: true)
|
|
273
|
+
mcpDir?: string; // Custom mcp directory path (optional)
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// Manual registration
|
|
277
|
+
server.registerService(instance: any): void;
|
|
278
|
+
|
|
279
|
+
// Get underlying MCP SDK server
|
|
280
|
+
server.getServer(): Server;
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**Options:**
|
|
284
|
+
|
|
285
|
+
- **`logging`**: Enable basic logging for server operations
|
|
286
|
+
- **`debug`**: Enable verbose debug logs showing detailed service registration (requires `logging: true`)
|
|
287
|
+
- **`autoDiscover`**: Automatically discover and register services from `./mcp` directory (default: `true`)
|
|
288
|
+
- **`mcpDir`**: Custom path to the mcp directory (default: auto-detected `./mcp`)
|
|
289
|
+
|
|
290
|
+
#### Zero-Config Auto-Discovery
|
|
291
|
+
|
|
292
|
+
Services are automatically discovered and registered from the `./mcp` directory when the server is created:
|
|
293
|
+
|
|
294
|
+
**Basic Usage (Simplified API):**
|
|
295
|
+
```typescript
|
|
296
|
+
import { createHTTPServer } from "@leanmcp/core";
|
|
297
|
+
|
|
298
|
+
await createHTTPServer({
|
|
299
|
+
name: "my-server",
|
|
300
|
+
version: "1.0.0",
|
|
301
|
+
port: 3000,
|
|
302
|
+
logging: true // Enable logging
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**With Debug Logging:**
|
|
307
|
+
```typescript
|
|
308
|
+
await createHTTPServer({
|
|
309
|
+
name: "my-server",
|
|
310
|
+
version: "1.0.0",
|
|
311
|
+
port: 3000,
|
|
312
|
+
logging: true,
|
|
313
|
+
debug: true // Show detailed service registration logs
|
|
314
|
+
});
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**With Shared Dependencies:**
|
|
318
|
+
|
|
319
|
+
For services that need shared dependencies, create a `config.ts` (example) file in your `mcp` directory:
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
// mcp/config.ts
|
|
323
|
+
import { AuthProvider } from "@leanmcp/auth";
|
|
324
|
+
|
|
325
|
+
if (!process.env.COGNITO_USER_POOL_ID || !process.env.COGNITO_CLIENT_ID) {
|
|
326
|
+
throw new Error('Missing required Cognito configuration');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export const authProvider = new AuthProvider('cognito', {
|
|
330
|
+
region: process.env.AWS_REGION || 'us-east-1',
|
|
331
|
+
userPoolId: process.env.COGNITO_USER_POOL_ID,
|
|
332
|
+
clientId: process.env.COGNITO_CLIENT_ID,
|
|
333
|
+
clientSecret: process.env.COGNITO_CLIENT_SECRET
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
await authProvider.init();
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Then import in your services:
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
// mcp/slack/index.ts
|
|
343
|
+
import { Tool } from "@leanmcp/core";
|
|
344
|
+
import { Authenticated } from "@leanmcp/auth";
|
|
345
|
+
import { authProvider } from "../config.js";
|
|
346
|
+
|
|
347
|
+
@Authenticated(authProvider)
|
|
348
|
+
export class SlackService {
|
|
349
|
+
constructor() {
|
|
350
|
+
// No parameters needed - use environment or imported config
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
@Tool({ description: 'Send a message' })
|
|
354
|
+
async sendMessage(args: any) {
|
|
355
|
+
// Implementation
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Your main file stays clean:
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
import { createHTTPServer } from "@leanmcp/core";
|
|
364
|
+
|
|
365
|
+
await createHTTPServer({
|
|
366
|
+
name: "my-server",
|
|
367
|
+
version: "1.0.0",
|
|
368
|
+
port: 3000,
|
|
369
|
+
logging: true
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// Services are automatically discovered and registered
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
**How It Works:**
|
|
376
|
+
- Automatically discovers and registers services from the `./mcp` directory during server initialization
|
|
377
|
+
- Recursively scans for `index.ts` or `index.js` files
|
|
378
|
+
- Dynamically imports each file and looks for exported classes
|
|
379
|
+
- Instantiates services with no-args constructors
|
|
380
|
+
- Registers all discovered services with their decorated methods
|
|
381
|
+
|
|
382
|
+
**Directory Structure:**
|
|
383
|
+
```
|
|
384
|
+
your-project/
|
|
385
|
+
├── main.ts
|
|
386
|
+
└── mcp/
|
|
387
|
+
├── config.ts # Optional: shared dependencies
|
|
388
|
+
├── slack/
|
|
389
|
+
│ └── index.ts # export class SlackService
|
|
390
|
+
├── database/
|
|
391
|
+
│ └── index.ts # export class DatabaseService
|
|
392
|
+
└── auth/
|
|
393
|
+
└── index.ts # export class AuthService
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### createHTTPServer
|
|
397
|
+
|
|
398
|
+
Create and start an HTTP server with streamable transport.
|
|
399
|
+
|
|
400
|
+
**Simplified API (Recommended):**
|
|
401
|
+
```typescript
|
|
402
|
+
await createHTTPServer({
|
|
403
|
+
name: string; // Server name (required)
|
|
404
|
+
version: string; // Server version (required)
|
|
405
|
+
port?: number; // Port number (default: 3001)
|
|
406
|
+
cors?: boolean | object; // Enable CORS (default: false)
|
|
407
|
+
logging?: boolean; // Enable logging (default: false)
|
|
408
|
+
debug?: boolean; // Enable debug logs (default: false)
|
|
409
|
+
autoDiscover?: boolean; // Auto-discover services (default: true)
|
|
410
|
+
mcpDir?: string; // Custom mcp directory path (optional)
|
|
411
|
+
sessionTimeout?: number; // Session timeout in ms (optional)
|
|
412
|
+
});
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
**Factory Pattern (Advanced):**
|
|
416
|
+
```typescript
|
|
417
|
+
await createHTTPServer(
|
|
418
|
+
serverFactory: () => Server | Promise<Server>,
|
|
419
|
+
options: {
|
|
420
|
+
port?: number; // Port number (default: 3001)
|
|
421
|
+
cors?: boolean | object; // Enable CORS (default: false)
|
|
422
|
+
logging?: boolean; // Enable HTTP request logging (default: false)
|
|
423
|
+
sessionTimeout?: number; // Session timeout in ms (optional)
|
|
424
|
+
}
|
|
425
|
+
);
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
**CORS Configuration:**
|
|
429
|
+
```typescript
|
|
430
|
+
// Simple CORS (allow all origins - not recommended for production)
|
|
431
|
+
await createHTTPServer({
|
|
432
|
+
name: "my-server",
|
|
433
|
+
version: "1.0.0",
|
|
434
|
+
cors: true
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// Advanced CORS configuration
|
|
438
|
+
await createHTTPServer({
|
|
439
|
+
name: "my-server",
|
|
440
|
+
version: "1.0.0",
|
|
441
|
+
cors: {
|
|
442
|
+
origin: 'https://example.com', // Specific origin
|
|
443
|
+
credentials: true // Allow credentials
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### Schema Generation
|
|
449
|
+
|
|
450
|
+
Generate JSON Schema from TypeScript classes:
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
import { classToJsonSchemaWithConstraints } from "@leanmcp/core";
|
|
454
|
+
|
|
455
|
+
const schema = classToJsonSchemaWithConstraints(MyInputClass);
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
## HTTP Endpoints
|
|
459
|
+
|
|
460
|
+
When using `createHTTPServer`, the following endpoints are available:
|
|
461
|
+
|
|
462
|
+
- `POST /mcp` - MCP protocol endpoint (accepts JSON-RPC 2.0 messages)
|
|
463
|
+
- `GET /health` - Health check endpoint
|
|
464
|
+
- `GET /` - Welcome message
|
|
465
|
+
|
|
466
|
+
## Environment Variables
|
|
467
|
+
|
|
468
|
+
```bash
|
|
469
|
+
PORT=3000 # Server port (optional)
|
|
470
|
+
NODE_ENV=production # Environment (optional)
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
## Error Handling
|
|
474
|
+
|
|
475
|
+
All tools automatically handle errors and return them in MCP format:
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
class DivideInput {
|
|
479
|
+
@SchemaConstraint({ description: 'Numerator' })
|
|
480
|
+
a!: number;
|
|
481
|
+
|
|
482
|
+
@SchemaConstraint({ description: 'Denominator' })
|
|
483
|
+
b!: number;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
@Tool({
|
|
487
|
+
description: 'Divide numbers',
|
|
488
|
+
inputClass: DivideInput
|
|
489
|
+
})
|
|
490
|
+
async divide(input: DivideInput) {
|
|
491
|
+
if (input.b === 0) {
|
|
492
|
+
throw new Error("Division by zero");
|
|
493
|
+
}
|
|
494
|
+
return { result: input.a / input.b };
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
Errors are returned as:
|
|
499
|
+
```json
|
|
500
|
+
{
|
|
501
|
+
"content": [{"type": "text", "text": "Error: Division by zero"}],
|
|
502
|
+
"isError": true
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
## TypeScript Support
|
|
507
|
+
|
|
508
|
+
Full TypeScript support with type inference:
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
class MyInput {
|
|
512
|
+
@SchemaConstraint({ description: 'Input field' })
|
|
513
|
+
field!: string;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
class MyOutput {
|
|
517
|
+
result!: string;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Input schema defined via inputClass, output type inferred from return type
|
|
521
|
+
@Tool({
|
|
522
|
+
description: 'My tool',
|
|
523
|
+
inputClass: MyInput
|
|
524
|
+
})
|
|
525
|
+
async myTool(input: MyInput): Promise<MyOutput> {
|
|
526
|
+
// TypeScript knows the exact types
|
|
527
|
+
const result: MyOutput = {
|
|
528
|
+
result: input.field.toUpperCase()
|
|
529
|
+
// Full autocomplete and type checking
|
|
530
|
+
};
|
|
531
|
+
return result;
|
|
532
|
+
}
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
**Key Points:**
|
|
536
|
+
- Input schema is defined using `inputClass` in the `@Tool` decorator
|
|
537
|
+
- Output schema is inferred from the return type
|
|
538
|
+
- For tools with no input parameters, omit the `inputClass` option
|
|
539
|
+
- Use `@SchemaConstraint` decorators to add validation and documentation to your input classes
|
|
540
|
+
|
|
541
|
+
## License
|
|
542
|
+
|
|
543
|
+
MIT
|
|
544
|
+
|
|
545
|
+
## Related Packages
|
|
546
|
+
|
|
547
|
+
- [@leanmcp/cli](../cli) - CLI tool for creating new projects
|
|
548
|
+
- [@leanmcp/auth](../auth) - Authentication decorators and providers
|
|
549
|
+
- [@leanmcp/utils](../utils) - Utility functions
|
|
550
|
+
|
|
551
|
+
## Links
|
|
552
|
+
|
|
553
|
+
- [GitHub Repository](https://github.com/LeanMCP/leanmcp-sdk)
|
|
554
|
+
- [MCP Specification](https://spec.modelcontextprotocol.io/)
|
|
555
|
+
- [Documentation](https://github.com/LeanMCP/leanmcp-sdk#readme)
|