@shuji-bonji/pdf-writer-mcp 0.2.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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +208 -0
  3. package/dist/config.d.ts +34 -0
  4. package/dist/config.js +38 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/constants.d.ts +46 -0
  7. package/dist/constants.js +46 -0
  8. package/dist/constants.js.map +1 -0
  9. package/dist/index.d.ts +6 -0
  10. package/dist/index.js +52 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/services/builder.d.ts +10 -0
  13. package/dist/services/builder.js +40 -0
  14. package/dist/services/builder.js.map +1 -0
  15. package/dist/services/editor.d.ts +24 -0
  16. package/dist/services/editor.js +194 -0
  17. package/dist/services/editor.js.map +1 -0
  18. package/dist/services/font-manager.d.ts +18 -0
  19. package/dist/services/font-manager.js +59 -0
  20. package/dist/services/font-manager.js.map +1 -0
  21. package/dist/services/layout.d.ts +62 -0
  22. package/dist/services/layout.js +198 -0
  23. package/dist/services/layout.js.map +1 -0
  24. package/dist/services/output.d.ts +13 -0
  25. package/dist/services/output.js +60 -0
  26. package/dist/services/output.js.map +1 -0
  27. package/dist/services/renderers/markdown.d.ts +8 -0
  28. package/dist/services/renderers/markdown.js +132 -0
  29. package/dist/services/renderers/markdown.js.map +1 -0
  30. package/dist/services/renderers/table.d.ts +7 -0
  31. package/dist/services/renderers/table.js +89 -0
  32. package/dist/services/renderers/table.js.map +1 -0
  33. package/dist/services/renderers/text.d.ts +9 -0
  34. package/dist/services/renderers/text.js +29 -0
  35. package/dist/services/renderers/text.js.map +1 -0
  36. package/dist/tools/definitions.d.ts +388 -0
  37. package/dist/tools/definitions.js +241 -0
  38. package/dist/tools/definitions.js.map +1 -0
  39. package/dist/tools/handlers.d.ts +23 -0
  40. package/dist/tools/handlers.js +82 -0
  41. package/dist/tools/handlers.js.map +1 -0
  42. package/dist/types/index.d.ts +135 -0
  43. package/dist/types/index.js +5 -0
  44. package/dist/types/index.js.map +1 -0
  45. package/dist/utils/logger.d.ts +14 -0
  46. package/dist/utils/logger.js +28 -0
  47. package/dist/utils/logger.js.map +1 -0
  48. package/dist/utils/page-spec.d.ts +12 -0
  49. package/dist/utils/page-spec.js +55 -0
  50. package/dist/utils/page-spec.js.map +1 -0
  51. package/dist/utils/validation.d.ts +24 -0
  52. package/dist/utils/validation.js +187 -0
  53. package/dist/utils/validation.js.map +1 -0
  54. package/package.json +48 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 shuji-bonji
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,208 @@
1
+ # pdf-writer-mcp
2
+
3
+ テキスト / Markdown / 表データからの **PDF 生成** と、既存 PDF の **編集**(メタデータ・ページ操作)を行う
4
+ MCP (Model Context Protocol) サーバです。
5
+ [pdf-lib](https://pdf-lib.js.org/) をコアに、日本語フォントの埋め込み(サブセット)に対応します。
6
+
7
+ ## 位置づけ
8
+
9
+ `shuji-bonji` の PDF 系 MCP エコシステムにおける「生成(write)」担当です。
10
+ 読み取り・検証系とは責務を分けています。
11
+
12
+ ```mermaid
13
+ graph LR
14
+ subgraph 生成
15
+ W[pdf-writer-mcp<br/>text/md/table→PDF]
16
+ end
17
+ subgraph 読取
18
+ R[pdf-reader-mcp<br/>抽出・検査]
19
+ end
20
+ subgraph 検証
21
+ V[pdf-verify-mcp<br/>署名・準拠性]
22
+ end
23
+ subgraph 仕様参照
24
+ S[pdf-spec-mcp<br/>ISO 32000]
25
+ end
26
+ W --> R
27
+ R --> V
28
+ S -.仕様根拠.-> W
29
+ S -.仕様根拠.-> V
30
+ ```
31
+
32
+ ## 特徴
33
+
34
+ - **3 つの生成ツール**: プレーンテキスト / Markdown / 表
35
+ - **7 つの編集ツール**: メタデータ更新、結合・分割・抽出・削除・並べ替え・回転
36
+ - **署名ガード**: 電子署名済み PDF(`/ByteRange` 検知)の編集は既定でエラーにし、
37
+ 署名の意図しない無効化を防止(`allowBreakingSignatures: true` で明示的に続行可能)
38
+ - **日本語対応**: `.ttf` / `.otf` フォントを fontkit でサブセット埋め込み(16MB のフォントでも出力は数十 KB)
39
+ - **抽出・検索・コピー可能**: 埋め込みフォントでも ToUnicode CMap が付与されるため、
40
+ 生成した PDF のテキストは選択・コピー・全文検索・スクリーンリーダ読み上げが可能
41
+ - **自動レイアウト**: テキスト折り返し(日英混在・長語の強制分割)、改ページ、表の改ページ時ヘッダ再描画
42
+ - **出力先の柔軟性**: ファイル保存 / base64 返却の両対応
43
+ - **堅牢な入力検査**: `asserts` によるバリデーションで不正値を早期に弾く
44
+
45
+ ## MCP クライアント設定
46
+
47
+ `claude_desktop_config.json`(例、npm 公開版を npx で起動):
48
+
49
+ ```json
50
+ {
51
+ "mcpServers": {
52
+ "pdf-writer": {
53
+ "command": "npx",
54
+ "args": ["-y", "@shuji-bonji/pdf-writer-mcp"],
55
+ "env": {
56
+ "PDF_WRITER_FONT": "/absolute/path/to/NotoSansJP-Regular.otf"
57
+ }
58
+ }
59
+ }
60
+ }
61
+ ```
62
+
63
+ ローカルのソースから起動する場合:
64
+
65
+ ```json
66
+ {
67
+ "mcpServers": {
68
+ "pdf-writer": {
69
+ "command": "node",
70
+ "args": ["/absolute/path/to/pdf-writer-mcp/dist/index.js"],
71
+ "env": {
72
+ "PDF_WRITER_FONT": "/absolute/path/to/NotoSansJP-Regular.otf"
73
+ }
74
+ }
75
+ }
76
+ }
77
+ ```
78
+
79
+ `PDF_WRITER_FONT` を設定しておくと、各ツールで `fontPath` を省略しても日本語が出せます。
80
+
81
+ ## 開発
82
+
83
+ ```bash
84
+ npm install
85
+ npm run build # dist/ に出力
86
+ npm test # vitest
87
+ npm run typecheck # tsc --noEmit
88
+ ```
89
+
90
+ ## フォントについて(重要)
91
+
92
+ - 標準フォント(Helvetica)は **ASCII のみ**。日本語を含む場合は `fontPath` か `PDF_WRITER_FONT` で
93
+ 埋め込み可能な **単一フォント(`.ttf` / `.otf`)** を指定してください。
94
+ - **`.ttc`(TrueTypeCollection)は非対応**です(pdf-lib がサブセット化できないため、検知してエラーにします)。
95
+ `.ttc` しか手元にない場合は、単一フェイスを抽出してください。
96
+
97
+ ```bash
98
+ # 例: Noto Sans CJK の .ttc から日本語フェイス(index 0)を .otf として取り出す
99
+ python3 -c "from fontTools.ttLib import TTCollection; \
100
+ TTCollection('NotoSansCJK-Regular.ttc').fonts[0].save('NotoSansCJKjp-Regular.otf')"
101
+ ```
102
+
103
+ - 日本語フォントの入手先の例: [Noto Sans JP](https://fonts.google.com/noto/specimen/Noto+Sans+JP)(SIL OFL)。
104
+
105
+ ## ツール
106
+
107
+ ### `create_text_pdf`
108
+
109
+ プレーンテキスト → PDF。`\n` で改行、空行で段落区切り。長い行は自動折り返し。
110
+
111
+ | 引数 | 型 | 必須 | 説明 |
112
+ |------|----|:---:|------|
113
+ | `text` | string | ✓ | 本文 |
114
+ | `outputPath` | string | | 保存先。省略時は base64 を返す |
115
+ | `returnBase64` | boolean | | 保存に加えて base64 も返す |
116
+ | `fontPath` | string | | 埋め込むフォント(日本語は必須) |
117
+ | `fontSize` | number | | 本文サイズ pt(既定 11、4〜96) |
118
+ | `pageSize` | enum | | A4/A3/A5/LETTER/LEGAL(既定 A4) |
119
+ | `margin` | number | | 余白 pt(既定 56、0〜300) |
120
+ | `title` | string | | タイトル(メタデータ+冒頭見出し) |
121
+ | `author` | string | | 作成者(メタデータ) |
122
+
123
+ ### `create_markdown_pdf`
124
+
125
+ Markdown → PDF。対応要素: 見出し / 段落 / 箇条書き・番号リスト / コードブロック / 引用 / 水平線 / 表。
126
+ インライン装飾(`**bold**` など)は単一フォントのため **記号を除去して字面のみ** 反映します。
127
+
128
+ 引数は `text` の代わりに `markdown`(string, 必須)。その他は共通。
129
+
130
+ ### `create_table_pdf`
131
+
132
+ ヘッダ + 行データ → 罫線付きの表 PDF。列幅は内容から自動算出、セル内は折り返し、改ページ時はヘッダを再描画。
133
+
134
+ | 引数 | 型 | 必須 | 説明 |
135
+ |------|----|:---:|------|
136
+ | `headers` | string[] | ✓ | 列見出し |
137
+ | `rows` | string[][] | ✓ | データ行(各行は headers と同数の列を推奨) |
138
+
139
+ その他は共通引数。
140
+
141
+ ## 編集ツール(v0.2.0〜)
142
+
143
+ 既存 PDF に対する操作です。共通引数: `outputPath`(省略時は base64 返却)/ `returnBase64` /
144
+ `allowBreakingSignatures`(署名済み PDF の編集続行フラグ、既定 false)。
145
+
146
+ ページ指定は `"1,3-5,8-"` 形式(1 始まり。`-3` = 先頭から 3 まで、`8-` = 8 から最終まで)。
147
+
148
+ | ツール | 主な引数 | 説明 |
149
+ |--------|----------|------|
150
+ | `set_metadata` | `inputPath`, `title` / `author` / `subject` / `keywords` / `creator` | 指定フィールドのみ更新、他は保持 |
151
+ | `merge_pdfs` | `inputPaths: string[]` | 指定順に結合(2〜50 ファイル) |
152
+ | `split_pdf` | `inputPath`, `ranges: string[]`, `outputDir`, `prefix?` | 範囲ごとに連番ファイルへ分割 |
153
+ | `extract_pages` | `inputPath`, `pages` | 指定順を保持して抽出(並べ替えを兼ねる) |
154
+ | `delete_pages` | `inputPath`, `pages` | ページ削除(全削除はエラー) |
155
+ | `reorder_pages` | `inputPath`, `order: number[]` | 全ページの順列で並べ替え |
156
+ | `rotate_pages` | `inputPath`, `rotation: 90\|180\|270`, `pages?` | 時計回りに回転(既存回転に加算) |
157
+
158
+ > **署名について**: pdf-lib はファイル全体を再構築して保存するため、編集すると既存の電子署名は
159
+ > 必ず無効化されます。署名(`/ByteRange`)を検知した場合は既定でエラーとし、
160
+ > `allowBreakingSignatures: true` を指定したときのみ続行します。
161
+ > 署名を保持する増分更新は将来対応(ロードマップ参照)。
162
+
163
+ ### 返り値(共通)
164
+
165
+ ```jsonc
166
+ {
167
+ "path": "/abs/out.pdf", // outputPath 指定時
168
+ "base64": "JVBERi0xLj...", // returnBase64 or outputPath 未指定時
169
+ "pageCount": 3,
170
+ "bytes": 91788,
171
+ "font": "NotoSansJP-Regular.otf"
172
+ }
173
+ ```
174
+
175
+ ## テキスト抽出・検索について
176
+
177
+ 生成される PDF は **テキストの選択・コピー・全文検索が可能** です。
178
+ 埋め込みフォント使用時も pdf-lib が ToUnicode CMap(CID→Unicode の逆引き)を出力するため、
179
+ `pdftotext` 等で本文を抽出できます。この性質は `tests/extract.test.ts` の回帰テストで担保しています
180
+ (ToUnicode に `日 → U+65E5` のマッピングが含まれることを検証)。
181
+
182
+ > 補足: 一部の PDF ビューア(poppler 系)は埋め込み CFF サブセットに対して
183
+ > `Embedded font file may be invalid` という警告を表示することがありますが、
184
+ > 表示・抽出・`qpdf --check` はいずれも正常で、実害はありません。
185
+
186
+ ## 既知の制約
187
+
188
+ - **インライン装飾**: 太字・斜体などはサイズ/字面のみで、書体としては反映されません
189
+ (見出し/本文/太字を別フェイスで埋め分ける対応はロードマップ参照)。
190
+ - **`.ttc` 非対応**: 単一フェイスへの抽出が必要です(上記参照)。
191
+ - **サブセット名の接頭辞**: pdf-lib は慣習的な `ABCDEF+` 接頭辞を付けないため、
192
+ 一部ツールがサブセットでないと誤認します(表示・抽出には無影響。PDF/A 厳密対応時の項目)。
193
+
194
+ ## ロードマップ
195
+
196
+ - [x] 編集系 Tier A 第1波(メタデータ・ページ操作)— v0.2.0
197
+ - [ ] 編集系 Tier A 第2波(しおり・注釈)
198
+ - [ ] 編集系 Tier B(フォーム記入・フラット化 / 透かし / 添付ファイル / ページ番号スタンプ)
199
+ - [ ] タグ付き PDF / PDF/UA 対応(スクリーンリーダ向けの構造タグ。抽出可否とは別レイヤの本格対応)
200
+ - [ ] `.ttc` からのフェイス自動抽出(Node だけで完結)
201
+ - [ ] 見出し用と本文用のフォント分け(太字フェイス埋め込み)
202
+ - [ ] 画像埋め込み / ヘッダー・フッター
203
+ - [ ] 署名を保持する増分更新・本文テキスト編集・タグ木保守(Tier C)
204
+ - [ ] PDF/A 変換(サブセット命名の正規化を含む・外部ツール連携)
205
+
206
+ ## ライセンス
207
+
208
+ MIT © shuji-bonji
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Application Configuration
3
+ * バージョンは package.json から動的取得(config と package.json の不一致を防ぐ)
4
+ */
5
+ /**
6
+ * Package information (dynamically loaded from package.json)
7
+ */
8
+ export declare const PACKAGE_INFO: {
9
+ readonly name: string;
10
+ readonly version: string;
11
+ };
12
+ /**
13
+ * 環境変数キー
14
+ */
15
+ export declare const ENV_KEYS: {
16
+ /** デフォルトで埋め込むフォントファイルのパス(.ttf / .otf) */
17
+ readonly DEFAULT_FONT: 'PDF_WRITER_FONT';
18
+ };
19
+ /**
20
+ * PDF 生成のデフォルト値
21
+ * 単位はすべて pt(1pt = 1/72 inch)
22
+ */
23
+ export declare const DEFAULTS: {
24
+ /** ページサイズ名 */
25
+ readonly pageSize: 'A4';
26
+ /** 上下左右マージン */
27
+ readonly margin: 56;
28
+ /** 本文フォントサイズ */
29
+ readonly fontSize: 11;
30
+ /** 行間係数(fontSize に対する倍率。行送り = fontSize * lineHeight) */
31
+ readonly lineHeight: 1.45;
32
+ /** 段落間の追加余白 */
33
+ readonly paragraphGap: 6;
34
+ };
package/dist/config.js ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Application Configuration
3
+ * バージョンは package.json から動的取得(config と package.json の不一致を防ぐ)
4
+ */
5
+ import { createRequire } from 'module';
6
+ const require = createRequire(import.meta.url);
7
+ const packageJson = require('../package.json');
8
+ /**
9
+ * Package information (dynamically loaded from package.json)
10
+ */
11
+ export const PACKAGE_INFO = {
12
+ name: packageJson.name,
13
+ version: packageJson.version,
14
+ };
15
+ /**
16
+ * 環境変数キー
17
+ */
18
+ export const ENV_KEYS = {
19
+ /** デフォルトで埋め込むフォントファイルのパス(.ttf / .otf) */
20
+ DEFAULT_FONT: 'PDF_WRITER_FONT',
21
+ };
22
+ /**
23
+ * PDF 生成のデフォルト値
24
+ * 単位はすべて pt(1pt = 1/72 inch)
25
+ */
26
+ export const DEFAULTS = {
27
+ /** ページサイズ名 */
28
+ pageSize: 'A4',
29
+ /** 上下左右マージン */
30
+ margin: 56, // ≒ 20mm
31
+ /** 本文フォントサイズ */
32
+ fontSize: 11,
33
+ /** 行間係数(fontSize に対する倍率。行送り = fontSize * lineHeight) */
34
+ lineHeight: 1.45,
35
+ /** 段落間の追加余白 */
36
+ paragraphGap: 6,
37
+ };
38
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAEvC,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,WAAW,GAAG,OAAO,CAAC,iBAAiB,CAG5C,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,IAAI,EAAE,WAAW,CAAC,IAAI;IACtB,OAAO,EAAE,WAAW,CAAC,OAAO;CACpB,CAAC;AAEX;;GAEG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,yCAAyC;IACzC,YAAY,EAAE,iBAAiB;CACvB,CAAC;AAEX;;;GAGG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,cAAc;IACd,QAAQ,EAAE,IAAI;IACd,eAAe;IACf,MAAM,EAAE,EAAE,EAAE,SAAS;IACrB,gBAAgB;IAChB,QAAQ,EAAE,EAAE;IACZ,wDAAwD;IACxD,UAAU,EAAE,IAAI;IAChB,eAAe;IACf,YAAY,EAAE,CAAC;CACP,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Constants
3
+ * マジックナンバー・上限値を一元管理(根拠はコメントで明示)
4
+ */
5
+ /**
6
+ * 標準ページサイズ [width, height](pt, 縦向き)
7
+ * ISO 216 (A系) と US サイズ
8
+ */
9
+ export declare const PAGE_SIZES: {
10
+ readonly A4: readonly [595.28, 841.89];
11
+ readonly A3: readonly [841.89, 1190.55];
12
+ readonly A5: readonly [419.53, 595.28];
13
+ readonly LETTER: readonly [612, 792];
14
+ readonly LEGAL: readonly [612, 1008];
15
+ };
16
+ export type PageSizeName = keyof typeof PAGE_SIZES;
17
+ /**
18
+ * 入力バリデーションの上限値
19
+ */
20
+ export declare const LIMITS: {
21
+ /** フォントサイズの範囲(pt) */
22
+ readonly FONT_SIZE_MIN: 4;
23
+ readonly FONT_SIZE_MAX: 96;
24
+ /** マージンの範囲(pt)。0 は全面利用、上限はページの暴走防止 */
25
+ readonly MARGIN_MIN: 0;
26
+ readonly MARGIN_MAX: 300;
27
+ /** 1 回の生成で受け付ける本文テキストの最大長(文字)。DoS/巨大PDF防止 */
28
+ readonly TEXT_MAX_LENGTH: 500000;
29
+ /** 表の最大列数・行数。レイアウト破綻と巨大化の防止 */
30
+ readonly TABLE_MAX_COLS: 40;
31
+ readonly TABLE_MAX_ROWS: 5000;
32
+ /** merge の最大入力ファイル数。暴走防止 */
33
+ readonly MERGE_MAX_INPUTS: 50;
34
+ /** split の最大分割数。暴走防止 */
35
+ readonly SPLIT_MAX_PARTS: 200;
36
+ };
37
+ /** rotate_pages が受け付ける回転角(時計回り・度) */
38
+ export declare const ROTATION_ANGLES: readonly [90, 180, 270];
39
+ /**
40
+ * フォントファイルのマジックナンバー(先頭 4 bytes)
41
+ * .ttc(TrueTypeCollection)は pdf-lib がサブセット化できないため検知して弾く
42
+ */
43
+ export declare const FONT_MAGIC: {
44
+ /** 'ttcf' — TrueType Collection */
45
+ readonly TTC: 'ttcf';
46
+ };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Constants
3
+ * マジックナンバー・上限値を一元管理(根拠はコメントで明示)
4
+ */
5
+ /**
6
+ * 標準ページサイズ [width, height](pt, 縦向き)
7
+ * ISO 216 (A系) と US サイズ
8
+ */
9
+ export const PAGE_SIZES = {
10
+ A4: [595.28, 841.89],
11
+ A3: [841.89, 1190.55],
12
+ A5: [419.53, 595.28],
13
+ LETTER: [612, 792],
14
+ LEGAL: [612, 1008],
15
+ };
16
+ /**
17
+ * 入力バリデーションの上限値
18
+ */
19
+ export const LIMITS = {
20
+ /** フォントサイズの範囲(pt) */
21
+ FONT_SIZE_MIN: 4,
22
+ FONT_SIZE_MAX: 96,
23
+ /** マージンの範囲(pt)。0 は全面利用、上限はページの暴走防止 */
24
+ MARGIN_MIN: 0,
25
+ MARGIN_MAX: 300,
26
+ /** 1 回の生成で受け付ける本文テキストの最大長(文字)。DoS/巨大PDF防止 */
27
+ TEXT_MAX_LENGTH: 500_000,
28
+ /** 表の最大列数・行数。レイアウト破綻と巨大化の防止 */
29
+ TABLE_MAX_COLS: 40,
30
+ TABLE_MAX_ROWS: 5_000,
31
+ /** merge の最大入力ファイル数。暴走防止 */
32
+ MERGE_MAX_INPUTS: 50,
33
+ /** split の最大分割数。暴走防止 */
34
+ SPLIT_MAX_PARTS: 200,
35
+ };
36
+ /** rotate_pages が受け付ける回転角(時計回り・度) */
37
+ export const ROTATION_ANGLES = [90, 180, 270];
38
+ /**
39
+ * フォントファイルのマジックナンバー(先頭 4 bytes)
40
+ * .ttc(TrueTypeCollection)は pdf-lib がサブセット化できないため検知して弾く
41
+ */
42
+ export const FONT_MAGIC = {
43
+ /** 'ttcf' — TrueType Collection */
44
+ TTC: 'ttcf',
45
+ };
46
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;IACrB,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC;IAClB,KAAK,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC;CACV,CAAC;AAIX;;GAEG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,qBAAqB;IACrB,aAAa,EAAE,CAAC;IAChB,aAAa,EAAE,EAAE;IACjB,sCAAsC;IACtC,UAAU,EAAE,CAAC;IACb,UAAU,EAAE,GAAG;IACf,6CAA6C;IAC7C,eAAe,EAAE,OAAO;IACxB,+BAA+B;IAC/B,cAAc,EAAE,EAAE;IAClB,cAAc,EAAE,KAAK;IACrB,4BAA4B;IAC5B,gBAAgB,EAAE,EAAE;IACpB,wBAAwB;IACxB,eAAe,EAAE,GAAG;CACZ,CAAC;AAEX,qCAAqC;AACrC,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,CAAU,CAAC;AAEvD;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,mCAAmC;IACnC,GAAG,EAAE,MAAM;CACH,CAAC"}
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * pdf-writer-mcp — MCP server entry
4
+ * テキスト / Markdown / 表データから PDF を生成する。
5
+ */
6
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * pdf-writer-mcp — MCP server entry
4
+ * テキスト / Markdown / 表データから PDF を生成する。
5
+ */
6
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
7
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
8
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
9
+ import { PACKAGE_INFO } from './config.js';
10
+ import { tools } from './tools/definitions.js';
11
+ import { toolHandlers } from './tools/handlers.js';
12
+ import { logger } from './utils/logger.js';
13
+ const server = new Server({
14
+ name: PACKAGE_INFO.name,
15
+ version: PACKAGE_INFO.version,
16
+ }, {
17
+ capabilities: { tools: {} },
18
+ });
19
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
20
+ tools,
21
+ }));
22
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
23
+ const { name, arguments: args } = request.params;
24
+ try {
25
+ const handler = toolHandlers[name];
26
+ if (!handler) {
27
+ throw new Error(`Unknown tool: ${name}`);
28
+ }
29
+ const result = await handler(args);
30
+ return {
31
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
32
+ };
33
+ }
34
+ catch (error) {
35
+ const message = error instanceof Error ? error.message : String(error);
36
+ logger.error(name, message, error instanceof Error ? error : undefined);
37
+ return {
38
+ content: [{ type: 'text', text: JSON.stringify({ error: message }, null, 2) }],
39
+ isError: true,
40
+ };
41
+ }
42
+ });
43
+ async function main() {
44
+ const transport = new StdioServerTransport();
45
+ await server.connect(transport);
46
+ logger.info(PACKAGE_INFO.name, `v${PACKAGE_INFO.version} started (stdio)`);
47
+ }
48
+ main().catch((error) => {
49
+ logger.error('Startup', 'Failed to start server', error instanceof Error ? error : undefined);
50
+ process.exit(1);
51
+ });
52
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE3C,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB;IACE,IAAI,EAAE,YAAY,CAAC,IAAI;IACvB,OAAO,EAAE,YAAY,CAAC,OAAO;CAC9B,EACD;IACE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;CAC5B,CACF,CAAC;AAEF,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;IAC5D,KAAK;CACN,CAAC,CAAC,CAAC;AAEJ,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;IAChE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAEjD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;QAC3C,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;QACnC,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;SACnE,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACxE,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;YAC9E,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,IAAI,YAAY,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC7E,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,wBAAwB,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC9F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Builder
3
+ * 「doc 生成 → フォント埋め込み → レイアウトエンジン → 描画 → 保存」の共通フロー。
4
+ * 各ツールハンドラは render コールバックだけ差し替えて使う。
5
+ */
6
+ import type { CommonCreateOptions, CreateResult } from '../types/index.js';
7
+ import { type LoadedFont } from './font-manager.js';
8
+ import { LayoutEngine } from './layout.js';
9
+ export type RenderFn = (engine: LayoutEngine, loaded: LoadedFont) => void;
10
+ export declare function buildPdf(opts: CommonCreateOptions, render: RenderFn): Promise<CreateResult>;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Builder
3
+ * 「doc 生成 → フォント埋め込み → レイアウトエンジン → 描画 → 保存」の共通フロー。
4
+ * 各ツールハンドラは render コールバックだけ差し替えて使う。
5
+ */
6
+ import { PDFDocument, rgb } from 'pdf-lib';
7
+ import { PAGE_SIZES } from '../constants.js';
8
+ import { DEFAULTS } from '../config.js';
9
+ import { loadFont } from './font-manager.js';
10
+ import { LayoutEngine } from './layout.js';
11
+ import { finalizePdf } from './output.js';
12
+ import { assertRenderable } from './renderers/text.js';
13
+ export async function buildPdf(opts, render) {
14
+ const doc = await PDFDocument.create();
15
+ const loaded = await loadFont(doc, opts.fontPath);
16
+ const pageName = opts.pageSize ?? DEFAULTS.pageSize;
17
+ const [pageWidth, pageHeight] = PAGE_SIZES[pageName];
18
+ const fontSize = opts.fontSize ?? DEFAULTS.fontSize;
19
+ const engine = new LayoutEngine(doc, {
20
+ pageWidth,
21
+ pageHeight,
22
+ margin: opts.margin ?? DEFAULTS.margin,
23
+ font: loaded.font,
24
+ fontSize,
25
+ lineHeight: DEFAULTS.lineHeight,
26
+ });
27
+ // タイトルが指定されていれば本文冒頭に見出しとして描画(メタデータにも finalizePdf で設定)
28
+ if (opts.title) {
29
+ assertRenderable(opts.title, loaded);
30
+ engine.drawParagraph(opts.title, {
31
+ size: fontSize + 7,
32
+ color: rgb(0, 0, 0),
33
+ lineHeight: 1.2,
34
+ spaceAfter: 12,
35
+ });
36
+ }
37
+ render(engine, loaded);
38
+ return finalizePdf(doc, opts, loaded.name);
39
+ }
40
+ //# sourceMappingURL=builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builder.js","sourceRoot":"","sources":["../../src/services/builder.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAqB,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,OAAO,EAAE,QAAQ,EAAmB,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAIvD,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,IAAyB,EACzB,MAAgB;IAEhB,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,MAAM,EAAE,CAAC;IACvC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IAElD,MAAM,QAAQ,GAAiB,IAAI,CAAC,QAAQ,IAAK,QAAQ,CAAC,QAAyB,CAAC;IACpF,MAAM,CAAC,SAAS,EAAE,UAAU,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC;IAEpD,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,GAAG,EAAE;QACnC,SAAS;QACT,UAAU;QACV,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM;QACtC,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,QAAQ;QACR,UAAU,EAAE,QAAQ,CAAC,UAAU;KAChC,CAAC,CAAC;IAEH,sDAAsD;IACtD,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,gBAAgB,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACrC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,EAAE;YAC/B,IAAI,EAAE,QAAQ,GAAG,CAAC;YAClB,KAAK,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YACnB,UAAU,EAAE,GAAG;YACf,UAAU,EAAE,EAAE;SACf,CAAC,CAAC;IACL,CAAC;IAED,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEvB,OAAO,WAAW,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Editor
3
+ * 既存 PDF の編集(Tier A: メタデータ・ページ操作)を担う。
4
+ *
5
+ * create 系(builder → font → layout → renderer)とはフローが異なり、
6
+ * 読込(loadForEdit: 署名ガード込み)→ 操作 → 保存(saveEdited)
7
+ * の 3 段で完結する。
8
+ *
9
+ * 署名保全について(specs/05 §3-1):
10
+ * pdf-lib の save() はファイル全体を再構築するため、既存の電子署名は必ず無効化される。
11
+ * 署名(/ByteRange)を検知した場合は既定でエラーとし、明示フラグ
12
+ * allowBreakingSignatures: true があるときのみ続行する。
13
+ * 署名を保持する増分更新(incremental_save)は Tier C の課題。
14
+ */
15
+ import type { CommonEditOptions, EditResult, SetMetadataArgs, SplitResult } from '../types/index.js';
16
+ /** 入力バイト列に電子署名(/ByteRange)が含まれるかの軽量検査 */
17
+ export declare function containsSignature(bytes: Uint8Array): boolean;
18
+ export declare function setMetadata(args: SetMetadataArgs): Promise<EditResult>;
19
+ export declare function mergePdfs(inputPaths: string[], opts: CommonEditOptions): Promise<EditResult>;
20
+ export declare function extractPages(inputPath: string, pages: string, opts: CommonEditOptions): Promise<EditResult>;
21
+ export declare function deletePages(inputPath: string, pages: string, opts: CommonEditOptions): Promise<EditResult>;
22
+ export declare function reorderPages(inputPath: string, order: number[], opts: CommonEditOptions): Promise<EditResult>;
23
+ export declare function rotatePages(inputPath: string, rotation: number, pages: string | undefined, opts: CommonEditOptions): Promise<EditResult>;
24
+ export declare function splitPdf(inputPath: string, ranges: string[], outputDir: string, prefix: string | undefined, opts: CommonEditOptions): Promise<SplitResult>;