@firfi/huly-mcp 0.30.0 → 0.31.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.
Files changed (3) hide show
  1. package/README.md +1 -0
  2. package/dist/index.cjs +325 -62
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -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. |
package/dist/index.cjs CHANGED
@@ -155260,6 +155260,10 @@ var HulyClient = class _HulyClient extends Context_exports.Tag("@hulymcp/HulyCli
155260
155260
  (client2) => client2.findOne(_class, query, options),
155261
155261
  "findOne failed"
155262
155262
  ),
155263
+ findAllInModel: (_class, query, options) => withClient(
155264
+ (client2) => Promise.resolve(client2.getModel().findAllSync(_class, query, options)),
155265
+ "findAllInModel failed"
155266
+ ),
155263
155267
  createDoc: (_class, space, attributes, id) => withClient(
155264
155268
  (client2) => client2.createDoc(_class, space, attributes, id),
155265
155269
  "createDoc failed"
@@ -155341,6 +155345,7 @@ var HulyClient = class _HulyClient extends Context_exports.Tag("@hulymcp/HulyCli
155341
155345
  markupUrlConfig: testMarkupUrlConfig,
155342
155346
  workbenchUrlConfig: testWorkbenchUrlConfig,
155343
155347
  findAll: noopFindAll,
155348
+ findAllInModel: noopFindAll,
155344
155349
  findOne: noopFindOne,
155345
155350
  createDoc: notImplemented("createDoc"),
155346
155351
  updateDoc: notImplemented("updateDoc"),
@@ -165740,11 +165745,14 @@ var McpErrorCode = {
165740
165745
  InternalError: -32603,
165741
165746
  ResourceNotFound: -32002
165742
165747
  };
165743
- var createErrorResponse = (text, errorCode, errorTag) => ({
165744
- content: [{ type: "text", text }],
165745
- isError: true,
165746
- _meta: { errorCode, errorTag }
165747
- });
165748
+ var createErrorResponse = (text, errorCode, errorTag, warnings = []) => {
165749
+ const warningContent = warnings.length > 0 ? [{ type: "text", text: encodeJsonText({ warnings }) }] : [];
165750
+ return {
165751
+ content: [{ type: "text", text }, ...warningContent],
165752
+ isError: true,
165753
+ _meta: { errorCode, errorTag }
165754
+ };
165755
+ };
165748
165756
  var INVALID_PARAMS_TAGS = /* @__PURE__ */ new Set([
165749
165757
  "IssueNotFoundError",
165750
165758
  "ProjectNotFoundError",
@@ -165857,13 +165865,13 @@ var INTERNAL_ERROR_PREFIX = {
165857
165865
  HulyConnectionError: "Connection error",
165858
165866
  HulyAuthError: "Authentication error"
165859
165867
  };
165860
- var mapDomainErrorToMcp = (error2) => {
165868
+ var mapDomainErrorToMcp = (error2, warnings = []) => {
165861
165869
  if (INVALID_PARAMS_TAGS.has(error2._tag)) {
165862
- return createErrorResponse(error2.message, McpErrorCode.InvalidParams);
165870
+ return createErrorResponse(error2.message, McpErrorCode.InvalidParams, void 0, warnings);
165863
165871
  }
165864
165872
  const prefix = INTERNAL_ERROR_PREFIX[error2._tag];
165865
165873
  const message = prefix !== void 0 ? `${prefix}: ${error2.message}` : error2.message;
165866
- return createErrorResponse(message, McpErrorCode.InternalError, error2._tag);
165874
+ return createErrorResponse(message, McpErrorCode.InternalError, error2._tag, warnings);
165867
165875
  };
165868
165876
  var formatParseError = (error2) => {
165869
165877
  const issues = ParseResult_exports.ArrayFormatter.formatErrorSync(error2);
@@ -165884,34 +165892,44 @@ var mapParseCauseToMcp = (cause3, toolName) => {
165884
165892
  }
165885
165893
  return createErrorResponse("An unexpected error occurred", McpErrorCode.InternalError);
165886
165894
  };
165887
- var mapDomainCauseToMcp = (cause3) => {
165895
+ var mapDomainCauseToMcp = (cause3, warnings = []) => {
165888
165896
  if (Cause_exports.isFailType(cause3)) {
165889
- return mapDomainErrorToMcp(cause3.error);
165897
+ return mapDomainErrorToMcp(cause3.error, warnings);
165890
165898
  }
165891
165899
  if (Cause_exports.isDieType(cause3)) {
165892
- return createErrorResponse("An unexpected error occurred", McpErrorCode.InternalError, "UnexpectedError");
165900
+ return createErrorResponse("An unexpected error occurred", McpErrorCode.InternalError, "UnexpectedError", warnings);
165893
165901
  }
165894
165902
  const failures3 = Chunk_exports.toArray(Cause_exports.failures(cause3));
165895
165903
  if (failures3.length > 0) {
165896
- return mapDomainErrorToMcp(failures3[0]);
165904
+ return mapDomainErrorToMcp(failures3[0], warnings);
165897
165905
  }
165898
- return createErrorResponse("An unexpected error occurred", McpErrorCode.InternalError);
165906
+ return createErrorResponse("An unexpected error occurred", McpErrorCode.InternalError, void 0, warnings);
165899
165907
  };
165900
165908
  var encodeJsonText = (value3) => {
165901
165909
  const text = JSON.stringify(value3);
165902
165910
  return typeof text === "string" ? text : "null";
165903
165911
  };
165904
- var createSuccessResponse = (result) => ({
165905
- content: [{ type: "text", text: encodeJsonText(result) }],
165906
- structuredContent: {
165912
+ var createSuccessResponse = (result, warnings = []) => ({
165913
+ content: [
165914
+ { type: "text", text: encodeJsonText(result) },
165915
+ ...warnings.length > 0 ? [{ type: "text", text: encodeJsonText({ warnings }) }] : []
165916
+ ],
165917
+ structuredContent: warnings.length > 0 ? {
165918
+ result,
165919
+ warnings
165920
+ } : {
165907
165921
  result
165908
165922
  }
165909
165923
  });
165910
165924
  var createUnknownToolError = (toolName) => createErrorResponse(`Unknown tool: ${toolName}`, McpErrorCode.InvalidParams, "UnknownTool");
165911
165925
  var createInvalidParamsError = (message, errorTag) => createErrorResponse(message, McpErrorCode.InvalidParams, errorTag);
165912
- var toMcpResponse = (response) => {
165913
- const { _meta: _, ...wire } = response;
165914
- return wire;
165926
+ var toMcpResponse = (response) => response.isError === true ? {
165927
+ content: response.content,
165928
+ isError: true
165929
+ } : {
165930
+ content: response.content,
165931
+ ...response.structuredContent === void 0 ? {} : { structuredContent: response.structuredContent },
165932
+ ...response.isError === void 0 ? {} : { isError: response.isError }
165915
165933
  };
165916
165934
 
165917
165935
  // src/mcp/http-2026-dispatcher.ts
@@ -166468,6 +166486,24 @@ var StdioServerTransport = class {
166468
166486
  }
166469
166487
  };
166470
166488
 
166489
+ // src/domain/schemas/tool-warnings.ts
166490
+ var ToolWarningCodeSchema = Schema_exports.Literal("status_metadata_unresolved").annotations({
166491
+ identifier: "ToolWarningCode",
166492
+ title: "ToolWarningCode",
166493
+ description: "Machine-readable code for an agent-visible MCP tool warning."
166494
+ });
166495
+ var StatusMetadataUnresolvedWarningCode = ToolWarningCodeSchema.literals[0];
166496
+ var ToolWarningSchema = Schema_exports.Struct({
166497
+ code: ToolWarningCodeSchema,
166498
+ message: Schema_exports.Trim.pipe(Schema_exports.nonEmptyString()).annotations({
166499
+ description: "LLM-facing explanation of what part of the returned tool payload is degraded and how the agent should interpret it."
166500
+ })
166501
+ }).annotations({
166502
+ identifier: "ToolWarning",
166503
+ title: "ToolWarning",
166504
+ description: "Warning surfaced to an agent when a tool result is intentionally degraded instead of failing."
166505
+ });
166506
+
166471
166507
  // src/domain/schemas/recurrence-primitives.ts
166472
166508
  var MAX_ZERO_BASED_MONTH_INDEX = 11;
166473
166509
  var MAX_MONTH_DAY = 31;
@@ -167252,6 +167288,7 @@ var ProjectTypeDetailSchema = Schema_exports.Struct({
167252
167288
  descriptor: NonEmptyString2,
167253
167289
  classic: Schema_exports.Boolean,
167254
167290
  isDefaultClassic: Schema_exports.Boolean,
167291
+ statusCount: Count,
167255
167292
  taskTypes: Schema_exports.Array(TaskTypeSummarySchema),
167256
167293
  statuses: Schema_exports.Array(IssueStatusSummarySchema),
167257
167294
  statusCategories: Schema_exports.Array(StatusCategorySummarySchema),
@@ -170099,6 +170136,91 @@ var parseUpdatePersonParams = Schema_exports.decodeUnknown(UpdatePersonParamsSch
170099
170136
  var parseDeletePersonParams = Schema_exports.decodeUnknown(DeletePersonParamsSchema);
170100
170137
  var parseListEmployeesParams = Schema_exports.decodeUnknown(ListEmployeesParamsSchema);
170101
170138
 
170139
+ // src/domain/schemas/external-channel-messages.ts
170140
+ var ExternalChannelMessageProviderValues = ["gmail", "telegram"];
170141
+ var ExternalChannelMessageProviderSchema = Schema_exports.Literal(...ExternalChannelMessageProviderValues);
170142
+ var DEFAULT_EXTERNAL_CHANNEL_MESSAGE_LIMIT = DEFAULT_LIMIT;
170143
+ var ListExternalChannelMessagesParamsSchema = Schema_exports.Struct({
170144
+ provider: ExternalChannelMessageProviderSchema.annotations({
170145
+ 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."
170146
+ }),
170147
+ channel: ChannelIdentifier.annotations({
170148
+ description: "External channel name or Huly channel ID locator, such as a Gmail label/inbox name or Telegram chat name/id."
170149
+ }),
170150
+ limit: Schema_exports.optional(LimitParam.annotations({
170151
+ description: `Maximum number of external messages to return (default: ${DEFAULT_EXTERNAL_CHANNEL_MESSAGE_LIMIT}, max: 200).`
170152
+ }))
170153
+ }).annotations({
170154
+ title: "ListExternalChannelMessagesParams",
170155
+ description: "Parameters for listing read-only Gmail or Telegram external channel messages."
170156
+ });
170157
+ var ExternalChannelMessageId = NonEmptyString2.pipe(Schema_exports.brand("ExternalChannelMessageId")).annotations({
170158
+ identifier: "ExternalChannelMessageId",
170159
+ title: "ExternalChannelMessageId",
170160
+ description: "Opaque external provider message ID."
170161
+ });
170162
+ var ExternalChannelMessageSubject = NonEmptyString2.pipe(
170163
+ Schema_exports.brand("ExternalChannelMessageSubject")
170164
+ ).annotations({
170165
+ identifier: "ExternalChannelMessageSubject",
170166
+ title: "ExternalChannelMessageSubject",
170167
+ description: "Non-empty external message subject. Omit the field when the provider has no subject value."
170168
+ });
170169
+ var ExternalChannelMessageSender = NonEmptyString2.pipe(Schema_exports.brand("ExternalChannelMessageSender")).annotations({
170170
+ identifier: "ExternalChannelMessageSender",
170171
+ title: "ExternalChannelMessageSender",
170172
+ description: "Non-empty normalized external message sender label or address."
170173
+ });
170174
+ var ExternalChannelMessageSenderId = NonEmptyString2.pipe(Schema_exports.brand("ExternalChannelMessageSenderId")).annotations({
170175
+ identifier: "ExternalChannelMessageSenderId",
170176
+ title: "ExternalChannelMessageSenderId",
170177
+ description: "Non-empty opaque external provider sender ID."
170178
+ });
170179
+ var ExternalChannelMessageSummarySchema = Schema_exports.Struct({
170180
+ id: ExternalChannelMessageId,
170181
+ subject: Schema_exports.optional(ExternalChannelMessageSubject),
170182
+ bodyPreview: NonEmptyString2,
170183
+ sender: Schema_exports.optional(ExternalChannelMessageSender),
170184
+ senderId: Schema_exports.optional(ExternalChannelMessageSenderId),
170185
+ createdOn: Schema_exports.optional(Timestamp),
170186
+ modifiedOn: Schema_exports.optional(Timestamp),
170187
+ url: Schema_exports.optional(UrlString)
170188
+ }).annotations({
170189
+ title: "ExternalChannelMessageSummary",
170190
+ description: "Normalized read-only summary of one external Gmail or Telegram message."
170191
+ });
170192
+ var ListExternalChannelMessagesSupportedResultSchema = Schema_exports.Struct({
170193
+ supported: Schema_exports.Literal(true),
170194
+ provider: ExternalChannelMessageProviderSchema,
170195
+ channel: ChannelIdentifier,
170196
+ limit: LimitParam,
170197
+ messages: Schema_exports.Array(ExternalChannelMessageSummarySchema)
170198
+ }).annotations({
170199
+ title: "ListExternalChannelMessagesSupportedResult",
170200
+ description: "External channel messages returned from a compatible installed Huly provider SDK/model."
170201
+ });
170202
+ var ListExternalChannelMessagesUnsupportedResultSchema = Schema_exports.Struct({
170203
+ supported: Schema_exports.Literal(false),
170204
+ provider: ExternalChannelMessageProviderSchema,
170205
+ channel: ChannelIdentifier,
170206
+ limit: LimitParam,
170207
+ unsupportedReason: NonEmptyString2,
170208
+ messages: Schema_exports.Tuple()
170209
+ }).annotations({
170210
+ title: "ListExternalChannelMessagesUnsupportedResult",
170211
+ description: "Explicit no-fake-data result when the requested external provider cannot be read in this build."
170212
+ });
170213
+ var ListExternalChannelMessagesResultSchema = Schema_exports.Union(
170214
+ ListExternalChannelMessagesSupportedResultSchema,
170215
+ ListExternalChannelMessagesUnsupportedResultSchema
170216
+ ).annotations({
170217
+ title: "ListExternalChannelMessagesResult",
170218
+ description: "Read-only external channel message listing result."
170219
+ });
170220
+ var listExternalChannelMessagesParamsJsonSchema = JSONSchema_exports.make(ListExternalChannelMessagesParamsSchema);
170221
+ var parseListExternalChannelMessagesParams = Schema_exports.decodeUnknown(ListExternalChannelMessagesParamsSchema);
170222
+ var encodeListExternalChannelMessagesResult = Schema_exports.encodeSync(ListExternalChannelMessagesResultSchema);
170223
+
170102
170224
  // src/domain/schemas/channels.ts
170103
170225
  var ListChannelsParamsBase = Schema_exports.Struct({
170104
170226
  nameSearch: Schema_exports.optional(Schema_exports.String.annotations({
@@ -174502,15 +174624,52 @@ var parseUpdateDriveFileCommentParams = Schema_exports.decodeUnknown(UpdateDrive
174502
174624
  var parseDeleteDriveFileCommentParams = Schema_exports.decodeUnknown(DeleteDriveFileCommentParamsSchema);
174503
174625
  var parseListDriveFileActivityParams = Schema_exports.decodeUnknown(ListDriveFileActivityParamsSchema);
174504
174626
 
174627
+ // src/huly/diagnostics.ts
174628
+ var Diagnostics = class extends Context_exports.Tag("@hulymcp/Diagnostics")() {
174629
+ };
174630
+ var warningLogText = (warning) => `Agent-visible tool warning [${warning.code}]: ${warning.message}`;
174631
+ var makeDiagnosticsScope = Effect_exports.gen(function* () {
174632
+ const warningsRef = yield* Ref_exports.make([]);
174633
+ return {
174634
+ service: {
174635
+ warnAgent: (warning) => Ref_exports.update(warningsRef, (warnings) => [...warnings, warning]).pipe(
174636
+ Effect_exports.zipRight(Effect_exports.logWarning(warningLogText(warning)))
174637
+ ),
174638
+ trail: (message) => Effect_exports.logInfo(`Diagnostic trail: ${message}`)
174639
+ },
174640
+ drainWarnings: Ref_exports.get(warningsRef)
174641
+ };
174642
+ });
174643
+
174505
174644
  // src/version.ts
174506
- var VERSION = true ? "0.30.0" : "0.0.0-dev";
174645
+ var VERSION = true ? "0.31.0" : "0.0.0-dev";
174507
174646
 
174508
174647
  // src/mcp/tool-output-schema.ts
174648
+ var toolWarningCodeEnum = [...ToolWarningCodeSchema.literals];
174509
174649
  var defaultToolOutputSchema = {
174510
174650
  type: "object",
174511
174651
  properties: {
174512
174652
  result: {
174513
174653
  description: "The successful tool result. The same value is also serialized as JSON in the text content for clients that do not read structuredContent."
174654
+ },
174655
+ warnings: {
174656
+ type: "array",
174657
+ description: "Optional agent-visible warnings about degraded result fidelity. Omitted when the server returned the documented happy-path payload.",
174658
+ items: {
174659
+ type: "object",
174660
+ properties: {
174661
+ code: {
174662
+ type: "string",
174663
+ enum: toolWarningCodeEnum
174664
+ },
174665
+ message: {
174666
+ type: "string",
174667
+ minLength: 1
174668
+ }
174669
+ },
174670
+ required: ["code", "message"],
174671
+ additionalProperties: false
174672
+ }
174514
174673
  }
174515
174674
  },
174516
174675
  required: ["result"]
@@ -174616,6 +174775,22 @@ var findActivityMessage = (client, messageId) => findOneOrFail(
174616
174775
  () => new ActivityMessageNotFoundError({ messageId })
174617
174776
  );
174618
174777
 
174778
+ // src/huly/operations/thread-replies-shared.ts
174779
+ var removeThreadReply = (client, reply) => Effect_exports.gen(function* () {
174780
+ const removeCollection = client.removeCollection;
174781
+ if (removeCollection === void 0) {
174782
+ return yield* new HulyError({ message: "Huly client does not support removeCollection" });
174783
+ }
174784
+ yield* removeCollection(
174785
+ chunter.class.ThreadMessage,
174786
+ reply.space,
174787
+ reply._id,
174788
+ reply.attachedTo,
174789
+ reply.attachedToClass,
174790
+ reply.collection
174791
+ );
174792
+ });
174793
+
174619
174794
  // src/huly/operations/activity-messages.ts
174620
174795
  var getActivityMessage = (params) => Effect_exports.gen(function* () {
174621
174796
  const client = yield* HulyClient;
@@ -174732,7 +174907,7 @@ var deleteActivityReply = (params) => Effect_exports.gen(function* () {
174732
174907
  hulyQuery({ _id: toRef(params.replyId) }),
174733
174908
  () => new ActivityMessageNotFoundError({ messageId: params.replyId })
174734
174909
  );
174735
- yield* client.removeDoc(chunter.class.ThreadMessage, reply.space, reply._id);
174910
+ yield* removeThreadReply(client, reply);
174736
174911
  return { replyId: ActivityMessageId.make(reply._id), deleted: true };
174737
174912
  });
174738
174913
 
@@ -176166,7 +176341,7 @@ var findProject = (projectIdentifier) => Effect_exports.gen(function* () {
176166
176341
  );
176167
176342
  return { client, project: project3 };
176168
176343
  });
176169
- var statusCategoryValueFromRef = (category) => category === void 0 ? "unknown" : StatusCategoryEntries.find((entry) => entry.ref === category)?.key ?? "unknown";
176344
+ var statusCategoryValueFromRef = (category) => category === void 0 ? UnknownStatusCategoryValue : StatusCategoryEntries.find((entry) => entry.ref === category)?.key ?? UnknownStatusCategoryValue;
176170
176345
  var workflowStatusFromDoc = (doc) => {
176171
176346
  return {
176172
176347
  _id: doc._id,
@@ -176179,7 +176354,7 @@ var workflowStatusFromRef = (statusRef) => {
176179
176354
  return {
176180
176355
  _id: statusRef,
176181
176356
  name,
176182
- category: "unknown"
176357
+ category: UnknownStatusCategoryValue
176183
176358
  };
176184
176359
  };
176185
176360
  var uniqueStatusRefs = (refs) => refs.reduce(
@@ -176191,6 +176366,51 @@ var uniqueStatusDocs = (statuses) => Array.from(statuses).reduce(
176191
176366
  []
176192
176367
  );
176193
176368
  var uniqueProjectTypeStatusRefs = (statuses) => uniqueStatusRefs(statuses.map((status) => status._id));
176369
+ var missingStatusRefs = (statusRefs, statusDocs) => statusRefs.filter((statusRef) => !statusDocs.some((statusDoc) => statusDoc._id === statusRef));
176370
+ var resolveByStatusRef = (statusRefs, statusDocs, fromDoc, fromRef) => {
176371
+ const statusDocsById = new Map(statusDocs.map((statusDoc) => [statusDoc._id, statusDoc]));
176372
+ return statusRefs.map((statusRef) => {
176373
+ const statusDoc = statusDocsById.get(statusRef);
176374
+ return statusDoc === void 0 ? fromRef(statusRef) : fromDoc(statusDoc);
176375
+ });
176376
+ };
176377
+ var workflowStatusesFromDocsOrRefs = (statusRefs, statusDocs) => resolveByStatusRef(statusRefs, statusDocs, workflowStatusFromDoc, workflowStatusFromRef);
176378
+ var findStatusDocs = (client, statusRefs) => Effect_exports.gen(function* () {
176379
+ const diagnostics = yield* Diagnostics;
176380
+ const remoteResult = yield* Effect_exports.either(
176381
+ client.findAll(
176382
+ core.class.Status,
176383
+ hulyQuery({ _id: { $in: [...statusRefs] } })
176384
+ )
176385
+ );
176386
+ const remoteDocs = remoteResult._tag === "Right" ? uniqueStatusDocs(remoteResult.right) : [];
176387
+ const unresolvedRefs = missingStatusRefs(statusRefs, remoteDocs);
176388
+ if (unresolvedRefs.length === 0) {
176389
+ return remoteDocs;
176390
+ }
176391
+ const modelResult = yield* Effect_exports.either(
176392
+ client.findAllInModel(
176393
+ core.class.Status,
176394
+ hulyQuery({ _id: { $in: unresolvedRefs } })
176395
+ )
176396
+ );
176397
+ const modelDocs = modelResult._tag === "Right" ? uniqueStatusDocs(modelResult.right) : [];
176398
+ const combinedDocs = uniqueStatusDocs([...remoteDocs, ...modelDocs]);
176399
+ const stillUnresolvedRefs = missingStatusRefs(statusRefs, combinedDocs);
176400
+ if (stillUnresolvedRefs.length > 0) {
176401
+ const remoteError = remoteResult._tag === "Left" ? ` Remote error: ${remoteResult.left.message}` : "";
176402
+ const modelError = modelResult._tag === "Left" ? ` Model error: ${modelResult.left.message}` : "";
176403
+ yield* diagnostics.warnAgent({
176404
+ code: StatusMetadataUnresolvedWarningCode,
176405
+ 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}`
176406
+ });
176407
+ } else if (remoteResult._tag === "Left") {
176408
+ yield* diagnostics.trail(
176409
+ `Server status metadata lookup failed, but the local Huly model resolved all requested workflow statuses. Remote error: ${remoteResult.left.message}`
176410
+ );
176411
+ }
176412
+ return combinedDocs;
176413
+ });
176194
176414
  var findProjectWithStatuses = (projectIdentifier) => Effect_exports.gen(function* () {
176195
176415
  const client = yield* HulyClient;
176196
176416
  const project3 = yield* findOneOrFail(
@@ -176206,19 +176426,8 @@ var findProjectWithStatuses = (projectIdentifier) => Effect_exports.gen(function
176206
176426
  if (statusRefs.length === 0) {
176207
176427
  return [];
176208
176428
  }
176209
- const statusDocsResult = yield* Effect_exports.either(
176210
- client.findAll(
176211
- core.class.Status,
176212
- hulyQuery({ _id: { $in: statusRefs } })
176213
- )
176214
- );
176215
- if (statusDocsResult._tag === "Right") {
176216
- return uniqueStatusDocs(statusDocsResult.right).map(workflowStatusFromDoc);
176217
- }
176218
- yield* Effect_exports.logWarning(
176219
- `Status query failed for project ${projectIdentifier}, using fallback. statusCategory filtering is unavailable until Huly returns status metadata. Error: ${statusDocsResult.left.message}`
176220
- );
176221
- return statusRefs.map(workflowStatusFromRef);
176429
+ const statusDocs = yield* findStatusDocs(client, statusRefs);
176430
+ return workflowStatusesFromDocsOrRefs(statusRefs, statusDocs);
176222
176431
  }) : [];
176223
176432
  const defaultStatusId = project3.defaultIssueStatus || statuses[0]?._id;
176224
176433
  return { client, defaultStatusId, project: project3, projectType, statuses };
@@ -176582,19 +176791,27 @@ var createHandler = (toolName, provide4, parse5, operation, encode8) => async (a
176582
176791
  if (Exit_exports.isFailure(parseResult)) {
176583
176792
  return mapParseCauseToMcp(parseResult.cause, toolName);
176584
176793
  }
176585
- const provided = provide4({ hulyClient, storageClient, workspaceClient })(operation(parseResult.value));
176794
+ const diagnosticsScope = await Effect_exports.runPromise(makeDiagnosticsScope);
176795
+ const provided = provide4({
176796
+ hulyClient,
176797
+ storageClient,
176798
+ workspaceClient
176799
+ })(operation(parseResult.value));
176586
176800
  if (Either_exports.isLeft(provided)) {
176587
176801
  return provided.left;
176588
176802
  }
176589
- const operationResult = await Effect_exports.runPromiseExit(provided.right);
176803
+ const operationResult = await Effect_exports.runPromiseExit(
176804
+ provided.right.pipe(Effect_exports.provideService(Diagnostics, diagnosticsScope.service))
176805
+ );
176806
+ const warnings = await Effect_exports.runPromise(diagnosticsScope.drainWarnings);
176590
176807
  if (Exit_exports.isFailure(operationResult)) {
176591
- return mapDomainCauseToMcp(operationResult.cause);
176808
+ return mapDomainCauseToMcp(operationResult.cause, warnings);
176592
176809
  }
176593
176810
  try {
176594
176811
  const output = encode8 !== void 0 ? encode8(operationResult.value) : operationResult.value;
176595
- return createSuccessResponse(output);
176812
+ return createSuccessResponse(output, warnings);
176596
176813
  } catch {
176597
- return mapDomainErrorToMcp(new HulyError({ message: `Tool ${toolName} produced invalid output` }));
176814
+ return mapDomainErrorToMcp(new HulyError({ message: `Tool ${toolName} produced invalid output` }), warnings);
176598
176815
  }
176599
176816
  };
176600
176817
  var createToolHandler = (toolName, parse5, operation) => createHandler(toolName, provideHulyClient, parse5, operation);
@@ -179497,6 +179714,19 @@ var createDirectMessage = (params) => Effect_exports.gen(function* () {
179497
179714
  return { id: ChannelId.make(dmId), created: true };
179498
179715
  });
179499
179716
 
179717
+ // src/huly/operations/external-channel-messages.ts
179718
+ 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";
179719
+ var listExternalChannelMessages = (params) => Effect_exports.sync(
179720
+ () => encodeListExternalChannelMessagesResult({
179721
+ supported: false,
179722
+ provider: params.provider,
179723
+ channel: params.channel,
179724
+ limit: params.limit ?? DEFAULT_EXTERNAL_CHANNEL_MESSAGE_LIMIT,
179725
+ unsupportedReason: EXTERNAL_CHANNEL_PACKAGE_INCOMPATIBLE_REASON,
179726
+ messages: []
179727
+ })
179728
+ );
179729
+
179500
179730
  // src/huly/operations/threads.ts
179501
179731
  var import_core31 = __toESM(require_lib4(), 1);
179502
179732
  var findReply = (client, channel, message, replyId) => Effect_exports.gen(function* () {
@@ -179601,11 +179831,7 @@ var updateThreadReply = (params) => Effect_exports.gen(function* () {
179601
179831
  var deleteThreadReply = (params) => Effect_exports.gen(function* () {
179602
179832
  const { channel, client, message } = yield* findChannelMessage(params);
179603
179833
  const reply = yield* findReply(client, channel, message, params.replyId);
179604
- yield* client.removeDoc(
179605
- chunter.class.ThreadMessage,
179606
- channel._id,
179607
- reply._id
179608
- );
179834
+ yield* removeThreadReply(client, reply);
179609
179835
  return { id: ThreadReplyId.make(reply._id), deleted: true };
179610
179836
  });
179611
179837
 
@@ -179794,6 +180020,17 @@ var channelTools = [
179794
180020
  listChannelMessages
179795
180021
  )
179796
180022
  },
180023
+ {
180024
+ name: "list_external_channel_messages",
180025
+ 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.",
180026
+ category: CATEGORY6,
180027
+ inputSchema: listExternalChannelMessagesParamsJsonSchema,
180028
+ handler: createToolHandler(
180029
+ "list_external_channel_messages",
180030
+ parseListExternalChannelMessagesParams,
180031
+ listExternalChannelMessages
180032
+ )
180033
+ },
179797
180034
  {
179798
180035
  name: "send_channel_message",
179799
180036
  description: "Send a message to a Huly channel. Message body supports markdown formatting.",
@@ -186682,6 +186919,18 @@ var labelTools = [
186682
186919
  // src/huly/operations/leads.ts
186683
186920
  var import_core50 = __toESM(require_lib4(), 1);
186684
186921
  var funnelAsSpace = (funnel) => toRef(funnel._id);
186922
+ var statusInfosWithFallbacks = (statusRefs, statusDocs) => resolveByStatusRef(
186923
+ statusRefs,
186924
+ statusDocs,
186925
+ (statusDoc) => ({
186926
+ _id: statusDoc._id,
186927
+ name: statusDoc.name
186928
+ }),
186929
+ (statusRef) => ({
186930
+ _id: statusRef,
186931
+ name: workflowStatusFromRef(statusRef).name
186932
+ })
186933
+ );
186685
186934
  var markupBlobRefAsMarkupRef = (value3) => value3;
186686
186935
  var normalizeLeadIdentifier = (identifier2) => {
186687
186936
  const match16 = /^(?:LEAD-)?(\d+)$/i.exec(identifier2.trim());
@@ -186730,7 +186979,7 @@ var getFunnelStatuses = (client, funnel) => Effect_exports.gen(function* () {
186730
186979
  })
186731
186980
  );
186732
186981
  }
186733
- const statusRefs = projectType.statuses.map((status) => status._id);
186982
+ const statusRefs = uniqueStatusRefs(projectType.statuses.map((status) => status._id));
186734
186983
  if (statusRefs.length === 0) {
186735
186984
  return yield* Effect_exports.fail(
186736
186985
  new HulyConnectionError({
@@ -186738,14 +186987,8 @@ var getFunnelStatuses = (client, funnel) => Effect_exports.gen(function* () {
186738
186987
  })
186739
186988
  );
186740
186989
  }
186741
- const statusDocs = yield* client.findAll(
186742
- core.class.Status,
186743
- { _id: { $in: [...statusRefs] } }
186744
- );
186745
- return statusDocs.map((doc) => ({
186746
- _id: doc._id,
186747
- name: doc.name
186748
- }));
186990
+ const statusDocs = yield* findStatusDocs(client, statusRefs);
186991
+ return statusInfosWithFallbacks(statusRefs, statusDocs);
186749
186992
  });
186750
186993
  var resolveStatusName2 = (statuses, statusId) => {
186751
186994
  const statusDoc = statuses.find((status) => status._id === statusId);
@@ -190579,7 +190822,7 @@ var STATUS_CATEGORIES = Object.values(STATUS_CATEGORY_BY_SDK_KEY).map((entry) =>
190579
190822
  name: entry.name
190580
190823
  }));
190581
190824
  var WORKFLOW_WARNING = "This changes workspace-level tracker configuration for every project using this project type.";
190582
- var toCategoryValue = (category) => category === void 0 ? "unknown" : REF_TO_CATEGORY.get(category) ?? "unknown";
190825
+ var toCategoryValue = (category) => category === void 0 ? UnknownStatusCategoryValue : REF_TO_CATEGORY.get(category) ?? UnknownStatusCategoryValue;
190583
190826
  var encodeOrConnectionError2 = (schema, value3, operation) => Schema_exports.encode(schema)(value3).pipe(
190584
190827
  Effect_exports.as(value3),
190585
190828
  Effect_exports.mapError(
@@ -190595,9 +190838,17 @@ var uniqueProjectStatuses = (statuses) => statuses.reduce(
190595
190838
  (unique, status) => unique.some((existing) => sameProjectStatus(existing, status)) ? unique : [...unique, status],
190596
190839
  []
190597
190840
  );
190598
- var getStatusDocs = (client, statusIds) => statusIds.length === 0 ? Effect_exports.succeed([]) : client.findAll(core.class.Status, hulyQuery({ _id: { $in: [...statusIds] } })).pipe(
190599
- Effect_exports.map(uniqueStatusDocs)
190600
- );
190841
+ var getStatusDocs = (client, statusIds) => statusIds.length === 0 ? Effect_exports.succeed([]) : findStatusDocs(client, statusIds);
190842
+ var fallbackStatusDoc = (statusId) => ({
190843
+ _id: statusId,
190844
+ _class: core.class.Status,
190845
+ space: core.space.Model,
190846
+ modifiedOn: 0,
190847
+ modifiedBy: core.account.System,
190848
+ ofAttribute: tracker.attribute.IssueStatus,
190849
+ name: workflowStatusFromRef(statusId).name
190850
+ });
190851
+ var statusDocsWithFallbacks = (statusIds, statusDocs) => resolveByStatusRef(statusIds, statusDocs, (statusDoc) => statusDoc, fallbackStatusDoc);
190601
190852
  var getTaskTypes = (client, taskTypeIds) => taskTypeIds.length === 0 ? Effect_exports.succeed([]) : client.findAll(task.class.TaskType, hulyQuery({ _id: { $in: [...taskTypeIds] } })).pipe(
190602
190853
  Effect_exports.map((result) => [...result])
190603
190854
  );
@@ -190616,7 +190867,9 @@ var getRecoverableStatusesByName = (client, name) => client.findAll(core.class.S
190616
190867
  );
190617
190868
  var loadWorkflowData = (client, projectType) => Effect_exports.gen(function* () {
190618
190869
  const taskTypes = yield* getTaskTypes(client, projectType.tasks);
190619
- const statuses = yield* getStatusDocs(client, uniqueStatusIds(projectType));
190870
+ const statusIds = uniqueStatusIds(projectType);
190871
+ const statusDocs = yield* getStatusDocs(client, statusIds);
190872
+ const statuses = statusDocsWithFallbacks(statusIds, statusDocs);
190620
190873
  return { projectType, taskTypes, statuses };
190621
190874
  });
190622
190875
  var isDefaultClassicProjectType = (projectType) => projectType._id === tracker.ids.ClassingProjectType || projectType.classic || normalizeForComparison(projectType.name) === "classic";
@@ -193339,6 +193592,13 @@ var DRAIN_TIMEOUT_MS2 = 3e4;
193339
193592
  var NPM_FETCH_TIMEOUT_MS = 5e3;
193340
193593
  var NPM_PACKAGE_NAME2 = "@firfi/huly-mcp";
193341
193594
  var computeOutputBytes = (response) => response.content.reduce((sum2, c) => sum2 + c.text.length, 0);
193595
+ var withResourceWarnings = (result, warnings) => warnings.length === 0 ? result : {
193596
+ ...result,
193597
+ _meta: {
193598
+ ...result._meta,
193599
+ warnings
193600
+ }
193601
+ };
193342
193602
  var deriveEditMode = (name, args2) => {
193343
193603
  if (name !== "edit_document" || args2 === void 0) return void 0;
193344
193604
  if (typeof args2 !== "object" || args2 === null || Array.isArray(args2)) return void 0;
@@ -193575,12 +193835,15 @@ var createMcpProtocolHandlers = (resolveClients, telemetry, registry2, getHulyCo
193575
193835
  resolveClients,
193576
193836
  (error2) => createResourceClientResolutionError(uri, error2)
193577
193837
  );
193838
+ const diagnosticsScope = await Effect_exports.runPromise(makeDiagnosticsScope);
193578
193839
  const resourceRead = await Effect_exports.runPromiseExit(
193579
193840
  readHulyResource(uri).pipe(
193580
- Effect_exports.provideService(HulyClient, clients.hulyClient)
193841
+ Effect_exports.provideService(HulyClient, clients.hulyClient),
193842
+ Effect_exports.provideService(Diagnostics, diagnosticsScope.service)
193581
193843
  )
193582
193844
  );
193583
- if (Exit_exports.isSuccess(resourceRead)) return resourceRead.value;
193845
+ const warnings = await Effect_exports.runPromise(diagnosticsScope.drainWarnings);
193846
+ if (Exit_exports.isSuccess(resourceRead)) return withResourceWarnings(resourceRead.value, warnings);
193584
193847
  return throwResourceReadError(uri, resourceRead.cause);
193585
193848
  } finally {
193586
193849
  leave();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firfi/huly-mcp",
3
- "version": "0.30.0",
3
+ "version": "0.31.0",
4
4
  "description": "MCP server for Huly integration",
5
5
  "mcpName": "io.github.dearlordylord/huly-mcp",
6
6
  "type": "module",