@frontmcp/skills 0.0.1
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 +201 -0
- package/README.md +135 -0
- package/catalog/TEMPLATE.md +49 -0
- package/catalog/adapters/create-adapter/SKILL.md +127 -0
- package/catalog/adapters/official-adapters/SKILL.md +136 -0
- package/catalog/auth/configure-auth/SKILL.md +250 -0
- package/catalog/auth/configure-auth/references/auth-modes.md +77 -0
- package/catalog/auth/configure-session/SKILL.md +201 -0
- package/catalog/config/configure-elicitation/SKILL.md +136 -0
- package/catalog/config/configure-http/SKILL.md +167 -0
- package/catalog/config/configure-throttle/SKILL.md +189 -0
- package/catalog/config/configure-throttle/references/guard-config.md +68 -0
- package/catalog/config/configure-transport/SKILL.md +151 -0
- package/catalog/config/configure-transport/references/protocol-presets.md +57 -0
- package/catalog/deployment/build-for-browser/SKILL.md +95 -0
- package/catalog/deployment/build-for-cli/SKILL.md +100 -0
- package/catalog/deployment/build-for-sdk/SKILL.md +218 -0
- package/catalog/deployment/deploy-to-cloudflare/SKILL.md +192 -0
- package/catalog/deployment/deploy-to-lambda/SKILL.md +304 -0
- package/catalog/deployment/deploy-to-node/SKILL.md +229 -0
- package/catalog/deployment/deploy-to-node/references/Dockerfile.example +45 -0
- package/catalog/deployment/deploy-to-vercel/SKILL.md +196 -0
- package/catalog/deployment/deploy-to-vercel/references/vercel.json.example +60 -0
- package/catalog/development/create-agent/SKILL.md +563 -0
- package/catalog/development/create-agent/references/llm-config.md +46 -0
- package/catalog/development/create-job/SKILL.md +566 -0
- package/catalog/development/create-prompt/SKILL.md +400 -0
- package/catalog/development/create-provider/SKILL.md +233 -0
- package/catalog/development/create-resource/SKILL.md +437 -0
- package/catalog/development/create-skill/SKILL.md +526 -0
- package/catalog/development/create-skill-with-tools/SKILL.md +579 -0
- package/catalog/development/create-tool/SKILL.md +418 -0
- package/catalog/development/create-tool/references/output-schema-types.md +56 -0
- package/catalog/development/create-tool/references/tool-annotations.md +34 -0
- package/catalog/development/create-workflow/SKILL.md +709 -0
- package/catalog/development/decorators-guide/SKILL.md +598 -0
- package/catalog/plugins/create-plugin/SKILL.md +336 -0
- package/catalog/plugins/create-plugin-hooks/SKILL.md +282 -0
- package/catalog/plugins/official-plugins/SKILL.md +667 -0
- package/catalog/setup/frontmcp-skills-usage/SKILL.md +200 -0
- package/catalog/setup/multi-app-composition/SKILL.md +358 -0
- package/catalog/setup/nx-workflow/SKILL.md +357 -0
- package/catalog/setup/project-structure-nx/SKILL.md +186 -0
- package/catalog/setup/project-structure-standalone/SKILL.md +153 -0
- package/catalog/setup/setup-project/SKILL.md +493 -0
- package/catalog/setup/setup-redis/SKILL.md +385 -0
- package/catalog/setup/setup-sqlite/SKILL.md +359 -0
- package/catalog/skills-manifest.json +414 -0
- package/catalog/testing/setup-testing/SKILL.md +539 -0
- package/catalog/testing/setup-testing/references/test-auth.md +88 -0
- package/catalog/testing/setup-testing/references/test-browser-build.md +57 -0
- package/catalog/testing/setup-testing/references/test-cli-binary.md +48 -0
- package/catalog/testing/setup-testing/references/test-direct-client.md +62 -0
- package/catalog/testing/setup-testing/references/test-e2e-handler.md +51 -0
- package/catalog/testing/setup-testing/references/test-tool-unit.md +41 -0
- package/package.json +34 -0
- package/src/index.d.ts +3 -0
- package/src/index.js +16 -0
- package/src/index.js.map +1 -0
- package/src/loader.d.ts +46 -0
- package/src/loader.js +75 -0
- package/src/loader.js.map +1 -0
- package/src/manifest.d.ts +81 -0
- package/src/manifest.js +26 -0
- package/src/manifest.js.map +1 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-tool
|
|
3
|
+
description: Create and register an MCP tool with Zod input validation and typed output. Use when building tools, defining input schemas, adding output validation, or registering tools in an app.
|
|
4
|
+
tags: [tools, mcp, zod, schema, decorator]
|
|
5
|
+
tools:
|
|
6
|
+
- name: create_tool
|
|
7
|
+
purpose: Scaffold a new tool class
|
|
8
|
+
parameters:
|
|
9
|
+
- name: name
|
|
10
|
+
description: Tool name in snake_case
|
|
11
|
+
type: string
|
|
12
|
+
required: true
|
|
13
|
+
examples:
|
|
14
|
+
- scenario: Create a calculator tool with add operation
|
|
15
|
+
expected-outcome: Tool registered and callable via MCP
|
|
16
|
+
- scenario: Create a tool with DI and error handling
|
|
17
|
+
expected-outcome: Tool using providers and proper error classes
|
|
18
|
+
priority: 10
|
|
19
|
+
visibility: both
|
|
20
|
+
license: Apache-2.0
|
|
21
|
+
metadata:
|
|
22
|
+
docs: https://docs.agentfront.dev/frontmcp/servers/tools
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Creating an MCP Tool
|
|
26
|
+
|
|
27
|
+
Tools are the primary way to expose executable actions to AI clients in the MCP protocol. In FrontMCP, tools are TypeScript classes that extend `ToolContext`, decorated with `@Tool`, and registered on a `@FrontMcp` server or inside an `@App`.
|
|
28
|
+
|
|
29
|
+
## When to Use @Tool
|
|
30
|
+
|
|
31
|
+
Use `@Tool` when you need to expose an action that an AI client can invoke. Tools accept validated input, perform work (database queries, API calls, computations), and return structured results. Every tool goes through Zod-based input validation before `execute()` runs.
|
|
32
|
+
|
|
33
|
+
## Class-Based Pattern
|
|
34
|
+
|
|
35
|
+
Create a class extending `ToolContext<In, Out>` and implement the `execute(input: In): Promise<Out>` method. The `@Tool` decorator requires at minimum a `name` and an `inputSchema`.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { Tool, ToolContext } from '@frontmcp/sdk';
|
|
39
|
+
import { z } from 'zod';
|
|
40
|
+
|
|
41
|
+
@Tool({
|
|
42
|
+
name: 'greet_user',
|
|
43
|
+
description: 'Greet a user by name',
|
|
44
|
+
inputSchema: {
|
|
45
|
+
name: z.string().describe('The name of the user to greet'),
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
class GreetUserTool extends ToolContext {
|
|
49
|
+
async execute(input: { name: string }) {
|
|
50
|
+
return `Hello, ${input.name}!`;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Available Context Methods and Properties
|
|
56
|
+
|
|
57
|
+
`ToolContext` extends `ExecutionContextBase`, which provides:
|
|
58
|
+
|
|
59
|
+
**Methods:**
|
|
60
|
+
|
|
61
|
+
- `execute(input: In): Promise<Out>` -- the main method you implement
|
|
62
|
+
- `this.get(token)` -- resolve a dependency from DI (throws if not found)
|
|
63
|
+
- `this.tryGet(token)` -- resolve a dependency from DI (returns `undefined` if not found)
|
|
64
|
+
- `this.fail(err)` -- abort execution, triggers error flow (never returns)
|
|
65
|
+
- `this.mark(stage)` -- set the active execution stage for debugging/tracking
|
|
66
|
+
- `this.fetch(input, init?)` -- HTTP fetch with context propagation
|
|
67
|
+
- `this.notify(message, level?)` -- send a log-level notification to the client
|
|
68
|
+
- `this.respondProgress(value, total?)` -- send a progress notification to the client
|
|
69
|
+
|
|
70
|
+
**Properties:**
|
|
71
|
+
|
|
72
|
+
- `this.input` -- the validated input object
|
|
73
|
+
- `this.output` -- the output (available after execute)
|
|
74
|
+
- `this.metadata` -- tool metadata from the decorator
|
|
75
|
+
- `this.scope` -- the current scope instance
|
|
76
|
+
- `this.context` -- the execution context
|
|
77
|
+
|
|
78
|
+
## Input Schema: Zod Raw Shapes
|
|
79
|
+
|
|
80
|
+
The `inputSchema` accepts a **Zod raw shape** -- a plain object mapping field names to Zod types. Do NOT wrap it in `z.object()`. The framework wraps it internally.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
@Tool({
|
|
84
|
+
name: 'search_documents',
|
|
85
|
+
description: 'Search documents by query and optional filters',
|
|
86
|
+
inputSchema: {
|
|
87
|
+
// This is a raw shape, NOT z.object({...})
|
|
88
|
+
query: z.string().min(1).describe('Search query'),
|
|
89
|
+
limit: z.number().int().min(1).max(100).default(10).describe('Max results'),
|
|
90
|
+
category: z.enum(['blog', 'docs', 'api']).optional().describe('Filter by category'),
|
|
91
|
+
},
|
|
92
|
+
})
|
|
93
|
+
class SearchDocumentsTool extends ToolContext {
|
|
94
|
+
async execute(input: { query: string; limit: number; category?: 'blog' | 'docs' | 'api' }) {
|
|
95
|
+
// input is already validated by Zod before execute() is called
|
|
96
|
+
return { results: [], total: 0 };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The `execute()` parameter type must match the inferred output of `z.object(inputSchema)`. Validated input is also available via `this.input`.
|
|
102
|
+
|
|
103
|
+
## Output Schema (Recommended Best Practice)
|
|
104
|
+
|
|
105
|
+
**Always define `outputSchema` for every tool.** This is a best practice for three critical reasons:
|
|
106
|
+
|
|
107
|
+
1. **Output validation** -- Prevents data leaks by ensuring your tool only returns fields you explicitly declare. Without `outputSchema`, any data in the return value passes through unvalidated, risking accidental exposure of sensitive fields (internal IDs, tokens, PII).
|
|
108
|
+
2. **CodeCall plugin compatibility** -- The CodeCall plugin uses `outputSchema` to understand what a tool returns, enabling correct VM-based orchestration and pass-by-reference. Tools without `outputSchema` degrade CodeCall's ability to chain results.
|
|
109
|
+
3. **Type safety** -- The `Out` generic on `ToolContext<In, Out>` is inferred from `outputSchema`, giving you compile-time guarantees that `execute()` returns the correct shape.
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
@Tool({
|
|
113
|
+
name: 'get_weather',
|
|
114
|
+
description: 'Get current weather for a location',
|
|
115
|
+
inputSchema: {
|
|
116
|
+
city: z.string().describe('City name'),
|
|
117
|
+
},
|
|
118
|
+
// Always define outputSchema to validate output and prevent data leaks
|
|
119
|
+
outputSchema: {
|
|
120
|
+
temperature: z.number(),
|
|
121
|
+
unit: z.enum(['celsius', 'fahrenheit']),
|
|
122
|
+
description: z.string(),
|
|
123
|
+
},
|
|
124
|
+
})
|
|
125
|
+
class GetWeatherTool extends ToolContext {
|
|
126
|
+
async execute(input: { city: string }): Promise<{
|
|
127
|
+
temperature: number;
|
|
128
|
+
unit: 'celsius' | 'fahrenheit';
|
|
129
|
+
description: string;
|
|
130
|
+
}> {
|
|
131
|
+
const response = await this.fetch(`https://api.weather.example.com/v1/current?city=${input.city}`);
|
|
132
|
+
const weather = await response.json();
|
|
133
|
+
// Only temperature, unit, and description are returned.
|
|
134
|
+
// Any extra fields from the API (e.g., internalId, apiKey) are stripped by outputSchema validation.
|
|
135
|
+
return {
|
|
136
|
+
temperature: weather.temp,
|
|
137
|
+
unit: 'celsius',
|
|
138
|
+
description: weather.summary,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Why not omit outputSchema?** Without it:
|
|
145
|
+
|
|
146
|
+
- The tool returns raw unvalidated data — any field your code accidentally includes leaks to the client
|
|
147
|
+
- CodeCall cannot infer return types for chaining tool calls in VM scripts
|
|
148
|
+
- No compile-time type checking on the return value
|
|
149
|
+
|
|
150
|
+
Supported `outputSchema` types:
|
|
151
|
+
|
|
152
|
+
- **Zod raw shapes** (recommended): `{ field: z.string(), count: z.number() }` — structured JSON output with validation
|
|
153
|
+
- **Zod schemas**: `z.object(...)`, `z.array(...)`, `z.union([...])` — for complex types
|
|
154
|
+
- **Primitive literals**: `'string'`, `'number'`, `'boolean'`, `'date'` — for simple returns
|
|
155
|
+
- **Media types**: `'image'`, `'audio'`, `'resource'`, `'resource_link'` — for binary/link content
|
|
156
|
+
- **Arrays**: `['string', 'image']` for multi-content responses
|
|
157
|
+
|
|
158
|
+
## Dependency Injection
|
|
159
|
+
|
|
160
|
+
Access providers registered in the scope using `this.get(token)` (throws if not found) or `this.tryGet(token)` (returns `undefined` if not found).
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import type { Token } from '@frontmcp/di';
|
|
164
|
+
|
|
165
|
+
interface DatabaseService {
|
|
166
|
+
query(sql: string, params: unknown[]): Promise<unknown[]>;
|
|
167
|
+
}
|
|
168
|
+
const DATABASE: Token<DatabaseService> = Symbol('database');
|
|
169
|
+
|
|
170
|
+
@Tool({
|
|
171
|
+
name: 'run_query',
|
|
172
|
+
description: 'Execute a database query',
|
|
173
|
+
inputSchema: {
|
|
174
|
+
sql: z.string().describe('SQL query to execute'),
|
|
175
|
+
},
|
|
176
|
+
})
|
|
177
|
+
class RunQueryTool extends ToolContext {
|
|
178
|
+
async execute(input: { sql: string }) {
|
|
179
|
+
const db = this.get(DATABASE); // throws if DATABASE not registered
|
|
180
|
+
const rows = await db.query(input.sql, []);
|
|
181
|
+
return { rows, count: rows.length };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Use `this.tryGet(token)` when the dependency is optional:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
async execute(input: { data: string }) {
|
|
190
|
+
const cache = this.tryGet(CACHE); // returns undefined if not registered
|
|
191
|
+
if (cache) {
|
|
192
|
+
const cached = await cache.get(input.data);
|
|
193
|
+
if (cached) return cached;
|
|
194
|
+
}
|
|
195
|
+
// proceed without cache
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Error Handling
|
|
200
|
+
|
|
201
|
+
Use `this.fail(err)` to abort execution and trigger the error flow. The method throws internally and never returns.
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
@Tool({
|
|
205
|
+
name: 'delete_record',
|
|
206
|
+
description: 'Delete a record by ID',
|
|
207
|
+
inputSchema: {
|
|
208
|
+
id: z.string().uuid().describe('Record UUID'),
|
|
209
|
+
},
|
|
210
|
+
})
|
|
211
|
+
class DeleteRecordTool extends ToolContext {
|
|
212
|
+
async execute(input: { id: string }) {
|
|
213
|
+
const record = await this.findRecord(input.id);
|
|
214
|
+
if (!record) {
|
|
215
|
+
this.fail(new Error(`Record not found: ${input.id}`));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
await this.deleteRecord(record);
|
|
219
|
+
return `Record ${input.id} deleted successfully`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private async findRecord(id: string) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private async deleteRecord(record: unknown) {
|
|
227
|
+
// delete implementation
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
For MCP-specific errors, use error classes with JSON-RPC codes:
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
import { ResourceNotFoundError, PublicMcpError, MCP_ERROR_CODES } from '@frontmcp/sdk';
|
|
236
|
+
|
|
237
|
+
this.fail(new ResourceNotFoundError(`Record ${input.id}`));
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Progress and Notifications
|
|
241
|
+
|
|
242
|
+
Use `this.notify(message, level?)` to send log-level notifications and `this.respondProgress(value, total?)` to send progress updates to the client.
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
@Tool({
|
|
246
|
+
name: 'batch_process',
|
|
247
|
+
description: 'Process a batch of items',
|
|
248
|
+
inputSchema: {
|
|
249
|
+
items: z.array(z.string()).min(1).describe('Items to process'),
|
|
250
|
+
},
|
|
251
|
+
})
|
|
252
|
+
class BatchProcessTool extends ToolContext {
|
|
253
|
+
async execute(input: { items: string[] }) {
|
|
254
|
+
this.mark('validation');
|
|
255
|
+
this.validateItems(input.items);
|
|
256
|
+
|
|
257
|
+
this.mark('processing');
|
|
258
|
+
const results: string[] = [];
|
|
259
|
+
for (let i = 0; i < input.items.length; i++) {
|
|
260
|
+
await this.respondProgress(i + 1, input.items.length);
|
|
261
|
+
const result = await this.processItem(input.items[i]);
|
|
262
|
+
results.push(result);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
this.mark('complete');
|
|
266
|
+
await this.notify(`Processed ${results.length} items`, 'info');
|
|
267
|
+
return { processed: results.length, results };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private validateItems(items: string[]) {
|
|
271
|
+
/* ... */
|
|
272
|
+
}
|
|
273
|
+
private async processItem(item: string): Promise<string> {
|
|
274
|
+
return item;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Tool Annotations
|
|
280
|
+
|
|
281
|
+
Provide behavioral hints to clients using `annotations`. These hints help clients decide how to present and gate tool usage.
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
@Tool({
|
|
285
|
+
name: 'web_search',
|
|
286
|
+
description: 'Search the web',
|
|
287
|
+
inputSchema: {
|
|
288
|
+
query: z.string(),
|
|
289
|
+
},
|
|
290
|
+
annotations: {
|
|
291
|
+
title: 'Web Search',
|
|
292
|
+
readOnlyHint: true,
|
|
293
|
+
openWorldHint: true,
|
|
294
|
+
},
|
|
295
|
+
})
|
|
296
|
+
class WebSearchTool extends ToolContext {
|
|
297
|
+
async execute(input: { query: string }) {
|
|
298
|
+
return await this.performSearch(input.query);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private async performSearch(query: string) {
|
|
302
|
+
return [];
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Annotation fields:
|
|
308
|
+
|
|
309
|
+
- `title` -- Human-readable title for the tool
|
|
310
|
+
- `readOnlyHint` -- Tool does not modify its environment (default: false)
|
|
311
|
+
- `destructiveHint` -- Tool may perform destructive updates (default: true, meaningful only when readOnlyHint is false)
|
|
312
|
+
- `idempotentHint` -- Calling repeatedly with same args has no additional effect (default: false)
|
|
313
|
+
- `openWorldHint` -- Tool interacts with external entities (default: true)
|
|
314
|
+
|
|
315
|
+
## Function-Style Builder
|
|
316
|
+
|
|
317
|
+
For simple tools that do not need a class, use the `tool()` function builder. It returns a value you register the same way as a class tool.
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
import { tool } from '@frontmcp/sdk';
|
|
321
|
+
import { z } from 'zod';
|
|
322
|
+
|
|
323
|
+
const AddNumbers = tool({
|
|
324
|
+
name: 'add_numbers',
|
|
325
|
+
description: 'Add two numbers',
|
|
326
|
+
inputSchema: {
|
|
327
|
+
a: z.number().describe('First number'),
|
|
328
|
+
b: z.number().describe('Second number'),
|
|
329
|
+
},
|
|
330
|
+
outputSchema: 'number',
|
|
331
|
+
})((input) => {
|
|
332
|
+
return input.a + input.b;
|
|
333
|
+
});
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
The callback receives `(input, ctx)` where `ctx` provides access to the same context methods (`get`, `tryGet`, `fail`, `mark`, `fetch`, `notify`, `respondProgress`).
|
|
337
|
+
|
|
338
|
+
Register it the same way as a class tool: `tools: [AddNumbers]`.
|
|
339
|
+
|
|
340
|
+
## Remote and ESM Loading
|
|
341
|
+
|
|
342
|
+
Load tools from external modules or remote URLs without importing them directly.
|
|
343
|
+
|
|
344
|
+
**ESM loading** -- load a tool from an ES module:
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
const RemoteTool = Tool.esm('@my-org/tools@^1.0.0', 'MyTool', {
|
|
348
|
+
description: 'A tool loaded from an ES module',
|
|
349
|
+
});
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
**Remote loading** -- load a tool from a remote URL:
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
const CloudTool = Tool.remote('https://example.com/tools/cloud-tool', 'CloudTool', {
|
|
356
|
+
description: 'A tool loaded from a remote server',
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Both return values that can be registered in `tools: [RemoteTool, CloudTool]`.
|
|
361
|
+
|
|
362
|
+
## Registration
|
|
363
|
+
|
|
364
|
+
Add tool classes (or function-style tools) to the `tools` array in `@FrontMcp` or `@App`.
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
import { FrontMcp, App } from '@frontmcp/sdk';
|
|
368
|
+
|
|
369
|
+
@App({
|
|
370
|
+
name: 'my-app',
|
|
371
|
+
tools: [GreetUserTool, SearchDocumentsTool, AddNumbers],
|
|
372
|
+
})
|
|
373
|
+
class MyApp {}
|
|
374
|
+
|
|
375
|
+
@FrontMcp({
|
|
376
|
+
info: { name: 'my-server', version: '1.0.0' },
|
|
377
|
+
apps: [MyApp],
|
|
378
|
+
tools: [RunQueryTool], // can also register tools directly on the server
|
|
379
|
+
})
|
|
380
|
+
class MyServer {}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
## Nx Generator
|
|
384
|
+
|
|
385
|
+
Scaffold a new tool using the Nx generator:
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
nx generate @frontmcp/nx:tool
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
This creates the tool file, spec file, and updates barrel exports.
|
|
392
|
+
|
|
393
|
+
## Rate Limiting and Concurrency
|
|
394
|
+
|
|
395
|
+
Protect tools with throttling controls:
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
@Tool({
|
|
399
|
+
name: 'expensive_operation',
|
|
400
|
+
description: 'An expensive operation that should be rate limited',
|
|
401
|
+
inputSchema: {
|
|
402
|
+
data: z.string(),
|
|
403
|
+
},
|
|
404
|
+
rateLimit: { maxRequests: 10, windowMs: 60_000 },
|
|
405
|
+
concurrency: { maxConcurrent: 2 },
|
|
406
|
+
timeout: { executeMs: 30_000 },
|
|
407
|
+
})
|
|
408
|
+
class ExpensiveOperationTool extends ToolContext {
|
|
409
|
+
async execute(input: { data: string }) {
|
|
410
|
+
// At most 10 calls per minute, 2 concurrent, 30s timeout
|
|
411
|
+
return await this.heavyComputation(input.data);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
private async heavyComputation(data: string) {
|
|
415
|
+
return data;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
```
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Output Schema Types Reference
|
|
2
|
+
|
|
3
|
+
All supported `outputSchema` types for `@Tool`:
|
|
4
|
+
|
|
5
|
+
## Zod Raw Shapes (Recommended)
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
outputSchema: {
|
|
9
|
+
name: z.string(),
|
|
10
|
+
count: z.number(),
|
|
11
|
+
items: z.array(z.string()),
|
|
12
|
+
}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Produces structured JSON output. **Best practice for CodeCall compatibility and data leak prevention.**
|
|
16
|
+
|
|
17
|
+
## Zod Schemas
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
outputSchema: z.object({ result: z.number() })
|
|
21
|
+
outputSchema: z.array(z.string())
|
|
22
|
+
outputSchema: z.union([z.string(), z.number()])
|
|
23
|
+
outputSchema: z.discriminatedUnion('type', [...])
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Primitive Literals
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
outputSchema: 'string'; // Returns plain text
|
|
30
|
+
outputSchema: 'number'; // Returns a number
|
|
31
|
+
outputSchema: 'boolean'; // Returns true/false
|
|
32
|
+
outputSchema: 'date'; // Returns an ISO date string
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Media Types
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
outputSchema: 'image'; // Returns base64 image data
|
|
39
|
+
outputSchema: 'audio'; // Returns base64 audio data
|
|
40
|
+
outputSchema: 'resource'; // Returns a resource content
|
|
41
|
+
outputSchema: 'resource_link'; // Returns a resource URI link
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Multi-Content Arrays
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
outputSchema: ['string', 'image']; // Returns text + image content
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## No OutputSchema (Not Recommended)
|
|
51
|
+
|
|
52
|
+
When `outputSchema` is omitted, the tool returns unvalidated content. This:
|
|
53
|
+
|
|
54
|
+
- Risks leaking internal fields to the client
|
|
55
|
+
- Prevents CodeCall from inferring return types
|
|
56
|
+
- Loses compile-time type checking on `Out` generic
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Tool Annotations Reference
|
|
2
|
+
|
|
3
|
+
Annotations provide hints to MCP clients about tool behavior:
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
@Tool({
|
|
7
|
+
name: 'my_tool',
|
|
8
|
+
inputSchema: { ... },
|
|
9
|
+
annotations: {
|
|
10
|
+
title: 'My Tool', // Human-readable display name
|
|
11
|
+
readOnlyHint: true, // Tool only reads data, no side effects
|
|
12
|
+
destructiveHint: false, // Tool does NOT destroy/delete data
|
|
13
|
+
idempotentHint: true, // Safe to call multiple times with same input
|
|
14
|
+
openWorldHint: false, // Tool does NOT interact with external world
|
|
15
|
+
},
|
|
16
|
+
})
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Fields
|
|
20
|
+
|
|
21
|
+
| Field | Type | Default | Description |
|
|
22
|
+
| ----------------- | --------- | ------- | ---------------------------------- |
|
|
23
|
+
| `title` | `string` | — | Human-friendly display name |
|
|
24
|
+
| `readOnlyHint` | `boolean` | `false` | Tool only reads, no mutations |
|
|
25
|
+
| `destructiveHint` | `boolean` | `true` | Tool may delete/overwrite data |
|
|
26
|
+
| `idempotentHint` | `boolean` | `false` | Repeated calls produce same result |
|
|
27
|
+
| `openWorldHint` | `boolean` | `true` | Tool may access external services |
|
|
28
|
+
|
|
29
|
+
## Usage Guidance
|
|
30
|
+
|
|
31
|
+
- Set `readOnlyHint: true` for query/lookup tools
|
|
32
|
+
- Set `destructiveHint: true` for delete/overwrite operations (triggers client warnings)
|
|
33
|
+
- Set `idempotentHint: true` for safe-to-retry tools
|
|
34
|
+
- Set `openWorldHint: false` for tools that only access local data
|