@prmichaelsen/task-mcp 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/.env.example +19 -0
  2. package/AGENT.md +1165 -0
  3. package/CHANGELOG.md +72 -0
  4. package/agent/commands/acp.commit.md +511 -0
  5. package/agent/commands/acp.init.md +376 -0
  6. package/agent/commands/acp.package-install.md +347 -0
  7. package/agent/commands/acp.proceed.md +311 -0
  8. package/agent/commands/acp.report.md +392 -0
  9. package/agent/commands/acp.status.md +280 -0
  10. package/agent/commands/acp.sync.md +323 -0
  11. package/agent/commands/acp.update.md +301 -0
  12. package/agent/commands/acp.validate.md +385 -0
  13. package/agent/commands/acp.version-check-for-updates.md +275 -0
  14. package/agent/commands/acp.version-check.md +190 -0
  15. package/agent/commands/acp.version-update.md +288 -0
  16. package/agent/commands/command.template.md +273 -0
  17. package/agent/commands/git.commit.md +511 -0
  18. package/agent/commands/git.init.md +513 -0
  19. package/agent/design/.gitkeep +0 -0
  20. package/agent/design/acp-task-execution-requirements.md +555 -0
  21. package/agent/design/api-dto-design.md +394 -0
  22. package/agent/design/code-extraction-guide.md +827 -0
  23. package/agent/design/design.template.md +136 -0
  24. package/agent/design/requirements.template.md +387 -0
  25. package/agent/design/rest-api-integration.md +489 -0
  26. package/agent/design/sdk-export-requirements.md +549 -0
  27. package/agent/milestones/.gitkeep +0 -0
  28. package/agent/milestones/milestone-1-{title}.template.md +206 -0
  29. package/agent/milestones/milestone-2-task-infrastructure.md +232 -0
  30. package/agent/milestones/milestone-4-autonomous-execution.md +235 -0
  31. package/agent/patterns/.gitkeep +0 -0
  32. package/agent/patterns/bootstrap.md +1271 -0
  33. package/agent/patterns/bootstrap.template.md +1237 -0
  34. package/agent/patterns/pattern.template.md +364 -0
  35. package/agent/progress.template.yaml +158 -0
  36. package/agent/progress.yaml +375 -0
  37. package/agent/scripts/check-for-updates.sh +88 -0
  38. package/agent/scripts/install.sh +157 -0
  39. package/agent/scripts/uninstall.sh +75 -0
  40. package/agent/scripts/update.sh +139 -0
  41. package/agent/scripts/version.sh +35 -0
  42. package/agent/tasks/.gitkeep +0 -0
  43. package/agent/tasks/task-1-{title}.template.md +225 -0
  44. package/agent/tasks/task-86-task-data-model-schemas.md +143 -0
  45. package/agent/tasks/task-87-task-database-service.md +220 -0
  46. package/agent/tasks/task-88-firebase-client-wrapper.md +139 -0
  47. package/agent/tasks/task-88-task-execution-engine.md +277 -0
  48. package/agent/tasks/task-89-mcp-server-implementation.md +197 -0
  49. package/agent/tasks/task-90-build-configuration.md +146 -0
  50. package/agent/tasks/task-91-deployment-configuration.md +128 -0
  51. package/coverage/base.css +224 -0
  52. package/coverage/block-navigation.js +87 -0
  53. package/coverage/favicon.png +0 -0
  54. package/coverage/index.html +191 -0
  55. package/coverage/lcov-report/base.css +224 -0
  56. package/coverage/lcov-report/block-navigation.js +87 -0
  57. package/coverage/lcov-report/favicon.png +0 -0
  58. package/coverage/lcov-report/index.html +191 -0
  59. package/coverage/lcov-report/prettify.css +1 -0
  60. package/coverage/lcov-report/prettify.js +2 -0
  61. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  62. package/coverage/lcov-report/sorter.js +210 -0
  63. package/coverage/lcov-report/src/client.ts.html +1030 -0
  64. package/coverage/lcov-report/src/constant/collections.ts.html +469 -0
  65. package/coverage/lcov-report/src/constant/index.html +116 -0
  66. package/coverage/lcov-report/src/dto/index.html +116 -0
  67. package/coverage/lcov-report/src/dto/transformers.ts.html +568 -0
  68. package/coverage/lcov-report/src/index.html +146 -0
  69. package/coverage/lcov-report/src/schemas/index.html +116 -0
  70. package/coverage/lcov-report/src/schemas/task.ts.html +547 -0
  71. package/coverage/lcov-report/src/server-factory.ts.html +418 -0
  72. package/coverage/lcov-report/src/server.ts.html +289 -0
  73. package/coverage/lcov-report/src/services/index.html +116 -0
  74. package/coverage/lcov-report/src/services/task-database.service.ts.html +1495 -0
  75. package/coverage/lcov-report/src/tools/index.html +236 -0
  76. package/coverage/lcov-report/src/tools/index.ts.html +292 -0
  77. package/coverage/lcov-report/src/tools/task-add-message.ts.html +277 -0
  78. package/coverage/lcov-report/src/tools/task-complete-task-item.ts.html +343 -0
  79. package/coverage/lcov-report/src/tools/task-create-milestone.ts.html +286 -0
  80. package/coverage/lcov-report/src/tools/task-create-task-item.ts.html +358 -0
  81. package/coverage/lcov-report/src/tools/task-get-next-step.ts.html +460 -0
  82. package/coverage/lcov-report/src/tools/task-get-status.ts.html +316 -0
  83. package/coverage/lcov-report/src/tools/task-report-completion.ts.html +343 -0
  84. package/coverage/lcov-report/src/tools/task-update-progress.ts.html +232 -0
  85. package/coverage/lcov.info +974 -0
  86. package/coverage/prettify.css +1 -0
  87. package/coverage/prettify.js +2 -0
  88. package/coverage/sort-arrow-sprite.png +0 -0
  89. package/coverage/sorter.js +210 -0
  90. package/coverage/src/client.ts.html +1030 -0
  91. package/coverage/src/constant/collections.ts.html +469 -0
  92. package/coverage/src/constant/index.html +116 -0
  93. package/coverage/src/dto/index.html +116 -0
  94. package/coverage/src/dto/transformers.ts.html +568 -0
  95. package/coverage/src/index.html +146 -0
  96. package/coverage/src/schemas/index.html +116 -0
  97. package/coverage/src/schemas/task.ts.html +547 -0
  98. package/coverage/src/server-factory.ts.html +418 -0
  99. package/coverage/src/server.ts.html +289 -0
  100. package/coverage/src/services/index.html +116 -0
  101. package/coverage/src/services/task-database.service.ts.html +1495 -0
  102. package/coverage/src/tools/index.html +236 -0
  103. package/coverage/src/tools/index.ts.html +292 -0
  104. package/coverage/src/tools/task-add-message.ts.html +277 -0
  105. package/coverage/src/tools/task-complete-task-item.ts.html +343 -0
  106. package/coverage/src/tools/task-create-milestone.ts.html +286 -0
  107. package/coverage/src/tools/task-create-task-item.ts.html +358 -0
  108. package/coverage/src/tools/task-get-next-step.ts.html +460 -0
  109. package/coverage/src/tools/task-get-status.ts.html +316 -0
  110. package/coverage/src/tools/task-report-completion.ts.html +343 -0
  111. package/coverage/src/tools/task-update-progress.ts.html +232 -0
  112. package/firestore.rules +95 -0
  113. package/jest.config.js +31 -0
  114. package/package.json +67 -0
  115. package/src/client.spec.ts +199 -0
  116. package/src/client.ts +315 -0
  117. package/src/constant/collections.ts +128 -0
  118. package/src/dto/index.ts +47 -0
  119. package/src/dto/task-api.dto.ts +219 -0
  120. package/src/dto/transformers.spec.ts +462 -0
  121. package/src/dto/transformers.ts +161 -0
  122. package/src/schemas/task.ts +154 -0
  123. package/src/server-factory.spec.ts +70 -0
  124. package/src/server-factory.ts +111 -0
  125. package/src/server.ts +68 -0
  126. package/src/services/task-database.service.e2e.ts +116 -0
  127. package/src/services/task-database.service.spec.ts +479 -0
  128. package/src/services/task-database.service.ts +470 -0
  129. package/src/test-schemas.ts +161 -0
  130. package/src/tools/index.ts +69 -0
  131. package/src/tools/task-add-message.ts +64 -0
  132. package/src/tools/task-complete-task-item.ts +86 -0
  133. package/src/tools/task-create-milestone.ts +67 -0
  134. package/src/tools/task-create-task-item.ts +91 -0
  135. package/src/tools/task-get-next-step.spec.ts +136 -0
  136. package/src/tools/task-get-next-step.ts +125 -0
  137. package/src/tools/task-get-status.spec.ts +213 -0
  138. package/src/tools/task-get-status.ts +77 -0
  139. package/src/tools/task-report-completion.ts +86 -0
  140. package/src/tools/task-update-progress.ts +49 -0
  141. package/src/tools/tools.spec.ts +194 -0
  142. package/tsconfig.json +31 -0
