@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,437 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-resource
|
|
3
|
+
description: Create MCP resources and resource templates with URI-based access. Use when exposing data via URIs, creating resource templates, or serving dynamic content.
|
|
4
|
+
tags: [resources, mcp, uri, templates, decorator]
|
|
5
|
+
tools:
|
|
6
|
+
- name: create_resource
|
|
7
|
+
purpose: Scaffold a new resource or resource template class
|
|
8
|
+
parameters:
|
|
9
|
+
- name: name
|
|
10
|
+
description: Resource name in kebab-case
|
|
11
|
+
type: string
|
|
12
|
+
required: true
|
|
13
|
+
- name: type
|
|
14
|
+
description: Whether to create a static resource or resource template
|
|
15
|
+
type: string
|
|
16
|
+
required: false
|
|
17
|
+
examples:
|
|
18
|
+
- scenario: Create a static configuration resource
|
|
19
|
+
expected-outcome: Resource registered and readable via MCP at a fixed URI
|
|
20
|
+
- scenario: Create a resource template for user profiles
|
|
21
|
+
expected-outcome: Resource template with parameterized URI pattern
|
|
22
|
+
priority: 10
|
|
23
|
+
visibility: both
|
|
24
|
+
license: Apache-2.0
|
|
25
|
+
metadata:
|
|
26
|
+
docs: https://docs.agentfront.dev/frontmcp/servers/resources
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
# Creating MCP Resources
|
|
30
|
+
|
|
31
|
+
Resources expose data to AI clients through URI-based access following the MCP protocol. FrontMCP supports two kinds: **static resources** with fixed URIs (`@Resource`) and **resource templates** with parameterized URI patterns (`@ResourceTemplate`).
|
|
32
|
+
|
|
33
|
+
## When to Use @Resource vs @ResourceTemplate
|
|
34
|
+
|
|
35
|
+
Use `@Resource` when the data lives at a single, known URI (e.g., `config://app/settings`, `status://server`). Use `@ResourceTemplate` when you need a family of related resources identified by parameters in the URI (e.g., `users://{userId}/profile`, `repo://{owner}/{repo}/files/{path}`).
|
|
36
|
+
|
|
37
|
+
## Static Resources with @Resource
|
|
38
|
+
|
|
39
|
+
### Decorator Options
|
|
40
|
+
|
|
41
|
+
The `@Resource` decorator accepts:
|
|
42
|
+
|
|
43
|
+
- `name` (required) -- unique resource name
|
|
44
|
+
- `uri` (required) -- static URI with a valid scheme per RFC 3986
|
|
45
|
+
- `description` (optional) -- human-readable description
|
|
46
|
+
- `mimeType` (optional) -- MIME type of the resource content
|
|
47
|
+
|
|
48
|
+
### Class-Based Pattern
|
|
49
|
+
|
|
50
|
+
Create a class extending `ResourceContext` and implement `execute(uri, params)`. It must return a `ReadResourceResult`.
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { Resource, ResourceContext } from '@frontmcp/sdk';
|
|
54
|
+
import { ReadResourceResult } from '@frontmcp/protocol';
|
|
55
|
+
|
|
56
|
+
@Resource({
|
|
57
|
+
name: 'app-config',
|
|
58
|
+
uri: 'config://app/settings',
|
|
59
|
+
description: 'Current application configuration',
|
|
60
|
+
mimeType: 'application/json',
|
|
61
|
+
})
|
|
62
|
+
class AppConfigResource extends ResourceContext {
|
|
63
|
+
async execute(uri: string, params: Record<string, string>): Promise<ReadResourceResult> {
|
|
64
|
+
const config = {
|
|
65
|
+
version: '2.1.0',
|
|
66
|
+
environment: 'production',
|
|
67
|
+
features: { darkMode: true, notifications: true },
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
contents: [
|
|
72
|
+
{
|
|
73
|
+
uri,
|
|
74
|
+
mimeType: 'application/json',
|
|
75
|
+
text: JSON.stringify(config, null, 2),
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### ReadResourceResult Structure
|
|
84
|
+
|
|
85
|
+
The `ReadResourceResult` returned by `execute()` has this shape:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
interface ReadResourceResult {
|
|
89
|
+
contents: Array<{
|
|
90
|
+
uri: string;
|
|
91
|
+
mimeType?: string;
|
|
92
|
+
text?: string; // string content
|
|
93
|
+
blob?: string; // base64-encoded binary content
|
|
94
|
+
}>;
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Each content item has a `uri`, optional `mimeType`, and either `text` (string data) or `blob` (base64 binary data).
|
|
99
|
+
|
|
100
|
+
### Available Context Methods and Properties
|
|
101
|
+
|
|
102
|
+
`ResourceContext` extends `ExecutionContextBase`, providing:
|
|
103
|
+
|
|
104
|
+
**Methods:**
|
|
105
|
+
|
|
106
|
+
- `execute(uri, params)` -- the main method you implement
|
|
107
|
+
- `this.get(token)` -- resolve a dependency from DI (throws if not found)
|
|
108
|
+
- `this.tryGet(token)` -- resolve a dependency from DI (returns `undefined` if not found)
|
|
109
|
+
- `this.fail(err)` -- abort execution, triggers error flow (never returns)
|
|
110
|
+
- `this.mark(stage)` -- set active execution stage for debugging/tracking
|
|
111
|
+
- `this.fetch(input, init?)` -- HTTP fetch with context propagation
|
|
112
|
+
|
|
113
|
+
**Properties:**
|
|
114
|
+
|
|
115
|
+
- `this.metadata` -- resource metadata from the decorator
|
|
116
|
+
- `this.scope` -- the current scope instance
|
|
117
|
+
- `this.context` -- the execution context
|
|
118
|
+
|
|
119
|
+
### Simplified Return Values
|
|
120
|
+
|
|
121
|
+
FrontMCP automatically normalizes common return shapes into valid `ReadResourceResult` format:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
@Resource({
|
|
125
|
+
name: 'server-status',
|
|
126
|
+
uri: 'status://server',
|
|
127
|
+
mimeType: 'application/json',
|
|
128
|
+
})
|
|
129
|
+
class ServerStatusResource extends ResourceContext {
|
|
130
|
+
async execute(uri: string, params: Record<string, string>) {
|
|
131
|
+
// Return a plain object -- FrontMCP wraps it in { contents: [{ uri, text: JSON.stringify(...) }] }
|
|
132
|
+
return { status: 'healthy', uptime: process.uptime() };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Supported return shapes:
|
|
138
|
+
|
|
139
|
+
- **Full `ReadResourceResult`**: `{ contents: [...] }` -- passed through as-is
|
|
140
|
+
- **Array of content items**: each item with `text` or `blob` is treated as a content entry
|
|
141
|
+
- **Plain string**: wrapped into a single text content block
|
|
142
|
+
- **Plain object**: serialized with `JSON.stringify` into a single text content block
|
|
143
|
+
|
|
144
|
+
## Resource Templates with @ResourceTemplate
|
|
145
|
+
|
|
146
|
+
### Decorator Options
|
|
147
|
+
|
|
148
|
+
The `@ResourceTemplate` decorator accepts:
|
|
149
|
+
|
|
150
|
+
- `name` (required) -- unique resource template name
|
|
151
|
+
- `uriTemplate` (required) -- URI pattern with `{paramName}` placeholders (RFC 6570 style)
|
|
152
|
+
- `description` (optional) -- human-readable description
|
|
153
|
+
- `mimeType` (optional) -- MIME type of the resource content
|
|
154
|
+
|
|
155
|
+
### Class-Based Pattern
|
|
156
|
+
|
|
157
|
+
Use `@ResourceTemplate` with `uriTemplate` instead of `uri`. Type the `ResourceContext` generic parameter to get typed `params`.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { ResourceTemplate, ResourceContext } from '@frontmcp/sdk';
|
|
161
|
+
import { ReadResourceResult } from '@frontmcp/protocol';
|
|
162
|
+
|
|
163
|
+
@ResourceTemplate({
|
|
164
|
+
name: 'user-profile',
|
|
165
|
+
uriTemplate: 'users://{userId}/profile',
|
|
166
|
+
description: 'User profile by ID',
|
|
167
|
+
mimeType: 'application/json',
|
|
168
|
+
})
|
|
169
|
+
class UserProfileResource extends ResourceContext<{ userId: string }> {
|
|
170
|
+
async execute(uri: string, params: { userId: string }): Promise<ReadResourceResult> {
|
|
171
|
+
const user = await this.fetchUser(params.userId);
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
contents: [
|
|
175
|
+
{
|
|
176
|
+
uri,
|
|
177
|
+
mimeType: 'application/json',
|
|
178
|
+
text: JSON.stringify(user),
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private async fetchUser(userId: string) {
|
|
185
|
+
return { id: userId, name: 'Alice', email: 'alice@example.com' };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
When a client reads `users://u-123/profile`, the framework matches the template and passes `{ userId: 'u-123' }` as `params`.
|
|
191
|
+
|
|
192
|
+
### Templates with Multiple Parameters
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
@ResourceTemplate({
|
|
196
|
+
name: 'repo-file',
|
|
197
|
+
uriTemplate: 'repo://{owner}/{repo}/files/{path}',
|
|
198
|
+
description: 'File content from a repository',
|
|
199
|
+
mimeType: 'text/plain',
|
|
200
|
+
})
|
|
201
|
+
class RepoFileResource extends ResourceContext<{ owner: string; repo: string; path: string }> {
|
|
202
|
+
async execute(uri: string, params: { owner: string; repo: string; path: string }): Promise<ReadResourceResult> {
|
|
203
|
+
const content = await this.fetchFileContent(params.owner, params.repo, params.path);
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
contents: [
|
|
207
|
+
{
|
|
208
|
+
uri,
|
|
209
|
+
mimeType: this.metadata.mimeType ?? 'text/plain',
|
|
210
|
+
text: content,
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private async fetchFileContent(owner: string, repo: string, path: string): Promise<string> {
|
|
217
|
+
const response = await this.fetch(`https://api.github.com/repos/${owner}/${repo}/contents/${path}`);
|
|
218
|
+
const data = await response.json();
|
|
219
|
+
return Buffer.from(data.content, 'base64').toString('utf-8');
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Function-Style Builders
|
|
225
|
+
|
|
226
|
+
For simple cases, use `resource()` and `resourceTemplate()` function builders.
|
|
227
|
+
|
|
228
|
+
**Static resource:**
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
import { resource } from '@frontmcp/sdk';
|
|
232
|
+
|
|
233
|
+
const SystemInfo = resource({
|
|
234
|
+
name: 'system-info',
|
|
235
|
+
uri: 'system://info',
|
|
236
|
+
mimeType: 'application/json',
|
|
237
|
+
})((uri) => ({
|
|
238
|
+
contents: [
|
|
239
|
+
{
|
|
240
|
+
uri,
|
|
241
|
+
text: JSON.stringify({
|
|
242
|
+
platform: process.platform,
|
|
243
|
+
nodeVersion: process.version,
|
|
244
|
+
memoryUsage: process.memoryUsage(),
|
|
245
|
+
}),
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
}));
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Resource template:**
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
import { resourceTemplate } from '@frontmcp/sdk';
|
|
255
|
+
|
|
256
|
+
const LogFile = resourceTemplate({
|
|
257
|
+
name: 'log-file',
|
|
258
|
+
uriTemplate: 'logs://{date}/{level}',
|
|
259
|
+
mimeType: 'text/plain',
|
|
260
|
+
})((uri, params) => ({
|
|
261
|
+
contents: [
|
|
262
|
+
{
|
|
263
|
+
uri,
|
|
264
|
+
text: `Logs for ${params.date} at level ${params.level}`,
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
}));
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Register them the same way as class resources: `resources: [SystemInfo, LogFile]`.
|
|
271
|
+
|
|
272
|
+
## Remote and ESM Loading
|
|
273
|
+
|
|
274
|
+
Load resources from external modules or remote URLs.
|
|
275
|
+
|
|
276
|
+
**ESM loading:**
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
const ExternalResource = Resource.esm('@my-org/resources@^1.0.0', 'ExternalResource', {
|
|
280
|
+
description: 'A resource loaded from an ES module',
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
**Remote loading:**
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
const CloudResource = Resource.remote('https://example.com/resources/data', 'CloudResource', {
|
|
288
|
+
description: 'A resource loaded from a remote server',
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Both return values that can be registered in `resources: [ExternalResource, CloudResource]`.
|
|
293
|
+
|
|
294
|
+
## Binary Content with Blob
|
|
295
|
+
|
|
296
|
+
Return binary data as base64-encoded blobs:
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
@Resource({
|
|
300
|
+
name: 'app-logo',
|
|
301
|
+
uri: 'assets://logo.png',
|
|
302
|
+
description: 'Application logo image',
|
|
303
|
+
mimeType: 'image/png',
|
|
304
|
+
})
|
|
305
|
+
class AppLogoResource extends ResourceContext {
|
|
306
|
+
async execute(uri: string, params: Record<string, string>): Promise<ReadResourceResult> {
|
|
307
|
+
const { readFileBuffer } = await import('@frontmcp/utils');
|
|
308
|
+
const buffer = await readFileBuffer('/assets/logo.png');
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
contents: [
|
|
312
|
+
{
|
|
313
|
+
uri,
|
|
314
|
+
mimeType: 'image/png',
|
|
315
|
+
blob: buffer.toString('base64'),
|
|
316
|
+
},
|
|
317
|
+
],
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Multiple Content Items
|
|
324
|
+
|
|
325
|
+
A single resource can return multiple content entries:
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
@Resource({
|
|
329
|
+
name: 'dashboard-data',
|
|
330
|
+
uri: 'dashboard://overview',
|
|
331
|
+
description: 'Dashboard overview with metrics and chart data',
|
|
332
|
+
mimeType: 'application/json',
|
|
333
|
+
})
|
|
334
|
+
class DashboardResource extends ResourceContext {
|
|
335
|
+
async execute(uri: string, params: Record<string, string>): Promise<ReadResourceResult> {
|
|
336
|
+
const metrics = await this.loadMetrics();
|
|
337
|
+
const chartData = await this.loadChartData();
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
contents: [
|
|
341
|
+
{
|
|
342
|
+
uri: `${uri}#metrics`,
|
|
343
|
+
mimeType: 'application/json',
|
|
344
|
+
text: JSON.stringify(metrics),
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
uri: `${uri}#charts`,
|
|
348
|
+
mimeType: 'application/json',
|
|
349
|
+
text: JSON.stringify(chartData),
|
|
350
|
+
},
|
|
351
|
+
],
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private async loadMetrics() {
|
|
356
|
+
return { users: 1500, revenue: 42000 };
|
|
357
|
+
}
|
|
358
|
+
private async loadChartData() {
|
|
359
|
+
return { labels: ['Jan', 'Feb'], values: [100, 200] };
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Dependency Injection
|
|
365
|
+
|
|
366
|
+
Resources have access to the same DI utilities as tools:
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import type { Token } from '@frontmcp/di';
|
|
370
|
+
|
|
371
|
+
interface CacheService {
|
|
372
|
+
get(key: string): Promise<string | null>;
|
|
373
|
+
set(key: string, value: string, ttlMs: number): Promise<void>;
|
|
374
|
+
}
|
|
375
|
+
const CACHE: Token<CacheService> = Symbol('cache');
|
|
376
|
+
|
|
377
|
+
@ResourceTemplate({
|
|
378
|
+
name: 'cached-data',
|
|
379
|
+
uriTemplate: 'cache://{key}',
|
|
380
|
+
description: 'Cached data by key',
|
|
381
|
+
mimeType: 'application/json',
|
|
382
|
+
})
|
|
383
|
+
class CachedDataResource extends ResourceContext<{ key: string }> {
|
|
384
|
+
async execute(uri: string, params: { key: string }): Promise<ReadResourceResult> {
|
|
385
|
+
const cache = this.get(CACHE);
|
|
386
|
+
const value = await cache.get(params.key);
|
|
387
|
+
|
|
388
|
+
if (!value) {
|
|
389
|
+
this.fail(new Error(`Cache key not found: ${params.key}`));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
contents: [{ uri, mimeType: 'application/json', text: value }],
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
## Registration
|
|
400
|
+
|
|
401
|
+
Add resource classes (or function-style resources) to the `resources` array in `@FrontMcp` or `@App`.
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
import { FrontMcp, App } from '@frontmcp/sdk';
|
|
405
|
+
|
|
406
|
+
@App({
|
|
407
|
+
name: 'my-app',
|
|
408
|
+
resources: [AppConfigResource, UserProfileResource, SystemInfo, LogFile],
|
|
409
|
+
})
|
|
410
|
+
class MyApp {}
|
|
411
|
+
|
|
412
|
+
@FrontMcp({
|
|
413
|
+
info: { name: 'my-server', version: '1.0.0' },
|
|
414
|
+
apps: [MyApp],
|
|
415
|
+
resources: [DashboardResource], // can also register resources directly on the server
|
|
416
|
+
})
|
|
417
|
+
class MyServer {}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## URI Validation Rules
|
|
421
|
+
|
|
422
|
+
All resource URIs are validated per RFC 3986 at metadata level:
|
|
423
|
+
|
|
424
|
+
- Must have a valid scheme (e.g., `file://`, `https://`, `config://`, `custom://`).
|
|
425
|
+
- Scheme-less URIs like `my-resource` will be rejected at registration time.
|
|
426
|
+
- Template URIs must also have a valid scheme: `users://{id}` is valid, `{id}/profile` is not.
|
|
427
|
+
- URI validation happens at decorator parse time, so errors surface immediately during server startup.
|
|
428
|
+
|
|
429
|
+
## Nx Generator
|
|
430
|
+
|
|
431
|
+
Scaffold a new resource using the Nx generator:
|
|
432
|
+
|
|
433
|
+
```bash
|
|
434
|
+
nx generate @frontmcp/nx:resource
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
This creates the resource file, spec file, and updates barrel exports.
|