fa-mcp-sdk 0.4.141 → 0.11.2

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 (200) hide show
  1. package/README.md +5 -0
  2. package/cli-template/.dockerignore +16 -0
  3. package/cli-template/.gitlab-ci.yml +135 -0
  4. package/cli-template/AGENTS.md +1 -0
  5. package/cli-template/CHANGELOG.md +64 -0
  6. package/cli-template/FA-MCP-SDK-DOC/00-FA-MCP-SDK-index.md +27 -4
  7. package/cli-template/FA-MCP-SDK-DOC/02-1-tools-and-api.md +195 -0
  8. package/cli-template/FA-MCP-SDK-DOC/02-2-prompts-and-resources.md +172 -9
  9. package/cli-template/FA-MCP-SDK-DOC/03-configuration.md +170 -12
  10. package/cli-template/FA-MCP-SDK-DOC/04-authentication.md +158 -8
  11. package/cli-template/FA-MCP-SDK-DOC/06-utilities.md +67 -6
  12. package/cli-template/FA-MCP-SDK-DOC/07-testing-and-operations.md +31 -15
  13. package/cli-template/FA-MCP-SDK-DOC/10-mcp-apps.md +1 -1
  14. package/cli-template/FA-MCP-SDK-DOC/11-public-contract.md +342 -0
  15. package/cli-template/README.md +37 -0
  16. package/cli-template/deploy/docker/.env.example +10 -0
  17. package/cli-template/deploy/docker/Dockerfile +44 -0
  18. package/cli-template/deploy/docker/Dockerfile.local +29 -0
  19. package/cli-template/deploy/docker/README.md +94 -0
  20. package/cli-template/deploy/docker/config/local.docker.yaml +14 -0
  21. package/cli-template/deploy/docker/docker-compose.yml +31 -0
  22. package/cli-template/deploy/gitlab-runner/.env.example +16 -0
  23. package/cli-template/deploy/gitlab-runner/README.md +65 -0
  24. package/cli-template/deploy/gitlab-runner/config/config.toml.template +26 -0
  25. package/cli-template/deploy/gitlab-runner/docker-compose.yml +39 -0
  26. package/cli-template/deploy/gitlab-runner/entrypoint.sh +27 -0
  27. package/cli-template/deploy/gitlab-runner/start.sh +47 -0
  28. package/cli-template/gitignore +96 -95
  29. package/cli-template/package.json +1 -1
  30. package/config/_local.yaml +73 -11
  31. package/config/custom-environment-variables.yaml +102 -0
  32. package/config/default.yaml +164 -11
  33. package/config/local.yaml +20 -19
  34. package/dist/core/_types_/config.d.ts +119 -0
  35. package/dist/core/_types_/config.d.ts.map +1 -1
  36. package/dist/core/_types_/types.d.ts +137 -4
  37. package/dist/core/_types_/types.d.ts.map +1 -1
  38. package/dist/core/agent-tester/agent-tester-router.d.ts.map +1 -1
  39. package/dist/core/agent-tester/agent-tester-router.js +25 -11
  40. package/dist/core/agent-tester/agent-tester-router.js.map +1 -1
  41. package/dist/core/agent-tester/services/TesterMcpClientService.d.ts.map +1 -1
  42. package/dist/core/agent-tester/services/TesterMcpClientService.js +6 -4
  43. package/dist/core/agent-tester/services/TesterMcpClientService.js.map +1 -1
  44. package/dist/core/auth/admin-auth.js +4 -4
  45. package/dist/core/auth/admin-auth.js.map +1 -1
  46. package/dist/core/auth/agent-tester-auth.d.ts +1 -1
  47. package/dist/core/auth/agent-tester-auth.d.ts.map +1 -1
  48. package/dist/core/auth/agent-tester-auth.js +8 -4
  49. package/dist/core/auth/agent-tester-auth.js.map +1 -1
  50. package/dist/core/auth/auth-profile.d.ts +38 -0
  51. package/dist/core/auth/auth-profile.d.ts.map +1 -0
  52. package/dist/core/auth/auth-profile.js +101 -0
  53. package/dist/core/auth/auth-profile.js.map +1 -0
  54. package/dist/core/auth/jwt-v2.d.ts +27 -0
  55. package/dist/core/auth/jwt-v2.d.ts.map +1 -0
  56. package/dist/core/auth/jwt-v2.js +180 -0
  57. package/dist/core/auth/jwt-v2.js.map +1 -0
  58. package/dist/core/auth/jwt.d.ts +27 -13
  59. package/dist/core/auth/jwt.d.ts.map +1 -1
  60. package/dist/core/auth/jwt.js +36 -13
  61. package/dist/core/auth/jwt.js.map +1 -1
  62. package/dist/core/auth/key-resolver.d.ts +74 -0
  63. package/dist/core/auth/key-resolver.d.ts.map +1 -0
  64. package/dist/core/auth/key-resolver.js +330 -0
  65. package/dist/core/auth/key-resolver.js.map +1 -0
  66. package/dist/core/auth/middleware.d.ts.map +1 -1
  67. package/dist/core/auth/middleware.js +66 -0
  68. package/dist/core/auth/middleware.js.map +1 -1
  69. package/dist/core/auth/multi-auth.d.ts +1 -1
  70. package/dist/core/auth/multi-auth.d.ts.map +1 -1
  71. package/dist/core/auth/multi-auth.js +7 -7
  72. package/dist/core/auth/multi-auth.js.map +1 -1
  73. package/dist/core/auth/token-generator/server.js +4 -4
  74. package/dist/core/auth/token-generator/server.js.map +1 -1
  75. package/dist/core/auth/types.d.ts +5 -0
  76. package/dist/core/auth/types.d.ts.map +1 -1
  77. package/dist/core/db/pg-db.d.ts +7 -0
  78. package/dist/core/db/pg-db.d.ts.map +1 -1
  79. package/dist/core/db/pg-db.js +54 -3
  80. package/dist/core/db/pg-db.js.map +1 -1
  81. package/dist/core/errors/BaseMcpError.d.ts +21 -1
  82. package/dist/core/errors/BaseMcpError.d.ts.map +1 -1
  83. package/dist/core/errors/BaseMcpError.js +20 -1
  84. package/dist/core/errors/BaseMcpError.js.map +1 -1
  85. package/dist/core/errors/ValidationError.d.ts +5 -0
  86. package/dist/core/errors/ValidationError.d.ts.map +1 -1
  87. package/dist/core/errors/ValidationError.js +6 -1
  88. package/dist/core/errors/ValidationError.js.map +1 -1
  89. package/dist/core/errors/errors.d.ts +31 -3
  90. package/dist/core/errors/errors.d.ts.map +1 -1
  91. package/dist/core/errors/errors.js +86 -6
  92. package/dist/core/errors/errors.js.map +1 -1
  93. package/dist/core/errors/specific-errors.d.ts +54 -0
  94. package/dist/core/errors/specific-errors.d.ts.map +1 -0
  95. package/dist/core/errors/specific-errors.js +82 -0
  96. package/dist/core/errors/specific-errors.js.map +1 -0
  97. package/dist/core/index.d.ts +10 -2
  98. package/dist/core/index.d.ts.map +1 -1
  99. package/dist/core/index.js +9 -1
  100. package/dist/core/index.js.map +1 -1
  101. package/dist/core/init-mcp-server.d.ts.map +1 -1
  102. package/dist/core/init-mcp-server.js +39 -0
  103. package/dist/core/init-mcp-server.js.map +1 -1
  104. package/dist/core/mcp/create-mcp-server.d.ts +12 -6
  105. package/dist/core/mcp/create-mcp-server.d.ts.map +1 -1
  106. package/dist/core/mcp/create-mcp-server.js +592 -33
  107. package/dist/core/mcp/create-mcp-server.js.map +1 -1
  108. package/dist/core/mcp/debug-trace.d.ts +3 -1
  109. package/dist/core/mcp/debug-trace.d.ts.map +1 -1
  110. package/dist/core/mcp/debug-trace.js +17 -2
  111. package/dist/core/mcp/debug-trace.js.map +1 -1
  112. package/dist/core/mcp/deprecation.d.ts +31 -0
  113. package/dist/core/mcp/deprecation.d.ts.map +1 -0
  114. package/dist/core/mcp/deprecation.js +96 -0
  115. package/dist/core/mcp/deprecation.js.map +1 -0
  116. package/dist/core/mcp/mcp-logging.d.ts +32 -0
  117. package/dist/core/mcp/mcp-logging.d.ts.map +1 -0
  118. package/dist/core/mcp/mcp-logging.js +97 -0
  119. package/dist/core/mcp/mcp-logging.js.map +1 -0
  120. package/dist/core/mcp/pagination.d.ts +13 -0
  121. package/dist/core/mcp/pagination.d.ts.map +1 -0
  122. package/dist/core/mcp/pagination.js +50 -0
  123. package/dist/core/mcp/pagination.js.map +1 -0
  124. package/dist/core/mcp/prompts.d.ts +5 -1
  125. package/dist/core/mcp/prompts.d.ts.map +1 -1
  126. package/dist/core/mcp/prompts.js +3 -1
  127. package/dist/core/mcp/prompts.js.map +1 -1
  128. package/dist/core/mcp/resources.d.ts +9 -0
  129. package/dist/core/mcp/resources.d.ts.map +1 -1
  130. package/dist/core/mcp/resources.js +158 -11
  131. package/dist/core/mcp/resources.js.map +1 -1
  132. package/dist/core/mcp/server-stdio.d.ts +7 -1
  133. package/dist/core/mcp/server-stdio.d.ts.map +1 -1
  134. package/dist/core/mcp/server-stdio.js +8 -3
  135. package/dist/core/mcp/server-stdio.js.map +1 -1
  136. package/dist/core/mcp/task-store.d.ts +97 -0
  137. package/dist/core/mcp/task-store.d.ts.map +1 -0
  138. package/dist/core/mcp/task-store.js +175 -0
  139. package/dist/core/mcp/task-store.js.map +1 -0
  140. package/dist/core/mcp/tool-limits.d.ts +22 -0
  141. package/dist/core/mcp/tool-limits.d.ts.map +1 -0
  142. package/dist/core/mcp/tool-limits.js +115 -0
  143. package/dist/core/mcp/tool-limits.js.map +1 -0
  144. package/dist/core/mcp/validate-tool-args.d.ts +16 -0
  145. package/dist/core/mcp/validate-tool-args.d.ts.map +1 -0
  146. package/dist/core/mcp/validate-tool-args.js +67 -0
  147. package/dist/core/mcp/validate-tool-args.js.map +1 -0
  148. package/dist/core/mcp/validate-tool-names.d.ts +11 -0
  149. package/dist/core/mcp/validate-tool-names.d.ts.map +1 -0
  150. package/dist/core/mcp/validate-tool-names.js +23 -0
  151. package/dist/core/mcp/validate-tool-names.js.map +1 -0
  152. package/dist/core/metrics/metrics.d.ts +45 -0
  153. package/dist/core/metrics/metrics.d.ts.map +1 -0
  154. package/dist/core/metrics/metrics.js +119 -0
  155. package/dist/core/metrics/metrics.js.map +1 -0
  156. package/dist/core/utils/mask-sensitive.d.ts +44 -0
  157. package/dist/core/utils/mask-sensitive.d.ts.map +1 -0
  158. package/dist/core/utils/mask-sensitive.js +64 -0
  159. package/dist/core/utils/mask-sensitive.js.map +1 -0
  160. package/dist/core/utils/testing/McpHttpClient.d.ts +8 -33
  161. package/dist/core/utils/testing/McpHttpClient.d.ts.map +1 -1
  162. package/dist/core/utils/testing/McpHttpClient.js +8 -74
  163. package/dist/core/utils/testing/McpHttpClient.js.map +1 -1
  164. package/dist/core/utils/testing/McpStreamableHttpClient.d.ts +24 -30
  165. package/dist/core/utils/testing/McpStreamableHttpClient.d.ts.map +1 -1
  166. package/dist/core/utils/testing/McpStreamableHttpClient.js +36 -198
  167. package/dist/core/utils/testing/McpStreamableHttpClient.js.map +1 -1
  168. package/dist/core/utils/utils.d.ts.map +1 -1
  169. package/dist/core/utils/utils.js +2 -0
  170. package/dist/core/utils/utils.js.map +1 -1
  171. package/dist/core/web/admin-router.js +3 -3
  172. package/dist/core/web/admin-router.js.map +1 -1
  173. package/dist/core/web/cors.d.ts +9 -1
  174. package/dist/core/web/cors.d.ts.map +1 -1
  175. package/dist/core/web/cors.js +26 -5
  176. package/dist/core/web/cors.js.map +1 -1
  177. package/dist/core/web/event-store.d.ts +33 -0
  178. package/dist/core/web/event-store.d.ts.map +1 -0
  179. package/dist/core/web/event-store.js +65 -0
  180. package/dist/core/web/event-store.js.map +1 -0
  181. package/dist/core/web/oauth-router.d.ts +37 -0
  182. package/dist/core/web/oauth-router.d.ts.map +1 -0
  183. package/dist/core/web/oauth-router.js +207 -0
  184. package/dist/core/web/oauth-router.js.map +1 -0
  185. package/dist/core/web/request-id.d.ts +44 -0
  186. package/dist/core/web/request-id.d.ts.map +1 -0
  187. package/dist/core/web/request-id.js +82 -0
  188. package/dist/core/web/request-id.js.map +1 -0
  189. package/dist/core/web/server-http.d.ts.map +1 -1
  190. package/dist/core/web/server-http.js +322 -182
  191. package/dist/core/web/server-http.js.map +1 -1
  192. package/package.json +15 -2
  193. package/scripts/claude-2-agents-symlink.js +10 -1
  194. package/scripts/generate-jwt.js +129 -51
  195. package/src/template/custom-resources.ts +14 -0
  196. package/src/template/prompts/custom-prompts.ts +4 -0
  197. package/src/template/tools/handle-tool-call.ts +59 -3
  198. package/src/template/tools/tools.ts +92 -31
  199. package/src/tests/mcp/test-http.js +1 -1
  200. package/src/tests/mcp/test-sse.js +1 -1
