huly-mcp-sdk 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/.env.example +16 -0
  2. package/README.md +189 -0
  3. package/dist/connection.d.ts +5 -0
  4. package/dist/connection.js +111 -0
  5. package/dist/connection.js.map +1 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.js +25 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/schemas.d.ts +177 -0
  10. package/dist/schemas.js +78 -0
  11. package/dist/schemas.js.map +1 -0
  12. package/dist/server.d.ts +2 -0
  13. package/dist/server.js +48 -0
  14. package/dist/server.js.map +1 -0
  15. package/dist/tools/comments.d.ts +9 -0
  16. package/dist/tools/comments.js +19 -0
  17. package/dist/tools/comments.js.map +1 -0
  18. package/dist/tools/documents.d.ts +14 -0
  19. package/dist/tools/documents.js +27 -0
  20. package/dist/tools/documents.js.map +1 -0
  21. package/dist/tools/issues.d.ts +51 -0
  22. package/dist/tools/issues.js +179 -0
  23. package/dist/tools/issues.js.map +1 -0
  24. package/dist/tools/labels.d.ts +35 -0
  25. package/dist/tools/labels.js +150 -0
  26. package/dist/tools/labels.js.map +1 -0
  27. package/dist/tools/members.d.ts +6 -0
  28. package/dist/tools/members.js +17 -0
  29. package/dist/tools/members.js.map +1 -0
  30. package/dist/tools/milestones.d.ts +8 -0
  31. package/dist/tools/milestones.js +26 -0
  32. package/dist/tools/milestones.js.map +1 -0
  33. package/dist/tools/projects.d.ts +14 -0
  34. package/dist/tools/projects.js +33 -0
  35. package/dist/tools/projects.js.map +1 -0
  36. package/dist/tools/relations.d.ts +27 -0
  37. package/dist/tools/relations.js +103 -0
  38. package/dist/tools/relations.js.map +1 -0
  39. package/dist/tools/search.d.ts +9 -0
  40. package/dist/tools/search.js +32 -0
  41. package/dist/tools/search.js.map +1 -0
  42. package/dist/utils/errors.d.ts +11 -0
  43. package/dist/utils/errors.js +24 -0
  44. package/dist/utils/errors.js.map +1 -0
  45. package/dist/utils/format.d.ts +4 -0
  46. package/dist/utils/format.js +28 -0
  47. package/dist/utils/format.js.map +1 -0
  48. package/package.json +63 -0
  49. package/scripts/cleanup-issues.js +132 -0
  50. package/scripts/demo.js +193 -0
  51. package/scripts/import-csv.js +242 -0
  52. package/scripts/sample-tasks.csv +11 -0
  53. package/scripts/setup.js +103 -0
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.listMilestones = void 0;
7
+ const tracker_1 = __importDefault(require("@hcengineering/tracker"));
8
+ const connection_1 = require("../connection");
9
+ const errors_1 = require("../utils/errors");
10
+ const format_1 = require("../utils/format");
11
+ exports.listMilestones = (0, errors_1.wrapToolHandler)(async (args) => {
12
+ const client = await (0, connection_1.getConnection)();
13
+ const project = await client.findOne(tracker_1.default.class.Project, { identifier: args.projectIdentifier });
14
+ if (project == null)
15
+ throw new Error(`Project '${args.projectIdentifier}' not found.`);
16
+ const milestones = await client.findAll(tracker_1.default.class.Milestone, { space: project._id });
17
+ if (milestones.length === 0)
18
+ return `No milestones found in project ${args.projectIdentifier}.`;
19
+ const lines = milestones.map((m) => {
20
+ const status = format_1.MILESTONE_STATUS_LABELS[m.status] ?? 'Unknown';
21
+ const target = (0, format_1.formatDate)(m.targetDate);
22
+ return `- **${m.label}** [${status}] — Target: ${target}`;
23
+ });
24
+ return `## Milestones in ${args.projectIdentifier} (${milestones.length})\n\n${lines.join('\n')}`;
25
+ });
26
+ //# sourceMappingURL=milestones.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"milestones.js","sourceRoot":"","sources":["../../src/tools/milestones.ts"],"names":[],"mappings":";;;;;;AAAA,qEAA4C;AAC5C,8CAA6C;AAC7C,4CAAiD;AACjD,4CAAqE;AAIxD,QAAA,cAAc,GAAG,IAAA,wBAAe,EAAuC,KAAK,EAAE,IAAI,EAAE,EAAE;IACjG,MAAM,MAAM,GAAG,MAAM,IAAA,0BAAa,GAAE,CAAA;IACpC,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,iBAAO,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,iBAAiB,EAAE,CAAC,CAAA;IACnG,IAAI,OAAO,IAAI,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,YAAY,IAAI,CAAC,iBAAiB,cAAc,CAAC,CAAA;IAEtF,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,iBAAO,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;IAExF,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,kCAAkC,IAAI,CAAC,iBAAiB,GAAG,CAAA;IAE/F,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACjC,MAAM,MAAM,GAAG,gCAAuB,CAAC,CAAC,CAAC,MAAgB,CAAC,IAAI,SAAS,CAAA;QACvE,MAAM,MAAM,GAAG,IAAA,mBAAU,EAAC,CAAC,CAAC,UAAU,CAAC,CAAA;QACvC,OAAO,OAAO,CAAC,CAAC,KAAK,OAAO,MAAM,eAAe,MAAM,EAAE,CAAA;IAC3D,CAAC,CAAC,CAAA;IAEF,OAAO,oBAAoB,IAAI,CAAC,iBAAiB,KAAK,UAAU,CAAC,MAAM,QAAQ,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAA;AACnG,CAAC,CAAC,CAAA"}
@@ -0,0 +1,14 @@
1
+ export declare const listProjects: (args: Record<string, never>) => Promise<{
2
+ content: Array<{
3
+ type: "text";
4
+ text: string;
5
+ }>;
6
+ }>;
7
+ export declare const getProject: (args: {
8
+ identifier: string;
9
+ }) => Promise<{
10
+ content: Array<{
11
+ type: "text";
12
+ text: string;
13
+ }>;
14
+ }>;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getProject = exports.listProjects = void 0;
7
+ const tracker_1 = __importDefault(require("@hcengineering/tracker"));
8
+ const connection_1 = require("../connection");
9
+ const errors_1 = require("../utils/errors");
10
+ exports.listProjects = (0, errors_1.wrapToolHandler)(async () => {
11
+ const client = await (0, connection_1.getConnection)();
12
+ const projects = await client.findAll(tracker_1.default.class.Project, {});
13
+ if (projects.length === 0)
14
+ return 'No projects found in this workspace.';
15
+ const lines = projects.map((p) => `- **${p.identifier}** — ${p.name ?? '(no name)'}${p.description != null && p.description !== '' ? `\n ${p.description}` : ''}`);
16
+ return `## Projects (${projects.length})\n\n${lines.join('\n')}`;
17
+ });
18
+ exports.getProject = (0, errors_1.wrapToolHandler)(async (args) => {
19
+ const client = await (0, connection_1.getConnection)();
20
+ const project = await client.findOne(tracker_1.default.class.Project, { identifier: args.identifier });
21
+ if (project == null)
22
+ throw new Error(`Project '${args.identifier}' not found.`);
23
+ const statuses = await client.findAll(tracker_1.default.class.IssueStatus, { space: project._id });
24
+ const statusList = statuses.map((s) => ` - ${s.name}`).join('\n');
25
+ return [
26
+ `## Project: ${project.identifier}`,
27
+ `**Name:** ${project.name ?? '(no name)'}`,
28
+ project.description != null && project.description !== '' ? `**Description:** ${project.description}` : null,
29
+ `**Issues created:** ${project.sequence}`,
30
+ `\n**Statuses:**\n${statusList}`
31
+ ].filter(Boolean).join('\n');
32
+ });
33
+ //# sourceMappingURL=projects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects.js","sourceRoot":"","sources":["../../src/tools/projects.ts"],"names":[],"mappings":";;;;;;AAAA,qEAA4C;AAC5C,8CAA6C;AAC7C,4CAAiD;AAIpC,QAAA,YAAY,GAAG,IAAA,wBAAe,EAAwB,KAAK,IAAI,EAAE;IAC5E,MAAM,MAAM,GAAG,MAAM,IAAA,0BAAa,GAAE,CAAA;IACpC,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,iBAAO,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,CAAA;IAEhE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,sCAAsC,CAAA;IAExE,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAC/B,OAAO,CAAC,CAAC,UAAU,QAAQ,CAAC,CAAC,IAAI,IAAI,WAAW,GAAG,CAAC,CAAC,WAAW,IAAI,IAAI,IAAI,CAAC,CAAC,WAAW,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CACjI,CAAA;IACD,OAAO,gBAAgB,QAAQ,CAAC,MAAM,QAAQ,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAA;AAClE,CAAC,CAAC,CAAA;AAEW,QAAA,UAAU,GAAG,IAAA,wBAAe,EAAmC,KAAK,EAAE,IAAI,EAAE,EAAE;IACzF,MAAM,MAAM,GAAG,MAAM,IAAA,0BAAa,GAAE,CAAA;IACpC,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,iBAAO,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAA;IAC5F,IAAI,OAAO,IAAI,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,YAAY,IAAI,CAAC,UAAU,cAAc,CAAC,CAAA;IAE/E,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,iBAAO,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;IACxF,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAElE,OAAO;QACL,eAAe,OAAO,CAAC,UAAU,EAAE;QACnC,aAAa,OAAO,CAAC,IAAI,IAAI,WAAW,EAAE;QAC1C,OAAO,CAAC,WAAW,IAAI,IAAI,IAAI,OAAO,CAAC,WAAW,KAAK,EAAE,CAAC,CAAC,CAAC,oBAAoB,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI;QAC5G,uBAAuB,OAAO,CAAC,QAAQ,EAAE;QACzC,oBAAoB,UAAU,EAAE;KACjC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAC9B,CAAC,CAAC,CAAA"}
@@ -0,0 +1,27 @@
1
+ export declare const addRelation: (args: {
2
+ identifier: string;
3
+ relatedTo: string;
4
+ }) => Promise<{
5
+ content: Array<{
6
+ type: "text";
7
+ text: string;
8
+ }>;
9
+ }>;
10
+ export declare const addBlockedBy: (args: {
11
+ identifier: string;
12
+ blockedBy: string;
13
+ }) => Promise<{
14
+ content: Array<{
15
+ type: "text";
16
+ text: string;
17
+ }>;
18
+ }>;
19
+ export declare const setParent: (args: {
20
+ identifier: string;
21
+ parentIdentifier?: string | null | undefined;
22
+ }) => Promise<{
23
+ content: Array<{
24
+ type: "text";
25
+ text: string;
26
+ }>;
27
+ }>;
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.setParent = exports.addBlockedBy = exports.addRelation = void 0;
7
+ const tracker_1 = __importDefault(require("@hcengineering/tracker"));
8
+ const connection_1 = require("../connection");
9
+ const errors_1 = require("../utils/errors");
10
+ exports.addRelation = (0, errors_1.wrapToolHandler)(async (args) => {
11
+ const client = await (0, connection_1.getConnection)();
12
+ const issueA = await client.findOne(tracker_1.default.class.Issue, { identifier: args.identifier });
13
+ if (issueA == null)
14
+ throw new Error(`Issue '${args.identifier}' not found.`);
15
+ const issueB = await client.findOne(tracker_1.default.class.Issue, { identifier: args.relatedTo });
16
+ if (issueB == null)
17
+ throw new Error(`Issue '${args.relatedTo}' not found.`);
18
+ const relA = { _id: issueB._id, _class: issueB._class };
19
+ const relB = { _id: issueA._id, _class: issueA._class };
20
+ const existingRels = issueA.relations ?? [];
21
+ if (existingRels.some((r) => r._id === issueB._id)) {
22
+ return `**${args.identifier}** is already related to **${args.relatedTo}**.`;
23
+ }
24
+ // Add bidirectional relation
25
+ await client.updateDoc(tracker_1.default.class.Issue, issueA.space, issueA._id, {
26
+ relations: [...existingRels, relA]
27
+ });
28
+ await client.updateDoc(tracker_1.default.class.Issue, issueB.space, issueB._id, {
29
+ relations: [...(issueB.relations ?? []), relB]
30
+ });
31
+ return `āœ… **${args.identifier}** is now related to **${args.relatedTo}**`;
32
+ });
33
+ exports.addBlockedBy = (0, errors_1.wrapToolHandler)(async (args) => {
34
+ const client = await (0, connection_1.getConnection)();
35
+ const issue = await client.findOne(tracker_1.default.class.Issue, { identifier: args.identifier });
36
+ if (issue == null)
37
+ throw new Error(`Issue '${args.identifier}' not found.`);
38
+ const blocker = await client.findOne(tracker_1.default.class.Issue, { identifier: args.blockedBy });
39
+ if (blocker == null)
40
+ throw new Error(`Issue '${args.blockedBy}' not found.`);
41
+ const blockerRef = { _id: blocker._id, _class: blocker._class };
42
+ const existing = issue.blockedBy ?? [];
43
+ if (existing.some((r) => r._id === blocker._id)) {
44
+ return `**${args.identifier}** is already blocked by **${args.blockedBy}**.`;
45
+ }
46
+ await client.updateDoc(tracker_1.default.class.Issue, issue.space, issue._id, {
47
+ blockedBy: [...existing, blockerRef]
48
+ });
49
+ return `āœ… **${args.identifier}** is now blocked by **${args.blockedBy}**`;
50
+ });
51
+ exports.setParent = (0, errors_1.wrapToolHandler)(async (args) => {
52
+ const client = await (0, connection_1.getConnection)();
53
+ const child = await client.findOne(tracker_1.default.class.Issue, { identifier: args.identifier });
54
+ if (child == null)
55
+ throw new Error(`Issue '${args.identifier}' not found.`);
56
+ // Clear parent if requested
57
+ if (args.parentIdentifier == null) {
58
+ await client.updateDoc(tracker_1.default.class.Issue, child.space, child._id, { parents: [] });
59
+ // Remove from old parent's childInfo if there was one
60
+ if (child.parents.length > 0) {
61
+ const oldParentId = child.parents[0].parentId;
62
+ const oldParent = await client.findOne(tracker_1.default.class.Issue, { _id: oldParentId });
63
+ if (oldParent != null) {
64
+ const newChildInfo = (oldParent.childInfo ?? []).filter((c) => c.childId !== child._id);
65
+ await client.updateDoc(tracker_1.default.class.Issue, oldParent.space, oldParent._id, {
66
+ subIssues: Math.max(0, (oldParent.subIssues ?? 1) - 1),
67
+ childInfo: newChildInfo
68
+ });
69
+ }
70
+ }
71
+ return `āœ… Cleared parent from **${args.identifier}**`;
72
+ }
73
+ const parent = await client.findOne(tracker_1.default.class.Issue, { identifier: args.parentIdentifier });
74
+ if (parent == null)
75
+ throw new Error(`Parent issue '${args.parentIdentifier}' not found.`);
76
+ if (parent._id === child._id)
77
+ throw new Error('An issue cannot be its own parent.');
78
+ const parentInfo = {
79
+ parentId: parent._id,
80
+ identifier: parent.identifier,
81
+ parentTitle: parent.title,
82
+ space: parent.space
83
+ };
84
+ const childInfoEntry = {
85
+ childId: child._id,
86
+ estimation: child.estimation ?? 0,
87
+ reportedTime: child.reportedTime ?? 0
88
+ };
89
+ // Update child's parents list
90
+ await client.updateDoc(tracker_1.default.class.Issue, child.space, child._id, {
91
+ parents: [parentInfo, ...child.parents]
92
+ });
93
+ // Update parent's subIssues count and childInfo
94
+ const existingChildInfo = parent.childInfo ?? [];
95
+ if (!existingChildInfo.some((c) => c.childId === child._id)) {
96
+ await client.updateDoc(tracker_1.default.class.Issue, parent.space, parent._id, {
97
+ subIssues: (parent.subIssues ?? 0) + 1,
98
+ childInfo: [...existingChildInfo, childInfoEntry]
99
+ });
100
+ }
101
+ return `āœ… **${args.identifier}** is now a sub-issue of **${args.parentIdentifier}**`;
102
+ });
103
+ //# sourceMappingURL=relations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relations.js","sourceRoot":"","sources":["../../src/tools/relations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export declare const searchIssues: (args: {
2
+ limit: number;
3
+ query: string;
4
+ }) => Promise<{
5
+ content: Array<{
6
+ type: "text";
7
+ text: string;
8
+ }>;
9
+ }>;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.searchIssues = void 0;
7
+ const tracker_1 = __importDefault(require("@hcengineering/tracker"));
8
+ const connection_1 = require("../connection");
9
+ const errors_1 = require("../utils/errors");
10
+ const format_1 = require("../utils/format");
11
+ exports.searchIssues = (0, errors_1.wrapToolHandler)(async (args) => {
12
+ const client = await (0, connection_1.getConnection)();
13
+ const result = await client.searchFulltext({ query: args.query, classes: [tracker_1.default.class.Issue] }, { limit: args.limit });
14
+ if (result.docs.length === 0)
15
+ return `No issues found for query: "${args.query}"`;
16
+ // Enrich each result with full issue data
17
+ const enriched = await Promise.all(result.docs.map(async (r) => {
18
+ const issue = await client.findOne(tracker_1.default.class.Issue, { _id: r.id });
19
+ if (issue == null)
20
+ return null;
21
+ const status = await client.findOne(tracker_1.default.class.IssueStatus, { _id: issue.status });
22
+ return { issue, statusName: status?.name ?? 'Unknown' };
23
+ }));
24
+ const lines = enriched
25
+ .filter((e) => e !== null)
26
+ .map((e) => {
27
+ const due = e.issue.dueDate != null ? ` | Due: ${(0, format_1.formatDate)(e.issue.dueDate)}` : '';
28
+ return `- **${e.issue.identifier}** [${e.statusName}] [${(0, format_1.priorityLabel)(e.issue.priority)}]${due}\n ${e.issue.title}`;
29
+ });
30
+ return `## Search results for "${args.query}" (${lines.length})\n\n${lines.join('\n')}`;
31
+ });
32
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":";;;;;;AAAA,qEAA4C;AAC5C,8CAA6C;AAC7C,4CAAiD;AACjD,4CAA2D;AAI9C,QAAA,YAAY,GAAG,IAAA,wBAAe,EAAqC,KAAK,EAAE,IAAI,EAAE,EAAE;IAC7F,MAAM,MAAM,GAAG,MAAM,IAAA,0BAAa,GAAE,CAAA;IAEpC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,cAAc,CACxC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC,iBAAO,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,EACrD,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CACtB,CAAA;IAED,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,+BAA+B,IAAI,CAAC,KAAK,GAAG,CAAA;IAEjF,0CAA0C;IAC1C,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAChC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;QAC1B,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,iBAAO,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,EAAS,EAAE,CAAC,CAAA;QAC7E,IAAI,KAAK,IAAI,IAAI;YAAE,OAAO,IAAI,CAAA;QAC9B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,iBAAO,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAA;QACrF,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,IAAI,SAAS,EAAE,CAAA;IACzD,CAAC,CAAC,CACH,CAAA;IAED,MAAM,KAAK,GAAG,QAAQ;SACnB,MAAM,CAAC,CAAC,CAAC,EAA8B,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;SACrD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,WAAW,IAAA,mBAAU,EAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;QACnF,OAAO,OAAO,CAAC,CAAC,KAAK,CAAC,UAAU,OAAO,CAAC,CAAC,UAAU,MAAM,IAAA,sBAAa,EAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,GAAG,OAAO,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;IACvH,CAAC,CAAC,CAAA;IAEJ,OAAO,0BAA0B,IAAI,CAAC,KAAK,MAAM,KAAK,CAAC,MAAM,QAAQ,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAA;AACzF,CAAC,CAAC,CAAA"}
@@ -0,0 +1,11 @@
1
+ export declare class HulyMcpError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ type ToolResult = {
5
+ content: Array<{
6
+ type: 'text';
7
+ text: string;
8
+ }>;
9
+ };
10
+ export declare function wrapToolHandler<T extends Record<string, unknown>>(handler: (args: T) => Promise<string>): (args: T) => Promise<ToolResult>;
11
+ export {};
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HulyMcpError = void 0;
4
+ exports.wrapToolHandler = wrapToolHandler;
5
+ class HulyMcpError extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = 'HulyMcpError';
9
+ }
10
+ }
11
+ exports.HulyMcpError = HulyMcpError;
12
+ function wrapToolHandler(handler) {
13
+ return async (args) => {
14
+ try {
15
+ const text = await handler(args);
16
+ return { content: [{ type: 'text', text }] };
17
+ }
18
+ catch (err) {
19
+ const message = err instanceof Error ? err.message : String(err);
20
+ throw new Error(`Huly MCP: ${message}`);
21
+ }
22
+ };
23
+ }
24
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":";;;AASA,0CAYC;AArBD,MAAa,YAAa,SAAQ,KAAK;IACrC,YAAa,OAAe;QAC1B,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,cAAc,CAAA;IAC5B,CAAC;CACF;AALD,oCAKC;AAID,SAAgB,eAAe,CAC7B,OAAqC;IAErC,OAAO,KAAK,EAAE,IAAO,EAAuB,EAAE;QAC5C,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;YAChC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAA;QAC9C,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YAChE,MAAM,IAAI,KAAK,CAAC,aAAa,OAAO,EAAE,CAAC,CAAA;QACzC,CAAC;IACH,CAAC,CAAA;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare const PRIORITY_LABELS: Record<number, string>;
2
+ export declare const MILESTONE_STATUS_LABELS: Record<number, string>;
3
+ export declare function formatDate(ts: number | null | undefined): string;
4
+ export declare function priorityLabel(priority: number): string;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MILESTONE_STATUS_LABELS = exports.PRIORITY_LABELS = void 0;
4
+ exports.formatDate = formatDate;
5
+ exports.priorityLabel = priorityLabel;
6
+ const tracker_1 = require("@hcengineering/tracker");
7
+ exports.PRIORITY_LABELS = {
8
+ [tracker_1.IssuePriority.NoPriority]: 'No Priority',
9
+ [tracker_1.IssuePriority.Urgent]: 'Urgent',
10
+ [tracker_1.IssuePriority.High]: 'High',
11
+ [tracker_1.IssuePriority.Medium]: 'Medium',
12
+ [tracker_1.IssuePriority.Low]: 'Low'
13
+ };
14
+ exports.MILESTONE_STATUS_LABELS = {
15
+ 0: 'Planned',
16
+ 1: 'In Progress',
17
+ 2: 'Completed',
18
+ 3: 'Canceled'
19
+ };
20
+ function formatDate(ts) {
21
+ if (ts == null)
22
+ return 'None';
23
+ return new Date(ts).toISOString().split('T')[0];
24
+ }
25
+ function priorityLabel(priority) {
26
+ return exports.PRIORITY_LABELS[priority] ?? 'Unknown';
27
+ }
28
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/utils/format.ts"],"names":[],"mappings":";;;AAiBA,gCAGC;AAED,sCAEC;AAxBD,oDAAsD;AAEzC,QAAA,eAAe,GAA2B;IACrD,CAAC,uBAAa,CAAC,UAAU,CAAC,EAAE,aAAa;IACzC,CAAC,uBAAa,CAAC,MAAM,CAAC,EAAE,QAAQ;IAChC,CAAC,uBAAa,CAAC,IAAI,CAAC,EAAE,MAAM;IAC5B,CAAC,uBAAa,CAAC,MAAM,CAAC,EAAE,QAAQ;IAChC,CAAC,uBAAa,CAAC,GAAG,CAAC,EAAE,KAAK;CAC3B,CAAA;AAEY,QAAA,uBAAuB,GAA2B;IAC7D,CAAC,EAAE,SAAS;IACZ,CAAC,EAAE,aAAa;IAChB,CAAC,EAAE,WAAW;IACd,CAAC,EAAE,UAAU;CACd,CAAA;AAED,SAAgB,UAAU,CAAE,EAA6B;IACvD,IAAI,EAAE,IAAI,IAAI;QAAE,OAAO,MAAM,CAAA;IAC7B,OAAO,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;AACjD,CAAC;AAED,SAAgB,aAAa,CAAE,QAAgB;IAC7C,OAAO,uBAAe,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAA;AAC/C,CAAC"}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "huly-mcp-sdk",
3
+ "version": "0.2.1",
4
+ "description": "MCP server for Huly — connect Claude Desktop to your Huly workspace via the native WebSocket SDK",
5
+ "keywords": [
6
+ "huly",
7
+ "mcp",
8
+ "model-context-protocol",
9
+ "claude",
10
+ "project-management",
11
+ "issue-tracker",
12
+ "anthropic"
13
+ ],
14
+ "homepage": "https://github.com/varaprasadreddy9676/huly-mcp#readme",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/varaprasadreddy9676/huly-mcp.git"
18
+ },
19
+ "license": "EPL-2.0",
20
+ "author": "SaiVaraprasad Medapati",
21
+ "bin": {
22
+ "huly-mcp": "dist/index.js",
23
+ "huly-mcp-setup": "scripts/setup.js"
24
+ },
25
+ "files": [
26
+ "dist/",
27
+ "scripts/",
28
+ ".env.example",
29
+ "README.md"
30
+ ],
31
+ "engines": {
32
+ "node": ">=20"
33
+ },
34
+ "scripts": {
35
+ "build": "node --max-old-space-size=4096 node_modules/typescript/bin/tsc",
36
+ "dev": "tsx watch src/index.ts",
37
+ "start": "node dist/index.js",
38
+ "setup": "node scripts/setup.js",
39
+ "prepublishOnly": "npm run build"
40
+ },
41
+ "dependencies": {
42
+ "@hcengineering/account-client": "^0.7.0",
43
+ "@hcengineering/chunter": "^0.7.0",
44
+ "@hcengineering/contact": "^0.7.0",
45
+ "@hcengineering/core": "^0.7.0",
46
+ "@hcengineering/document": "^0.7.0",
47
+ "@hcengineering/platform": "^0.7.0",
48
+ "@hcengineering/rank": "^0.7.0",
49
+ "@hcengineering/tags": "^0.7.0",
50
+ "@hcengineering/server-client": "^0.7.0",
51
+ "@hcengineering/task": "^0.7.0",
52
+ "@hcengineering/tracker": "^0.7.0",
53
+ "@modelcontextprotocol/sdk": "^1.10.0",
54
+ "ws": "^8.18.0",
55
+ "zod": "^3.23.0"
56
+ },
57
+ "devDependencies": {
58
+ "@types/node": "^22.0.0",
59
+ "@types/ws": "^8.5.0",
60
+ "tsx": "^4.19.0",
61
+ "typescript": "^5.7.0"
62
+ }
63
+ }
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Delete a range of issues from a Huly project.
4
+ *
5
+ * Usage:
6
+ * node scripts/cleanup-issues.js <project> <start> <end> [--confirm]
7
+ *
8
+ * Example (dry run — shows what would be deleted):
9
+ * node scripts/cleanup-issues.js VISIO 59 68
10
+ *
11
+ * Example (actually delete):
12
+ * node scripts/cleanup-issues.js VISIO 59 68 --confirm
13
+ *
14
+ * Auth is read from .env (HULY_TOKEN + HULY_WORKSPACE, or HULY_EMAIL + HULY_PASSWORD).
15
+ */
16
+
17
+ 'use strict'
18
+
19
+ const fs = require('fs')
20
+ const path = require('path')
21
+
22
+ // Load .env
23
+ const envPath = path.join(__dirname, '..', '.env')
24
+ if (fs.existsSync(envPath)) {
25
+ for (const line of fs.readFileSync(envPath, 'utf8').split('\n')) {
26
+ const m = line.match(/^([^#=\s]+)\s*=\s*(.*)$/)
27
+ if (m) process.env[m[1]] = m[2].trim()
28
+ }
29
+ }
30
+
31
+ // Load compiled modules
32
+ const distDir = path.join(__dirname, '..', 'dist')
33
+ if (!fs.existsSync(path.join(distDir, 'connection.js'))) {
34
+ console.error('āŒ dist/ not found. Run `npm run build` first.')
35
+ process.exit(1)
36
+ }
37
+ const { getConnection, closeConnection } = require(path.join(distDir, 'connection'))
38
+ const tracker = require('@hcengineering/tracker')
39
+
40
+ // Parse args
41
+ const args = process.argv.slice(2)
42
+ const confirm = args.includes('--confirm')
43
+ const positional = args.filter(a => !a.startsWith('--'))
44
+
45
+ const [projectIdentifier, startStr, endStr] = positional
46
+ if (!projectIdentifier || !startStr || !endStr) {
47
+ console.error('Usage: node scripts/cleanup-issues.js <project> <start> <end> [--confirm]')
48
+ console.error('Example: node scripts/cleanup-issues.js VISIO 59 68 --confirm')
49
+ process.exit(1)
50
+ }
51
+
52
+ const start = parseInt(startStr, 10)
53
+ const end = parseInt(endStr, 10)
54
+ if (isNaN(start) || isNaN(end) || start > end) {
55
+ console.error('āŒ start and end must be valid numbers with start <= end')
56
+ process.exit(1)
57
+ }
58
+
59
+ async function main () {
60
+ const identifiers = []
61
+ for (let n = start; n <= end; n++) {
62
+ identifiers.push(`${projectIdentifier}-${n}`)
63
+ }
64
+
65
+ console.log(`\nšŸ” Huly Issue Cleanup`)
66
+ console.log(` Project: ${projectIdentifier}`)
67
+ console.log(` Range: ${projectIdentifier}-${start} → ${projectIdentifier}-${end} (${identifiers.length} issues)`)
68
+ console.log(` Mode: ${confirm ? 'āš ļø DELETING for real' : 'šŸ‘ DRY RUN (pass --confirm to delete)'}\n`)
69
+
70
+ console.log('šŸ”Œ Connecting to Huly...')
71
+ const client = await getConnection()
72
+ console.log('āœ… Connected.\n')
73
+
74
+ // Find all issues in the range
75
+ const issues = await client.findAll(
76
+ tracker.default.class.Issue,
77
+ { identifier: { $in: identifiers } }
78
+ )
79
+
80
+ if (issues.length === 0) {
81
+ console.log('āœ… No matching issues found — nothing to delete.')
82
+ await closeConnection()
83
+ return
84
+ }
85
+
86
+ console.log(`Found ${issues.length} issue(s) to delete:\n`)
87
+ for (const issue of issues) {
88
+ console.log(` • ${issue.identifier} "${issue.title}"`)
89
+ }
90
+ console.log()
91
+
92
+ if (!confirm) {
93
+ console.log('ā„¹ļø This was a DRY RUN. To actually delete, run:')
94
+ console.log(` node scripts/cleanup-issues.js ${projectIdentifier} ${start} ${end} --confirm\n`)
95
+ await closeConnection()
96
+ return
97
+ }
98
+
99
+ // Delete
100
+ let deleted = 0
101
+ let failed = 0
102
+
103
+ for (const issue of issues) {
104
+ process.stdout.write(` Deleting ${issue.identifier}... `)
105
+ try {
106
+ await client.removeCollection(
107
+ tracker.default.class.Issue,
108
+ issue.space,
109
+ issue._id,
110
+ issue.attachedTo,
111
+ issue.attachedToClass,
112
+ issue.collection
113
+ )
114
+ console.log('āœ…')
115
+ deleted++
116
+ } catch (err) {
117
+ console.log(`āŒ ${err.message}`)
118
+ failed++
119
+ }
120
+ }
121
+
122
+ await closeConnection()
123
+
124
+ console.log(`\n${'─'.repeat(50)}`)
125
+ console.log(`āœ… Deleted: ${deleted} āŒ Failed: ${failed}`)
126
+ console.log(`${'─'.repeat(50)}\n`)
127
+ }
128
+
129
+ main().catch(err => {
130
+ console.error('\nāŒ Fatal error:', err.message)
131
+ process.exit(1)
132
+ })