@ezmodo/mcp-server 0.20.0 → 0.20.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.
- package/handlers/epics.js +9 -34
- package/handlers/tasks.js +10 -31
- package/lib/auto-assign.js +8 -2
- package/lib/suggested-tags.js +21 -0
- package/lib/version.js +1 -1
- package/package.json +1 -1
- package/tools/epics.js +5 -3
package/handlers/epics.js
CHANGED
|
@@ -5,35 +5,18 @@
|
|
|
5
5
|
* Project-First Hierarchy: Epics belong to projects (required),
|
|
6
6
|
* with optional milestone linking.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Tags: only the tagIds the caller passes are applied, in the create request
|
|
9
|
+
* itself. Keyword matches against the local project cache come back as
|
|
10
|
+
* `suggestedTags` (see lib/auto-assign.js for why they are not applied).
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import { callZephlyAPI } from '../lib/http-client.js';
|
|
13
14
|
import { resolveEpicAutoAssign } from '../lib/auto-assign.js';
|
|
15
|
+
import { suggestTags } from '../lib/suggested-tags.js';
|
|
14
16
|
import { buildEpicUrl } from '../lib/web-url.js';
|
|
15
|
-
import { getLogger } from '../lib/logger.js';
|
|
16
17
|
import { attachLinks } from '../lib/links-at-create.js';
|
|
17
18
|
import { normalizeChangedFiles } from '../lib/changed-files.js';
|
|
18
19
|
|
|
19
|
-
/**
|
|
20
|
-
* Apply tags to a newly created entity via bulkTagEntities.
|
|
21
|
-
* Non-fatal — logs errors but doesn't throw.
|
|
22
|
-
*/
|
|
23
|
-
async function applyAutoTags(organizationId, entityType, entityId, tagIds) {
|
|
24
|
-
if (!organizationId || !tagIds?.length || !entityId) return;
|
|
25
|
-
try {
|
|
26
|
-
await callZephlyAPI('mcpBulkTagEntities', {
|
|
27
|
-
organizationId,
|
|
28
|
-
tagIds,
|
|
29
|
-
entities: [{ entityType, entityId }],
|
|
30
|
-
operation: 'add',
|
|
31
|
-
});
|
|
32
|
-
} catch (err) {
|
|
33
|
-
getLogger().warn('Auto-tag failed', { entityType, entityId, error: err.message });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
20
|
/**
|
|
38
21
|
* Flatten the nested-task result into the fields an agent acts on (#2247).
|
|
39
22
|
*
|
|
@@ -128,7 +111,7 @@ async function createEpic(args) {
|
|
|
128
111
|
|
|
129
112
|
// An epic with its breakdown goes to the composing endpoint (#2247) so the
|
|
130
113
|
// whole thing is one request; without tasks nothing changes. Everything below
|
|
131
|
-
// this line —
|
|
114
|
+
// this line — suggested tags, links, web URL — acts on the EPIC and so is identical
|
|
132
115
|
// either way.
|
|
133
116
|
const result = tasks?.length
|
|
134
117
|
? await callZephlyAPI('mcpCreateEpicWithTasks', {
|
|
@@ -143,18 +126,10 @@ async function createEpic(args) {
|
|
|
143
126
|
|
|
144
127
|
if (tasks?.length) summarizeNestedTasks(result);
|
|
145
128
|
|
|
146
|
-
//
|
|
147
|
-
const
|
|
148
|
-
if (
|
|
149
|
-
|
|
150
|
-
autoAssign.organizationId,
|
|
151
|
-
'epic',
|
|
152
|
-
result.epicId,
|
|
153
|
-
autoAssign.matchedTags.map((t) => t.id),
|
|
154
|
-
);
|
|
155
|
-
result.autoAssigned = {
|
|
156
|
-
tags: autoAssign.matchedTags.map((t) => t.name),
|
|
157
|
-
};
|
|
129
|
+
// Suggest, never apply, keyword-matched tags (#2830).
|
|
130
|
+
const suggestedTags = suggestTags(await resolveEpicAutoAssign(title, description), createArgs.tagIds);
|
|
131
|
+
if (suggestedTags.length && result?.epicId) {
|
|
132
|
+
result.suggestedTags = suggestedTags;
|
|
158
133
|
}
|
|
159
134
|
|
|
160
135
|
// Attach create-time links (E-225) — best effort, never fails the create.
|
package/handlers/tasks.js
CHANGED
|
@@ -7,12 +7,14 @@
|
|
|
7
7
|
* Code links are derived: the files a task touches (changedFiles / linkedFiles)
|
|
8
8
|
* resolve to the features that own those paths (E-258). Agents name the feature
|
|
9
9
|
* the work advances through `links`.
|
|
10
|
-
* Tags
|
|
10
|
+
* Tags: only the tagIds the caller passes are applied; keyword matches against
|
|
11
|
+
* the local project cache come back as `suggestedTags` (see lib/auto-assign.js).
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
import { previewEntityLinks, partitionProposals, attachSuggestionIds } from '../lib/autolink.js';
|
|
14
15
|
import { callZephlyAPI } from '../lib/http-client.js';
|
|
15
16
|
import { resolveTaskAutoAssign } from '../lib/auto-assign.js';
|
|
17
|
+
import { suggestTags } from '../lib/suggested-tags.js';
|
|
16
18
|
import { buildTaskUrl } from '../lib/web-url.js';
|
|
17
19
|
import { getLogger } from '../lib/logger.js';
|
|
18
20
|
import { writeActiveSession, clearActiveSession } from '../lib/active-session.js';
|
|
@@ -21,24 +23,6 @@ import { getContext } from './context-manifest.js';
|
|
|
21
23
|
import { getCommitFiles, getRepositoryRoot } from '../lib/git-helpers.js';
|
|
22
24
|
import { normalizeChangedFiles } from '../lib/changed-files.js';
|
|
23
25
|
|
|
24
|
-
/**
|
|
25
|
-
* Apply tags to a newly created entity via bulkTagEntities.
|
|
26
|
-
* Non-fatal — logs errors but doesn't throw.
|
|
27
|
-
*/
|
|
28
|
-
async function applyAutoTags(organizationId, entityType, entityId, tagIds) {
|
|
29
|
-
if (!organizationId || !tagIds?.length || !entityId) return;
|
|
30
|
-
try {
|
|
31
|
-
await callZephlyAPI('mcpBulkTagEntities', {
|
|
32
|
-
organizationId,
|
|
33
|
-
tagIds,
|
|
34
|
-
entities: [{ entityType, entityId }],
|
|
35
|
-
operation: 'add',
|
|
36
|
-
});
|
|
37
|
-
} catch (err) {
|
|
38
|
-
getLogger().warn('Auto-tag failed', { entityType, entityId, error: err.message });
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
26
|
/**
|
|
43
27
|
* Dispatch manage_task actions to the appropriate handler
|
|
44
28
|
*/
|
|
@@ -154,18 +138,13 @@ async function createTask(args) {
|
|
|
154
138
|
};
|
|
155
139
|
}
|
|
156
140
|
|
|
157
|
-
//
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
autoAssign.matchedTags.map((t) => t.id),
|
|
165
|
-
);
|
|
166
|
-
result.autoAssigned = {
|
|
167
|
-
tags: autoAssign.matchedTags.map((t) => t.name),
|
|
168
|
-
};
|
|
141
|
+
// Suggest, never apply, keyword-matched tags (#2830).
|
|
142
|
+
const suggestedTags = suggestTags(
|
|
143
|
+
await resolveTaskAutoAssign(projectId, title, description),
|
|
144
|
+
createArgs.tagIds,
|
|
145
|
+
);
|
|
146
|
+
if (suggestedTags.length && result?.taskId) {
|
|
147
|
+
result.suggestedTags = suggestedTags;
|
|
169
148
|
}
|
|
170
149
|
|
|
171
150
|
if (autoContext) {
|
package/lib/auto-assign.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-assign Utility
|
|
3
|
-
* Matches task/epic content against cached tags
|
|
4
|
-
*
|
|
3
|
+
* Matches task/epic content against cached tags to SUGGEST tags at creation.
|
|
4
|
+
*
|
|
5
|
+
* Suggest-only, deliberately (#2830). The matcher fires on any word in the
|
|
6
|
+
* title or description, so it proposes tags like "notes" or "docs" on work
|
|
7
|
+
* that merely mentions them. It never actually applied anything before #2830
|
|
8
|
+
* (the API side was a stub), so applying its output would have switched on a
|
|
9
|
+
* noisy behaviour nobody had seen. The agent passes the tags it wants as
|
|
10
|
+
* `tagIds`; matches come back as `suggestedTags` for it to accept.
|
|
5
11
|
*/
|
|
6
12
|
|
|
7
13
|
import { readConfig } from './local-cache.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Suggested tags (#2830).
|
|
3
|
+
*
|
|
4
|
+
* Keyword matches from lib/auto-assign.js are returned to the agent as
|
|
5
|
+
* `suggestedTags` and never applied; see that file for why.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Turn keyword matches into the `suggestedTags` a create response carries,
|
|
10
|
+
* leaving out tags the caller already applied explicitly.
|
|
11
|
+
*
|
|
12
|
+
* @param {object|null} autoAssign - Result of resolveTaskAutoAssign / resolveEpicAutoAssign
|
|
13
|
+
* @param {string[]} [appliedTagIds] - Tag IDs sent with the create
|
|
14
|
+
* @returns {Array<{id: string, name: string}>}
|
|
15
|
+
*/
|
|
16
|
+
export function suggestTags(autoAssign, appliedTagIds = []) {
|
|
17
|
+
const applied = new Set(appliedTagIds || []);
|
|
18
|
+
return (autoAssign?.matchedTags || [])
|
|
19
|
+
.filter((t) => !applied.has(t.id))
|
|
20
|
+
.map((t) => ({ id: t.id, name: t.name }));
|
|
21
|
+
}
|
package/lib/version.js
CHANGED
package/package.json
CHANGED
package/tools/epics.js
CHANGED
|
@@ -44,7 +44,8 @@ export const EPIC_TOOLS = [
|
|
|
44
44
|
'IMPORTANT when creating: Call get_context first for each area the epic covers to discover relevant ' +
|
|
45
45
|
'files and integration points. Include in the description which layers/services are affected and ' +
|
|
46
46
|
'reference specific file paths. Create child tasks that each reference specific files from context queries. ' +
|
|
47
|
-
'
|
|
47
|
+
'Tags: pass the tag IDs you want as tagIds; the response lists keyword-matched tags you did not pass as ' +
|
|
48
|
+
'suggestedTags, which are NOT applied — add any that fit with an update. ' +
|
|
48
49
|
'To align an epic to an organization goal, link it to a milestone that is linked to that goal ' +
|
|
49
50
|
'(set milestoneId) — epics have no direct goal field. ' +
|
|
50
51
|
'BREAKING DOWN A FEATURE: pass the child tasks in the `tasks` array on create and the epic and its ' +
|
|
@@ -146,10 +147,11 @@ export const EPIC_TOOLS = [
|
|
|
146
147
|
name: { type: 'string' },
|
|
147
148
|
},
|
|
148
149
|
},
|
|
149
|
-
|
|
150
|
+
tagIds: {
|
|
150
151
|
type: 'array',
|
|
151
152
|
items: { type: 'string' },
|
|
152
|
-
description: '
|
|
153
|
+
description: 'Tag IDs for categorization (get them from get_current_project_context). ' +
|
|
154
|
+
'Used by create and update; on update this REPLACES the set.',
|
|
153
155
|
},
|
|
154
156
|
color: {
|
|
155
157
|
type: 'string',
|