@ttoss/http-server-mcp 0.29.0 → 0.30.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
@@ -503,6 +503,25 @@ register({
503
503
 
504
504
  **`buildContext`** injects request-scoped values (DB clients, tenant IDs) into every handler call without threading them through each individual tool. It receives the full `ToolCallContext` so context can vary by identity or by call args.
505
505
 
506
+ ### Localized refusals and errors
507
+
508
+ Pass `i18n` to render a [`LocalizedError`](https://ttoss.dev/docs/modules/packages/i18n-core/) thrown by a **gate** or by the **handler** as an `isError` result `{ "error": "<message>", "code": "<CODE>" }` in the caller's locale. Gates run outside the handler, so the registrar is the one place that covers both.
509
+
510
+ ```ts
511
+ const { register } = createGatedToolRegistrar({
512
+ server,
513
+ gates: [subscriptionGate],
514
+ i18n: {
515
+ catalog,
516
+ // Default: the MCP request's Accept-Language (`getRequestLocale()`).
517
+ // Pin the agent contract to one language instead:
518
+ getLocale: () => 'en',
519
+ },
520
+ });
521
+ ```
522
+
523
+ `onError` still runs for a handler error before it is rendered. Errors that are not localized behave exactly as without the option.
524
+
506
525
  ## MCP Apps (interactive UIs)
507
526
 
508
527
  The [`io.modelcontextprotocol/ui`](https://github.com/modelcontextprotocol/ext-apps) extension lets a tool result render as an interactive view instead of text. The view is a `ui://` resource holding an HTML document, which the host loads into a sandboxed iframe and talks to over `postMessage`; the tool points at it through its `_meta`.
package/dist/index.cjs CHANGED
@@ -37,6 +37,14 @@ var getApiHeaders = () => {
37
37
  ...(requestContextStore.getStore()?.apiHeaders ?? {})
38
38
  };
39
39
  };
40
+ /**
41
+ * The `Accept-Language` header of the current MCP request, or `undefined`
42
+ * outside a request or when the client sent none. The default locale source
43
+ * for `createGatedToolRegistrar`'s `i18n` option.
44
+ */
45
+ var getRequestLocale = () => {
46
+ return requestContextStore.getStore()?.acceptLanguage;
47
+ };
40
48
 
41
49
  //#endregion
42
50
  //#region src/serveMcpRequest.ts
@@ -431,18 +439,44 @@ var registerToolFromSchema = (server, params) => {
431
439
 
432
440
  //#endregion
433
441
  //#region src/createGatedToolRegistrar.ts
434
- var toolError = message => {
442
+ var toolError = (message, code) => {
435
443
  return {
436
444
  content: [{
437
445
  type: "text",
438
- text: JSON.stringify({
446
+ text: JSON.stringify(code === void 0 ? {
439
447
  error: message
448
+ } : {
449
+ error: message,
450
+ code
440
451
  })
441
452
  }],
442
453
  isError: true
443
454
  };
444
455
  };
445
456
  /**
457
+ * The localized `isError` result for `error`, or `undefined` when it is not
458
+ * a `LocalizedError` or rendering fails — the caller then keeps its usual path.
459
+ */
460
+ var localizedToolError = async ({
461
+ error,
462
+ ctx,
463
+ i18n
464
+ }) => {
465
+ try {
466
+ const requested = i18n.getLocale ? await i18n.getLocale(ctx) : getRequestLocale();
467
+ const {
468
+ renderLocalizedError
469
+ } = await import("@ttoss/i18n-core");
470
+ const rendered = renderLocalizedError({
471
+ error,
472
+ i18n: await i18n.catalog.getI18n(requested)
473
+ });
474
+ return rendered ? toolError(rendered.message, rendered.code) : void 0;
475
+ } catch {
476
+ return;
477
+ }
478
+ };
479
+ /**
446
480
  * Factory that returns a `register` helper for MCP tools that require
447
481
  * authentication and a specific OAuth scope.
448
482
  *
@@ -461,6 +495,8 @@ var toolError = message => {
461
495
  * 4. Merges `buildContext` output into the handler args.
462
496
  * 5. Wraps `null`/`undefined` results in a configurable "Not found" error result.
463
497
  * 6. Calls `onError` on handler throw before rethrowing.
498
+ * 7. With `i18n`, renders a `LocalizedError` from a gate or the handler as an
499
+ * `isError` result in the caller's locale instead of rethrowing it.
464
500
  *
465
501
  * @example
466
502
  * ```typescript
@@ -502,8 +538,37 @@ var createGatedToolRegistrar = ({
502
538
  enforceScope = true,
503
539
  onError,
504
540
  buildContext,
505
- notFoundMessage = "Not found"
541
+ notFoundMessage = "Not found",
542
+ i18n
506
543
  }) => {
544
+ const renderOrRethrow = async (error, ctx) => {
545
+ const localized = i18n ? await localizedToolError({
546
+ error,
547
+ ctx,
548
+ i18n
549
+ }) : void 0;
550
+ if (localized) return localized;
551
+ throw error;
552
+ };
553
+ const runMethod = async (def, ctx) => {
554
+ const extra = buildContext ? buildContext(ctx) : {};
555
+ try {
556
+ const result = await def.method({
557
+ ...ctx.args,
558
+ ...extra
559
+ });
560
+ if (result == null) return toolError(notFoundMessage);
561
+ return {
562
+ content: [{
563
+ type: "text",
564
+ text: JSON.stringify(result)
565
+ }]
566
+ };
567
+ } catch (error) {
568
+ if (onError) await onError(error, ctx);
569
+ return renderOrRethrow(error, ctx);
570
+ }
571
+ };
507
572
  const register = def => {
508
573
  const handler = async args => {
509
574
  const identity = resolveIdentity();
@@ -517,24 +582,12 @@ var createGatedToolRegistrar = ({
517
582
  const scopeError = checkScope(identity, def.requiredScope);
518
583
  if (scopeError) return scopeError;
519
584
  }
520
- await runGates([...gates, ...(def.gates ?? [])], ctx);
521
- const extra = buildContext ? buildContext(ctx) : {};
522
585
  try {
523
- const result = await def.method({
524
- ...args,
525
- ...extra
526
- });
527
- if (result == null) return toolError(notFoundMessage);
528
- return {
529
- content: [{
530
- type: "text",
531
- text: JSON.stringify(result)
532
- }]
533
- };
586
+ await runGates([...gates, ...(def.gates ?? [])], ctx);
534
587
  } catch (error) {
535
- if (onError) await onError(error, ctx);
536
- throw error;
588
+ return renderOrRethrow(error, ctx);
537
589
  }
590
+ return runMethod(def, ctx);
538
591
  };
539
592
  server.registerTool(def.name, {
540
593
  description: def.description,
@@ -856,10 +909,12 @@ var createMcpRouter = (server, options = {}) => {
856
909
  await serveRequest(ctx, body);
857
910
  ctx.respond = false;
858
911
  };
859
- if (needsContext) await requestContextStore.run({
912
+ const acceptLanguage = ctx.get("Accept-Language") || void 0;
913
+ if (needsContext || acceptLanguage) await requestContextStore.run({
860
914
  apiBaseUrl,
861
915
  apiHeaders,
862
- identity
916
+ identity,
917
+ acceptLanguage
863
918
  }, runRequest);else await runRequest();
864
919
  };
865
920
  const postHandler = async ctx => {
@@ -913,6 +968,7 @@ exports.createMcpRouter = createMcpRouter;
913
968
  exports.errorBodyMessage = errorBodyMessage;
914
969
  exports.getApiHeaders = getApiHeaders;
915
970
  exports.getIdentity = getIdentity;
971
+ exports.getRequestLocale = getRequestLocale;
916
972
  exports.registerAppResource = registerAppResource;
917
973
  exports.registerToolFromSchema = registerToolFromSchema;
918
974
  Object.defineProperty(exports, 'z', {
package/dist/index.d.cts CHANGED
@@ -16,6 +16,7 @@ import compose from "koa-compose";
16
16
  import { ListenOptions, Socket } from "net";
17
17
  import { ParsedUrlQuery } from "querystring";
18
18
  import * as url from "url";
19
+ import { Catalog } from "@ttoss/i18n-core";
19
20
  import { z } from "zod";
20
21
 
21
22
  //#region ../../node_modules/.pnpm/@types+koa@3.0.3/node_modules/@types/koa/index.d.ts
@@ -683,6 +684,12 @@ declare const getIdentity: <T = unknown>() => T | undefined;
683
684
  * Returns `{}` outside a request, or when `getApiHeaders` is not configured.
684
685
  */
685
686
  declare const getApiHeaders: () => Record<string, string>;
687
+ /**
688
+ * The `Accept-Language` header of the current MCP request, or `undefined`
689
+ * outside a request or when the client sent none. The default locale source
690
+ * for `createGatedToolRegistrar`'s `i18n` option.
691
+ */
692
+ declare const getRequestLocale: () => string | undefined;
686
693
  //#endregion
687
694
  //#region src/registerAppResource.d.ts
688
695
  /**
@@ -1062,6 +1069,22 @@ type CreateGatedToolRegistrarOptions = {
1062
1069
  * `null` or `undefined`. Defaults to `"Not found"`.
1063
1070
  */
1064
1071
  notFoundMessage?: string;
1072
+ /**
1073
+ * Renders a `LocalizedError` — thrown by a gate or by the handler — as an
1074
+ * `isError` result `{ error: <message>, code }` in the caller's locale,
1075
+ * instead of letting the SDK surface its source-language message. Gates run
1076
+ * outside the handler, so this is the one place both are covered.
1077
+ *
1078
+ * `getLocale` defaults to the MCP request's `Accept-Language`
1079
+ * (`getRequestLocale()`); return a fixed locale to pin the agent contract to
1080
+ * one language. `onError` still runs for a handler error before it is
1081
+ * rendered. Errors that are not localized behave exactly as without this
1082
+ * option.
1083
+ */
1084
+ i18n?: {
1085
+ catalog: Pick<Catalog, 'getI18n'>;
1086
+ getLocale?: (ctx: ToolCallContext) => string | string[] | null | undefined | Promise<string | string[] | null | undefined>;
1087
+ };
1065
1088
  };
1066
1089
  declare const createGatedToolRegistrar: ({
1067
1090
  server,
@@ -1070,7 +1093,8 @@ declare const createGatedToolRegistrar: ({
1070
1093
  enforceScope,
1071
1094
  onError,
1072
1095
  buildContext,
1073
- notFoundMessage
1096
+ notFoundMessage,
1097
+ i18n
1074
1098
  }: CreateGatedToolRegistrarOptions) => {
1075
1099
  register: (def: GatedToolDef) => void;
1076
1100
  };
@@ -1474,4 +1498,4 @@ interface McpRouterOptions {
1474
1498
  */
1475
1499
  declare const createMcpRouter: (server: McpServer$1, options?: McpRouterOptions) => Router<Application.DefaultState, Application.DefaultContext>;
1476
1500
  //#endregion
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 };
1501
+ 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, getRequestLocale, registerAppResource, registerToolFromSchema, z };
package/dist/index.d.mts CHANGED
@@ -18,6 +18,7 @@ import compose from "koa-compose";
18
18
  import { ListenOptions, Socket } from "net";
19
19
  import { ParsedUrlQuery } from "querystring";
20
20
  import * as url from "url";
21
+ import { Catalog } from "@ttoss/i18n-core";
21
22
 
22
23
  //#region ../../node_modules/.pnpm/@types+koa@3.0.3/node_modules/@types/koa/index.d.ts
23
24
  declare interface ContextDelegatedRequest {
@@ -684,6 +685,12 @@ declare const getIdentity: <T = unknown>() => T | undefined;
684
685
  * Returns `{}` outside a request, or when `getApiHeaders` is not configured.
685
686
  */
686
687
  declare const getApiHeaders: () => Record<string, string>;
688
+ /**
689
+ * The `Accept-Language` header of the current MCP request, or `undefined`
690
+ * outside a request or when the client sent none. The default locale source
691
+ * for `createGatedToolRegistrar`'s `i18n` option.
692
+ */
693
+ declare const getRequestLocale: () => string | undefined;
687
694
  //#endregion
688
695
  //#region src/registerAppResource.d.ts
689
696
  /**
@@ -1063,6 +1070,22 @@ type CreateGatedToolRegistrarOptions = {
1063
1070
  * `null` or `undefined`. Defaults to `"Not found"`.
1064
1071
  */
1065
1072
  notFoundMessage?: string;
1073
+ /**
1074
+ * Renders a `LocalizedError` — thrown by a gate or by the handler — as an
1075
+ * `isError` result `{ error: <message>, code }` in the caller's locale,
1076
+ * instead of letting the SDK surface its source-language message. Gates run
1077
+ * outside the handler, so this is the one place both are covered.
1078
+ *
1079
+ * `getLocale` defaults to the MCP request's `Accept-Language`
1080
+ * (`getRequestLocale()`); return a fixed locale to pin the agent contract to
1081
+ * one language. `onError` still runs for a handler error before it is
1082
+ * rendered. Errors that are not localized behave exactly as without this
1083
+ * option.
1084
+ */
1085
+ i18n?: {
1086
+ catalog: Pick<Catalog, 'getI18n'>;
1087
+ getLocale?: (ctx: ToolCallContext) => string | string[] | null | undefined | Promise<string | string[] | null | undefined>;
1088
+ };
1066
1089
  };
1067
1090
  declare const createGatedToolRegistrar: ({
1068
1091
  server,
@@ -1071,7 +1094,8 @@ declare const createGatedToolRegistrar: ({
1071
1094
  enforceScope,
1072
1095
  onError,
1073
1096
  buildContext,
1074
- notFoundMessage
1097
+ notFoundMessage,
1098
+ i18n
1075
1099
  }: CreateGatedToolRegistrarOptions) => {
1076
1100
  register: (def: GatedToolDef) => void;
1077
1101
  };
@@ -1475,4 +1499,4 @@ interface McpRouterOptions {
1475
1499
  */
1476
1500
  declare const createMcpRouter: (server: McpServer$1, options?: McpRouterOptions) => Router<Application.DefaultState, Application.DefaultContext>;
1477
1501
  //#endregion
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 };
1502
+ 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, getRequestLocale, registerAppResource, registerToolFromSchema, z };
package/dist/index.mjs CHANGED
@@ -34,6 +34,14 @@ var getApiHeaders = () => {
34
34
  ...(requestContextStore.getStore()?.apiHeaders ?? {})
35
35
  };
36
36
  };
37
+ /**
38
+ * The `Accept-Language` header of the current MCP request, or `undefined`
39
+ * outside a request or when the client sent none. The default locale source
40
+ * for `createGatedToolRegistrar`'s `i18n` option.
41
+ */
42
+ var getRequestLocale = () => {
43
+ return requestContextStore.getStore()?.acceptLanguage;
44
+ };
37
45
 
38
46
  //#endregion
39
47
  //#region src/serveMcpRequest.ts
@@ -428,18 +436,44 @@ var registerToolFromSchema = (server, params) => {
428
436
 
429
437
  //#endregion
430
438
  //#region src/createGatedToolRegistrar.ts
431
- var toolError = message => {
439
+ var toolError = (message, code) => {
432
440
  return {
433
441
  content: [{
434
442
  type: "text",
435
- text: JSON.stringify({
443
+ text: JSON.stringify(code === void 0 ? {
436
444
  error: message
445
+ } : {
446
+ error: message,
447
+ code
437
448
  })
438
449
  }],
439
450
  isError: true
440
451
  };
441
452
  };
442
453
  /**
454
+ * The localized `isError` result for `error`, or `undefined` when it is not
455
+ * a `LocalizedError` or rendering fails — the caller then keeps its usual path.
456
+ */
457
+ var localizedToolError = async ({
458
+ error,
459
+ ctx,
460
+ i18n
461
+ }) => {
462
+ try {
463
+ const requested = i18n.getLocale ? await i18n.getLocale(ctx) : getRequestLocale();
464
+ const {
465
+ renderLocalizedError
466
+ } = await import("@ttoss/i18n-core");
467
+ const rendered = renderLocalizedError({
468
+ error,
469
+ i18n: await i18n.catalog.getI18n(requested)
470
+ });
471
+ return rendered ? toolError(rendered.message, rendered.code) : void 0;
472
+ } catch {
473
+ return;
474
+ }
475
+ };
476
+ /**
443
477
  * Factory that returns a `register` helper for MCP tools that require
444
478
  * authentication and a specific OAuth scope.
445
479
  *
@@ -458,6 +492,8 @@ var toolError = message => {
458
492
  * 4. Merges `buildContext` output into the handler args.
459
493
  * 5. Wraps `null`/`undefined` results in a configurable "Not found" error result.
460
494
  * 6. Calls `onError` on handler throw before rethrowing.
495
+ * 7. With `i18n`, renders a `LocalizedError` from a gate or the handler as an
496
+ * `isError` result in the caller's locale instead of rethrowing it.
461
497
  *
462
498
  * @example
463
499
  * ```typescript
@@ -499,8 +535,37 @@ var createGatedToolRegistrar = ({
499
535
  enforceScope = true,
500
536
  onError,
501
537
  buildContext,
502
- notFoundMessage = "Not found"
538
+ notFoundMessage = "Not found",
539
+ i18n
503
540
  }) => {
541
+ const renderOrRethrow = async (error, ctx) => {
542
+ const localized = i18n ? await localizedToolError({
543
+ error,
544
+ ctx,
545
+ i18n
546
+ }) : void 0;
547
+ if (localized) return localized;
548
+ throw error;
549
+ };
550
+ const runMethod = async (def, ctx) => {
551
+ const extra = buildContext ? buildContext(ctx) : {};
552
+ try {
553
+ const result = await def.method({
554
+ ...ctx.args,
555
+ ...extra
556
+ });
557
+ if (result == null) return toolError(notFoundMessage);
558
+ return {
559
+ content: [{
560
+ type: "text",
561
+ text: JSON.stringify(result)
562
+ }]
563
+ };
564
+ } catch (error) {
565
+ if (onError) await onError(error, ctx);
566
+ return renderOrRethrow(error, ctx);
567
+ }
568
+ };
504
569
  const register = def => {
505
570
  const handler = async args => {
506
571
  const identity = resolveIdentity();
@@ -514,24 +579,12 @@ var createGatedToolRegistrar = ({
514
579
  const scopeError = checkScope(identity, def.requiredScope);
515
580
  if (scopeError) return scopeError;
516
581
  }
517
- await runGates([...gates, ...(def.gates ?? [])], ctx);
518
- const extra = buildContext ? buildContext(ctx) : {};
519
582
  try {
520
- const result = await def.method({
521
- ...args,
522
- ...extra
523
- });
524
- if (result == null) return toolError(notFoundMessage);
525
- return {
526
- content: [{
527
- type: "text",
528
- text: JSON.stringify(result)
529
- }]
530
- };
583
+ await runGates([...gates, ...(def.gates ?? [])], ctx);
531
584
  } catch (error) {
532
- if (onError) await onError(error, ctx);
533
- throw error;
585
+ return renderOrRethrow(error, ctx);
534
586
  }
587
+ return runMethod(def, ctx);
535
588
  };
536
589
  server.registerTool(def.name, {
537
590
  description: def.description,
@@ -853,10 +906,12 @@ var createMcpRouter = (server, options = {}) => {
853
906
  await serveRequest(ctx, body);
854
907
  ctx.respond = false;
855
908
  };
856
- if (needsContext) await requestContextStore.run({
909
+ const acceptLanguage = ctx.get("Accept-Language") || void 0;
910
+ if (needsContext || acceptLanguage) await requestContextStore.run({
857
911
  apiBaseUrl,
858
912
  apiHeaders,
859
- identity
913
+ identity,
914
+ acceptLanguage
860
915
  }, runRequest);else await runRequest();
861
916
  };
862
917
  const postHandler = async ctx => {
@@ -895,4 +950,4 @@ var createMcpRouter = (server, options = {}) => {
895
950
  };
896
951
 
897
952
  //#endregion
898
- export { McpServer, UI_EXTENSION_ID, UI_RESOURCE_MIME_TYPE, apiCall, checkScopes, createGatedToolRegistrar, createMcpRouter, errorBodyMessage, getApiHeaders, getIdentity, registerAppResource, registerToolFromSchema, z };
953
+ export { McpServer, UI_EXTENSION_ID, UI_RESOURCE_MIME_TYPE, apiCall, checkScopes, createGatedToolRegistrar, createMcpRouter, errorBodyMessage, getApiHeaders, getIdentity, getRequestLocale, registerAppResource, registerToolFromSchema, z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttoss/http-server-mcp",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "description": "Model Context Protocol (MCP) server integration for @ttoss/http-server",
5
5
  "keywords": [
6
6
  "ai",
@@ -37,9 +37,10 @@
37
37
  "@modelcontextprotocol/node": "^2.0.0",
38
38
  "@modelcontextprotocol/server": "^2.0.0",
39
39
  "zod": "^4.4.3",
40
- "@ttoss/auth-core": "^0.14.1",
41
- "@ttoss/http-server": "^0.8.1",
42
- "@ttoss/http-server-auth": "^0.6.4"
40
+ "@ttoss/auth-core": "^0.15.0",
41
+ "@ttoss/http-server": "^0.10.0",
42
+ "@ttoss/http-server-auth": "^0.7.1",
43
+ "@ttoss/i18n-core": "^0.2.0"
43
44
  },
44
45
  "devDependencies": {
45
46
  "@modelcontextprotocol/client": "^2.0.0",
@@ -47,7 +48,7 @@
47
48
  "jest": "^30.4.2",
48
49
  "supertest": "^7.2.2",
49
50
  "tsdown": "^0.22.2",
50
- "@ttoss/config": "^1.38.0"
51
+ "@ttoss/config": "^1.39.0"
51
52
  },
52
53
  "publishConfig": {
53
54
  "access": "public",