@@ -3,27 +3,53 @@
3
3
  ## Error Classes
4
4
 
5
5
  ```typescript
6
- import { BaseMcpError, ToolExecutionError, ValidationError, ServerError } from 'fa-mcp-sdk';
6
+ import {
7
+ BaseMcpError, ToolExecutionError, ValidationError, ServerError,
8
+ // Phase 1 HTTP hardening — Appendix B specific errors:
9
+ PayloadTooLargeError, TimeoutError, RateLimitedError, ResourceNotFoundError,
10
+ MCP_ERROR_CODES, IMcpErrorData,
11
+ } from 'fa-mcp-sdk';
7
12
 
8
13
  throw new ValidationError('Input validation failed');
9
14
  throw new ToolExecutionError('my_tool', 'Execution failed');
10
15
  throw new ServerError('Database connection failed', { key: 'value' });
11
16
 
12
- // Custom error
17
+ // Standard §13 / Appendix B — already used by the SDK transport, but exported for tool / API code:
18
+ throw new PayloadTooLargeError('Image exceeds 1 MiB'); // -32005 / 413
19
+ throw new TimeoutError('Upstream did not respond in time'); // -32004 / 504
20
+ throw new RateLimitedError('Too many calls', 30); // -32003 / 429 (retryAfter=30s)
21
+ throw new ResourceNotFoundError('Ticket not found', { field: 'key' }); // -32002 / 404
22
+
23
+ // Custom error — supply both the legacy string code and the JSON-RPC numeric code
13
24
  class MyError extends BaseMcpError {
14
- constructor(msg: string) { super(msg, 'MY_ERROR'); }
25
+ constructor(msg: string) {
26
+ super('MY_ERROR', msg, undefined, 422, undefined, -32600, { reason: 'my_reason' });
27
+ }
15
28
  }
16
29
  ```
