ynosis-agent-mcp 0.2.0 → 0.2.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/README.md CHANGED
@@ -35,10 +35,13 @@ Ynosis Agent API 用の **MCP(stdio)**。Cursor など Node がある環境
35
35
  ## 施設登録フロー(Python MCP と同一)
36
36
 
37
37
  ```
38
- find_existing_facility → update_facility(既存)
38
+ find_existing_facility → update_facility(既存・認定・タグ不足時も編集依頼)
39
39
  → resolve_place_candidates → prepare_facility_for_register → register_facility(新規)
40
+ 誤登録整理: remediate_duplicate_facility(正規へ依頼 + withdraw)
40
41
  ```
41
42
 
43
+ 主要ツール: `find_existing_facility`, `prepare_facility_for_register`, `resolve_place_candidates`, `withdraw_facility`, `remediate_duplicate_facility`, `get_tag_options`
44
+
42
45
  ## 運営: npm 公開
43
46
 
44
47
  リポジトリ内手順: [docs/agent/publishing.md](../../docs/agent/publishing.md#npmynosis-agent-mcp)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ynosis-agent-mcp",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "scripts": {
5
5
  "test": "node --test test/*.test.mjs"
6
6
  },
package/src/client.mjs CHANGED
@@ -106,6 +106,16 @@ export class AgentApiClient {
106
106
  return findExisting(this, name, prefecture, options);
107
107
  }
108
108
 
