fa-mcp-sdk 0.4.66 → 0.4.67

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.
@@ -260,7 +260,11 @@ Follow the plan. For each tool/resource/prompt:
260
260
  leave demo code in the final build.
261
261
  2. Add new config keys to `config/default.yaml` (and matching env mappings in
262
262
  `config/custom-environment-variables.yaml` when appropriate). Mirror structural changes
263
- in `config/_local.yaml`.
263
+ in `config/_local.yaml`. **If the feature talks to any third-party / external service
264
+ (REST API, legacy system, partner endpoint), put its connection attributes — `host`,
265
+ `port`, `protocol`, `token`, credentials, custom fields — under the `accessPoints` block,
266
+ not ad-hoc sections. See `FA-MCP-SDK-DOC/03-configuration.md` → "Access Points" for the
267
+ YAML shape and access pattern.**
264
268
  3. Update `tests/mcp/test-cases.js` with real cases.
265
269
  4. `yarn cb` after each meaningful change; don't accumulate type errors.
266
270
 
@@ -15,7 +15,7 @@ npm install fa-mcp-sdk
15
15
  | [01-getting-started](01-getting-started.md) | `initMcpServer()`, `McpServerData`, `IPromptData`, `IResourceData`, `AppConfig` | Starting new project |
16
16
  | [02-1-tools-and-api](02-1-tools-and-api.md) | Tool definitions, `toolHandler`, outbound webhooks, REST API with tsoa, OpenAPI/Swagger | Creating tools, REST endpoints, webhook callbacks |
17
17
  | [02-2-prompts-and-resources](02-2-prompts-and-resources.md) | Standard/custom prompts, resources, `requireAuth` | Configuring prompts/resources |
18
- | [03-configuration](03-configuration.md) | `appConfig`, YAML config, cache, PostgreSQL | Server configuration, DB |
18
+ | [03-configuration](03-configuration.md) | `appConfig`, YAML config, access points for external services, cache, PostgreSQL | Server configuration, external services, DB |
19
19
  | [04-authentication](04-authentication.md) | JWT, Basic auth, server tokens, `createAuthMW()`, Token Generator, CLI Token Generator, JWT Generation API | Authentication setup |
20
20
  | [05-ad-authorization](05-ad-authorization.md) | AD group authorization at HTTP/tool levels | AD group restrictions |
21
21
  | [06-utilities](06-utilities.md) | `ServerError`, `normalizeHeaders`, logging, Consul, graceful shutdown | Error handling, utilities |
@@ -183,6 +183,158 @@ webServer:
183
183
 
