@hidechae/eztab-mcp 0.5.0 → 0.5.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
@@ -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` は `x-eztab-format-revision: 2` を付けて送る。0.2.0 以前の MCP は複数パートのタブを
117
- 送り返せない(サーバが 409 で拒否する)。新しい形式のタブ(リビジョン 3 以降)は読めるが編集・送信できない
116
+ - `send_to_eztab` は対応する最新の形式リビジョンをヘッダーに付けて送る(0.5.1では `5`)。
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
package/dist/server.js CHANGED
@@ -7,10 +7,11 @@ import { z } from "zod";
7
7
 
8
8
  // ../src/types/tab.ts
9
9
  var TAB_FILE_VERSION = 1;
10
- var SUPPORTED_FORMAT_REVISION = 4;
10
+ var SUPPORTED_FORMAT_REVISION = 5;
11
11
  var MULTI_TRACK_FORMAT_REVISION = 2;
12
12
  var HARMONICS_FORMAT_REVISION = 3;
13
13
  var TEMPO_CHANGES_FORMAT_REVISION = 4;
14
+ var MEASURE_MEMOS_FORMAT_REVISION = 5;
14
15
  var MAX_TRACKS = 4;
15
16
  var DEFAULT_MAIN_TRACK_NAME = "Guitar";
16
17
  var DEFAULT_TEMPO = 120;
@@ -193,6 +194,15 @@ function makeNoteCell(fret, bend, tie, pm, harmonic) {
193
194
  ...harm && harmonic?.harmAt != null ? { harmAt: harmonic.harmAt } : {}
194
195
  };
195
196
  }
197
+ var MAX_MEASURE_MEMO_LENGTH = 1e3;
198
+ function normalizeMeasureMemo(raw) {
199
+ if (typeof raw !== "string") return void 0;
200
+ const memo = raw.replace(/\r\n?|\n/g, "\n");
201
+ if (memo.length > MAX_MEASURE_MEMO_LENGTH) {
202
+ throw new Error(`Measure memo exceeds ${MAX_MEASURE_MEMO_LENGTH} characters`);
203
+ }
204
+ return memo.trim() ? memo : void 0;
205
+ }
196
206
  var CHORD_REST = "rest";
197
207
  var CHORD_REST_LABEL = "N.C.";
198
208
  function chordDisplay(label) {
@@ -268,7 +278,15 @@ function normalizeDocument(data) {
268
278
  );
269
279
  const rawChords = Array.isArray(m.chords) ? m.chords : typeof m.chord === "string" && m.chord ? [m.chord] : [];
270
280
  const chords = normalizeChords(rawChords, subdivisions);
271
- return { beats, beatUnit, subdivisions, cells, chords };
281
+ const memo = normalizeMeasureMemo(m.memo);
282
+ return {
283
+ beats,
284
+ beatUnit,
285
+ subdivisions,
286
+ cells,
287
+ chords,
288
+ ...memo !== void 0 ? { memo } : {}
289
+ };
272
290
  });
273
291
  if (!Array.isArray(data.keys) || data.keys.length === 0) {
274
292
  data.keys = [{ measure: 0, tonic: "C", mode: "major" }];
@@ -459,8 +477,9 @@ function newUid() {
459
477
  return newTabId();
460
478
  }
461
479
  function storedMeasure(m) {
462
- const { uid: _uid, beatWidthOverride: _w, ...rest } = m;
463
- return rest;
480
+ const { uid: _uid, beatWidthOverride: _w, memo, ...rest } = m;
481
+ const normalizedMemo = normalizeMeasureMemo(memo);
482
+ return normalizedMemo === void 0 ? rest : { ...rest, memo: normalizedMemo };
464
483
  }
465
484
  function openWorkspace(doc) {
466
485
  if (doc.workspace) return doc;
@@ -553,6 +572,9 @@ function closeWorkspace(doc) {
553
572
  required = Math.max(required, HARMONICS_FORMAT_REVISION);
554
573
  }
555
574
  if (tempoChanges) required = Math.max(required, TEMPO_CHANGES_FORMAT_REVISION);
575
+ if (out.measures.some((m) => m.memo !== void 0)) {
576
+ required = Math.max(required, MEASURE_MEMOS_FORMAT_REVISION);
577
+ }
556
578
  const revision = formatRevision != null && formatRevision > SUPPORTED_FORMAT_REVISION ? formatRevision : required;
557
579
  if (revision > 1) out.formatRevision = revision;
558
580
  return out;
@@ -562,6 +584,20 @@ function reconcileTracks(prev, next) {
562
584
  const ws = doc.workspace;
563
585
  const measures = assignUids(prev, doc.measures);
564
586
  if (measures !== doc.measures) doc = { ...doc, measures };
587
+ if (ws.active === 0 && prev) {
588
+ const oldMemos = new Map(mainMeasures(prev).map((m) => [m.uid, m.memo]));
589
+ let restored = null;
590
+ measures.forEach((m, i) => {
591
+ const memo = oldMemos.get(m.uid);
592
+ if (memo !== void 0 && !("memo" in m)) {
593
+ restored ??= [...measures];
594
+ restored[i] = { ...m, memo };
595
+ }
596
+ });
597
+ if (restored) {
598
+ doc = { ...doc, measures: restored };
599
+ }
600
+ }
565
601
  let changed = false;
566
602
  const parked = ws.parked.map((body) => {
567
603
  if (!body) return body;
@@ -985,7 +1021,7 @@ var state = {
985
1021
  source: null
986
1022
  };
987
1023
  var server = new McpServer(
988
- { name: "eztab", version: "0.5.0" },
1024
+ { name: "eztab", version: "0.5.1" },
989
1025
  {
990
1026
  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
1027
  }
@@ -1278,7 +1314,7 @@ server.tool(
1278
1314
  }
1279
1315
  }
1280
1316
  if (!existing) doc.measures.push(built);
1281
- else doc.measures[i] = built;
1317
+ else doc.measures[i] = a.track ? built : { ...built, memo: existing.memo };
1282
1318
  lo = Math.min(lo, i);
1283
1319
  hi = Math.max(hi, i);
1284
1320
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hidechae/eztab-mcp",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "MCP server for EZTab \u2014 lets an AI agent build and edit guitar/bass tablature.",
5
5
  "type": "module",
6
6
  "bin": {