umtri-mcp 1.4.0 → 1.4.2
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/core/tools.js +25 -7
- package/package.json +1 -1
package/core/tools.js
CHANGED
|
@@ -592,6 +592,8 @@ export function registerTools(server, { api }) {
|
|
|
592
592
|
},
|
|
593
593
|
);
|
|
594
594
|
|
|
595
|
+
// visibility 인자는 두지 않는다. 배지를 밖에 보이는 경로가 없어 받아도 버려졌고(#70), 트리는
|
|
596
|
+
// 경로·연동·버그까지 담은 지도라 공개 여부는 에이전트가 아니라 사람이 정할 일이다.
|
|
595
597
|
server.registerTool(
|
|
596
598
|
'create_project',
|
|
597
599
|
{
|
|
@@ -601,17 +603,15 @@ export function registerTools(server, { api }) {
|
|
|
601
603
|
slug: z.string().regex(/^[a-z0-9][a-z0-9-]{0,49}$/).describe('URL slug. Lowercase letters, digits, hyphens; 1–50 chars.'),
|
|
602
604
|
name: z.string().min(1).max(200).describe('Display name.'),
|
|
603
605
|
description: z.string().optional(),
|
|
604
|
-
visibility: z.enum(['private', 'unlisted', 'public']).optional().describe('Defaults to private.'),
|
|
605
606
|
org: z.string().min(1).optional().describe('Team slug to create the ground in. Omit for your personal space. You must own the team.'),
|
|
606
607
|
}),
|
|
607
608
|
},
|
|
608
|
-
async ({ slug, name, description,
|
|
609
|
+
async ({ slug, name, description, org }) => {
|
|
609
610
|
try {
|
|
610
611
|
const markupWarnings = toolMarkupCheck({ description });
|
|
611
612
|
const body = { slug, name };
|
|
612
613
|
if (org !== undefined) body.org = org;
|
|
613
614
|
if (description !== undefined) body.description = description;
|
|
614
|
-
if (visibility !== undefined) body.visibility = visibility;
|
|
615
615
|
const created = await api.post('/api/projects', body);
|
|
616
616
|
return jsonResult(markupWarnings.length ? { ...created, warnings: markupWarnings } : created);
|
|
617
617
|
} catch (e) { return errorResult(e); }
|
|
@@ -725,7 +725,7 @@ export function registerTools(server, { api }) {
|
|
|
725
725
|
'update_node',
|
|
726
726
|
{
|
|
727
727
|
title: 'Update a node',
|
|
728
|
-
description: 'Partial update of a node. Patchable fields: label, type, parent, season, description, tags, metadata, sproutedAt. Type change reclassifies role; parent change recomputes the ltree path automatically. On a grown (past-season) node, only the tree\'s shape and timeline are locked — parent, season, type and sproutedAt are rejected. Content fields (label, description, metadata, tags) stay editable, so you can keep metadata.implements current when code moves without reopening transplanting. Moving any node into a `past` season is rejected. Both restrictions lift while the ground is transplanting (project.transplanting=true), so historical structure can be reconstructed (see umtri://rules/transplant). Same protocol validation as create_node — reject on hierarchy violations, warn on soft issues.',
|
|
728
|
+
description: 'Partial update of a node. Patchable fields: label, type, parent, season, description, tags, metadata, sproutedAt. metadata is merged key by key: only the keys you send change, keys you leave out (commits that record_commit accumulated, transplanted, and so on) are kept, and a key sent as null is removed. So to change implements, send just { metadata: { implements: [...] } }. Type change reclassifies role; parent change recomputes the ltree path automatically. On a grown (past-season) node, only the tree\'s shape and timeline are locked — parent, season, type and sproutedAt are rejected. Content fields (label, description, metadata, tags) stay editable, so you can keep metadata.implements current when code moves without reopening transplanting. Moving any node into a `past` season is rejected. Both restrictions lift while the ground is transplanting (project.transplanting=true), so historical structure can be reconstructed (see umtri://rules/transplant). Same protocol validation as create_node — reject on hierarchy violations, warn on soft issues.',
|
|
729
729
|
inputSchema: z.object({
|
|
730
730
|
slug: z.string().min(1).describe('Ground slug.'),
|
|
731
731
|
id: z.string().min(1).describe('Node id.'),
|
|
@@ -736,7 +736,7 @@ export function registerTools(server, { api }) {
|
|
|
736
736
|
season: z.string().nullable().optional().describe('New season id, or null to detach from any season. Past seasons are rejected.'),
|
|
737
737
|
description: z.string().nullable().optional(),
|
|
738
738
|
tags: z.array(z.string()).optional(),
|
|
739
|
-
metadata: z.record(z.any()).optional(),
|
|
739
|
+
metadata: z.record(z.any()).optional().describe('Keys to change — merged into the existing metadata. null removes a key. Keys you omit are kept.'),
|
|
740
740
|
sproutedAt: z.string().datetime({ offset: true }).nullable().optional().describe('Effective creation time (ISO 8601 with offset). Set to null to clear and fall back to the system timestamp. Use only for back-filling historical projects.'),
|
|
741
741
|
}).describe('Only the fields you want to change.'),
|
|
742
742
|
}),
|
|
@@ -758,7 +758,9 @@ export function registerTools(server, { api }) {
|
|
|
758
758
|
const newLabel = patch.label ?? current.label;
|
|
759
759
|
const newParentId = patch.parent !== undefined ? patch.parent : current.parent;
|
|
760
760
|
const newDescription = patch.description !== undefined ? patch.description : current.description;
|
|
761
|
-
const newMetadata = patch.metadata !== undefined
|
|
761
|
+
const newMetadata = patch.metadata !== undefined
|
|
762
|
+
? Object.fromEntries(Object.entries({ ...(current.metadata || {}), ...patch.metadata }).filter(([, v]) => v !== null))
|
|
763
|
+
: current.metadata;
|
|
762
764
|
const parentType = newParentId
|
|
763
765
|
? (graph.nodes || []).find(n => n.id === newParentId)?.type ?? null
|
|
764
766
|
: null;
|
|
@@ -775,7 +777,23 @@ export function registerTools(server, { api }) {
|
|
|
775
777
|
warnings = warnings.concat(result.warnings);
|
|
776
778
|
}
|
|
777
779
|
|
|
778
|
-
|
|
780
|
+
// metadata는 키 단위로 병합해서 보낸다. REST는 metadata를 통째로 교체하는데(앱의 plan 커밋이
|
|
781
|
+
// 그 동작에 기댄다), 에이전트가 implements만 보내면 record_commit이 쌓은 commits까지 조용히
|
|
782
|
+
// 지워졌다(건의 #2). 그래서 이 도구에서 현재 값을 읽어 합친 전체를 보낸다. null은 키 삭제.
|
|
783
|
+
let body = patch;
|
|
784
|
+
if (patch.metadata !== undefined && patch.metadata !== null) {
|
|
785
|
+
const g = await api.get(`/api/projects/${encodeURIComponent(slug)}/graph?root=${encodeURIComponent(id)}&depth=0`);
|
|
786
|
+
const current = (g.nodes || []).find(n => n.id === id);
|
|
787
|
+
if (!current) throw new Error(`Node "${id}" not found in ground "${slug}".`);
|
|
788
|
+
const merged = { ...(current.metadata || {}) };
|
|
789
|
+
for (const [k, v] of Object.entries(patch.metadata)) {
|
|
790
|
+
if (v === null) delete merged[k];
|
|
791
|
+
else merged[k] = v;
|
|
792
|
+
}
|
|
793
|
+
body = { ...patch, metadata: merged };
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const updated = await api.put(`/api/projects/${encodeURIComponent(slug)}/nodes/${encodeURIComponent(id)}`, body);
|
|
779
797
|
return jsonResult(warnings.length ? { ...updated, warnings } : updated);
|
|
780
798
|
} catch (e) { return errorResult(e); }
|
|
781
799
|
},
|
package/package.json
CHANGED