umtri-mcp 1.5.1 → 1.5.3
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/api-client.js +7 -2
- package/core/policy.js +29 -0
- package/core/resources/trunk-kinds.md +1 -0
- package/core/resources/vocabulary.md +1 -0
- package/core/tools.js +2 -2
- package/package.json +1 -1
package/core/api-client.js
CHANGED
|
@@ -12,9 +12,14 @@ export function createApiClient({ baseUrl, getToken }) {
|
|
|
12
12
|
return `Bearer ${token}`;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
// 서버가 "이 사용자가 MCP로 연결해 본 적 있음"을 기록하는 표시 — stdio·HTTP 공용.
|
|
16
|
+
async function headers() {
|
|
17
|
+
return { Authorization: await authHeader(), 'X-Umtri-Client': 'mcp' };
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
async function get(path) {
|
|
16
21
|
const res = await fetch(`${base}${path}`, {
|
|
17
|
-
headers:
|
|
22
|
+
headers: await headers(),
|
|
18
23
|
});
|
|
19
24
|
if (!res.ok) {
|
|
20
25
|
const body = await res.text().catch(() => '');
|
|
@@ -26,7 +31,7 @@ export function createApiClient({ baseUrl, getToken }) {
|
|
|
26
31
|
async function mutate(method, path, body) {
|
|
27
32
|
const init = {
|
|
28
33
|
method,
|
|
29
|
-
headers:
|
|
34
|
+
headers: await headers(),
|
|
30
35
|
};
|
|
31
36
|
if (body != null) {
|
|
32
37
|
init.headers['Content-Type'] = 'application/json';
|
package/core/policy.js
CHANGED
|
@@ -44,6 +44,18 @@ const LAYER_LIMB_LABELS = [
|
|
|
44
44
|
'모델', '서비스', '컨트롤러', '컴포넌트', '핸들러',
|
|
45
45
|
];
|
|
46
46
|
|
|
47
|
+
// front trunk의 공통 limb에 들어가야 할 것들 — 따로 limb로 서면 공통이 여러 갈래로 흩어진다
|
|
48
|
+
// (Coolus '공용 요소', TennisRepublic store-app '디자인 시스템'). 라벨에 포함되면 잡는다.
|
|
49
|
+
const FRONT_COMMON_CONTENT = [
|
|
50
|
+
'디자인 시스템', '디자인 토큰', '공용 컴포넌트', '공통 컴포넌트', '공용 요소', '공통 요소',
|
|
51
|
+
'공용 ui', '공통 ui',
|
|
52
|
+
'design system', 'design tokens', 'shared components', 'common components', 'ui system', 'ui kit',
|
|
53
|
+
];
|
|
54
|
+
// 셸·레이아웃은 기능 이름에도 흔히 들어가서("매장 레이아웃 편집") 라벨 전체가 그 말일 때만 잡는다.
|
|
55
|
+
const FRONT_COMMON_EXACT = ['앱 셸', '앱 틀', '레이아웃', '셸', 'app shell', 'shell', 'layout'];
|
|
56
|
+
// 공통 limb다운 이름 — common 표시만 빠진 경우를 알린다.
|
|
57
|
+
const COMMON_NAMES = ['공통', '공용', 'common', 'shared'];
|
|
58
|
+
|
|
47
59
|
// Promotion signals — when a twig's description contains these, it might belong at limb level.
|
|
48
60
|
// Derived from sidebar/auth/ui-system limb promotions on 2026-05-21.
|
|
49
61
|
const PROMOTION_KEYWORDS = [
|
|
@@ -175,6 +187,23 @@ export function limbUnderTrunkChecks({ type, label, parentType, parentKind, meta
|
|
|
175
187
|
});
|
|
176
188
|
}
|
|
177
189
|
|
|
190
|
+
if (metadata?.common !== true) {
|
|
191
|
+
const lower = trimmed.toLowerCase();
|
|
192
|
+
if (COMMON_NAMES.includes(lower)) {
|
|
193
|
+
out.push({
|
|
194
|
+
rule: 'common-content-outside-common',
|
|
195
|
+
severity: 'info',
|
|
196
|
+
message: `Limb "${label}" reads like the trunk's common limb but is not marked. Set metadata.common=true on it (one per trunk) so the rules can find it. See umtri://rules/trunk-kinds.`,
|
|
197
|
+
});
|
|
198
|
+
} else if (parentKind === 'front' && (FRONT_COMMON_EXACT.includes(lower) || FRONT_COMMON_CONTENT.some(k => lower.includes(k)))) {
|
|
199
|
+
out.push({
|
|
200
|
+
rule: 'common-content-outside-common',
|
|
201
|
+
severity: 'info',
|
|
202
|
+
message: `Limb "${label}" holds what a front trunk keeps in its one common limb (app shell, design system, shared components). Make it a twig under the common limb (metadata.common=true) instead of a separate limb. See umtri://rules/trunk-kinds.`,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
178
207
|
if (metadata?.common === true && siblingCommonCount > 0) {
|
|
179
208
|
out.push({
|
|
180
209
|
rule: 'common-limb-duplicate',
|
|
@@ -82,6 +82,7 @@ exists there, reuse its exact label.
|
|
|
82
82
|
| `limb-domain-suffix` | A limb directly under a trunk ends in a kind suffix (`API`, `응답`, `테이블`, `관리`, `Table(s)`, `Admin`, `Management`). Drop it and reuse the domain name. |
|
|
83
83
|
| `limb-layer-axis` | A limb is named after a technical layer. Split by the trunk kind's axis instead. |
|
|
84
84
|
| `common-limb-duplicate` | The trunk already has a limb with `metadata.common: true`. |
|
|
85
|
+
| `common-content-outside-common` | A front limb is named like common content (design system, shared components, app shell, layout), or a limb is named `공통`/`Common`/`Shared` without `metadata.common: true`. Make it a twig of the common limb, or mark it. |
|
|
85
86
|
|
|
86
87
|
A table leaf (`metadata.table` set) may share its name with a first-class record — a `Bugs` table is not a bug — so `reserved-domain` does not fire on it.
|
|
87
88
|
|
|
@@ -176,6 +176,7 @@ If you see one of these in the response, read it before proceeding:
|
|
|
176
176
|
| `limb-domain-suffix` | Limb under a trunk ends in `API`/`응답`/`테이블`/`관리`… — name the domain alone. |
|
|
177
177
|
| `limb-layer-axis` | Limb is a technical layer (State, Utils, Routes, Middleware…). |
|
|
178
178
|
| `common-limb-duplicate` | The trunk already has its one common limb (`metadata.common`). |
|
|
179
|
+
| `common-content-outside-common` | Front limb holds common content (design system, shell…) outside the common limb, or a `공통` limb isn't marked. |
|
|
179
180
|
|
|
180
181
|
## Further reading (read only when relevant)
|
|
181
182
|
|
package/core/tools.js
CHANGED
|
@@ -177,7 +177,7 @@ function compressProjects(projects, { descMode = 'excerpt' } = {}) {
|
|
|
177
177
|
openBugs: p.openBugs,
|
|
178
178
|
seasonCount: p.seasonCount,
|
|
179
179
|
};
|
|
180
|
-
// 팀 ground면 소속과 내 역할을 싣는다 — viewer면 쓰기 도구가 403으로 막힌다는 걸 미리 알 수 있게.
|
|
180
|
+
// 팀 ground면 소속과 내 역할을 싣는다 — viewer·reporter면 쓰기 도구가 403으로 막힌다는 걸 미리 알 수 있게.
|
|
181
181
|
// personal이면 생략(대부분의 ground가 personal이라 매번 실으면 잡음이다).
|
|
182
182
|
if (p.org && p.org.kind === 'team') {
|
|
183
183
|
out.org = p.org.slug;
|
|
@@ -335,7 +335,7 @@ export function registerTools(server, { api }) {
|
|
|
335
335
|
'list_projects',
|
|
336
336
|
{
|
|
337
337
|
title: 'List grounds (projects)',
|
|
338
|
-
description: 'Returns all grounds the authenticated user can access, with latest activity timestamp. Use this first to discover slugs for other tools. By default returns a summary view — each ground carries slug, name, isActive, transplanting, nodeCount, openBugs, seasonCount, nowSeasonLabel, latestActivityAt, plus a 200-char description excerpt. A ground that belongs to a team also carries org (the team slug) and role (owner/editor/viewer) — a viewer can read but every write tool returns 403 there. A ground with wiki pages also carries wikiCount — prose the tree cannot hold (conventions, decisions, glossary); read it with list_wiki. Pass view="full" for all fields (seedMeta — the ground\'s human-written intent: goal, endDate, budget, audience — plus typeCounts, raw metadata, timestamps); the icon base64 in metadata is always stripped (UI-only).',
|
|
338
|
+
description: 'Returns all grounds the authenticated user can access, with latest activity timestamp. Use this first to discover slugs for other tools. By default returns a summary view — each ground carries slug, name, isActive, transplanting, nodeCount, openBugs, seasonCount, nowSeasonLabel, latestActivityAt, plus a 200-char description excerpt. A ground that belongs to a team also carries org (the team slug) and role (owner/editor/reporter/viewer) — a viewer can read but every write tool returns 403 there; a reporter can only create_bug and update_bug. A ground with wiki pages also carries wikiCount — prose the tree cannot hold (conventions, decisions, glossary); read it with list_wiki. Pass view="full" for all fields (seedMeta — the ground\'s human-written intent: goal, endDate, budget, audience — plus typeCounts, raw metadata, timestamps); the icon base64 in metadata is always stripped (UI-only).',
|
|
339
339
|
inputSchema: z.object({
|
|
340
340
|
view: z.enum(['summary', 'full']).optional().describe('summary (default) for a compact list; full for every field.'),
|
|
341
341
|
}),
|
package/package.json
CHANGED