@frontmcp/skills 1.2.0 → 1.3.0
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/catalog/frontmcp-config/SKILL.md +5 -5
- package/catalog/frontmcp-config/references/configure-deployment-targets.md +84 -1
- package/catalog/frontmcp-config/references/configure-http.md +57 -2
- package/catalog/frontmcp-config/references/configure-session.md +14 -7
- package/catalog/frontmcp-deployment/SKILL.md +3 -3
- package/catalog/frontmcp-deployment/references/build-for-mcpb.md +1 -1
- package/catalog/frontmcp-deployment/references/mcp-client-integration.md +107 -0
- package/catalog/frontmcp-development/SKILL.md +7 -7
- package/catalog/frontmcp-development/examples/create-provider/basic-database-provider.md +14 -0
- package/catalog/frontmcp-development/examples/create-provider/config-and-api-providers.md +85 -9
- package/catalog/frontmcp-development/examples/create-tool/basic-class-tool.md +30 -11
- package/catalog/frontmcp-development/examples/create-tool/tool-with-di-and-errors.md +62 -14
- package/catalog/frontmcp-development/examples/create-tool/tool-with-rate-limiting-and-progress.md +30 -12
- package/catalog/frontmcp-development/references/create-job.md +45 -11
- package/catalog/frontmcp-development/references/create-provider.md +80 -8
- package/catalog/frontmcp-development/references/create-skill-with-tools.md +31 -0
- package/catalog/frontmcp-development/references/create-skill.md +45 -0
- package/catalog/frontmcp-development/references/create-tool.md +124 -46
- package/catalog/frontmcp-guides/SKILL.md +7 -7
- package/catalog/frontmcp-observability/SKILL.md +15 -7
- package/catalog/frontmcp-observability/examples/metrics-endpoint/enable-metrics-endpoint.md +77 -0
- package/catalog/frontmcp-observability/references/metrics-endpoint.md +161 -0
- package/catalog/frontmcp-setup/SKILL.md +11 -11
- package/catalog/frontmcp-setup/examples/frontmcp-skills-usage/install-and-search-skills.md +19 -1
- package/catalog/frontmcp-setup/references/frontmcp-skills-usage.md +260 -19
- package/catalog/frontmcp-setup/references/setup-project.md +29 -0
- package/catalog/frontmcp-setup/references/setup-sqlite.md +68 -9
- package/catalog/frontmcp-testing/SKILL.md +17 -17
- package/catalog/skills-manifest.json +32 -6
- package/package.json +1 -1
|
@@ -2,18 +2,40 @@
|
|
|
2
2
|
name: config-and-api-providers
|
|
3
3
|
reference: create-provider
|
|
4
4
|
level: intermediate
|
|
5
|
-
description: 'A configuration provider
|
|
5
|
+
description: 'A configuration provider and an HTTP API client provider, organized as one folder per provider with co-located specs and barrels.'
|
|
6
6
|
tags: [development, provider, config, api, providers]
|
|
7
7
|
features:
|
|
8
8
|
- 'A configuration provider using `readonly` properties from environment variables (sync construction)'
|
|
9
9
|
- 'An API client provider that reads credentials in the constructor (no `onInit` — `@Provider` has no lifecycle hooks)'
|
|
10
|
+
- 'Folder-per-provider layout (`src/apps/main/providers/<slug>/`) with a barrel `index.ts` and a co-located `.provider.spec.ts`'
|
|
11
|
+
- 'Top-level `src/apps/main/providers/index.ts` barrel re-exporting each provider folder'
|
|
10
12
|
- 'Registering providers at `@FrontMcp` level for server-wide sharing across all apps'
|
|
11
13
|
- 'Separating token definitions from provider implementations for clean dependency boundaries'
|
|
12
14
|
---
|
|
13
15
|
|
|
14
16
|
# Configuration and API Client Providers
|
|
15
17
|
|
|
16
|
-
A configuration provider
|
|
18
|
+
A configuration provider and an HTTP API client provider, organized as one folder per provider with co-located specs and barrels.
|
|
19
|
+
|
|
20
|
+
## File layout
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
src/apps/main/
|
|
24
|
+
├── tokens.ts # shared token + interface definitions
|
|
25
|
+
├── index.ts # @App / @FrontMcp registration
|
|
26
|
+
└── providers/
|
|
27
|
+
├── index.ts # top-level barrel
|
|
28
|
+
├── config/
|
|
29
|
+
│ ├── index.ts # barrel: ConfigProvider
|
|
30
|
+
│ ├── config.provider.ts # @Provider class
|
|
31
|
+
│ └── config.provider.spec.ts # tests
|
|
32
|
+
└── api-client/
|
|
33
|
+
├── index.ts # barrel: ApiClientProvider
|
|
34
|
+
├── api-client.provider.ts # @Provider class
|
|
35
|
+
└── api-client.provider.spec.ts # tests
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Each provider lives in its own subfolder with a barrel — cross-provider imports go through the barrel (`from '../config'`), never reaching into another provider's implementation file.
|
|
17
39
|
|
|
18
40
|
## Code
|
|
19
41
|
|
|
@@ -38,13 +60,13 @@ export const API_TOKEN: Token<ApiClient> = Symbol('ApiClient');
|
|
|
38
60
|
```
|
|
39
61
|
|
|
40
62
|
```typescript
|
|
41
|
-
// src/apps/main/providers/config.provider.ts
|
|
63
|
+
// src/apps/main/providers/config/config.provider.ts
|
|
42
64
|
import { Provider } from '@frontmcp/sdk';
|
|
43
65
|
|
|
44
|
-
import type { AppConfig } from '
|
|
66
|
+
import type { AppConfig } from '../../tokens';
|
|
45
67
|
|
|
46
68
|
@Provider({ name: 'ConfigProvider' })
|
|
47
|
-
class ConfigProvider implements AppConfig {
|
|
69
|
+
export class ConfigProvider implements AppConfig {
|
|
48
70
|
readonly apiBaseUrl = process.env.API_BASE_URL ?? 'https://api.example.com';
|
|
49
71
|
readonly maxRetries = Number(process.env.MAX_RETRIES ?? 3);
|
|
50
72
|
readonly debug = process.env.DEBUG === 'true';
|
|
@@ -52,13 +74,31 @@ class ConfigProvider implements AppConfig {
|
|
|
52
74
|
```
|
|
53
75
|
|
|
54
76
|
```typescript
|
|
55
|
-
// src/apps/main/providers/
|
|
77
|
+
// src/apps/main/providers/config/index.ts
|
|
78
|
+
export { ConfigProvider } from './config.provider';
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// src/apps/main/providers/config/config.provider.spec.ts
|
|
83
|
+
import { ConfigProvider } from './config.provider';
|
|
84
|
+
|
|
85
|
+
describe('ConfigProvider', () => {
|
|
86
|
+
it('reads apiBaseUrl from env with a default fallback', () => {
|
|
87
|
+
const provider = new ConfigProvider();
|
|
88
|
+
expect(provider.apiBaseUrl).toBeDefined();
|
|
89
|
+
expect(typeof provider.apiBaseUrl).toBe('string');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
// src/apps/main/providers/api-client/api-client.provider.ts
|
|
56
96
|
import { Provider } from '@frontmcp/sdk';
|
|
57
97
|
|
|
58
|
-
import type { ApiClient } from '
|
|
98
|
+
import type { ApiClient } from '../../tokens';
|
|
59
99
|
|
|
60
100
|
@Provider({ name: 'ApiClientProvider' })
|
|
61
|
-
class ApiClientProvider implements ApiClient {
|
|
101
|
+
export class ApiClientProvider implements ApiClient {
|
|
62
102
|
// `@Provider` has no `onInit` lifecycle hook — read env in the constructor.
|
|
63
103
|
// First instantiation throws synchronously on missing config (fail fast).
|
|
64
104
|
private readonly baseUrl: string;
|
|
@@ -92,10 +132,44 @@ class ApiClientProvider implements ApiClient {
|
|
|
92
132
|
}
|
|
93
133
|
```
|
|
94
134
|
|
|
135
|
+
```typescript
|
|
136
|
+
// src/apps/main/providers/api-client/index.ts
|
|
137
|
+
export { ApiClientProvider } from './api-client.provider';
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// src/apps/main/providers/api-client/api-client.provider.spec.ts
|
|
142
|
+
import { ApiClientProvider } from './api-client.provider';
|
|
143
|
+
|
|
144
|
+
describe('ApiClientProvider', () => {
|
|
145
|
+
const origEnv = { ...process.env };
|
|
146
|
+
afterEach(() => {
|
|
147
|
+
process.env = { ...origEnv };
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('throws fast when API_URL or API_KEY is missing', () => {
|
|
151
|
+
delete process.env.API_URL;
|
|
152
|
+
delete process.env.API_KEY;
|
|
153
|
+
expect(() => new ApiClientProvider()).toThrow(/API_URL and API_KEY/);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// src/apps/main/providers/index.ts
|
|
160
|
+
// Top-level barrel — re-exports each provider folder so importers can do
|
|
161
|
+
// `import { ConfigProvider, ApiClientProvider } from './providers'`.
|
|
162
|
+
export * from './config';
|
|
163
|
+
export * from './api-client';
|
|
164
|
+
```
|
|
165
|
+
|
|
95
166
|
```typescript
|
|
96
167
|
// src/index.ts
|
|
97
168
|
import { FrontMcp } from '@frontmcp/sdk';
|
|
98
169
|
|
|
170
|
+
import { MainApp } from './apps/main';
|
|
171
|
+
import { ApiClientProvider, ConfigProvider } from './apps/main/providers';
|
|
172
|
+
|
|
99
173
|
@FrontMcp({
|
|
100
174
|
info: { name: 'my-server', version: '1.0.0' },
|
|
101
175
|
apps: [MainApp],
|
|
@@ -108,9 +182,11 @@ class MyServer {}
|
|
|
108
182
|
|
|
109
183
|
- A configuration provider using `readonly` properties from environment variables (sync construction)
|
|
110
184
|
- An API client provider that reads credentials in the constructor (no `onInit` — `@Provider` has no lifecycle hooks)
|
|
185
|
+
- Folder-per-provider layout (`src/apps/main/providers/<slug>/`) with a barrel `index.ts` and a co-located `.provider.spec.ts`
|
|
186
|
+
- Top-level `src/apps/main/providers/index.ts` barrel re-exporting each provider folder
|
|
111
187
|
- Registering providers at `@FrontMcp` level for server-wide sharing across all apps
|
|
112
188
|
- Separating token definitions from provider implementations for clean dependency boundaries
|
|
113
189
|
|
|
114
190
|
## Related
|
|
115
191
|
|
|
116
|
-
- See `create-provider` for cache providers, lifecycle details, and the `tryGet()` safe access pattern
|
|
192
|
+
- See `create-provider` for the [File Layout](../../references/create-provider.md#file-layout) section, cache providers, lifecycle details, and the `tryGet()` safe access pattern
|
|
@@ -2,37 +2,54 @@
|
|
|
2
2
|
name: basic-class-tool
|
|
3
3
|
reference: create-tool
|
|
4
4
|
level: basic
|
|
5
|
-
description: 'A minimal tool using the class-based pattern with Zod input validation and
|
|
5
|
+
description: 'A minimal tool using the class-based pattern with Zod input validation, output schema, and types derived from the schemas.'
|
|
6
6
|
tags: [development, tool, class]
|
|
7
7
|
features:
|
|
8
8
|
- 'Extending `ToolContext` and implementing the `execute()` method'
|
|
9
9
|
- 'Using a Zod raw shape for `inputSchema` (not wrapped in `z.object()`)'
|
|
10
10
|
- 'Defining `outputSchema` to validate and restrict output fields'
|
|
11
|
+
- 'Deriving `execute()` input/output types from the schemas via `ToolInputOf<>` / `ToolOutputOf<>` (no duplicated annotation)'
|
|
12
|
+
- 'Co-locating schema and tool in sibling files (`<name>.schema.ts` / `<name>.tool.ts`)'
|
|
11
13
|
- 'Registering the tool in an `@App` via the `tools` array'
|
|
12
14
|
---
|
|
13
15
|
|
|
14
16
|
# Basic Class-Based Tool
|
|
15
17
|
|
|
16
|
-
A minimal tool using the class-based pattern with Zod input validation and
|
|
18
|
+
A minimal tool using the class-based pattern with Zod input validation, output schema, and types derived from the schemas.
|
|
17
19
|
|
|
18
20
|
## Code
|
|
19
21
|
|
|
22
|
+
```typescript
|
|
23
|
+
// src/apps/main/tools/greet-user.schema.ts
|
|
24
|
+
import { ToolInputOf, ToolOutputOf, z } from '@frontmcp/sdk';
|
|
25
|
+
|
|
26
|
+
// Hoist only the schemas — keep `@Tool({…})` self-contained in the tool file.
|
|
27
|
+
export const inputSchema = {
|
|
28
|
+
name: z.string().describe('The name of the user to greet'),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const outputSchema = {
|
|
32
|
+
greeting: z.string(),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type GreetUserInput = ToolInputOf<{ inputSchema: typeof inputSchema }>;
|
|
36
|
+
export type GreetUserOutput = ToolOutputOf<{ outputSchema: typeof outputSchema }>;
|
|
37
|
+
```
|
|
38
|
+
|
|
20
39
|
```typescript
|
|
21
40
|
// src/apps/main/tools/greet-user.tool.ts
|
|
22
|
-
import { Tool, ToolContext
|
|
41
|
+
import { Tool, ToolContext } from '@frontmcp/sdk';
|
|
42
|
+
|
|
43
|
+
import { inputSchema, outputSchema, type GreetUserInput, type GreetUserOutput } from './greet-user.schema';
|
|
23
44
|
|
|
24
45
|
@Tool({
|
|
25
46
|
name: 'greet_user',
|
|
26
47
|
description: 'Greet a user by name',
|
|
27
|
-
inputSchema
|
|
28
|
-
|
|
29
|
-
},
|
|
30
|
-
outputSchema: {
|
|
31
|
-
greeting: z.string(),
|
|
32
|
-
},
|
|
48
|
+
inputSchema,
|
|
49
|
+
outputSchema,
|
|
33
50
|
})
|
|
34
51
|
class GreetUserTool extends ToolContext {
|
|
35
|
-
async execute(input:
|
|
52
|
+
async execute(input: GreetUserInput): Promise<GreetUserOutput> {
|
|
36
53
|
return { greeting: `Hello, ${input.name}!` };
|
|
37
54
|
}
|
|
38
55
|
}
|
|
@@ -54,8 +71,10 @@ class MainApp {}
|
|
|
54
71
|
- Extending `ToolContext` and implementing the `execute()` method
|
|
55
72
|
- Using a Zod raw shape for `inputSchema` (not wrapped in `z.object()`)
|
|
56
73
|
- Defining `outputSchema` to validate and restrict output fields
|
|
74
|
+
- Deriving `execute()` input/output types from the schemas via `ToolInputOf<>` / `ToolOutputOf<>` (no duplicated annotation)
|
|
75
|
+
- Co-locating schema and tool in sibling files (`<name>.schema.ts` / `<name>.tool.ts`)
|
|
57
76
|
- Registering the tool in an `@App` via the `tools` array
|
|
58
77
|
|
|
59
78
|
## Related
|
|
60
79
|
|
|
61
|
-
- See `create-tool` for the full API reference including annotations, rate limiting, and elicitation
|
|
80
|
+
- See `create-tool` for the full API reference including the derive-types pattern, file layouts, annotations, rate limiting, and elicitation
|
|
@@ -2,18 +2,32 @@
|
|
|
2
2
|
name: tool-with-di-and-errors
|
|
3
3
|
reference: create-tool
|
|
4
4
|
level: intermediate
|
|
5
|
-
description: 'A tool that resolves a database service via DI and uses `this.fail()` for business-logic errors.'
|
|
5
|
+
description: 'A tool that resolves a database service via DI and uses `this.fail()` for business-logic errors, with `execute()` types derived from the schemas.'
|
|
6
6
|
tags: [development, database, tool, di, errors]
|
|
7
7
|
features:
|
|
8
8
|
- 'Defining a typed DI token with `Token<T>` and resolving it via `this.get()`'
|
|
9
9
|
- 'Using `this.fail()` with `ResourceNotFoundError` for MCP-compliant error responses'
|
|
10
10
|
- 'Letting infrastructure errors (database failures) propagate naturally to the framework'
|
|
11
|
+
- 'Deriving `execute()` types from `inputSchema` / `outputSchema` via `ToolInputOf<>` / `ToolOutputOf<>`'
|
|
12
|
+
- 'Folder-per-tool layout (`tools/delete-record/{schema,tool,index}.ts`) for tools with local helpers or error types'
|
|
11
13
|
- 'Registering both the provider and tool in the same `@App`'
|
|
12
14
|
---
|
|
13
15
|
|
|
14
16
|
# Tool with Dependency Injection and Error Handling
|
|
15
17
|
|
|
16
|
-
A tool that resolves a database service via DI and uses `this.fail()` for business-logic errors.
|
|
18
|
+
A tool that resolves a database service via DI and uses `this.fail()` for business-logic errors, with `execute()` types derived from the schemas.
|
|
19
|
+
|
|
20
|
+
## File layout
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
src/apps/main/
|
|
24
|
+
├── tokens.ts # shared DI tokens
|
|
25
|
+
└── tools/
|
|
26
|
+
└── delete-record/
|
|
27
|
+
├── delete-record.schema.ts # input/output schemas + derived types
|
|
28
|
+
├── delete-record.tool.ts # @Tool class, execute()
|
|
29
|
+
└── index.ts # barrel re-export
|
|
30
|
+
```
|
|
17
31
|
|
|
18
32
|
## Code
|
|
19
33
|
|
|
@@ -29,23 +43,38 @@ export const DATABASE: Token<DatabaseService> = Symbol('database');
|
|
|
29
43
|
```
|
|
30
44
|
|
|
31
45
|
```typescript
|
|
32
|
-
// src/apps/main/tools/delete-record.
|
|
33
|
-
import {
|
|
46
|
+
// src/apps/main/tools/delete-record/delete-record.schema.ts
|
|
47
|
+
import { ToolInputOf, ToolOutputOf, z } from '@frontmcp/sdk';
|
|
48
|
+
|
|
49
|
+
// Only the schemas live here — decorator config (`name`, `description`, …)
|
|
50
|
+
// stays inside @Tool({…}) in the tool file.
|
|
51
|
+
export const inputSchema = {
|
|
52
|
+
id: z.string().uuid().describe('Record UUID'),
|
|
53
|
+
};
|
|
34
54
|
|
|
35
|
-
|
|
55
|
+
export const outputSchema = {
|
|
56
|
+
message: z.string(),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type DeleteRecordInput = ToolInputOf<{ inputSchema: typeof inputSchema }>;
|
|
60
|
+
export type DeleteRecordOutput = ToolOutputOf<{ outputSchema: typeof outputSchema }>;
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// src/apps/main/tools/delete-record/delete-record.tool.ts
|
|
65
|
+
import { ResourceNotFoundError, Tool, ToolContext } from '@frontmcp/sdk';
|
|
66
|
+
|
|
67
|
+
import { DATABASE } from '../../tokens';
|
|
68
|
+
import { inputSchema, outputSchema, type DeleteRecordInput, type DeleteRecordOutput } from './delete-record.schema';
|
|
36
69
|
|
|
37
70
|
@Tool({
|
|
38
71
|
name: 'delete_record',
|
|
39
72
|
description: 'Delete a record by ID',
|
|
40
|
-
inputSchema
|
|
41
|
-
|
|
42
|
-
},
|
|
43
|
-
outputSchema: {
|
|
44
|
-
message: z.string(),
|
|
45
|
-
},
|
|
73
|
+
inputSchema,
|
|
74
|
+
outputSchema,
|
|
46
75
|
})
|
|
47
|
-
class DeleteRecordTool extends ToolContext {
|
|
48
|
-
async execute(input:
|
|
76
|
+
export class DeleteRecordTool extends ToolContext {
|
|
77
|
+
async execute(input: DeleteRecordInput): Promise<DeleteRecordOutput> {
|
|
49
78
|
const db = this.get(DATABASE);
|
|
50
79
|
const rows = await db.query('SELECT * FROM records WHERE id = $1', [input.id]);
|
|
51
80
|
|
|
@@ -59,10 +88,27 @@ class DeleteRecordTool extends ToolContext {
|
|
|
59
88
|
}
|
|
60
89
|
```
|
|
61
90
|
|
|
91
|
+
```typescript
|
|
92
|
+
// src/apps/main/tools/delete-record/index.ts
|
|
93
|
+
export { DeleteRecordTool } from './delete-record.tool';
|
|
94
|
+
export {
|
|
95
|
+
inputSchema as deleteRecordInputSchema,
|
|
96
|
+
outputSchema as deleteRecordOutputSchema,
|
|
97
|
+
type DeleteRecordInput,
|
|
98
|
+
type DeleteRecordOutput,
|
|
99
|
+
} from './delete-record.schema';
|
|
100
|
+
```
|
|
101
|
+
|
|
62
102
|
```typescript
|
|
63
103
|
// src/apps/main/index.ts
|
|
64
104
|
import { App } from '@frontmcp/sdk';
|
|
65
105
|
|
|
106
|
+
// `DatabaseProvider` is the singleton that backs `DATABASE` — see the
|
|
107
|
+
// `create-provider` skill for an `AsyncProvider({ useFactory })` reference
|
|
108
|
+
// implementation that opens a connection pool at startup.
|
|
109
|
+
import { DatabaseProvider } from './providers/database';
|
|
110
|
+
import { DeleteRecordTool } from './tools/delete-record';
|
|
111
|
+
|
|
66
112
|
@App({
|
|
67
113
|
name: 'main',
|
|
68
114
|
providers: [DatabaseProvider],
|
|
@@ -76,9 +122,11 @@ class MainApp {}
|
|
|
76
122
|
- Defining a typed DI token with `Token<T>` and resolving it via `this.get()`
|
|
77
123
|
- Using `this.fail()` with `ResourceNotFoundError` for MCP-compliant error responses
|
|
78
124
|
- Letting infrastructure errors (database failures) propagate naturally to the framework
|
|
125
|
+
- Deriving `execute()` types from `inputSchema` / `outputSchema` via `ToolInputOf<>` / `ToolOutputOf<>`
|
|
126
|
+
- Folder-per-tool layout (`tools/delete-record/{schema,tool,index}.ts`) for tools with local helpers or error types
|
|
79
127
|
- Registering both the provider and tool in the same `@App`
|
|
80
128
|
|
|
81
129
|
## Related
|
|
82
130
|
|
|
83
|
-
- See `create-tool` for all context methods and error handling
|
|
131
|
+
- See `create-tool` for all context methods, the derive-types pattern, and error handling
|
|
84
132
|
- See `create-provider` for how to implement the `DatabaseProvider` class
|
package/catalog/frontmcp-development/examples/create-tool/tool-with-rate-limiting-and-progress.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: tool-with-rate-limiting-and-progress
|
|
3
3
|
reference: create-tool
|
|
4
4
|
level: advanced
|
|
5
|
-
description: 'A batch processing tool that uses rate limiting, concurrency control, progress notifications, and annotations.'
|
|
5
|
+
description: 'A batch processing tool that uses rate limiting, concurrency control, progress notifications, and annotations, with `execute()` types derived from the schemas.'
|
|
6
6
|
tags: [development, throttle, tool, rate, limiting, progress]
|
|
7
7
|
features:
|
|
8
8
|
- 'Configuring `rateLimit`, `concurrency`, and `timeout` for throttling protection'
|
|
@@ -10,28 +10,45 @@ features:
|
|
|
10
10
|
- 'Using `this.mark(stage)` for execution stage tracking and debugging'
|
|
11
11
|
- 'Sending log-level notifications with `this.notify(message, level)`'
|
|
12
12
|
- 'Setting tool `annotations` to communicate behavioral hints to clients'
|
|
13
|
+
- 'Deriving `execute()` types from the schemas via `ToolInputOf<>` / `ToolOutputOf<>`'
|
|
13
14
|
---
|
|
14
15
|
|
|
15
16
|
# Tool with Rate Limiting, Progress, and Annotations
|
|
16
17
|
|
|
17
|
-
A batch processing tool that uses rate limiting, concurrency control, progress notifications, and annotations.
|
|
18
|
+
A batch processing tool that uses rate limiting, concurrency control, progress notifications, and annotations, with `execute()` types derived from the schemas.
|
|
18
19
|
|
|
19
20
|
## Code
|
|
20
21
|
|
|
22
|
+
```typescript
|
|
23
|
+
// src/apps/main/tools/batch-process.schema.ts
|
|
24
|
+
import { ToolInputOf, ToolOutputOf, z } from '@frontmcp/sdk';
|
|
25
|
+
|
|
26
|
+
// Schemas only — `annotations`, `rateLimit`, `concurrency`, `timeout` etc.
|
|
27
|
+
// stay inside @Tool({…}) in the tool file.
|
|
28
|
+
export const inputSchema = {
|
|
29
|
+
items: z.array(z.string()).min(1).describe('Items to process'),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const outputSchema = {
|
|
33
|
+
processed: z.number(),
|
|
34
|
+
results: z.array(z.string()),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type BatchProcessInput = ToolInputOf<{ inputSchema: typeof inputSchema }>;
|
|
38
|
+
export type BatchProcessOutput = ToolOutputOf<{ outputSchema: typeof outputSchema }>;
|
|
39
|
+
```
|
|
40
|
+
|
|
21
41
|
```typescript
|
|
22
42
|
// src/apps/main/tools/batch-process.tool.ts
|
|
23
|
-
import { Tool, ToolContext
|
|
43
|
+
import { Tool, ToolContext } from '@frontmcp/sdk';
|
|
44
|
+
|
|
45
|
+
import { inputSchema, outputSchema, type BatchProcessInput, type BatchProcessOutput } from './batch-process.schema';
|
|
24
46
|
|
|
25
47
|
@Tool({
|
|
26
48
|
name: 'batch_process',
|
|
27
49
|
description: 'Process a batch of items with progress tracking',
|
|
28
|
-
inputSchema
|
|
29
|
-
|
|
30
|
-
},
|
|
31
|
-
outputSchema: {
|
|
32
|
-
processed: z.number(),
|
|
33
|
-
results: z.array(z.string()),
|
|
34
|
-
},
|
|
50
|
+
inputSchema,
|
|
51
|
+
outputSchema,
|
|
35
52
|
annotations: {
|
|
36
53
|
title: 'Batch Processor',
|
|
37
54
|
readOnlyHint: false,
|
|
@@ -43,7 +60,7 @@ import { Tool, ToolContext, z } from '@frontmcp/sdk';
|
|
|
43
60
|
timeout: { executeMs: 30_000 },
|
|
44
61
|
})
|
|
45
62
|
class BatchProcessTool extends ToolContext {
|
|
46
|
-
async execute(input:
|
|
63
|
+
async execute(input: BatchProcessInput): Promise<BatchProcessOutput> {
|
|
47
64
|
this.mark('validation');
|
|
48
65
|
if (input.items.some((item) => item.trim() === '')) {
|
|
49
66
|
this.fail(new Error('Items must not be empty strings'));
|
|
@@ -86,7 +103,8 @@ class MainApp {}
|
|
|
86
103
|
- Using `this.mark(stage)` for execution stage tracking and debugging
|
|
87
104
|
- Sending log-level notifications with `this.notify(message, level)`
|
|
88
105
|
- Setting tool `annotations` to communicate behavioral hints to clients
|
|
106
|
+
- Deriving `execute()` types from the schemas via `ToolInputOf<>` / `ToolOutputOf<>`
|
|
89
107
|
|
|
90
108
|
## Related
|
|
91
109
|
|
|
92
|
-
- See `create-tool` for
|
|
110
|
+
- See `create-tool` for the full derive-types pattern, annotation fields, elicitation, and auth provider patterns
|
|
@@ -418,7 +418,9 @@ class DataApp {}
|
|
|
418
418
|
|
|
419
419
|
### Enabling the Jobs System
|
|
420
420
|
|
|
421
|
-
|
|
421
|
+
**Auto-enable (issue #408):** declaring any `@App({ jobs: [...] })` (or `workflows: [...]`) is enough — the jobs subsystem comes up with in-memory stores by default and the management tools (`execute_job`, `list_jobs`, `get_job_status`, `register_job`, `remove_job`) are registered automatically so agents can invoke them. No `@FrontMcp({ jobs: { enabled: true } })` is required for the happy path.
|
|
422
|
+
|
|
423
|
+
**When to configure `@FrontMcp({ jobs })` explicitly:** override the in-memory default with persistent storage (Redis recommended for multi-replica HA) so job state, progress, logs, and outputs survive retries and server restarts.
|
|
422
424
|
|
|
423
425
|
```typescript
|
|
424
426
|
import { FrontMcp } from '@frontmcp/sdk';
|
|
@@ -426,8 +428,9 @@ import { FrontMcp } from '@frontmcp/sdk';
|
|
|
426
428
|
@FrontMcp({
|
|
427
429
|
info: { name: 'my-server', version: '1.0.0' },
|
|
428
430
|
apps: [DataApp],
|
|
431
|
+
// `enabled: true` is implied by `@App({ jobs })`. Set explicitly only to
|
|
432
|
+
// configure store, or set to `false` to opt out and suppress the tools.
|
|
429
433
|
jobs: {
|
|
430
|
-
enabled: true,
|
|
431
434
|
store: {
|
|
432
435
|
redis: {
|
|
433
436
|
provider: 'redis',
|
|
@@ -441,7 +444,36 @@ import { FrontMcp } from '@frontmcp/sdk';
|
|
|
441
444
|
class MyServer {}
|
|
442
445
|
```
|
|
443
446
|
|
|
444
|
-
|
|
447
|
+
Setting `jobs: { enabled: false }` is an explicit opt-out — declared jobs will NOT be activated and the framework logs a warning so the configuration mismatch is loud.
|
|
448
|
+
|
|
449
|
+
### Calling Jobs from an Agent
|
|
450
|
+
|
|
451
|
+
Once jobs are registered, the SDK exposes five MCP tools (snake_case per ecosystem convention; hyphen aliases like `execute-job` keep working with a deprecation log line for one release):
|
|
452
|
+
|
|
453
|
+
| Tool | Purpose |
|
|
454
|
+
| ---------------- | ------------------------------------------------------------------------ |
|
|
455
|
+
| `list_jobs` | List registered jobs with optional `tags` / `labels` / `query` filters |
|
|
456
|
+
| `execute_job` | Execute a registered job by name (`{ name, input?, background? }`) |
|
|
457
|
+
| `get_job_status` | Get the run state for a `runId` returned by `execute_job` |
|
|
458
|
+
| `register_job` | Register a dynamic job at runtime (sandboxed; `hideFromDiscovery: true`) |
|
|
459
|
+
| `remove_job` | Remove a dynamic job by name (`hideFromDiscovery: true`) |
|
|
460
|
+
|
|
461
|
+
Workflows expose a parallel set: `list_workflows`, `execute_workflow`, `get_workflow_status`, `register_workflow`, `remove_workflow`.
|
|
462
|
+
|
|
463
|
+
For finer-grained control (e.g. omit `register_job` / `remove_job` in production), opt out of auto-registration by importing the tool classes manually:
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
import { App, ExecuteJobTool, GetJobStatusTool, ListJobsTool } from '@frontmcp/sdk';
|
|
467
|
+
|
|
468
|
+
@App({
|
|
469
|
+
name: 'data-app',
|
|
470
|
+
jobs: [GenerateReportJob],
|
|
471
|
+
tools: [ExecuteJobTool, ListJobsTool, GetJobStatusTool], // omit register/remove
|
|
472
|
+
})
|
|
473
|
+
class DataApp {}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
The same classes are also reachable via the subpath `@frontmcp/sdk/job/tools` and `@frontmcp/sdk/workflow/tools` for bundlers that prefer narrower imports.
|
|
445
477
|
|
|
446
478
|
## Nx Generator
|
|
447
479
|
|
|
@@ -603,7 +635,9 @@ class DataServer {}
|
|
|
603
635
|
|
|
604
636
|
### Runtime
|
|
605
637
|
|
|
606
|
-
- [ ]
|
|
638
|
+
- [ ] Jobs subsystem is active — either implicitly via `@App({ jobs: [...] })`
|
|
639
|
+
(auto-enable, issue #408) or explicitly via `@FrontMcp({ jobs: { store: { ... } } })`
|
|
640
|
+
when overriding the in-memory default
|
|
607
641
|
- [ ] Job executes and returns output matching `outputSchema`
|
|
608
642
|
- [ ] Progress is reported and queryable during execution
|
|
609
643
|
- [ ] Retry fires with correct backoff delays on transient failures
|
|
@@ -611,13 +645,13 @@ class DataServer {}
|
|
|
611
645
|
|
|
612
646
|
## Troubleshooting
|
|
613
647
|
|
|
614
|
-
| Problem | Cause
|
|
615
|
-
| -------------------------- |
|
|
616
|
-
| Job not activated | `jobs
|
|
617
|
-
| Job fails without retrying | No `retry` policy configured
|
|
618
|
-
| Progress not visible | Not calling `this.progress()` during execution
|
|
619
|
-
| Job times out unexpectedly | Default 5-minute timeout too short
|
|
620
|
-
| Permission denied error | User lacks required roles or scopes
|
|
648
|
+
| Problem | Cause | Solution |
|
|
649
|
+
| -------------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
650
|
+
| Job not activated | `@App` doesn't declare `jobs: [...]` AND `@FrontMcp({ jobs: { enabled: true } })` isn't set | Add the job class to `@App({ jobs: [...] })` (auto-enables) OR set `@FrontMcp({ jobs: { enabled: true, store: { ... } } })` |
|
|
651
|
+
| Job fails without retrying | No `retry` policy configured | Add `retry: { maxAttempts: 3, backoffMs: 2000 }` to `@Job` options |
|
|
652
|
+
| Progress not visible | Not calling `this.progress()` during execution | Add `this.progress(pct, total, message)` calls at each stage |
|
|
653
|
+
| Job times out unexpectedly | Default 5-minute timeout too short | Set `timeout` in `@Job` to a higher value (e.g., `600000` for 10 minutes) |
|
|
654
|
+
| Permission denied error | User lacks required roles or scopes | Verify user has one of the `roles` and all `scopes` defined in `permissions` |
|
|
621
655
|
|
|
622
656
|
## Examples
|
|
623
657
|
|