@gethmy/mcp 2.8.5 → 2.8.6

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/dist/cli.js CHANGED
@@ -1720,6 +1720,10 @@ class HarmonyApiClient {
1720
1720
  params.set("type", options.type);
1721
1721
  if (options?.limit !== undefined)
1722
1722
  params.set("limit", String(options.limit));
1723
+ for (const tag of options?.tags ?? [])
1724
+ params.append("tags", tag);
1725
+ if (options?.include_superseded)
1726
+ params.set("include_superseded", "true");
1723
1727
  return this.request("GET", `/memory/search?${params.toString()}`);
1724
1728
  }
1725
1729
  async getVaultIndex(options) {
@@ -5329,23 +5333,21 @@ async function handleToolCall(name, args, deps) {
5329
5333
  let entities;
5330
5334
  let relevanceMap;
5331
5335
  if (queryText) {
5336
+ const requestedTags = args.tags;
5332
5337
  const searchResult = await client3.searchMemoryEntities(workspaceId, queryText, {
5333
5338
  project_id: projectId,
5334
5339
  type: args.type,
5335
- limit: fetchLimit
5340
+ limit: fetchLimit,
5341
+ tags: requestedTags && requestedTags.length > 0 ? requestedTags : undefined,
5342
+ include_superseded: includeSuperseded
5336
5343
  });
5337
5344
  entities = searchResult.entities ?? [];
5338
- const requestedTags = args.tags;
5339
5345
  const minConfidence = args.minConfidence;
5340
5346
  if (userScopeFilter) {
5341
5347
  entities = entities.filter((e) => e?.scope === userScopeFilter);
5342
5348
  } else if (excludeSessionFromLongTerm) {
5343
5349
  entities = entities.filter((e) => !isSessionScope(e?.scope));
5344
5350
  }
5345
- if (requestedTags && requestedTags.length > 0) {
5346
- const wanted = new Set(requestedTags);
5347
- entities = entities.filter((e) => (e?.tags ?? []).some((t) => wanted.has(t)));
5348
- }
5349
5351
  entities = filterByMinConfidence(entities, minConfidence);
5350
5352
  if (!includeSuperseded) {
5351
5353
  entities = entities.filter((e) => !e?.superseded_at);
package/dist/index.js CHANGED
@@ -1716,6 +1716,10 @@ class HarmonyApiClient {
1716
1716
  params.set("type", options.type);
1717
1717
  if (options?.limit !== undefined)
1718
1718
  params.set("limit", String(options.limit));
1719
+ for (const tag of options?.tags ?? [])
1720
+ params.append("tags", tag);
1721
+ if (options?.include_superseded)
1722
+ params.set("include_superseded", "true");
1719
1723
  return this.request("GET", `/memory/search?${params.toString()}`);
1720
1724
  }
1721
1725
  async getVaultIndex(options) {
@@ -5325,23 +5329,21 @@ async function handleToolCall(name, args, deps) {
5325
5329
  let entities;
5326
5330
  let relevanceMap;
5327
5331
  if (queryText) {
5332
+ const requestedTags = args.tags;
5328
5333
  const searchResult = await client3.searchMemoryEntities(workspaceId, queryText, {
5329
5334
  project_id: projectId,
5330
5335
  type: args.type,
5331
- limit: fetchLimit
5336
+ limit: fetchLimit,
5337
+ tags: requestedTags && requestedTags.length > 0 ? requestedTags : undefined,
5338
+ include_superseded: includeSuperseded
5332
5339
  });
5333
5340
  entities = searchResult.entities ?? [];
5334
- const requestedTags = args.tags;
5335
5341
  const minConfidence = args.minConfidence;
5336
5342
  if (userScopeFilter) {
5337
5343
  entities = entities.filter((e) => e?.scope === userScopeFilter);
5338
5344
  } else if (excludeSessionFromLongTerm) {
5339
5345
  entities = entities.filter((e) => !isSessionScope(e?.scope));
5340
5346
  }
5341
- if (requestedTags && requestedTags.length > 0) {
5342
- const wanted = new Set(requestedTags);
5343
- entities = entities.filter((e) => (e?.tags ?? []).some((t) => wanted.has(t)));
5344
- }
5345
5347
  entities = filterByMinConfidence(entities, minConfidence);
5346
5348
  if (!includeSuperseded) {
5347
5349
  entities = entities.filter((e) => !e?.superseded_at);
@@ -1323,6 +1323,10 @@ class HarmonyApiClient {
1323
1323
  params.set("type", options.type);
1324
1324
  if (options?.limit !== undefined)
1325
1325
  params.set("limit", String(options.limit));
1326
+ for (const tag of options?.tags ?? [])
1327
+ params.append("tags", tag);
1328
+ if (options?.include_superseded)
1329
+ params.set("include_superseded", "true");
1326
1330
  return this.request("GET", `/memory/search?${params.toString()}`);
1327
1331
  }
1328
1332
  async getVaultIndex(options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gethmy/mcp",
3
- "version": "2.8.5",
3
+ "version": "2.8.6",
4
4
  "description": "MCP server for Harmony Kanban board - enables AI coding agents to manage your boards",
5
5
  "publishConfig": {
6
6
  "access": "public"
package/src/api-client.ts CHANGED
@@ -1003,6 +1003,8 @@ export class HarmonyApiClient {
1003
1003
  project_id?: string;
1004
1004
  type?: string;
1005
1005
  limit?: number;
1006
+ tags?: string[];
1007
+ include_superseded?: boolean;
1006
1008
  },
1007
1009
  ): Promise<{ entities: unknown[]; count: number }> {
1008
1010
  const params = new URLSearchParams();
@@ -1012,6 +1014,10 @@ export class HarmonyApiClient {
1012
1014
  if (options?.type) params.set("type", options.type);
1013
1015
  if (options?.limit !== undefined)
1014
1016
  params.set("limit", String(options.limit));
1017
+ // Repeated `tags` params — the search endpoint reads them via getAll and
1018
+ // matches against the canonical `tags_normalized` column (#299).
1019
+ for (const tag of options?.tags ?? []) params.append("tags", tag);
1020
+ if (options?.include_superseded) params.set("include_superseded", "true");
1015
1021
  return this.request("GET", `/memory/search?${params.toString()}`);
1016
1022
  }
1017
1023
 
@@ -264,13 +264,11 @@ export async function findSupersedeCandidates(
264
264
  if (options?.scope && (e as { scope?: string }).scope !== undefined) {
265
265
  if ((e as { scope?: string }).scope !== options.scope) return false;
266
266
  }
267
- // Skip already-superseded rows when the field is present. NOTE: the
268
- // hybrid-search RPC does not return `superseded_at`, so this only fires
269
- // on the FTS-fallback path; on the embedding path an already-retired row
270
- // can still surface as a *candidate*. That is non-destructive the
271
- // `similar` list is advisory and the caller decides explicitly whether
272
- // to supersede. A complete fix needs the RPC to return/filter the column
273
- // (migration + deploy); tracked in docs/memory.md.
267
+ // Skip already-superseded rows. The hybrid-search RPC now both returns
268
+ // `superseded_at` and excludes tombstoned rows by default (#298), so
269
+ // retired rows no longer surface as candidates on the embedding path.
270
+ // Kept as belt-and-suspenders for the FTS fallback and any caller that
271
+ // opts into include_superseded.
274
272
  if ((e as { superseded_at?: string | null }).superseded_at) {
275
273
  return false;
276
274
  }
package/src/server.ts CHANGED
@@ -3080,6 +3080,12 @@ async function handleToolCall(
3080
3080
  let relevanceMap: Map<string, number>;
3081
3081
 
3082
3082
  if (queryText) {
3083
+ const requestedTags = args.tags as string[] | undefined;
3084
+ // Tag + superseded filtering is now authoritative in the hybrid_search
3085
+ // RPC (#298/#299): tags match the canonical `tags_normalized` column at
3086
+ // the DB level (no client-side fetch-then-filter completeness gap), and
3087
+ // tombstoned rows are excluded unless include_superseded is set. Tags
3088
+ // are normalized server-side; we pass them through verbatim.
3083
3089
  const searchResult = await client.searchMemoryEntities(
3084
3090
  workspaceId,
3085
3091
  queryText,
@@ -3087,14 +3093,18 @@ async function handleToolCall(
3087
3093
  project_id: projectId,
3088
3094
  type: args.type as string | undefined,
3089
3095
  limit: fetchLimit,
3096
+ tags:
3097
+ requestedTags && requestedTags.length > 0
3098
+ ? requestedTags
3099
+ : undefined,
3100
+ include_superseded: includeSuperseded,
3090
3101
  },
3091
3102
  );
3092
3103
  entities = (searchResult.entities ?? []) as any[];
3093
3104
 
3094
- // Post-filter the rest of the params client-side. Hybrid search RPC
3095
- // exposes only project_id + type; tags / scope / minConfidence /
3096
- // include_superseded are applied here on the rank-ordered set.
3097
- const requestedTags = args.tags as string[] | undefined;
3105
+ // Post-filter the params the RPC does not handle. Scope + minConfidence
3106
+ // are applied here on the rank-ordered set. (Tags + include_superseded
3107
+ // are handled server-side above.)
3098
3108
  const minConfidence = args.minConfidence as number | undefined;
3099
3109
  if (userScopeFilter) {
3100
3110
  entities = entities.filter((e) => e?.scope === userScopeFilter);
@@ -3103,13 +3113,9 @@ async function handleToolCall(
3103
3113
  // out of the long-term mix so the same row never shows twice.
3104
3114
  entities = entities.filter((e) => !isSessionScope(e?.scope));
3105
3115
  }
3106
- if (requestedTags && requestedTags.length > 0) {
3107
- const wanted = new Set(requestedTags);
3108
- entities = entities.filter((e) =>
3109
- (e?.tags ?? []).some((t: string) => wanted.has(t)),
3110
- );
3111
- }
3112
3116
  entities = filterByMinConfidence(entities, minConfidence);
3117
+ // Belt-and-suspenders: the RPC already excludes tombstoned rows; this
3118
+ // also drops any if a stale/FTS path slipped one through.
3113
3119
  if (!includeSuperseded) {
3114
3120
  entities = entities.filter((e) => !e?.superseded_at);
3115
3121
  }