17
30
 
18
- **ServerError**: `code: 'SERVER_ERROR'`, `httpStatus: 500`
31
+ | Class | `code` | `jsonRpcCode` | HTTP | Standard |
32
+ |-------|--------|---------------|------|----------|
33
+ | `ServerError` | `SERVER_ERROR` | `-32000` (default) | 500 | — |
34
+ | `ToolExecutionError` | `TOOL_EXECUTION_ERROR` | `-32000` (default) | 400 | — |
35
+ | `ValidationError` | `VALIDATION_ERROR` | `-32000` (default) | 400 | — |
36
+ | `ResourceNotFoundError` | `RESOURCE_NOT_FOUND` | `-32002` | 404 | Appendix B |
37
+ | `RateLimitedError` | `RATE_LIMITED` | `-32003` | 429 | Appendix B |
38
+ | `TimeoutError` | `TIMEOUT` | `-32004` | 504 | Appendix B |
39
+ | `PayloadTooLargeError` | `PAYLOAD_TOO_LARGE` | `-32005` | 413 | Appendix B |
19
40
 
20
41
  ## Error Utilities
21
42
 
22
43
  ```typescript
23
44
  import { createJsonRpcErrorResponse, toError, toStr, addErrorMessage } from 'fa-mcp-sdk';
24
45
 
25
- // Create JSON-RPC error response
26
- const response = createJsonRpcErrorResponse(error, 'request-123');
46
+ // Create JSON-RPC error response. Optional third argument injects extra `error.data` keys
47
+ // (standard Appendix B.3 — { requestId?, field?, reason?, retryAfter?, … }).
48
+ const response = createJsonRpcErrorResponse(error, 'request-123', { requestId: 'req-abc' });
49
+
50
+ // Resulting body:
51
+ // { jsonrpc: '2.0', id: 'request-123',
52
+ // error: { code: -32004, message: '…', data: { reason: 'tool_timeout', requestId: 'req-abc' } } }
27
53
 
28
54
  // Safe error conversion
29
55
  const err = toError(anything); // → Error object
@@ -124,6 +150,41 @@ function getJsonFromResult<T = any>(result: TToolHandlerResponse | any): T;
124
150
  ignore `tools.answerAs`. Use when a specific shape is required.
125
151
  - **`getJsonFromResult()`** — Inverse of `formatToolResult()`. Extracts JSON from either format. Use in tests.
126
152
 
153
+ ## Masking Sensitive Data (`maskSensitive`, standard §12.2)
154
+
155
+ Masking personal / sensitive data in tool results is the **server's** responsibility — the SDK never
156
+ masks automatically (it does not know the domain model). `maskSensitive(value, rules)` is an optional,
157
+ reusable helper: call it inside a tool handler before returning the result.
158
+
159
+ ```typescript
160
+ import { maskSensitive } from 'fa-mcp-sdk';
161
+
162
+ const result = await fetchUserRecord(args);
163
+ // Mask by field name (case-insensitive) and/or regex on string values at any depth.
164
+ const safe = maskSensitive(result, {
165
+ fieldNames: ['password', 'token', 'ssn', 'emailAddress'],
166
+ patterns: [/\b\d{13,19}\b/g], // card-like number sequences
167
+ replacement: '***', // default; or a function for partial masking
168
+ });
169
+ return formatToolResult(safe);
170
+
171
+ // Partial masking via a replacement function:
172
+ maskSensitive({ card: '4111111111111111' }, {
173
+ fieldNames: ['card'],
174
+ replacement: (v) => `${v.slice(0, 4)}********${v.slice(-4)}`, // → '4111********1111'
175
+ });
176
+ ```
177
+
178
+ `IMaskRules`: `fieldNames?: string[]` (whole value of a matching key is replaced), `patterns?: RegExp[]`
179
+ (matches in string values are replaced; use a global flag to replace all in one string), `replacement?:
180
+ string | ((original: string) => string)` (default `'***'`). The input is never mutated; primitives pass
181
+ through unchanged. It is **not** wired into `tools/call` — applying it and choosing the rules stays with
182
+ the server.
183
+
184
+ > **Caveat.** Mask what genuinely must not leave the server (secrets, raw emails, account ids). Avoid
185
+ > blanket-masking display names the assistant actually needs to be useful (e.g. an issue's assignee) —
186
+ > that trades correctness for nothing. Pick field names deliberately.
187
+
127
188
  ## Network Utilities
128
189
 
129
190
  ```typescript
