@bgx4k3p/huly-mcp-server 2.1.0 → 2.2.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.
- package/README.md +1 -0
- package/node_modules/@msgpackr-extract/msgpackr-extract-linux-x64/README.md +1 -0
- package/node_modules/@msgpackr-extract/msgpackr-extract-linux-x64/node.abi115.glibc.node +0 -0
- package/node_modules/@msgpackr-extract/msgpackr-extract-linux-x64/node.abi115.musl.node +0 -0
- package/node_modules/@msgpackr-extract/msgpackr-extract-linux-x64/node.napi.glibc.node +0 -0
- package/node_modules/@msgpackr-extract/msgpackr-extract-linux-x64/node.napi.musl.node +0 -0
- package/node_modules/@msgpackr-extract/{msgpackr-extract-darwin-x64 → msgpackr-extract-linux-x64}/package.json +3 -3
- package/package.json +8 -3
- package/src/client.mjs +101 -53
- package/src/dispatch.mjs +11 -11
- package/src/helpers.mjs +24 -0
- package/src/mcpShared.mjs +36 -24
- package/src/server.mjs +1 -1
- package/node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64/README.md +0 -1
- package/node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64/node.napi.glibc.node +0 -0
- /package/node_modules/@msgpackr-extract/{msgpackr-extract-darwin-x64 → msgpackr-extract-linux-x64}/index.js +0 -0
package/README.md
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# Huly MCP Server
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@bgx4k3p/huly-mcp-server)
|
|
3
4
|
[](LICENSE)
|
|
4
5
|
[](https://nodejs.org)
|
|
5
6
|
[](https://modelcontextprotocol.io)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Platform specific binary for msgpackr-extract on linux OS with x64 architecture
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "@msgpackr-extract/msgpackr-extract-
|
|
2
|
+
"name": "@msgpackr-extract/msgpackr-extract-linux-x64",
|
|
3
3
|
"version": "3.0.3",
|
|
4
4
|
"os": [
|
|
5
|
-
"
|
|
5
|
+
"linux"
|
|
6
6
|
],
|
|
7
7
|
"cpu": [
|
|
8
8
|
"x64"
|
|
@@ -13,5 +13,5 @@
|
|
|
13
13
|
"type": "git",
|
|
14
14
|
"url": "http://github.com/kriszyp/msgpackr-extract"
|
|
15
15
|
},
|
|
16
|
-
"description": "Platform specific binary for msgpackr-extract on
|
|
16
|
+
"description": "Platform specific binary for msgpackr-extract on linux OS with x64 architecture"
|
|
17
17
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bgx4k3p/huly-mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "MCP server for Huly issue tracking with stdio and Streamable HTTP transports",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,8 +38,10 @@
|
|
|
38
38
|
"test": "HULY_TRANSPORT=ws node --test test/integration.test.mjs && HULY_TRANSPORT=rest node --test test/integration.test.mjs",
|
|
39
39
|
"test:ws": "HULY_TRANSPORT=ws node --test test/integration.test.mjs",
|
|
40
40
|
"test:rest": "HULY_TRANSPORT=rest node --test test/integration.test.mjs",
|
|
41
|
+
"lint": "eslint src/ scripts/",
|
|
41
42
|
"postinstall": "node scripts/patch-sdk.mjs",
|
|
42
|
-
"pack": "node scripts/pack.mjs"
|
|
43
|
+
"pack": "node scripts/pack.mjs",
|
|
44
|
+
"version:sync": "node -e \"const{readFileSync:r,writeFileSync:w}=require('fs');const v=r('VERSION','utf-8').trim();const f='package.json';const j=JSON.parse(r(f,'utf-8'));j.version=v;w(f,JSON.stringify(j,null,2)+'\\n');console.log('Synced version '+v+' to package.json')\""
|
|
43
45
|
},
|
|
44
46
|
"dependencies": {
|
|
45
47
|
"@hcengineering/api-client": "^0.7.3",
|
|
@@ -58,5 +60,8 @@
|
|
|
58
60
|
"@hcengineering/tags",
|
|
59
61
|
"@hcengineering/task",
|
|
60
62
|
"@hcengineering/tracker"
|
|
61
|
-
]
|
|
63
|
+
],
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"eslint": "^10.1.0"
|
|
66
|
+
}
|
|
62
67
|
}
|
package/src/client.mjs
CHANGED
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
import {
|
|
8
8
|
PRIORITY_MAP, PRIORITY_NAMES,
|
|
9
9
|
MILESTONE_STATUS_MAP, MILESTONE_STATUS_NAMES,
|
|
10
|
-
|
|
10
|
+
resolveColor,
|
|
11
11
|
DONE_CATEGORY, LOST_CATEGORY, STATUS_CATEGORY_NAMES,
|
|
12
12
|
DEFAULT_LABEL_CATEGORY, DEFAULT_LABEL_COLOR,
|
|
13
13
|
PAGE_SIZE, MAX_BATCH_SIZE, AUTH_CACHE_TTL_MS, DEFAULT_MILESTONE_DAYS,
|
|
14
|
+
DEFAULT_PAGE_SIZE, DEFAULT_DETAIL_PAGE_SIZE,
|
|
15
|
+
encodeCursor, decodeCursor,
|
|
14
16
|
nameMatch, strictGet, withExtra,
|
|
15
17
|
toCollaboratorMarkup, fromCollaboratorMarkup,
|
|
16
18
|
toMarkup, fromMarkup
|
|
@@ -749,7 +751,11 @@ export class HulyClient {
|
|
|
749
751
|
if (projectType?.tasks?.length) {
|
|
750
752
|
const taskTypes = await client.findAll(task.class.TaskType, {});
|
|
751
753
|
const scoped = taskTypes.filter(tt => projectType.tasks.includes(tt._id));
|
|
752
|
-
if (scoped.length)
|
|
754
|
+
if (scoped.length) {
|
|
755
|
+
// Prefer the type named "Task" (the common default), not the first in list (often Epic)
|
|
756
|
+
const taskType = scoped.find(tt => nameMatch(tt.name || tt._id.split(':').pop(), 'Task'));
|
|
757
|
+
return (taskType || scoped[0])._id;
|
|
758
|
+
}
|
|
753
759
|
}
|
|
754
760
|
|
|
755
761
|
throw new Error(
|
|
@@ -785,18 +791,23 @@ export class HulyClient {
|
|
|
785
791
|
* The SDK's findAll has a server-side page limit. If limit exceeds
|
|
786
792
|
* the page size, this fetches multiple pages using createdOn cursor.
|
|
787
793
|
*/
|
|
794
|
+
/**
|
|
795
|
+
* Paginated findAll with cursor support.
|
|
796
|
+
* Fetches from the SDK in PAGE_SIZE batches, returns { items, nextCursor? }.
|
|
797
|
+
* @param {Object} client - SDK client
|
|
798
|
+
* @param {string} _class - Document class
|
|
799
|
+
* @param {Object} query - Query filter
|
|
800
|
+
* @param {Object} options - { limit, cursor, ...findAllOptions }
|
|
801
|
+
* @returns {{ items: Object[], nextCursor?: string }}
|
|
802
|
+
*/
|
|
788
803
|
async _paginatedFindAll(client, _class, query, options = {}) {
|
|
789
|
-
const limit = options.limit ||
|
|
804
|
+
const limit = options.limit || DEFAULT_PAGE_SIZE;
|
|
805
|
+
let lastCreatedOn = options.cursor
|
|
806
|
+
? decodeCursor(options.cursor).createdOn
|
|
807
|
+
: undefined;
|
|
790
808
|
|
|
791
|
-
// If within a single page, just fetch directly
|
|
792
|
-
if (limit <= PAGE_SIZE) {
|
|
793
|
-
return await client.findAll(_class, query, options);
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
// Fetch in pages using createdOn as cursor
|
|
797
809
|
const allResults = [];
|
|
798
|
-
let remaining = limit;
|
|
799
|
-
let lastCreatedOn = undefined;
|
|
810
|
+
let remaining = limit + 1; // fetch one extra to detect next page
|
|
800
811
|
|
|
801
812
|
while (remaining > 0) {
|
|
802
813
|
const pageLimit = Math.min(remaining, PAGE_SIZE);
|
|
@@ -817,11 +828,42 @@ export class HulyClient {
|
|
|
817
828
|
remaining -= page.length;
|
|
818
829
|
lastCreatedOn = page[page.length - 1].createdOn;
|
|
819
830
|
|
|
820
|
-
// If we got less than requested, no more pages
|
|
821
831
|
if (page.length < pageLimit) break;
|
|
822
832
|
}
|
|
823
833
|
|
|
824
|
-
|
|
834
|
+
// If we got more than limit, there are more results
|
|
835
|
+
if (allResults.length > limit) {
|
|
836
|
+
const items = allResults.slice(0, limit);
|
|
837
|
+
return { items, nextCursor: encodeCursor(items[items.length - 1].createdOn) };
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
return { items: allResults };
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* In-memory cursor pagination for small collections fetched via findAll.
|
|
845
|
+
* Filters by cursor, sorts by createdOn desc, slices to limit.
|
|
846
|
+
* @param {Object[]} allResults - Full result set from findAll
|
|
847
|
+
* @param {Object} options - { cursor?, limit? }
|
|
848
|
+
* @returns {{ items: Object[], nextCursor?: string }}
|
|
849
|
+
*/
|
|
850
|
+
_cursoredFindAll(allResults, options = {}) {
|
|
851
|
+
const { cursor, limit = DEFAULT_PAGE_SIZE } = options;
|
|
852
|
+
let items = [...allResults];
|
|
853
|
+
|
|
854
|
+
if (cursor) {
|
|
855
|
+
const { createdOn } = decodeCursor(cursor);
|
|
856
|
+
items = items.filter(r => r.createdOn < createdOn);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
items.sort((a, b) => b.createdOn - a.createdOn);
|
|
860
|
+
|
|
861
|
+
if (items.length > limit) {
|
|
862
|
+
const page = items.slice(0, limit);
|
|
863
|
+
return { items: page, nextCursor: encodeCursor(page[page.length - 1].createdOn) };
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
return { items };
|
|
825
867
|
}
|
|
826
868
|
|
|
827
869
|
async _findEmployeeByName(client, name) {
|
|
@@ -921,8 +963,7 @@ export class HulyClient {
|
|
|
921
963
|
const projects = await client.findAll(tracker.class.Project, {});
|
|
922
964
|
|
|
923
965
|
if (!options.include_details) {
|
|
924
|
-
|
|
925
|
-
return projects.map(project => withExtra(project, {
|
|
966
|
+
const enriched = projects.map(project => withExtra(project, {
|
|
926
967
|
id: project._id,
|
|
927
968
|
identifier: project.identifier,
|
|
928
969
|
name: project.name || project.identifier,
|
|
@@ -934,6 +975,7 @@ export class HulyClient {
|
|
|
934
975
|
createdOn: project.createdOn,
|
|
935
976
|
modifiedOn: project.modifiedOn
|
|
936
977
|
}));
|
|
978
|
+
return this._cursoredFindAll(enriched, options);
|
|
937
979
|
}
|
|
938
980
|
|
|
939
981
|
// Detailed mode: batch fetch all related data once, then group by project
|
|
@@ -960,7 +1002,7 @@ export class HulyClient {
|
|
|
960
1002
|
componentsByProject.get(c.space).push(c);
|
|
961
1003
|
}
|
|
962
1004
|
|
|
963
|
-
|
|
1005
|
+
const detailed = limitedProjects.map(project => {
|
|
964
1006
|
const projMilestones = (milestonesByProject.get(project._id) || []).map(m => ({
|
|
965
1007
|
name: m.label,
|
|
966
1008
|
status: strictGet(MILESTONE_STATUS_NAMES, m.status, 'Milestone status'),
|
|
@@ -993,6 +1035,7 @@ export class HulyClient {
|
|
|
993
1035
|
labels: projLabels
|
|
994
1036
|
});
|
|
995
1037
|
});
|
|
1038
|
+
return this._cursoredFindAll(detailed, options);
|
|
996
1039
|
}
|
|
997
1040
|
|
|
998
1041
|
/**
|
|
@@ -1067,9 +1110,9 @@ export class HulyClient {
|
|
|
1067
1110
|
* @param {number} [limit=500] - Maximum number of issues
|
|
1068
1111
|
* @returns {Promise<Object[]>}
|
|
1069
1112
|
*/
|
|
1070
|
-
async listIssues(project, status, priority, label, milestone, limit, include_details = false) {
|
|
1113
|
+
async listIssues(project, status, priority, label, milestone, limit, include_details = false, cursor) {
|
|
1071
1114
|
if (limit === undefined || limit === null) {
|
|
1072
|
-
limit = include_details ?
|
|
1115
|
+
limit = include_details ? DEFAULT_DETAIL_PAGE_SIZE : DEFAULT_PAGE_SIZE;
|
|
1073
1116
|
}
|
|
1074
1117
|
const client = await this._getClient();
|
|
1075
1118
|
|
|
@@ -1112,10 +1155,11 @@ export class HulyClient {
|
|
|
1112
1155
|
}
|
|
1113
1156
|
}
|
|
1114
1157
|
|
|
1115
|
-
|
|
1116
|
-
limit,
|
|
1117
|
-
sort: { modifiedOn: -1 }
|
|
1158
|
+
const fetchResult = await this._paginatedFindAll(client, tracker.class.Issue, query, {
|
|
1159
|
+
limit, cursor
|
|
1118
1160
|
});
|
|
1161
|
+
const issues = fetchResult.items;
|
|
1162
|
+
const nextCursor = fetchResult.nextCursor;
|
|
1119
1163
|
|
|
1120
1164
|
let labelFilter = null;
|
|
1121
1165
|
if (label) {
|
|
@@ -1267,7 +1311,9 @@ export class HulyClient {
|
|
|
1267
1311
|
result.push(withExtra(issue, entry));
|
|
1268
1312
|
}
|
|
1269
1313
|
|
|
1270
|
-
|
|
1314
|
+
const response = { items: result };
|
|
1315
|
+
if (nextCursor) response.nextCursor = nextCursor;
|
|
1316
|
+
return response;
|
|
1271
1317
|
}
|
|
1272
1318
|
|
|
1273
1319
|
/**
|
|
@@ -1650,20 +1696,21 @@ export class HulyClient {
|
|
|
1650
1696
|
* List all available labels for issues.
|
|
1651
1697
|
* @returns {Promise<Object[]>}
|
|
1652
1698
|
*/
|
|
1653
|
-
async listLabels() {
|
|
1699
|
+
async listLabels(options = {}) {
|
|
1654
1700
|
const client = await this._getClient();
|
|
1655
1701
|
|
|
1656
1702
|
const tagElements = await client.findAll(tags.class.TagElement, {
|
|
1657
1703
|
targetClass: tracker.class.Issue
|
|
1658
1704
|
});
|
|
1659
1705
|
|
|
1660
|
-
|
|
1706
|
+
const enriched = tagElements.map(t => withExtra(t, {
|
|
1661
1707
|
id: t._id,
|
|
1662
1708
|
name: t.title,
|
|
1663
1709
|
description: t.description || '',
|
|
1664
1710
|
color: t.color ? `#${t.color.toString(16).padStart(6, '0')}` : null,
|
|
1665
1711
|
category: t.category || null
|
|
1666
1712
|
}));
|
|
1713
|
+
return this._cursoredFindAll(enriched, options);
|
|
1667
1714
|
}
|
|
1668
1715
|
|
|
1669
1716
|
/**
|
|
@@ -1894,7 +1941,7 @@ export class HulyClient {
|
|
|
1894
1941
|
* @param {string} projectIdent - Project identifier
|
|
1895
1942
|
* @returns {Promise<Object[]>}
|
|
1896
1943
|
*/
|
|
1897
|
-
async listTaskTypes(projectIdent) {
|
|
1944
|
+
async listTaskTypes(projectIdent, options = {}) {
|
|
1898
1945
|
const client = await this._getClient();
|
|
1899
1946
|
|
|
1900
1947
|
const project = await client.findOne(tracker.class.Project, {
|
|
@@ -1923,7 +1970,7 @@ export class HulyClient {
|
|
|
1923
1970
|
);
|
|
1924
1971
|
}
|
|
1925
1972
|
|
|
1926
|
-
|
|
1973
|
+
const enriched = typesToReturn.map(tt => ({
|
|
1927
1974
|
id: tt._id,
|
|
1928
1975
|
name: tt.name || tt._id.split(':').pop(),
|
|
1929
1976
|
description: fromMarkup(tt.description),
|
|
@@ -1934,6 +1981,7 @@ export class HulyClient {
|
|
|
1934
1981
|
statusCategories: tt.statusCategories || [],
|
|
1935
1982
|
statuses: tt.statuses || []
|
|
1936
1983
|
}));
|
|
1984
|
+
return this._cursoredFindAll(enriched, options);
|
|
1937
1985
|
}
|
|
1938
1986
|
|
|
1939
1987
|
/**
|
|
@@ -1942,20 +1990,21 @@ export class HulyClient {
|
|
|
1942
1990
|
* @param {string} [taskTypeName] - Task type name to scope statuses (e.g., "Task", "Epic")
|
|
1943
1991
|
* @returns {Promise<Object[]>}
|
|
1944
1992
|
*/
|
|
1945
|
-
async listStatuses(projectIdent, taskTypeName) {
|
|
1993
|
+
async listStatuses(projectIdent, taskTypeName, options = {}) {
|
|
1946
1994
|
const client = await this._getClient();
|
|
1947
1995
|
|
|
1948
1996
|
const allStatuses = await client.findAll(tracker.class.IssueStatus, {});
|
|
1949
1997
|
|
|
1950
1998
|
// If no scoping requested, return all
|
|
1951
1999
|
if (!projectIdent && !taskTypeName) {
|
|
1952
|
-
|
|
2000
|
+
const enriched = allStatuses.map(s => ({
|
|
1953
2001
|
id: s._id,
|
|
1954
2002
|
name: s.name,
|
|
1955
2003
|
category: STATUS_CATEGORY_NAMES[s.category] || s.category,
|
|
1956
2004
|
color: s.color,
|
|
1957
2005
|
description: fromMarkup(s.description)
|
|
1958
2006
|
}));
|
|
2007
|
+
return this._cursoredFindAll(enriched, options);
|
|
1959
2008
|
}
|
|
1960
2009
|
|
|
1961
2010
|
// Get task types scoped to this project
|
|
@@ -1996,13 +2045,14 @@ export class HulyClient {
|
|
|
1996
2045
|
? allStatuses.filter(s => statusIds.has(s._id))
|
|
1997
2046
|
: allStatuses;
|
|
1998
2047
|
|
|
1999
|
-
|
|
2048
|
+
const enriched = scopedStatuses.map(s => ({
|
|
2000
2049
|
id: s._id,
|
|
2001
2050
|
name: s.name,
|
|
2002
2051
|
category: STATUS_CATEGORY_NAMES[s.category] || s.category,
|
|
2003
2052
|
color: s.color,
|
|
2004
2053
|
description: s.description || ''
|
|
2005
2054
|
}));
|
|
2055
|
+
return this._cursoredFindAll(enriched, options);
|
|
2006
2056
|
}
|
|
2007
2057
|
|
|
2008
2058
|
/**
|
|
@@ -2036,7 +2086,7 @@ export class HulyClient {
|
|
|
2036
2086
|
});
|
|
2037
2087
|
|
|
2038
2088
|
if (!options.include_details) {
|
|
2039
|
-
|
|
2089
|
+
const enriched = milestones.map(m => withExtra(m, {
|
|
2040
2090
|
id: m._id,
|
|
2041
2091
|
name: m.label,
|
|
2042
2092
|
description: fromMarkup(m.description),
|
|
@@ -2044,6 +2094,7 @@ export class HulyClient {
|
|
|
2044
2094
|
targetDate: m.targetDate ? new Date(m.targetDate).toISOString().split('T')[0] : null,
|
|
2045
2095
|
comments: m.comments || 0
|
|
2046
2096
|
}));
|
|
2097
|
+
return this._cursoredFindAll(enriched, options);
|
|
2047
2098
|
}
|
|
2048
2099
|
|
|
2049
2100
|
// Detailed mode: batch fetch all issues for the project, then group by milestone
|
|
@@ -2059,7 +2110,7 @@ export class HulyClient {
|
|
|
2059
2110
|
}
|
|
2060
2111
|
}
|
|
2061
2112
|
|
|
2062
|
-
|
|
2113
|
+
const detailed = milestones.map(m => {
|
|
2063
2114
|
const mIssues = (issuesByMilestone.get(m._id) || []).map(i => ({
|
|
2064
2115
|
id: `${project.identifier}-${i.number}`,
|
|
2065
2116
|
title: i.title,
|
|
@@ -2077,6 +2128,7 @@ export class HulyClient {
|
|
|
2077
2128
|
issues: mIssues
|
|
2078
2129
|
});
|
|
2079
2130
|
});
|
|
2131
|
+
return this._cursoredFindAll(detailed, options);
|
|
2080
2132
|
}
|
|
2081
2133
|
|
|
2082
2134
|
/**
|
|
@@ -2253,16 +2305,17 @@ export class HulyClient {
|
|
|
2253
2305
|
* List all active workspace members.
|
|
2254
2306
|
* @returns {Promise<Object[]>}
|
|
2255
2307
|
*/
|
|
2256
|
-
async listMembers() {
|
|
2308
|
+
async listMembers(options = {}) {
|
|
2257
2309
|
const client = await this._getClient();
|
|
2258
2310
|
const employees = await client.findAll(contactPlugin.mixin.Employee, { active: true });
|
|
2259
|
-
|
|
2311
|
+
const enriched = employees.map(e => withExtra(e, {
|
|
2260
2312
|
id: e._id,
|
|
2261
2313
|
name: e.name,
|
|
2262
2314
|
email: e.channels?.[0]?.value || null,
|
|
2263
2315
|
role: e.role || 'USER',
|
|
2264
2316
|
position: e.position || null
|
|
2265
2317
|
}));
|
|
2318
|
+
return this._cursoredFindAll(enriched, options);
|
|
2266
2319
|
}
|
|
2267
2320
|
|
|
2268
2321
|
/**
|
|
@@ -2326,7 +2379,7 @@ export class HulyClient {
|
|
|
2326
2379
|
* @param {string} issueId - Issue identifier
|
|
2327
2380
|
* @returns {Promise<Object[]>}
|
|
2328
2381
|
*/
|
|
2329
|
-
async listComments(issueId) {
|
|
2382
|
+
async listComments(issueId, options = {}) {
|
|
2330
2383
|
const client = await this._getClient();
|
|
2331
2384
|
const { issue } = await this._parseAndFindIssue(client, issueId);
|
|
2332
2385
|
|
|
@@ -2334,13 +2387,14 @@ export class HulyClient {
|
|
|
2334
2387
|
attachedTo: issue._id
|
|
2335
2388
|
}, { sort: { createdOn: 1 } });
|
|
2336
2389
|
|
|
2337
|
-
|
|
2390
|
+
const enriched = comments.map(c => withExtra(c, {
|
|
2338
2391
|
id: c._id,
|
|
2339
2392
|
text: fromMarkup(c.message),
|
|
2340
2393
|
createdBy: c.createdBy || null,
|
|
2341
2394
|
createdOn: c.createdOn,
|
|
2342
2395
|
modifiedOn: c.modifiedOn
|
|
2343
2396
|
}));
|
|
2397
|
+
return this._cursoredFindAll(enriched, options);
|
|
2344
2398
|
}
|
|
2345
2399
|
|
|
2346
2400
|
/**
|
|
@@ -2574,10 +2628,10 @@ export class HulyClient {
|
|
|
2574
2628
|
}
|
|
2575
2629
|
}
|
|
2576
2630
|
|
|
2577
|
-
|
|
2578
|
-
limit
|
|
2579
|
-
sort: { modifiedOn: -1 }
|
|
2631
|
+
const fetchResult = await this._paginatedFindAll(client, tracker.class.Issue, query, {
|
|
2632
|
+
limit
|
|
2580
2633
|
});
|
|
2634
|
+
const issues = fetchResult.items;
|
|
2581
2635
|
|
|
2582
2636
|
const projects = await client.findAll(tracker.class.Project, {});
|
|
2583
2637
|
const projMap = new Map(projects.map(p => [p._id, p.identifier]));
|
|
@@ -3367,7 +3421,7 @@ export class HulyClient {
|
|
|
3367
3421
|
|
|
3368
3422
|
// ── Components ──────────────────────────────────────────────
|
|
3369
3423
|
|
|
3370
|
-
async listComponents(projectIdent) {
|
|
3424
|
+
async listComponents(projectIdent, options = {}) {
|
|
3371
3425
|
const client = await this._getClient();
|
|
3372
3426
|
const project = await client.findOne(tracker.class.Project, {
|
|
3373
3427
|
identifier: projectIdent.toUpperCase()
|
|
@@ -3376,12 +3430,13 @@ export class HulyClient {
|
|
|
3376
3430
|
|
|
3377
3431
|
const components = await client.findAll(tracker.class.Component, { space: project._id });
|
|
3378
3432
|
|
|
3379
|
-
|
|
3433
|
+
const enriched = components.map(c => withExtra(c, {
|
|
3380
3434
|
id: c._id,
|
|
3381
3435
|
name: c.label,
|
|
3382
3436
|
description: fromMarkup(c.description),
|
|
3383
3437
|
lead: c.lead || null
|
|
3384
3438
|
}));
|
|
3439
|
+
return this._cursoredFindAll(enriched, options);
|
|
3385
3440
|
}
|
|
3386
3441
|
|
|
3387
3442
|
async createComponent(projectIdent, name, description, lead, format) {
|
|
@@ -3486,7 +3541,7 @@ export class HulyClient {
|
|
|
3486
3541
|
|
|
3487
3542
|
// ── Time Reports ────────────────────────────────────────────
|
|
3488
3543
|
|
|
3489
|
-
async listTimeReports(issueId) {
|
|
3544
|
+
async listTimeReports(issueId, options = {}) {
|
|
3490
3545
|
const client = await this._getClient();
|
|
3491
3546
|
const { issue } = await this._parseAndFindIssue(client, issueId);
|
|
3492
3547
|
|
|
@@ -3494,12 +3549,13 @@ export class HulyClient {
|
|
|
3494
3549
|
attachedTo: issue._id
|
|
3495
3550
|
}, { sort: { date: -1 } });
|
|
3496
3551
|
|
|
3497
|
-
|
|
3552
|
+
const enriched = reports.map(r => withExtra(r, {
|
|
3498
3553
|
id: r._id,
|
|
3499
3554
|
hours: r.value,
|
|
3500
3555
|
description: fromMarkup(r.description),
|
|
3501
3556
|
date: r.date ? new Date(r.date).toISOString() : null
|
|
3502
3557
|
}));
|
|
3558
|
+
return this._cursoredFindAll(enriched, options);
|
|
3503
3559
|
}
|
|
3504
3560
|
|
|
3505
3561
|
async deleteTimeReport(reportId) {
|
|
@@ -3508,17 +3564,9 @@ export class HulyClient {
|
|
|
3508
3564
|
const report = await client.findOne(tracker.class.TimeSpendReport, { _id: reportId });
|
|
3509
3565
|
if (!report) throw new Error(`Time report not found: ${reportId}`);
|
|
3510
3566
|
|
|
3511
|
-
//
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
if (issue) {
|
|
3515
|
-
const newReported = Math.max(0, (issue.reportedTime || 0) - (report.value || 0));
|
|
3516
|
-
await client.updateDoc(tracker.class.Issue, issue.space, issue._id, {
|
|
3517
|
-
reportedTime: newReported
|
|
3518
|
-
});
|
|
3519
|
-
}
|
|
3520
|
-
}
|
|
3521
|
-
|
|
3567
|
+
// The transactor automatically decrements reportedTime on the issue
|
|
3568
|
+
// when a TimeSpendReport is removed via removeCollection — do NOT
|
|
3569
|
+
// manually update reportedTime here or it gets decremented twice.
|
|
3522
3570
|
await client.removeCollection(tracker.class.TimeSpendReport, report.space, report._id, report.attachedTo, report.attachedToClass, report.collection);
|
|
3523
3571
|
|
|
3524
3572
|
return {
|
package/src/dispatch.mjs
CHANGED
|
@@ -78,11 +78,11 @@ export const accountTools = {
|
|
|
78
78
|
*/
|
|
79
79
|
export const workspaceTools = {
|
|
80
80
|
list_projects: (a, c) =>
|
|
81
|
-
c.listProjects({ include_details: a.include_details }),
|
|
81
|
+
c.listProjects({ include_details: a.include_details, cursor: a.cursor, limit: a.limit }),
|
|
82
82
|
get_project: (a, c) =>
|
|
83
83
|
c.getProject(a.project, { include_details: a.include_details }),
|
|
84
84
|
list_issues: (a, c) =>
|
|
85
|
-
c.listIssues(a.project, a.status, a.priority, a.label, a.milestone, a.limit, a.include_details),
|
|
85
|
+
c.listIssues(a.project, a.status, a.priority, a.label, a.milestone, a.limit, a.include_details, a.cursor),
|
|
86
86
|
get_issue: (a, c) =>
|
|
87
87
|
c.getIssue(a.issueId, { include_details: a.include_details }),
|
|
88
88
|
create_issue: (a, c) =>
|
|
@@ -113,7 +113,7 @@ export const workspaceTools = {
|
|
|
113
113
|
// Labels
|
|
114
114
|
add_label: (a, c) => c.addLabel(a.issueId, a.label),
|
|
115
115
|
remove_label: (a, c) => c.removeLabel(a.issueId, a.label),
|
|
116
|
-
list_labels: (a, c) => c.listLabels(),
|
|
116
|
+
list_labels: (a, c) => c.listLabels({ cursor: a.cursor, limit: a.limit }),
|
|
117
117
|
create_label: (a, c) => c.createLabel(a.name, a.color, a.description),
|
|
118
118
|
update_label: (a, c) =>
|
|
119
119
|
c.updateLabel(a.name, { newName: a.newName, color: a.color, description: a.description }),
|
|
@@ -121,14 +121,14 @@ export const workspaceTools = {
|
|
|
121
121
|
// Relations
|
|
122
122
|
add_relation: (a, c) => c.addRelation(a.issueId, a.relatedToIssueId),
|
|
123
123
|
add_blocked_by: (a, c) => c.addBlockedBy(a.issueId, a.blockedByIssueId),
|
|
124
|
-
set_parent: (a, c) => c.setParent(a.issueId, a.
|
|
124
|
+
set_parent: (a, c) => c.setParent(a.issueId, a.parentId),
|
|
125
125
|
|
|
126
126
|
// Task types & statuses
|
|
127
|
-
list_task_types: (a, c) => c.listTaskTypes(a.project),
|
|
128
|
-
list_statuses: (a, c) => c.listStatuses(a.project, a.taskType),
|
|
127
|
+
list_task_types: (a, c) => c.listTaskTypes(a.project, { cursor: a.cursor, limit: a.limit }),
|
|
128
|
+
list_statuses: (a, c) => c.listStatuses(a.project, a.taskType, { cursor: a.cursor, limit: a.limit }),
|
|
129
129
|
|
|
130
130
|
// Milestones
|
|
131
|
-
list_milestones: (a, c) => c.listMilestones(a.project, a.status, { include_details: a.include_details }),
|
|
131
|
+
list_milestones: (a, c) => c.listMilestones(a.project, a.status, { include_details: a.include_details, cursor: a.cursor, limit: a.limit }),
|
|
132
132
|
get_milestone: (a, c) => c.getMilestone(a.project, a.name, { include_details: a.include_details }),
|
|
133
133
|
create_milestone: (a, c) =>
|
|
134
134
|
c.createMilestone(a.project, a.name, a.description, a.targetDate, a.status, a.descriptionFormat),
|
|
@@ -141,17 +141,17 @@ export const workspaceTools = {
|
|
|
141
141
|
delete_milestone: (a, c) => c.deleteMilestone(a.project, a.name),
|
|
142
142
|
|
|
143
143
|
// Members
|
|
144
|
-
list_members: (a, c) => c.listMembers(),
|
|
144
|
+
list_members: (a, c) => c.listMembers({ cursor: a.cursor, limit: a.limit }),
|
|
145
145
|
|
|
146
146
|
// Comments
|
|
147
147
|
add_comment: (a, c) => c.addComment(a.issueId, a.text, a.format),
|
|
148
|
-
list_comments: (a, c) => c.listComments(a.issueId),
|
|
148
|
+
list_comments: (a, c) => c.listComments(a.issueId, { cursor: a.cursor, limit: a.limit }),
|
|
149
149
|
update_comment: (a, c) => c.updateComment(a.issueId, a.commentId, a.text, a.format),
|
|
150
150
|
delete_comment: (a, c) => c.deleteComment(a.issueId, a.commentId),
|
|
151
151
|
|
|
152
152
|
// Time tracking
|
|
153
153
|
log_time: (a, c) => c.logTime(a.issueId, a.hours, a.description, a.descriptionFormat, a.date, a.employee),
|
|
154
|
-
list_time_reports: (a, c) => c.listTimeReports(a.issueId),
|
|
154
|
+
list_time_reports: (a, c) => c.listTimeReports(a.issueId, { cursor: a.cursor, limit: a.limit }),
|
|
155
155
|
delete_time_report: (a, c) => c.deleteTimeReport(a.reportId),
|
|
156
156
|
|
|
157
157
|
// Projects
|
|
@@ -166,7 +166,7 @@ export const workspaceTools = {
|
|
|
166
166
|
delete_project: (a, c) => c.deleteProject(a.project),
|
|
167
167
|
|
|
168
168
|
// Components
|
|
169
|
-
list_components: (a, c) => c.listComponents(a.project),
|
|
169
|
+
list_components: (a, c) => c.listComponents(a.project, { cursor: a.cursor, limit: a.limit }),
|
|
170
170
|
create_component: (a, c) =>
|
|
171
171
|
c.createComponent(a.project, a.name, a.description, a.lead, a.descriptionFormat),
|
|
172
172
|
update_component: (a, c) =>
|
package/src/helpers.mjs
CHANGED
|
@@ -100,6 +100,30 @@ export const PAGE_SIZE = 500;
|
|
|
100
100
|
export const MAX_BATCH_SIZE = 500;
|
|
101
101
|
export const AUTH_CACHE_TTL_MS = 600000;
|
|
102
102
|
export const DEFAULT_MILESTONE_DAYS = 30;
|
|
103
|
+
export const DEFAULT_PAGE_SIZE = 50;
|
|
104
|
+
export const DEFAULT_DETAIL_PAGE_SIZE = 20;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Encode a pagination cursor from a createdOn timestamp.
|
|
108
|
+
* Returns an opaque base64url string.
|
|
109
|
+
*/
|
|
110
|
+
export function encodeCursor(createdOn) {
|
|
111
|
+
return Buffer.from(JSON.stringify({ createdOn })).toString('base64url');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Decode a pagination cursor back to { createdOn }.
|
|
116
|
+
* Throws on invalid input.
|
|
117
|
+
*/
|
|
118
|
+
export function decodeCursor(cursor) {
|
|
119
|
+
try {
|
|
120
|
+
const parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString());
|
|
121
|
+
if (typeof parsed.createdOn !== 'number') throw new Error();
|
|
122
|
+
return parsed;
|
|
123
|
+
} catch {
|
|
124
|
+
throw new Error('Invalid pagination cursor');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
103
127
|
|
|
104
128
|
/**
|
|
105
129
|
* Resolve a color value: name ("blue"), palette index (9), or RGB (0x5E6AD2).
|
package/src/mcpShared.mjs
CHANGED
|
@@ -31,6 +31,18 @@ const workspaceProp = {
|
|
|
31
31
|
}
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
// Pagination properties for all list tools
|
|
35
|
+
const paginationProps = {
|
|
36
|
+
cursor: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
description: 'Opaque pagination cursor from a previous response\'s nextCursor field. Omit for the first page.'
|
|
39
|
+
},
|
|
40
|
+
limit: {
|
|
41
|
+
type: 'number',
|
|
42
|
+
description: 'Maximum items per page (default: 50, or 20 with include_details)'
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
34
46
|
// ── Tool Definitions ──────────────────────────────────────────
|
|
35
47
|
|
|
36
48
|
export { workspaceProp };
|
|
@@ -112,9 +124,9 @@ export function createMcpServer(capabilities = {}) {
|
|
|
112
124
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
113
125
|
try {
|
|
114
126
|
const client = await pool.getClient();
|
|
115
|
-
const
|
|
127
|
+
const result = await client.withReconnect(() => client.listProjects());
|
|
116
128
|
return {
|
|
117
|
-
resources:
|
|
129
|
+
resources: result.items.map(p => ({
|
|
118
130
|
uri: `huly://projects/${p.identifier}`,
|
|
119
131
|
name: `${p.identifier}: ${p.name}`,
|
|
120
132
|
description: `Project with ${p.issueCount} issues`,
|
|
@@ -324,7 +336,7 @@ function getToolDefinitions() {
|
|
|
324
336
|
{
|
|
325
337
|
name: 'list_projects',
|
|
326
338
|
description: 'List all projects in the Huly workspace. Returns each project\'s identifier (e.g., "PROJ"), display name, and total issue count. Use this first to discover available projects before querying issues. Set include_details=true to also fetch milestones, components, labels, and member names for each project (limited to 20 projects).',
|
|
327
|
-
inputSchema: { type: 'object', properties: { include_details: { type: 'boolean', description: 'Include milestones, components, labels, and members for each project (default: false). Limits to 20 projects.' }, ...workspaceProp }, required: [] }
|
|
339
|
+
inputSchema: { type: 'object', properties: { include_details: { type: 'boolean', description: 'Include milestones, components, labels, and members for each project (default: false). Limits to 20 projects.' }, ...paginationProps, ...workspaceProp }, required: [] }
|
|
328
340
|
},
|
|
329
341
|
{
|
|
330
342
|
name: 'get_project',
|
|
@@ -333,8 +345,8 @@ function getToolDefinitions() {
|
|
|
333
345
|
},
|
|
334
346
|
{
|
|
335
347
|
name: 'list_issues',
|
|
336
|
-
description: 'List issues in a project with optional filtering
|
|
337
|
-
inputSchema: { type: 'object', properties: { project: { type: 'string', description: 'Project identifier (e.g., "PROJ")' }, status: { type: 'string', description: 'Filter by status: Backlog, Todo, In Progress, Done, Canceled' }, priority: { type: 'string', description: 'Filter by priority: urgent, high, medium, low, none' }, label: { type: 'string', description: 'Filter by label name (exact match)' }, milestone: { type: 'string', description: 'Filter by milestone name (exact match)' },
|
|
348
|
+
description: 'List issues in a project with optional filtering and cursor-based pagination. Returns { items, nextCursor? }. Pass nextCursor from a previous response to get the next page. Default page size: 50 (20 with include_details).',
|
|
349
|
+
inputSchema: { type: 'object', properties: { project: { type: 'string', description: 'Project identifier (e.g., "PROJ")' }, status: { type: 'string', description: 'Filter by status: Backlog, Todo, In Progress, Done, Canceled' }, priority: { type: 'string', description: 'Filter by priority: urgent, high, medium, low, none' }, label: { type: 'string', description: 'Filter by label name (exact match)' }, milestone: { type: 'string', description: 'Filter by milestone name (exact match)' }, include_details: { type: 'boolean', description: 'Include full details: descriptions, comments, time reports, relations, and children. Reduces default page size to 20.' }, ...paginationProps, ...workspaceProp }, required: ['project'] }
|
|
338
350
|
},
|
|
339
351
|
{
|
|
340
352
|
name: 'get_issue',
|
|
@@ -363,8 +375,8 @@ function getToolDefinitions() {
|
|
|
363
375
|
},
|
|
364
376
|
{
|
|
365
377
|
name: 'list_labels',
|
|
366
|
-
description: 'List all available labels in the workspace. Returns
|
|
367
|
-
inputSchema: { type: 'object', properties: { ...workspaceProp }, required: [] }
|
|
378
|
+
description: 'List all available labels in the workspace. Returns { items, nextCursor? }. Each label has name and hex color.',
|
|
379
|
+
inputSchema: { type: 'object', properties: { ...paginationProps, ...workspaceProp }, required: [] }
|
|
368
380
|
},
|
|
369
381
|
{
|
|
370
382
|
name: 'create_label',
|
|
@@ -459,8 +471,8 @@ function getToolDefinitions() {
|
|
|
459
471
|
// ── Components ───────────────────────────────────────────
|
|
460
472
|
{
|
|
461
473
|
name: 'list_components',
|
|
462
|
-
description: 'List all components in a project.',
|
|
463
|
-
inputSchema: { type: 'object', properties: { project: { type: 'string', description: 'Project identifier' }, ...workspaceProp }, required: ['project'] }
|
|
474
|
+
description: 'List all components in a project. Returns { items, nextCursor? }.',
|
|
475
|
+
inputSchema: { type: 'object', properties: { project: { type: 'string', description: 'Project identifier' }, ...paginationProps, ...workspaceProp }, required: ['project'] }
|
|
464
476
|
},
|
|
465
477
|
{
|
|
466
478
|
name: 'get_component',
|
|
@@ -470,12 +482,12 @@ function getToolDefinitions() {
|
|
|
470
482
|
{
|
|
471
483
|
name: 'create_component',
|
|
472
484
|
description: 'Create a new component in a project.',
|
|
473
|
-
inputSchema: { type: 'object', properties: { project: { type: 'string', description: 'Project identifier' }, name: { type: 'string', description: 'Component name' }, description: { type: 'string', description: 'Component description' }, ...workspaceProp }, required: ['project', 'name'] }
|
|
485
|
+
inputSchema: { type: 'object', properties: { project: { type: 'string', description: 'Project identifier' }, name: { type: 'string', description: 'Component name' }, description: { type: 'string', description: 'Component description' }, lead: { type: 'string', description: 'Lead member name' }, ...workspaceProp }, required: ['project', 'name'] }
|
|
474
486
|
},
|
|
475
487
|
{
|
|
476
488
|
name: 'update_component',
|
|
477
|
-
description: 'Update a component\'s name or
|
|
478
|
-
inputSchema: { type: 'object', properties: { project: { type: 'string', description: 'Project identifier' }, name: { type: 'string', description: 'Current component name' }, newName: { type: 'string', description: 'New component name' }, description: { type: 'string', description: 'New description' }, ...workspaceProp }, required: ['project', 'name'] }
|
|
489
|
+
description: 'Update a component\'s name, description, or lead.',
|
|
490
|
+
inputSchema: { type: 'object', properties: { project: { type: 'string', description: 'Project identifier' }, name: { type: 'string', description: 'Current component name' }, newName: { type: 'string', description: 'New component name' }, description: { type: 'string', description: 'New description' }, lead: { type: 'string', description: 'Lead member name (empty string to clear)' }, ...workspaceProp }, required: ['project', 'name'] }
|
|
479
491
|
},
|
|
480
492
|
{
|
|
481
493
|
name: 'delete_component',
|
|
@@ -486,8 +498,8 @@ function getToolDefinitions() {
|
|
|
486
498
|
// ── Milestones ───────────────────────────────────────────
|
|
487
499
|
{
|
|
488
500
|
name: 'list_milestones',
|
|
489
|
-
description: 'List all milestones in a project. Returns
|
|
490
|
-
inputSchema: { type: 'object', properties: { project: { type: 'string', description: 'Project identifier' }, include_details: { type: 'boolean', description: 'Include issue list for each milestone' }, ...workspaceProp }, required: ['project'] }
|
|
501
|
+
description: 'List all milestones in a project. Returns { items, nextCursor? }.',
|
|
502
|
+
inputSchema: { type: 'object', properties: { project: { type: 'string', description: 'Project identifier' }, include_details: { type: 'boolean', description: 'Include issue list for each milestone' }, ...paginationProps, ...workspaceProp }, required: ['project'] }
|
|
491
503
|
},
|
|
492
504
|
{
|
|
493
505
|
name: 'get_milestone',
|
|
@@ -518,8 +530,8 @@ function getToolDefinitions() {
|
|
|
518
530
|
// ── Members ──────────────────────────────────────────────
|
|
519
531
|
{
|
|
520
532
|
name: 'list_members',
|
|
521
|
-
description: 'List all active members of the workspace
|
|
522
|
-
inputSchema: { type: 'object', properties: { ...workspaceProp }, required: [] }
|
|
533
|
+
description: 'List all active members of the workspace. Returns { items, nextCursor? }.',
|
|
534
|
+
inputSchema: { type: 'object', properties: { ...paginationProps, ...workspaceProp }, required: [] }
|
|
523
535
|
},
|
|
524
536
|
{
|
|
525
537
|
name: 'get_member',
|
|
@@ -530,8 +542,8 @@ function getToolDefinitions() {
|
|
|
530
542
|
// ── Comments ─────────────────────────────────────────────
|
|
531
543
|
{
|
|
532
544
|
name: 'list_comments',
|
|
533
|
-
description: 'List
|
|
534
|
-
inputSchema: { type: 'object', properties: { issueId: { type: 'string', description: 'Issue identifier (e.g., "PROJ-42")' }, ...workspaceProp }, required: ['issueId'] }
|
|
545
|
+
description: 'List comments on an issue. Returns { items, nextCursor? }.',
|
|
546
|
+
inputSchema: { type: 'object', properties: { issueId: { type: 'string', description: 'Issue identifier (e.g., "PROJ-42")' }, ...paginationProps, ...workspaceProp }, required: ['issueId'] }
|
|
535
547
|
},
|
|
536
548
|
{
|
|
537
549
|
name: 'get_comment',
|
|
@@ -557,8 +569,8 @@ function getToolDefinitions() {
|
|
|
557
569
|
// ── Metadata ─────────────────────────────────────────────
|
|
558
570
|
{
|
|
559
571
|
name: 'list_statuses',
|
|
560
|
-
description: 'List
|
|
561
|
-
inputSchema: { type: 'object', properties: { project: { type: 'string', description: 'Project identifier' }, ...workspaceProp }, required: ['project'] }
|
|
572
|
+
description: 'List issue statuses available in a project. Returns { items, nextCursor? }.',
|
|
573
|
+
inputSchema: { type: 'object', properties: { project: { type: 'string', description: 'Project identifier' }, ...paginationProps, ...workspaceProp }, required: ['project'] }
|
|
562
574
|
},
|
|
563
575
|
{
|
|
564
576
|
name: 'get_status',
|
|
@@ -567,8 +579,8 @@ function getToolDefinitions() {
|
|
|
567
579
|
},
|
|
568
580
|
{
|
|
569
581
|
name: 'list_task_types',
|
|
570
|
-
description: 'List
|
|
571
|
-
inputSchema: { type: 'object', properties: { project: { type: 'string', description: 'Project identifier' }, ...workspaceProp }, required: ['project'] }
|
|
582
|
+
description: 'List task types available in a project. Returns { items, nextCursor? }.',
|
|
583
|
+
inputSchema: { type: 'object', properties: { project: { type: 'string', description: 'Project identifier' }, ...paginationProps, ...workspaceProp }, required: ['project'] }
|
|
572
584
|
},
|
|
573
585
|
{
|
|
574
586
|
name: 'get_task_type',
|
|
@@ -584,8 +596,8 @@ function getToolDefinitions() {
|
|
|
584
596
|
},
|
|
585
597
|
{
|
|
586
598
|
name: 'list_time_reports',
|
|
587
|
-
description: 'List
|
|
588
|
-
inputSchema: { type: 'object', properties: { issueId: { type: 'string', description: 'Issue identifier' }, ...workspaceProp }, required: ['issueId'] }
|
|
599
|
+
description: 'List time reports for an issue. Returns { items, nextCursor? }.',
|
|
600
|
+
inputSchema: { type: 'object', properties: { issueId: { type: 'string', description: 'Issue identifier' }, ...paginationProps, ...workspaceProp }, required: ['issueId'] }
|
|
589
601
|
},
|
|
590
602
|
{
|
|
591
603
|
name: 'get_time_report',
|
package/src/server.mjs
CHANGED
|
@@ -279,7 +279,7 @@ function shutdown(signal) {
|
|
|
279
279
|
timeout.unref();
|
|
280
280
|
|
|
281
281
|
// Close all sessions
|
|
282
|
-
for (const
|
|
282
|
+
for (const session of sessions.values()) {
|
|
283
283
|
session.transport.close().catch(() => {});
|
|
284
284
|
session.server.close().catch(() => {});
|
|
285
285
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
Platform specific binary for msgpackr-extract on darwin OS with x64 architecture
|
|
Binary file
|
|
File without changes
|