184
184
  ```
185
185
 
186
+ ## Access Points
187
+
188
+ If your MCP server talks to third-party / external services (REST APIs, legacy systems, partner endpoints, etc.),
189
+ declare their connection attributes (`host`, `port`, `protocol`, `token`, credentials, custom fields) under the
190
+ top-level `accessPoints` block in the config — **not** scattered through code or ad-hoc config sections. Benefits:
191
+
192
+ - Single registry of outbound dependencies visible in diagnostics and admin pages.
193
+ - Automatic `host`/`port` resolution via Consul for services registered there.
194
+ - Uniform access pattern (`appConfig.accessPoints.<alias>`) across all tools and modules.
195
+ - Runtime updates — the SDK periodically refreshes dynamic access points from Consul without restarting the server.
196
+
197
+ The SDK automatically wraps `appConfig.accessPoints` in an `AccessPoints` instance on startup and starts the Consul
198
+ updater — **do not call `new AccessPoints(...)` or `accessPointUpdater.start()` manually**.
199
+
200
+ ### Declaring Access Points
201
+
202
+ ```yaml
203
+ accessPoints:
204
+ # Dynamic AP — host/port resolved from Consul
205
+ wso2siAPI:
206
+ title: 'WSO2 SI API'
207
+ consulServiceName: 'dev01-wso2si-d2'
208
+ host: null # filled in from Consul
209
+ port: 9443 # fallback; also used when Consul meta specifies a different port
210
+ protocol: 'https'
211
+ user: 'admin'
212
+ pass: '***'
213
+ myProp: 'anyValue' # any custom field is preserved and available at runtime
214
+
215
+ # Static AP — Consul is NOT used
216
+ externalAPI:
217
+ noConsul: true
218
+ host: 'api.partner.com'
219
+ port: 443
220
+ protocol: 'https'
221
+ token: '***'
222
+ timeoutMs: 5000
223
+ ```
224
+
225
+ ### Using Access Points in Code
226
+
227
+ ```typescript
228
+ import { appConfig } from 'fa-mcp-sdk';
229
+
230
+ // Direct access — always works, for dynamic and static APs alike
231
+ const ap = appConfig.accessPoints.wso2siAPI;
232
+ const url = `${ap.protocol}://${ap.host}:${ap.port}`;
233
+ const token = ap.token; // custom fields available
234
+ const custom = ap.myProp;
235
+
236
+ // "Clean" copy without service fields
237
+ const ap2 = appConfig.accessPoints.getAP('wso2siAPI');
238
+
239
+ // All access points at once
240
+ const all = appConfig.accessPoints.get();
241
+
242
+ // For dynamic APs — wait until host/port are resolved from Consul (first run)
243
+ await ap.waitForHostPortUpdated(5000);
244
+ ```
245
+
246
+ **Strict typing for custom fields:**
247
+
248
+ ```typescript
249
+ import type { IAccessPoint } from 'fa-consul';
250
+
251
+ interface IWso2AP extends IAccessPoint {
252
+ user: string;
253
+ pass: string;
254
+ myProp: string;
255
+ }
256
+
257
+ const ap = appConfig.accessPoints.wso2siAPI as IWso2AP;
258
+ ```
259
+
260
+ ### Access Point Properties
261
+
262
+ **User-defined (configured in YAML):**
263
+
264
+ | Property | Required | Purpose |
265
+ |----------------------------------|-----------------|---------------------------------------------------------------------------------------|
266
+ | `consulServiceName` | yes for dynamic | Consul service name used to resolve `host`/`port` |
267
+ | `host` | — | IP/hostname. Dynamic: usually `null`, filled from Consul. Static (`noConsul`): manual |
268
+ | `port` | — | TCP port. Coerced to `Number` or `null`. Dynamic: from Consul (or `meta.port`) |
269
+ | `protocol` | — | `http` or `https`. Anything other than `https?` is coerced to `http`, lowercased |
270
+ | `title` | — | Human-readable name (defaults to the AP key) |
271
+ | `noConsul` | — | `true` → static AP: Consul is not polled, `consulServiceName` is not required |
272
+ | `retrieveProps` | — | `(host, meta) => ({host, port})`. Custom extractor for Consul response |
273
+ | `updateIntervalIfSuccessMillis` | — | Interval between successful Consul polls for this AP (default 2 min) |
274
+ | `user`, `pass`, `token`, any key | — | Application fields — stored as-is and available at runtime |
275
+
276
+ **Service fields (added automatically by the SDK for dynamic APs):**
277
+
278
+ | Property | Purpose |
279
+ |------------------------------|-------------------------------------------------------------------------|
280
+ | `id` | The AP key from the config |
281
+ | `isAP` | Marker for a dynamic AP; absent on `noConsul` APs |
282
+ | `meta` | Filled from `Service.Meta` of the Consul service on successful poll |
283
+ | `isReachable` | `true` if the last Consul poll returned data |
284
+ | `lastSuccessUpdate` | Timestamp of the last successful update |
285
+ | `idHostPortUpdated` | `true` once `host` + `port` have been populated at least once |
286
+ | `setProps(data)` | Method for externally updating AP fields |
287
+ | `waitForHostPortUpdated(ms)` | Promise that resolves when `host`/`port` have been populated |
288
+ | `getChanges()` | Returns `[propName, oldValue, newValue][]` for the last `setProps` call |
289
+
290
+ ### `noConsul` Access Points
291
+
292
+ Setting `noConsul: true` makes the access point **static** — its address is not resolved through Consul. Typical use
293
+ cases: partner APIs, legacy systems, or services with fixed addresses that cannot (or should not) be registered in
294
+ Consul.
295
+
296
+ Differences from a dynamic AP:
297
+
298
+ - `consulServiceName` is not required.
299
+ - The AP object is stored **as-is** — no normalization of `port`/`protocol`, no service fields (`isAP`, `setProps`,
300
+ `waitForHostPortUpdated`, etc.) are added.
301
+ - The AP is excluded from Consul polling; `host`/`port` are never overwritten.
302
+ - `getAP('key')` and `get()` do **not** return static APs by default (they filter on `isAP`). Pass `andNotIsAP = true`
303
+ to include them, or use direct access — `appConfig.accessPoints.externalAPI` — which always works.
304
+
305
+ ```typescript
306
+ appConfig.accessPoints.getAP('externalAPI', true); // include static AP in lookup
307
+ appConfig.accessPoints.externalAPI; // direct access always works
308
+ ```
309
+
310
+ ### Custom Fields
311
+
312
+ Any additional property on an AP (`apiKey`, `timeoutMs`, `headers: {...}`, etc.) is preserved verbatim and accessible at
313
+ runtime:
314
+
315
+ - On creation, all fields from the config are copied onto the AP object.
316
+ - Periodic Consul updates only refresh `host`/`port` (and optionally `meta`) — other properties are **never
317
+ overwritten**.
318
+ - `get()` / `getAP()` copy all enumerable properties except `undefined` and functions.
319
+ - Nested objects are copied shallowly — if a custom field is an object, its inner references are shared with the
320
+ original config.
321
+ - Only `port` (coerced to `Number`) and `protocol` (coerced to `http`/`https`) are normalized; all other fields are
322
+ left untouched.
323
+
324
+ ### Subscribing to Updates
325
+
326
+ When a dynamic AP is refreshed from Consul, events are emitted on the SDK's `eventEmitter`
327
+ (see "Event System" in `06-utilities.md`):
328
+
329
+ ```typescript
330
+ import { eventEmitter } from 'fa-mcp-sdk';
331
+
332
+ eventEmitter.on('access-point-updated', ({ accessPoint, changes }) => {
333
+ // changes: [propName, oldValue, newValue][]
334
+ });
335
+ eventEmitter.on('access-points-updated', () => { /* any AP was updated this cycle */ });
336
+ ```
337
+
186
338
  ## Cache
187
339
 
188
340
  ```typescript