@@ -20,13 +20,20 @@ const prompt = await client.getPrompt('agent_brief');
20
20
 
21
21
  ### HTTP Transport
22
22
 
23
+ > **Deprecated:** `McpHttpClient` is now a thin alias of `McpStreamableHttpClient` (see below). The
24
+ > server's `/mcp` endpoint runs the SDK `StreamableHTTPServerTransport`, so the old plain-POST client
25
+ > no longer applies — both names speak the same Streamable HTTP protocol. Prefer
26
+ > `McpStreamableHttpClient` in new code.
27
+
23
28
  ```typescript
24
29
  import { McpHttpClient } from 'fa-mcp-sdk';
25
30
 
26
- const client = new McpHttpClient('http://localhost:3000');
27
- const result = await client.callTool('my_tool', { query: 'test' }, {
28
- 'Authorization': 'Bearer token'
31
+ // Auth headers go in the constructor (not as a per-call argument).
32
+ const client = new McpHttpClient('http://localhost:3000', {
33
+ headers: { Authorization: 'Bearer token' },
29
34
  });
35
+ await client.initialize();
36
+ const result = await client.callTool('my_tool', { query: 'test' });
30
37
  ```
31
38
 
32
39
  ### SSE Transport
@@ -38,41 +45,50 @@ const client = new McpSseClient('http://localhost:3000');
38
45
  const result = await client.callTool('my_tool', { query: 'test' });
39
46
  ```
40
47
 
41
- ### Streamable HTTP (MCP 2025)
48
+ ### Streamable HTTP (MCP 2025-11-25)
49
+
50
+ Wraps the official `@modelcontextprotocol/sdk` `Client` + `StreamableHTTPClientTransport`. The SDK
51
+ transport sets `Accept: application/json, text/event-stream`, captures/resends `Mcp-Session-Id`,
52
+ negotiates the protocol version, and sends `DELETE /mcp` on `close()`.
42
53
 
43
54
  ```typescript
44
55
  import { McpStreamableHttpClient } from 'fa-mcp-sdk';
45
56
 
46
57
  const client = new McpStreamableHttpClient('http://localhost:3000', {
47
- headers: { 'Authorization': 'Bearer token' },
58
+ headers: { Authorization: 'Bearer token' },
48
59
  requestTimeoutMs: 60000,
49
60
  });
50
61
 
51
- await client.initialize({
52
- protocolVersion: '2024-11-05',
53
- clientInfo: { name: 'test-client', version: '1.0.0' },
54
- });
62
+ // `initialize()` runs the full handshake; the server answers the negotiated version (2025-11-25).
63
+ await client.initialize();
55
64
 
56
65
  const result = await client.callTool('my_tool', { query: 'test' });
57
66
  const prompt = await client.getPrompt('agent_brief');
58
67
  const resources = await client.listResources();
59
68
  const content = await client.readResource('custom://data');
60
69
 
61
- // Notifications
62
- const unsub = client.onNotification('notifications/tools/list_changed', (p) => console.log(p));
63
-
64
70
  await client.close();
65
71
  ```
66
72
 
67
- **Methods:** `initialize`, `close`, `callTool`, `getPrompt`, `listResources`, `readResource`, `listTools`, `listPrompts`, `sendRpc`, `notify`, `onNotification`
73
+ **Methods:** `initialize`, `close`, `callTool`, `getPrompt`, `listResources`, `readResource`,
74
+ `listTools`, `listPrompts`. After `initialize()`, `client.serverInfo`, `client.capabilities` and
75
+ `client.protocolVersion` are populated.
68
76
 
69
77
  ## Transport Types
70
78
 
71
79
  | Transport | Config | Use Case |
72
80
  |-----------|--------|----------|
73
81
  | STDIO | `mcp.transportType: "stdio"` | CLI, local dev |
74
- | HTTP | `mcp.transportType: "http"` | Web integrations, REST API |
75
- | SSE | HTTP transport | Long-running ops, streaming |
82
+ | HTTP (Streamable HTTP) | `mcp.transportType: "http"` | Web integrations, REST API |
83
+ | SSE (legacy) | HTTP transport | Long-running ops, streaming; older clients |
84
+
85
+ The HTTP transport is **Streamable HTTP** (SDK `StreamableHTTPServerTransport`), stateful per session:
86
+
87
+ | Method `/mcp` | Behaviour |
88
+ |---------------|-----------|
89
+ | `POST` | `initialize` opens a session (server returns `Mcp-Session-Id`); subsequent calls must echo it. Notifications → **202**. No session + not `initialize` → **400**. |
90
+ | `GET` | Server→client SSE stream for the session. |
91
+ | `DELETE` | Tears down the session. |
76
92
 
77
93
  ## Running Tests
78
94
 
@@ -485,7 +485,7 @@ import {
485
485
  |-----------|--------|--------------|
486
486
  | STDIO | `Server.getClientCapabilities()` (low-level SDK) read inside every handler in `createMcpServer()`. | Every call. |
487
487
  | SSE | Same as STDIO, but resolved per-connection (each SSE session owns its own `Server`). | Every call. |
488
- | Streamable HTTP | Cached on `initialize` keyed by `Mcp-Session-Id` (in-memory `Map`, soft FIFO cap of 4096 sessions). | Subsequent calls only when the client sends the `Mcp-Session-Id` header. `undefined` otherwise handlers MUST fall back to text-only. |
488
+ | Streamable HTTP | Same as STDIO — each session owns its own `Server` + `StreamableHTTPServerTransport` (stateful, keyed by `Mcp-Session-Id`, in-memory `Map` with soft FIFO cap of 4096 sessions). `getClientCapabilities()` is read per handler. | Every call within a session. A client that never sends `Mcp-Session-Id` cannot make post-`initialize` calls (→ 400), so handlers still treat `undefined` as text-only. |
489
489
 
490
490
  ```ts
