@firfi/huly-mcp 0.30.1 → 0.31.1

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 (3) hide show
  1. package/README.md +6 -2
  2. package/dist/index.cjs +644 -66
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -269,7 +269,7 @@ The roadmap is driven by SDK parity and the project principle that this server s
269
269
 
270
270
  Highest-value additions for coding agents:
271
271
 
272
- - Generic space follow-ups: role assignment mutations, role/permission definition writes, generic space creation, and module-specific wrappers above the shared space foundation.
272
+ - Generic space follow-ups: role/permission definition writes, generic space creation, and module-specific wrappers above the shared space foundation. Generic space metadata, member/owner mutations, and typed-space role member mutations are covered by the shared spaces tools.
273
273
  - SDK discovery phase 2: space types, roles, permissions, plugin configuration, sequence metadata, and richer tool hints.
274
274
  - Drive follow-ups: drive create/update/delete, item move/rename/delete, adding new versions to existing files, permissions, and comments/activity.
275
275
  - Planner/ToDos: personal and project ToDo CRUD, scheduling, complete/reopen, priority, privacy/visibility, and document action items.
@@ -298,7 +298,7 @@ Planned feature surfaces:
298
298
  - Chat and communication: direct-message send/update/delete, group DMs, channel member mutations, join/leave/request access, archive/unarchive, star/favorite channels, close/reopen conversations, pinned messages, message attachments, translation, applets, in-message polls, and guest communication settings.
299
299
  - Notifications and activity: browser/push subscription internals, provider defaults, UI presenter/viewlet metadata, and activity control/extension metadata.
300
300
  - Attachments and media: previews/preview metadata and friendly wrappers for additional object types beyond issue/document.
301
- - Core schema and workspace administration: attribute/property create/update/delete/hide, enum CRUD/options, sequence management, role assignment mutations, role/permission definition writes, generic space creation, global space admins, integrations registry, invite settings, role capability settings, and workspace setting metadata.
301
+ - Core schema and workspace administration: attribute/property create/update/delete/hide, enum CRUD/options, sequence management, role/permission definition writes, generic space creation, global space admins, integrations registry, invite settings, role capability settings, and workspace setting metadata.
302
302
  - Integrations: GitHub repository/project mappings and sync metadata (deferred), Google Calendar connect/configure/sync controls, Bitrix entity/field mappings and sync status, Gmail/email channel messages, Telegram messages, Huly Mail/Mail plugin behavior, AI assistant integration state, and AI bot configuration if server-side APIs expose stable behavior.
