@firfi/huly-mcp 0.20.0 → 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 +248 -86
  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,
@@ -153764,6 +153775,12 @@ var isMarkdownSerializableMark = (mark) => !isInlineCommentMark(mark);
153764
153775
 
153765
153776
  // src/huly/operations/markup.ts
153766
153777
  var jsonAsMarkup = import_text.jsonToMarkup;
153778
+ var MARKDOWN_INPUT_REF_URL = "https://huly-mcp.invalid/no-reference-conversion";
153779
+ var markdownInputRefUrl = UrlString.make(MARKDOWN_INPUT_REF_URL);
153780
+ var markdownInputUrlConfig = (urls) => ({
153781
+ refUrl: markdownInputRefUrl,
153782
+ imageUrl: urls.imageUrl
153783
+ });
153767
153784
  var testMarkupUrlConfig = {
153768
153785
  refUrl: UrlString.make("https://test.invalid/browse?workspace=test"),
153769
153786
  imageUrl: UrlString.make("https://test.invalid/files?workspace=test&file=")
@@ -153803,9 +153820,125 @@ var markupToMarkdownString = (markup, urls) => {
153803
153820
  return markupNodeToMarkdownString(json3, urls);
153804
153821
  };
153805
153822
  var markdownToMarkupString = (markdown, urls) => {
153806
- const json3 = (0, import_text_markdown.markdownToMarkup)(markdown, urls);
153823
+ const json3 = (0, import_text_markdown.markdownToMarkup)(markdown, markdownInputUrlConfig(urls));
153807
153824
  return jsonAsMarkup(json3);
153808
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
+ };
153809
153942
  var optionalMarkdownToMarkup = (md, urls, fallback = "") => md && md.trim() !== "" ? markdownToMarkupString(md, urls) : fallback;
153810
153943
  function optionalMarkupToMarkdown(markup, urls, fallback = "") {
153811
153944
  return markup === null || markup === void 0 ? fallback : markupToMarkdownString(markup, urls);
@@ -153899,6 +154032,10 @@ var connectWithRetry = (connect, errorPrefix) => withConnectionRetry(
153899
154032
  }
153900
154033
  })
153901
154034
  );
