@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,539 @@
1
+ ---
2
+ name: setup-testing
3
+ description: Configure and run unit and E2E tests for FrontMCP applications. Use when writing tests, setting up Jest, configuring coverage, or testing tools and resources.
4
+ tags:
5
+ - testing
6
+ - jest
7
+ - e2e
8
+ - quality
9
+ bundle:
10
+ - recommended
11
+ - full
12
+ visibility: both
13
+ priority: 5
14
+ parameters:
15
+ - name: test-type
16
+ description: Type of test to set up (unit, e2e, or both)
17
+ type: string
18
+ required: false
19
+ default: both
20
+ - name: coverage-threshold
21
+ description: Minimum coverage percentage required
22
+ type: number
23
+ required: false
24
+ default: 95
25
+ examples:
26
+ - scenario: Set up unit tests for a tool with Jest
27
+ parameters:
28
+ test-type: unit
29
+ expected-outcome: Tool execute method is tested with mocked context, assertions verify output schema
30
+ - scenario: Set up E2E tests against a running MCP server
31
+ parameters:
32
+ test-type: e2e
33
+ expected-outcome: McpTestClient connects to server, calls tools, and verifies responses with MCP matchers
34
+ - scenario: Configure full test suite with 95% coverage enforcement
35
+ parameters:
36
+ test-type: both
37
+ coverage-threshold: 95
38
+ expected-outcome: Jest runs unit and E2E tests with coverage thresholds enforced in CI
39
+ license: MIT
40
+ compatibility: Requires Node.js 18+, Jest 29+, and @frontmcp/testing for E2E tests
41
+ metadata:
42
+ category: testing
43
+ difficulty: beginner
44
+ docs: https://docs.agentfront.dev/frontmcp/testing/overview
45
+ ---
46
+
47
+ # Set Up Testing for FrontMCP Applications
48
+
49
+ This skill covers testing FrontMCP applications at three levels: unit tests for individual tools/resources/prompts, E2E tests exercising the full MCP protocol, and manual testing with `frontmcp dev`.
50
+
51
+ ## Testing Standards
52
+
53
+ FrontMCP requires:
54
+
55
+ - **95%+ coverage** across statements, branches, functions, and lines
56
+ - **All tests passing** with zero failures
57
+ - **File naming**: all test files use `.spec.ts` extension (NOT `.test.ts`)
58
+ - **E2E test naming**: use `.e2e.spec.ts` suffix
59
+ - **Performance test naming**: use `.perf.spec.ts` suffix
60
+ - **Playwright test naming**: use `.pw.spec.ts` suffix
61
+
62
+ ## Unit Testing with Jest
63
+
64
+ ### Test File Structure
65
+
66
+ Place test files next to the source file or in a `__tests__` directory:
67
+
68
+ ```
69
+ src/
70
+ tools/
71
+ my-tool.ts
72
+ __tests__/
73
+ my-tool.spec.ts # Unit tests
74
+ ```
75
+
76
+ ### Testing a Tool
77
+
78
+ Tools extend `ToolContext` and implement `execute()`. Test the execute method by providing mock inputs and verifying outputs match the MCP `CallToolResult` shape.
79
+
80
+ ```typescript
81
+ // my-tool.spec.ts
82
+ import { MyTool } from '../my-tool';
83
+
84
+ describe('MyTool', () => {
85
+ let tool: MyTool;
86
+
87
+ beforeEach(() => {
88
+ tool = new MyTool();
89
+ });
90
+
91
+ it('should return formatted result for valid input', async () => {
92
+ // Create a mock execution context
93
+ const mockContext = {
94
+ scope: {
95
+ get: jest.fn(),
96
+ tryGet: jest.fn(),
97
+ },
98
+ fail: jest.fn(),
99
+ mark: jest.fn(),
100
+ fetch: jest.fn(),
101
+ };
102
+
103
+ // Bind mock context
104
+ Object.assign(tool, mockContext);
105
+
106
+ const result = await tool.execute({ query: 'test input' });
107
+
108
+ expect(result).toEqual({
109
+ content: [{ type: 'text', text: expect.stringContaining('test input') }],
110
+ });
111
+ });
112
+
113
+ it('should handle missing optional parameters', async () => {
114
+ const mockContext = {
115
+ scope: { get: jest.fn(), tryGet: jest.fn() },
116
+ fail: jest.fn(),
117
+ mark: jest.fn(),
118
+ fetch: jest.fn(),
119
+ };
120
+ Object.assign(tool, mockContext);
121
+
122
+ const result = await tool.execute({ query: 'test' });
123
+
124
+ expect(result.content).toBeDefined();
125
+ expect(result.content.length).toBeGreaterThan(0);
126
+ });
127
+
128
+ it('should throw for invalid input', async () => {
129
+ const mockContext = {
130
+ scope: { get: jest.fn(), tryGet: jest.fn() },
131
+ fail: jest.fn(),
132
+ };
133
+ Object.assign(tool, mockContext);
134
+
135
+ await expect(tool.execute({ query: '' })).rejects.toThrow();
136
+ });
137
+ });
138
+ ```
139
+
140
+ ### Testing a Resource
141
+
142
+ Resources extend `ResourceContext` and implement `read()`. Verify the output matches the MCP `ReadResourceResult` shape.
143
+
144
+ ```typescript
145
+ // my-resource.spec.ts
146
+ import { MyResource } from '../my-resource';
147
+
148
+ describe('MyResource', () => {
149
+ it('should return resource contents', async () => {
150
+ const resource = new MyResource();
151
+ const result = await resource.read({ id: '123' });
152
+
153
+ expect(result).toEqual({
154
+ contents: [
155
+ {
156
+ uri: expect.stringMatching(/^resource:\/\//),
157
+ mimeType: 'application/json',
158
+ text: expect.any(String),
159
+ },
160
+ ],
161
+ });
162
+ });
163
+ });
164
+ ```
165
+
166
+ ### Testing a Prompt
167
+
168
+ Prompts extend `PromptContext` and implement `execute()`. Verify the output matches the MCP `GetPromptResult` shape.
169
+
170
+ ```typescript
171
+ // my-prompt.spec.ts
172
+ import { MyPrompt } from '../my-prompt';
173
+
174
+ describe('MyPrompt', () => {
175
+ it('should return a valid GetPromptResult', async () => {
176
+ const prompt = new MyPrompt();
177
+ const result = await prompt.execute({ topic: 'testing' });
178
+
179
+ expect(result).toEqual({
180
+ messages: expect.arrayContaining([
181
+ expect.objectContaining({
182
+ role: 'user',
183
+ content: expect.objectContaining({ type: 'text' }),
184
+ }),
185
+ ]),
186
+ });
187
+ });
188
+ });
189
+ ```
190
+
191
+ ### Testing Error Classes
192
+
193
+ Always verify error classes with `instanceof` checks and error codes:
194
+
195
+ ```typescript
196
+ import { ResourceNotFoundError, MCP_ERROR_CODES } from '@frontmcp/sdk';
197
+
198
+ describe('ResourceNotFoundError', () => {
199
+ it('should be instanceof ResourceNotFoundError', () => {
200
+ const error = new ResourceNotFoundError('test://resource');
201
+ expect(error).toBeInstanceOf(ResourceNotFoundError);
202
+ expect(error.mcpErrorCode).toBe(MCP_ERROR_CODES.RESOURCE_NOT_FOUND);
203
+ });
204
+
205
+ it('should produce correct JSON-RPC error', () => {
206
+ const error = new ResourceNotFoundError('test://resource');
207
+ const rpc = error.toJsonRpcError();
208
+ expect(rpc.code).toBe(-32002);
209
+ expect(rpc.data).toEqual({ uri: 'test://resource' });
210
+ });
211
+ });
212
+ ```
213
+
214
+ ### Testing Constructor Validation
215
+
216
+ Always test that constructors throw on invalid input:
217
+
218
+ ```typescript
219
+ describe('MyService constructor', () => {
220
+ it('should throw when required config is missing', () => {
221
+ expect(() => new MyService({})).toThrow();
222
+ });
223
+
224
+ it('should accept valid config', () => {
225
+ const service = new MyService({ endpoint: 'https://example.com' });
226
+ expect(service).toBeDefined();
227
+ });
228
+ });
229
+ ```
230
+
231
+ ## E2E Testing with @frontmcp/testing
232
+
233
+ The `@frontmcp/testing` library provides a full E2E testing framework with a test client, server lifecycle management, custom matchers, and fixture utilities.
234
+
235
+ ### Key Exports from @frontmcp/testing
236
+
237
+ ```typescript
238
+ import {
239
+ // Primary API (fixture-based)
240
+ test,
241
+ expect,
242
+
243
+ // Manual client API
244
+ McpTestClient,
245
+ McpTestClientBuilder,
246
+
247
+ // Server management
248
+ TestServer,
249
+
250
+ // Auth testing
251
+ TestTokenFactory,
252
+ AuthHeaders,
253
+ TestUsers,
254
+ MockOAuthServer,
255
+ MockAPIServer,
256
+ MockCimdServer,
257
+
258
+ // Assertions & matchers
259
+ McpAssertions,
260
+ mcpMatchers,
261
+
262
+ // Interceptors & mocking
263
+ DefaultMockRegistry,
264
+ DefaultInterceptorChain,
265
+ mockResponse,
266
+ interceptors,
267
+ httpMock,
268
+ httpResponse,
269
+
270
+ // Performance testing
271
+ perfTest,
272
+ MetricsCollector,
273
+ LeakDetector,
274
+ BaselineStore,
275
+ RegressionDetector,
276
+ ReportGenerator,
277
+
278
+ // Low-level client
279
+ McpClient,
280
+ McpStdioClientTransport,
281
+ } from '@frontmcp/testing';
282
+ ```
283
+
284
+ ### Install the Testing Package
285
+
286
+ ```bash
287
+ yarn add -D @frontmcp/testing
288
+ ```
289
+
290
+ ### Fixture-Based E2E Tests (Recommended)
291
+
292
+ The fixture API manages server lifecycle automatically:
293
+
294
+ ```typescript
295
+ // my-server.e2e.spec.ts
296
+ import { test, expect } from '@frontmcp/testing';
297
+
298
+ test.use({
299
+ server: './src/main.ts',
300
+ port: 3003,
301
+ });
302
+
303
+ test('server exposes expected tools', async ({ mcp }) => {
304
+ const tools = await mcp.tools.list();
305
+ expect(tools).toContainTool('create_record');
306
+ expect(tools).toContainTool('delete_record');
307
+ });
308
+
309
+ test('create_record tool returns success', async ({ mcp }) => {
310
+ const result = await mcp.tools.call('create_record', {
311
+ name: 'Test Record',
312
+ type: 'example',
313
+ });
314
+
315
+ expect(result).toBeSuccessful();
316
+ expect(result).toHaveTextContent('created');
317
+ });
318
+
319
+ test('reading a resource returns valid content', async ({ mcp }) => {
320
+ const result = await mcp.resources.read('config://server-info');
321
+
322
+ expect(result.contents).toHaveLength(1);
323
+ expect(result.contents[0]).toHaveProperty('mimeType', 'application/json');
324
+ });
325
+
326
+ test('prompts return well-formed messages', async ({ mcp }) => {
327
+ const result = await mcp.prompts.get('summarize', { topic: 'testing' });
328
+
329
+ expect(result.messages).toBeDefined();
330
+ expect(result.messages.length).toBeGreaterThan(0);
331
+ });
332
+ ```
333
+
334
+ ### Manual Client E2E Tests
335
+
336
+ For more control, use `McpTestClient` and `TestServer` directly:
337
+
338
+ ```typescript
339
+ // advanced.e2e.spec.ts
340
+ import { McpTestClient, TestServer } from '@frontmcp/testing';
341
+
342
+ describe('Advanced E2E', () => {
343
+ let server: TestServer;
344
+ let client: McpTestClient;
345
+
346
+ beforeAll(async () => {
347
+ server = await TestServer.start({
348
+ command: 'npx tsx src/main.ts',
349
+ port: 3004,
350
+ });
351
+
352
+ client = await McpTestClient.create({ baseUrl: server.info.baseUrl })
353
+ .withTransport('streamable-http')
354
+ .buildAndConnect();
355
+ });
356
+
357
+ afterAll(async () => {
358
+ await client.disconnect();
359
+ await server.stop();
360
+ });
361
+
362
+ it('should list tools after initialization', async () => {
363
+ const tools = await client.tools.list();
364
+ expect(tools.length).toBeGreaterThan(0);
365
+ });
366
+
367
+ it('should handle tool errors gracefully', async () => {
368
+ const result = await client.tools.call('nonexistent_tool', {});
369
+ expect(result).toBeError();
370
+ });
371
+ });
372
+ ```
373
+
374
+ ### Testing with Authentication
375
+
376
+ ```typescript
377
+ import { test, expect, TestTokenFactory } from '@frontmcp/testing';
378
+
379
+ test.use({
380
+ server: './src/main.ts',
381
+ port: 3005,
382
+ auth: {
383
+ issuer: 'https://auth.example.com/',
384
+ audience: 'https://api.example.com',
385
+ },
386
+ });
387
+
388
+ test('authenticated tool call succeeds', async ({ mcp, auth }) => {
389
+ const token = await auth.createToken({ sub: 'user-123', scopes: ['tools:read'] });
390
+ mcp.setAuthToken(token);
391
+
392
+ const result = await mcp.tools.call('get_user_profile', {});
393
+ expect(result).toBeSuccessful();
394
+ });
395
+
396
+ test('unauthenticated call is rejected', async ({ mcp }) => {
397
+ mcp.clearAuthToken();
398
+
399
+ const result = await mcp.tools.call('get_user_profile', {});
400
+ expect(result).toBeError();
401
+ });
402
+ ```
403
+
404
+ ## Custom MCP Matchers
405
+
406
+ `@frontmcp/testing` provides Jest matchers tailored for MCP responses. Import `expect` from `@frontmcp/testing` instead of from Jest:
407
+
408
+ ```typescript
409
+ import { expect } from '@frontmcp/testing';
410
+ ```
411
+
412
+ | Matcher | Asserts |
413
+ | ------------------------- | ----------------------------------------------------- |
414
+ | `toContainTool(name)` | Tools list includes a tool with the given name |
415
+ | `toContainResource(uri)` | Resources list includes a resource with the given URI |
416
+ | `toContainPrompt(name)` | Prompts list includes a prompt with the given name |
417
+ | `toBeSuccessful()` | Tool call result is not an error |
418
+ | `toBeError()` | Tool call result is an MCP error |
419
+ | `toHaveTextContent(text)` | Result contains text content matching the string |
420
+ | `toHaveMimeType(mime)` | Resource content has the expected MIME type |
421
+
422
+ ## Running Tests with Nx
423
+
424
+ FrontMCP uses Nx as its build system. Run tests with these commands:
425
+
426
+ ```bash
427
+ # Run all tests for a specific library
428
+ nx test sdk
429
+
430
+ # Run tests for a specific file
431
+ nx test my-app --testFile=src/tools/__tests__/my-tool.spec.ts
432
+
433
+ # Run all tests across the monorepo
434
+ nx run-many -t test
435
+
436
+ # Run with coverage
437
+ nx test sdk --coverage
438
+
439
+ # Run only E2E tests (by pattern)
440
+ nx test sdk --testPathPattern='\.e2e\.spec\.ts$'
441
+
442
+ # Run a single test by name
443
+ nx test sdk --testNamePattern='should return formatted output'
444
+ ```
445
+
446
+ ## Jest Configuration
447
+
448
+ Each library has its own `jest.config.ts`. Coverage thresholds are enforced per library:
449
+
450
+ ```typescript
451
+ // jest.config.ts
452
+ export default {
453
+ displayName: 'my-lib',
454
+ preset: '../../jest.preset.js',
455
+ transform: {
456
+ '^.+\\.tsx?$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
457
+ },
458
+ coverageThreshold: {
459
+ global: {
460
+ statements: 95,
461
+ branches: 95,
462
+ functions: 95,
463
+ lines: 95,
464
+ },
465
+ },
466
+ };
467
+ ```
468
+
469
+ ## Manual Testing with frontmcp dev
470
+
471
+ For interactive development and manual testing, use the CLI:
472
+
473
+ ```bash
474
+ # Start the dev server with hot reload
475
+ frontmcp dev
476
+
477
+ # Start on a specific port
478
+ frontmcp dev --port 4000
479
+
480
+ # The dev server exposes your MCP server over Streamable HTTP
481
+ # Connect any MCP client (Claude Desktop, cursor, etc.) to test interactively
482
+ ```
483
+
484
+ This is useful for:
485
+
486
+ - Verifying tool behavior with a real AI client
487
+ - Testing the full request/response cycle
488
+ - Debugging issues that are hard to reproduce in automated tests
489
+ - Validating authentication flows end-to-end
490
+
491
+ ## Cleanup Before Committing
492
+
493
+ Always run the unused import cleanup script on changed files:
494
+
495
+ ```bash
496
+ # Remove unused imports from files changed vs main
497
+ node scripts/fix-unused-imports.mjs
498
+
499
+ # Custom base branch
500
+ node scripts/fix-unused-imports.mjs feature/my-branch
501
+ ```
502
+
503
+ ## Testing Patterns Summary
504
+
505
+ | What to Test | How | File Suffix |
506
+ | ------------------------ | ------------------------------------------------- | --------------- |
507
+ | Tool execute logic | Unit test with mock context | `.spec.ts` |
508
+ | Resource read logic | Unit test with mock params | `.spec.ts` |
509
+ | Prompt output shape | Unit test verifying GetPromptResult | `.spec.ts` |
510
+ | Full MCP protocol flow | E2E with McpTestClient | `.e2e.spec.ts` |
511
+ | Error handling | Unit test verifying specific error classes/codes | `.spec.ts` |
512
+ | Plugin behavior | Unit test providers + integration via test server | `.spec.ts` |
513
+ | Performance regression | Perf tests with MetricsCollector | `.perf.spec.ts` |
514
+ | Playwright browser tests | UI tests with Playwright | `.pw.spec.ts` |
515
+ | Constructor validation | Unit test verifying throws on invalid input | `.spec.ts` |
516
+
517
+ ## Common Mistakes
518
+
519
+ - **Using `.test.ts` file extension** -- all test files must use `.spec.ts`. The Nx and Jest configurations expect this convention.
520
+ - **Testing implementation details** -- test inputs and outputs, not internal method calls. Tools should be tested through their `execute` interface.
521
+ - **Skipping constructor validation tests** -- always test that constructors throw on invalid input.
522
+ - **Skipping error `instanceof` checks** -- verify that thrown errors are instances of the correct error class, not just that an error was thrown.
523
+ - **Using test ID prefixes** -- do not use prefixes like "PT-001" in test names. Use descriptive names like "should return formatted output for valid input".
524
+ - **Falling below 95% coverage** -- the CI pipeline enforces coverage thresholds. Run `nx test <lib> --coverage` locally before pushing.
525
+ - **Using `any` in test mocks** -- use `unknown` or properly typed mocks. Follow the strict TypeScript guidelines.
526
+
527
+ ## Reference
528
+
529
+ - Testing package: [`@frontmcp/testing`](https://docs.agentfront.dev/frontmcp/testing/overview)
530
+ - Test client: `McpTestClient` — import from `@frontmcp/testing`
531
+ - Test client builder: `McpTestClient.builder()` — fluent API for test setup
532
+ - MCP matchers: `toContainTool()`, `toBeSuccessful()` — import from `@frontmcp/testing`
533
+ - Test fixtures: `createTestFixture()` — import from `@frontmcp/testing`
534
+ - Test server: `TestServer` — import from `@frontmcp/testing`
535
+ - Performance testing: `perfTest()`, `MetricsCollector` — import from `@frontmcp/testing`
536
+ - Auth testing: `TestTokenFactory`, `MockOAuthServer` — import from `@frontmcp/testing`
537
+ - Interceptors: `TestInterceptor` — import from `@frontmcp/testing`
538
+ - HTTP mocking: `HttpMock` — import from `@frontmcp/testing`
539
+ - [Source code on GitHub](https://github.com/agentfront/frontmcp/tree/main/libs/testing)
@@ -0,0 +1,88 @@
1
+ # Testing with Authentication
2
+
3
+ ```typescript
4
+ import { McpTestClient, TestServer, TestTokenFactory, MockOAuthServer } from '@frontmcp/testing';
5
+ import Server from '../src/main';
6
+
7
+ describe('Authenticated Server', () => {
8
+ let server: TestServer;
9
+ let tokenFactory: TestTokenFactory;
10
+
11
+ beforeAll(async () => {
12
+ server = await TestServer.create(Server);
13
+ tokenFactory = new TestTokenFactory({
14
+ issuer: 'https://test-idp.example.com',
15
+ audience: 'my-api',
16
+ });
17
+ });
18
+
19
+ afterAll(async () => {
20
+ await server.dispose();
21
+ });
22
+
23
+ it('should reject unauthenticated requests', async () => {
24
+ const client = await server.connect();
25
+ const result = await client.callTool('protected_tool', {});
26
+ expect(result.isError).toBe(true);
27
+ await client.close();
28
+ });
29
+
30
+ it('should accept valid token', async () => {
31
+ const token = await tokenFactory.createToken({
32
+ sub: 'user-123',
33
+ scopes: ['read', 'write'],
34
+ });
35
+
36
+ const client = await server.connect({ authToken: token });
37
+ const result = await client.callTool('protected_tool', { data: 'test' });
38
+ expect(result).toBeSuccessful();
39
+ await client.close();
40
+ });
41
+
42
+ it('should enforce role-based access', async () => {
43
+ const adminToken = await tokenFactory.createToken({
44
+ sub: 'admin-1',
45
+ roles: ['admin'],
46
+ });
47
+ const userToken = await tokenFactory.createToken({
48
+ sub: 'user-1',
49
+ roles: ['user'],
50
+ });
51
+
52
+ const adminClient = await server.connect({ authToken: adminToken });
53
+ const adminResult = await adminClient.callTool('admin_only_tool', {});
54
+ expect(adminResult).toBeSuccessful();
55
+
56
+ const userClient = await server.connect({ authToken: userToken });
57
+ const userResult = await userClient.callTool('admin_only_tool', {});
58
+ expect(userResult.isError).toBe(true);
59
+
60
+ await adminClient.close();
61
+ await userClient.close();
62
+ });
63
+ });
64
+
65
+ describe('OAuth Flow', () => {
66
+ let mockOAuth: MockOAuthServer;
67
+
68
+ beforeAll(async () => {
69
+ mockOAuth = await MockOAuthServer.create({
70
+ issuer: 'https://test-idp.example.com',
71
+ port: 9999,
72
+ });
73
+ });
74
+
75
+ afterAll(async () => {
76
+ await mockOAuth.close();
77
+ });
78
+
79
+ it('should complete OAuth authorization code flow', async () => {
80
+ const { authorizationUrl } = await mockOAuth.startFlow({
81
+ clientId: 'test-client',
82
+ redirectUri: 'http://localhost:3001/callback',
83
+ scopes: ['openid', 'profile'],
84
+ });
85
+ expect(authorizationUrl).toContain('code=');
86
+ });
87
+ });
88
+ ```
@@ -0,0 +1,57 @@
1
+ # Testing Browser Build
2
+
3
+ After building with `frontmcp build --target browser`, validate the output:
4
+
5
+ ```typescript
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+
9
+ const DIST_DIR = path.resolve(__dirname, '../dist/browser');
10
+
11
+ describe('Browser Build', () => {
12
+ it('should produce browser-compatible bundle', () => {
13
+ const files = fs.readdirSync(DIST_DIR);
14
+ expect(files.some((f) => f.endsWith('.js'))).toBe(true);
15
+ });
16
+
17
+ it('should not contain Node.js-only modules', () => {
18
+ const bundle = fs.readFileSync(path.join(DIST_DIR, 'index.js'), 'utf-8');
19
+ // These should be polyfilled or excluded
20
+ expect(bundle).not.toContain("require('fs')");
21
+ expect(bundle).not.toContain("require('child_process')");
22
+ });
23
+
24
+ it('should export expected functions', async () => {
25
+ // Use dynamic import to test ESM compatibility
26
+ const mod = await import(path.join(DIST_DIR, 'index.js'));
27
+ expect(mod).toBeDefined();
28
+ });
29
+ });
30
+ ```
31
+
32
+ ## Testing with Playwright (.pw.spec.ts)
33
+
34
+ ```typescript
35
+ import { test, expect } from '@playwright/test';
36
+
37
+ test('browser MCP client loads tools', async ({ page }) => {
38
+ await page.goto('http://localhost:3000');
39
+
40
+ // Wait for tools to load from MCP server
41
+ await page.waitForSelector('[data-testid="tool-list"]');
42
+
43
+ const tools = await page.locator('[data-testid="tool-item"]').count();
44
+ expect(tools).toBeGreaterThan(0);
45
+ });
46
+
47
+ test('browser client can call a tool', async ({ page }) => {
48
+ await page.goto('http://localhost:3000');
49
+
50
+ await page.fill('[data-testid="input-a"]', '5');
51
+ await page.fill('[data-testid="input-b"]', '3');
52
+ await page.click('[data-testid="call-tool"]');
53
+
54
+ const result = await page.textContent('[data-testid="result"]');
55
+ expect(result).toContain('8');
56
+ });
57
+ ```