@frontmcp/skills 0.0.1 → 1.0.0-beta.11
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/README.md +2 -2
- package/catalog/TEMPLATE.md +58 -13
- package/catalog/frontmcp-config/SKILL.md +156 -0
- package/catalog/{auth/configure-auth/references/auth-modes.md → frontmcp-config/references/configure-auth-modes.md} +5 -0
- package/catalog/frontmcp-config/references/configure-auth.md +243 -0
- package/catalog/frontmcp-config/references/configure-elicitation.md +183 -0
- package/catalog/frontmcp-config/references/configure-http.md +210 -0
- package/catalog/frontmcp-config/references/configure-session.md +210 -0
- package/catalog/{config/configure-throttle/references/guard-config.md → frontmcp-config/references/configure-throttle-guard-config.md} +5 -0
- package/catalog/frontmcp-config/references/configure-throttle.md +234 -0
- package/catalog/{config/configure-transport/references/protocol-presets.md → frontmcp-config/references/configure-transport-protocol-presets.md} +5 -0
- package/catalog/frontmcp-config/references/configure-transport.md +200 -0
- package/catalog/frontmcp-config/references/setup-redis.md +9 -0
- package/catalog/frontmcp-config/references/setup-sqlite.md +9 -0
- package/catalog/frontmcp-deployment/SKILL.md +152 -0
- package/catalog/frontmcp-deployment/references/build-for-browser.md +143 -0
- package/catalog/frontmcp-deployment/references/build-for-cli.md +191 -0
- package/catalog/{deployment/build-for-sdk/SKILL.md → frontmcp-deployment/references/build-for-sdk.md} +66 -20
- package/catalog/frontmcp-deployment/references/deploy-to-cloudflare.md +218 -0
- package/catalog/{deployment/deploy-to-lambda/SKILL.md → frontmcp-deployment/references/deploy-to-lambda.md} +77 -59
- package/catalog/{deployment/deploy-to-node/references/Dockerfile.example → frontmcp-deployment/references/deploy-to-node-dockerfile.md} +18 -4
- package/catalog/{deployment/deploy-to-node/SKILL.md → frontmcp-deployment/references/deploy-to-node.md} +69 -36
- package/catalog/frontmcp-deployment/references/deploy-to-vercel-config.md +65 -0
- package/catalog/frontmcp-deployment/references/deploy-to-vercel.md +229 -0
- package/catalog/frontmcp-development/SKILL.md +126 -0
- package/catalog/frontmcp-development/references/create-adapter.md +170 -0
- package/catalog/{development/create-agent/references/llm-config.md → frontmcp-development/references/create-agent-llm-config.md} +10 -5
- package/catalog/{development/create-agent/SKILL.md → frontmcp-development/references/create-agent.md} +83 -40
- package/catalog/{development/create-job/SKILL.md → frontmcp-development/references/create-job.md} +62 -15
- package/catalog/{plugins/create-plugin-hooks/SKILL.md → frontmcp-development/references/create-plugin-hooks.md} +100 -7
- package/catalog/frontmcp-development/references/create-plugin.md +506 -0
- package/catalog/{development/create-prompt/SKILL.md → frontmcp-development/references/create-prompt.md} +65 -22
- package/catalog/{development/create-provider/SKILL.md → frontmcp-development/references/create-provider.md} +63 -23
- package/catalog/{development/create-resource/SKILL.md → frontmcp-development/references/create-resource.md} +148 -26
- package/catalog/{development/create-skill-with-tools/SKILL.md → frontmcp-development/references/create-skill-with-tools.md} +174 -20
- package/catalog/{development/create-skill/SKILL.md → frontmcp-development/references/create-skill.md} +114 -28
- package/catalog/{development/create-tool/references/tool-annotations.md → frontmcp-development/references/create-tool-annotations.md} +5 -0
- package/catalog/{development/create-tool/references/output-schema-types.md → frontmcp-development/references/create-tool-output-schema-types.md} +5 -0
- package/catalog/{development/create-tool/SKILL.md → frontmcp-development/references/create-tool.md} +172 -23
- package/catalog/{development/create-workflow/SKILL.md → frontmcp-development/references/create-workflow.md} +61 -14
- package/catalog/frontmcp-development/references/decorators-guide.md +754 -0
- package/catalog/frontmcp-development/references/official-adapters.md +199 -0
- package/catalog/{plugins/official-plugins/SKILL.md → frontmcp-development/references/official-plugins.md} +97 -27
- package/catalog/frontmcp-extensibility/SKILL.md +103 -0
- package/catalog/frontmcp-extensibility/references/vectoriadb.md +289 -0
- package/catalog/frontmcp-guides/SKILL.md +420 -0
- package/catalog/frontmcp-guides/references/example-knowledge-base.md +641 -0
- package/catalog/frontmcp-guides/references/example-task-manager.md +517 -0
- package/catalog/frontmcp-guides/references/example-weather-api.md +297 -0
- package/catalog/frontmcp-production-readiness/SKILL.md +98 -0
- package/catalog/frontmcp-production-readiness/references/common-checklist.md +156 -0
- package/catalog/frontmcp-production-readiness/references/production-browser.md +46 -0
- package/catalog/frontmcp-production-readiness/references/production-cli-binary.md +62 -0
- package/catalog/frontmcp-production-readiness/references/production-cli-daemon.md +61 -0
- package/catalog/frontmcp-production-readiness/references/production-cloudflare.md +52 -0
- package/catalog/frontmcp-production-readiness/references/production-lambda.md +53 -0
- package/catalog/frontmcp-production-readiness/references/production-node-sdk.md +66 -0
- package/catalog/frontmcp-production-readiness/references/production-node-server.md +61 -0
- package/catalog/frontmcp-production-readiness/references/production-vercel.md +52 -0
- package/catalog/frontmcp-setup/SKILL.md +132 -0
- package/catalog/frontmcp-setup/references/frontmcp-skills-usage.md +280 -0
- package/catalog/{setup/multi-app-composition/SKILL.md → frontmcp-setup/references/multi-app-composition.md} +66 -19
- package/catalog/{setup/nx-workflow/SKILL.md → frontmcp-setup/references/nx-workflow.md} +79 -17
- package/catalog/frontmcp-setup/references/project-structure-nx.md +251 -0
- package/catalog/frontmcp-setup/references/project-structure-standalone.md +217 -0
- package/catalog/frontmcp-setup/references/readme-guide.md +226 -0
- package/catalog/{setup/setup-project/SKILL.md → frontmcp-setup/references/setup-project.md} +63 -58
- package/catalog/{setup/setup-redis/SKILL.md → frontmcp-setup/references/setup-redis.md} +60 -82
- package/catalog/{setup/setup-sqlite/SKILL.md → frontmcp-setup/references/setup-sqlite.md} +65 -72
- package/catalog/frontmcp-testing/SKILL.md +135 -0
- package/catalog/{testing/setup-testing/SKILL.md → frontmcp-testing/references/setup-testing.md} +79 -63
- package/catalog/{testing/setup-testing → frontmcp-testing}/references/test-auth.md +5 -0
- package/catalog/{testing/setup-testing → frontmcp-testing}/references/test-browser-build.md +5 -0
- package/catalog/{testing/setup-testing → frontmcp-testing}/references/test-cli-binary.md +5 -0
- package/catalog/{testing/setup-testing → frontmcp-testing}/references/test-direct-client.md +5 -0
- package/catalog/{testing/setup-testing → frontmcp-testing}/references/test-e2e-handler.md +5 -0
- package/catalog/{testing/setup-testing → frontmcp-testing}/references/test-tool-unit.md +6 -0
- package/catalog/skills-manifest.json +337 -382
- package/package.json +2 -2
- package/src/index.d.ts +1 -1
- package/src/index.js.map +1 -1
- package/src/loader.js +0 -1
- package/src/loader.js.map +1 -1
- package/src/manifest.d.ts +15 -3
- package/src/manifest.js +3 -3
- package/src/manifest.js.map +1 -1
- package/catalog/adapters/create-adapter/SKILL.md +0 -127
- package/catalog/adapters/official-adapters/SKILL.md +0 -136
- package/catalog/auth/configure-auth/SKILL.md +0 -250
- package/catalog/auth/configure-session/SKILL.md +0 -201
- package/catalog/config/configure-elicitation/SKILL.md +0 -136
- package/catalog/config/configure-http/SKILL.md +0 -167
- package/catalog/config/configure-throttle/SKILL.md +0 -189
- package/catalog/config/configure-transport/SKILL.md +0 -151
- package/catalog/deployment/build-for-browser/SKILL.md +0 -95
- package/catalog/deployment/build-for-cli/SKILL.md +0 -100
- package/catalog/deployment/deploy-to-cloudflare/SKILL.md +0 -192
- package/catalog/deployment/deploy-to-vercel/SKILL.md +0 -196
- package/catalog/deployment/deploy-to-vercel/references/vercel.json.example +0 -60
- package/catalog/development/decorators-guide/SKILL.md +0 -598
- package/catalog/plugins/create-plugin/SKILL.md +0 -336
- package/catalog/setup/frontmcp-skills-usage/SKILL.md +0 -200
- package/catalog/setup/project-structure-nx/SKILL.md +0 -186
- package/catalog/setup/project-structure-standalone/SKILL.md +0 -153
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: example-task-manager
|
|
3
|
+
description: Authenticated task management server with CRUD tools, Redis storage, OAuth, and Vercel deployment
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Example: Task Manager (Intermediate)
|
|
7
|
+
|
|
8
|
+
> Skills used: setup-project, create-tool, create-provider, configure-auth, configure-session, setup-redis, setup-testing, deploy-to-vercel
|
|
9
|
+
|
|
10
|
+
An authenticated task management MCP server with CRUD tools, a Redis-backed provider for storage, OAuth authentication, and Vercel deployment. Demonstrates DI with tokens, session management, per-user data isolation, and authenticated E2E testing.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Project Setup
|
|
15
|
+
|
|
16
|
+
```jsonc
|
|
17
|
+
// package.json
|
|
18
|
+
{
|
|
19
|
+
"name": "task-manager",
|
|
20
|
+
"version": "1.0.0",
|
|
21
|
+
"private": true,
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "frontmcp build --target vercel",
|
|
24
|
+
"start": "frontmcp start",
|
|
25
|
+
"test": "jest --coverage",
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@frontmcp/sdk": "^1.0.0",
|
|
29
|
+
"ioredis": "^5.4.0",
|
|
30
|
+
"zod": "^3.23.0",
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@frontmcp/testing": "^1.0.0",
|
|
34
|
+
"jest": "^29.0.0",
|
|
35
|
+
"ts-jest": "^29.0.0",
|
|
36
|
+
"typescript": "^5.4.0",
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Server Entry Point
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// src/main.ts
|
|
47
|
+
import { FrontMcp } from '@frontmcp/sdk';
|
|
48
|
+
import { TasksApp } from './tasks.app';
|
|
49
|
+
|
|
50
|
+
@FrontMcp({
|
|
51
|
+
info: { name: 'task-manager', version: '1.0.0' },
|
|
52
|
+
apps: [TasksApp],
|
|
53
|
+
auth: { mode: 'remote', provider: 'https://auth.example.com', clientId: 'my-client-id' },
|
|
54
|
+
redis: { provider: 'redis', host: process.env.REDIS_URL ?? 'localhost' },
|
|
55
|
+
})
|
|
56
|
+
export default class TaskManagerServer {}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## App Registration
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// src/tasks.app.ts
|
|
65
|
+
import { App } from '@frontmcp/sdk';
|
|
66
|
+
import { RedisTaskStoreProvider } from './providers/task-store.provider';
|
|
67
|
+
import { CreateTaskTool } from './tools/create-task.tool';
|
|
68
|
+
import { ListTasksTool } from './tools/list-tasks.tool';
|
|
69
|
+
import { UpdateTaskTool } from './tools/update-task.tool';
|
|
70
|
+
import { DeleteTaskTool } from './tools/delete-task.tool';
|
|
71
|
+
|
|
72
|
+
@App({
|
|
73
|
+
name: 'Tasks',
|
|
74
|
+
description: 'Task management with CRUD operations',
|
|
75
|
+
providers: [RedisTaskStoreProvider],
|
|
76
|
+
tools: [CreateTaskTool, ListTasksTool, UpdateTaskTool, DeleteTaskTool],
|
|
77
|
+
})
|
|
78
|
+
export class TasksApp {}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Shared Types
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// src/types/task.ts
|
|
87
|
+
export interface Task {
|
|
88
|
+
id: string;
|
|
89
|
+
title: string;
|
|
90
|
+
priority: 'low' | 'medium' | 'high';
|
|
91
|
+
status: 'pending' | 'in_progress' | 'done';
|
|
92
|
+
userId: string;
|
|
93
|
+
createdAt: string;
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Provider: Redis Task Store
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// src/providers/task-store.provider.ts
|
|
103
|
+
import { Provider } from '@frontmcp/sdk';
|
|
104
|
+
import type { Token } from '@frontmcp/di';
|
|
105
|
+
import type { Task } from '../types/task';
|
|
106
|
+
|
|
107
|
+
export interface TaskStore {
|
|
108
|
+
create(task: Omit<Task, 'id' | 'createdAt'>): Promise<Task>;
|
|
109
|
+
list(userId: string): Promise<Task[]>;
|
|
110
|
+
update(id: string, userId: string, data: Partial<Pick<Task, 'title' | 'priority' | 'status'>>): Promise<Task>;
|
|
111
|
+
delete(id: string, userId: string): Promise<void>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export const TASK_STORE: Token<TaskStore> = Symbol('TaskStore');
|
|
115
|
+
|
|
116
|
+
@Provider({ token: TASK_STORE })
|
|
117
|
+
export class RedisTaskStoreProvider implements TaskStore {
|
|
118
|
+
private redis!: import('ioredis').default;
|
|
119
|
+
|
|
120
|
+
async onInit(): Promise<void> {
|
|
121
|
+
const Redis = (await import('ioredis')).default;
|
|
122
|
+
this.redis = new Redis(process.env.REDIS_URL ?? 'redis://localhost:6379');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async create(input: Omit<Task, 'id' | 'createdAt'>): Promise<Task> {
|
|
126
|
+
const { randomUUID } = await import('@frontmcp/utils');
|
|
127
|
+
const task: Task = {
|
|
128
|
+
...input,
|
|
129
|
+
id: randomUUID(),
|
|
130
|
+
createdAt: new Date().toISOString(),
|
|
131
|
+
};
|
|
132
|
+
await this.redis.hset(`tasks:${task.userId}`, task.id, JSON.stringify(task));
|
|
133
|
+
return task;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async list(userId: string): Promise<Task[]> {
|
|
137
|
+
const entries = await this.redis.hgetall(`tasks:${userId}`);
|
|
138
|
+
return Object.values(entries).map((v) => JSON.parse(v) as Task);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async update(id: string, userId: string, data: Partial<Pick<Task, 'title' | 'priority' | 'status'>>): Promise<Task> {
|
|
142
|
+
const raw = await this.redis.hget(`tasks:${userId}`, id);
|
|
143
|
+
if (!raw) {
|
|
144
|
+
throw new Error(`Task not found: ${id}`);
|
|
145
|
+
}
|
|
146
|
+
const task: Task = { ...(JSON.parse(raw) as Task), ...data };
|
|
147
|
+
await this.redis.hset(`tasks:${userId}`, id, JSON.stringify(task));
|
|
148
|
+
return task;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async delete(id: string, userId: string): Promise<void> {
|
|
152
|
+
const removed = await this.redis.hdel(`tasks:${userId}`, id);
|
|
153
|
+
if (removed === 0) {
|
|
154
|
+
throw new Error(`Task not found: ${id}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async onDestroy(): Promise<void> {
|
|
159
|
+
await this.redis.quit();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Tool: Create Task
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// src/tools/create-task.tool.ts
|
|
170
|
+
import { Tool, ToolContext } from '@frontmcp/sdk';
|
|
171
|
+
import { z } from 'zod';
|
|
172
|
+
import { TASK_STORE } from '../providers/task-store.provider';
|
|
173
|
+
|
|
174
|
+
@Tool({
|
|
175
|
+
name: 'create_task',
|
|
176
|
+
description: 'Create a new task for the authenticated user',
|
|
177
|
+
inputSchema: {
|
|
178
|
+
title: z.string().min(1).max(200).describe('Task title'),
|
|
179
|
+
priority: z.enum(['low', 'medium', 'high']).default('medium').describe('Task priority'),
|
|
180
|
+
},
|
|
181
|
+
outputSchema: {
|
|
182
|
+
id: z.string(),
|
|
183
|
+
title: z.string(),
|
|
184
|
+
priority: z.string(),
|
|
185
|
+
status: z.string(),
|
|
186
|
+
createdAt: z.string(),
|
|
187
|
+
},
|
|
188
|
+
})
|
|
189
|
+
export class CreateTaskTool extends ToolContext {
|
|
190
|
+
async execute(input: { title: string; priority: 'low' | 'medium' | 'high' }) {
|
|
191
|
+
const store = this.get(TASK_STORE);
|
|
192
|
+
const userId = this.context.session?.userId;
|
|
193
|
+
|
|
194
|
+
if (!userId) {
|
|
195
|
+
this.fail(new Error('Authentication required'));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const task = await store.create({
|
|
199
|
+
title: input.title,
|
|
200
|
+
priority: input.priority,
|
|
201
|
+
status: 'pending',
|
|
202
|
+
userId,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
id: task.id,
|
|
207
|
+
title: task.title,
|
|
208
|
+
priority: task.priority,
|
|
209
|
+
status: task.status,
|
|
210
|
+
createdAt: task.createdAt,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Tool: List Tasks
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
// src/tools/list-tasks.tool.ts
|
|
222
|
+
import { Tool, ToolContext } from '@frontmcp/sdk';
|
|
223
|
+
import { z } from 'zod';
|
|
224
|
+
import { TASK_STORE } from '../providers/task-store.provider';
|
|
225
|
+
|
|
226
|
+
@Tool({
|
|
227
|
+
name: 'list_tasks',
|
|
228
|
+
description: 'List all tasks for the authenticated user',
|
|
229
|
+
inputSchema: {
|
|
230
|
+
status: z.enum(['pending', 'in_progress', 'done']).optional().describe('Filter by status'),
|
|
231
|
+
},
|
|
232
|
+
outputSchema: {
|
|
233
|
+
tasks: z.array(
|
|
234
|
+
z.object({
|
|
235
|
+
id: z.string(),
|
|
236
|
+
title: z.string(),
|
|
237
|
+
priority: z.string(),
|
|
238
|
+
status: z.string(),
|
|
239
|
+
createdAt: z.string(),
|
|
240
|
+
}),
|
|
241
|
+
),
|
|
242
|
+
total: z.number(),
|
|
243
|
+
},
|
|
244
|
+
})
|
|
245
|
+
export class ListTasksTool extends ToolContext {
|
|
246
|
+
async execute(input: { status?: 'pending' | 'in_progress' | 'done' }) {
|
|
247
|
+
const store = this.get(TASK_STORE);
|
|
248
|
+
const userId = this.context.session?.userId;
|
|
249
|
+
|
|
250
|
+
if (!userId) {
|
|
251
|
+
this.fail(new Error('Authentication required'));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
let tasks = await store.list(userId);
|
|
255
|
+
|
|
256
|
+
if (input.status) {
|
|
257
|
+
tasks = tasks.filter((t) => t.status === input.status);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
tasks: tasks.map((t) => ({
|
|
262
|
+
id: t.id,
|
|
263
|
+
title: t.title,
|
|
264
|
+
priority: t.priority,
|
|
265
|
+
status: t.status,
|
|
266
|
+
createdAt: t.createdAt,
|
|
267
|
+
})),
|
|
268
|
+
total: tasks.length,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Tool: Update Task
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
// src/tools/update-task.tool.ts
|
|
280
|
+
import { Tool, ToolContext } from '@frontmcp/sdk';
|
|
281
|
+
import { z } from 'zod';
|
|
282
|
+
import { TASK_STORE } from '../providers/task-store.provider';
|
|
283
|
+
|
|
284
|
+
@Tool({
|
|
285
|
+
name: 'update_task',
|
|
286
|
+
description: 'Update the status or priority of an existing task',
|
|
287
|
+
inputSchema: {
|
|
288
|
+
id: z.string().min(1).describe('Task ID to update'),
|
|
289
|
+
status: z.enum(['pending', 'in_progress', 'done']).optional().describe('New status'),
|
|
290
|
+
priority: z.enum(['low', 'medium', 'high']).optional().describe('New priority'),
|
|
291
|
+
},
|
|
292
|
+
outputSchema: {
|
|
293
|
+
id: z.string(),
|
|
294
|
+
title: z.string(),
|
|
295
|
+
priority: z.string(),
|
|
296
|
+
status: z.string(),
|
|
297
|
+
},
|
|
298
|
+
})
|
|
299
|
+
export class UpdateTaskTool extends ToolContext {
|
|
300
|
+
async execute(input: {
|
|
301
|
+
id: string;
|
|
302
|
+
status?: 'pending' | 'in_progress' | 'done';
|
|
303
|
+
priority?: 'low' | 'medium' | 'high';
|
|
304
|
+
}) {
|
|
305
|
+
const store = this.get(TASK_STORE);
|
|
306
|
+
const userId = this.context.session?.userId;
|
|
307
|
+
|
|
308
|
+
if (!userId) {
|
|
309
|
+
this.fail(new Error('Authentication required'));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
const updated = await store.update(input.id, userId, {
|
|
314
|
+
...(input.status && { status: input.status }),
|
|
315
|
+
...(input.priority && { priority: input.priority }),
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
id: updated.id,
|
|
320
|
+
title: updated.title,
|
|
321
|
+
priority: updated.priority,
|
|
322
|
+
status: updated.status,
|
|
323
|
+
};
|
|
324
|
+
} catch (err) {
|
|
325
|
+
this.fail(new Error(`Failed to update task: ${String(err)}`));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Tool: Delete Task
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
// src/tools/delete-task.tool.ts
|
|
337
|
+
import { Tool, ToolContext } from '@frontmcp/sdk';
|
|
338
|
+
import { z } from 'zod';
|
|
339
|
+
import { TASK_STORE } from '../providers/task-store.provider';
|
|
340
|
+
|
|
341
|
+
@Tool({
|
|
342
|
+
name: 'delete_task',
|
|
343
|
+
description: 'Delete a task by ID',
|
|
344
|
+
inputSchema: {
|
|
345
|
+
id: z.string().min(1).describe('Task ID to delete'),
|
|
346
|
+
},
|
|
347
|
+
outputSchema: {
|
|
348
|
+
deleted: z.boolean(),
|
|
349
|
+
id: z.string(),
|
|
350
|
+
},
|
|
351
|
+
})
|
|
352
|
+
export class DeleteTaskTool extends ToolContext {
|
|
353
|
+
async execute(input: { id: string }) {
|
|
354
|
+
const store = this.get(TASK_STORE);
|
|
355
|
+
const userId = this.context.session?.userId;
|
|
356
|
+
|
|
357
|
+
if (!userId) {
|
|
358
|
+
this.fail(new Error('Authentication required'));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
await store.delete(input.id, userId);
|
|
363
|
+
return { deleted: true, id: input.id };
|
|
364
|
+
} catch (err) {
|
|
365
|
+
this.fail(new Error(`Failed to delete task: ${String(err)}`));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Vercel Deployment Config
|
|
374
|
+
|
|
375
|
+
```jsonc
|
|
376
|
+
// vercel.json
|
|
377
|
+
{
|
|
378
|
+
"version": 2,
|
|
379
|
+
"builds": [{ "src": "api/**/*.ts", "use": "@vercel/node" }],
|
|
380
|
+
"routes": [{ "src": "/mcp/(.*)", "dest": "/api/mcp" }],
|
|
381
|
+
"env": {
|
|
382
|
+
"REDIS_URL": "@redis-url",
|
|
383
|
+
},
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## Unit Test: CreateTaskTool
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
// test/create-task.tool.spec.ts
|
|
393
|
+
import { ToolContext } from '@frontmcp/sdk';
|
|
394
|
+
import { CreateTaskTool } from '../src/tools/create-task.tool';
|
|
395
|
+
import { TASK_STORE, type TaskStore } from '../src/providers/task-store.provider';
|
|
396
|
+
import type { Task } from '../src/types/task';
|
|
397
|
+
|
|
398
|
+
describe('CreateTaskTool', () => {
|
|
399
|
+
let tool: CreateTaskTool;
|
|
400
|
+
let mockStore: jest.Mocked<TaskStore>;
|
|
401
|
+
|
|
402
|
+
beforeEach(() => {
|
|
403
|
+
tool = new CreateTaskTool();
|
|
404
|
+
mockStore = {
|
|
405
|
+
create: jest.fn(),
|
|
406
|
+
list: jest.fn(),
|
|
407
|
+
update: jest.fn(),
|
|
408
|
+
delete: jest.fn(),
|
|
409
|
+
};
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
function applyContext(userId: string | undefined): void {
|
|
413
|
+
const ctx = {
|
|
414
|
+
get: jest.fn((token: symbol) => {
|
|
415
|
+
if (token === TASK_STORE) return mockStore;
|
|
416
|
+
throw new Error(`Unknown token: ${String(token)}`);
|
|
417
|
+
}),
|
|
418
|
+
tryGet: jest.fn(),
|
|
419
|
+
fail: jest.fn((err: Error) => {
|
|
420
|
+
throw err;
|
|
421
|
+
}),
|
|
422
|
+
mark: jest.fn(),
|
|
423
|
+
notify: jest.fn(),
|
|
424
|
+
respondProgress: jest.fn(),
|
|
425
|
+
context: { session: userId ? { userId } : undefined },
|
|
426
|
+
} as unknown as ToolContext;
|
|
427
|
+
Object.assign(tool, ctx);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
it('should create a task for an authenticated user', async () => {
|
|
431
|
+
const mockTask: Task = {
|
|
432
|
+
id: 'task-001',
|
|
433
|
+
title: 'Write tests',
|
|
434
|
+
priority: 'high',
|
|
435
|
+
status: 'pending',
|
|
436
|
+
userId: 'user-123',
|
|
437
|
+
createdAt: '2026-03-27T10:00:00.000Z',
|
|
438
|
+
};
|
|
439
|
+
mockStore.create.mockResolvedValue(mockTask);
|
|
440
|
+
applyContext('user-123');
|
|
441
|
+
|
|
442
|
+
const result = await tool.execute({ title: 'Write tests', priority: 'high' });
|
|
443
|
+
|
|
444
|
+
expect(result).toEqual({
|
|
445
|
+
id: 'task-001',
|
|
446
|
+
title: 'Write tests',
|
|
447
|
+
priority: 'high',
|
|
448
|
+
status: 'pending',
|
|
449
|
+
createdAt: '2026-03-27T10:00:00.000Z',
|
|
450
|
+
});
|
|
451
|
+
expect(mockStore.create).toHaveBeenCalledWith({
|
|
452
|
+
title: 'Write tests',
|
|
453
|
+
priority: 'high',
|
|
454
|
+
status: 'pending',
|
|
455
|
+
userId: 'user-123',
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it('should fail when user is not authenticated', async () => {
|
|
460
|
+
applyContext(undefined);
|
|
461
|
+
|
|
462
|
+
await expect(tool.execute({ title: 'Write tests', priority: 'medium' })).rejects.toThrow('Authentication required');
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
## E2E Test: Task Manager
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
// test/tasks.e2e.spec.ts
|
|
473
|
+
import { McpTestClient, TestServer, TestTokenFactory } from '@frontmcp/testing';
|
|
474
|
+
import Server from '../src/main';
|
|
475
|
+
|
|
476
|
+
describe('Task Manager E2E', () => {
|
|
477
|
+
let client: McpTestClient;
|
|
478
|
+
let server: TestServer;
|
|
479
|
+
|
|
480
|
+
beforeAll(async () => {
|
|
481
|
+
server = await TestServer.start({ command: 'npx tsx src/main.ts' });
|
|
482
|
+
const tokenFactory = new TestTokenFactory();
|
|
483
|
+
const token = await tokenFactory.createTestToken({ sub: 'user-e2e', scopes: ['tasks'] });
|
|
484
|
+
client = await McpTestClient.create({ baseUrl: server.info.baseUrl }).withToken(token).buildAndConnect();
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
afterAll(async () => {
|
|
488
|
+
await client.disconnect();
|
|
489
|
+
await server.stop();
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it('should list all CRUD tools', async () => {
|
|
493
|
+
const { tools } = await client.listTools();
|
|
494
|
+
const names = tools.map((t) => t.name);
|
|
495
|
+
|
|
496
|
+
expect(names).toContain('create_task');
|
|
497
|
+
expect(names).toContain('list_tasks');
|
|
498
|
+
expect(names).toContain('update_task');
|
|
499
|
+
expect(names).toContain('delete_task');
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it('should create and list a task', async () => {
|
|
503
|
+
const createResult = await client.callTool('create_task', {
|
|
504
|
+
title: 'E2E test task',
|
|
505
|
+
priority: 'high',
|
|
506
|
+
});
|
|
507
|
+
expect(createResult).toBeSuccessful();
|
|
508
|
+
|
|
509
|
+
const listResult = await client.callTool('list_tasks', {});
|
|
510
|
+
expect(listResult).toBeSuccessful();
|
|
511
|
+
|
|
512
|
+
const parsed = JSON.parse(listResult.content[0].text);
|
|
513
|
+
expect(parsed.tasks.length).toBeGreaterThan(0);
|
|
514
|
+
expect(parsed.tasks.some((t: { title: string }) => t.title === 'E2E test task')).toBe(true);
|
|
515
|
+
});
|
|
516
|
+
});
|
|
517
|
+
```
|