154035
+ var markdownInputOptions = (opts) => ({
154036
+ refUrl: MARKDOWN_INPUT_REF_URL,
154037
+ imageUrl: opts.imageUrl
154038
+ });
153902
154039
  function toInternalMarkup(value3, format7, opts, sdk) {
153903
154040
  switch (format7) {
153904
154041
  case "markup":
@@ -153906,7 +154043,7 @@ function toInternalMarkup(value3, format7, opts, sdk) {
153906
154043
  case "html":
153907
154044
  return sdk.jsonToMarkup(sdk.htmlToJSON(value3));
153908
154045
  case "markdown":
153909
- return sdk.jsonToMarkup(sdk.markdownToMarkup(value3, opts));
154046
+ return sdk.jsonToMarkup(sdk.markdownToMarkup(value3, markdownInputOptions(opts)));
153910
154047
  default:
153911
154048
  absurd(format7);
153912
154049
  throw new Error(`Invalid format: ${format7}`);
@@ -164471,6 +164608,7 @@ var INVALID_PARAMS_TAGS = /* @__PURE__ */ new Set([
164471
164608
  "DocumentEmptyContentError",
164472
164609
  "DocumentContentCorruptedError",
164473
164610
  "DocumentEditModeError",
164611
+ "DocumentReferenceError",
164474
164612
  "CommentNotFoundError",
164475
164613
  "MilestoneNotFoundError",
164476
164614
  "ChannelNotFoundError",
@@ -167160,6 +167298,10 @@ var parseDeleteIssueTemplateParams = Schema_exports.decodeUnknown(DeleteIssueTem
167160
167298
  var parseAddTemplateChildParams = Schema_exports.decodeUnknown(AddTemplateChildParamsSchema);
167161
167299
  var parseRemoveTemplateChildParams = Schema_exports.decodeUnknown(RemoveTemplateChildParamsSchema);
167162
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
+
167163
167305
  // src/domain/schemas/documents.ts
167164
167306
  var ListTeamspacesParamsSchema = Schema_exports.Struct({
167165
167307
  includeArchived: Schema_exports.optional(Schema_exports.Boolean.annotations({
@@ -167264,7 +167406,7 @@ var EditDocumentParamsSchema = EditDocumentParamsBase.pipe(
167264
167406
  const hasSearchReplace = hasOldText && hasNewText;
167265
167407
  const hasUpdateField = params.title !== void 0 || hasContent || hasSearchReplace;
167266
167408
  if (hasContent && (hasOldText || hasNewText)) {
167267
- 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.";
167268
167410
  }
167269
167411
  if (hasOldText !== hasNewText) {
167270
167412
  return "Both 'old_text' and 'new_text' must be provided together for search-and-replace mode.";
@@ -170223,7 +170365,7 @@ var parseDeleteTestResultParams = Schema_exports.decodeUnknown(DeleteTestResultP
170223
170365
  var parseRunTestPlanParams = Schema_exports.decodeUnknown(RunTestPlanParamsSchema);
170224
170366
 
170225
170367
  // src/version.ts
170226
- var VERSION = true ? "0.20.0" : "0.0.0-dev";
170368
+ var VERSION = true ? "0.21.0" : "0.0.0-dev";
170227
170369
 
170228
170370
  // src/mcp/tool-output-schema.ts
170229
170371
  var defaultToolOutputSchema = {
@@ -170598,6 +170740,19 @@ var listDirectMessages = (params) => Effect_exports.gen(function* () {
170598
170740
  var import_core13 = __toESM(require_lib4(), 1);
170599
170741
  var import_rank = __toESM(require_lib33(), 1);
170600
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
+
170601
170756
  // src/huly/operations/documents-shared.ts
170602
170757
  var findTeamspace = (identifier2, opts) => Effect_exports.gen(function* () {
170603
170758
  const client = yield* HulyClient;
@@ -170705,20 +170860,22 @@ var editDocument = (params) => Effect_exports.gen(function* () {
170705
170860
  if (params.content.trim() === "") {
170706
170861
  updateOps.content = null;
170707
170862
  } else if (doc.content) {
170863
+ const renderedContent = yield* renderDocumentContentForWrite(params.content);
170708
170864
  yield* client.updateMarkup(
170709
170865
  documentPlugin.class.Document,
170710
170866
  doc._id,
170711
170867
  "content",
170712
- params.content,
170713
- "markdown"
170868
+ renderedContent.markup,
170869
+ renderedContent.format
170714
170870
  );
170715
170871
  } else {
170872
+ const renderedContent = yield* renderDocumentContentForWrite(params.content);
170716
170873
  const contentMarkupRef = yield* client.uploadMarkup(
170717
170874
  documentPlugin.class.Document,
170718
170875
  doc._id,
170719
170876
  "content",
170720
- params.content,
170721
- "markdown"
170877
+ renderedContent.markup,
170878
+ renderedContent.format
170722
170879
  );
170723
170880
  updateOps.content = contentMarkupRef;
170724
170881
  }
@@ -170746,12 +170903,13 @@ var editDocument = (params) => Effect_exports.gen(function* () {
170746
170903
  }
170747
170904
  const idx = currentContent.indexOf(params.old_text);
170748
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);
170749
170907
  yield* client.updateMarkup(
170750
170908
  documentPlugin.class.Document,
170751
170909
  doc._id,
170752
170910
  "content",
170753
- newContent,
170754
- "markdown"
170911
+ renderedContent.markup,
170912
+ renderedContent.format
170755
170913
  );
170756
170914
  }
170757
170915
  const finalTitle = updateOps.title ?? doc.title;
@@ -171067,13 +171225,17 @@ var createDocument = (params) => Effect_exports.gen(function* () {
171067
171225
  { sort: { rank: import_core13.SortingOrder.Descending } }
171068
171226
  );
171069
171227
  const rank = (0, import_rank.makeRank)(lastDoc?.rank, void 0);
171070
- const contentMarkupRef = params.content !== void 0 && params.content.trim() !== "" ? yield* client.uploadMarkup(
171071
- documentPlugin.class.Document,
171072
- documentId,
171073
- "content",
171074
- params.content,
171075
- "markdown"
171076
- ) : 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;
171077
171239
  const documentData = {
171078
171240
  title: params.title,
171079
171241
  content: contentMarkupRef,
@@ -175283,7 +175445,7 @@ var documentTools = [
175283
175445
  },
175284
175446
  {
175285
175447
  name: "create_document",
175286
- 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.",
175287
175449
  category: CATEGORY10,
175288
175450
  inputSchema: createDocumentParamsJsonSchema,
175289
175451
  handler: createToolHandler(
@@ -175294,7 +175456,7 @@ var documentTools = [
175294
175456
  },
175295
175457
  {
175296
175458
  name: "edit_document",
175297
- 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.",
175298
175460
  category: CATEGORY10,
175299
175461
  inputSchema: editDocumentParamsJsonSchema,
175300
175462
  handler: createToolHandler(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firfi/huly-mcp",
3
- "version": "0.20.0",
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",