@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 CHANGED
@@ -5,35 +5,18 @@
5
5
  * Project-First Hierarchy: Epics belong to projects (required),
6
6
  * with optional milestone linking.
7
7
  *
8
- * Auto-assignment: When creating epics, automatically applies matching tags
9
- * based on content analysis against the local project cache.
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 — auto-tags, links, web URL — acts on the EPIC and so is identical
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
- // Auto-apply matching tags (non-fatal)
147
- const autoAssign = await resolveEpicAutoAssign(title, description);
148
- if (autoAssign?.matchedTags?.length && result?.epicId) {
149
- await applyAutoTags(
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 are auto-assigned based on content analysis against the local project cache.
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
- // Auto-apply tags (non-fatal)
158
- const autoAssign = await resolveTaskAutoAssign(projectId, title, description);
159
- if (autoAssign?.matchedTags?.length && result?.taskId) {
160
- await applyAutoTags(
161
- autoAssign.organizationId,
162
- 'task',
163
- result.taskId,
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) {
@@ -1,7 +1,13 @@
1
1
  /**
2
2
  * Auto-assign Utility
3
- * Matches task/epic content against cached tags for automatic assignment
4
- * during creation.
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
@@ -7,4 +7,4 @@
7
7
  *
8
8
  * Update this when bumping the version in package.json.
9
9
  */
10
- export const MCP_VERSION = '0.20.0';
10
+ export const MCP_VERSION = '0.20.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ezmodo/mcp-server",
3
- "version": "0.20.0",
3
+ "version": "0.20.1",
4
4
  "description": "MCP server for ezmodo - AI-first project management",
5
5
  "main": "index.js",
6
6
  "type": "module",
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
- 'Auto-applies matching tags from cached project context based on content analysis. ' +
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
- labels: {
150
+ tagIds: {
150
151
  type: 'array',
151
152
  items: { type: 'string' },
152
- description: 'Array of label names',
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',