303
303
  - Templates, rating, support, billing, analytics, views, workbench, and preferences: message templates/categories/fields, document/person rating data blocked by unpublished `@hcengineering/rating` SDK package (#90), support conversations, billing tier/status discovery, onboarding channels, saved filtered views, user view preferences, tabs/widgets/apps, and module preference discovery/update.
304
304
  - Document-specific gaps: snapshot restore, backlinks, notes, structured action items/tables, PDF/export, advanced document relationships, and document printing/export once SDK support is safe.
@@ -489,6 +489,7 @@ SDK upgrade revisit:
489
489
  | `update_channel` | Update fields on an existing Huly channel. Only provided fields are modified. |
490
490
  | `delete_channel` | Permanently delete a Huly channel. This action cannot be undone. For reversible channel lifecycle changes, use archive_channel and unarchive_channel instead. |
491
491
  | `list_channel_messages` | List messages in a Huly channel. Returns messages sorted by date (newest first). |
492
+ | `list_external_channel_messages` | List read-only messages for an external Gmail or Telegram channel by channel name or ID. The limit defaults to 50 and is capped at 200. When this build does not include a compatible Huly external-message SDK/model for the requested provider, returns supported=false, an unsupportedReason, and an empty messages array; it never sends, replies, deletes, mutates, or returns fake messages. |
492
493
  | `send_channel_message` | Send a message to a Huly channel. Message body supports markdown formatting. |
493
494
  | `update_channel_message` | Update a channel message. Only the body can be modified. |
494
495
  | `delete_channel_message` | Permanently delete a channel message. This action cannot be undone. |
@@ -739,6 +740,9 @@ SDK upgrade revisit:
739
740
  | `add_space_members` | Idempotently add members to an existing Huly space. Members accept account UUID, exact email, or exact person display name and resolve to Huly account UUIDs before replacing the full members array. |
740
741
  | `remove_space_members` | Idempotently remove members from an existing Huly space. Members accept account UUID, exact email, or exact person display name and resolve to Huly account UUIDs before replacing the full members array. |
741
742
  | `set_space_owners` | Replace owners on an existing Huly space. Owners accept account UUID, exact email, or exact person display name. By default, owners are also ensured in members. |
743
+ | `set_space_role_members` | Replace members assigned to one role on a typed Huly space while preserving all other role assignments. Role accepts a raw role _id or exact role name from the space's SpaceType. Members accept account UUID, exact email, or exact person display name; pass members=[] to clear this role. |
744
+ | `add_space_role_members` | Idempotently add members to one role on a typed Huly space while preserving all other role assignments. Role accepts a raw role _id or exact role name from the space's SpaceType. Members accept account UUID, exact email, or exact person display name. |
745
+ | `remove_space_role_members` | Idempotently remove members from one role on a typed Huly space while preserving all other role assignments. Role accepts a raw role _id or exact role name from the space's SpaceType. Members accept account UUID, exact email, or exact person display name. |
742
746
 
743
747
  ### Tag-Categories
744
748
 
package/dist/index.cjs CHANGED
@@ -154570,6 +154570,10 @@ var AmbiguousSpaceTypeMatchSchema = Schema_exports.Struct({
154570
154570
  name: NonEmptyString2,
154571
154571
  targetClass: ObjectClassName
154572
154572
  });
154573
+ var AmbiguousSpaceRoleMatchSchema = Schema_exports.Struct({
154574
+ id: RoleId,
154575
+ name: NonEmptyString2
154576
+ });
154573
154577
  var SpaceNotFoundError = class extends Schema_exports.TaggedError()(
154574
154578
  "SpaceNotFoundError",
154575
154579
  {
@@ -154614,6 +154618,54 @@ var SpaceTypeIdentifierAmbiguousError = class extends Schema_exports.TaggedError
154614
154618
  return `Space type '${this.identifier}' is ambiguous; use a space type id. Matches: ${details}`;
154615
154619
  }
154616
154620
  };
154621
+ var SpaceNotTypedError = class extends Schema_exports.TaggedError()(
154622
+ "SpaceNotTypedError",
154623
+ {
154624
+ id: SpaceId,
154625
+ name: NonEmptyString2
154626
+ }
154627
+ ) {
154628
+ get message() {
154629
+ return `Space '${this.name}' (${this.id}) is not typed; role members can only be changed on spaces with a SpaceType`;
154630
+ }
154631
+ };
154632
+ var SpaceRoleNotFoundError = class extends Schema_exports.TaggedError()(
154633
+ "SpaceRoleNotFoundError",
154634
+ {
154635
+ identifier: NonEmptyString2,
154636
+ spaceType: SpaceTypeId
154637
+ }
154638
+ ) {
154639
+ get message() {
154640
+ return `Role '${this.identifier}' not found in space type '${this.spaceType}'`;
154641
+ }
154642
+ };
154643
+ var SpaceRoleIdentifierAmbiguousError = class extends Schema_exports.TaggedError()(
154644
+ "SpaceRoleIdentifierAmbiguousError",
154645
+ {
154646
+ identifier: NonEmptyString2,
154647
+ spaceType: SpaceTypeId,
154648
+ matches: Schema_exports.Array(AmbiguousSpaceRoleMatchSchema).pipe(Schema_exports.minItems(MIN_AMBIGUOUS_SPACE_MATCHES))
154649
+ }
154650
+ ) {
154651
+ get message() {
154652
+ const details = this.matches.map((match16) => `${match16.id} (${match16.name})`).join(", ");
154653
+ return `Role '${this.identifier}' is ambiguous in space type '${this.spaceType}'; use a role id. Matches: ${details}`;
154654
+ }
154655
+ };
154656
+ var SpaceRoleAssignmentsMalformedError = class extends Schema_exports.TaggedError()(
154657
+ "SpaceRoleAssignmentsMalformedError",
154658
+ {
154659
+ space: SpaceId,
154660
+ spaceType: SpaceTypeId,
154661
+ targetClass: ObjectClassName,
154662
+ reason: NonEmptyString2
154663
+ }
154664
+ ) {
154665
+ get message() {
154666
+ return `Role assignments for space '${this.space}' and space type '${this.spaceType}' are malformed at '${this.targetClass}': ${this.reason}. Refusing to write role members to avoid access-control data loss.`;
154667
+ }
154668
+ };
154617
154669
 
154618
154670
  // src/huly/errors-test-management.ts
154619
154671
  var TestProjectNotFoundError = class extends Schema_exports.TaggedError()(
@@ -154890,6 +154942,10 @@ var HulyDomainError = Schema_exports.Union(
154890
154942
  HulyClassNotFoundError,
154891
154943
  SpaceNotFoundError,
154892
154944
  SpaceIdentifierAmbiguousError,
154945
+ SpaceNotTypedError,
154946
+ SpaceRoleNotFoundError,
154947
+ SpaceRoleIdentifierAmbiguousError,
154948
+ SpaceRoleAssignmentsMalformedError,
154893
154949
  SpaceTypeNotFoundError,
154894
154950
  SpaceTypeIdentifierAmbiguousError,
154895
154951
  TodoNotFoundError,
@@ -155260,6 +155316,10 @@ var HulyClient = class _HulyClient extends Context_exports.Tag("@hulymcp/HulyCli
155260
155316
  (client2) => client2.findOne(_class, query, options),
155261
155317
  "findOne failed"
155262
155318
  ),
155319
+ findAllInModel: (_class, query, options) => withClient(
155320
+ (client2) => Promise.resolve(client2.getModel().findAllSync(_class, query, options)),
155321
+ "findAllInModel failed"
155322
+ ),
155263
155323
  createDoc: (_class, space, attributes, id) => withClient(
155264
155324
  (client2) => client2.createDoc(_class, space, attributes, id),
155265
155325
  "createDoc failed"
@@ -155341,6 +155401,7 @@ var HulyClient = class _HulyClient extends Context_exports.Tag("@hulymcp/HulyCli
155341
155401
  markupUrlConfig: testMarkupUrlConfig,
155342
155402
  workbenchUrlConfig: testWorkbenchUrlConfig,
155343
155403
  findAll: noopFindAll,
155404
+ findAllInModel: noopFindAll,
155344
155405
  findOne: noopFindOne,
155345
155406
  createDoc: notImplemented("createDoc"),
155346
155407
  updateDoc: notImplemented("updateDoc"),
@@ -155418,6 +155479,7 @@ var import_api_client3 = __toESM(require_lib20(), 1);
155418
155479
  // src/huly/operations/sdk-boundary.ts
155419
155480
  var toRef = (id) => id;
155420
155481
  var toClassRef = (id) => id;
155482
+ var toMixinRef = (id) => id;
155421
155483
  var toAccountUuid = (uuid5) => uuid5;
155422
155484
  var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
155423
155485
  var validatePersonUuid = (uuid5) => {
@@ -165740,11 +165802,14 @@ var McpErrorCode = {
165740
165802
  InternalError: -32603,
165741
165803
  ResourceNotFound: -32002
165742
165804
  };
165743
- var createErrorResponse = (text, errorCode, errorTag) => ({
165744
- content: [{ type: "text", text }],
165745
- isError: true,
165746
- _meta: { errorCode, errorTag }
165747
- });
165805
+ var createErrorResponse = (text, errorCode, errorTag, warnings = []) => {
165806
+ const warningContent = warnings.length > 0 ? [{ type: "text", text: encodeJsonText({ warnings }) }] : [];
165807
+ return {
165808
+ content: [{ type: "text", text }, ...warningContent],
165809
+ isError: true,
165810
+ _meta: { errorCode, errorTag }
165811
+ };
165812
+ };
165748
165813
  var INVALID_PARAMS_TAGS = /* @__PURE__ */ new Set([
165749
165814
  "IssueNotFoundError",
165750
165815
  "ProjectNotFoundError",
@@ -165835,6 +165900,9 @@ var INVALID_PARAMS_TAGS = /* @__PURE__ */ new Set([
165835
165900
  "GenericObjectNotFoundError",
165836
165901
  "SpaceNotFoundError",
165837
165902
  "SpaceIdentifierAmbiguousError",
165903
+ "SpaceNotTypedError",
165904
+ "SpaceRoleNotFoundError",
165905
+ "SpaceRoleIdentifierAmbiguousError",
165838
165906
  "SpaceTypeNotFoundError",
165839
165907
  "SpaceTypeIdentifierAmbiguousError",
165840
165908
  "DriveNotFoundError",
@@ -165857,13 +165925,13 @@ var INTERNAL_ERROR_PREFIX = {
165857
165925
  HulyConnectionError: "Connection error",
165858
165926
  HulyAuthError: "Authentication error"
165859
165927
  };
165860
- var mapDomainErrorToMcp = (error2) => {
165928
+ var mapDomainErrorToMcp = (error2, warnings = []) => {
165861
165929
  if (INVALID_PARAMS_TAGS.has(error2._tag)) {
165862
- return createErrorResponse(error2.message, McpErrorCode.InvalidParams);
165930
+ return createErrorResponse(error2.message, McpErrorCode.InvalidParams, void 0, warnings);
165863
165931
  }
165864
165932
  const prefix = INTERNAL_ERROR_PREFIX[error2._tag];
165865
165933
  const message = prefix !== void 0 ? `${prefix}: ${error2.message}` : error2.message;
165866
- return createErrorResponse(message, McpErrorCode.InternalError, error2._tag);
165934
+ return createErrorResponse(message, McpErrorCode.InternalError, error2._tag, warnings);
165867
165935
  };
165868
165936
  var formatParseError = (error2) => {
165869
165937
  const issues = ParseResult_exports.ArrayFormatter.formatErrorSync(error2);
@@ -165884,34 +165952,44 @@ var mapParseCauseToMcp = (cause3, toolName) => {
165884
165952
  }
165885
165953
  return createErrorResponse("An unexpected error occurred", McpErrorCode.InternalError);
165886
165954
  };
165887
- var mapDomainCauseToMcp = (cause3) => {
165955
+ var mapDomainCauseToMcp = (cause3, warnings = []) => {
165888
165956
  if (Cause_exports.isFailType(cause3)) {
165889
- return mapDomainErrorToMcp(cause3.error);
165957
+ return mapDomainErrorToMcp(cause3.error, warnings);
165890
165958
  }
165891
165959
  if (Cause_exports.isDieType(cause3)) {
165892
- return createErrorResponse("An unexpected error occurred", McpErrorCode.InternalError, "UnexpectedError");
165960
+ return createErrorResponse("An unexpected error occurred", McpErrorCode.InternalError, "UnexpectedError", warnings);
165893
165961
  }
165894
165962
  const failures3 = Chunk_exports.toArray(Cause_exports.failures(cause3));
165895
165963
  if (failures3.length > 0) {
165896
- return mapDomainErrorToMcp(failures3[0]);
165964
+ return mapDomainErrorToMcp(failures3[0], warnings);
165897
165965
  }
165898
- return createErrorResponse("An unexpected error occurred", McpErrorCode.InternalError);
165966
+ return createErrorResponse("An unexpected error occurred", McpErrorCode.InternalError, void 0, warnings);
165899
165967
  };
165900
165968
  var encodeJsonText = (value3) => {
165901
165969
  const text = JSON.stringify(value3);
165902
165970
  return typeof text === "string" ? text : "null";
165903
165971
  };
165904
- var createSuccessResponse = (result) => ({
165905
- content: [{ type: "text", text: encodeJsonText(result) }],
165906
- structuredContent: {
165972
+ var createSuccessResponse = (result, warnings = []) => ({
165973
+ content: [
165974
+ { type: "text", text: encodeJsonText(result) },
165975
+ ...warnings.length > 0 ? [{ type: "text", text: encodeJsonText({ warnings }) }] : []
165976
+ ],
165977
+ structuredContent: warnings.length > 0 ? {
165978
+ result,
165979
+ warnings
165980
+ } : {
165907
165981
  result
165908
165982
  }
165909
165983
  });
165910
165984
  var createUnknownToolError = (toolName) => createErrorResponse(`Unknown tool: ${toolName}`, McpErrorCode.InvalidParams, "UnknownTool");
165911
165985
  var createInvalidParamsError = (message, errorTag) => createErrorResponse(message, McpErrorCode.InvalidParams, errorTag);
165912
- var toMcpResponse = (response) => {
165913
- const { _meta: _, ...wire } = response;
165914
- return wire;
165986
+ var toMcpResponse = (response) => response.isError === true ? {
165987
+ content: response.content,
165988
+ isError: true
165989
+ } : {
165990
+ content: response.content,
165991
+ ...response.structuredContent === void 0 ? {} : { structuredContent: response.structuredContent },
165992
+ ...response.isError === void 0 ? {} : { isError: response.isError }
165915
165993
  };
165916
165994
 
165917
165995
  // src/mcp/http-2026-dispatcher.ts
@@ -166468,6 +166546,28 @@ var StdioServerTransport = class {
166468
166546
  }
166469
166547
  };
166470
166548
 
166549
+ // src/domain/schemas/tool-warnings.ts
166550
+ var ToolWarningCodeSchema = Schema_exports.Literal(
166551
+ "status_metadata_unresolved",
166552
+ "space_role_assignments_degraded"
166553
+ ).annotations({
166554
+ identifier: "ToolWarningCode",
166555
+ title: "ToolWarningCode",
166556
+ description: "Machine-readable code for an agent-visible MCP tool warning."
166557
+ });
166558
+ var StatusMetadataUnresolvedWarningCode = ToolWarningCodeSchema.literals[0];
166559
+ var SpaceRoleAssignmentsDegradedWarningCode = ToolWarningCodeSchema.literals[1];
166560
+ var ToolWarningSchema = Schema_exports.Struct({
166561
+ code: ToolWarningCodeSchema,
166562
+ message: Schema_exports.Trim.pipe(Schema_exports.nonEmptyString()).annotations({
166563
+ description: "LLM-facing explanation of what part of the returned tool payload is degraded and how the agent should interpret it."
166564
+ })
166565
+ }).annotations({
166566
+ identifier: "ToolWarning",
166567
+ title: "ToolWarning",
166568
+ description: "Warning surfaced to an agent when a tool result is intentionally degraded instead of failing."
166569
+ });
166570
+
166471
166571
  // src/domain/schemas/recurrence-primitives.ts
166472
166572
  var MAX_ZERO_BASED_MONTH_INDEX = 11;
166473
166573
  var MAX_MONTH_DAY = 31;
@@ -166518,9 +166618,13 @@ var clearableText = (description) => Schema_exports.NullOr(Schema_exports.String
166518
166618
  var limitDescription = (subject) => `Maximum ${subject} to return (default: ${DEFAULT_LIMIT}).`;
166519
166619
  var SpacePermissionScopeSchema = Schema_exports.Literal("space", "workspace");
166520
166620
  var SpaceMemberIdentifier = NonEmptyString2.pipe(Schema_exports.brand("SpaceMemberIdentifier"));
166621
+ var SpaceRoleIdentifier = NonEmptyString2.pipe(Schema_exports.brand("SpaceRoleIdentifier"));
166521
166622
  var SpaceMemberIdentifierSchema = SpaceMemberIdentifier.annotations({
166522
166623
  description: "Workspace member to resolve. Accepts a Huly account UUID directly, an exact email address, or an exact person display name."
166523
166624
  });
166625
+ var SpaceRoleIdentifierSchema = SpaceRoleIdentifier.annotations({
166626
+ description: "Role to resolve within the space's SpaceType. Accepts a raw Huly role _id or an exact role name from get_space_type."
166627
+ });
166524
166628
  var SpaceRoleAssignmentSchema = Schema_exports.Struct({
166525
166629
  roleId: RoleId,
166526
166630
  members: Schema_exports.Array(AccountUuid)
@@ -166695,6 +166799,28 @@ var SetSpaceOwnersParamsSchema = Schema_exports.Struct({
166695
166799
  description: `Also add each owner to members. Defaults to ${DEFAULT_SPACE_OWNER_ENSURE_MEMBERS}.`
166696
166800
  }))
166697
166801
  });
166802
+ var SpaceRoleMemberMutationFields = {
166803
+ space: SpaceIdentifier.annotations({
166804
+ description: "Typed space _id or exact space name whose role assignment should change. The space must have a SpaceType."
166805
+ }),
166806
+ class: Schema_exports.optional(SpaceClassFilter.annotations({
166807
+ description: "Optional raw Huly space class ID used to disambiguate exact-name lookup."
166808
+ })),
166809
+ type: Schema_exports.optional(SpaceTypeId.annotations({
166810
+ description: "Optional raw Huly SpaceType _id used to disambiguate exact-name lookup."
166811
+ })),
166812
+ role: SpaceRoleIdentifierSchema,
166813
+ members: Schema_exports.Array(SpaceMemberIdentifierSchema).pipe(Schema_exports.minItems(1)).annotations({
166814
+ description: "Members to add or remove from this role. Each entry may be an account UUID, exact email address, or exact person name."
166815
+ })
166816
+ };
166817
+ var SpaceRoleMemberMutationParamsSchema = Schema_exports.Struct(SpaceRoleMemberMutationFields);
166818
+ var SetSpaceRoleMembersParamsSchema = Schema_exports.Struct({
166819
+ ...SpaceRoleMemberMutationFields,
166820
+ members: Schema_exports.Array(SpaceMemberIdentifierSchema).annotations({
166821
+ description: "Replacement member list for this role only. Each entry may be an account UUID, exact email address, or exact person name. Pass [] to clear this role."
166822
+ })
166823
+ });
166698
166824
  var listSpacesParamsJsonSchema = JSONSchema_exports.make(ListSpacesParamsSchema);
166699
166825
  var getSpaceParamsJsonSchema = JSONSchema_exports.make(GetSpaceParamsSchema);
166700
166826
  var listSpaceTypesParamsJsonSchema = JSONSchema_exports.make(ListSpaceTypesParamsSchema);
@@ -166706,6 +166832,8 @@ var updateSpaceParamsJsonSchema = withAtLeastOneRequired(
166706
166832
  );
166707
166833
  var spaceMemberMutationParamsJsonSchema = JSONSchema_exports.make(SpaceMemberMutationParamsSchema);
166708
166834
  var setSpaceOwnersParamsJsonSchema = JSONSchema_exports.make(SetSpaceOwnersParamsSchema);
166835
+ var spaceRoleMemberMutationParamsJsonSchema = JSONSchema_exports.make(SpaceRoleMemberMutationParamsSchema);
166836
+ var setSpaceRoleMembersParamsJsonSchema = JSONSchema_exports.make(SetSpaceRoleMembersParamsSchema);
166709
166837
  var parseListSpacesParams = Schema_exports.decodeUnknown(ListSpacesParamsSchema);
166710
166838
  var parseGetSpaceParams = Schema_exports.decodeUnknown(GetSpaceParamsSchema);
166711
166839
  var parseListSpaceTypesParams = Schema_exports.decodeUnknown(ListSpaceTypesParamsSchema);
@@ -166714,6 +166842,8 @@ var parseListSpacePermissionsParams = Schema_exports.decodeUnknown(ListSpacePerm
166714
166842
  var parseUpdateSpaceParams = Schema_exports.decodeUnknown(UpdateSpaceParamsSchema);
166715
166843
  var parseSpaceMemberMutationParams = Schema_exports.decodeUnknown(SpaceMemberMutationParamsSchema);
166716
166844
  var parseSetSpaceOwnersParams = Schema_exports.decodeUnknown(SetSpaceOwnersParamsSchema);
166845
+ var parseSpaceRoleMemberMutationParams = Schema_exports.decodeUnknown(SpaceRoleMemberMutationParamsSchema);
166846
+ var parseSetSpaceRoleMembersParams = Schema_exports.decodeUnknown(SetSpaceRoleMembersParamsSchema);
166717
166847
 
166718
166848
  // src/domain/schemas/sdk-discovery.ts
166719
166849
  var import_core10 = __toESM(require_lib4(), 1);
@@ -167252,6 +167382,7 @@ var ProjectTypeDetailSchema = Schema_exports.Struct({
167252
167382
  descriptor: NonEmptyString2,
167253
167383
  classic: Schema_exports.Boolean,
167254
167384
  isDefaultClassic: Schema_exports.Boolean,
167385
+ statusCount: Count,
167255
167386
  taskTypes: Schema_exports.Array(TaskTypeSummarySchema),
167256
167387
  statuses: Schema_exports.Array(IssueStatusSummarySchema),
167257
167388
  statusCategories: Schema_exports.Array(StatusCategorySummarySchema),
@@ -170099,6 +170230,91 @@ var parseUpdatePersonParams = Schema_exports.decodeUnknown(UpdatePersonParamsSch
170099
170230
  var parseDeletePersonParams = Schema_exports.decodeUnknown(DeletePersonParamsSchema);
170100
170231
  var parseListEmployeesParams = Schema_exports.decodeUnknown(ListEmployeesParamsSchema);
170101
170232
 
170233
+ // src/domain/schemas/external-channel-messages.ts
170234
+ var ExternalChannelMessageProviderValues = ["gmail", "telegram"];
170235
+ var ExternalChannelMessageProviderSchema = Schema_exports.Literal(...ExternalChannelMessageProviderValues);
170236
+ var DEFAULT_EXTERNAL_CHANNEL_MESSAGE_LIMIT = DEFAULT_LIMIT;
170237
+ var ListExternalChannelMessagesParamsSchema = Schema_exports.Struct({
170238
+ provider: ExternalChannelMessageProviderSchema.annotations({
170239
+ description: "External provider to read from. Supported locator values are validated for gmail and telegram; providers without a compatible installed Huly message SDK return structured unsupported results instead of fake data."
170240
+ }),
170241
+ channel: ChannelIdentifier.annotations({
170242
+ description: "External channel name or Huly channel ID locator, such as a Gmail label/inbox name or Telegram chat name/id."
170243
+ }),
170244
+ limit: Schema_exports.optional(LimitParam.annotations({
170245
+ description: `Maximum number of external messages to return (default: ${DEFAULT_EXTERNAL_CHANNEL_MESSAGE_LIMIT}, max: 200).`
170246
+ }))
170247
+ }).annotations({
170248
+ title: "ListExternalChannelMessagesParams",
170249
+ description: "Parameters for listing read-only Gmail or Telegram external channel messages."
170250
+ });
170251
+ var ExternalChannelMessageId = NonEmptyString2.pipe(Schema_exports.brand("ExternalChannelMessageId")).annotations({
170252
+ identifier: "ExternalChannelMessageId",
170253
+ title: "ExternalChannelMessageId",
170254
+ description: "Opaque external provider message ID."
170255
+ });
170256
+ var ExternalChannelMessageSubject = NonEmptyString2.pipe(
170257
+ Schema_exports.brand("ExternalChannelMessageSubject")
170258
+ ).annotations({
170259
+ identifier: "ExternalChannelMessageSubject",
170260
+ title: "ExternalChannelMessageSubject",
170261
+ description: "Non-empty external message subject. Omit the field when the provider has no subject value."
170262
+ });
170263
+ var ExternalChannelMessageSender = NonEmptyString2.pipe(Schema_exports.brand("ExternalChannelMessageSender")).annotations({
170264
+ identifier: "ExternalChannelMessageSender",
170265
+ title: "ExternalChannelMessageSender",
170266
+ description: "Non-empty normalized external message sender label or address."
170267
+ });
170268
+ var ExternalChannelMessageSenderId = NonEmptyString2.pipe(Schema_exports.brand("ExternalChannelMessageSenderId")).annotations({
170269
+ identifier: "ExternalChannelMessageSenderId",
170270
+ title: "ExternalChannelMessageSenderId",
170271
+ description: "Non-empty opaque external provider sender ID."
170272
+ });
170273
+ var ExternalChannelMessageSummarySchema = Schema_exports.Struct({
170274
+ id: ExternalChannelMessageId,
170275
+ subject: Schema_exports.optional(ExternalChannelMessageSubject),
170276
+ bodyPreview: NonEmptyString2,
170277
+ sender: Schema_exports.optional(ExternalChannelMessageSender),
170278
+ senderId: Schema_exports.optional(ExternalChannelMessageSenderId),
170279
+ createdOn: Schema_exports.optional(Timestamp),
170280
+ modifiedOn: Schema_exports.optional(Timestamp),
170281
+ url: Schema_exports.optional(UrlString)
170282
+ }).annotations({
170283
+ title: "ExternalChannelMessageSummary",
170284
+ description: "Normalized read-only summary of one external Gmail or Telegram message."
170285
+ });
170286
+ var ListExternalChannelMessagesSupportedResultSchema = Schema_exports.Struct({
170287
+ supported: Schema_exports.Literal(true),
170288
+ provider: ExternalChannelMessageProviderSchema,
170289
+ channel: ChannelIdentifier,
170290
+ limit: LimitParam,
170291
+ messages: Schema_exports.Array(ExternalChannelMessageSummarySchema)
170292
+ }).annotations({
170293
+ title: "ListExternalChannelMessagesSupportedResult",
170294
+ description: "External channel messages returned from a compatible installed Huly provider SDK/model."
170295
+ });
170296
+ var ListExternalChannelMessagesUnsupportedResultSchema = Schema_exports.Struct({
170297
+ supported: Schema_exports.Literal(false),
170298
+ provider: ExternalChannelMessageProviderSchema,
170299
+ channel: ChannelIdentifier,
170300
+ limit: LimitParam,
170301
+ unsupportedReason: NonEmptyString2,
170302
+ messages: Schema_exports.Tuple()
170303
+ }).annotations({
170304
+ title: "ListExternalChannelMessagesUnsupportedResult",
170305
+ description: "Explicit no-fake-data result when the requested external provider cannot be read in this build."
170306
+ });
170307
+ var ListExternalChannelMessagesResultSchema = Schema_exports.Union(
170308
+ ListExternalChannelMessagesSupportedResultSchema,
170309
+ ListExternalChannelMessagesUnsupportedResultSchema
170310
+ ).annotations({
170311
+ title: "ListExternalChannelMessagesResult",
170312
+ description: "Read-only external channel message listing result."
170313
+ });
170314
+ var listExternalChannelMessagesParamsJsonSchema = JSONSchema_exports.make(ListExternalChannelMessagesParamsSchema);
170315
+ var parseListExternalChannelMessagesParams = Schema_exports.decodeUnknown(ListExternalChannelMessagesParamsSchema);
170316
+ var encodeListExternalChannelMessagesResult = Schema_exports.encodeSync(ListExternalChannelMessagesResultSchema);
170317
+
170102
170318
  // src/domain/schemas/channels.ts
170103
170319
  var ListChannelsParamsBase = Schema_exports.Struct({
170104
170320
  nameSearch: Schema_exports.optional(Schema_exports.String.annotations({
@@ -174502,15 +174718,52 @@ var parseUpdateDriveFileCommentParams = Schema_exports.decodeUnknown(UpdateDrive
174502
174718
  var parseDeleteDriveFileCommentParams = Schema_exports.decodeUnknown(DeleteDriveFileCommentParamsSchema);
174503
174719
  var parseListDriveFileActivityParams = Schema_exports.decodeUnknown(ListDriveFileActivityParamsSchema);
174504
174720
 
174721
+ // src/huly/diagnostics.ts
174722
+ var Diagnostics = class extends Context_exports.Tag("@hulymcp/Diagnostics")() {
174723
+ };
174724
+ var warningLogText = (warning) => `Agent-visible tool warning [${warning.code}]: ${warning.message}`;
174725
+ var makeDiagnosticsScope = Effect_exports.gen(function* () {
174726
+ const warningsRef = yield* Ref_exports.make([]);
174727
+ return {
174728
+ service: {
174729
+ warnAgent: (warning) => Ref_exports.update(warningsRef, (warnings) => [...warnings, warning]).pipe(
174730
+ Effect_exports.zipRight(Effect_exports.logWarning(warningLogText(warning)))
174731
+ ),
174732
+ trail: (message) => Effect_exports.logInfo(`Diagnostic trail: ${message}`)
174733
+ },
174734
+ drainWarnings: Ref_exports.get(warningsRef)
174735
+ };
174736
+ });
174737
+
174505
174738
  // src/version.ts
174506
- var VERSION = true ? "0.30.1" : "0.0.0-dev";
174739
+ var VERSION = true ? "0.31.1" : "0.0.0-dev";
174507
174740
 
174508
174741
  // src/mcp/tool-output-schema.ts
174742
+ var toolWarningCodeEnum = [...ToolWarningCodeSchema.literals];
174509
174743
  var defaultToolOutputSchema = {
174510
174744
  type: "object",
174511
174745
  properties: {
174512
174746
  result: {
174513
174747
  description: "The successful tool result. The same value is also serialized as JSON in the text content for clients that do not read structuredContent."
174748
+ },
174749
+ warnings: {
174750
+ type: "array",
174751
+ description: "Optional agent-visible warnings about degraded result fidelity. Omitted when the server returned the documented happy-path payload.",
174752
+ items: {
174753
+ type: "object",
174754
+ properties: {
174755
+ code: {
174756
+ type: "string",
174757
+ enum: toolWarningCodeEnum
174758
+ },
174759
+ message: {
174760
+ type: "string",
174761
+ minLength: 1
174762
+ }
174763
+ },
174764
+ required: ["code", "message"],
174765
+ additionalProperties: false
174766
+ }
174514
174767
  }
174515
174768
  },
174516
174769
  required: ["result"]
@@ -174983,6 +175236,93 @@ var removeAccountUuids = (current, removals) => {
174983
175236
  var arraysEqual = (left3, right3) => left3.length === right3.length && left3.every((value3, index) => value3 === right3[index]);
174984
175237
  var optionalString = (value3) => value3 === void 0 || value3 === "" ? void 0 : value3;
174985
175238
  var optionalObjectClassName = (value3) => value3 === void 0 || value3 === "" ? void 0 : ObjectClassName.make(value3);
175239
+ var RoleAssignmentStorageSchema = Schema_exports.Record({ key: Schema_exports.String, value: Schema_exports.Array(AccountUuid) });
175240
+ var isRecordObject = (value3) => typeof value3 === "object" && value3 !== null && !Array.isArray(value3);
175241
+ var validStoredAccountUuid = (value3) => {
175242
+ const decoded = Schema_exports.decodeUnknownEither(AccountUuid)(value3);
175243
+ return decoded._tag === "Right" ? toAccountUuid(NonEmptyString2.make(decoded.right)) : void 0;
175244
+ };
175245
+ var parsedSpaceRoleAssignmentEntry = (roleId, members) => ({
175246
+ _tag: "entry",
175247
+ entry: [
175248
+ toRef(roleId),
175249
+ sortStrings(members).map(toAccountUuid)
175250
+ ]
175251
+ });
175252
+ var spaceRoleAssignmentSource = (space, spaceType) => Object.prototype.hasOwnProperty.call(space, spaceType.targetClass) ? { present: true, value: Object.entries(space).find(([key]) => key === spaceType.targetClass)?.[1] } : { present: false, value: void 0 };
175253
+ var roleAssignmentsMalformedError = (space, spaceType, reason) => new SpaceRoleAssignmentsMalformedError({
175254
+ space: SpaceId.make(space._id),
175255
+ spaceType: SpaceTypeId.make(spaceType._id),
175256
+ targetClass: ObjectClassName.make(spaceType.targetClass),
175257
+ reason: NonEmptyString2.make(reason)
175258
+ });
175259
+ var readSpaceRoleAssignmentEntries = (space, spaceType, validRoleIds) => {
175260
+ const source = spaceRoleAssignmentSource(space, spaceType);
175261
+ if (!source.present) return { entries: [], degradationReasons: [] };
175262
+ if (!isRecordObject(source.value)) {
175263
+ return {
175264
+ entries: [],
175265
+ degradationReasons: [`role assignment mixin ${spaceType.targetClass} is not an object`]
175266
+ };
175267
+ }
175268
+ const parsed = Object.entries(source.value).flatMap(([roleId, members]) => {
175269
+ if (!validRoleIds.has(toRef(roleId))) {
175270
+ return [{
175271
+ _tag: "dropped",
175272
+ reason: `role assignment '${roleId}' is not defined on space type ${spaceType._id}`
175273
+ }];
175274
+ }
175275
+ if (!Array.isArray(members)) {
175276
+ return [{ _tag: "dropped", reason: `role assignment '${roleId}' members are not an array` }];
175277
+ }
175278
+ const accountUuids = members.flatMap((member) => {
175279
+ const accountUuid = validStoredAccountUuid(member);
175280
+ return accountUuid === void 0 ? [] : [accountUuid];
175281
+ });
175282
+ const invalidMemberCount = members.length - accountUuids.length;
175283
+ return [
175284
+ ...invalidMemberCount > 0 ? [{
175285
+ _tag: "dropped",
175286
+ reason: `role assignment '${roleId}' has ${invalidMemberCount} malformed account UUID value(s)`
175287
+ }] : [],
175288
+ parsedSpaceRoleAssignmentEntry(roleId, accountUuids)
175289
+ ];
175290
+ });
175291
+ return {
175292
+ entries: parsed.flatMap((item) => item._tag === "entry" ? [item.entry] : []),
175293
+ degradationReasons: parsed.flatMap((item) => item._tag === "dropped" ? [item.reason] : [])
175294
+ };
175295
+ };
175296
+ var spaceRoleAssignmentEntries = (space, spaceType, validRoleIds) => readSpaceRoleAssignmentEntries(space, spaceType, validRoleIds).entries;
175297
+ var hasSpaceRoleAssignmentMixin = (space, spaceType) => spaceRoleAssignmentSource(space, spaceType).present;
175298
+ var strictSpaceRoleAssignments = (space, spaceType, validRoleIds) => Effect_exports.gen(function* () {
175299
+ const source = spaceRoleAssignmentSource(space, spaceType);
175300
+ if (!source.present) return {};
175301
+ const decoded = Schema_exports.decodeUnknownEither(RoleAssignmentStorageSchema)(source.value);
175302
+ if (decoded._tag === "Left") {
175303
+ return yield* roleAssignmentsMalformedError(
175304
+ space,
175305
+ spaceType,
175306
+ `expected an object whose keys are role ids and values are arrays of account UUIDs`
175307
+ );
175308
+ }
175309
+ const unknownRoleIds = Object.keys(decoded.right).filter((roleId) => !validRoleIds.has(toRef(roleId)));
175310
+ if (unknownRoleIds.length > 0) {
175311
+ return yield* roleAssignmentsMalformedError(
175312
+ space,
175313
+ spaceType,
175314
+ `unknown role assignment key(s): ${unknownRoleIds.join(", ")}`
175315
+ );
175316
+ }
175317
+ return Object.fromEntries(
175318
+ Object.entries(decoded.right).map(([roleId, members]) => [
175319
+ toRef(roleId),
175320
+ members.map((member) => toAccountUuid(NonEmptyString2.make(member)))
175321
+ ])
175322
+ );
175323
+ });
175324
+ var roleAssignmentDegradationMessage = (reasons) => `Some typed-space role assignment data was omitted because existing Huly storage is malformed: ${reasons.join("; ")}. Read results include only valid role assignments; role-member write tools will refuse to modify this space until the stored role assignment data is repaired.`;
175325
+ var spaceRoleAssignmentsMixin = (spaceType) => toMixinRef(spaceType.targetClass);
174986
175326
  var applySpaceFilters = (query, filters) => {
174987
175327
  const next4 = { ...query };
174988
175328
  if (!filters.includeArchived) {
@@ -176182,7 +176522,7 @@ var findProject = (projectIdentifier) => Effect_exports.gen(function* () {
176182
176522
  );
176183
176523
  return { client, project: project3 };
176184
176524
  });
176185
- var statusCategoryValueFromRef = (category) => category === void 0 ? "unknown" : StatusCategoryEntries.find((entry) => entry.ref === category)?.key ?? "unknown";
176525
+ var statusCategoryValueFromRef = (category) => category === void 0 ? UnknownStatusCategoryValue : StatusCategoryEntries.find((entry) => entry.ref === category)?.key ?? UnknownStatusCategoryValue;
176186
176526
  var workflowStatusFromDoc = (doc) => {
176187
176527
  return {
176188
176528
  _id: doc._id,
@@ -176195,7 +176535,7 @@ var workflowStatusFromRef = (statusRef) => {
176195
176535
  return {
176196
176536
  _id: statusRef,
176197
176537
  name,
176198
- category: "unknown"
176538
+ category: UnknownStatusCategoryValue
176199
176539
  };
176200
176540
  };
176201
176541
  var uniqueStatusRefs = (refs) => refs.reduce(
@@ -176207,6 +176547,51 @@ var uniqueStatusDocs = (statuses) => Array.from(statuses).reduce(
176207
176547
  []
176208
176548
  );
176209
176549
  var uniqueProjectTypeStatusRefs = (statuses) => uniqueStatusRefs(statuses.map((status) => status._id));
176550
+ var missingStatusRefs = (statusRefs, statusDocs) => statusRefs.filter((statusRef) => !statusDocs.some((statusDoc) => statusDoc._id === statusRef));
176551
+ var resolveByStatusRef = (statusRefs, statusDocs, fromDoc, fromRef) => {
176552
+ const statusDocsById = new Map(statusDocs.map((statusDoc) => [statusDoc._id, statusDoc]));
176553
+ return statusRefs.map((statusRef) => {
176554
+ const statusDoc = statusDocsById.get(statusRef);
176555
+ return statusDoc === void 0 ? fromRef(statusRef) : fromDoc(statusDoc);
176556
+ });
176557
+ };
176558
+ var workflowStatusesFromDocsOrRefs = (statusRefs, statusDocs) => resolveByStatusRef(statusRefs, statusDocs, workflowStatusFromDoc, workflowStatusFromRef);
176559
+ var findStatusDocs = (client, statusRefs) => Effect_exports.gen(function* () {
176560
+ const diagnostics = yield* Diagnostics;
176561
+ const remoteResult = yield* Effect_exports.either(
176562
+ client.findAll(
176563
+ core.class.Status,
176564
+ hulyQuery({ _id: { $in: [...statusRefs] } })
176565
+ )
176566
+ );
176567
+ const remoteDocs = remoteResult._tag === "Right" ? uniqueStatusDocs(remoteResult.right) : [];
176568
+ const unresolvedRefs = missingStatusRefs(statusRefs, remoteDocs);
176569
+ if (unresolvedRefs.length === 0) {
176570
+ return remoteDocs;
176571
+ }
176572
+ const modelResult = yield* Effect_exports.either(
176573
+ client.findAllInModel(
176574
+ core.class.Status,
176575
+ hulyQuery({ _id: { $in: unresolvedRefs } })
176576
+ )
176577
+ );
176578
+ const modelDocs = modelResult._tag === "Right" ? uniqueStatusDocs(modelResult.right) : [];
176579
+ const combinedDocs = uniqueStatusDocs([...remoteDocs, ...modelDocs]);
176580
+ const stillUnresolvedRefs = missingStatusRefs(statusRefs, combinedDocs);
176581
+ if (stillUnresolvedRefs.length > 0) {
176582
+ const remoteError = remoteResult._tag === "Left" ? ` Remote error: ${remoteResult.left.message}` : "";
176583
+ const modelError = modelResult._tag === "Left" ? ` Model error: ${modelResult.left.message}` : "";
176584
+ yield* diagnostics.warnAgent({
176585
+ code: StatusMetadataUnresolvedWarningCode,
176586
+ message: `Huly did not return metadata for ${stillUnresolvedRefs.length} workflow status ref(s). The tool result uses ref-derived status names and category "${UnknownStatusCategoryValue}" for those statuses; do not infer completion or cancellation semantics from those fallback names.${remoteError}${modelError}`
176587
+ });
176588
+ } else if (remoteResult._tag === "Left") {
176589
+ yield* diagnostics.trail(
176590
+ `Server status metadata lookup failed, but the local Huly model resolved all requested workflow statuses. Remote error: ${remoteResult.left.message}`
176591
+ );
176592
+ }
176593
+ return combinedDocs;
176594
+ });
176210
176595
  var findProjectWithStatuses = (projectIdentifier) => Effect_exports.gen(function* () {
176211
176596
  const client = yield* HulyClient;
176212
176597
  const project3 = yield* findOneOrFail(
@@ -176222,19 +176607,8 @@ var findProjectWithStatuses = (projectIdentifier) => Effect_exports.gen(function
176222
176607
  if (statusRefs.length === 0) {
176223
176608
  return [];
176224
176609
  }
176225
- const statusDocsResult = yield* Effect_exports.either(
176226
- client.findAll(
176227
- core.class.Status,
176228
- hulyQuery({ _id: { $in: statusRefs } })
176229
- )
176230
- );
176231
- if (statusDocsResult._tag === "Right") {
176232
- return uniqueStatusDocs(statusDocsResult.right).map(workflowStatusFromDoc);
176233
- }
176234
- yield* Effect_exports.logWarning(
176235
- `Status query failed for project ${projectIdentifier}, using fallback. statusCategory filtering is unavailable until Huly returns status metadata. Error: ${statusDocsResult.left.message}`
176236
- );
176237
- return statusRefs.map(workflowStatusFromRef);
176610
+ const statusDocs = yield* findStatusDocs(client, statusRefs);
176611
+ return workflowStatusesFromDocsOrRefs(statusRefs, statusDocs);
176238
176612
  }) : [];
176239
176613
  const defaultStatusId = project3.defaultIssueStatus || statuses[0]?._id;
176240
176614
  return { client, defaultStatusId, project: project3, projectType, statuses };
@@ -176598,19 +176972,27 @@ var createHandler = (toolName, provide4, parse5, operation, encode8) => async (a
176598
176972
  if (Exit_exports.isFailure(parseResult)) {
176599
176973
  return mapParseCauseToMcp(parseResult.cause, toolName);
176600
176974
  }
176601
- const provided = provide4({ hulyClient, storageClient, workspaceClient })(operation(parseResult.value));
176975
+ const diagnosticsScope = await Effect_exports.runPromise(makeDiagnosticsScope);
176976
+ const provided = provide4({
176977
+ hulyClient,
176978
+ storageClient,
176979
+ workspaceClient
176980
+ })(operation(parseResult.value));
176602
176981
  if (Either_exports.isLeft(provided)) {
176603
176982
  return provided.left;
176604
176983
  }
176605
- const operationResult = await Effect_exports.runPromiseExit(provided.right);
176984
+ const operationResult = await Effect_exports.runPromiseExit(
176985
+ provided.right.pipe(Effect_exports.provideService(Diagnostics, diagnosticsScope.service))
176986
+ );
176987
+ const warnings = await Effect_exports.runPromise(diagnosticsScope.drainWarnings);
176606
176988
  if (Exit_exports.isFailure(operationResult)) {
176607
- return mapDomainCauseToMcp(operationResult.cause);
176989
+ return mapDomainCauseToMcp(operationResult.cause, warnings);
176608
176990
  }
176609
176991
  try {
176610
176992
  const output = encode8 !== void 0 ? encode8(operationResult.value) : operationResult.value;
176611
- return createSuccessResponse(output);
176993
+ return createSuccessResponse(output, warnings);
176612
176994
  } catch {
176613
- return mapDomainErrorToMcp(new HulyError({ message: `Tool ${toolName} produced invalid output` }));
176995
+ return mapDomainErrorToMcp(new HulyError({ message: `Tool ${toolName} produced invalid output` }), warnings);
176614
176996
  }
176615
176997
  };
176616
176998
  var createToolHandler = (toolName, parse5, operation) => createHandler(toolName, provideHulyClient, parse5, operation);
@@ -178722,7 +179104,7 @@ var contactCoveredRationale = "Current contacts tools expose person, organizatio
178722
179104
  var cardCoveredRationale = "Current card tools cover card spaces, master tags, and card CRUD.";
178723
179105
  var chunterCoveredRationale = "Current channel and direct-message tools cover channels, channel messages, one-to-one DM listing, and thread replies.";
178724
179106
  var coreCoveredRationale = "Existing tools expose user statuses, full-text search, blobs through storage/download flows, generic association/relation discovery/mutation helpers, class/interface/mixin, attribute, enum, plugin configuration, domain index configuration, sequence, and space type capability discovery.";
178725
- var coreGapRationale = "Remaining core write-side configuration, role assignment writes, role/permission definition writes, generic space creation, class collaborator metadata, statuses, and write-side model management are represented as matrix gaps. Generic space discovery, space type/permission reads, safe existing-space metadata updates, member mutations, owner replacement, object collaborators, read-only plugin configuration, domain index configuration, and sequence discovery are covered.";
179107
+ var coreGapRationale = "Remaining core write-side configuration, role/permission definition writes, generic space creation, class collaborator metadata, statuses, and write-side model management are represented as matrix gaps. Generic space discovery, space type/permission reads, safe existing-space metadata updates, member mutations, owner replacement, typed-space role member mutations, object collaborators, read-only plugin configuration, domain index configuration, and sequence discovery are covered.";
178726
179108
  var coreNotMcpFacingRationale = "Core primitive model infrastructure, transaction classes, type wrappers, and versioning internals are not LLM-facing product resources by themselves.";
178727
179109
  var routingRow = (classId, packageName, exportName, hint) => ({
178728
179110
  classId: ObjectClassName.make(classId),
@@ -179513,6 +179895,19 @@ var createDirectMessage = (params) => Effect_exports.gen(function* () {
179513
179895
  return { id: ChannelId.make(dmId), created: true };
179514
179896
  });
179515
179897
 
179898
+ // src/huly/operations/external-channel-messages.ts
179899
+ var EXTERNAL_CHANNEL_PACKAGE_INCOMPATIBLE_REASON = "package-incompatible: package.json and pnpm-lock.yaml include @hcengineering/contact provider refs for email/telegram, but no compatible Huly Gmail or Telegram message SDK package/model is installed; local platform-api examples only expose contact.class.Channel provider values, not external message documents";
179900
+ var listExternalChannelMessages = (params) => Effect_exports.sync(
179901
+ () => encodeListExternalChannelMessagesResult({
179902
+ supported: false,
179903
+ provider: params.provider,
179904
+ channel: params.channel,
179905
+ limit: params.limit ?? DEFAULT_EXTERNAL_CHANNEL_MESSAGE_LIMIT,
179906
+ unsupportedReason: EXTERNAL_CHANNEL_PACKAGE_INCOMPATIBLE_REASON,
179907
+ messages: []
179908
+ })
179909
+ );
179910
+
179516
179911
  // src/huly/operations/threads.ts
179517
179912
  var import_core31 = __toESM(require_lib4(), 1);
179518
179913
  var findReply = (client, channel, message, replyId) => Effect_exports.gen(function* () {
@@ -179806,6 +180201,17 @@ var channelTools = [
179806
180201
  listChannelMessages
179807
180202
  )
179808
180203
  },
180204
+ {
180205
+ name: "list_external_channel_messages",
180206
+ description: "List read-only messages for an external Gmail or Telegram channel by channel name or ID. The limit defaults to 50 and is capped at 200. When this build does not include a compatible Huly external-message SDK/model for the requested provider, returns supported=false, an unsupportedReason, and an empty messages array; it never sends, replies, deletes, mutates, or returns fake messages.",
180207
+ category: CATEGORY6,
180208
+ inputSchema: listExternalChannelMessagesParamsJsonSchema,
180209
+ handler: createToolHandler(
180210
+ "list_external_channel_messages",
180211
+ parseListExternalChannelMessagesParams,
180212
+ listExternalChannelMessages
180213
+ )
180214
+ },
179809
180215
  {
179810
180216
  name: "send_channel_message",
179811
180217
  description: "Send a message to a Huly channel. Message body supports markdown formatting.",
@@ -186694,6 +187100,18 @@ var labelTools = [
186694
187100
  // src/huly/operations/leads.ts
186695
187101
  var import_core50 = __toESM(require_lib4(), 1);
186696
187102
  var funnelAsSpace = (funnel) => toRef(funnel._id);
187103
+ var statusInfosWithFallbacks = (statusRefs, statusDocs) => resolveByStatusRef(
187104
+ statusRefs,
187105
+ statusDocs,
187106
+ (statusDoc) => ({
187107
+ _id: statusDoc._id,
187108
+ name: statusDoc.name
187109
+ }),
187110
+ (statusRef) => ({
187111
+ _id: statusRef,
187112
+ name: workflowStatusFromRef(statusRef).name
187113
+ })
187114
+ );
186697
187115
  var markupBlobRefAsMarkupRef = (value3) => value3;
186698
187116
  var normalizeLeadIdentifier = (identifier2) => {
186699
187117
  const match16 = /^(?:LEAD-)?(\d+)$/i.exec(identifier2.trim());
@@ -186742,7 +187160,7 @@ var getFunnelStatuses = (client, funnel) => Effect_exports.gen(function* () {
186742
187160
  })
186743
187161
  );
186744
187162
  }
186745
- const statusRefs = projectType.statuses.map((status) => status._id);
187163
+ const statusRefs = uniqueStatusRefs(projectType.statuses.map((status) => status._id));
186746
187164
  if (statusRefs.length === 0) {
186747
187165
  return yield* Effect_exports.fail(
186748
187166
  new HulyConnectionError({
@@ -186750,14 +187168,8 @@ var getFunnelStatuses = (client, funnel) => Effect_exports.gen(function* () {
186750
187168
  })
186751
187169
  );
186752
187170
  }
186753
- const statusDocs = yield* client.findAll(
186754
- core.class.Status,
186755
- { _id: { $in: [...statusRefs] } }
186756
- );
186757
- return statusDocs.map((doc) => ({
186758
- _id: doc._id,
186759
- name: doc.name
186760
- }));
187171
+ const statusDocs = yield* findStatusDocs(client, statusRefs);
187172
+ return statusInfosWithFallbacks(statusRefs, statusDocs);
186761
187173
  });
186762
187174
  var resolveStatusName2 = (statuses, statusId) => {
186763
187175
  const statusDoc = statuses.find((status) => status._id === statusId);
@@ -189553,14 +189965,15 @@ var toSpaceSummary = (space) => ({
189553
189965
  membersCount: Count.make(space.members.length),
189554
189966
  ownersCount: Count.make(space.owners?.length ?? 0)
189555
189967
  });
189556
- var roleAssignments = (space) => {
189557
- if (space.roles === void 0) return void 0;
189558
- return Object.entries(space.roles).map(([roleId, members]) => ({
189968
+ var roleAssignments = (space, spaceType, validRoleIds) => {
189969
+ if (spaceType === void 0) return void 0;
189970
+ const entries2 = spaceRoleAssignmentEntries(space, spaceType, validRoleIds);
189971
+ return entries2.length === 0 ? void 0 : entries2.map(([roleId, members]) => ({
189559
189972
  roleId: RoleId.make(roleId),
189560
- members: (members ?? []).map((member) => AccountUuid.make(member))
189973
+ members: members.map((member) => AccountUuid.make(member))
189561
189974
  }));
189562
189975
  };
189563
- var toSpaceDetail = (space) => ({
189976
+ var toSpaceDetail = (space, spaceType, validRoleIds = /* @__PURE__ */ new Set()) => ({
189564
189977
  id: SpaceId.make(space._id),
189565
189978
  name: space.name,
189566
189979
  description: space.description,
@@ -189571,7 +189984,7 @@ var toSpaceDetail = (space) => ({
189571
189984
  autoJoin: space.autoJoin,
189572
189985
  members: space.members.map((member) => AccountUuid.make(member)),
189573
189986
  owners: (space.owners ?? []).map((owner) => AccountUuid.make(owner)),
189574
- roleAssignments: roleAssignments(space)
189987
+ roleAssignments: roleAssignments(space, spaceType, validRoleIds)
189575
189988
  });
189576
189989
  var spaceTypeSummary = (spaceType, descriptor3) => ({
189577
189990
  id: SpaceTypeId.make(spaceType._id),
@@ -189653,8 +190066,28 @@ var listSpaces = (params) => Effect_exports.gen(function* () {
189653
190066
  });
189654
190067
  var getSpace = (params) => Effect_exports.gen(function* () {
189655
190068
  const client = yield* HulyClient;
190069
+ const diagnostics = yield* Diagnostics;
189656
190070
  const space = yield* findSpace(client, params);
189657
- return toSpaceDetail(space);
190071
+ const spaceType = space.type === void 0 ? void 0 : yield* client.findOne(
190072
+ core.class.SpaceType,
190073
+ hulyQuery({ _id: toRef(space.type) })
190074
+ );
190075
+ const roles = spaceType === void 0 ? [] : yield* client.findAll(
190076
+ core.class.Role,
190077
+ hulyQuery({ attachedTo: spaceType._id }),
190078
+ { limit: Math.max(spaceType.roles, 1) }
190079
+ );
190080
+ const validRoleIds = new Set(roles.map((role) => role._id));
190081
+ if (spaceType !== void 0) {
190082
+ const readResult = readSpaceRoleAssignmentEntries(space, spaceType, validRoleIds);
190083
+ if (readResult.degradationReasons.length > 0) {
190084
+ yield* diagnostics.warnAgent({
190085
+ code: SpaceRoleAssignmentsDegradedWarningCode,
190086
+ message: roleAssignmentDegradationMessage(readResult.degradationReasons)
190087
+ });
190088
+ }
190089
+ }
190090
+ return toSpaceDetail(space, spaceType, validRoleIds);
189658
190091
  });
189659
190092
  var listSpaceTypes = (params) => Effect_exports.gen(function* () {
189660
190093
  const client = yield* HulyClient;
@@ -189837,8 +190270,8 @@ var describeHulySpaceTypeCapabilities = (params) => Effect_exports.gen(function*
189837
190270
  roles: detail.roles,
189838
190271
  rolePermissions: detail.availablePermissions,
189839
190272
  assignmentShape: {
189840
- storedOnSpaceField: HulyConfigurationMetadataKey.make("roles"),
189841
- roleKeyField: HulyConfigurationMetadataKey.make("roleId"),
190273
+ storedOnSpaceField: HulyConfigurationMetadataKey.make(`mixin:${detail.targetClass}`),
190274
+ roleKeyField: HulyConfigurationMetadataKey.make("role._id"),
189842
190275
  memberValueShape: "accountUuidArrayOrUndefined",
189843
190276
  readProjectionTools: ["get_space"].map((tool) => HulyMcpToolName.make(tool))
189844
190277
  }
@@ -190172,6 +190605,104 @@ var searchTools = [
190172
190605
  ];
190173
190606
 
190174
190607
  // src/huly/operations/spaces-write.ts
190608
+ var roleClass = core.class.Role;
190609
+ var spaceTypeClass = core.class.SpaceType;
190610
+ var requireTypedSpaceType = (space) => Effect_exports.gen(function* () {
190611
+ if (space.type === void 0) {
190612
+ return yield* new SpaceNotTypedError({
190613
+ id: SpaceId.make(space._id),
190614
+ name: NonEmptyString2.make(space.name)
190615
+ });
190616
+ }
190617
+ return SpaceTypeId.make(space.type);
190618
+ });
190619
+ var findSpaceType2 = (client, spaceType) => Effect_exports.gen(function* () {
190620
+ const result = yield* client.findOne(
190621
+ spaceTypeClass,
190622
+ hulyQuery({ _id: toRef(spaceType) })
190623
+ );
190624
+ if (result === void 0) {
190625
+ return yield* new SpaceRoleNotFoundError({
190626
+ identifier: NonEmptyString2.make("SpaceType roles"),
190627
+ spaceType
190628
+ });
190629
+ }
190630
+ return result;
190631
+ });
190632
+ var resolveSpaceRole = (client, spaceType, role) => Effect_exports.gen(function* () {
190633
+ const byId = yield* client.findOne(
190634
+ roleClass,
190635
+ hulyQuery({
190636
+ _id: toRef(role),
190637
+ attachedTo: toRef(spaceType)
190638
+ })
190639
+ );
190640
+ if (byId !== void 0) return byId;
190641
+ const matches = yield* client.findAll(
190642
+ roleClass,
190643
+ hulyQuery({
190644
+ attachedTo: toRef(spaceType),
190645
+ name: role
190646
+ }),
190647
+ { limit: 2 }
190648
+ );
190649
+ if (matches.length === 0) {
190650
+ return yield* new SpaceRoleNotFoundError({
190651
+ identifier: NonEmptyString2.make(role),
190652
+ spaceType
190653
+ });
190654
+ }
190655
+ if (matches.length > 1) {
190656
+ return yield* new SpaceRoleIdentifierAmbiguousError({
190657
+ identifier: NonEmptyString2.make(role),
190658
+ spaceType,
190659
+ matches: matches.map((match16) => ({
190660
+ id: RoleId.make(match16._id),
190661
+ name: NonEmptyString2.make(match16.name)
190662
+ }))
190663
+ });
190664
+ }
190665
+ return matches[0];
190666
+ });
190667
+ var findSpaceTypeRoles = (client, spaceType) => client.findAll(
190668
+ roleClass,
190669
+ hulyQuery({ attachedTo: spaceType._id }),
190670
+ { limit: Math.max(spaceType.roles, 1) }
190671
+ );
190672
+ var writeSpaceRoleMembers = (client, space, spaceType, role, currentAssignments, members) => {
190673
+ const mixin = spaceRoleAssignmentsMixin(spaceType);
190674
+ const attributes = { ...currentAssignments, [role._id]: members };
190675
+ const objectId = toRef(space._id);
190676
+ const objectClass = toClassRef(space._class);
190677
+ const objectSpace = toRef(space.space);
190678
+ return hasSpaceRoleAssignmentMixin(space, spaceType) ? client.updateMixin(objectId, objectClass, objectSpace, mixin, attributes).pipe(Effect_exports.asVoid) : client.createMixin(objectId, objectClass, objectSpace, mixin, attributes).pipe(Effect_exports.asVoid);
190679
+ };
190680
+ var mutateSpaceRoleMembers = (params, mutateMembers) => Effect_exports.gen(function* () {
190681
+ const client = yield* HulyClient;
190682
+ const space = yield* findSpace(client, params);
190683
+ const spaceType = yield* requireTypedSpaceType(space);
190684
+ const spaceTypeDoc = yield* findSpaceType2(client, spaceType);
190685
+ const role = yield* resolveSpaceRole(client, spaceType, params.role);
190686
+ const validRoles = yield* findSpaceTypeRoles(client, spaceTypeDoc);
190687
+ const resolvedMembers = yield* resolveMembers(client, params.members);
190688
+ const currentAssignments = yield* strictSpaceRoleAssignments(
190689
+ space,
190690
+ spaceTypeDoc,
190691
+ new Set(validRoles.map((validRole) => validRole._id))
190692
+ );
190693
+ const currentMembers = sortStrings(currentAssignments[role._id] ?? []).map(toAccountUuid);
190694
+ const nextMembers = mutateMembers(currentMembers, resolvedMembers).map(toAccountUuid);
190695
+ const changed = !arraysEqual(currentMembers, nextMembers);
190696
+ if (changed) {
190697
+ yield* writeSpaceRoleMembers(client, space, spaceTypeDoc, role, currentAssignments, nextMembers);
190698
+ }
190699
+ return {
190700
+ id: SpaceId.make(space._id),
190701
+ roleId: RoleId.make(role._id),
190702
+ members: nextMembers.map((member) => AccountUuid.make(member)),
190703
+ changed
190704
+ };
190705
+ });
190175
190706
  var mutateSpaceMembers = (params, mutateMembers) => Effect_exports.gen(function* () {
190176
190707
  const client = yield* HulyClient;
190177
190708
  const space = yield* findSpace(client, params);
@@ -190224,6 +190755,9 @@ var setSpaceOwners = (params) => Effect_exports.gen(function* () {
190224
190755
  changed: changedOwners || changedMembers
190225
190756
  };
190226
190757
  });
190758
+ var setSpaceRoleMembers = (params) => mutateSpaceRoleMembers(params, (_currentMembers, resolvedMembers) => sortStrings(resolvedMembers).map(toAccountUuid));
190759
+ var addSpaceRoleMembers = (params) => mutateSpaceRoleMembers(params, mergeUniqueSortedAccountUuids);
190760
+ var removeSpaceRoleMembers = (params) => mutateSpaceRoleMembers(params, removeAccountUuids);
190227
190761
 
190228
190762
  // src/mcp/tools/spaces.ts
190229
190763
  var CATEGORY28 = "spaces";
@@ -190290,6 +190824,30 @@ var spaceTools = [
190290
190824
  category: CATEGORY28,
190291
190825
  inputSchema: setSpaceOwnersParamsJsonSchema,
190292
190826
  handler: createToolHandler("set_space_owners", parseSetSpaceOwnersParams, setSpaceOwners)
190827
+ },
190828
+ {
190829
+ name: "set_space_role_members",
190830
+ description: "Replace members assigned to one role on a typed Huly space while preserving all other role assignments. Role accepts a raw role _id or exact role name from the space's SpaceType. Members accept account UUID, exact email, or exact person display name; pass members=[] to clear this role.",
190831
+ category: CATEGORY28,
190832
+ inputSchema: setSpaceRoleMembersParamsJsonSchema,
190833
+ annotations: { idempotentHint: true, destructiveHint: false },
190834
+ handler: createToolHandler("set_space_role_members", parseSetSpaceRoleMembersParams, setSpaceRoleMembers)
190835
+ },
190836
+ {
190837
+ name: "add_space_role_members",
190838
+ description: "Idempotently add members to one role on a typed Huly space while preserving all other role assignments. Role accepts a raw role _id or exact role name from the space's SpaceType. Members accept account UUID, exact email, or exact person display name.",
190839
+ category: CATEGORY28,
190840
+ inputSchema: spaceRoleMemberMutationParamsJsonSchema,
190841
+ annotations: { idempotentHint: true, destructiveHint: false },
190842
+ handler: createToolHandler("add_space_role_members", parseSpaceRoleMemberMutationParams, addSpaceRoleMembers)
190843
+ },
190844
+ {
190845
+ name: "remove_space_role_members",
190846
+ description: "Idempotently remove members from one role on a typed Huly space while preserving all other role assignments. Role accepts a raw role _id or exact role name from the space's SpaceType. Members accept account UUID, exact email, or exact person display name.",
190847
+ category: CATEGORY28,
190848
+ inputSchema: spaceRoleMemberMutationParamsJsonSchema,
190849
+ annotations: { idempotentHint: true, destructiveHint: false },
190850
+ handler: createToolHandler("remove_space_role_members", parseSpaceRoleMemberMutationParams, removeSpaceRoleMembers)
190293
190851
  }
190294
190852
  ];
190295
190853
 
@@ -190591,7 +191149,7 @@ var STATUS_CATEGORIES = Object.values(STATUS_CATEGORY_BY_SDK_KEY).map((entry) =>
190591
191149
  name: entry.name
190592
191150
  }));
190593
191151
  var WORKFLOW_WARNING = "This changes workspace-level tracker configuration for every project using this project type.";
190594
- var toCategoryValue = (category) => category === void 0 ? "unknown" : REF_TO_CATEGORY.get(category) ?? "unknown";
191152
+ var toCategoryValue = (category) => category === void 0 ? UnknownStatusCategoryValue : REF_TO_CATEGORY.get(category) ?? UnknownStatusCategoryValue;
190595
191153
  var encodeOrConnectionError2 = (schema, value3, operation) => Schema_exports.encode(schema)(value3).pipe(
190596
191154
  Effect_exports.as(value3),
190597
191155
  Effect_exports.mapError(
@@ -190607,9 +191165,17 @@ var uniqueProjectStatuses = (statuses) => statuses.reduce(
190607
191165
  (unique, status) => unique.some((existing) => sameProjectStatus(existing, status)) ? unique : [...unique, status],
190608
191166
  []
190609
191167
  );
190610
- var getStatusDocs = (client, statusIds) => statusIds.length === 0 ? Effect_exports.succeed([]) : client.findAll(core.class.Status, hulyQuery({ _id: { $in: [...statusIds] } })).pipe(
190611
- Effect_exports.map(uniqueStatusDocs)
190612
- );
191168
+ var getStatusDocs = (client, statusIds) => statusIds.length === 0 ? Effect_exports.succeed([]) : findStatusDocs(client, statusIds);
191169
+ var fallbackStatusDoc = (statusId) => ({
191170
+ _id: statusId,
191171
+ _class: core.class.Status,
191172
+ space: core.space.Model,
191173
+ modifiedOn: 0,
191174
+ modifiedBy: core.account.System,
191175
+ ofAttribute: tracker.attribute.IssueStatus,
191176
+ name: workflowStatusFromRef(statusId).name
191177
+ });
191178
+ var statusDocsWithFallbacks = (statusIds, statusDocs) => resolveByStatusRef(statusIds, statusDocs, (statusDoc) => statusDoc, fallbackStatusDoc);
190613
191179
  var getTaskTypes = (client, taskTypeIds) => taskTypeIds.length === 0 ? Effect_exports.succeed([]) : client.findAll(task.class.TaskType, hulyQuery({ _id: { $in: [...taskTypeIds] } })).pipe(
190614
191180
  Effect_exports.map((result) => [...result])
190615
191181
  );
@@ -190628,7 +191194,9 @@ var getRecoverableStatusesByName = (client, name) => client.findAll(core.class.S
190628
191194
  );
190629
191195
  var loadWorkflowData = (client, projectType) => Effect_exports.gen(function* () {
190630
191196
  const taskTypes = yield* getTaskTypes(client, projectType.tasks);
190631
- const statuses = yield* getStatusDocs(client, uniqueStatusIds(projectType));
191197
+ const statusIds = uniqueStatusIds(projectType);
191198
+ const statusDocs = yield* getStatusDocs(client, statusIds);
191199
+ const statuses = statusDocsWithFallbacks(statusIds, statusDocs);
190632
191200
  return { projectType, taskTypes, statuses };
190633
191201
  });
190634
191202
  var isDefaultClassicProjectType = (projectType) => projectType._id === tracker.ids.ClassingProjectType || projectType.classic || normalizeForComparison(projectType.name) === "classic";
@@ -193351,6 +193919,13 @@ var DRAIN_TIMEOUT_MS2 = 3e4;
193351
193919
  var NPM_FETCH_TIMEOUT_MS = 5e3;
193352
193920
  var NPM_PACKAGE_NAME2 = "@firfi/huly-mcp";
193353
193921
  var computeOutputBytes = (response) => response.content.reduce((sum2, c) => sum2 + c.text.length, 0);
193922
+ var withResourceWarnings = (result, warnings) => warnings.length === 0 ? result : {
193923
+ ...result,
193924
+ _meta: {
193925
+ ...result._meta,
193926
+ warnings
193927
+ }
193928
+ };
193354
193929
  var deriveEditMode = (name, args2) => {
193355
193930
  if (name !== "edit_document" || args2 === void 0) return void 0;
193356
193931
  if (typeof args2 !== "object" || args2 === null || Array.isArray(args2)) return void 0;
@@ -193587,12 +194162,15 @@ var createMcpProtocolHandlers = (resolveClients, telemetry, registry2, getHulyCo
193587
194162
  resolveClients,
193588
194163
  (error2) => createResourceClientResolutionError(uri, error2)
193589
194164
  );
194165
+ const diagnosticsScope = await Effect_exports.runPromise(makeDiagnosticsScope);
193590
194166
  const resourceRead = await Effect_exports.runPromiseExit(
193591
194167
  readHulyResource(uri).pipe(
193592
- Effect_exports.provideService(HulyClient, clients.hulyClient)
194168
+ Effect_exports.provideService(HulyClient, clients.hulyClient),
194169
+ Effect_exports.provideService(Diagnostics, diagnosticsScope.service)
193593
194170
  )
193594
194171
  );
193595
- if (Exit_exports.isSuccess(resourceRead)) return resourceRead.value;
194172
+ const warnings = await Effect_exports.runPromise(diagnosticsScope.drainWarnings);
194173
+ if (Exit_exports.isSuccess(resourceRead)) return withResourceWarnings(resourceRead.value, warnings);
193596
194174
  return throwResourceReadError(uri, resourceRead.cause);
193597
194175
  } finally {
193598
194176
  leave();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firfi/huly-mcp",
3
- "version": "0.30.1",
3
+ "version": "0.31.1",
4
4
  "description": "MCP server for Huly integration",
5
5
  "mcpName": "io.github.dearlordylord/huly-mcp",
6
6
  "type": "module",