@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.
Files changed (65) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +135 -0
  3. package/catalog/TEMPLATE.md +49 -0
  4. package/catalog/adapters/create-adapter/SKILL.md +127 -0
  5. package/catalog/adapters/official-adapters/SKILL.md +136 -0
  6. package/catalog/auth/configure-auth/SKILL.md +250 -0
  7. package/catalog/auth/configure-auth/references/auth-modes.md +77 -0
  8. package/catalog/auth/configure-session/SKILL.md +201 -0
  9. package/catalog/config/configure-elicitation/SKILL.md +136 -0
  10. package/catalog/config/configure-http/SKILL.md +167 -0
  11. package/catalog/config/configure-throttle/SKILL.md +189 -0
  12. package/catalog/config/configure-throttle/references/guard-config.md +68 -0
  13. package/catalog/config/configure-transport/SKILL.md +151 -0
  14. package/catalog/config/configure-transport/references/protocol-presets.md +57 -0
  15. package/catalog/deployment/build-for-browser/SKILL.md +95 -0
  16. package/catalog/deployment/build-for-cli/SKILL.md +100 -0
  17. package/catalog/deployment/build-for-sdk/SKILL.md +218 -0
  18. package/catalog/deployment/deploy-to-cloudflare/SKILL.md +192 -0
  19. package/catalog/deployment/deploy-to-lambda/SKILL.md +304 -0
  20. package/catalog/deployment/deploy-to-node/SKILL.md +229 -0
  21. package/catalog/deployment/deploy-to-node/references/Dockerfile.example +45 -0
  22. package/catalog/deployment/deploy-to-vercel/SKILL.md +196 -0
  23. package/catalog/deployment/deploy-to-vercel/references/vercel.json.example +60 -0
  24. package/catalog/development/create-agent/SKILL.md +563 -0
  25. package/catalog/development/create-agent/references/llm-config.md +46 -0
  26. package/catalog/development/create-job/SKILL.md +566 -0
  27. package/catalog/development/create-prompt/SKILL.md +400 -0
  28. package/catalog/development/create-provider/SKILL.md +233 -0
  29. package/catalog/development/create-resource/SKILL.md +437 -0
  30. package/catalog/development/create-skill/SKILL.md +526 -0
  31. package/catalog/development/create-skill-with-tools/SKILL.md +579 -0
  32. package/catalog/development/create-tool/SKILL.md +418 -0
  33. package/catalog/development/create-tool/references/output-schema-types.md +56 -0
  34. package/catalog/development/create-tool/references/tool-annotations.md +34 -0
  35. package/catalog/development/create-workflow/SKILL.md +709 -0
  36. package/catalog/development/decorators-guide/SKILL.md +598 -0
  37. package/catalog/plugins/create-plugin/SKILL.md +336 -0
  38. package/catalog/plugins/create-plugin-hooks/SKILL.md +282 -0
  39. package/catalog/plugins/official-plugins/SKILL.md +667 -0
  40. package/catalog/setup/frontmcp-skills-usage/SKILL.md +200 -0
  41. package/catalog/setup/multi-app-composition/SKILL.md +358 -0
  42. package/catalog/setup/nx-workflow/SKILL.md +357 -0
  43. package/catalog/setup/project-structure-nx/SKILL.md +186 -0
  44. package/catalog/setup/project-structure-standalone/SKILL.md +153 -0
  45. package/catalog/setup/setup-project/SKILL.md +493 -0
  46. package/catalog/setup/setup-redis/SKILL.md +385 -0
  47. package/catalog/setup/setup-sqlite/SKILL.md +359 -0
  48. package/catalog/skills-manifest.json +414 -0
  49. package/catalog/testing/setup-testing/SKILL.md +539 -0
  50. package/catalog/testing/setup-testing/references/test-auth.md +88 -0
  51. package/catalog/testing/setup-testing/references/test-browser-build.md +57 -0
  52. package/catalog/testing/setup-testing/references/test-cli-binary.md +48 -0
  53. package/catalog/testing/setup-testing/references/test-direct-client.md +62 -0
  54. package/catalog/testing/setup-testing/references/test-e2e-handler.md +51 -0
  55. package/catalog/testing/setup-testing/references/test-tool-unit.md +41 -0
  56. package/package.json +34 -0
  57. package/src/index.d.ts +3 -0
  58. package/src/index.js +16 -0
  59. package/src/index.js.map +1 -0
  60. package/src/loader.d.ts +46 -0
  61. package/src/loader.js +75 -0
  62. package/src/loader.js.map +1 -0
  63. package/src/manifest.d.ts +81 -0
  64. package/src/manifest.js +26 -0
  65. package/src/manifest.js.map +1 -0
