@firfi/huly-mcp 0.20.1 → 0.21.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 +2 -2
  2. package/dist/index.cjs +236 -84
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -351,8 +351,8 @@ Resource roadmap:
351
351
  | `delete_teamspace` | Permanently delete a Huly document teamspace. This action cannot be undone. |
352
352
  | `list_documents` | List documents in a Huly teamspace. Returns documents sorted by modification date (newest first). Each result includes a 'url' field pointing to the document in the Huly web app. Supports searching by title substring (titleSearch) and content (contentSearch). |
353
353
  | `get_document` | Retrieve full details for a Huly document including markdown content and a 'url' field pointing to the document in the Huly web app. Use this to view document content and metadata. |
354
- | `create_document` | Create a new document in a Huly teamspace. Content supports full markdown including native Mermaid diagrams (```mermaid blocks render interactively in Huly UI). Optionally pass parent as a document title or ID to create a nested child document; invalid parents fail instead of silently creating a top-level document. Returns the created document id and a 'url' field pointing to the document in the Huly web app. Use link_document_to_issue to associate the document with a tracker issue. |
355
- | `edit_document` | Edit an existing Huly document. Two content modes (mutually exclusive): (1) 'content' for full replace, (2) 'old_text' + 'new_text' for targeted search-and-replace. Multiple matches error unless replace_all is true. Empty new_text deletes matched text. Also supports renaming via 'title'. Content supports full markdown including native Mermaid diagrams. Returns a 'url' field pointing to the document in the Huly web app. |
354
+ | `create_document` | Create a new document in a Huly teamspace. Content is markdown and supports native Mermaid diagrams (```mermaid blocks render interactively in Huly UI). Use markdown links to current-workspace Huly browse URLs for native references; Huly browse links returned in get_document content round-trip as native references. The URL identifies the object; link text is display text; plain issue keys stay text. External URLs stay normal markdown links. Optionally pass parent as a document title or ID to create a nested child document; invalid parents fail instead of silently creating a top-level document. Returns the created document id and a 'url' field pointing to the document in the Huly web app. Use link_document_to_issue only if you also want an issue-document association. |
355
+ | `edit_document` | Edit an existing Huly document. You may rename with title and/or edit the body. Body editing has two mutually exclusive modes: (1) content replaces the entire markdown body, (2) old_text + new_text performs exact targeted search-and-replace. Use markdown links to current-workspace Huly browse URLs for native references; Huly browse links returned in get_document content round-trip as native references. The URL identifies the object; link text is display text; plain issue keys stay text. External URLs stay normal markdown links. For targeted replace, multiple matches error unless replace_all is true; empty new_text deletes matched text. Content supports native Mermaid diagrams. Returns a 'url' field pointing to the document in the Huly web app. |
356
356
  | `list_inline_comments` | List inline comment threads from a Huly document. Extracts comments embedded in document content as ProseMirror marks. Each comment includes the highlighted text and thread ID. Set includeReplies=true to also fetch thread reply messages with sender names. |
357
357
  | `delete_document` | Permanently delete a Huly document. This action cannot be undone. |
358
358
 
