@ttoss/http-server-mcp 0.28.1 → 0.29.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.
package/README.md CHANGED
@@ -127,6 +127,31 @@ const data = await apiCall('GET', 'https://partner.api.com/data', {
127
127
 
128
128
  `apiCall` throws with a clear message when called with a relative path and no `apiBaseUrl` is configured in the context.
129
129
 
130
+ ### Errors
131
+
132
+ A non-2xx response throws, with the message read off the error body — `{ error: 'text' }` and `{ error: { code, message } }` both work, the latter as `code: message`. A body naming neither falls back to `HTTP <status>`.
133
+
134
+ ```typescript
135
+ // { "error": { "code": "plan_limit_reached", "message": "Upgrade to add more." } }
136
+ // → Error: plan_limit_reached: Upgrade to add more.
137
+ ```
138
+
139
+ Inside a tool handler the thrown message is what the MCP SDK returns to the client, so it is the whole answer the calling model acts on.
140
+
141
+ ### Using your own HTTP client
142
+
143
+ A handler that does not use `apiCall` can still reuse its two request-scoped pieces: `getApiHeaders()` returns the headers `getApiHeaders` produced for the current MCP request, and `errorBodyMessage(body)` renders an error body exactly as `apiCall` does.
144
+
145
+ ```typescript
146
+ import { errorBodyMessage, getApiHeaders } from '@ttoss/http-server-mcp';
147
+
148
+ const response = await fetch(url, { headers: getApiHeaders() });
149
+ if (!response.ok) {
150
+ const body = await response.json().catch(() => undefined);
151
+ throw new Error(errorBodyMessage(body) ?? `HTTP ${response.status}`);
152
+ }
153
+ ```
154
+
130
155
  ## Authentication
131
156
 
132
157
  `createMcpRouter` supports OAuth 2.0 Bearer token authentication via the `auth` option. Incoming MCP requests must include a valid `Authorization: Bearer <token>` header — invalid or missing tokens receive a `401 Unauthorized` response. The MCP lifecycle methods `initialize` and `tools/list` are exempt by default so clients can discover the server before authenticating (see [Public methods and discovery](#public-methods-and-discovery)).
@@ -593,6 +618,20 @@ Generic HTTP helper for use inside MCP tool handlers.
593
618
 
594
619
  **Returns:** `Promise<unknown>` — Parsed JSON response body
595
620
 
621
+ **Throws:** `Error` on a non-2xx response, carrying the error body's message (see [Errors](#errors))
622
+
623
+ ### `getApiHeaders()`
624
+
625
+ Returns a copy of the headers `createMcpRouter`'s `getApiHeaders` option produced for the current MCP request — the same headers `apiCall` injects.
626
+
627
+ **Returns:** `Record<string, string>` — `{}` outside a request or when `getApiHeaders` is not configured
628
+
629
+ ### `errorBodyMessage(body)`
630
+
631
+ Reads the message off a REST error body — `{ error: 'text' }` or `{ error: { code?, message? } }` (rendered `code: message`).
632
+
633
+ **Returns:** `string | undefined` — `undefined` when the body carries no message
634
+
596
635
  ### `getIdentity<T>()`
597
636
 
598
637
  Returns the verified JWT payload for the current MCP request. Only available inside a tool handler when `auth` is configured. Returns `undefined` when called outside an authenticated context.
package/dist/index.cjs CHANGED
@@ -24,6 +24,19 @@ var requestContextStore = new node_async_hooks.AsyncLocalStorage();
24
24
  var getIdentity = () => {
25
25
  return requestContextStore.getStore()?.identity;
26
26
  };
27
+ /**
28
+ * Returns the headers `getApiHeaders` produced for the current MCP request —
29
+ * the same headers `apiCall` injects. Use it inside a tool handler that runs
30
+ * its own HTTP client instead of `apiCall`, so caller credentials still reach
31
+ * the upstream API.
32
+ *
33
+ * Returns `{}` outside a request, or when `getApiHeaders` is not configured.
34
+ */
35
+ var getApiHeaders = () => {
36
+ return {
37
+ ...(requestContextStore.getStore()?.apiHeaders ?? {})
38
+ };
39
+ };
27
40
 
28
41
  //#endregion
29
42
  //#region src/serveMcpRequest.ts
@@ -588,6 +601,43 @@ var buildVerifyToken = auth => {
588
601
  return payload;
589
602
  };
590
603
  };
604
+ var isRecord = value => {
605
+ return typeof value === "object" && value !== null && !Array.isArray(value);
606
+ };
607
+ var readText = value => {
608
+ return typeof value === "string" && value !== "" ? value : void 0;
609
+ };
610
+ /**
611
+ * The message an error body carries, across the two shapes REST APIs answer
612
+ * with: `{ error: 'text' }` and `{ error: { code?, message? } }`.
613
+ *
614
+ * The structured one is why this is read rather than taken: an object handed
615
+ * to `new Error()` renders as `[object Object]`, and inside a tool handler
616
+ * that string is the whole answer the calling model acts on.
617
+ *
618
+ * Exported so a tool handler with its own HTTP client renders error bodies
619
+ * exactly as `apiCall` does. Returns `undefined` when the body carries no
620
+ * message.
621
+ *
622
+ * @example
623
+ * ```typescript
624
+ * if (!response.ok) {
625
+ * const body = await response.json().catch(() => undefined);
626
+ * throw new Error(errorBodyMessage(body) ?? `HTTP ${response.status}`);
627
+ * }
628
+ * ```
629
+ */
630
+ var errorBodyMessage = body => {
631
+ if (!isRecord(body)) return;
632
+ const {
633
+ error
634
+ } = body;
635
+ if (!isRecord(error)) return readText(error);
636
+ const code = readText(error.code);
637
+ const message = readText(error.message);
638
+ if (message === void 0) return code;
639
+ return code === void 0 ? message : `${code}: ${message}`;
640
+ };
591
641
  /**
592
642
  * Generic HTTP helper for use inside MCP tool handlers.
593
643
  *
@@ -663,12 +713,12 @@ var apiCall = async (method, url, options) => {
663
713
  body: hasBody ? JSON.stringify(options.body) : void 0
664
714
  });
665
715
  if (!response.ok) {
666
- const err = await response.json().catch(() => {
716
+ const errorBody = await response.json().catch(() => {
667
717
  return {
668
718
  error: response.statusText
669
719
  };
670
720
  });
671
- throw new Error(err.error || `HTTP ${response.status}`);
721
+ throw new Error(errorBodyMessage(errorBody) ?? `HTTP ${response.status}`);
672
722
  }
673
723
  if (response.status === 204 || response.status === 205) return;
674
724
  if (response.headers.get("content-type")?.includes("application/json")) return response.json();
@@ -860,6 +910,8 @@ exports.apiCall = apiCall;
860
910
  exports.checkScopes = checkScopes;
861
911
  exports.createGatedToolRegistrar = createGatedToolRegistrar;
862
912
  exports.createMcpRouter = createMcpRouter;
913
+ exports.errorBodyMessage = errorBodyMessage;
914
+ exports.getApiHeaders = getApiHeaders;
863
915
  exports.getIdentity = getIdentity;
864
916
  exports.registerAppResource = registerAppResource;
865
917
  exports.registerToolFromSchema = registerToolFromSchema;
package/dist/index.d.cts CHANGED
@@ -674,6 +674,15 @@ declare namespace Application {
674
674
  * Omitting the type parameter keeps the return type as `unknown | undefined`.
675
675
  */
676
676
  declare const getIdentity: <T = unknown>() => T | undefined;
677
+ /**
678
+ * Returns the headers `getApiHeaders` produced for the current MCP request —
679
+ * the same headers `apiCall` injects. Use it inside a tool handler that runs
680
+ * its own HTTP client instead of `apiCall`, so caller credentials still reach
681
+ * the upstream API.
682
+ *
683
+ * Returns `{}` outside a request, or when `getApiHeaders` is not configured.
684
+ */
685
+ declare const getApiHeaders: () => Record<string, string>;
677
686
  //#endregion
678
687
  //#region src/registerAppResource.d.ts
679
688
  /**
@@ -1191,6 +1200,27 @@ interface McpAuthOptions {
1191
1200
  */
1192
1201
  resourceIndicator?: string | string[];
1193
1202
  }
1203
+ /**
1204
+ * The message an error body carries, across the two shapes REST APIs answer
1205
+ * with: `{ error: 'text' }` and `{ error: { code?, message? } }`.
1206
+ *
1207
+ * The structured one is why this is read rather than taken: an object handed
1208
+ * to `new Error()` renders as `[object Object]`, and inside a tool handler
1209
+ * that string is the whole answer the calling model acts on.
1210
+ *
1211
+ * Exported so a tool handler with its own HTTP client renders error bodies
1212
+ * exactly as `apiCall` does. Returns `undefined` when the body carries no
1213
+ * message.
1214
+ *
1215
+ * @example
1216
+ * ```typescript
1217
+ * if (!response.ok) {
1218
+ * const body = await response.json().catch(() => undefined);
1219
+ * throw new Error(errorBodyMessage(body) ?? `HTTP ${response.status}`);
1220
+ * }
1221
+ * ```
1222
+ */
1223
+ declare const errorBodyMessage: (body: unknown) => string | undefined;
1194
1224
  /**
1195
1225
  * Options for a single `apiCall` request.
1196
1226
  */
@@ -1444,4 +1474,4 @@ interface McpRouterOptions {
1444
1474
  */
1445
1475
  declare const createMcpRouter: (server: McpServer$1, options?: McpRouterOptions) => Router<Application.DefaultState, Application.DefaultContext>;
1446
1476
  //#endregion
1447
- export { ApiCallOptions, CognitoUserPoolConfig, type CreateGatedToolRegistrarOptions, type GatedToolDef, type JsonObjectSchema, McpAuthOptions, McpRouterOptions, McpServer, type RegisterAppResourceParams, type RegisterToolFromSchemaParams, type RegisteredAppResource, type ToolCallContext, type ToolIdentity, type ToolMetaParams, UI_EXTENSION_ID, UI_RESOURCE_MIME_TYPE, type UiResourceCsp, type UiResourceMeta, type UiResourcePermissions, type UiToolVisibility, apiCall, checkScopes, createGatedToolRegistrar, createMcpRouter, getIdentity, registerAppResource, registerToolFromSchema, z };
1477
+ export { ApiCallOptions, CognitoUserPoolConfig, type CreateGatedToolRegistrarOptions, type GatedToolDef, type JsonObjectSchema, McpAuthOptions, McpRouterOptions, McpServer, type RegisterAppResourceParams, type RegisterToolFromSchemaParams, type RegisteredAppResource, type ToolCallContext, type ToolIdentity, type ToolMetaParams, UI_EXTENSION_ID, UI_RESOURCE_MIME_TYPE, type UiResourceCsp, type UiResourceMeta, type UiResourcePermissions, type UiToolVisibility, apiCall, checkScopes, createGatedToolRegistrar, createMcpRouter, errorBodyMessage, getApiHeaders, getIdentity, registerAppResource, registerToolFromSchema, z };
package/dist/index.d.mts CHANGED
@@ -675,6 +675,15 @@ declare namespace Application {
675
675
  * Omitting the type parameter keeps the return type as `unknown | undefined`.
676
676
  */
677
677
  declare const getIdentity: <T = unknown>() => T | undefined;
678
+ /**
679
+ * Returns the headers `getApiHeaders` produced for the current MCP request —
680
+ * the same headers `apiCall` injects. Use it inside a tool handler that runs
681
+ * its own HTTP client instead of `apiCall`, so caller credentials still reach
682
+ * the upstream API.
683
+ *
684
+ * Returns `{}` outside a request, or when `getApiHeaders` is not configured.
685
+ */
686
+ declare const getApiHeaders: () => Record<string, string>;
678
687
  //#endregion
679
688
  //#region src/registerAppResource.d.ts
680
689
  /**
@@ -1192,6 +1201,27 @@ interface McpAuthOptions {
1192
1201
  */
1193
1202
  resourceIndicator?: string | string[];
1194
1203
  }
1204
+ /**
1205
+ * The message an error body carries, across the two shapes REST APIs answer
1206
+ * with: `{ error: 'text' }` and `{ error: { code?, message? } }`.
1207
+ *
1208
+ * The structured one is why this is read rather than taken: an object handed
1209
+ * to `new Error()` renders as `[object Object]`, and inside a tool handler
1210
+ * that string is the whole answer the calling model acts on.
1211
+ *
1212
+ * Exported so a tool handler with its own HTTP client renders error bodies
1213
+ * exactly as `apiCall` does. Returns `undefined` when the body carries no
1214
+ * message.
1215
+ *
1216
+ * @example
1217
+ * ```typescript
1218
+ * if (!response.ok) {
1219
+ * const body = await response.json().catch(() => undefined);
1220
+ * throw new Error(errorBodyMessage(body) ?? `HTTP ${response.status}`);
1221
+ * }
1222
+ * ```
1223
+ */
1224
+ declare const errorBodyMessage: (body: unknown) => string | undefined;
1195
1225
  /**
1196
1226
  * Options for a single `apiCall` request.
1197
1227
  */
@@ -1445,4 +1475,4 @@ interface McpRouterOptions {
1445
1475
  */
1446
1476
  declare const createMcpRouter: (server: McpServer$1, options?: McpRouterOptions) => Router<Application.DefaultState, Application.DefaultContext>;
1447
1477
  //#endregion
1448
- export { ApiCallOptions, CognitoUserPoolConfig, type CreateGatedToolRegistrarOptions, type GatedToolDef, type JsonObjectSchema, McpAuthOptions, McpRouterOptions, McpServer, type RegisterAppResourceParams, type RegisterToolFromSchemaParams, type RegisteredAppResource, type ToolCallContext, type ToolIdentity, type ToolMetaParams, UI_EXTENSION_ID, UI_RESOURCE_MIME_TYPE, type UiResourceCsp, type UiResourceMeta, type UiResourcePermissions, type UiToolVisibility, apiCall, checkScopes, createGatedToolRegistrar, createMcpRouter, getIdentity, registerAppResource, registerToolFromSchema, z };
1478
+ export { ApiCallOptions, CognitoUserPoolConfig, type CreateGatedToolRegistrarOptions, type GatedToolDef, type JsonObjectSchema, McpAuthOptions, McpRouterOptions, McpServer, type RegisterAppResourceParams, type RegisterToolFromSchemaParams, type RegisteredAppResource, type ToolCallContext, type ToolIdentity, type ToolMetaParams, UI_EXTENSION_ID, UI_RESOURCE_MIME_TYPE, type UiResourceCsp, type UiResourceMeta, type UiResourcePermissions, type UiToolVisibility, apiCall, checkScopes, createGatedToolRegistrar, createMcpRouter, errorBodyMessage, getApiHeaders, getIdentity, registerAppResource, registerToolFromSchema, z };
package/dist/index.mjs CHANGED
@@ -21,6 +21,19 @@ var requestContextStore = new AsyncLocalStorage();
21
21
  var getIdentity = () => {
22
22
  return requestContextStore.getStore()?.identity;
23
23
  };
24
+ /**
25
+ * Returns the headers `getApiHeaders` produced for the current MCP request —
26
+ * the same headers `apiCall` injects. Use it inside a tool handler that runs
27
+ * its own HTTP client instead of `apiCall`, so caller credentials still reach
28
+ * the upstream API.
29
+ *
30
+ * Returns `{}` outside a request, or when `getApiHeaders` is not configured.
31
+ */
32
+ var getApiHeaders = () => {
33
+ return {
34
+ ...(requestContextStore.getStore()?.apiHeaders ?? {})
35
+ };
36
+ };
24
37
 
25
38
  //#endregion
26
39
  //#region src/serveMcpRequest.ts
@@ -585,6 +598,43 @@ var buildVerifyToken = auth => {
585
598
  return payload;
586
599
  };
587
600
  };
601
+ var isRecord = value => {
602
+ return typeof value === "object" && value !== null && !Array.isArray(value);
603
+ };
604
+ var readText = value => {
605
+ return typeof value === "string" && value !== "" ? value : void 0;
606
+ };
607
+ /**
608
+ * The message an error body carries, across the two shapes REST APIs answer
609
+ * with: `{ error: 'text' }` and `{ error: { code?, message? } }`.
610
+ *
611
+ * The structured one is why this is read rather than taken: an object handed
612
+ * to `new Error()` renders as `[object Object]`, and inside a tool handler
613
+ * that string is the whole answer the calling model acts on.
614
+ *
615
+ * Exported so a tool handler with its own HTTP client renders error bodies
616
+ * exactly as `apiCall` does. Returns `undefined` when the body carries no
617
+ * message.
618
+ *
619
+ * @example
620
+ * ```typescript
621
+ * if (!response.ok) {
622
+ * const body = await response.json().catch(() => undefined);
623
+ * throw new Error(errorBodyMessage(body) ?? `HTTP ${response.status}`);
624
+ * }
625
+ * ```
626
+ */
627
+ var errorBodyMessage = body => {
628
+ if (!isRecord(body)) return;
629
+ const {
630
+ error
631
+ } = body;
632
+ if (!isRecord(error)) return readText(error);
633
+ const code = readText(error.code);
634
+ const message = readText(error.message);
635
+ if (message === void 0) return code;
636
+ return code === void 0 ? message : `${code}: ${message}`;
637
+ };
588
638
  /**
589
639
  * Generic HTTP helper for use inside MCP tool handlers.
590
640
  *
@@ -660,12 +710,12 @@ var apiCall = async (method, url, options) => {
660
710
  body: hasBody ? JSON.stringify(options.body) : void 0
661
711
  });
662
712
  if (!response.ok) {
663
- const err = await response.json().catch(() => {
713
+ const errorBody = await response.json().catch(() => {
664
714
  return {
665
715
  error: response.statusText
666
716
  };
667
717
  });
668
- throw new Error(err.error || `HTTP ${response.status}`);
718
+ throw new Error(errorBodyMessage(errorBody) ?? `HTTP ${response.status}`);
669
719
  }
670
720
  if (response.status === 204 || response.status === 205) return;
671
721
  if (response.headers.get("content-type")?.includes("application/json")) return response.json();
@@ -845,4 +895,4 @@ var createMcpRouter = (server, options = {}) => {
845
895
  };
846
896
 
847
897
  //#endregion
848
- export { McpServer, UI_EXTENSION_ID, UI_RESOURCE_MIME_TYPE, apiCall, checkScopes, createGatedToolRegistrar, createMcpRouter, getIdentity, registerAppResource, registerToolFromSchema, z };
898
+ export { McpServer, UI_EXTENSION_ID, UI_RESOURCE_MIME_TYPE, apiCall, checkScopes, createGatedToolRegistrar, createMcpRouter, errorBodyMessage, getApiHeaders, getIdentity, registerAppResource, registerToolFromSchema, z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttoss/http-server-mcp",
3
- "version": "0.28.1",
3
+ "version": "0.29.0",
4
4
  "description": "Model Context Protocol (MCP) server integration for @ttoss/http-server",
5
5
  "keywords": [
6
6
  "ai",