109
+ async withdrawFacility(facilityId) {
110
+ const res = await this.request("POST", `/api/agent/facilities/${facilityId}/withdraw`);
111
+ return res.json;
112
+ }
113
+
114
+ async remediateDuplicateFacility(duplicateId, { canonicalId = null, dryRun = false } = {}) {
115
+ const { remediateDuplicateFacility: remediate } = await import("./facility_remediate.mjs");
116
+ return remediate(this, duplicateId, { canonicalId, dryRun });
117
+ }
118
+
109
119
  async prepareFacilityPayload(payload, { place = null, maxResults = 5 } = {}) {
110
120
  const { filterValidTags, resolvePlaceForPayload } = await import("./facility_prepare.mjs");
111
121
  const prepared = { ...payload };
@@ -0,0 +1,102 @@
1
+ /** Agent 重複誤登録の整理(Python ynosis_agent.facility_remediate と同期)。 */
2
+
3
+ import { MATCH_SCORE_MIN, facilitySearchQueries, scoreFacilityItem } from "./facility_match.mjs";
4
+ import { mergeFacilityUpdates } from "./facility_prepare.mjs";
5
+
6
+ function appendNote(existing, line) {
7
+ const base = String(existing || "").trim();
8
+ if (base.includes(line)) return base;
9
+ return base ? `${base}\n${line}` : line;
10
+ }
11
+
12
+ export async function findCanonicalFacility(client, duplicate, { searchLimit = 30 } = {}) {
13
+ const dupId = Number(duplicate.id);
14
+ const name = String(duplicate.name || "").trim();
15
+ const prefecture = duplicate.prefecture;
16
+ if (!name) return null;
17
+
18
+ let bestScore = 0;
19
+ let bestItem = null;
20
+ const queries = facilitySearchQueries(name);
21
+
22
+ for (const q of queries) {
23
+ const params = { q, limit: searchLimit };
24
+ if (prefecture) params.prefecture = prefecture;
25
+ let res;
26
+ try {
27
+ res = await client.searchFacilities(params);
28
+ } catch {
29
+ continue;
30
+ }
31
+ for (const item of res.items || []) {
32
+ if (Number(item.id) === dupId) continue;
33
+ if (prefecture && (item.prefecture || "") !== prefecture) continue;
34
+ const sc = await scoreFacilityItem(name, item);
35
+ if (sc > bestScore) {
36
+ bestScore = sc;
37
+ bestItem = item;
38
+ }
39
+ }
40
+ }
41
+
42
+ if (bestScore < MATCH_SCORE_MIN || !bestItem) return null;
43
+ return bestItem;
44
+ }
45
+
46
+ export async function remediateDuplicateFacility(
47
+ client,
48
+ duplicateId,
49
+ { canonicalId = null, dryRun = false } = {}
50
+ ) {
51
+ const duplicate = await client.getFacility(duplicateId);
52
+ if (duplicate.is_agent_approved) {
53
+ return { ok: false, error: "承認済みのため取り下げ不可", duplicate_id: duplicateId };
54
+ }
55
+
56
+ let canonical = canonicalId != null ? await client.getFacility(canonicalId) : await findCanonicalFacility(client, duplicate);
57
+ if (!canonical) {
58
+ return {
59
+ ok: false,
60
+ action: "no_canonical",
61
+ duplicate_id: duplicateId,
62
+ duplicate_name: duplicate.name,
63
+ };
64
+ }
65
+
66
+ const canonId = Number(canonical.id);
67
+ canonical = await client.getFacility(canonId);
68
+ const marker =
69
+ `[Agent重複整理] 未承認ID ${duplicateId}「${duplicate.name}」は` +
70
+ ` 当施設(ID ${canonId})と同一の誤登録。Agent 側で取り下げ${dryRun ? "予定" : "済み"}。`;
71
+ const proposed = { ...duplicate, notes: appendNote(canonical.notes, marker) };
72
+ const updates = mergeFacilityUpdates(canonical, proposed) || {};
73
+ if (proposed.notes !== canonical.notes) updates.notes = proposed.notes;
74
+
75
+ const result = {
76
+ ok: true,
77
+ action: "remediate",
78
+ duplicate_id: duplicateId,
79
+ duplicate_name: duplicate.name,
80
+ canonical_id: canonId,
81
+ canonical_name: canonical.name,
82
+ updates,
83
+ dry_run: dryRun,
84
+ };
85
+
86
+ if (dryRun) return result;
87
+
88
+ let editRequestId = null;
89
+ if (!Object.keys(updates).length) {
90
+ result.warning = "正規施設への追記差分なし(認定・タグは既に付与済みの可能性)";
91
+ } else {
92
+ const resp = await client.updateFacility(canonId, updates);
93
+ editRequestId = resp.edit_request_id;
94
+ if (!editRequestId) {
95
+ result.warning = "編集依頼が作成されなかった(差分なし扱い)";
96
+ }
97
+ }
98
+ const withdraw = await client.withdrawFacility(duplicateId);
99
+ result.edit_request_id = editRequestId;
100
+ result.withdraw = withdraw;
101
+ return result;
102
+ }
package/src/server.mjs CHANGED
@@ -29,7 +29,7 @@ function textResult(text) {
29
29
 
30
30
  const server = new McpServer({
31
31
  name: "ynosis-agent",
32
- version: "0.2.0",
32
+ version: "0.2.1",
33
33
  instructions:
34
34
  "Ynosis(温泉データベース)への施設・成分表登録・検索用ツール。" +
35
35
  "新規施設登録前: find_existing_facility で重複確認 → 既存なら update_facility、" +
@@ -88,6 +88,43 @@ server.tool(
88
88
  }
89
89
  );
90
90
 
91
+ server.tool(
92
+ "withdraw_facility",
93
+ "未承認 Agent 新規施設を取り下げ(削除)。重複誤登録の整理用。",
94
+ { facility_id: z.number() },
95
+ async ({ facility_id }) => {
96
+ try {
97
+ return textResult(dump(await client().withdrawFacility(facility_id)));
98
+ } catch (e) {
99
+ return textResult(errText(e));
100
+ }
101
+ }
102
+ );
103
+
104
+ server.tool(
105
+ "remediate_duplicate_facility",
106
+ "正規施設へ編集依頼(備考に重複整理メモ)→ 誤登録を withdraw。",
107
+ {
108
+ duplicate_id: z.number(),
109
+ canonical_id: z.number().optional(),
110
+ dry_run: z.boolean().optional(),
111
+ },
112
+ async ({ duplicate_id, canonical_id, dry_run }) => {
113
+ try {
114
+ return textResult(
115
+ dump(
116
+ await client().remediateDuplicateFacility(duplicate_id, {
117
+ canonicalId: canonical_id ?? null,
118
+ dryRun: dry_run ?? false,
119
+ })
120
+ )
121
+ );
122
+ } catch (e) {
123
+ return textResult(errText(e));
124
+ }
125
+ }
126
+ );
127
+
91
128
  server.tool(
92
129
  "find_existing_facility",
93
130
  "登録前に既存施設を照合(未承認含む)。ヒット時は update_facility、無ければ新規 register_facility。",