@@ -0,0 +1,400 @@
1
+ ---
2
+ name: create-prompt
3
+ description: Create MCP prompts for reusable AI interaction patterns. Use when building prompts, defining prompt arguments, or creating conversation templates.
4
+ tags: [prompts, mcp, templates, messages, decorator]
5
+ tools:
6
+ - name: create_prompt
7
+ purpose: Scaffold a new prompt class
8
+ parameters:
9
+ - name: name
10
+ description: Prompt name in kebab-case
11
+ type: string
12
+ required: true
13
+ examples:
14
+ - scenario: Create a code review prompt with language argument
15
+ expected-outcome: Prompt registered and callable via MCP
16
+ - scenario: Create a multi-turn debugging prompt with assistant priming
17
+ expected-outcome: Prompt producing structured message sequences
18
+ priority: 10
19
+ visibility: both
20
+ license: Apache-2.0
21
+ metadata:
22
+ docs: https://docs.agentfront.dev/frontmcp/servers/prompts
23
+ ---
24
+
25
+ # Creating MCP Prompts
26
+
27
+ Prompts define reusable AI interaction patterns in the MCP protocol. They produce structured message sequences that clients use to guide LLM conversations. In FrontMCP, prompts are classes extending `PromptContext`, decorated with `@Prompt`, that return `GetPromptResult` objects.
28
+
29
+ ## When to Use @Prompt
30
+
31
+ Use `@Prompt` when you need to expose a reusable conversation template that an AI client can invoke with arguments. Prompts are ideal for code review patterns, debugging sessions, RAG queries, report generation, translation workflows, and any scenario where you want a standardized message structure.
32
+
33
+ ## Class-Based Pattern
34
+
35
+ Create a class extending `PromptContext` and implement `execute(args)`. The `@Prompt` decorator accepts `name`, optional `description`, and `arguments` (the prompt's input parameters).
36
+
37
+ ```typescript
38
+ import { Prompt, PromptContext } from '@frontmcp/sdk';
39
+ import { GetPromptResult } from '@frontmcp/protocol';
40
+
41
+ @Prompt({
42
+ name: 'code-review',
43
+ description: 'Generate a structured code review for the given code',
44
+ arguments: [
45
+ { name: 'code', description: 'The code to review', required: true },
46
+ { name: 'language', description: 'Programming language', required: false },
47
+ ],
48
+ })
49
+ class CodeReviewPrompt extends PromptContext {
50
+ async execute(args: Record<string, string>): Promise<GetPromptResult> {
51
+ const language = args.language ?? 'unknown language';
52
+
53
+ return {
54
+ messages: [
55
+ {
56
+ role: 'user',
57
+ content: {
58
+ type: 'text',
59
+ text: `Please review the following ${language} code. Focus on correctness, performance, and maintainability.\n\n\`\`\`${language}\n${args.code}\n\`\`\``,
60
+ },
61
+ },
62
+ ],
63
+ };
64
+ }
65
+ }
66
+ ```
67
+
68
+ ### Decorator Options
69
+
70
+ The `@Prompt` decorator accepts:
71
+
72
+ - `name` (required) -- unique prompt name
73
+ - `description` (optional) -- human-readable description
74
+ - `arguments` (optional) -- array of `PromptArgument` objects
75
+
76
+ ### PromptArgument Structure
77
+
78
+ Each entry in the `arguments` array has this shape:
79
+
80
+ ```typescript
81
+ interface PromptArgument {
82
+ name: string; // argument name
83
+ description?: string; // human-readable description
84
+ required?: boolean; // whether the argument must be provided (default: false)
85
+ }
86
+ ```
87
+
88
+ Required arguments are validated before `execute()` runs. Missing required arguments throw `MissingPromptArgumentError`.
89
+
90
+ ### GetPromptResult Structure
91
+
92
+ The `execute()` method must return a `GetPromptResult`:
93
+
94
+ ```typescript
95
+ interface GetPromptResult {
96
+ messages: Array<{
97
+ role: 'user' | 'assistant';
98
+ content: {
99
+ type: 'text';
100
+ text: string;
101
+ };
102
+ }>;
103
+ }
104
+ ```
105
+
106
+ Messages use two roles:
107
+
108
+ - `user` -- represents the human side of the conversation
109
+ - `assistant` -- primes the conversation with expected response patterns
110
+
111
+ ### Available Context Methods and Properties
112
+
113
+ `PromptContext` extends `ExecutionContextBase`, providing:
114
+
115
+ **Methods:**
116
+
117
+ - `execute(args)` -- the main method you implement, receives `Record<string, string>`
118
+ - `this.get(token)` -- resolve a dependency from DI (throws if not found)
119
+ - `this.tryGet(token)` -- resolve a dependency from DI (returns `undefined` if not found)
120
+ - `this.fail(err)` -- abort execution, triggers error flow (never returns)
121
+ - `this.mark(stage)` -- set active execution stage for debugging/tracking
122
+ - `this.fetch(input, init?)` -- HTTP fetch with context propagation
123
+
124
+ **Properties:**
125
+
126
+ - `this.metadata` -- prompt metadata from the decorator
127
+ - `this.scope` -- the current scope instance
128
+ - `this.context` -- the execution context
129
+
130
+ ## Multi-Turn Conversations
131
+
132
+ Use `assistant` role messages to prime the conversation with expected response patterns:
133
+
134
+ ```typescript
135
+ @Prompt({
136
+ name: 'debug-session',
137
+ description: 'Start a structured debugging session',
138
+ arguments: [
139
+ { name: 'error', description: 'The error message or stack trace', required: true },
140
+ { name: 'context', description: 'Additional context about what was happening', required: false },
141
+ ],
142
+ })
143
+ class DebugSessionPrompt extends PromptContext {
144
+ async execute(args: Record<string, string>): Promise<GetPromptResult> {
145
+ const contextNote = args.context ? `\n\nAdditional context: ${args.context}` : '';
146
+
147
+ return {
148
+ messages: [
149
+ {
150
+ role: 'user',
151
+ content: {
152
+ type: 'text',
153
+ text: `I encountered an error and need help debugging it.\n\nError:\n\`\`\`\n${args.error}\n\`\`\`${contextNote}`,
154
+ },
155
+ },
156
+ {
157
+ role: 'assistant',
158
+ content: {
159
+ type: 'text',
160
+ text: "I'll help you debug this. Let me analyze the error systematically.\n\n**Step 1: Error Classification**\nLet me first identify what type of error this is and its likely root cause.\n\n",
161
+ },
162
+ },
163
+ {
164
+ role: 'user',
165
+ content: {
166
+ type: 'text',
167
+ text: 'Please continue with your analysis and suggest specific fixes.',
168
+ },
169
+ },
170
+ ],
171
+ };
172
+ }
173
+ }
174
+ ```
175
+
176
+ ## Dynamic Prompt Generation
177
+
178
+ Prompts can perform async operations to generate context-aware messages. Use `this.get()` for dependency injection and `this.fetch()` for HTTP requests.
179
+
180
+ ```typescript
181
+ import type { Token } from '@frontmcp/di';
182
+
183
+ interface KnowledgeBase {
184
+ search(query: string, limit: number): Promise<Array<{ title: string; content: string }>>;
185
+ }
186
+ const KNOWLEDGE_BASE: Token<KnowledgeBase> = Symbol('knowledge-base');
187
+
188
+ @Prompt({
189
+ name: 'rag-query',
190
+ description: 'Answer a question using knowledge base context',
191
+ arguments: [
192
+ { name: 'question', description: 'The question to answer', required: true },
193
+ { name: 'maxSources', description: 'Maximum number of sources to include', required: false },
194
+ ],
195
+ })
196
+ class RagQueryPrompt extends PromptContext {
197
+ async execute(args: Record<string, string>): Promise<GetPromptResult> {
198
+ const kb = this.get(KNOWLEDGE_BASE);
199
+ const maxSources = parseInt(args.maxSources ?? '3', 10);
200
+ const sources = await kb.search(args.question, maxSources);
201
+
202
+ const contextBlock = sources.map((s, i) => `### Source ${i + 1}: ${s.title}\n${s.content}`).join('\n\n');
203
+
204
+ return {
205
+ messages: [
206
+ {
207
+ role: 'user',
208
+ content: {
209
+ type: 'text',
210
+ text: `Answer the following question using only the provided sources. If the sources do not contain enough information, say so clearly.\n\n**Question:** ${args.question}\n\n---\n\n${contextBlock}`,
211
+ },
212
+ },
213
+ ],
214
+ };
215
+ }
216
+ }
217
+ ```
218
+
219
+ ## Embedding Resources in Prompts
220
+
221
+ Include MCP resource content directly in prompt messages using the `resource` content type:
222
+
223
+ ```typescript
224
+ @Prompt({
225
+ name: 'analyze-config',
226
+ description: 'Analyze application configuration and suggest improvements',
227
+ arguments: [{ name: 'configUri', description: 'URI of the config resource to analyze', required: true }],
228
+ })
229
+ class AnalyzeConfigPrompt extends PromptContext {
230
+ async execute(args: Record<string, string>): Promise<GetPromptResult> {
231
+ return {
232
+ messages: [
233
+ {
234
+ role: 'user',
235
+ content: {
236
+ type: 'resource',
237
+ resource: {
238
+ uri: args.configUri,
239
+ mimeType: 'application/json',
240
+ text: '(resource content will be resolved by the client)',
241
+ },
242
+ },
243
+ },
244
+ {
245
+ role: 'user',
246
+ content: {
247
+ type: 'text',
248
+ text: 'Analyze the configuration above. Identify potential issues, security concerns, and suggest improvements.',
249
+ },
250
+ },
251
+ ],
252
+ };
253
+ }
254
+ }
255
+ ```
256
+
257
+ ## Function-Style Builder
258
+
259
+ For simple prompts that do not need a class, use the `prompt()` function builder:
260
+
261
+ ```typescript
262
+ import { prompt } from '@frontmcp/sdk';
263
+ import { GetPromptResult } from '@frontmcp/protocol';
264
+
265
+ const TranslatePrompt = prompt({
266
+ name: 'translate',
267
+ description: 'Translate text between languages',
268
+ arguments: [
269
+ { name: 'text', description: 'Text to translate', required: true },
270
+ { name: 'from', description: 'Source language', required: true },
271
+ { name: 'to', description: 'Target language', required: true },
272
+ ],
273
+ })(
274
+ (args): GetPromptResult => ({
275
+ messages: [
276
+ {
277
+ role: 'user',
278
+ content: {
279
+ type: 'text',
280
+ text: `Translate the following text from ${args?.from} to ${args?.to}. Provide only the translation, no explanations.\n\nText: ${args?.text}`,
281
+ },
282
+ },
283
+ ],
284
+ }),
285
+ );
286
+ ```
287
+
288
+ Register it the same way as a class prompt: `prompts: [TranslatePrompt]`.
289
+
290
+ ## Error Handling
291
+
292
+ Use `this.fail()` to abort prompt execution. Missing required arguments are caught automatically before `execute()` runs.
293
+
294
+ ```typescript
295
+ @Prompt({
296
+ name: 'generate-tests',
297
+ description: 'Generate test cases for a function',
298
+ arguments: [
299
+ { name: 'functionCode', description: 'The function to test', required: true },
300
+ { name: 'framework', description: 'Test framework (jest, mocha, vitest)', required: true },
301
+ ],
302
+ })
303
+ class GenerateTestsPrompt extends PromptContext {
304
+ async execute(args: Record<string, string>): Promise<GetPromptResult> {
305
+ const validFrameworks = ['jest', 'mocha', 'vitest'];
306
+ if (!validFrameworks.includes(args.framework)) {
307
+ this.fail(new Error(`Unsupported test framework: "${args.framework}". Supported: ${validFrameworks.join(', ')}`));
308
+ }
309
+
310
+ return {
311
+ messages: [
312
+ {
313
+ role: 'user',
314
+ content: {
315
+ type: 'text',
316
+ text: `Write comprehensive ${args.framework} test cases for the following function. Include edge cases, error handling, and boundary conditions.\n\n\`\`\`\n${args.functionCode}\n\`\`\``,
317
+ },
318
+ },
319
+ ],
320
+ };
321
+ }
322
+ }
323
+ ```
324
+
325
+ ## Stage Tracking
326
+
327
+ Use `this.mark()` for debugging and observability in complex prompts:
328
+
329
+ ```typescript
330
+ @Prompt({
331
+ name: 'research-report',
332
+ description: 'Generate a structured research report prompt',
333
+ arguments: [
334
+ { name: 'topic', description: 'Research topic', required: true },
335
+ { name: 'depth', description: 'Report depth: brief, standard, or comprehensive', required: false },
336
+ ],
337
+ })
338
+ class ResearchReportPrompt extends PromptContext {
339
+ async execute(args: Record<string, string>): Promise<GetPromptResult> {
340
+ this.mark('build-outline');
341
+ const depth = args.depth ?? 'standard';
342
+ const outline = this.buildOutline(depth);
343
+
344
+ this.mark('compose-messages');
345
+ return {
346
+ messages: [
347
+ {
348
+ role: 'user',
349
+ content: {
350
+ type: 'text',
351
+ text: `Write a ${depth} research report on "${args.topic}".\n\nFollow this structure:\n${outline}\n\nInclude citations where possible and maintain an objective, academic tone.`,
352
+ },
353
+ },
354
+ ],
355
+ };
356
+ }
357
+
358
+ private buildOutline(depth: string): string {
359
+ const sections = ['Introduction', 'Background', 'Key Findings'];
360
+ if (depth === 'standard' || depth === 'comprehensive') {
361
+ sections.push('Analysis', 'Discussion');
362
+ }
363
+ if (depth === 'comprehensive') {
364
+ sections.push('Methodology', 'Limitations', 'Future Research');
365
+ }
366
+ sections.push('Conclusion');
367
+ return sections.map((s, i) => `${i + 1}. ${s}`).join('\n');
368
+ }
369
+ }
370
+ ```
371
+
372
+ ## Registration
373
+
374
+ Add prompt classes (or function-style prompts) to the `prompts` array in `@App`.
375
+
376
+ ```typescript
377
+ import { FrontMcp, App } from '@frontmcp/sdk';
378
+
379
+ @App({
380
+ name: 'my-app',
381
+ prompts: [CodeReviewPrompt, DebugSessionPrompt, TranslatePrompt],
382
+ })
383
+ class MyApp {}
384
+
385
+ @FrontMcp({
386
+ info: { name: 'my-server', version: '1.0.0' },
387
+ apps: [MyApp],
388
+ })
389
+ class MyServer {}
390
+ ```
391
+
392
+ ## Nx Generator
393
+
394
+ Scaffold a new prompt using the Nx generator:
395
+
396
+ ```bash
397
+ nx generate @frontmcp/nx:prompt
398
+ ```
399
+
400
+ This creates the prompt file, spec file, and updates barrel exports.
@@ -0,0 +1,233 @@
1
+ ---
2
+ name: create-provider
3
+ description: Create dependency injection providers for database connections, API clients, and singleton services. Use when tools and resources need shared services, DB pools, or configuration objects.
4
+ tags: [provider, di, dependency-injection, singleton, database, service]
5
+ parameters:
6
+ - name: name
7
+ description: Provider name
8
+ type: string
9
+ required: true
10
+ examples:
11
+ - scenario: Create a database connection pool provider
12
+ expected-outcome: Singleton DB pool injectable into all tools via this.get()
13
+ - scenario: Create a config provider from environment variables
14
+ expected-outcome: Type-safe config object available in any context
15
+ priority: 8
16
+ visibility: both
17
+ license: Apache-2.0
18
+ metadata:
19
+ docs: https://docs.agentfront.dev/frontmcp/extensibility/providers
20
+ ---
21
+
22
+ # Creating Providers (Dependency Injection)
23
+
24
+ Providers are singleton services — database pools, API clients, config objects — that tools, resources, prompts, and agents can access via `this.get(token)`.
25
+
26
+ ## When to Use
27
+
28
+ Create a provider when:
29
+
30
+ - Multiple tools need the same database connection pool
31
+ - You have API clients that should be shared (not recreated per request)
32
+ - Configuration values should be centralized and type-safe
33
+ - You need lifecycle management (initialize on startup, cleanup on shutdown)
34
+
35
+ ## Step 1: Define a Token
36
+
37
+ Tokens identify providers in the DI container:
38
+
39
+ ```typescript
40
+ import type { Token } from '@frontmcp/di';
41
+
42
+ // Define a typed token
43
+ interface DatabaseService {
44
+ query(sql: string, params?: unknown[]): Promise<unknown[]>;
45
+ close(): Promise<void>;
46
+ }
47
+
48
+ const DB_TOKEN: Token<DatabaseService> = Symbol('DatabaseService');
49
+ ```
50
+
51
+ ## Step 2: Create the Provider
52
+
53
+ ```typescript
54
+ import { Provider } from '@frontmcp/sdk';
55
+ import { createPool, Pool } from 'your-db-driver';
56
+
57
+ @Provider({ name: 'DatabaseProvider' })
58
+ class DatabaseProvider implements DatabaseService {
59
+ private pool!: Pool;
60
+
61
+ async onInit() {
62
+ // Called once when server starts
63
+ this.pool = await createPool({
64
+ connectionString: process.env.DATABASE_URL,
65
+ max: 20,
66
+ });
67
+ }
68
+
69
+ async query(sql: string, params?: unknown[]) {
70
+ return this.pool.query(sql, params);
71
+ }
72
+
73
+ async onDestroy() {
74
+ // Called when server shuts down
75
+ await this.pool.end();
76
+ }
77
+ }
78
+ ```
79
+
80
+ ## Step 3: Register in @App or @FrontMcp
81
+
82
+ ```typescript
83
+ @App({
84
+ name: 'MyApp',
85
+ providers: [DatabaseProvider], // App-scoped provider
86
+ tools: [QueryTool, InsertTool],
87
+ })
88
+ class MyApp {}
89
+
90
+ // OR at server level (shared across all apps)
91
+ @FrontMcp({
92
+ info: { name: 'my-server', version: '1.0.0' },
93
+ apps: [MyApp],
94
+ providers: [DatabaseProvider], // Server-scoped provider
95
+ })
96
+ class Server {}
97
+ ```
98
+
99
+ ## Step 4: Use in Tools
100
+
101
+ Access providers via `this.get(token)` in any context (ToolContext, ResourceContext, PromptContext, AgentContext):
102
+
103
+ ```typescript
104
+ @Tool({
105
+ name: 'query_users',
106
+ description: 'Query users from the database',
107
+ inputSchema: {
108
+ filter: z.string().optional(),
109
+ limit: z.number().default(10),
110
+ },
111
+ outputSchema: {
112
+ users: z.array(z.object({ id: z.string(), name: z.string(), email: z.string() })),
113
+ },
114
+ })
115
+ class QueryUsersTool extends ToolContext {
116
+ async execute(input: { filter?: string; limit: number }) {
117
+ const db = this.get(DB_TOKEN); // Get the database provider
118
+ const users = await db.query('SELECT id, name, email FROM users WHERE name LIKE $1 LIMIT $2', [
119
+ `%${input.filter ?? ''}%`,
120
+ input.limit,
121
+ ]);
122
+ return { users };
123
+ }
124
+ }
125
+ ```
126
+
127
+ ### Safe Access
128
+
129
+ ```typescript
130
+ // Throws if not registered
131
+ const db = this.get(DB_TOKEN);
132
+
133
+ // Returns undefined if not registered
134
+ const db = this.tryGet(DB_TOKEN);
135
+ if (!db) {
136
+ this.fail(new Error('Database not configured'));
137
+ }
138
+ ```
139
+
140
+ ## Common Provider Patterns
141
+
142
+ ### Configuration Provider
143
+
144
+ ```typescript
145
+ interface AppConfig {
146
+ apiBaseUrl: string;
147
+ maxRetries: number;
148
+ debug: boolean;
149
+ }
150
+
151
+ const CONFIG_TOKEN: Token<AppConfig> = Symbol('AppConfig');
152
+
153
+ @Provider({ name: 'ConfigProvider' })
154
+ class ConfigProvider implements AppConfig {
155
+ readonly apiBaseUrl = process.env.API_BASE_URL ?? 'https://api.example.com';
156
+ readonly maxRetries = Number(process.env.MAX_RETRIES ?? 3);
157
+ readonly debug = process.env.DEBUG === 'true';
158
+ }
159
+ ```
160
+
161
+ ### HTTP API Client Provider
162
+
163
+ ```typescript
164
+ interface ApiClient {
165
+ get(path: string): Promise<unknown>;
166
+ post(path: string, body: unknown): Promise<unknown>;
167
+ }
168
+
169
+ const API_TOKEN: Token<ApiClient> = Symbol('ApiClient');
170
+
171
+ @Provider({ name: 'ApiClientProvider' })
172
+ class ApiClientProvider implements ApiClient {
173
+ private baseUrl!: string;
174
+ private apiKey!: string;
175
+
176
+ async onInit() {
177
+ this.baseUrl = process.env.API_URL!;
178
+ this.apiKey = process.env.API_KEY!;
179
+ }
180
+
181
+ async get(path: string) {
182
+ const res = await fetch(`${this.baseUrl}${path}`, {
183
+ headers: { Authorization: `Bearer ${this.apiKey}` },
184
+ });
185
+ return res.json();
186
+ }
187
+
188
+ async post(path: string, body: unknown) {
189
+ const res = await fetch(`${this.baseUrl}${path}`, {
190
+ method: 'POST',
191
+ headers: { Authorization: `Bearer ${this.apiKey}`, 'Content-Type': 'application/json' },
192
+ body: JSON.stringify(body),
193
+ });
194
+ return res.json();
195
+ }
196
+ }
197
+ ```
198
+
199
+ ### Cache Provider
200
+
201
+ ```typescript
202
+ const CACHE_TOKEN: Token<Map<string, unknown>> = Symbol('Cache');
203
+
204
+ @Provider({ name: 'CacheProvider' })
205
+ class CacheProvider extends Map<string, unknown> {
206
+ // Map is already a valid provider - no lifecycle needed
207
+ }
208
+ ```
209
+
210
+ ## Provider Lifecycle
211
+
212
+ | Method | When Called | Use For |
213
+ | ------------- | ----------------------- | -------------------------------- |
214
+ | `onInit()` | Server startup (async) | Open connections, load config |
215
+ | `onDestroy()` | Server shutdown (async) | Close connections, flush buffers |
216
+
217
+ Providers are initialized in dependency order — if Provider A depends on Provider B, B initializes first.
218
+
219
+ ## Nx Generator
220
+
221
+ ```bash
222
+ nx generate @frontmcp/nx:provider my-provider --project=my-app
223
+ ```
224
+
225
+ ## Verification
226
+
227
+ ```bash
228
+ # Start server — providers initialize on startup
229
+ frontmcp dev
230
+
231
+ # Call a tool that uses the provider
232
+ # If provider fails to init, you'll see an error at startup
233
+ ```