umtri-mcp 1.0.2 → 1.1.0
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/instructions.js +12 -0
- package/core/resources/commit-sync.md +55 -0
- package/core/resources.js +7 -0
- package/core/tools.js +53 -19
- package/package.json +1 -1
- package/server.js +2 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// MCP initialize 응답의 instructions — 클라이언트가 세션 시작에 모델에게 그대로
|
|
2
|
+
// 전달한다. 서버가 먼저 말을 걸 수 있는 유일한 자리다(리소스·도구는 불러야 열린다).
|
|
3
|
+
//
|
|
4
|
+
// 그래서 여기엔 "언젠가 읽으면 좋은 것"이 아니라 **읽지 않으면 조용히 어긋나는 것**만
|
|
5
|
+
// 둔다. 매 세션 컨텍스트에 실리므로 길어지면 그 자체가 비용이고, 길수록 안 읽힌다.
|
|
6
|
+
// 상세는 전부 umtri://rules/* 리소스로 넘긴다.
|
|
7
|
+
|
|
8
|
+
export const SERVER_INSTRUCTIONS = `Umtri maps a project's structure as a tree (trunk → limb → twig → leaf → vein) and tracks how it changes over seasons. Start with list_projects, then get_graph.
|
|
9
|
+
|
|
10
|
+
Umtri never reads git. A commit reaches the ground only because something called create_node / update_node / record_commit — so the tree drifts behind the code unless the repo has a habit that says otherwise. If this repo's own rules file (CLAUDE.md, AGENTS.md, …) has no commit-sync policy, read umtri://rules/commit-sync and offer the human a short one to paste in.
|
|
11
|
+
|
|
12
|
+
Before creating or reclassifying nodes, read umtri://rules/vocabulary. Seasons are human-only (umtri://rules/seasons-human-only).`;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Recommendation: Keep a Commit-Sync Policy in the Repo
|
|
2
|
+
|
|
3
|
+
**Umtri never reads your git history by itself.** It has no job runner and no
|
|
4
|
+
repo access — a node learns about a commit only because something called
|
|
5
|
+
`record_commit`, and it learns about a *new* file only because something called
|
|
6
|
+
`create_node`. Nothing in this server can interrupt you at commit time.
|
|
7
|
+
|
|
8
|
+
That is why the tree drifts: code moves forward, the ground stays where it was,
|
|
9
|
+
and no one finds out until a person compares the two by hand.
|
|
10
|
+
|
|
11
|
+
## What to do about it
|
|
12
|
+
|
|
13
|
+
Write a short commit-sync policy into **the repo's own agent rules file** — the
|
|
14
|
+
one every session already loads (`CLAUDE.md`, `AGENTS.md`, `.cursorrules`,
|
|
15
|
+
whatever that project uses). That file is read before the work starts, by the
|
|
16
|
+
same agent that will do the committing. This resource cannot be, because MCP
|
|
17
|
+
resources are pull-only: they are read when someone asks for them, which is
|
|
18
|
+
exactly the moment that never arrives on its own.
|
|
19
|
+
|
|
20
|
+
This is a recommendation, not a gate. Nothing here blocks a commit, and a
|
|
21
|
+
project that ignores it still works — the ground simply ages faster than the
|
|
22
|
+
code.
|
|
23
|
+
|
|
24
|
+
## Snippet to adapt
|
|
25
|
+
|
|
26
|
+
Paste into the repo's rules file and edit to fit the project (translate it if
|
|
27
|
+
the file is in another language — the wording matters less than the trigger):
|
|
28
|
+
|
|
29
|
+
```markdown
|
|
30
|
+
### Umtri sync (before committing)
|
|
31
|
+
|
|
32
|
+
Umtri does not read git. When a commit is about to be made, check whether the
|
|
33
|
+
change also belongs in the ground:
|
|
34
|
+
|
|
35
|
+
1. Changed paths vs `metadata.implements` — does every changed file map to a node?
|
|
36
|
+
- New unit of the system (route, screen, table, integration) → `create_node`,
|
|
37
|
+
with `metadata.implements` set. Then wire its connections
|
|
38
|
+
(`create_edge` / `create_api`) — an unconnected node is invisible to impact.
|
|
39
|
+
- File moved or renamed → `update_node` on the affected nodes' `implements`.
|
|
40
|
+
Directory moves are the expensive case: one `git mv` can strand dozens.
|
|
41
|
+
- Behavior changed but structure did not → nothing to do.
|
|
42
|
+
2. Does the commit close a tracked bug? → `update_bug` (status, and `solution`
|
|
43
|
+
describing what was actually done).
|
|
44
|
+
3. Then `record_commit` with the SHA and the changed paths.
|
|
45
|
+
|
|
46
|
+
Ask the human before adding nodes for anything ambiguous — a wrong node is
|
|
47
|
+
harder to notice later than a missing one.
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Where automation fits
|
|
51
|
+
|
|
52
|
+
CI (a GitHub Action on push) is a good place for step 3 — it never forgets a
|
|
53
|
+
SHA. It is a poor place for steps 1 and 2: by then the commit exists, the log
|
|
54
|
+
is unread, and the run is green either way. Detection belongs in CI; the
|
|
55
|
+
decision belongs where a human or agent is still in the loop.
|
package/core/resources.js
CHANGED
|
@@ -43,6 +43,13 @@ const RESOURCES = [
|
|
|
43
43
|
title: 'Plan nodes are a node-based brief',
|
|
44
44
|
description: 'Nodes marked plan:true are the human\'s structural brief (intent expressed as nodes, not a prompt). Read them as instructions, realize the code, attach metadata.implements (required), keep new detail as plan, and leave committing to the human. Read when get_graph shows any plan:true node.',
|
|
45
45
|
},
|
|
46
|
+
{
|
|
47
|
+
name: 'commit-sync',
|
|
48
|
+
uri: 'umtri://rules/commit-sync',
|
|
49
|
+
file: 'commit-sync.md',
|
|
50
|
+
title: 'Keep the ground in step with commits',
|
|
51
|
+
description: 'Umtri never reads git — nodes drift unless something records the change. Recommends writing a short commit-sync policy into the repo\'s own agent rules file (CLAUDE.md/AGENTS.md), with a snippet to adapt. Read when setting Umtri up in a repo, or when a commit turns out to have left the tree behind.',
|
|
52
|
+
},
|
|
46
53
|
{
|
|
47
54
|
name: 'system-structure',
|
|
48
55
|
uri: 'umtri://rules/system-structure',
|
package/core/tools.js
CHANGED
|
@@ -64,12 +64,28 @@ function computeDormantSet(nodes) {
|
|
|
64
64
|
return dormant;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
// 버그 status
|
|
68
|
-
//
|
|
67
|
+
// 버그 필터 어휘 → DB status 집합. 도구 표면은 제품 어휘(wild/chasing/catched)를 쓴다 —
|
|
68
|
+
// UI·vocabulary.md가 쓰는 말과 같아야 사람이 본 화면과 도구 응답이 같은 것을 가리킨다.
|
|
69
|
+
// DB 상태값(open/in_progress/…)도 계속 받는다: 기존 호출자를 깨지 않기 위해서다.
|
|
70
|
+
//
|
|
71
|
+
// 'open'은 여기서 **wild와 다르다.** REST의 status=open은 store에서 open+in_progress로
|
|
72
|
+
// 번역되고(앱 대시보드의 Bugs Alive가 그 의미에 기대고 있다) 그래서 REST로는 순수 wild를
|
|
73
|
+
// 뽑을 수 없다. 그 갈래는 이 표에서만 갈린다.
|
|
74
|
+
const BUG_STATUS_SETS = {
|
|
75
|
+
active: ['open', 'in_progress'],
|
|
76
|
+
wild: ['open'],
|
|
77
|
+
chasing: ['in_progress'],
|
|
78
|
+
catched: ['resolved', 'closed'],
|
|
79
|
+
open: ['open', 'in_progress'], // REST status=open과 같은 의미 (하위호환)
|
|
80
|
+
in_progress: ['in_progress'],
|
|
81
|
+
resolved: ['resolved'],
|
|
82
|
+
closed: ['closed'],
|
|
83
|
+
};
|
|
84
|
+
|
|
69
85
|
function filterBugsByStatus(bugs, bugStatus) {
|
|
70
86
|
if (!Array.isArray(bugs) || bugStatus === 'all') return bugs;
|
|
71
|
-
|
|
72
|
-
return bugs.filter((b) =>
|
|
87
|
+
const allowed = BUG_STATUS_SETS[bugStatus] || BUG_STATUS_SETS.active;
|
|
88
|
+
return bugs.filter((b) => allowed.includes(b.status));
|
|
73
89
|
}
|
|
74
90
|
|
|
75
91
|
// project.metadata에서 UI 전용 무거운 키(아이콘 base64 등)를 제거 — LLM 컨텍스트엔 무의미.
|
|
@@ -323,12 +339,12 @@ export function registerTools(server, { api }) {
|
|
|
323
339
|
slug: z.string().min(1).describe('Ground slug (from list_projects).'),
|
|
324
340
|
view: z.enum(['summary', 'full']).optional().describe('summary (default) for compact nodes; full for raw shape with all fields.'),
|
|
325
341
|
rootId: z.string().optional().describe('Scope to this node and its descendants — read one trunk or limb at a time. Get the id from a prior get_graph (e.g. maxType="trunk" for a cheap skeleton). Bugs are scoped to the subtree; edges/apis that cross the subtree boundary are still returned (boundary:true) with their outside endpoint in externalNodes.'),
|
|
326
|
-
depth: z.number().int().min(0).optional().describe('With rootId: how many levels below the root to include (0 = just the root, 1 = root + direct children). Omit for the whole subtree.'),
|
|
342
|
+
depth: z.coerce.number().int().min(0).optional().describe('With rootId: how many levels below the root to include (0 = just the root, 1 = root + direct children). Omit for the whole subtree.'),
|
|
327
343
|
maxType: z.enum(['trunk', 'limb', 'twig', 'leaf', 'vein']).optional().describe('Layer ceiling — return only nodes at this level or higher (trunk is highest). maxType="twig" yields the structural skeleton without leaves/veins. Great for a cheap overview before drilling in with rootId.'),
|
|
328
344
|
role: z.enum(['structure', 'object', 'action']).optional().describe('Cross-section by role (structure=trunk/limb/twig, object=leaf, action=vein). Parents may fall outside the result.'),
|
|
329
345
|
season: z.string().optional().describe('Born-in delta — return only nodes that first appeared in this season (season id from list_seasons): what grew that season.'),
|
|
330
346
|
descriptions: z.enum(['none', 'excerpt', 'full']).optional().describe('How much of each node/bug description to include in summary view: none (drop them — lightest structural read), excerpt (200-char preview, default), or full (verbatim). Ignored when view="full" (always verbatim).'),
|
|
331
|
-
bugStatus: z.enum(['active', 'all', 'open', 'in_progress', 'resolved', 'closed']).optional().describe('Which bugs to include. Default "active" = open + in_progress
|
|
347
|
+
bugStatus: z.enum(['active', 'wild', 'chasing', 'catched', 'all', 'open', 'in_progress', 'resolved', 'closed']).optional().describe('Which bugs to include. Default "active" = wild + chasing (open + in_progress) — catched ones are healed erosion, noise on the tree. "wild" for the untouched only, "catched" for healed, "all" for every bug. Raw DB statuses still work. Use list_bugs for richer bug queries (limit/order), get_bug for one you can name.'),
|
|
332
348
|
}),
|
|
333
349
|
},
|
|
334
350
|
async ({ slug, view, rootId, depth, maxType, role, season, descriptions, bugStatus }) => {
|
|
@@ -367,7 +383,7 @@ export function registerTools(server, { api }) {
|
|
|
367
383
|
node: z.string().optional().describe('Start node id. Provide this OR bug.'),
|
|
368
384
|
bug: z.string().optional().describe('Start from this bug\'s target. Provide this OR node.'),
|
|
369
385
|
direction: z.enum(['affected', 'dependsOn', 'both']).optional().describe('affected (default): what breaks if the start breaks. dependsOn: what the start relies on. both: union.'),
|
|
370
|
-
maxDepth: z.number().int().min(0).optional().describe('Max hops to traverse. Omit for unbounded.'),
|
|
386
|
+
maxDepth: z.coerce.number().int().min(0).optional().describe('Max hops to traverse. Omit for unbounded.'),
|
|
371
387
|
}),
|
|
372
388
|
},
|
|
373
389
|
async ({ slug, node, bug, direction, maxDepth }) => {
|
|
@@ -388,19 +404,37 @@ export function registerTools(server, { api }) {
|
|
|
388
404
|
'list_bugs',
|
|
389
405
|
{
|
|
390
406
|
title: 'List bugs of a ground',
|
|
391
|
-
description: '
|
|
407
|
+
description: 'Scans the bugs (issues eroding the ground) of one ground. Looking at ONE bug you already know the number/id of? Call get_bug instead — no need to list. Bugs default to status="active" (wild + chasing): a healed bug is history, and history is not what a scan is for. Ask for "catched" or "all" when you actually want it. Status speaks the product\'s own words — wild (found, nobody on it) → chasing (someone is fixing it) → catched (it landed) — the same words the UI shows. Each bug has a score (0–8 change-risk; 8 = riskiest). Targets: a node, an api, or the ground itself. By default returns a summary view — each bug carries id, seq (the number a human says, "#14"), target, title, score, status, createdAt/resolvedAt, plus a 200-char description excerpt. Each node/api bug also carries impactCount — how many other nodes its target reaches by blast radius (affected direction), so you can spot wide-blast bugs at a glance; call get_impact on that bug for the full reached list. Pass view="full" for full descriptions and metadata. Use limit + order to bound a scan (e.g. the 5 newest) instead of pulling every bug.',
|
|
392
408
|
inputSchema: z.object({
|
|
393
409
|
slug: z.string().min(1).describe('Ground slug.'),
|
|
394
|
-
status: z.enum(['open', 'in_progress', 'resolved', 'closed']).optional()
|
|
395
|
-
.describe('
|
|
410
|
+
status: z.enum(['active', 'wild', 'chasing', 'catched', 'all', 'open', 'in_progress', 'resolved', 'closed']).optional()
|
|
411
|
+
.describe('Which bugs to include. Default "active" = wild + chasing (open + in_progress). "wild" = found but untouched, "chasing" = someone is on it, "catched" = healed (resolved + closed), "all" = every bug. The raw DB statuses are accepted too; note "open" means wild + chasing there (REST semantics), so pass "wild" when you want only the untouched ones.'),
|
|
412
|
+
limit: z.coerce.number().int().positive().optional().describe('Return at most this many bugs (applied after the status filter). Omit for all matching.'),
|
|
413
|
+
order: z.enum(['asc', 'desc']).optional().describe('By creation time: "asc" oldest first (default), "desc" newest first. Pair with limit for "the N newest".'),
|
|
396
414
|
view: z.enum(['summary', 'full']).optional().describe('summary (default) for a compact list; full for verbatim descriptions + metadata.'),
|
|
397
415
|
}),
|
|
398
416
|
},
|
|
399
|
-
async ({ slug, status, view }) => {
|
|
417
|
+
async ({ slug, status, limit, order, view }) => {
|
|
400
418
|
try {
|
|
419
|
+
const want = status || 'active';
|
|
401
420
|
const qs = ['impact=true']; // 각 버그에 impactCount(blast radius 크기) 자동 첨부
|
|
402
|
-
if (
|
|
403
|
-
|
|
421
|
+
if (order) qs.push(`order=${encodeURIComponent(order)}`);
|
|
422
|
+
// status·limit을 서버로 넘기는 건 REST 필터가 원하는 집합과 **정확히** 같을 때만이다.
|
|
423
|
+
// wild(순수 open)·catched(resolved+closed)는 REST에 대응 값이 없어 여기서 거른다 —
|
|
424
|
+
// 그때 limit까지 넘기면 걸러지기 전 개수로 잘려 결과가 모자란다.
|
|
425
|
+
const REST_EXACT = {
|
|
426
|
+
active: 'open', open: 'open', // REST의 open = open + in_progress
|
|
427
|
+
chasing: 'in_progress', in_progress: 'in_progress',
|
|
428
|
+
resolved: 'resolved', closed: 'closed',
|
|
429
|
+
};
|
|
430
|
+
const exact = REST_EXACT[want] || null; // wild·catched는 REST에 대응 값이 없다
|
|
431
|
+
if (exact) qs.push(`status=${exact}`);
|
|
432
|
+
if (exact || want === 'all') { if (limit) qs.push(`limit=${limit}`); }
|
|
433
|
+
let bugs = await api.get(`/api/projects/${encodeURIComponent(slug)}/bugs?${qs.join('&')}`);
|
|
434
|
+
if (!exact && want !== 'all') {
|
|
435
|
+
bugs = filterBugsByStatus(bugs, want);
|
|
436
|
+
if (limit) bugs = bugs.slice(0, limit);
|
|
437
|
+
}
|
|
404
438
|
return jsonResult(view === 'full' ? bugs : compressBugs(bugs));
|
|
405
439
|
} catch (e) { return errorResult(e); }
|
|
406
440
|
},
|
|
@@ -410,10 +444,10 @@ export function registerTools(server, { api }) {
|
|
|
410
444
|
'get_bug',
|
|
411
445
|
{
|
|
412
446
|
title: 'Get one bug of a ground',
|
|
413
|
-
description: 'Returns a single bug by reference. `ref` accepts either the human-facing number shown in the UI (seq — "14" or #14) or the internal id (bug-<uuid>).
|
|
447
|
+
description: 'Returns a single bug by reference — the cheap path when you already know which bug you mean. `ref` accepts either the human-facing number shown in the UI (seq — "14" or #14) or the internal id (bug-<uuid>), so a human saying "#8" needs no listing at all. Unlike list_bugs this ignores status: a catched bug is still readable by number, which is how you review what a past fix actually did. Use list_bugs only to scan for bugs you cannot yet name. The response carries the full description and metadata, plus impact — the blast radius reached from the bug\'s target (reachedCount, the reached nodes with hop distance, and other active bugs sitting in that radius). Numbers are per-ground and never reused, so a deleted bug leaves a gap rather than shifting the others. Returns an error if no bug matches.',
|
|
414
448
|
inputSchema: z.object({
|
|
415
449
|
slug: z.string().min(1).describe('Ground slug.'),
|
|
416
|
-
ref: z.union([z.string().min(1), z.number().int().positive()])
|
|
450
|
+
ref: z.union([z.string().min(1), z.coerce.number().int().positive()])
|
|
417
451
|
.describe('Bug number (seq, e.g. 14) or internal id (bug-<uuid>).'),
|
|
418
452
|
}),
|
|
419
453
|
},
|
|
@@ -452,7 +486,7 @@ export function registerTools(server, { api }) {
|
|
|
452
486
|
description: 'Returns the append-only change history (most recent first) of a ground: node/edge/api/bug create, update (with a field-level diff {from,to}), delete, and plan_commit. Each event carries the actor ("user:<id>" for humans, "token:<id>" for agents/automation), a summary label, and for updates a diff. Use it to answer "what changed, when, and by whom" — e.g. a bug\'s status transitions or a node\'s edits over time. Filter with entityType/entityId to get a single entity\'s timeline. Note: git commits recorded via record_commit live in a node\'s metadata.commits, not here.',
|
|
453
487
|
inputSchema: z.object({
|
|
454
488
|
slug: z.string().min(1).describe('Ground slug.'),
|
|
455
|
-
limit: z.number().int().positive().max(200).optional().describe('Max events to return (default 50, cap 200).'),
|
|
489
|
+
limit: z.coerce.number().int().positive().max(200).optional().describe('Max events to return (default 50, cap 200).'),
|
|
456
490
|
before: z.string().optional().describe('ISO timestamp — return events strictly older than this (keyset pagination).'),
|
|
457
491
|
entityType: z.enum(['node', 'edge', 'api', 'bug']).optional().describe('Filter to one entity type.'),
|
|
458
492
|
entityId: z.string().optional().describe('Filter to one entity\'s timeline (usually paired with entityType).'),
|
|
@@ -497,7 +531,7 @@ export function registerTools(server, { api }) {
|
|
|
497
531
|
}).describe('Bug target. Use {kind:"ground"} when the bug is about the project as a whole.'),
|
|
498
532
|
description: z.string().optional().describe('Longer details on what is wrong (markdown allowed).'),
|
|
499
533
|
solution: z.string().optional().describe('How to fix it. At report time this is the plan/idea; update it to what was actually applied when you resolve. Markdown allowed. Omit if you do not know yet.'),
|
|
500
|
-
score: z.number().int().min(0).max(8).optional().describe('Change risk 0–8 (8 = riskiest: critical impact × complex fix). Defaults to 4. See tool description for the rubric.'),
|
|
534
|
+
score: z.coerce.number().int().min(0).max(8).optional().describe('Change risk 0–8 (8 = riskiest: critical impact × complex fix). Defaults to 4. See tool description for the rubric.'),
|
|
501
535
|
status: z.enum(['open', 'in_progress', 'resolved', 'closed']).optional()
|
|
502
536
|
.describe('Defaults to "open".'),
|
|
503
537
|
}),
|
|
@@ -751,7 +785,7 @@ export function registerTools(server, { api }) {
|
|
|
751
785
|
'record_commit',
|
|
752
786
|
{
|
|
753
787
|
title: 'Record a git commit onto the nodes it touched',
|
|
754
|
-
description: 'Configuration record (CI/CD). Given a commit SHA and the files it changed, finds nodes whose metadata.implements include any of those files and appends the commit (sha + timestamp + optional message) to their metadata.commits (deduped by sha). Returns the matched node ids. When one commit touches several nodes that are NOT yet connected, the response also carries coChangeCandidates[] ({a, aLabel, b, bLabel}) — nodes that change together are dependency candidates; review them and add a create_edge/create_api where a real relation exists (not auto-created; suppressed for large multi-node commits). Umtri does not run jobs or read git itself — a GitHub Action / CI step or an agent supplies the sha+files. See umtri://rules/plan.',
|
|
788
|
+
description: 'Configuration record (CI/CD). Given a commit SHA and the files it changed, finds nodes whose metadata.implements include any of those files and appends the commit (sha + timestamp + optional message) to their metadata.commits (deduped by sha). Returns the matched node ids. When one commit touches several nodes that are NOT yet connected, the response also carries coChangeCandidates[] ({a, aLabel, b, bLabel}) — nodes that change together are dependency candidates; review them and add a create_edge/create_api where a real relation exists (not auto-created; suppressed for large multi-node commits). Umtri does not run jobs or read git itself — a GitHub Action / CI step or an agent supplies the sha+files. Recording a commit is the LAST step: first make sure the change is in the tree (new unit → create_node with metadata.implements; moved file → update_node), otherwise the sha lands on the nodes that happen to exist and the new ones stay invisible. A repo that keeps forgetting should write the habit down — see umtri://rules/commit-sync. See also umtri://rules/plan.',
|
|
755
789
|
inputSchema: z.object({
|
|
756
790
|
slug: z.string().min(1).describe('Ground slug.'),
|
|
757
791
|
sha: z.string().min(1).describe('Commit SHA (short or full).'),
|
|
@@ -905,7 +939,7 @@ export function registerTools(server, { api }) {
|
|
|
905
939
|
id: z.string().min(1).describe('Bug id.'),
|
|
906
940
|
patch: z.object({
|
|
907
941
|
status: z.enum(['open', 'in_progress', 'resolved', 'closed']).optional(),
|
|
908
|
-
score: z.number().int().min(0).max(8).optional().describe('Change risk 0–8 (8 = riskiest). See create_bug for the rubric.'),
|
|
942
|
+
score: z.coerce.number().int().min(0).max(8).optional().describe('Change risk 0–8 (8 = riskiest). See create_bug for the rubric.'),
|
|
909
943
|
title: z.string().min(1).max(200).optional(),
|
|
910
944
|
description: z.string().nullable().optional().describe('What is wrong.'),
|
|
911
945
|
solution: z.string().nullable().optional().describe('How it is being / was fixed. Rewrite this to the actually-applied fix when you set status="resolved" — a stale plan left behind is worse than an empty field. null clears it.'),
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -19,6 +19,7 @@ import { dirname, join } from 'node:path';
|
|
|
19
19
|
import { createApiClient } from './core/api-client.js';
|
|
20
20
|
import { registerTools } from './core/tools.js';
|
|
21
21
|
import { registerResources } from './core/resources.js';
|
|
22
|
+
import { SERVER_INSTRUCTIONS } from './core/instructions.js';
|
|
22
23
|
|
|
23
24
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
24
25
|
const RESOURCES_DIR = join(__dirname, 'core', 'resources');
|
|
@@ -44,7 +45,7 @@ const api = createApiClient({
|
|
|
44
45
|
const server = new McpServer({
|
|
45
46
|
name: 'umtri-mcp',
|
|
46
47
|
version: VERSION,
|
|
47
|
-
});
|
|
48
|
+
}, { instructions: SERVER_INSTRUCTIONS });
|
|
48
49
|
|
|
49
50
|
registerTools(server, { api });
|
|
50
51
|
registerResources(server, { resourcesDir: RESOURCES_DIR });
|