package/dist/index.cjs CHANGED
@@ -62706,41 +62706,41 @@ var require_model = __commonJS({
62706
62706
  var model_exports = {};
62707
62707
  __export2(model_exports, {
62708
62708
  MarkupMarkType: () => MarkupMarkType,
62709
- MarkupNodeType: () => MarkupNodeType,
62709
+ MarkupNodeType: () => MarkupNodeType2,
62710
62710
  emptyMarkupNode: () => emptyMarkupNode
62711
62711
  });
62712
62712
  module2.exports = __toCommonJS2(model_exports);
62713
- var MarkupNodeType = /* @__PURE__ */ ((MarkupNodeType2) => {
62714
- MarkupNodeType2["doc"] = "doc";
62715
- MarkupNodeType2["paragraph"] = "paragraph";
62716
- MarkupNodeType2["blockquote"] = "blockquote";
62717
- MarkupNodeType2["horizontal_rule"] = "horizontalRule";
62718
- MarkupNodeType2["heading"] = "heading";
62719
- MarkupNodeType2["code_block"] = "codeBlock";
62720
- MarkupNodeType2["text"] = "text";
62721
- MarkupNodeType2["image"] = "image";
62722
- MarkupNodeType2["file"] = "file";
62723
- MarkupNodeType2["reference"] = "reference";
62724
- MarkupNodeType2["emoji"] = "emoji";
62725
- MarkupNodeType2["hard_break"] = "hardBreak";
62726
- MarkupNodeType2["ordered_list"] = "orderedList";
62727
- MarkupNodeType2["bullet_list"] = "bulletList";
62728
- MarkupNodeType2["list_item"] = "listItem";
62729
- MarkupNodeType2["taskList"] = "taskList";
62730
- MarkupNodeType2["taskItem"] = "taskItem";
62731
- MarkupNodeType2["todoList"] = "todoList";
62732
- MarkupNodeType2["todoItem"] = "todoItem";
62733
- MarkupNodeType2["subLink"] = "subLink";
62734
- MarkupNodeType2["table"] = "table";
62735
- MarkupNodeType2["table_row"] = "tableRow";
62736
- MarkupNodeType2["table_cell"] = "tableCell";
62737
- MarkupNodeType2["table_header"] = "tableHeader";
62738
- MarkupNodeType2["mermaid"] = "mermaid";
62739
- MarkupNodeType2["comment"] = "comment";
62740
- MarkupNodeType2["markdown"] = "markdown";
62741
- MarkupNodeType2["embed"] = "embed";
62742
- return MarkupNodeType2;
62743
- })(MarkupNodeType || {});
62713
+ var MarkupNodeType2 = /* @__PURE__ */ ((MarkupNodeType22) => {
62714
+ MarkupNodeType22["doc"] = "doc";
62715
+ MarkupNodeType22["paragraph"] = "paragraph";
62716
+ MarkupNodeType22["blockquote"] = "blockquote";
62717
+ MarkupNodeType22["horizontal_rule"] = "horizontalRule";
62718
+ MarkupNodeType22["heading"] = "heading";
62719
+ MarkupNodeType22["code_block"] = "codeBlock";
62720
+ MarkupNodeType22["text"] = "text";
62721
+ MarkupNodeType22["image"] = "image";
62722
+ MarkupNodeType22["file"] = "file";
62723
+ MarkupNodeType22["reference"] = "reference";
62724
+ MarkupNodeType22["emoji"] = "emoji";
62725
+ MarkupNodeType22["hard_break"] = "hardBreak";
62726
+ MarkupNodeType22["ordered_list"] = "orderedList";
62727
+ MarkupNodeType22["bullet_list"] = "bulletList";
62728
+ MarkupNodeType22["list_item"] = "listItem";
62729
+ MarkupNodeType22["taskList"] = "taskList";
62730
+ MarkupNodeType22["taskItem"] = "taskItem";
62731
+ MarkupNodeType22["todoList"] = "todoList";
62732
+ MarkupNodeType22["todoItem"] = "todoItem";
62733
+ MarkupNodeType22["subLink"] = "subLink";
62734
+ MarkupNodeType22["table"] = "table";
62735
+ MarkupNodeType22["table_row"] = "tableRow";
62736
+ MarkupNodeType22["table_cell"] = "tableCell";
62737
+ MarkupNodeType22["table_header"] = "tableHeader";
62738
+ MarkupNodeType22["mermaid"] = "mermaid";
62739
+ MarkupNodeType22["comment"] = "comment";
62740
+ MarkupNodeType22["markdown"] = "markdown";
62741
+ MarkupNodeType22["embed"] = "embed";
62742
+ return MarkupNodeType22;
62743
+ })(MarkupNodeType2 || {});
62744
62744
  var MarkupMarkType = /* @__PURE__ */ ((MarkupMarkType2) => {
62745
62745
  MarkupMarkType2["link"] = "link";
62746
62746
  MarkupMarkType2["em"] = "italic";
@@ -81771,7 +81771,7 @@ var require_connection2 = __commonJS({
81771
81771
  });
81772
81772
  module2.exports = __toCommonJS2(connection_exports);
81773
81773
  var import_analytics = require_lib2();
81774
- var import_client38 = __toESM2(require_lib6());
81774
+ var import_client39 = __toESM2(require_lib6());
81775
81775
  var import_core57 = __toESM2(require_lib4());
81776
81776
  var import_platform4 = __toESM2(require_lib());
81777
81777
  var import_rpc = require_lib18();
@@ -81882,7 +81882,7 @@ var require_connection2 = __commonJS({
81882
81882
  }
81883
81883
  if (!this.closed) {
81884
81884
  void this.sendRequest({
81885
- method: import_client38.pingConst,
81885
+ method: import_client39.pingConst,
81886
81886
  params: [],
81887
81887
  once: true,
81888
81888
  handleResult: /* @__PURE__ */ __name(async (result) => {
@@ -81915,7 +81915,7 @@ var require_connection2 = __commonJS({
81915
81915
  }
81916
81916
  }
81917
81917
  isConnected() {
81918
- return this.websocket != null && this.websocket.readyState === import_client38.ClientSocketReadyState.OPEN && this.helloReceived;
81918
+ return this.websocket != null && this.websocket.readyState === import_client39.ClientSocketReadyState.OPEN && this.helloReceived;
81919
81919
  }
81920
81920
  delay = 0;
81921
81921
  onConnectHandlers = [];
@@ -82055,8 +82055,8 @@ var require_connection2 = __commonJS({
82055
82055
  }
82056
82056
  return;
82057
82057
  }
82058
- if (resp.result === import_client38.pingConst) {
82059
- void this.sendRequest({ method: import_client38.pingConst, params: [] }).catch((err) => {
82058
+ if (resp.result === import_client39.pingConst) {
82059
+ void this.sendRequest({ method: import_client39.pingConst, params: [] }).catch((err) => {
82060
82060
  this.ctx.error("failed to send ping", { err });
82061
82061
  });
82062
82062
  return;
@@ -82148,15 +82148,15 @@ var require_connection2 = __commonJS({
82148
82148
  }
82149
82149
  }
82150
82150
  checkArrayBufferPing(data) {
82151
- if (data.byteLength === import_client38.pingConst.length || data.byteLength === import_client38.pongConst.length) {
82151
+ if (data.byteLength === import_client39.pingConst.length || data.byteLength === import_client39.pongConst.length) {
82152
82152
  const text = new TextDecoder().decode(data);
82153
- if (text === import_client38.pingConst) {
82154
- void this.sendRequest({ method: import_client38.pingConst, params: [] }).catch((err) => {
82153
+ if (text === import_client39.pingConst) {
82154
+ void this.sendRequest({ method: import_client39.pingConst, params: [] }).catch((err) => {
82155
82155
  this.ctx.error("failed to send ping", { err });
82156
82156
  });
82157
82157
  return true;
82158
82158
  }
82159
- if (text === import_client38.pongConst) {
82159
+ if (text === import_client39.pongConst) {
82160
82160
  this.pingResponse = Date.now();
82161
82161
  return true;
82162
82162
  }
@@ -82166,7 +82166,7 @@ var require_connection2 = __commonJS({
82166
82166
  openConnection(ctx, socketId) {
82167
82167
  this.binaryMode = false;
82168
82168
  this.helloReceived = false;
82169
- const clientSocketFactory = this.opt?.socketFactory ?? (0, import_platform4.getMetadata)(import_client38.default.metadata.ClientSocketFactory) ?? ((url4) => {
82169
+ const clientSocketFactory = this.opt?.socketFactory ?? (0, import_platform4.getMetadata)(import_client39.default.metadata.ClientSocketFactory) ?? ((url4) => {
82170
82170
  const s = new WebSocket(url4);
82171
82171
  return s;
82172
82172
  });
@@ -82201,12 +82201,12 @@ var require_connection2 = __commonJS({
82201
82201
  if (this.websocket !== wsocket) {
82202
82202
  return;
82203
82203
  }
82204
- if (event.data === import_client38.pongConst) {
82204
+ if (event.data === import_client39.pongConst) {
82205
82205
  this.pingResponse = Date.now();
82206
82206
  return;
82207
82207
  }
82208
- if (event.data === import_client38.pingConst) {
82209
- void this.sendRequest({ method: import_client38.pingConst, params: [] }).catch((err) => {
82208
+ if (event.data === import_client39.pingConst) {
82209
+ void this.sendRequest({ method: import_client39.pingConst, params: [] }).catch((err) => {
82210
82210
  this.ctx.error("failed to send ping", { err });
82211
82211
  });
82212
82212
  return;
@@ -82271,8 +82271,8 @@ var require_connection2 = __commonJS({
82271
82271
  if (this.websocket !== wsocket) {
82272
82272
  return;
82273
82273
  }
82274
- const useBinary = this.opt?.useBinaryProtocol ?? (0, import_platform4.getMetadata)(import_client38.default.metadata.UseBinaryProtocol) ?? true;
82275
- this.compressionMode = this.opt?.useProtocolCompression ?? (0, import_platform4.getMetadata)(import_client38.default.metadata.UseProtocolCompression) ?? false;
82274
+ const useBinary = this.opt?.useBinaryProtocol ?? (0, import_platform4.getMetadata)(import_client39.default.metadata.UseBinaryProtocol) ?? true;
82275
+ this.compressionMode = this.opt?.useProtocolCompression ?? (0, import_platform4.getMetadata)(import_client39.default.metadata.UseProtocolCompression) ?? false;
82276
82276
  const helloRequest = {
82277
82277
  method: "hello",
82278
82278
  params: [],
@@ -82318,13 +82318,13 @@ var require_connection2 = __commonJS({
82318
82318
  if (w instanceof Promise) {
82319
82319
  await w;
82320
82320
  }
82321
- if (data.method !== import_client38.pingConst) {
82321
+ if (data.method !== import_client39.pingConst) {
82322
82322
  this.requests.set(id, promise4);
82323
82323
  }
82324
82324
  promise4.sendData = () => {
82325
- if (this.websocket?.readyState === import_client38.ClientSocketReadyState.OPEN) {
82325
+ if (this.websocket?.readyState === import_client39.ClientSocketReadyState.OPEN) {
82326
82326
  promise4.startTime = Date.now();
82327
- if (data.method !== import_client38.pingConst) {
82327
+ if (data.method !== import_client39.pingConst) {
82328
82328
  const dta = this.rpcHandler.serialize(
82329
82329
  {
82330
82330
  method: data.method,
@@ -82337,7 +82337,7 @@ var require_connection2 = __commonJS({
82337
82337
  );
82338
82338
  this.websocket?.send(dta);
82339
82339
  } else {
82340
- this.websocket?.send(import_client38.pingConst);
82340
+ this.websocket?.send(import_client39.pingConst);
82341
82341
  }
82342
82342
  }
82343
82343
  };
@@ -82351,7 +82351,7 @@ var require_connection2 = __commonJS({
82351
82351
  };
82352
82352
  }
82353
82353
  promise4.sendData();
82354
- if (data.method !== import_client38.pingConst) {
82354
+ if (data.method !== import_client39.pingConst) {
82355
82355
  return await promise4.promise;
82356
82356
  }
82357
82357
  },
@@ -82517,7 +82517,7 @@ var require_lib19 = __commonJS({
82517
82517
  default: () => index_default
82518
82518
  });
82519
82519
  module2.exports = __toCommonJS2(index_exports2);
82520
- var import_client38 = __toESM2(require_lib6());
82520
+ var import_client39 = __toESM2(require_lib6());
82521
82521
  var import_core57 = __toESM2(require_lib4());
82522
82522
  var import_platform4 = __toESM2(require_lib());
82523
82523
  var import_connection = require_connection2();
@@ -82561,8 +82561,8 @@ var require_lib19 = __commonJS({
82561
82561
  return {
82562
82562
  function: {
82563
82563
  GetClient: /* @__PURE__ */ __name(async (token, endpoint, opt) => {
82564
- const filterModel = (0, import_platform4.getMetadata)(import_client38.default.metadata.FilterModel) ?? "none";
82565
- const extraFilter = (0, import_platform4.getMetadata)(import_client38.default.metadata.ExtraFilter) ?? [];
82564
+ const filterModel = (0, import_platform4.getMetadata)(import_client39.default.metadata.FilterModel) ?? "none";
82565
+ const extraFilter = (0, import_platform4.getMetadata)(import_client39.default.metadata.ExtraFilter) ?? [];
82566
82566
  const handler = /* @__PURE__ */ __name(async (handler2) => {
82567
82567
  const url4 = (0, import_core57.concatLink)(endpoint, `/${token}`);
82568
82568
  const upgradeHandler = /* @__PURE__ */ __name((...txes) => {
@@ -82590,7 +82590,7 @@ var require_lib19 = __commonJS({
82590
82590
  throw new Error("Workspace or account not found in token");
82591
82591
  }
82592
82592
  const newOpt = { ...opt };
82593
- const connectTimeout = opt?.connectionTimeout ?? (0, import_platform4.getMetadata)(import_client38.default.metadata.ConnectionTimeout);
82593
+ const connectTimeout = opt?.connectionTimeout ?? (0, import_platform4.getMetadata)(import_client39.default.metadata.ConnectionTimeout);
82594
82594
  let connectPromise;
82595
82595
  if ((connectTimeout ?? 0) > 0) {
82596
82596
  connectPromise = new Promise((resolve2, reject) => {
@@ -82642,7 +82642,7 @@ var require_lib19 = __commonJS({
82642
82642
  function returnUITxes(txes, extraFilter) {
82643
82643
  const configs = /* @__PURE__ */ new Map();
82644
82644
  (0, import_core57.fillConfiguration)(txes, configs);
82645
- const allowedPlugins = [...(0, import_platform4.getPlugins)(), ...(0, import_platform4.getMetadata)(import_client38.default.metadata.ExtraPlugins) ?? []];
82645
+ const allowedPlugins = [...(0, import_platform4.getPlugins)(), ...(0, import_platform4.getMetadata)(import_client39.default.metadata.ExtraPlugins) ?? []];
82646
82646
  const excludedPlugins = Array.from(configs.values()).filter(
82647
82647
  (it) => !it.enabled || !allowedPlugins.includes(it.pluginId) || extraFilter.includes(it.pluginId)
82648
82648
  );
@@ -82690,7 +82690,7 @@ var require_lib19 = __commonJS({
82690
82690
  }
82691
82691
  __name(returnClientTxes, "returnClientTxes");
82692
82692
  function createModelPersistence(workspace) {
82693
- const overrideStore = (0, import_platform4.getMetadata)(import_client38.default.metadata.OverridePersistenceStore);
82693
+ const overrideStore = (0, import_platform4.getMetadata)(import_client39.default.metadata.OverridePersistenceStore);
82694
82694
  if (overrideStore !== void 0) {
82695
82695
  return overrideStore;
82696
82696
  }
@@ -82787,11 +82787,11 @@ var require_client6 = __commonJS({
82787
82787
  });
82788
82788
  module2.exports = __toCommonJS2(client_exports);
82789
82789
  var import_account_client2 = require_lib5();
82790
- var import_client38 = __toESM2(require_lib6());
82790
+ var import_client39 = __toESM2(require_lib6());
82791
82791
  var import_core57 = require_lib4();
82792
82792
  var import_platform4 = require_lib();
82793
82793
  var import_config9 = require_config();
82794
- var import_markup11 = require_markup();
82794
+ var import_markup12 = require_markup();
82795
82795
  var import_utils14 = require_utils9();
82796
82796
  async function connect(url4, options) {
82797
82797
  const config3 = await (0, import_config9.loadServerConfig)(url4);
@@ -82813,9 +82813,9 @@ var require_client6 = __commonJS({
82813
82813
  }
82814
82814
  __name(connect, "connect");
82815
82815
  async function createClient(url4, endpoint, token, workspaceUuid, account, config3, options) {
82816
- (0, import_platform4.addLocation)(import_client38.clientId, () => Promise.resolve().then(() => __toESM(require_lib19())));
82816
+ (0, import_platform4.addLocation)(import_client39.clientId, () => Promise.resolve().then(() => __toESM(require_lib19())));
82817
82817
  const { socketFactory, connectionTimeout } = options;
82818
- const clientFactory = await (0, import_platform4.getResource)(import_client38.default.function.GetClient);
82818
+ const clientFactory = await (0, import_platform4.getResource)(import_client39.default.function.GetClient);
82819
82819
  const connection = await clientFactory(token, endpoint, {
82820
82820
  socketFactory,
82821
82821
  connectionTimeout
@@ -82832,7 +82832,7 @@ var require_client6 = __commonJS({
82832
82832
  this.connection = connection;
82833
82833
  this.account = account;
82834
82834
  this.client = new import_core57.TxOperations(connection, account.primarySocialId);
82835
- this.markup = (0, import_markup11.createMarkupOperations)(url4, workspace, token, config3);
82835
+ this.markup = (0, import_markup12.createMarkupOperations)(url4, workspace, token, config3);
82836
82836
  }
82837
82837
  static {
82838
82838
  __name(this, "PlatformClientImpl");
@@ -82861,7 +82861,7 @@ var require_client6 = __commonJS({
82861
82861
  async processMarkup(_class, id, data) {
82862
82862
  const result = {};
82863
82863
  for (const [key, value3] of Object.entries(data)) {
82864
- if (value3 instanceof import_markup11.MarkupContent) {
82864
+ if (value3 instanceof import_markup12.MarkupContent) {
82865
82865
  result[key] = this.markup.uploadMarkup(_class, id, key, value3.content, value3.kind);
82866
82866
  } else {
82867
82867
  result[key] = value3;
@@ -83936,11 +83936,11 @@ var require_storage2 = __commonJS({
83936
83936
  var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
83937
83937
  var storage_exports = {};
83938
83938
  __export2(storage_exports, {
83939
- connectStorage: () => import_client38.connectStorage,
83940
- createStorageClient: () => import_client38.createStorageClient
83939
+ connectStorage: () => import_client39.connectStorage,
83940
+ createStorageClient: () => import_client39.createStorageClient
83941
83941
  });
83942
83942
  module2.exports = __toCommonJS2(storage_exports);
83943
- var import_client38 = require_client7();
83943
+ var import_client39 = require_client7();
83944
83944
  __reExport2(storage_exports, require_error3(), module2.exports);
83945
83945
  __reExport2(storage_exports, require_types8(), module2.exports);
83946
83946
  }
@@ -152358,6 +152358,16 @@ var DocumentEditModeError = class extends Schema_exports.TaggedError()(
152358
152358
  return `Invalid edit_document mode: ${this.reason}`;
152359
152359
  }
152360
152360
  };
152361
+ var DocumentReferenceError = class extends Schema_exports.TaggedError()(
152362
+ "DocumentReferenceError",
152363
+ {
152364
+ reason: Schema_exports.String
152365
+ }
152366
+ ) {
152367
+ get message() {
152368
+ return `Invalid document references: ${this.reason}`;
152369
+ }
152370
+ };
152361
152371
 
152362
152372
  // src/huly/errors-files.ts
152363
152373
  var BYTES_PER_MB = 1024 * 1024;
@@ -153686,6 +153696,7 @@ var HulyDomainError = Schema_exports.Union(
153686
153696
  DocumentEmptyContentError,
153687
153697
  DocumentContentCorruptedError,
153688
153698
  DocumentEditModeError,
153699
+ DocumentReferenceError,
153689
153700
  CommentNotFoundError,
153690
153701
  MilestoneNotFoundError,
153691
153702
  ChannelNotFoundError,
@@ -153812,6 +153823,122 @@ var markdownToMarkupString = (markdown, urls) => {
153812
153823
  const json3 = (0, import_text_markdown.markdownToMarkup)(markdown, markdownInputUrlConfig(urls));
153813
153824
  return jsonAsMarkup(json3);
153814
153825
  };
153826
+ var trimmedQueryValue = (query, name) => {
153827
+ const value3 = query.get(name);
153828
+ if (value3 === null) {
153829
+ return void 0;
153830
+ }
153831
+ const trimmed2 = value3.trim();
153832
+ return trimmed2 === "" ? void 0 : trimmed2;
153833
+ };
153834
+ var hasAnyNativeReferenceQueryField = (query) => ["_id", "_class", "label"].some((name) => query.has(name));
153835
+ var parseUrlPair = (href, refUrl) => {
153836
+ try {
153837
+ return {
153838
+ candidateUrl: new URL(href),
153839
+ refUrl: new URL(refUrl)
153840
+ };
153841
+ } catch {
153842
+ return void 0;
153843
+ }
153844
+ };
153845
+ var parseNativeReferenceQuery = (query) => {
153846
+ const id = trimmedQueryValue(query, "_id");
153847
+ const objectClass = trimmedQueryValue(query, "_class");
153848
+ const label = trimmedQueryValue(query, "label");
153849
+ if (id === void 0 || objectClass === void 0 || label === void 0) {
153850
+ const missing = [
153851
+ id === void 0 ? "id" : void 0,
153852
+ objectClass === void 0 ? "objectclass" : void 0,
153853
+ label === void 0 ? "label" : void 0
153854
+ ].filter((name) => name !== void 0);
153855
+ return { _tag: "missing", missing };
153856
+ }
153857
+ return {
153858
+ _tag: "reference",
153859
+ reference: {
153860
+ id: DocId.make(id),
153861
+ objectClass: ObjectClassName.make(objectClass),
153862
+ label: NonEmptyString2.make(label)
153863
+ }
153864
+ };
153865
+ };
153866
+ var parseNativeBrowseReferenceHref = (href, urls) => {
153867
+ const parsedUrlPair = parseUrlPair(href, urls.refUrl);
153868
+ if (parsedUrlPair === void 0) {
153869
+ return { _tag: "notReference" };
153870
+ }
153871
+ const { candidateUrl, refUrl } = parsedUrlPair;
153872
+ if (candidateUrl.origin !== refUrl.origin || candidateUrl.pathname !== refUrl.pathname) {
153873
+ return { _tag: "notReference" };
153874
+ }
153875
+ if (candidateUrl.searchParams.get("workspace") !== refUrl.searchParams.get("workspace")) {
153876
+ return { _tag: "notReference" };
153877
+ }
153878
+ if (!hasAnyNativeReferenceQueryField(candidateUrl.searchParams)) {
153879
+ return { _tag: "notReference" };
153880
+ }
153881
+ const parsedQuery = parseNativeReferenceQuery(candidateUrl.searchParams);
153882
+ if (parsedQuery._tag === "missing") {
153883
+ return { _tag: "malformed", reason: `reference missing ${parsedQuery.missing.join(", ")}` };
153884
+ }
153885
+ return {
153886
+ _tag: "reference",
153887
+ reference: parsedQuery.reference
153888
+ };
153889
+ };
153890
+ var linkHref = (node) => {
153891
+ const linkMark = node.marks?.find((mark) => mark.type === "link" && typeof mark.attrs?.href === "string");
153892
+ const href = linkMark?.attrs?.href;
153893
+ return typeof href === "string" ? href : void 0;
153894
+ };
153895
+ var referenceNodeFromTextNode = (node, reference) => ({
153896
+ type: import_text.MarkupNodeType.reference,
153897
+ attrs: {
153898
+ id: reference.id,
153899
+ objectclass: reference.objectClass,
153900
+ label: reference.label
153901
+ },
153902
+ content: [{
153903
+ type: import_text.MarkupNodeType.text,
153904
+ text: typeof node.text === "string" && node.text !== "" ? node.text : reference.label,
153905
+ marks: []
153906
+ }]
153907
+ });
153908
+ var transformNativeReferenceLinks = (node, urls) => {
153909
+ const href = node.type === import_text.MarkupNodeType.text ? linkHref(node) : void 0;
153910
+ if (href !== void 0) {
153911
+ const parsed = parseNativeBrowseReferenceHref(href, urls);
153912
+ if (parsed._tag === "malformed") {
153913
+ return { node, malformedReferences: [parsed.reason], changed: false };
153914
+ }
153915
+ if (parsed._tag === "reference") {
153916
+ return {
153917
+ node: referenceNodeFromTextNode(node, parsed.reference),
153918
+ malformedReferences: [],
153919
+ changed: true
153920
+ };
153921
+ }
153922
+ }
153923
+ if (node.content === void 0) {
153924
+ return { node, malformedReferences: [], changed: false };
153925
+ }
153926
+ const transformed = node.content.map((child) => transformNativeReferenceLinks(child, urls));
153927
+ const changed = transformed.some((entry) => entry.changed);
153928
+ return {
153929
+ node: changed ? { ...node, content: transformed.map((entry) => entry.node) } : node,
153930
+ malformedReferences: transformed.flatMap((entry) => entry.malformedReferences),
153931
+ changed
153932
+ };
153933
+ };
153934
+ var markdownToMarkupStringWithHulyLinks = (markdown, urls) => {
153935
+ const json3 = (0, import_text_markdown.markdownToMarkup)(markdown, markdownInputUrlConfig(urls));
153936
+ const transformed = transformNativeReferenceLinks(json3, urls);
153937
+ return {
153938
+ markup: jsonAsMarkup(transformed.node),
153939
+ malformedReferences: transformed.malformedReferences
153940
+ };
153941
+ };
153815
153942
  var optionalMarkdownToMarkup = (md, urls, fallback = "") => md && md.trim() !== "" ? markdownToMarkupString(md, urls) : fallback;
153816
153943
  function optionalMarkupToMarkdown(markup, urls, fallback = "") {
153817
153944
  return markup === null || markup === void 0 ? fallback : markupToMarkdownString(markup, urls);
@@ -164481,6 +164608,7 @@ var INVALID_PARAMS_TAGS = /* @__PURE__ */ new Set([
164481
164608
  "DocumentEmptyContentError",
164482
164609
  "DocumentContentCorruptedError",
164483
164610
  "DocumentEditModeError",
164611
+ "DocumentReferenceError",
164484
164612
  "CommentNotFoundError",
164485
164613
  "MilestoneNotFoundError",
164486
164614
  "ChannelNotFoundError",
@@ -167170,6 +167298,10 @@ var parseDeleteIssueTemplateParams = Schema_exports.decodeUnknown(DeleteIssueTem
167170
167298
  var parseAddTemplateChildParams = Schema_exports.decodeUnknown(AddTemplateChildParamsSchema);
167171
167299
  var parseRemoveTemplateChildParams = Schema_exports.decodeUnknown(RemoveTemplateChildParamsSchema);
167172
167300
 
167301
+ // src/domain/schemas/document-native-references.ts
167302
+ var DOCUMENT_NATIVE_REFERENCE_LINK_USAGE = "Use markdown links to current-workspace Huly browse URLs for native references; Huly browse links returned in get_document content round-trip as native references. The URL identifies the object; link text is display text; plain issue keys stay text.";
167303
+ var DOCUMENT_NATIVE_REFERENCE_TOOL_USAGE = `${DOCUMENT_NATIVE_REFERENCE_LINK_USAGE} External URLs stay normal markdown links.`;
167304
+
167173
167305
  // src/domain/schemas/documents.ts
167174
167306
  var ListTeamspacesParamsSchema = Schema_exports.Struct({
167175
167307
  includeArchived: Schema_exports.optional(Schema_exports.Boolean.annotations({
@@ -167274,7 +167406,7 @@ var EditDocumentParamsSchema = EditDocumentParamsBase.pipe(
167274
167406
  const hasSearchReplace = hasOldText && hasNewText;
167275
167407
  const hasUpdateField = params.title !== void 0 || hasContent || hasSearchReplace;
167276
167408
  if (hasContent && (hasOldText || hasNewText)) {
167277
- return "Cannot provide both 'content' (full replace) and 'old_text'/'new_text' (search-and-replace). Use one mode or the other.";
167409
+ return "Cannot provide 'content' with 'old_text'/'new_text'. Use full replace or search-and-replace, not both.";
167278
167410
  }
167279
167411
  if (hasOldText !== hasNewText) {
167280
167412
  return "Both 'old_text' and 'new_text' must be provided together for search-and-replace mode.";
@@ -170233,7 +170365,7 @@ var parseDeleteTestResultParams = Schema_exports.decodeUnknown(DeleteTestResultP
170233
170365
  var parseRunTestPlanParams = Schema_exports.decodeUnknown(RunTestPlanParamsSchema);
170234
170366
 
170235
170367
  // src/version.ts
170236
- var VERSION = true ? "0.20.1" : "0.0.0-dev";
170368
+ var VERSION = true ? "0.21.0" : "0.0.0-dev";
170237
170369
 
170238
170370
  // src/mcp/tool-output-schema.ts
170239
170371
  var defaultToolOutputSchema = {
@@ -170608,6 +170740,19 @@ var listDirectMessages = (params) => Effect_exports.gen(function* () {
170608
170740
  var import_core13 = __toESM(require_lib4(), 1);
170609
170741
  var import_rank = __toESM(require_lib33(), 1);
170610
170742
 
170743
+ // src/huly/operations/document-native-references.ts
170744
+ var malformedReferenceList = (entries2) => entries2.map((entry) => `'${entry}'`).join(", ");
170745
+ var renderDocumentContentForWrite = (content) => Effect_exports.gen(function* () {
170746
+ const client = yield* HulyClient;
170747
+ const rendered = markdownToMarkupStringWithHulyLinks(content, client.markupUrlConfig);
170748
+ if (rendered.malformedReferences.length > 0) {
170749
+ return yield* new DocumentReferenceError({
170750
+ reason: `malformed Huly native reference links in content: ${malformedReferenceList(rendered.malformedReferences)}`
170751
+ });
170752
+ }
170753
+ return { markup: rendered.markup, format: "markup" };
170754
+ });
170755
+
170611
170756
  // src/huly/operations/documents-shared.ts
170612
170757
  var findTeamspace = (identifier2, opts) => Effect_exports.gen(function* () {
170613
170758
  const client = yield* HulyClient;
@@ -170715,20 +170860,22 @@ var editDocument = (params) => Effect_exports.gen(function* () {
170715
170860
  if (params.content.trim() === "") {
170716
170861
  updateOps.content = null;
170717
170862
  } else if (doc.content) {
170863
+ const renderedContent = yield* renderDocumentContentForWrite(params.content);
170718
170864
  yield* client.updateMarkup(
170719
170865
  documentPlugin.class.Document,
170720
170866
  doc._id,
170721
170867
  "content",
170722
- params.content,
170723
- "markdown"
170868
+ renderedContent.markup,
170869
+ renderedContent.format
170724
170870
  );
170725
170871
  } else {
170872
+ const renderedContent = yield* renderDocumentContentForWrite(params.content);
170726
170873
  const contentMarkupRef = yield* client.uploadMarkup(
170727
170874
  documentPlugin.class.Document,
170728
170875
  doc._id,
170729
170876
  "content",
170730
- params.content,
170731
- "markdown"
170877
+ renderedContent.markup,
170878
+ renderedContent.format
170732
170879
  );
170733
170880
  updateOps.content = contentMarkupRef;
170734
170881
  }
@@ -170756,12 +170903,13 @@ var editDocument = (params) => Effect_exports.gen(function* () {
170756
170903
  }
170757
170904
  const idx = currentContent.indexOf(params.old_text);
170758
170905
  const newContent = params.replace_all ? currentContent.split(params.old_text).join(params.new_text) : currentContent.substring(0, idx) + params.new_text + currentContent.substring(idx + params.old_text.length);
170906
+ const renderedContent = yield* renderDocumentContentForWrite(newContent);
170759
170907
  yield* client.updateMarkup(
170760
170908
  documentPlugin.class.Document,
170761
170909
  doc._id,
170762
170910
  "content",
170763
- newContent,
170764
- "markdown"
170911
+ renderedContent.markup,
170912
+ renderedContent.format
170765
170913
  );
170766
170914
  }
170767
170915
  const finalTitle = updateOps.title ?? doc.title;
@@ -171077,13 +171225,17 @@ var createDocument = (params) => Effect_exports.gen(function* () {
171077
171225
  { sort: { rank: import_core13.SortingOrder.Descending } }
171078
171226
  );
171079
171227
  const rank = (0, import_rank.makeRank)(lastDoc?.rank, void 0);
171080
- const contentMarkupRef = params.content !== void 0 && params.content.trim() !== "" ? yield* client.uploadMarkup(
171081
- documentPlugin.class.Document,
171082
- documentId,
171083
- "content",
171084
- params.content,
171085
- "markdown"
171086
- ) : null;
171228
+ const content = params.content;
171229
+ const contentMarkupRef = content !== void 0 && content.trim() !== "" ? yield* Effect_exports.gen(function* () {
171230
+ const renderedContent = yield* renderDocumentContentForWrite(content);
171231
+ return yield* client.uploadMarkup(
171232
+ documentPlugin.class.Document,
171233
+ documentId,
171234
+ "content",
171235
+ renderedContent.markup,
171236
+ renderedContent.format
171237
+ );
171238
+ }) : null;
171087
171239
  const documentData = {
171088
171240
  title: params.title,
171089
171241
  content: contentMarkupRef,
@@ -175293,7 +175445,7 @@ var documentTools = [
175293
175445
  },
175294
175446
  {
175295
175447
  name: "create_document",
175296
- description: "Create a new document in a Huly teamspace. Content supports full markdown including native Mermaid diagrams (```mermaid blocks render interactively in Huly UI). Optionally pass parent as a document title or ID to create a nested child document; invalid parents fail instead of silently creating a top-level document. Returns the created document id and a 'url' field pointing to the document in the Huly web app. Use link_document_to_issue to associate the document with a tracker issue.",
175448
+ description: "Create a new document in a Huly teamspace. Content is markdown and supports native Mermaid diagrams (```mermaid blocks render interactively in Huly UI). " + DOCUMENT_NATIVE_REFERENCE_TOOL_USAGE + " Optionally pass parent as a document title or ID to create a nested child document; invalid parents fail instead of silently creating a top-level document. Returns the created document id and a 'url' field pointing to the document in the Huly web app. Use link_document_to_issue only if you also want an issue-document association.",
175297
175449
  category: CATEGORY10,
175298
175450
  inputSchema: createDocumentParamsJsonSchema,
175299
175451
  handler: createToolHandler(
@@ -175304,7 +175456,7 @@ var documentTools = [
175304
175456
  },
175305
175457
  {
175306
175458
  name: "edit_document",
175307
- description: "Edit an existing Huly document. Two content modes (mutually exclusive): (1) 'content' for full replace, (2) 'old_text' + 'new_text' for targeted search-and-replace. Multiple matches error unless replace_all is true. Empty new_text deletes matched text. Also supports renaming via 'title'. Content supports full markdown including native Mermaid diagrams. Returns a 'url' field pointing to the document in the Huly web app.",
175459
+ description: "Edit an existing Huly document. You may rename with title and/or edit the body. Body editing has two mutually exclusive modes: (1) content replaces the entire markdown body, (2) old_text + new_text performs exact targeted search-and-replace. " + DOCUMENT_NATIVE_REFERENCE_TOOL_USAGE + " For targeted replace, multiple matches error unless replace_all is true; empty new_text deletes matched text. Content supports native Mermaid diagrams. Returns a 'url' field pointing to the document in the Huly web app.",
175308
175460
  category: CATEGORY10,
175309
175461
  inputSchema: editDocumentParamsJsonSchema,
175310
175462
  handler: createToolHandler(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firfi/huly-mcp",
3
- "version": "0.20.1",
3
+ "version": "0.21.0",
4
4
  "description": "MCP server for Huly integration",
5
5
  "mcpName": "io.github.dearlordylord/huly-mcp",
6
6
  "type": "module",