491
491
  export const handleToolCall = async (params: IToolHandlerParams) => {
@@ -0,0 +1,342 @@
1
+ # 11 — Public Contract
2
+
3
+ This document is the **formal public contract** of the `fa-mcp-sdk` package. Everything listed
4
+ here is part of the API surface that the SDK guarantees, and every change to it follows the
5
+ versioning policy at the bottom of this file (semver: MAJOR / MINOR / PATCH).
6
+
7
+ If a behaviour is **not** described here — even if it is currently observable in the source — it
8
+ is considered an implementation detail and may change in any release.
9
+
10
+ ---
11
+
12
+ ## 1. Transports
13
+
14
+ | Transport | Standard | Status | Notes |
15
+ |--------------------|----------|--------|-------------------------------------------------------------|
16
+ | `stdio` | §6 | MUST | Single JSON-RPC stream over stdin/stdout |
17
+ | `streamable_http` | §6 | MUST | `POST/GET/DELETE /mcp` driven by the SDK transport |
18
+ | `legacy_http_sse` | §6 | SHOULD | `GET /sse` + `POST /messages` — kept for backwards-compat |
19
+
20
+ All HTTP routes hosted by the SDK are listed in §2.
21
+
22
+ **SSE resumability (opt-in, §6 MAY).** With `mcp.sse.resumability: true` the Streamable HTTP transport
23
+ keeps recent SSE events in a per-process in-memory ring buffer (`mcp.sse.maxStoredEvents`, default 1000),
24
+ so a client reconnecting to `GET /mcp` with a `Last-Event-ID` header replays the events it missed. Off by
25
+ default. The buffer does not survive a restart and does not span multiple server instances — a persistent
26
+ store would be required for that.
27
+
28
+ ---
29
+
30
+ ## 2. HTTP endpoints
31
+
32
+ | Path | Method | Auth | Level | Purpose |
33
+ |---------------------------------------------------|--------|------|--------|---------------------------------------------------------------|
34
+ | `/mcp` | POST | Yes | MUST | JSON-RPC entry point (`initialize`, `tools/*`, …) |
35
+ | `/mcp` | GET | Yes | MUST | Server-initiated SSE stream for the active session |
36
+ | `/mcp` | DELETE | Yes | MUST | Session teardown |
37
+ | `/sse` | GET | Yes | SHOULD | Legacy SSE connect |
38
+ | `/sse` | POST | Yes | SHOULD | Legacy direct JSON-RPC |
39
+ | `/messages` | POST | Yes | SHOULD | Legacy SSE message channel |
40
+ | `/health` | GET | No | MUST | Liveness; returns `{status, version, uptime, details}` |
41
+ | `/ready` | GET | No | SHOULD | Readiness; `{status, checks}` |
42
+ | `/metrics` | GET | No | SHOULD | Prometheus exposition (opt-in via `webServer.metrics.enabled`) |
43
+ | `/` | GET | No | MAY | Static home page |
44
+ | `/ct` | POST | No | MUST | Token validity check via JSON body |
45
+ | `/ct?t=…` | GET | No | MAY | Disabled by default (`webServer.tokenCheck.allowQueryToken`) |
46
+ | `/used-http-headers` | GET | No | MAY | Returns the project's `usedHttpHeaders` declaration |
47
+ | `/.well-known/oauth-protected-resource` | GET | No | MUST* | Active in JWT modes `embedded` / `localKey` / `remoteJwks` |
48
+ | `/.well-known/openid-configuration` | GET | No | MUST* | OIDC discovery |
49
+ | `/.well-known/jwks.json` | GET | No | MUST* | JWK Set with the active public key |
50
+ | `/oauth/token` | POST | No | MUST* | Embedded IdP — `grant_type=password` |
51
+ | `/gen-jwt` | POST | Yes | MAY | JWT issuance API (`webServer.genJwtApiEnable`) |
52
+ | `/admin` | GET | Yes | MAY | Token Generator UI |
53
+ | `/agent-tester` | GET | Yes? | MAY | Built-in chat UI (`agentTester.enabled`) |
54
+ | `/api/openapi.json` / `/api/openapi.yaml` / `/docs` | GET | - | MAY | OpenAPI when the project supplies `httpComponents.apiRouter` |
55
+
56
+ `MUST*` rows are mandatory only when the corresponding feature is active.
57
+
58
+ ---
59
+
60
+ ## 3. Authentication
61
+
62
+ The SDK accepts the following `Authorization` schemes, picked by header format (not order):
63
+
64
+ - `Bearer <token>` — JWT (any of the four modes: `legacyAesCtr` / `embedded` / `localKey` /
65
+ `remoteJwks`) or a permanent server token.
66
+ - `Basic <base64>` — HTTP Basic auth.
67
+ - Optional `customAuthValidator` — last fallback for project-specific schemes.
68
+
69
+ JWT modes are documented in [04-authentication](04-authentication.md). Public contract:
70
+
71
+ | Claim | Required | Notes |
72
+ |-------------|----------|--------------------------------------------------------------------------|
73
+ | `sub` | MUST | Subject — drives rate-limit bucket and concurrency cap |
74
+ | `exp` | MUST | Expiration; SDK enforces with `clockSkew` (default 30 s, max 60 s) |
75
+ | `aud` | SHOULD | Defaults to `appConfig.name`; configurable via `expectedAudience` |
76
+ | `iss` | SHOULD* | Required in modes `embedded` / `localKey` / `remoteJwks` |
77
+ | `scope` | MAY | Space-separated scopes; matched against `requiredScopes` per §7.5 |
78
+ | `ip` | MAY | When set + `isCheckIP=true`, client IP must match |
79
+ | `service` | MAY | When set + `checkMCPName=true`, must contain `appConfig.name` |
80
+ | `jti` | MAY | Used by the revocation list |
81
+
82
+ Scopes are matched against `requiredScopes` on tools, prompts, and resources (§7.5).
83
+
84
+ `WWW-Authenticate: Bearer realm="<name>" resource_metadata="<url>"` is emitted on every 401
85
+ from MCP endpoints per §7.4. 403 responses (authenticated but forbidden) carry NO
86
+ `WWW-Authenticate` header.
87
+
88
+ ---
89
+
90
+ ## 4. MCP methods
91
+
92
+ | Method | Status | Notes |
93
+ |---------------------------------------|--------|-----------------------------------------------------------|
94
+ | `initialize` | MUST | |
95
+ | `notifications/initialized` | MUST | |
96
+ | `tools/list` | MUST | Server-side pagination via `mcp.pagination.pageSize` |
97
+ | `tools/call` | MUST | Honours `signal`, `_meta.progressToken`, `requiredScopes` |
98
+ | `prompts/list` | MUST | Capability advertised only when the server has prompts (§8.2) |
99
+ | `prompts/get` | MUST | Returns `-32601` when no prompts are configured |
100
+ | `resources/list` | MUST | Same pagination contract |
101
+ | `resources/read` | MUST | Returns `text` or base64 `blob` per entry (§11.4) |
102
+ | `resources/templates/list` | MAY | `mcp.resources.templatesEnabled` |
103
+ | `resources/subscribe` / `unsubscribe` | MAY | `mcp.resources.subscribeEnabled` |
104
+ | `completion/complete` | MAY | `mcp.completions.enabled` + `completionProvider` (§8.2) |
105
+ | `tasks/list` | MAY | `mcp.tasks.enabled`; caller's own tasks, newest first, paginated (§8.7) |
106
+ | `tasks/get` | MAY | `mcp.tasks.enabled`; current task metadata (§8.7) |
107
+ | `tasks/result` | MAY | `mcp.tasks.enabled`; the `tools/call` result once completed (§8.7) |
108
+ | `tasks/cancel` | MAY | `mcp.tasks.enabled`; aborts a running task, idempotent (§8.7) |
109
+ | `logging/setLevel` | SHOULD | Capability `logging: {}` (default ON) |
110
+ | `notifications/message` | SHOULD | Emitted by `sendLoggingMessage()` |
111
+ | `notifications/progress` | SHOULD | Emitted by `IToolHandlerParams.sendProgress()` (§8.6) |
112
+ | `notifications/cancelled` | SHOULD | Aborts `IToolHandlerParams.signal` (§8.5) |
113
+ | `notifications/tasks/status` | MAY | Emitted on every task status transition (§8.7) |
114
+
115
+ When `mcp.tasks.enabled` is `true`, the server advertises the `tasks` capability
116
+ (`{ list, cancel, requests: { tools: { call } } }`) and a `tools/call` carrying a `task` parameter
117
+ is executed as a task: the server returns a `CreateTaskResult` (`{ task: { taskId, status, … } }`)
118
+ immediately and runs the tool in the background. A tool opts in via `execution.taskSupport`
119
+ (`optional` / `required` / `forbidden`, see §5) — sending `task` to a tool that does not support it,
120
+ or omitting `task` for a `required` tool, returns `-32602`. The default task store keeps records in
121
+ process memory only; it does **not** survive a restart. When `mcp.tasks.enabled` is `false` (the
122
+ default) the capability is not advertised and all four `tasks/*` methods return `-32601`.
123
+
124
+ ---
125
+
126
+ ## 5. Tool / Prompt / Resource format
127
+
128
+ ### Tool (`Tool` from `@modelcontextprotocol/sdk`)
129
+
130
+ - `name` — MUST be `snake_case` and unique (validated at boot via
131
+ `validate-tool-names.ts`).
132
+ - `description` — MUST be non-empty. Deprecation prefix `[DEPRECATED until …]` is added
133
+ automatically when `_meta.deprecated` is set.
134
+ - `inputSchema` — MUST declare `$schema: 'https://json-schema.org/draft/2020-12/schema'` and
135
+ `additionalProperties: false`.
136
+ - `outputSchema` — MAY; when present, the SDK validates `structuredContent` against it and
137
+ mirrors the value into `content[0]` as JSON text (§12.4).
138
+ - `title` — SHOULD; user-facing label.
139
+ - `execution.taskSupport` — MAY; one of `optional` / `required` / `forbidden` (default — absence is
140
+ treated as `forbidden`, i.e. synchronous only). Controls task-augmented execution (§8.7); passed
141
+ through verbatim in `tools/list`. Effective only when `mcp.tasks.enabled` is `true`.
142
+ - `annotations` — MAY; may be hidden via `mcp.tools.hideAnnotations`.
143
+ - `_meta._meta.requiredScopes` (or top-level `requiredScopes`) — MAY; OAuth scopes enforced
144
+ before dispatch.
145
+ - `_meta.deprecated` — MAY; structured `IDeprecationInfo`.
146
+ - `_meta.ui` — MAY; MCP Apps widget metadata.
147
+
148
+ ### Prompt (`IPromptData`)
149
+
150
+ `name`, `description`, `arguments[]` (each `IPromptArgument`), `content` (string or function),
151
+ `requireAuth`, `requiredScopes`, `deprecated`. Optional UI metadata (§10.5, MAY): `title` (human-facing
152
+ label, falls back to `name`) and `icons` (`IIcon[]` — `{ src; mimeType?; sizes? }`). Both pass through
153
+ `prompts/list` unchanged; built-in `agent_brief` / `agent_prompt` carry a `title`.
154
+
155
+ ### Resource (`IResourceData` / `IResourceInfo`)
156
+
157
+ `uri`, `name`, `description`, `mimeType`, optional `title`, `size` (bytes, §11.3 MAY),
158
+ `icons` (`IIcon[]`, §11.3 MAY), `requireAuth`, `requiredScopes`, `_meta`, `deprecated`. On
159
+ `resources/list` the SDK computes `size` from the content (UTF-8 byte length for text/objects, buffer
160
+ length for blobs) when the author did not set it; lazy (function) content omits `size`. `content` is a
161
+ string / object / function for text resources, or
162
+ `IResourceBinaryContent` (`{ blob: Buffer | base64-string, base64?: boolean }`) for binary
163
+ resources — `resources/read` then returns base64 `contents[0].blob` (no `text`) with the
164
+ resource's `mimeType` (§11.4 / §12.2). Built-in URI schemes are guaranteed by the SDK:
165
+
166
+ | URI | Purpose |
167
+ |--------------------------------------------|------------------------------------------------|
168
+ | `project://version` | Returns `appConfig.version` |
169
+ | `use://auth` | Authentication self-description |
170
+ | `<service>://agent/brief` | Mirrors `agent_brief` prompt |
171
+ | `<service>://agent/prompt` | Mirrors `agent_prompt` prompt |
172
+ | `doc://...` | Application docs |
173
+
174
+ ### Sensitive data masking (`maskSensitive`, §12.2)
175
+
176
+ Masking personal / sensitive data in tool results is the server's responsibility — the SDK never masks
177
+ automatically. The optional helper `maskSensitive(value, rules)` (exported from the barrel, with the
178
+ `IMaskRules` type) is a reusable building block: it walks an object / array / string and applies explicit
179
+ rules — `fieldNames` (case-insensitive field-name match) and `patterns` (regular expressions on string
180
+ values at any depth) — replacing matches with `replacement` (a string, default `'***'`, or a function for
181
+ partial masking like `4111********1111`). It returns a new value and never mutates the input. Call it
182
+ inside a tool handler before returning the result; choosing the rules and where to apply them stays with
183
+ the server.
184
+
185
+ ---
186
+
187
+ ## 6. Error format
188
+
189
+ JSON-RPC errors follow Appendix B of the standard. Mapping (JSON-RPC → HTTP):
190
+
191
+ | JSON-RPC code | HTTP | Class | Trigger |
192
+ |---------------|------|----------------------|-----------------------------------------------|
193
+ | `-32600` | 400 | (none) | Invalid Request |
194
+ | `-32601` | 404 | `ResourceNotFoundError` (when applicable) | Method/resource not found |
195
+ | `-32602` | 400 | `ValidationError` | Invalid params (input schema, unknown tool) |
196
+ | `-32603` | 500 | `ServerError` | Internal error |
197
+ | `-32000` | varies | `BaseMcpError` | Generic SDK error |
198
+ | `-32002` | 404 | `ResourceNotFoundError` | Resource lookup failed |
199
+ | `-32003` | 429 | `RateLimitedError` | Rate limit / concurrent-call cap (+ `Retry-After`) |
200
+ | `-32004` | 504 | `TimeoutError` | `mcp.limits.toolTimeoutMs` exceeded |
201
+ | `-32005` | 413 | `PayloadTooLargeError` | `mcp.limits.maxPayloadBytes` exceeded |
202
+ | `-32006` | 503 | `UpstreamUnavailableError` | Dependency (DB / downstream) unreachable |
203
+ | `-32007` | 409 | `ConflictError` | State conflict (duplicate / optimistic lock) |
204
+
205
+ Unrecognized internal errors are sanitized (§13.3 / Appendix C.3): the outward `error.message`
206
+ collapses to `Internal error`, the full text is written to the internal log keyed by `requestId`,
207
+ and absolute filesystem paths are scrubbed from any outward message. Recognized domain errors (any
208
+ class above) keep their message verbatim.
209
+
210
+ `error.data` is structured per Appendix B.3:
211
+
212
+ ```jsonc
213
+ {
214
+ "requestId": "uuid…", // §15.1, always set by the SDK if absent
215
+ "field": "name", // input validation diagnostics
216
+ "reason": "invalid_type", // machine-readable hint
217
+ "retryAfter": 12 // seconds, for -32003
218
+ // …implementation-specific keys are allowed but not part of the contract
219
+ }
220
+ ```
221
+
222
+ ---
223
+
224
+ ## 7. Limits and headers
225
+
226
+ | Limit / Header | Source / Default |
227
+ |------------------------------|-------------------------------------------------------------------------|
228
+ | `mcp.limits.maxPayloadBytes` | 1 MiB |
229
+ | `mcp.limits.maxToolResultBytes` | 10 MiB |
230
+ | `mcp.limits.toolTimeoutMs` | 30 000 ms |
231
+ | `mcp.rateLimit.maxRequests` | 100 / window |
232
+ | `mcp.rateLimit.windowMs` | 60 000 ms |
233
+ | `mcp.rateLimit.maxConcurrentPerSubject` | 16 |
234
+ | `mcp.pagination.pageSize` | 100 |
235
+ | `mcp.logging.defaultLevel` | `info` (Syslog ladder) |
236
+ | `mcp.progress.throttleMs` | 100 (10 events/s/token) |
237
+ | `mcp.completions.enabled` | `false` (opt-in; needs `completionProvider`) |
238
+ | `mcp.tasks.enabled` | `false` (opt-in; advertises `tasks` capability) |
239
+ | `mcp.tasks.defaultTtlMs` | 3 600 000 ms (finished-task retention; clamped to `[minTtlMs, maxTtlMs]`) |
240
+ | `mcp.tasks.maxTtlMs` | 86 400 000 ms (hard retention ceiling) |
241
+ | `mcp.tasks.pollIntervalMs` | 1000 ms (suggested to client in every task object) |
242
+ | `mcp.tasks.maxTasks` | 1000 (retained tasks; oldest finished evicted first) |
243
+ | `webServer.metrics.enabled` | `false` (opt-in) |
244
+ | `X-Request-Id` (response) | Always present — generated when client did not supply one (§15.1) |
245
+ | `tracestate` (response) | Echoed back unchanged when client supplied a valid value |
246
+ | `WWW-Authenticate` | On every 401 from MCP endpoints (§7.4) |
247
+ | `Retry-After` | On every 429 (§14) |
248
+ | `MCP-Session-Id` | Set by SDK on `initialize`; subsequent requests MUST echo it |
249
+ | `MCP-Protocol-Version` | Negotiated by the SDK transport |
250
+
251
+ ---
252
+
253
+ ## 8. Versioning policy (§17.1)
254
+
255
+ | Change | Bump |
256
+ |-----------------------------------------------------------------|-------|
257
+ | Removing a tool / prompt / resource | MAJOR |
258
+ | Adding a `required` field to an `inputSchema` | MAJOR |
259
+ | Removing a field from an `outputSchema` | MAJOR |
260
+ | Changing the default JWT algorithm / mode | MAJOR |
261
+ | Renaming or removing an HTTP endpoint | MAJOR |
262
+ | Removing a configuration key (`mcp.*`, `webServer.*`, …) | MAJOR |
263
+ | Backwards-incompatible change to `error.data` shape | MAJOR |
264
+ | Adding a new tool / prompt / resource | MINOR |
265
+ | Adding an optional field to any schema | MINOR |
266
+ | Adding a new capability or behaviour gated by an opt-in flag | MINOR |
267
+ | Adding a new optional configuration key (with safe default) | MINOR |
268
+ | Extending `description` or `title` | PATCH |
269
+ | Bug-fix without changing the contract | PATCH |
270
+ | Documentation-only change | PATCH |
271
+
272
+ `[BREAKING]` is the required marker in `CHANGELOG.md` for any MAJOR entry.
273
+
274
+ ### Historical examples
275
+
276
+ | Release | Bump | Driver |
277
+ |---------|-------|------------------------------------------------------------------------|
278
+ | 0.4.145 | MINOR | MCP 2025-11-25 via SDK Streamable HTTP |
279
+ | 0.5.0 | MAJOR | HTTP hardening (default bind `127.0.0.1`, error codes, rate-limit) |
280
+ | 0.6.0 | MAJOR | Tools/Prompts/Resources contract (`additionalProperties:false`, mirror)|
281
+ | 0.7.0 | MAJOR | RS256/ES256 JWT runtime, OAuth/OIDC discovery, scope enforcement |
282
+ | 0.8.x | MINOR | Observability (X-Request-Id, traceparent, logging, metrics, progress) |
283
+ | 0.9.1 | MINOR | Conditional capabilities, `-32006`/`-32007`, binary `blob`, error sanitization, opt-in completions |
284
+ | 0.10.0 | MINOR | Opt-in `tasks` capability (task-augmented execution), `execution.taskSupport`, in-memory task store |
285
+
286
+ ---
287
+
288
+ ## 9. Deprecation process (§17.2)
289
+
290
+ Authors declare deprecation in a structured shape (no free-form `[DEPRECATED]` in descriptions):
291
+
292
+ ```typescript
293
+ // tools.ts
294
+ const myTool: Tool = {
295
+ name: 'old_tool',
296
+ description: 'Returns the rate.',
297
+ _meta: {
298
+ deprecated: { until: '2026-08-28', replacedBy: 'new_tool', note: 'See migration guide' },
299
+ },
300
+ // …
301
+ };
302
+
303
+ // prompts / resources
304
+ const myPrompt: IPromptData = {
305
+ name: 'old_prompt',
306
+ description: '…',
307
+ deprecated: { until: '2026-08-28', replacedBy: 'new_prompt' },
308
+ // …
309
+ };
310
+ ```
311
+
312
+ The SDK then:
313
+
314
+ 1. mutates `description` on list responses to include
315
+ `[DEPRECATED until YYYY-MM-DD, use <replacedBy>]`;
316
+ 2. logs a `logger.warn` the first time per hour each `(kind, name)` is invoked;
317
+ 3. logs a `logger.error` at registration time if `until` is already in the past — the entry
318
+ should be removed instead of shipped.
319
+
320
+ **Window**: minimum `2 MINOR releases OR 3 months` from announcement to removal (per §17.2),
321
+ whichever is longer.
322
+
323
+ ---
324
+
325
+ ## 10. Public contract source list
326
+
327
+ The runtime sources of the contract above are:
328
+
329
+ - `src/core/_types_/types.ts` — `IToolHandlerParams`, `IPromptData`, `IResourceInfo`,
330
+ `IDeprecationInfo`, `McpServerData`.
331
+ - `src/core/_types_/config.ts` — `AppConfig` (every documented configuration key).
332
+ - `src/core/errors/BaseMcpError.ts` + `src/core/errors/specific-errors.ts` — error codes.
333
+ - `src/core/mcp/create-mcp-server.ts` — handler contract.
334
+ - `src/core/mcp/task-store.ts` — `ITaskStore` / `InMemoryTaskStore`, task lifecycle (§8.7).
335
+ - `src/core/web/server-http.ts` — HTTP endpoints, headers, response shape.
336
+ - `src/core/web/request-id.ts` — `X-Request-Id` + W3C trace context middleware.
337
+ - `src/core/mcp/mcp-logging.ts` — `logging` capability.
338
+ - `src/core/metrics/metrics.ts` — Prometheus series.
339
+ - `src/core/mcp/deprecation.ts` — deprecation lifecycle.
340
+
341
+ Anything that lives outside this list (file names, internal helpers, log line formats, etc.) is
342
+ **not** part of the contract and may change without notice.
@@ -208,3 +208,40 @@ Generate an admin-capable JWT:
208
208
  node scripts/generate-jwt.js -u admin -ttl 30d -p "allow=gen-token"
209
209
  ```
210
210
 
211
+ ## Public Contract
212
+
213
+ The runtime contract surfaced by this server (transports, HTTP endpoints, JWT claims, tool /
214
+ prompt / resource shape, error mapping, headers, semver and deprecation policy) is documented
215
+ in [FA-MCP-SDK-DOC/11-public-contract.md](FA-MCP-SDK-DOC/11-public-contract.md).
216
+
217
+ Tools, prompts and resources exposed by this project are listed in the table below — update
218
+ this section whenever you add, rename, or remove an entry.
219
+
220
+ ### Tools
221
+
222
+ | Name | Description |
223
+ |------|-------------|
224
+ | `<tool_name>` | Short description |
225
+
226
+ ### Prompts
227
+
228
+ | Name | Description |
229
+ |------|-------------|
230
+ | `agent_brief` | Short agent description (built-in) |
231
+ | `agent_prompt` | Full system prompt (built-in) |
232
+
233
+ ### Resources
234
+
235
+ | URI | Description |
236
+ |-----|-------------|
237
+ | `project://version` | SDK / project version (built-in) |
238
+ | `use://auth` | Authentication self-description (built-in) |
239
+ | `<service>://agent/brief` | Mirror of `agent_brief` (built-in) |
240
+ | `<service>://agent/prompt` | Mirror of `agent_prompt` (built-in) |
241
+
242
+ ## Versioning policy
243
+
244
+ This project follows the semver contract documented in
245
+ [FA-MCP-SDK-DOC/11-public-contract.md](FA-MCP-SDK-DOC/11-public-contract.md#8-versioning-policy-171).
246
+ Mark every `MAJOR` change with `[BREAKING]` in `CHANGELOG.md`.
247
+
@@ -0,0 +1,10 @@
1
+ # ── Image tag (set by CI deploy job) ───────────────────────
2
+ IMAGE_TAG={{project.name}}:latest
3
+
4
+ # ── Host port (external port on the server) ────────────────
5
+ HOST_PORT={{port}}
6
+
7
+ # ── Service identity ──────────────────────────────────────
8
+ SERVICE_NAME={{project.name}}
9
+ SERVICE_INSTANCE=prod
10
+ NODE_ENV=production
@@ -0,0 +1,44 @@
1
+ # ── Stage 1: install + build ──────────────────────────────────────────────────
2
+ FROM node:22-alpine AS builder
3
+
4
+ WORKDIR /app
5
+
6
+ # Зависимости (кэшируется отдельным слоем)
7
+ COPY package.json yarn.lock ./
8
+ RUN yarn install --frozen-lockfile --production=false
9
+
10
+ # Исходники и конфиг сборки
11
+ COPY tsconfig.json ./
12
+ COPY src/ ./src/
13
+ COPY scripts/ ./scripts/
14
+ COPY config/ ./config/
15
+
16
+ # Backend build (tsc → dist/)
17
+ RUN npm run build
18
+
19
+ # ── Stage 2: production image ────────────────────────────────────────────────
20
+ FROM node:22-alpine AS production
21
+
22
+ WORKDIR /app
23
+
24
+ ARG NODE_ENV=production
25
+ ENV NODE_ENV=${NODE_ENV}
26
+
27
+ # Только production-зависимости
28
+ COPY package.json yarn.lock ./
29
+ RUN yarn install --production=true && yarn cache clean
30
+
31
+ # Собранный backend
32
+ COPY --from=builder /app/dist/ ./dist/
33
+
34
+ # Конфиги (default.yaml, production.yaml, custom-environment-variables.yaml и т.д.)
35
+ COPY --from=builder /app/config/ ./config/
36
+
37
+ # Внешние конфиги (local.yaml) монтируются через volumes в docker-compose.yml.
38
+
39
+ EXPOSE {{port}}
40
+
41
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
42
+ CMD wget --spider -q http://localhost:{{port}}/health || exit 1
43
+
44
+ ENTRYPOINT ["node", "dist/src/start.js"]