@@ -143,7 +143,9 @@ import { getConsulAPI, accessPointUpdater, deregisterServiceFromConsul } from 'f
143
143
  const consul = await getConsulAPI();
144
144
  const services = await consul.catalog.service.list();
145
145
 
146
- accessPointUpdater.start(); // Auto-update access points
146
+ // accessPointUpdater is started/stopped by the SDK automatically — see 03-configuration.md "Access Points".
147
+ // The start()/stop() hooks below are exposed only for tests and diagnostics.
148
+ accessPointUpdater.start();
147
149
  accessPointUpdater.stop();
148
150
 
149
151
  await deregisterServiceFromConsul();
@@ -50,7 +50,7 @@
50
50
  "dependencies": {
51
51
  "@modelcontextprotocol/sdk": "^1.29.0",
52
52
  "dotenv": "^17.4.1",
53
- "fa-mcp-sdk": "^0.4.66"
53
+ "fa-mcp-sdk": "^0.4.67"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/express": "^5.0.6",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fa-mcp-sdk",
3
3
  "productName": "FA MCP SDK",
4
- "version": "0.4.66",
4
+ "version": "0.4.67",
5
5
  "description": "Core infrastructure and templates for building Model Context Protocol (MCP) servers with TypeScript",
6
6
  "type": "module",
7
7
  "main": "dist/core/index.js",
@@ -2,17 +2,22 @@
2
2
  import { cpSync, existsSync, rmSync } from 'fs';
3
3
  import { join } from 'path';
4
4
 
5
- const src = join(process.cwd(), './node_modules/fa-mcp-sdk/cli-template/FA-MCP-SDK-DOC');
6
- const dest = join(process.cwd(), 'FA-MCP-SDK-DOC');
5
+ const templateDir = join(process.cwd(), './node_modules/fa-mcp-sdk/cli-template');
6
+ const cwd = process.cwd();
7
7
 
8
- if (!existsSync(src)) {
9
- console.error('Source not found:', src);
10
- process.exit(1);
11
- }
8
+ const targets = [
9
+ { name: 'FA-MCP-SDK-DOC', src: join(templateDir, 'FA-MCP-SDK-DOC'), dest: join(cwd, 'FA-MCP-SDK-DOC') },
10
+ { name: '.claude', src: join(templateDir, '.claude'), dest: join(cwd, '.claude') },
11
+ ];
12
12
 
13
- if (existsSync(dest)) {
14
- rmSync(dest, { recursive: true });
13
+ for (const { name, src, dest } of targets) {
14
+ if (!existsSync(src)) {
15
+ console.error('Source not found:', src);
16
+ process.exit(1);
17
+ }
18
+ if (existsSync(dest)) {
19
+ rmSync(dest, { recursive: true });
20
+ }
21
+ cpSync(src, dest, { recursive: true });
22
+ console.log(`${name} updated`);
15
23
  }
16
-
17
- cpSync(src, dest, { recursive: true });
18
- console.log('FA-MCP-SDK-DOC updated');