@@ -0,0 +1,1271 @@
1
+ # MCP Server Bootstrap Pattern
2
+
3
+ ## Overview
4
+
5
+ This document describes the organizational patterns for bootstrapping a new MCP (Model Context Protocol) server library. The focus is on **structure and organization** rather than specific tools or technologies. This pattern is compatible with [`mcp-auth`](https://github.com/prmichaelsen/mcp-auth) multi-tenant authentication patterns.
6
+
7
+ ## Core Principles
8
+
9
+ 1. **Separation of Concerns**: Clear boundaries between server logic, business logic, and infrastructure
10
+ 2. **Multi-Tenant Ready**: Architecture supports per-user/per-tenant server instances
11
+ 3. **Type Safety**: Strong typing throughout with TypeScript
12
+ 4. **Modular Tools**: Each tool is self-contained with definition and handler
13
+ 5. **Dual Build Strategy**: Bundle for CLI, preserve modules for library usage
14
+ 6. **ESM-First**: Modern ES modules with proper `.js` extensions in imports
15
+
16
+ ## Project Structure
17
+
18
+ ```
19
+ project-root/
20
+ ├── src/
21
+ │ ├── index.ts # CLI entry point (bundled)
22
+ │ ├── server.ts # Server class (for standalone)
23
+ │ ├── server-factory.ts # Factory function (for multi-tenant)
24
+ │ ├── client.ts # External API client wrapper
25
+ │ ├── types.ts # Shared type definitions
26
+ │ │
27
+ │ ├── tools/ # Tool definitions
28
+ │ │ ├── index.ts # Tool exports
29
+ │ │ ├── tool-one.ts # Individual tool (definition + handler)
30
+ │ │ ├── tool-two.ts
31
+ │ │ └── ...
32
+ │ │
33
+ │ ├── types/ # Type definitions (optional subdirectory)
34
+ │ │ ├── mcp.ts # MCP-specific types
35
+ │ │ ├── api.ts # External API types
36
+ │ │ └── ...
37
+ │ │
38
+ │ └── utils/ # Utilities
39
+ │ ├── logger.ts # Logging (stdio-safe for MCP)
40
+ │ ├── error-serializer.ts # Error handling
41
+ │ └── ...
42
+
43
+ ├── agent/ # Documentation & planning
44
+ │ ├── patterns/ # Architecture patterns
45
+ │ ├── tasks/ # Task tracking
46
+ │ └── ...
47
+
48
+ ├── package.json # Package configuration
49
+ ├── tsconfig.json # TypeScript configuration
50
+ ├── esbuild.build.js # Build script
51
+ ├── esbuild.watch.js # Watch mode script
52
+ ├── .gitignore
53
+ └── README.md
54
+ ```
55
+
56
+ ## Configuration Files
57
+
58
+ ### package.json Structure
59
+
60
+ For a simple MCP server:
61
+
62
+ ```json
63
+ {
64
+ "name": "remember-mcp",
65
+ "version": "0.1.0",
66
+ "description": "Multi-tenant memory system MCP server with vector search and relationships",
67
+ "main": "dist/server.js",
68
+ "type": "module",
69
+ "repository": {
70
+ "type": "git",
71
+ "url": "git+https://github.com/prmichaelsen/remember-mcp.git"
72
+ },
73
+ "bugs": {
74
+ "url": "https://github.com/prmichaelsen/remember-mcp/issues"
75
+ },
76
+ "homepage": "https://github.com/prmichaelsen/remember-mcp#readme",
77
+ "scripts": {
78
+ "build": "node esbuild.build.js",
79
+ "build:watch": "node esbuild.watch.js",
80
+ "clean": "rm -rf dist",
81
+ "dev": "tsx watch src/server.ts",
82
+ "start": "node dist/server.js",
83
+ "test": "jest",
84
+ "test:watch": "jest --watch",
85
+ "test:e2e": "jest --config jest.e2e.config.js",
86
+ "test:e2e:watch": "jest --config jest.e2e.config.js --watch",
87
+ "test:all": "npm test && npm run test:e2e",
88
+ "lint": "eslint src/**/*.ts",
89
+ "typecheck": "tsc --noEmit",
90
+ "prepublishOnly": "npm run clean && npm run build"
91
+ },
92
+ "keywords": [
93
+ "mcp",
94
+ "memory",
95
+ "vector-search",
96
+ "weaviate",
97
+ "firebase"
98
+ ],
99
+ "author": "Patrick Michaelsen",
100
+ "license": "MIT"
101
+ }
102
+ ```
103
+
104
+ For a library with multiple exports:
105
+
106
+ ```json
107
+ {
108
+ "name": "@scope/package-name",
109
+ "version": "1.0.0",
110
+ "description": "MCP server for [purpose]",
111
+ "type": "module",
112
+ "main": "dist/index.js",
113
+ "types": "dist/index.d.ts",
114
+
115
+ "repository": {
116
+ "type": "git",
117
+ "url": "git+https://github.com/username/repo.git"
118
+ },
119
+ "bugs": {
120
+ "url": "https://github.com/username/repo/issues"
121
+ },
122
+ "homepage": "https://github.com/username/repo#readme",
123
+
124
+ "exports": {
125
+ ".": {
126
+ "types": "./dist/index.d.ts",
127
+ "import": "./dist/index.js"
128
+ },
129
+ "./factory": {
130
+ "types": "./dist/server-factory.d.ts",
131
+ "import": "./dist/server-factory.js"
132
+ },
133
+ "./client": {
134
+ "types": "./dist/client.d.ts",
135
+ "import": "./dist/client.js"
136
+ },
137
+ "./tools": {
138
+ "types": "./dist/tools/index.d.ts",
139
+ "import": "./dist/tools/index.js"
140
+ },
141
+ "./types": {
142
+ "types": "./dist/types.d.ts",
143
+ "import": "./dist/types.js"
144
+ }
145
+ },
146
+
147
+ "files": [
148
+ "dist",
149
+ "README.md",
150
+ "LICENSE"
151
+ ],
152
+
153
+ "scripts": {
154
+ "build": "npm run build:types && npm run build:bundle",
155
+ "build:types": "tsc --emitDeclarationOnly",
156
+ "build:bundle": "node esbuild.build.js",
157
+ "build:watch": "node esbuild.watch.js",
158
+ "start": "node dist/index.js",
159
+ "clean": "rm -rf dist",
160
+ "test": "jest",
161
+ "test:watch": "jest --watch",
162
+ "test:e2e": "jest --config jest.e2e.config.js",
163
+ "test:e2e:watch": "jest --config jest.e2e.config.js --watch",
164
+ "test:all": "npm test && npm run test:e2e",
165
+ "prepublishOnly": "npm run clean && npm run build"
166
+ },
167
+
168
+ "keywords": [
169
+ "mcp",
170
+ "model-context-protocol",
171
+ "[domain-specific-keywords]"
172
+ ],
173
+
174
+ "dependencies": {
175
+ "@modelcontextprotocol/sdk": "^1.0.0"
176
+ },
177
+
178
+ "devDependencies": {
179
+ "@types/jest": "^30.0.0",
180
+ "@types/node": "^20.0.0",
181
+ "esbuild": "^0.25.0",
182
+ "jest": "^30.0.0",
183
+ "ts-jest": "^29.0.0",
184
+ "typescript": "^5.3.0"
185
+ },
186
+
187
+ "engines": {
188
+ "node": ">=18.0.0"
189
+ }
190
+ }
191
+ ```
192
+
193
+ **Key Points:**
194
+ - `"type": "module"` required for ESM
195
+ - `repository`, `bugs`, `homepage` for GitHub integration
196
+ - `main` points to the built entry file
197
+ - `exports` field for libraries with multiple entry points
198
+ - `files` array specifies what to publish to npm
199
+ - `build:watch` script for development
200
+ - `clean` script removes build artifacts
201
+ - `prepublishOnly` ensures clean build before publishing
202
+ - `test` scripts for jest (unit and e2e)
203
+ - `author` field for attribution
204
+ - `engines` specifies minimum Node.js version
205
+
206
+ ### tsconfig.json Structure
207
+
208
+ ```json
209
+ {
210
+ "compilerOptions": {
211
+ "target": "ES2022",
212
+ "module": "Node16",
213
+ "moduleResolution": "Node16",
214
+ "lib": ["ES2022"],
215
+ "types": ["node"],
216
+
217
+ "outDir": "./dist",
218
+ "rootDir": "./src",
219
+
220
+ "strict": true,
221
+ "esModuleInterop": true,
222
+ "skipLibCheck": true,
223
+ "forceConsistentCasingInFileNames": true,
224
+ "resolveJsonModule": true,
225
+
226
+ "declaration": true,
227
+ "declarationMap": true,
228
+ "sourceMap": true,
229
+
230
+ "baseUrl": ".",
231
+ "paths": {
232
+ "@/*": ["src/*"]
233
+ }
234
+ },
235
+
236
+ "include": ["src/**/*"],
237
+ "exclude": ["node_modules", "dist"]
238
+ }
239
+ ```
240
+
241
+ **Key Points:**
242
+ - `Node16` module resolution for proper ESM support
243
+ - `declaration: true` for type definitions
244
+ - `strict: true` for type safety
245
+ - Source maps for debugging
246
+ - `baseUrl` and `paths` for module name mapping (`@/` → `src/`)
247
+
248
+ ### Jest Configuration
249
+
250
+ For projects with colocated tests (`.spec.ts` and `.e2e.ts` files alongside source code):
251
+
252
+ #### jest.config.js - Unit Tests
253
+
254
+ ```javascript
255
+ module.exports = {
256
+ preset: 'ts-jest',
257
+ testEnvironment: 'node',
258
+ roots: ['<rootDir>/src'],
259
+ testMatch: ['**/*.spec.ts'],
260
+ moduleFileExtensions: ['ts', 'js'],
261
+ collectCoverage: true,
262
+ coverageDirectory: 'coverage',
263
+ coverageReporters: ['text', 'lcov', 'html'],
264
+ collectCoverageFrom: [
265
+ 'src/**/*.ts',
266
+ '!src/**/*.d.ts',
267
+ '!src/**/*.spec.ts',
268
+ '!src/**/*.e2e.ts',
269
+ '!src/index.ts', // Barrel export only
270
+ '!src/types/**/*.ts', // Type definitions only
271
+ ],
272
+ moduleNameMapper: {
273
+ '^@/(.*)$': '<rootDir>/src/$1',
274
+ },
275
+ };
276
+ ```
277
+
278
+ #### jest.e2e.config.js - E2E Tests
279
+
280
+ ```javascript
281
+ module.exports = {
282
+ preset: 'ts-jest',
283
+ testEnvironment: 'node',
284
+ testMatch: ['**/*.e2e.ts'],
285
+ testTimeout: 30000, // 30 seconds for real API calls
286
+ roots: ['<rootDir>/src'],
287
+ collectCoverageFrom: [
288
+ 'src/**/*.ts',
289
+ '!src/**/*.spec.ts',
290
+ '!src/**/*.e2e.ts',
291
+ '!src/types/**/*.ts',
292
+ '!src/index.ts',
293
+ ],
294
+ moduleNameMapper: {
295
+ '^@/(.*)$': '<rootDir>/src/$1',
296
+ },
297
+ };
298
+ ```
299
+
300
+ **Key Points:**
301
+ - Separate configs for unit tests (`.spec.ts`) and e2e tests (`.e2e.ts`)
302
+ - E2E tests have longer timeout for real API calls
303
+ - Coverage excludes test files and type definitions
304
+ - `moduleNameMapper` matches TypeScript path aliases
305
+ - Tests are colocated with source files in `src/`
306
+
307
+ **Package.json Scripts:**
308
+ ```json
309
+ {
310
+ "scripts": {
311
+ "test": "jest --config jest.config.js",
312
+ "test:e2e": "jest --config jest.e2e.config.js",
313
+ "test:watch": "jest --config jest.config.js --watch",
314
+ "test:coverage": "jest --config jest.config.js --coverage"
315
+ },
316
+ "devDependencies": {
317
+ "@types/jest": "^29.0.0",
318
+ "jest": "^29.0.0",
319
+ "ts-jest": "^29.0.0"
320
+ }
321
+ }
322
+ ```
323
+
324
+ ### esbuild.build.js Structure
325
+
326
+ For a simple MCP server (single entry point):
327
+
328
+ ```javascript
329
+ import * as esbuild from 'esbuild';
330
+ import { execSync } from 'child_process';
331
+
332
+ await esbuild.build({
333
+ entryPoints: ['src/server.ts'],
334
+ bundle: true,
335
+ platform: 'node',
336
+ target: 'node20',
337
+ format: 'esm',
338
+ outfile: 'dist/server.js',
339
+ sourcemap: true,
340
+ external: [
341
+ 'weaviate-client',
342
+ 'firebase-admin',
343
+ '@modelcontextprotocol/sdk'
344
+ ],
345
+ banner: {
346
+ js: "import { createRequire } from 'module'; const require = createRequire(import.meta.url);"
347
+ },
348
+ alias: {
349
+ '@': './src'
350
+ }
351
+ });
352
+
353
+ console.log('✓ JavaScript bundle built');
354
+
355
+ // Generate TypeScript declarations
356
+ console.log('Generating TypeScript declarations...');
357
+ try {
358
+ execSync('tsc --emitDeclarationOnly --outDir dist', { stdio: 'inherit' });
359
+ console.log('✓ TypeScript declarations generated');
360
+ } catch (error) {
361
+ console.error('✗ Failed to generate TypeScript declarations');
362
+ process.exit(1);
363
+ }
364
+
365
+ console.log('✓ Build complete');
366
+ ```
367
+
368
+ For a library with multiple entry points:
369
+
370
+ ```javascript
371
+ import * as esbuild from 'esbuild';
372
+ import { execSync } from 'child_process';
373
+ import { readdir } from 'fs/promises';
374
+ import { join } from 'path';
375
+
376
+ // Option 1: Find all entry points dynamically
377
+ async function findEntryPoints(dir, base = 'src') {
378
+ const entries = [];
379
+ const files = await readdir(dir, { withFileTypes: true });
380
+
381
+ for (const file of files) {
382
+ const fullPath = join(dir, file.name);
383
+ if (file.isDirectory()) {
384
+ entries.push(...await findEntryPoints(fullPath, base));
385
+ } else if (file.name.endsWith('.ts') && !file.name.endsWith('.d.ts')) {
386
+ entries.push(fullPath);
387
+ }
388
+ }
389
+
390
+ return entries;
391
+ }
392
+
393
+ // Option 2: Explicit entry points
394
+ const explicitEntryPoints = [
395
+ 'src/server-factory.ts',
396
+ 'src/client.ts',
397
+ 'src/types.ts',
398
+ 'src/tools/tool-one.ts',
399
+ 'src/tools/tool-two.ts'
400
+ ];
401
+
402
+ // Build CLI entry point (bundled)
403
+ await esbuild.build({
404
+ entryPoints: ['src/index.ts'],
405
+ bundle: true,
406
+ outfile: 'dist/index.js',
407
+ platform: 'node',
408
+ target: 'node18',
409
+ format: 'esm',
410
+ sourcemap: true,
411
+ external: [
412
+ '@modelcontextprotocol/sdk',
413
+ // Add other peer dependencies
414
+ ],
415
+ banner: {
416
+ js: "import { createRequire } from 'module'; const require = createRequire(import.meta.url);"
417
+ },
418
+ alias: {
419
+ '@': './src'
420
+ },
421
+ minify: false,
422
+ keepNames: true
423
+ });
424
+
425
+ // Build library exports (unbundled, preserves module structure)
426
+ await esbuild.build({
427
+ entryPoints: await findEntryPoints('src'), // or explicitEntryPoints
428
+ bundle: false, // Key: don't bundle for library
429
+ outdir: 'dist',
430
+ outbase: 'src', // Preserve directory structure
431
+ platform: 'node',
432
+ target: 'node18',
433
+ format: 'esm',
434
+ sourcemap: true,
435
+ alias: {
436
+ '@': './src'
437
+ }
438
+ });
439
+
440
+ console.log('✓ JavaScript bundles built');
441
+
442
+ // Generate TypeScript declarations
443
+ console.log('Generating TypeScript declarations...');
444
+ try {
445
+ execSync('tsc --emitDeclarationOnly --outDir dist', { stdio: 'inherit' });
446
+ console.log('✓ TypeScript declarations generated');
447
+ } catch (error) {
448
+ console.error('✗ Failed to generate TypeScript declarations');
449
+ process.exit(1);
450
+ }
451
+
452
+ console.log('✓ Build complete');
453
+ ```
454
+
455
+ **Key Points:**
456
+ - **Simple servers**: Single bundled entry point
457
+ - **Library exports**: Dual build strategy (bundle CLI, preserve modules)
458
+ - `bundle: true` for standalone executable
459
+ - `bundle: false` + `outbase: 'src'` for library exports
460
+ - `external` array lists dependencies not to bundle (peer dependencies)
461
+ - `banner` adds CommonJS compatibility for ESM bundles
462
+ - `alias` enables path alias resolution (`@/` → `src/`)
463
+ - `target` specifies Node.js version compatibility
464
+ - Dynamic or explicit entry point discovery for libraries
465
+ - **TypeScript declarations**: Always run `tsc --emitDeclarationOnly` after esbuild
466
+ - **Why separate**: esbuild doesn't generate `.d.ts` files, TypeScript compiler does
467
+ - **Build order**: 1) esbuild bundles JS, 2) tsc generates types, 3) both in dist/
468
+
469
+ ### esbuild.watch.js Structure
470
+
471
+ ```javascript
472
+ import * as esbuild from 'esbuild';
473
+
474
+ const ctx = await esbuild.context({
475
+ entryPoints: ['src/server.ts'],
476
+ bundle: true,
477
+ platform: 'node',
478
+ target: 'node20',
479
+ format: 'esm',
480
+ outfile: 'dist/server.js',
481
+ sourcemap: true,
482
+ external: [
483
+ 'weaviate-client',
484
+ 'firebase-admin',
485
+ '@modelcontextprotocol/sdk'
486
+ ],
487
+ banner: {
488
+ js: "import { createRequire } from 'module'; const require = createRequire(import.meta.url);"
489
+ },
490
+ alias: {
491
+ '@': './src'
492
+ }
493
+ });
494
+
495
+ await ctx.watch();
496
+ console.log('👀 Watching for changes...');
497
+ ```
498
+
499
+ **Key Points:**
500
+ - Uses `esbuild.context()` API for watch mode
501
+ - Same configuration as `esbuild.build.js` for consistency
502
+ - Automatically rebuilds on file changes
503
+ - Includes all the same options: `external`, `banner`, `alias`, etc.
504
+
505
+ ## Source Code Patterns
506
+
507
+ ### Tool Definition Pattern
508
+
509
+ Each tool file exports both definition and handler:
510
+
511
+ ```typescript
512
+ // src/tools/example-tool.ts
513
+ import { ClientWrapper } from '../client.js';
514
+
515
+ export const exampleTool = {
516
+ name: 'prefix_tool_name',
517
+ description: 'Clear description of what the tool does',
518
+ inputSchema: {
519
+ type: 'object',
520
+ properties: {
521
+ param1: {
522
+ type: 'string',
523
+ description: 'Parameter description'
524
+ },
525
+ param2: {
526
+ type: 'number',
527
+ description: 'Optional parameter',
528
+ default: 10
529
+ }
530
+ },
531
+ required: ['param1']
532
+ }
533
+ };
534
+
535
+ export async function handleExampleTool(
536
+ client: ClientWrapper,
537
+ args: any
538
+ ): Promise<string> {
539
+ try {
540
+ const result = await client.doSomething(args.param1, args.param2);
541
+ return JSON.stringify(result, null, 2);
542
+ } catch (error) {
543
+ throw new Error(`Failed to execute: ${error instanceof Error ? error.message : String(error)}`);
544
+ }
545
+ }
546
+ ```
547
+
548
+ **Key Points:**
549
+ - Tool definition is a plain object (MCP Tool schema)
550
+ - Handler is a separate async function
551
+ - Handler receives client instance and args
552
+ - Returns JSON string for MCP response
553
+ - Proper error handling
554
+
555
+ ### Server Factory Pattern (Multi-Tenant)
556
+
557
+ For use with `mcp-auth` or other multi-tenant wrappers:
558
+
559
+ ```typescript
560
+ // src/server-factory.ts
561
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
562
+ import { ClientWrapper } from './client.js';
563
+ import {
564
+ CallToolRequestSchema,
565
+ ListToolsRequestSchema,
566
+ ErrorCode,
567
+ McpError
568
+ } from '@modelcontextprotocol/sdk/types.js';
569
+
570
+ // Import all tools
571
+ import { toolOne, handleToolOne } from './tools/tool-one.js';
572
+ import { toolTwo, handleToolTwo } from './tools/tool-two.js';
573
+
574
+ export interface ServerOptions {
575
+ name?: string;
576
+ version?: string;
577
+ }
578
+
579
+ /**
580
+ * Create a server instance for a specific user/tenant
581
+ *
582
+ * @param accessToken - User's access token for external API
583
+ * @param userId - User identifier
584
+ * @param options - Optional server configuration
585
+ * @returns Configured MCP Server instance
586
+ */
587
+ export function createServer(
588
+ accessToken: string,
589
+ userId: string,
590
+ options: ServerOptions = {}
591
+ ): Server {
592
+ if (!accessToken) {
593
+ throw new Error('accessToken is required');
594
+ }
595
+
596
+ if (!userId) {
597
+ throw new Error('userId is required');
598
+ }
599
+
600
+ // Initialize client with user's credentials
601
+ const client = new ClientWrapper(accessToken);
602
+
603
+ // Create MCP server
604
+ const server = new Server(
605
+ {
606
+ name: options.name || 'mcp-server',
607
+ version: options.version || '1.0.0'
608
+ },
609
+ {
610
+ capabilities: {
611
+ tools: {}
612
+ }
613
+ }
614
+ );
615
+
616
+ // Register list_tools handler
617
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
618
+ return {
619
+ tools: [
620
+ toolOne,
621
+ toolTwo,
622
+ // ... all tool definitions
623
+ ]
624
+ };
625
+ });
626
+
627
+ // Register call_tool handler
628
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
629
+ const { name, arguments: args } = request.params;
630
+
631
+ try {
632
+ let result: string;
633
+
634
+ switch (name) {
635
+ case 'prefix_tool_one':
636
+ result = await handleToolOne(client, args);
637
+ break;
638
+
639
+ case 'prefix_tool_two':
640
+ result = await handleToolTwo(client, args);
641
+ break;
642
+
643
+ default:
644
+ throw new McpError(
645
+ ErrorCode.MethodNotFound,
646
+ `Unknown tool: ${name}`
647
+ );
648
+ }
649
+
650
+ return {
651
+ content: [
652
+ {
653
+ type: 'text',
654
+ text: result
655
+ }
656
+ ]
657
+ };
658
+ } catch (error) {
659
+ if (error instanceof McpError) {
660
+ throw error;
661
+ }
662
+
663
+ throw new McpError(
664
+ ErrorCode.InternalError,
665
+ `Tool execution failed: ${error instanceof Error ? error.message : String(error)}`
666
+ );
667
+ }
668
+ });
669
+
670
+ return server;
671
+ }
672
+ ```
673
+
674
+ **Key Points:**
675
+ - Factory function creates isolated server instances
676
+ - Each instance has its own client with user credentials
677
+ - No shared state between instances
678
+ - Compatible with `mcp-auth` wrapping pattern
679
+
680
+ ### Standalone Server Pattern
681
+
682
+ For direct stdio usage without multi-tenancy:
683
+
684
+ ```typescript
685
+ // src/server.ts
686
+ #!/usr/bin/env node
687
+
688
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
689
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
690
+ import {
691
+ CallToolRequestSchema,
692
+ ListToolsRequestSchema,
693
+ } from '@modelcontextprotocol/sdk/types.js';
694
+ import { config } from 'dotenv';
695
+ import { ClientWrapper } from './client.js';
696
+ import { logger } from './utils/logger.js';
697
+
698
+ // Import tools
699
+ import { ToolOne } from './tools/tool-one.js';
700
+ import { ToolTwo } from './tools/tool-two.js';
701
+
702
+ // Load environment variables
703
+ config();
704
+
705
+ class MCPServer {
706
+ private server: Server;
707
+ private client: ClientWrapper;
708
+ private toolOne: ToolOne;
709
+ private toolTwo: ToolTwo;
710
+
711
+ constructor() {
712
+ // Initialize server
713
+ this.server = new Server(
714
+ {
715
+ name: 'mcp-server',
716
+ version: '1.0.0',
717
+ },
718
+ {
719
+ capabilities: {
720
+ tools: {},
721
+ },
722
+ }
723
+ );
724
+
725
+ // Initialize client
726
+ const apiKey = process.env.API_KEY;
727
+ if (!apiKey) {
728
+ throw new Error('API_KEY environment variable is required');
729
+ }
730
+
731
+ this.client = new ClientWrapper(apiKey);
732
+
733
+ // Initialize tools
734
+ this.toolOne = new ToolOne(this.client);
735
+ this.toolTwo = new ToolTwo(this.client);
736
+
737
+ this.setupHandlers();
738
+ }
739
+
740
+ private setupHandlers(): void {
741
+ // List available tools
742
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
743
+ return {
744
+ tools: [
745
+ this.toolOne.getToolDefinition(),
746
+ this.toolTwo.getToolDefinition(),
747
+ ],
748
+ };
749
+ });
750
+
751
+ // Handle tool calls
752
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
753
+ const { name, arguments: args } = request.params;
754
+
755
+ try {
756
+ let result: any;
757
+
758
+ switch (name) {
759
+ case 'prefix_tool_one':
760
+ result = await this.toolOne.execute(args);
761
+ break;
762
+
763
+ case 'prefix_tool_two':
764
+ result = await this.toolTwo.execute(args);
765
+ break;
766
+
767
+ default:
768
+ throw new Error(`Unknown tool: ${name}`);
769
+ }
770
+
771
+ return {
772
+ content: [
773
+ {
774
+ type: 'text',
775
+ text: JSON.stringify(result, null, 2),
776
+ },
777
+ ],
778
+ };
779
+ } catch (error) {
780
+ logger.error(`Tool execution failed for ${name}:`, error);
781
+ return {
782
+ content: [
783
+ {
784
+ type: 'text',
785
+ text: JSON.stringify({
786
+ error: error instanceof Error ? error.message : 'Unknown error',
787
+ tool: name
788
+ }, null, 2),
789
+ },
790
+ ],
791
+ isError: true,
792
+ };
793
+ }
794
+ });
795
+ }
796
+
797
+ async start(): Promise<void> {
798
+ try {
799
+ logger.info('Starting MCP Server...');
800
+
801
+ // Connect to external service
802
+ await this.client.connect();
803
+
804
+ // Start MCP server with stdio transport
805
+ const transport = new StdioServerTransport();
806
+ await this.server.connect(transport);
807
+
808
+ // Don't log to stdout/stderr when using stdio transport
809
+ // It interferes with MCP JSON protocol
810
+
811
+ } catch (error) {
812
+ process.exit(1);
813
+ }
814
+ }
815
+
816
+ async stop(): Promise<void> {
817
+ await this.server.close();
818
+ }
819
+ }
820
+
821
+ // Handle graceful shutdown
822
+ const server = new MCPServer();
823
+
824
+ process.on('SIGINT', async () => {
825
+ await server.stop();
826
+ process.exit(0);
827
+ });
828
+
829
+ process.on('SIGTERM', async () => {
830
+ await server.stop();
831
+ process.exit(0);
832
+ });
833
+
834
+ // Start the server
835
+ server.start().catch((error) => {
836
+ process.exit(1);
837
+ });
838
+ ```
839
+
840
+ **Key Points:**
841
+ - Class-based server for encapsulation
842
+ - Environment variable configuration
843
+ - Graceful shutdown handling
844
+ - **Critical**: No stdout/stderr logging when using stdio transport
845
+ - Tool instances as class properties
846
+
847
+ ### Client Wrapper Pattern
848
+
849
+ ```typescript
850
+ // src/client.ts
851
+ export interface ClientConfig {
852
+ apiKey: string;
853
+ baseUrl?: string;
854
+ timeout?: number;
855
+ }
856
+
857
+ export class ClientWrapper {
858
+ private config: ClientConfig;
859
+ private isConnected = false;
860
+
861
+ constructor(apiKey: string, options?: Partial<ClientConfig>) {
862
+ this.config = {
863
+ apiKey,
864
+ baseUrl: options?.baseUrl || 'https://api.example.com',
865
+ timeout: options?.timeout || 30000
866
+ };
867
+ }
868
+
869
+ async connect(): Promise<void> {
870
+ // Initialize connection, validate credentials, etc.
871
+ this.isConnected = true;
872
+ }
873
+
874
+ async doSomething(param: string): Promise<any> {
875
+ if (!this.isConnected) {
876
+ throw new Error('Client not connected');
877
+ }
878
+
879
+ // Make API call
880
+ const response = await fetch(`${this.config.baseUrl}/endpoint`, {
881
+ method: 'POST',
882
+ headers: {
883
+ 'Authorization': `Bearer ${this.config.apiKey}`,
884
+ 'Content-Type': 'application/json'
885
+ },
886
+ body: JSON.stringify({ param })
887
+ });
888
+
889
+ if (!response.ok) {
890
+ throw new Error(`API error: ${response.statusText}`);
891
+ }
892
+
893
+ return response.json();
894
+ }
895
+
896
+ isClientConnected(): boolean {
897
+ return this.isConnected;
898
+ }
899
+ }
900
+ ```
901
+
902
+ **Key Points:**
903
+ - Encapsulates external API communication
904
+ - Accepts credentials in constructor (for multi-tenant)
905
+ - Connection state management
906
+ - Error handling
907
+
908
+ ### Logger Pattern (Stdio-Safe)
909
+
910
+ ```typescript
911
+ // src/utils/logger.ts
912
+ export enum LogLevel {
913
+ ERROR = 0,
914
+ WARN = 1,
915
+ INFO = 2,
916
+ DEBUG = 3
917
+ }
918
+
919
+ class Logger {
920
+ // No-op logger to avoid interfering with stdio MCP transport
921
+ // All logging methods do nothing to prevent JSON corruption
922
+
923
+ error(message: string, ...args: any[]): void {
924
+ // No-op when using stdio
925
+ // Could write to file or use process.stderr in non-stdio mode
926
+ }
927
+
928
+ warn(message: string, ...args: any[]): void {
929
+ // No-op
930
+ }
931
+
932
+ info(message: string, ...args: any[]): void {
933
+ // No-op
934
+ }
935
+
936
+ debug(message: string, ...args: any[]): void {
937
+ // No-op
938
+ }
939
+ }
940
+
941
+ export const logger = new Logger();
942
+ ```
943
+
944
+ **Key Points:**
945
+ - **Critical**: No console output when using stdio transport
946
+ - Stdio transport uses stdout/stdin for JSON-RPC
947
+ - Any console output corrupts the protocol
948
+ - Alternative: Write to file or use stderr carefully
949
+
950
+ ### Error Serializer Pattern
951
+
952
+ ```typescript
953
+ // src/utils/error-serializer.ts
954
+ export function serializeError(error: unknown): any {
955
+ if (error instanceof Error) {
956
+ return {
957
+ name: error.name,
958
+ message: error.message,
959
+ stack: error.stack,
960
+ ...(error as any) // Include any additional properties
961
+ };
962
+ }
963
+
964
+ return {
965
+ message: String(error)
966
+ };
967
+ }
968
+ ```
969
+
970
+ ## Integration with mcp-auth
971
+
972
+ ### Using AuthenticatedMCPServer
973
+
974
+ If building a new server with tool-level auth:
975
+
976
+ ```typescript
977
+ // src/index.ts
978
+ import { AuthenticatedMCPServer } from '@prmichaelsen/mcp-auth/server';
979
+ import { EnvAuthProvider } from '@prmichaelsen/mcp-auth/providers/env';
980
+ import { SimpleTokenResolver } from '@prmichaelsen/mcp-auth';
981
+ import { withAuth } from '@prmichaelsen/mcp-auth/server';
982
+
983
+ const server = new AuthenticatedMCPServer({
984
+ name: 'my-server',
985
+ authProvider: new EnvAuthProvider(),
986
+ tokenResolver: new SimpleTokenResolver({ tokenEnvVar: 'API_TOKEN' }),
987
+ resourceType: 'myapi',
988
+ transport: { type: 'stdio' }
989
+ });
990
+
991
+ server.registerTool('get_data', withAuth(async (args, accessToken, userId) => {
992
+ const client = new ClientWrapper(accessToken);
993
+ return client.getData(args);
994
+ }));
995
+
996
+ await server.start();
997
+ ```
998
+
999
+ ### Using Server Wrapping Pattern
1000
+
1001
+ If wrapping an existing server factory:
1002
+
1003
+ ```typescript
1004
+ // Wrapper server using mcp-auth
1005
+ import { wrapServer } from '@prmichaelsen/mcp-auth/wrapper';
1006
+ import { createServer } from './server-factory.js';
1007
+
1008
+ const wrappedServer = wrapServer({
1009
+ serverFactory: createServer,
1010
+ authProvider: new JWTAuthProvider({ secret: process.env.JWT_SECRET }),
1011
+ tokenResolver: new APITokenResolver({ apiUrl: process.env.API_URL }),
1012
+ resourceType: 'myapi',
1013
+ transport: { type: 'sse', port: 3000 }
1014
+ });
1015
+
1016
+ await wrappedServer.start();
1017
+ ```
1018
+
1019
+ ## Directory Organization Best Practices
1020
+
1021
+ ### Agent Directory
1022
+
1023
+ The `agent/` directory contains documentation and planning:
1024
+
1025
+ ```
1026
+ agent/
1027
+ ├── patterns/ # Architecture patterns
1028
+ │ ├── bootstrap.md # This document
1029
+ │ ├── library-services.md # Service layer patterns
1030
+ │ └── ...
1031
+
1032
+ ├── tasks/ # Task tracking
1033
+ │ ├── task-001.md
1034
+ │ └── ...
1035
+
1036
+ ├── milestones/ # Milestone planning
1037
+ │ ├── milestone-1.md
1038
+ │ └── ...
1039
+
1040
+ ├── progress.yaml # Progress tracking
1041
+ └── requirements.md # Requirements document
1042
+ ```
1043
+
1044
+ ### Types Organization
1045
+
1046
+ Types can be organized in two ways:
1047
+
1048
+ **Option 1: Flat structure** (simple projects)
1049
+ ```
1050
+ src/
1051
+ ├── types.ts # All types in one file
1052
+ └── ...
1053
+ ```
1054
+
1055
+ **Option 2: Types directory** (complex projects)
1056
+ ```
1057
+ src/
1058
+ ├── types/
1059
+ │ ├── mcp.ts # MCP-specific types
1060
+ │ ├── api.ts # External API types
1061
+ │ ├── domain.ts # Domain types
1062
+ │ └── index.ts # Re-exports
1063
+ └── ...
1064
+ ```
1065
+
1066
+ ### Utils Organization
1067
+
1068
+ ```
1069
+ src/
1070
+ ├── utils/
1071
+ │ ├── logger.ts # Logging utility
1072
+ │ ├── error-serializer.ts # Error handling
1073
+ │ ├── validation.ts # Input validation
1074
+ │ └── index.ts # Re-exports
1075
+ └── ...
1076
+ ```
1077
+
1078
+ ## Build Output Structure
1079
+
1080
+ After building, the output should mirror the source structure:
1081
+
1082
+ ```
1083
+ dist/
1084
+ ├── index.js # Bundled CLI entry
1085
+ ├── index.d.ts
1086
+ ├── server-factory.js # Unbundled library exports
1087
+ ├── server-factory.d.ts
1088
+ ├── client.js
1089
+ ├── client.d.ts
1090
+ ├── types.js
1091
+ ├── types.d.ts
1092
+ ├── tools/
1093
+ │ ├── tool-one.js
1094
+ │ ├── tool-one.d.ts
1095
+ │ ├── tool-two.js
1096
+ │ ├── tool-two.d.ts
1097
+ │ └── index.js
1098
+ └── utils/
1099
+ ├── logger.js
1100
+ ├── logger.d.ts
1101
+ └── ...
1102
+ ```
1103
+
1104
+ **Key Points:**
1105
+ - `index.js` is bundled (single file)
1106
+ - Other exports preserve module structure
1107
+ - Type definitions (`.d.ts`) for all modules
1108
+ - Source maps (`.js.map`) for debugging
1109
+
1110
+ ## Import Patterns
1111
+
1112
+ ### ESM Import Extensions
1113
+
1114
+ Always include `.js` extension in imports (even for `.ts` files):
1115
+
1116
+ ```typescript
1117
+ // ✅ Correct
1118
+ import { ClientWrapper } from './client.js';
1119
+ import { toolOne } from './tools/tool-one.js';
1120
+
1121
+ // ❌ Wrong
1122
+ import { ClientWrapper } from './client';
1123
+ import { toolOne } from './tools/tool-one';
1124
+ ```
1125
+
1126
+ ### Re-export Patterns
1127
+
1128
+ ```typescript
1129
+ // src/tools/index.ts
1130
+ export * from './tool-one.js';
1131
+ export * from './tool-two.js';
1132
+
1133
+ // Usage
1134
+ import { toolOne, toolTwo } from './tools/index.js';
1135
+ ```
1136
+
1137
+ ## Environment Configuration
1138
+
1139
+ ### .env.example
1140
+
1141
+ ```bash
1142
+ # API Configuration
1143
+ API_KEY=your_api_key_here
1144
+ API_URL=https://api.example.com
1145
+
1146
+ # Server Configuration
1147
+ PORT=3000
1148
+ NODE_ENV=development
1149
+
1150
+ # Logging
1151
+ LOG_LEVEL=info
1152
+ ```
1153
+
1154
+ ### Environment Loading
1155
+
1156
+ ```typescript
1157
+ import { config } from 'dotenv';
1158
+
1159
+ // Load at server startup
1160
+ config();
1161
+
1162
+ // Access variables
1163
+ const apiKey = process.env.API_KEY;
1164
+ if (!apiKey) {
1165
+ throw new Error('API_KEY is required');
1166
+ }
1167
+ ```
1168
+
1169
+ ## Testing Considerations
1170
+
1171
+ While not covered in detail, consider:
1172
+
1173
+ ```
1174
+ src/
1175
+ ├── tools/
1176
+ │ ├── tool-one.ts
1177
+ │ ├── tool-one.test.ts # Co-located tests
1178
+ │ └── ...
1179
+ ```
1180
+
1181
+ Or separate test directory:
1182
+
1183
+ ```
1184
+ tests/
1185
+ ├── tools/
1186
+ │ ├── tool-one.test.ts
1187
+ │ └── ...
1188
+ └── integration/
1189
+ └── ...
1190
+ ```
1191
+
1192
+ ## Common Patterns Summary
1193
+
1194
+ ### 1. Tool Organization
1195
+ - One file per tool
1196
+ - Export definition and handler separately
1197
+ - Handler receives client and args
1198
+ - Return JSON strings
1199
+
1200
+ ### 2. Server Patterns
1201
+ - **Factory**: For multi-tenant (returns Server instance)
1202
+ - **Class**: For standalone (manages lifecycle)
1203
+ - Both patterns supported
1204
+
1205
+ ### 3. Build Strategy
1206
+ - **Bundle**: CLI entry point (single file)
1207
+ - **Preserve**: Library exports (module structure)
1208
+ - TypeScript declarations always generated
1209
+
1210
+ ### 4. Client Pattern
1211
+ - Wrapper class for external API
1212
+ - Accept credentials in constructor
1213
+ - Stateful connection management
1214
+
1215
+ ### 5. Logging Pattern
1216
+ - No-op for stdio transport
1217
+ - File or stderr for other transports
1218
+ - Never use console.log with stdio
1219
+
1220
+ ### 6. Type Safety
1221
+ - Strong typing throughout
1222
+ - Separate type files or directories
1223
+ - Export types for library consumers
1224
+
1225
+ ### 7. Error Handling
1226
+ - Serialize errors for MCP responses
1227
+ - Proper error types (McpError)
1228
+ - Graceful degradation
1229
+
1230
+ ## Compatibility Checklist
1231
+
1232
+ When building a server compatible with `mcp-auth`:
1233
+
1234
+ - ✅ Export a factory function that accepts `(accessToken, userId, options?)`
1235
+ - ✅ Factory returns a configured `Server` instance
1236
+ - ✅ No shared state between server instances
1237
+ - ✅ Client wrapper accepts credentials in constructor
1238
+ - ✅ Tools are stateless (receive client as parameter)
1239
+ - ✅ Proper TypeScript types exported
1240
+ - ✅ ESM with `.js` extensions in imports
1241
+ - ✅ Dual build: bundled CLI + preserved modules
1242
+
1243
+ ## Migration Path
1244
+
1245
+ ### From Standalone to Multi-Tenant
1246
+
1247
+ 1. Extract server creation into factory function
1248
+ 2. Move credential loading from env to factory parameters
1249
+ 3. Ensure no shared state between instances
1250
+ 4. Add factory export to package.json
1251
+ 5. Update build to preserve module structure
1252
+
1253
+ ### From Multi-Tenant to mcp-auth Integration
1254
+
1255
+ 1. Keep existing factory function
1256
+ 2. Add mcp-auth wrapper in separate entry point
1257
+ 3. Configure auth provider and token resolver
1258
+ 4. Deploy wrapped server for remote access
1259
+ 5. Keep factory for direct usage
1260
+
1261
+ ## Conclusion
1262
+
1263
+ This bootstrap pattern provides a foundation for building MCP servers that are:
1264
+
1265
+ - **Modular**: Clear separation of concerns
1266
+ - **Type-safe**: Strong TypeScript typing
1267
+ - **Multi-tenant ready**: Isolated instances per user
1268
+ - **Library-friendly**: Dual build strategy
1269
+ - **mcp-auth compatible**: Works with authentication framework
1270
+
1271
+ The pattern emphasizes **organization and structure** over specific implementations, allowing flexibility in choosing tools and technologies while maintaining consistency and compatibility.