@firfi/huly-mcp 0.1.37 → 0.1.38
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 +3 -0
- package/dist/index.cjs +238 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -167,6 +167,9 @@ MCP_TRANSPORT=http MCP_HTTP_PORT=8080 MCP_HTTP_HOST=0.0.0.0 npx -y @firfi/huly-m
|
|
|
167
167
|
| `create_issue_from_template` | Create a new issue from a template. Applies template defaults, allowing overrides for specific fields. Returns the created issue identifier. |
|
|
168
168
|
| `update_issue_template` | Update fields on an existing Huly issue template. Only provided fields are modified. |
|
|
169
169
|
| `delete_issue_template` | Permanently delete a Huly issue template. This action cannot be undone. |
|
|
170
|
+
| `add_issue_relation` | Add a relation between two issues. Relation types: 'blocks' (source blocks target — pushes into target's blockedBy), 'is-blocked-by' (source is blocked by target — pushes into source's blockedBy), 'relates-to' (bidirectional link — updates both sides). targetIssue accepts cross-project identifiers like 'OTHER-42'. No-op if the relation already exists. |
|
|
171
|
+
| `remove_issue_relation` | Remove a relation between two issues. Mirrors add_issue_relation: 'blocks' pulls from target's blockedBy, 'is-blocked-by' pulls from source's blockedBy, 'relates-to' pulls from both sides. No-op if the relation doesn't exist. |
|
|
172
|
+
| `list_issue_relations` | List all relations of an issue. Returns blockedBy (issues blocking this one) and relations (bidirectional links) with resolved identifiers. Does NOT return issues that this issue blocks — use list_issue_relations on the target issue to see that. |
|
|
170
173
|
|
|
171
174
|
### Comments
|
|
172
175
|
|
package/dist/index.cjs
CHANGED
|
@@ -173663,7 +173663,7 @@ var PostHog = class extends PostHogBackendClient {
|
|
|
173663
173663
|
};
|
|
173664
173664
|
|
|
173665
173665
|
// src/version.ts
|
|
173666
|
-
var VERSION = true ? "0.1.
|
|
173666
|
+
var VERSION = true ? "0.1.37" : "0.0.0-dev";
|
|
173667
173667
|
|
|
173668
173668
|
// src/telemetry/posthog.ts
|
|
173669
173669
|
var POSTHOG_API_KEY = "phc_TGfFqCGdnF0p68wuFzd5WSw1IsBvOJW0YgoMJDyZPjm";
|
|
@@ -177114,6 +177114,51 @@ var FulltextSearchParamsSchema = Schema_exports.Struct({
|
|
|
177114
177114
|
var fulltextSearchParamsJsonSchema = JSONSchema_exports.make(FulltextSearchParamsSchema);
|
|
177115
177115
|
var parseFulltextSearchParams = Schema_exports.decodeUnknown(FulltextSearchParamsSchema);
|
|
177116
177116
|
|
|
177117
|
+
// src/domain/schemas/relations.ts
|
|
177118
|
+
var RelationTypeValues = ["blocks", "is-blocked-by", "relates-to"];
|
|
177119
|
+
var RelationTypeSchema = Schema_exports.Literal(...RelationTypeValues).annotations({
|
|
177120
|
+
title: "RelationType",
|
|
177121
|
+
description: "Type of issue relation: 'blocks' (source blocks target), 'is-blocked-by' (source is blocked by target), 'relates-to' (bidirectional link)",
|
|
177122
|
+
jsonSchema: { type: "string", enum: [...RelationTypeValues] }
|
|
177123
|
+
});
|
|
177124
|
+
var issueRelationFields = {
|
|
177125
|
+
project: ProjectIdentifier.annotations({
|
|
177126
|
+
description: "Project identifier of the source issue (e.g., 'HULY')"
|
|
177127
|
+
}),
|
|
177128
|
+
issueIdentifier: IssueIdentifier.annotations({
|
|
177129
|
+
description: "Source issue identifier (e.g., 'HULY-123')"
|
|
177130
|
+
}),
|
|
177131
|
+
targetIssue: IssueIdentifier.annotations({
|
|
177132
|
+
description: "Target issue identifier \u2014 same project: '42' or 'PROJ-42'; cross-project: 'OTHER-42'"
|
|
177133
|
+
}),
|
|
177134
|
+
relationType: RelationTypeSchema
|
|
177135
|
+
};
|
|
177136
|
+
var AddIssueRelationParamsSchema = Schema_exports.Struct(issueRelationFields).annotations({
|
|
177137
|
+
title: "AddIssueRelationParams",
|
|
177138
|
+
description: "Parameters for adding a relation between two issues"
|
|
177139
|
+
});
|
|
177140
|
+
var RemoveIssueRelationParamsSchema = Schema_exports.Struct(issueRelationFields).annotations({
|
|
177141
|
+
title: "RemoveIssueRelationParams",
|
|
177142
|
+
description: "Parameters for removing a relation between two issues"
|
|
177143
|
+
});
|
|
177144
|
+
var ListIssueRelationsParamsSchema = Schema_exports.Struct({
|
|
177145
|
+
project: ProjectIdentifier.annotations({
|
|
177146
|
+
description: "Project identifier (e.g., 'HULY')"
|
|
177147
|
+
}),
|
|
177148
|
+
issueIdentifier: IssueIdentifier.annotations({
|
|
177149
|
+
description: "Issue identifier (e.g., 'HULY-123')"
|
|
177150
|
+
})
|
|
177151
|
+
}).annotations({
|
|
177152
|
+
title: "ListIssueRelationsParams",
|
|
177153
|
+
description: "Parameters for listing all relations of an issue"
|
|
177154
|
+
});
|
|
177155
|
+
var addIssueRelationParamsJsonSchema = JSONSchema_exports.make(AddIssueRelationParamsSchema);
|
|
177156
|
+
var removeIssueRelationParamsJsonSchema = JSONSchema_exports.make(RemoveIssueRelationParamsSchema);
|
|
177157
|
+
var listIssueRelationsParamsJsonSchema = JSONSchema_exports.make(ListIssueRelationsParamsSchema);
|
|
177158
|
+
var parseAddIssueRelationParams = Schema_exports.decodeUnknown(AddIssueRelationParamsSchema);
|
|
177159
|
+
var parseRemoveIssueRelationParams = Schema_exports.decodeUnknown(RemoveIssueRelationParamsSchema);
|
|
177160
|
+
var parseListIssueRelationsParams = Schema_exports.decodeUnknown(ListIssueRelationsParamsSchema);
|
|
177161
|
+
|
|
177117
177162
|
// src/domain/schemas/notifications.ts
|
|
177118
177163
|
var ListNotificationsParamsSchema = Schema_exports.Struct({
|
|
177119
177164
|
limit: Schema_exports.optional(
|
|
@@ -179515,6 +179560,165 @@ var deleteIssueTemplate = (params) => Effect_exports.gen(function* () {
|
|
|
179515
179560
|
return { id: IssueTemplateId.make(template._id), deleted: true };
|
|
179516
179561
|
});
|
|
179517
179562
|
|
|
179563
|
+
// src/huly/operations/relations.ts
|
|
179564
|
+
var resolveTargetIssue = (client, sourceProject, targetIssueStr) => Effect_exports.gen(function* () {
|
|
179565
|
+
const { fullIdentifier } = parseIssueIdentifier(targetIssueStr, sourceProject.identifier);
|
|
179566
|
+
const match16 = fullIdentifier.match(/^([A-Z]+)-\d+$/i);
|
|
179567
|
+
const prefix = match16 ? match16[1].toUpperCase() : null;
|
|
179568
|
+
if (prefix !== null && prefix !== sourceProject.identifier.toUpperCase()) {
|
|
179569
|
+
const { client: c, project: targetProject } = yield* findProject(prefix);
|
|
179570
|
+
const issue3 = yield* findIssueInProject(c, targetProject, targetIssueStr);
|
|
179571
|
+
return { issue: issue3, project: targetProject };
|
|
179572
|
+
}
|
|
179573
|
+
const issue2 = yield* findIssueInProject(client, sourceProject, targetIssueStr);
|
|
179574
|
+
return { issue: issue2, project: sourceProject };
|
|
179575
|
+
});
|
|
179576
|
+
var makeRelatedDoc = (issue2) => ({
|
|
179577
|
+
_id: toRef(issue2._id),
|
|
179578
|
+
_class: toRef(tracker.class.Issue)
|
|
179579
|
+
});
|
|
179580
|
+
var hasRelation = (arr, targetId) => arr?.some((r) => r._id === toRef(targetId)) ?? false;
|
|
179581
|
+
var addIssueRelation = (params) => Effect_exports.gen(function* () {
|
|
179582
|
+
const { client, issue: source, project: project3 } = yield* findProjectAndIssue({
|
|
179583
|
+
project: params.project,
|
|
179584
|
+
identifier: params.issueIdentifier
|
|
179585
|
+
});
|
|
179586
|
+
const { issue: target, project: targetProject } = yield* resolveTargetIssue(
|
|
179587
|
+
client,
|
|
179588
|
+
project3,
|
|
179589
|
+
params.targetIssue
|
|
179590
|
+
);
|
|
179591
|
+
const result = { sourceIssue: source.identifier, targetIssue: target.identifier, relationType: params.relationType };
|
|
179592
|
+
switch (params.relationType) {
|
|
179593
|
+
case "blocks": {
|
|
179594
|
+
if (hasRelation(target.blockedBy, source._id)) {
|
|
179595
|
+
return { ...result, added: false };
|
|
179596
|
+
}
|
|
179597
|
+
yield* client.updateDoc(
|
|
179598
|
+
tracker.class.Issue,
|
|
179599
|
+
targetProject._id,
|
|
179600
|
+
target._id,
|
|
179601
|
+
{ $push: { blockedBy: makeRelatedDoc(source) } }
|
|
179602
|
+
);
|
|
179603
|
+
return { ...result, added: true };
|
|
179604
|
+
}
|
|
179605
|
+
case "is-blocked-by": {
|
|
179606
|
+
if (hasRelation(source.blockedBy, target._id)) {
|
|
179607
|
+
return { ...result, added: false };
|
|
179608
|
+
}
|
|
179609
|
+
yield* client.updateDoc(
|
|
179610
|
+
tracker.class.Issue,
|
|
179611
|
+
project3._id,
|
|
179612
|
+
source._id,
|
|
179613
|
+
{ $push: { blockedBy: makeRelatedDoc(target) } }
|
|
179614
|
+
);
|
|
179615
|
+
return { ...result, added: true };
|
|
179616
|
+
}
|
|
179617
|
+
case "relates-to": {
|
|
179618
|
+
if (hasRelation(source.relations, target._id)) {
|
|
179619
|
+
return { ...result, added: false };
|
|
179620
|
+
}
|
|
179621
|
+
yield* client.updateDoc(
|
|
179622
|
+
tracker.class.Issue,
|
|
179623
|
+
project3._id,
|
|
179624
|
+
source._id,
|
|
179625
|
+
{ $push: { relations: makeRelatedDoc(target) } }
|
|
179626
|
+
);
|
|
179627
|
+
yield* client.updateDoc(
|
|
179628
|
+
tracker.class.Issue,
|
|
179629
|
+
targetProject._id,
|
|
179630
|
+
target._id,
|
|
179631
|
+
{ $push: { relations: makeRelatedDoc(source) } }
|
|
179632
|
+
);
|
|
179633
|
+
return { ...result, added: true };
|
|
179634
|
+
}
|
|
179635
|
+
}
|
|
179636
|
+
});
|
|
179637
|
+
var removeIssueRelation = (params) => Effect_exports.gen(function* () {
|
|
179638
|
+
const { client, issue: source, project: project3 } = yield* findProjectAndIssue({
|
|
179639
|
+
project: params.project,
|
|
179640
|
+
identifier: params.issueIdentifier
|
|
179641
|
+
});
|
|
179642
|
+
const { issue: target, project: targetProject } = yield* resolveTargetIssue(
|
|
179643
|
+
client,
|
|
179644
|
+
project3,
|
|
179645
|
+
params.targetIssue
|
|
179646
|
+
);
|
|
179647
|
+
const result = { sourceIssue: source.identifier, targetIssue: target.identifier, relationType: params.relationType };
|
|
179648
|
+
switch (params.relationType) {
|
|
179649
|
+
case "blocks": {
|
|
179650
|
+
if (!hasRelation(target.blockedBy, source._id)) {
|
|
179651
|
+
return { ...result, removed: false };
|
|
179652
|
+
}
|
|
179653
|
+
yield* client.updateDoc(
|
|
179654
|
+
tracker.class.Issue,
|
|
179655
|
+
targetProject._id,
|
|
179656
|
+
target._id,
|
|
179657
|
+
{ $pull: { blockedBy: { _id: toRef(source._id) } } }
|
|
179658
|
+
);
|
|
179659
|
+
return { ...result, removed: true };
|
|
179660
|
+
}
|
|
179661
|
+
case "is-blocked-by": {
|
|
179662
|
+
if (!hasRelation(source.blockedBy, target._id)) {
|
|
179663
|
+
return { ...result, removed: false };
|
|
179664
|
+
}
|
|
179665
|
+
yield* client.updateDoc(
|
|
179666
|
+
tracker.class.Issue,
|
|
179667
|
+
project3._id,
|
|
179668
|
+
source._id,
|
|
179669
|
+
{ $pull: { blockedBy: { _id: toRef(target._id) } } }
|
|
179670
|
+
);
|
|
179671
|
+
return { ...result, removed: true };
|
|
179672
|
+
}
|
|
179673
|
+
case "relates-to": {
|
|
179674
|
+
if (!hasRelation(source.relations, target._id)) {
|
|
179675
|
+
return { ...result, removed: false };
|
|
179676
|
+
}
|
|
179677
|
+
yield* client.updateDoc(
|
|
179678
|
+
tracker.class.Issue,
|
|
179679
|
+
project3._id,
|
|
179680
|
+
source._id,
|
|
179681
|
+
{ $pull: { relations: { _id: toRef(target._id) } } }
|
|
179682
|
+
);
|
|
179683
|
+
yield* client.updateDoc(
|
|
179684
|
+
tracker.class.Issue,
|
|
179685
|
+
targetProject._id,
|
|
179686
|
+
target._id,
|
|
179687
|
+
{ $pull: { relations: { _id: toRef(source._id) } } }
|
|
179688
|
+
);
|
|
179689
|
+
return { ...result, removed: true };
|
|
179690
|
+
}
|
|
179691
|
+
}
|
|
179692
|
+
});
|
|
179693
|
+
var listIssueRelations = (params) => Effect_exports.gen(function* () {
|
|
179694
|
+
const { client, issue: issue2 } = yield* findProjectAndIssue({
|
|
179695
|
+
project: params.project,
|
|
179696
|
+
identifier: params.issueIdentifier
|
|
179697
|
+
});
|
|
179698
|
+
const blockedByRefs = issue2.blockedBy ?? [];
|
|
179699
|
+
const relationsRefs = issue2.relations ?? [];
|
|
179700
|
+
const allIds = [...blockedByRefs, ...relationsRefs].map((r) => r._id);
|
|
179701
|
+
if (allIds.length === 0) {
|
|
179702
|
+
return { blockedBy: [], relations: [] };
|
|
179703
|
+
}
|
|
179704
|
+
const toIssueRef = toRef;
|
|
179705
|
+
const issueIds = allIds.map(toIssueRef);
|
|
179706
|
+
const issues = yield* client.findAll(
|
|
179707
|
+
tracker.class.Issue,
|
|
179708
|
+
{ _id: { $in: issueIds } }
|
|
179709
|
+
);
|
|
179710
|
+
const idToIdentifier = new Map(issues.map((i) => [String(i._id), i.identifier]));
|
|
179711
|
+
const toEntry = (r) => ({
|
|
179712
|
+
identifier: idToIdentifier.get(String(r._id)) ?? String(r._id),
|
|
179713
|
+
_id: String(r._id),
|
|
179714
|
+
_class: String(r._class)
|
|
179715
|
+
});
|
|
179716
|
+
return {
|
|
179717
|
+
blockedBy: blockedByRefs.map(toEntry),
|
|
179718
|
+
relations: relationsRefs.map(toEntry)
|
|
179719
|
+
};
|
|
179720
|
+
});
|
|
179721
|
+
|
|
179518
179722
|
// src/mcp/tools/issues.ts
|
|
179519
179723
|
var CATEGORY9 = "issues";
|
|
179520
179724
|
var issueTools = [
|
|
@@ -179715,6 +179919,39 @@ var issueTools = [
|
|
|
179715
179919
|
parseDeleteIssueTemplateParams,
|
|
179716
179920
|
deleteIssueTemplate
|
|
179717
179921
|
)
|
|
179922
|
+
},
|
|
179923
|
+
{
|
|
179924
|
+
name: "add_issue_relation",
|
|
179925
|
+
description: "Add a relation between two issues. Relation types: 'blocks' (source blocks target \u2014 pushes into target's blockedBy), 'is-blocked-by' (source is blocked by target \u2014 pushes into source's blockedBy), 'relates-to' (bidirectional link \u2014 updates both sides). targetIssue accepts cross-project identifiers like 'OTHER-42'. No-op if the relation already exists.",
|
|
179926
|
+
category: CATEGORY9,
|
|
179927
|
+
inputSchema: addIssueRelationParamsJsonSchema,
|
|
179928
|
+
handler: createToolHandler(
|
|
179929
|
+
"add_issue_relation",
|
|
179930
|
+
parseAddIssueRelationParams,
|
|
179931
|
+
addIssueRelation
|
|
179932
|
+
)
|
|
179933
|
+
},
|
|
179934
|
+
{
|
|
179935
|
+
name: "remove_issue_relation",
|
|
179936
|
+
description: "Remove a relation between two issues. Mirrors add_issue_relation: 'blocks' pulls from target's blockedBy, 'is-blocked-by' pulls from source's blockedBy, 'relates-to' pulls from both sides. No-op if the relation doesn't exist.",
|
|
179937
|
+
category: CATEGORY9,
|
|
179938
|
+
inputSchema: removeIssueRelationParamsJsonSchema,
|
|
179939
|
+
handler: createToolHandler(
|
|
179940
|
+
"remove_issue_relation",
|
|
179941
|
+
parseRemoveIssueRelationParams,
|
|
179942
|
+
removeIssueRelation
|
|
179943
|
+
)
|
|
179944
|
+
},
|
|
179945
|
+
{
|
|
179946
|
+
name: "list_issue_relations",
|
|
179947
|
+
description: "List all relations of an issue. Returns blockedBy (issues blocking this one) and relations (bidirectional links) with resolved identifiers. Does NOT return issues that this issue blocks \u2014 use list_issue_relations on the target issue to see that.",
|
|
179948
|
+
category: CATEGORY9,
|
|
179949
|
+
inputSchema: listIssueRelationsParamsJsonSchema,
|
|
179950
|
+
handler: createToolHandler(
|
|
179951
|
+
"list_issue_relations",
|
|
179952
|
+
parseListIssueRelationsParams,
|
|
179953
|
+
listIssueRelations
|
|
179954
|
+
)
|
|
179718
179955
|
}
|
|
179719
179956
|
];
|
|
179720
179957
|
|