@hidechae/eztab-mcp 0.5.0 → 0.5.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/README.md +12 -2
- package/dist/server.js +62 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -113,8 +113,16 @@ Run standalone for testing: `task mcp` (or `npx tsx mcp/server.ts`).
|
|
|
113
113
|
- コードは track 0 のもの(`set_chords` は track 0 の列で指定)。`write_measures(track > 0)` ではコード指定と、
|
|
114
114
|
既存小節の拍子を変える `time` を拒否する
|
|
115
115
|
- `get_tab` は全パートを小節ごとに並べて表示する(`track` で1パートに絞れる)
|
|
116
|
-
- `send_to_eztab`
|
|
117
|
-
|
|
116
|
+
- `send_to_eztab` は対応する最新の形式リビジョンをヘッダーに付けて送る(0.5.2では `6`)。
|
|
117
|
+
対応上限を超える形式のタブは閲覧のみで、編集・送信できない。
|
|
118
|
+
0.2.0以前のMCPは複数パートのタブを送り返せない(サーバが409で拒否する)。
|
|
119
|
+
|
|
120
|
+
### 小節メモの保持
|
|
121
|
+
|
|
122
|
+
0.5.1以降はアプリで入力した小節共通メモ(形式リビジョン5)を保持して編集・送信できます。
|
|
123
|
+
メモ専用の入力ツールはなく、追加・変更・削除はアプリの「小節メモ…」で行います。
|
|
124
|
+
`write_measures` で既存小節を書き換えた場合もメモを残します。小節削除ではその小節のメモも削除し、
|
|
125
|
+
挿入した空小節はメモなしになります。旧MCPはメモのあるタブを編集・送信できません。
|
|
118
126
|
|
|
119
127
|
These cover the editor's current model: variable string count, per-beat chords,
|
|
120
128
|
keys with mid-piece changes, section markers, per-measure resolution, time
|
|
@@ -138,3 +146,5 @@ Claude Desktop では入力欄の「+」→ eztab → `import_tab_from_image`
|
|
|
138
146
|
EZTab のモデルに無い記譜(スライド・ハンマリング・プリング → タイ/スラー、
|
|
139
147
|
ハーモニクス・ビブラート・装飾音 → 主音のみ等)の寄せ方は手順書
|
|
140
148
|
([prompts.ts](prompts.ts))に書いてあり、書き起こし後に簡略化した箇所を報告させる。
|
|
149
|
+
|
|
150
|
+
0.5.2以降は曲全体のMarkdownメモ(形式リビジョン6)を既存の編集・送信操作で保持します。曲メモの入力・表示はwebアプリで行います。
|
package/dist/server.js
CHANGED
|
@@ -7,10 +7,12 @@ import { z } from "zod";
|
|
|
7
7
|
|
|
8
8
|
// ../src/types/tab.ts
|
|
9
9
|
var TAB_FILE_VERSION = 1;
|
|
10
|
-
var SUPPORTED_FORMAT_REVISION =
|
|
10
|
+
var SUPPORTED_FORMAT_REVISION = 6;
|
|
11
|
+
var SONG_MEMO_FORMAT_REVISION = 6;
|
|
11
12
|
var MULTI_TRACK_FORMAT_REVISION = 2;
|
|
12
13
|
var HARMONICS_FORMAT_REVISION = 3;
|
|
13
14
|
var TEMPO_CHANGES_FORMAT_REVISION = 4;
|
|
15
|
+
var MEASURE_MEMOS_FORMAT_REVISION = 5;
|
|
14
16
|
var MAX_TRACKS = 4;
|
|
15
17
|
var DEFAULT_MAIN_TRACK_NAME = "Guitar";
|
|
16
18
|
var DEFAULT_TEMPO = 120;
|
|
@@ -193,6 +195,15 @@ function makeNoteCell(fret, bend, tie, pm, harmonic) {
|
|
|
193
195
|
...harm && harmonic?.harmAt != null ? { harmAt: harmonic.harmAt } : {}
|
|
194
196
|
};
|
|
195
197
|
}
|
|
198
|
+
var MAX_MEASURE_MEMO_LENGTH = 1e3;
|
|
199
|
+
function normalizeMeasureMemo(raw) {
|
|
200
|
+
if (typeof raw !== "string") return void 0;
|
|
201
|
+
const memo = raw.replace(/\r\n?|\n/g, "\n");
|
|
202
|
+
if (memo.length > MAX_MEASURE_MEMO_LENGTH) {
|
|
203
|
+
throw new Error(`Measure memo exceeds ${MAX_MEASURE_MEMO_LENGTH} characters`);
|
|
204
|
+
}
|
|
205
|
+
return memo.trim() ? memo : void 0;
|
|
206
|
+
}
|
|
196
207
|
var CHORD_REST = "rest";
|
|
197
208
|
var CHORD_REST_LABEL = "N.C.";
|
|
198
209
|
function chordDisplay(label) {
|
|
@@ -242,10 +253,24 @@ function normalizeChords(raw, subdivisions) {
|
|
|
242
253
|
}
|
|
243
254
|
return out;
|
|
244
255
|
}
|
|
256
|
+
var MAX_SONG_MEMO_LENGTH = 1e4;
|
|
257
|
+
var DocumentValidationError = class extends Error {
|
|
258
|
+
};
|
|
259
|
+
function normalizeSongMemo(raw) {
|
|
260
|
+
if (typeof raw !== "string") return void 0;
|
|
261
|
+
const memo = raw.replace(/\r\n?/g, "\n");
|
|
262
|
+
if (memo.length > MAX_SONG_MEMO_LENGTH) {
|
|
263
|
+
throw new DocumentValidationError(`\u66F2\u30E1\u30E2\u306F${MAX_SONG_MEMO_LENGTH}\u6587\u5B57\u4EE5\u5185\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
|
|
264
|
+
}
|
|
265
|
+
return memo.trim() ? memo : void 0;
|
|
266
|
+
}
|
|
245
267
|
function normalizeDocument(data) {
|
|
246
268
|
if (!data || !Array.isArray(data.tuning) || !Array.isArray(data.measures)) {
|
|
247
269
|
throw new Error("Not a valid EZTab document");
|
|
248
270
|
}
|
|
271
|
+
const memo = normalizeSongMemo(data.memo);
|
|
272
|
+
if (memo === void 0) delete data.memo;
|
|
273
|
+
else data.memo = memo;
|
|
249
274
|
data.measures = data.measures.map((m) => {
|
|
250
275
|
let beats = m.beats;
|
|
251
276
|
let rawSubs = m.subdivisions;
|
|
@@ -268,7 +293,15 @@ function normalizeDocument(data) {
|
|
|
268
293
|
);
|
|
269
294
|
const rawChords = Array.isArray(m.chords) ? m.chords : typeof m.chord === "string" && m.chord ? [m.chord] : [];
|
|
270
295
|
const chords = normalizeChords(rawChords, subdivisions);
|
|
271
|
-
|
|
296
|
+
const memo2 = normalizeMeasureMemo(m.memo);
|
|
297
|
+
return {
|
|
298
|
+
beats,
|
|
299
|
+
beatUnit,
|
|
300
|
+
subdivisions,
|
|
301
|
+
cells,
|
|
302
|
+
chords,
|
|
303
|
+
...memo2 !== void 0 ? { memo: memo2 } : {}
|
|
304
|
+
};
|
|
272
305
|
});
|
|
273
306
|
if (!Array.isArray(data.keys) || data.keys.length === 0) {
|
|
274
307
|
data.keys = [{ measure: 0, tonic: "C", mode: "major" }];
|
|
@@ -459,8 +492,9 @@ function newUid() {
|
|
|
459
492
|
return newTabId();
|
|
460
493
|
}
|
|
461
494
|
function storedMeasure(m) {
|
|
462
|
-
const { uid: _uid, beatWidthOverride: _w, ...rest } = m;
|
|
463
|
-
|
|
495
|
+
const { uid: _uid, beatWidthOverride: _w, memo, ...rest } = m;
|
|
496
|
+
const normalizedMemo = normalizeMeasureMemo(memo);
|
|
497
|
+
return normalizedMemo === void 0 ? rest : { ...rest, memo: normalizedMemo };
|
|
464
498
|
}
|
|
465
499
|
function openWorkspace(doc) {
|
|
466
500
|
if (doc.workspace) return doc;
|
|
@@ -517,7 +551,7 @@ function mainMeasures(doc) {
|
|
|
517
551
|
}
|
|
518
552
|
function closeWorkspace(doc) {
|
|
519
553
|
const ws = doc.workspace;
|
|
520
|
-
if (!ws) return doc;
|
|
554
|
+
if (!ws) return closeWorkspace(openWorkspace(doc));
|
|
521
555
|
const bodies = trackBodies(doc);
|
|
522
556
|
const main = bodies[0];
|
|
523
557
|
const {
|
|
@@ -527,6 +561,7 @@ function closeWorkspace(doc) {
|
|
|
527
561
|
tracks: _tr,
|
|
528
562
|
formatRevision,
|
|
529
563
|
tempoChanges: rawTempoChanges,
|
|
564
|
+
memo: rawMemo,
|
|
530
565
|
...rest
|
|
531
566
|
} = doc;
|
|
532
567
|
const tempoChanges = normalizeTempoChanges(doc.tempo, rawTempoChanges, main.measures.length);
|
|
@@ -535,6 +570,8 @@ function closeWorkspace(doc) {
|
|
|
535
570
|
tuning: [...main.tuning],
|
|
536
571
|
measures: main.measures.map(storedMeasure)
|
|
537
572
|
};
|
|
573
|
+
const memo = normalizeSongMemo(rawMemo);
|
|
574
|
+
if (memo !== void 0) out.memo = memo;
|
|
538
575
|
const extras = bodies.slice(1).map(
|
|
539
576
|
(b, i) => ({
|
|
540
577
|
...ws.metas[i + 1],
|
|
@@ -548,11 +585,15 @@ function closeWorkspace(doc) {
|
|
|
548
585
|
if (extras.length > 0) out.tracks = extras;
|
|
549
586
|
if (tempoChanges) out.tempoChanges = tempoChanges;
|
|
550
587
|
let required = 1;
|
|
588
|
+
if (memo !== void 0) required = SONG_MEMO_FORMAT_REVISION;
|
|
551
589
|
if (extras.length > 0) required = Math.max(required, MULTI_TRACK_FORMAT_REVISION);
|
|
552
590
|
if (bodies.some((b) => measuresHaveHarmonics(b.measures))) {
|
|
553
591
|
required = Math.max(required, HARMONICS_FORMAT_REVISION);
|
|
554
592
|
}
|
|
555
593
|
if (tempoChanges) required = Math.max(required, TEMPO_CHANGES_FORMAT_REVISION);
|
|
594
|
+
if (out.measures.some((m) => m.memo !== void 0)) {
|
|
595
|
+
required = Math.max(required, MEASURE_MEMOS_FORMAT_REVISION);
|
|
596
|
+
}
|
|
556
597
|
const revision = formatRevision != null && formatRevision > SUPPORTED_FORMAT_REVISION ? formatRevision : required;
|
|
557
598
|
if (revision > 1) out.formatRevision = revision;
|
|
558
599
|
return out;
|
|
@@ -562,6 +603,20 @@ function reconcileTracks(prev, next) {
|
|
|
562
603
|
const ws = doc.workspace;
|
|
563
604
|
const measures = assignUids(prev, doc.measures);
|
|
564
605
|
if (measures !== doc.measures) doc = { ...doc, measures };
|
|
606
|
+
if (ws.active === 0 && prev) {
|
|
607
|
+
const oldMemos = new Map(mainMeasures(prev).map((m) => [m.uid, m.memo]));
|
|
608
|
+
let restored = null;
|
|
609
|
+
measures.forEach((m, i) => {
|
|
610
|
+
const memo = oldMemos.get(m.uid);
|
|
611
|
+
if (memo !== void 0 && !("memo" in m)) {
|
|
612
|
+
restored ??= [...measures];
|
|
613
|
+
restored[i] = { ...m, memo };
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
if (restored) {
|
|
617
|
+
doc = { ...doc, measures: restored };
|
|
618
|
+
}
|
|
619
|
+
}
|
|
565
620
|
let changed = false;
|
|
566
621
|
const parked = ws.parked.map((body) => {
|
|
567
622
|
if (!body) return body;
|
|
@@ -985,7 +1040,7 @@ var state = {
|
|
|
985
1040
|
source: null
|
|
986
1041
|
};
|
|
987
1042
|
var server = new McpServer(
|
|
988
|
-
{ name: "eztab", version: "0.5.
|
|
1043
|
+
{ name: "eztab", version: "0.5.2" },
|
|
989
1044
|
{
|
|
990
1045
|
instructions: "All indices are 0-based: measure 0 is the first measure, string 0 is the top (highest) string, and column 0 is the first column. Notes and chords share the same column grid. Subdivisions are per beat and can differ within a measure (e.g. [2,2,3,2] mixes a triplet beat into straight eighths), so columns per measure = the sum of its per-beat subdivisions; get_tab's ruler shows the column indices. add_measures `at` is a 0-based insert position too. No need to ask the user about indexing. Exception: write_measures uses guitar string numbers (1 = top line), matching the labels get_tab prints. To write whole measures (e.g. transcribing a score or an image), prefer write_measures over set_cells \u2014 it takes one string per beat and does the column arithmetic for you. For transcribing from an image, follow the import_tab_from_image prompt. To deliver a result to the EZTab app, call send_to_eztab; to fix an existing tab, list_tabs \u2192 open_tab \u2192 edit \u2192 send_to_eztab. A tab can hold up to 4 tracks (parts, e.g. guitar + bass); track 0 is the main track. Tools that edit notes take an optional 0-based `track` (default 0). Measures and time signatures are shared by all tracks (adding/removing a measure affects every track); subdivisions are per track; chords belong to track 0 and use its columns."
|
|
991
1046
|
}
|
|
@@ -1278,7 +1333,7 @@ server.tool(
|
|
|
1278
1333
|
}
|
|
1279
1334
|
}
|
|
1280
1335
|
if (!existing) doc.measures.push(built);
|
|
1281
|
-
else doc.measures[i] = built;
|
|
1336
|
+
else doc.measures[i] = a.track ? built : { ...built, memo: existing.memo };
|
|
1282
1337
|
lo = Math.min(lo, i);
|
|
1283
1338
|
hi = Math.max(hi, i);
|
|
1284
1339
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hidechae/eztab-mcp",
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"description": "MCP server for EZTab
|
|
3
|
+
"version": "0.5.2",
|
|
4
|
+
"description": "MCP server for EZTab — lets an AI agent build and edit guitar/bass tablature.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"eztab-mcp": "dist/server.js"
|