friday-mcp-v2 3.1.2 → 3.1.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.
@@ -414,10 +414,12 @@ export class FridayWPClient {
414
414
  * @param {Buffer} fileBuffer 画像バイナリ
415
415
  * @param {string} filename WordPress 上のファイル名(拡張子付き)
416
416
  * @param {string} contentType MIME type (e.g. image/jpeg)
417
+ * @param {{timeoutMs?: number}} options timeout options
417
418
  * @returns {object} WP media オブジェクト
418
419
  */
419
- async uploadMedia(fileBuffer, filename, contentType) {
420
+ async uploadMedia(fileBuffer, filename, contentType, { timeoutMs } = {}) {
420
421
  const url = `${this.wpUrl.replace(/\/$/, '')}/wp-json/wp/v2/media`;
422
+ const signal = timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined;
421
423
  const res = await fetch(url, {
422
424
  method: 'POST',
423
425
  headers: {
@@ -425,6 +427,7 @@ export class FridayWPClient {
425
427
  'Content-Disposition': `attachment; filename="${encodeURIComponent(filename)}"`,
426
428
  'Content-Type': contentType,
427
429
  },
430
+ signal,
428
431
  body: fileBuffer,
429
432
  });
430
433
  const text = await res.text();
@@ -455,6 +458,42 @@ export class FridayWPClient {
455
458
  });
456
459
  }
457
460
 
461
+ /**
462
+ * メディア単体を取得
463
+ * @param {number} mediaId メディアID
464
+ * @returns {object} WP media オブジェクト
465
+ */
466
+ async getMedia(mediaId) {
467
+ return this.wpCoreRequest(`/wp/v2/media/${mediaId}`);
468
+ }
469
+
470
+ /**
471
+ * メディアを ID 配列で取得
472
+ * 100件超は 100件チャンクに分割して取得しマージする (Phase 2 列挙パス)。
473
+ * @param {number[]|number} mediaIds メディアID配列
474
+ * @param {object} [options]
475
+ * @param {string} [options.mediaType] 'image' 等の media_type フィルタ。未指定なら全タイプ
476
+ * (move 検証など既存呼び出しは未指定で全タイプ検証を維持する)
477
+ * @returns {object[]} WP media オブジェクト配列
478
+ */
479
+ async getMediaByIds(mediaIds, { mediaType } = {}) {
480
+ const ids = Array.isArray(mediaIds) ? mediaIds : [mediaIds];
481
+ if (ids.length === 0) return [];
482
+ const CHUNK = 100;
483
+ const out = [];
484
+ for (let i = 0; i < ids.length; i += CHUNK) {
485
+ const chunk = ids.slice(i, i + CHUNK);
486
+ const params = new URLSearchParams({
487
+ include: chunk.join(','),
488
+ per_page: String(chunk.length),
489
+ });
490
+ if (mediaType) params.set('media_type', mediaType);
491
+ const res = await this.wpCoreRequest(`/wp/v2/media?${params.toString()}`);
492
+ if (Array.isArray(res)) out.push(...res);
493
+ }
494
+ return out;
495
+ }
496
+
458
497
  /**
459
498
  * メディアライブラリを検索(画像のみ)
460
499
  * @param {string} query 検索キーワード
@@ -462,13 +501,20 @@ export class FridayWPClient {
462
501
  * @returns {object[]} メディアオブジェクト配列
463
502
  */
464
503
  async searchMedia(query, perPage = 10, includeIds = null) {
504
+ const hasInclude = Array.isArray(includeIds) && includeIds.length > 0;
505
+ // include 指定(スコープ検索)時のみ最大100。グローバル検索は 1-20 契約を維持 (#9)
506
+ const cap = hasInclude ? 100 : 20;
465
507
  const params = new URLSearchParams({
466
508
  media_type: 'image',
467
- per_page: String(Math.min(Math.max(1, perPage), 20)),
509
+ per_page: String(Math.min(Math.max(1, perPage), cap)),
468
510
  });
469
511
  if (query) params.set('search', query);
470
- if (includeIds && includeIds.length > 0) {
471
- params.set('include', includeIds.slice(0, 100).join(','));
512
+ if (hasInclude) {
513
+ // 呼び出し側が 100件チャンクに分割する前提。違反は黙って切らず明示エラー (#10)
514
+ if (includeIds.length > 100) {
515
+ throw new Error(`searchMedia の includeIds は100件以下にチャンクして渡してください (受領: ${includeIds.length}件)`);
516
+ }
517
+ params.set('include', includeIds.join(','));
472
518
  }
473
519
  return this.wpCoreRequest(`/wp/v2/media?${params.toString()}`);
474
520
  }
@@ -491,6 +537,115 @@ export class FridayWPClient {
491
537
  return json.success ? json.data.attachment_ids : null;
492
538
  }
493
539
 
540
+ /**
541
+ * Uncategorized 等の件数取得
542
+ * @param {number} folderId 0=Uncategorized, -1=全総数, >0=通常フォルダ
543
+ * @returns {number|null} 件数。token 未設定/失敗時は null
544
+ */
545
+ async getFileBirdAttachmentCount(folderId) {
546
+ if (!this.filebirdToken) return null;
547
+ const url = `${this.wpUrl}/wp-json/filebird/public/v1/attachment-count/?folder_id=${folderId}&token=${this.filebirdToken}`;
548
+ const res = await fetch(url);
549
+ if (!res.ok) return null;
550
+ const json = await res.json();
551
+ return json.success ? json.data.count : null;
552
+ }
553
+
554
+ /**
555
+ * FileBird フォルダ作成
556
+ * 注意: 親存在検証なし・同名は "name (1)" 自動リネーム (プラグイン Api.php:103 / Folder.php:453)
557
+ * @param {string} name フォルダ名
558
+ * @param {number} parentId 親 folder id (0=ルート)
559
+ * @returns {object|null} { id } 。token 未設定時は null。失敗時は throw
560
+ */
561
+ async createFileBirdFolder(name, parentId = 0) {
562
+ if (!this.filebirdToken) return null;
563
+ const url = `${this.wpUrl}/wp-json/filebird/public/v1/folders?token=${this.filebirdToken}`;
564
+ const res = await fetch(url, {
565
+ method: 'POST',
566
+ headers: { 'Content-Type': 'application/json' },
567
+ body: JSON.stringify({ name, parent_id: parentId }),
568
+ });
569
+ if (!res.ok) {
570
+ const text = await res.text();
571
+ const err = new Error(`FileBird createFolder failed (HTTP ${res.status}): ${text.slice(0, 300)}`);
572
+ err.statusCode = res.status;
573
+ throw err;
574
+ }
575
+ const json = await res.json();
576
+ return json.success ? json.data : null;
577
+ }
578
+
579
+ /**
580
+ * 添付メディアをフォルダに割り当て
581
+ * 注意: folder は数値 id のみ (パス不可)・ID 存在検証なし・folder=-1 も通過 (プラグイン Api.php:117)
582
+ * @param {number[]|number} ids 添付 ID (配列または単一)
583
+ * @param {number} folderId 割当先 folder id
584
+ * @returns {boolean|null} 成否。token 未設定時は null。失敗時は throw
585
+ */
586
+ async setFileBirdAttachmentFolder(ids, folderId) {
587
+ if (!this.filebirdToken) return null;
588
+ const url = `${this.wpUrl}/wp-json/filebird/public/v1/folder/set-attachment?token=${this.filebirdToken}`;
589
+ const res = await fetch(url, {
590
+ method: 'POST',
591
+ headers: { 'Content-Type': 'application/json' },
592
+ body: JSON.stringify({ ids, folder: folderId }),
593
+ });
594
+ if (!res.ok) {
595
+ const text = await res.text();
596
+ const err = new Error(`FileBird setAttachment failed (HTTP ${res.status}): ${text.slice(0, 300)}`);
597
+ err.statusCode = res.status;
598
+ throw err;
599
+ }
600
+ const json = await res.json();
601
+ return json.success === true;
602
+ }
603
+
604
+ // ========================================
605
+ // FileBird フォルダ rename / delete (Phase 3)
606
+ // admin namespace filebird/v1 を App Password (wpCoreRequest) で叩く。
607
+ // public token は使わない (認証主体を実行ユーザーに統一)。
608
+ // ========================================
609
+
610
+ /**
611
+ * FileBird フォルダツリー取得 (admin namespace)
612
+ * public getFileBirdFolders と別経路。tree はノードに data-count を持たず、
613
+ * 件数は attachmentsCount で別途返る (呼び出し側で id マージする)。
614
+ * @returns {{ tree: object[], allAttachmentsCount: number, attachmentsCount: object }}
615
+ */
616
+ async getFileBirdFoldersAdmin() {
617
+ return this.wpCoreRequest('/filebird/v1/get-folders');
618
+ }
619
+
620
+ /**
621
+ * FileBird フォルダ名変更 (admin namespace)
622
+ * 注意: parent は移動用ではなく重複チェック用。現在の parentId をそのまま渡す。
623
+ * @param {number} folderId 対象フォルダ id
624
+ * @param {number} currentParentId 現在の親 id (重複チェック用)
625
+ * @param {string} newName 新フォルダ名
626
+ * @returns {boolean} 成功時 true。同名時は WP_Error('folder_name_exist') が throw される
627
+ */
628
+ async renameFileBirdFolder(folderId, currentParentId, newName) {
629
+ return this.wpCoreRequest('/filebird/v1/update-folder', {
630
+ method: 'POST',
631
+ body: JSON.stringify({ id: folderId, parent: currentParentId, title: newName }),
632
+ });
633
+ }
634
+
635
+ /**
636
+ * FileBird フォルダ削除 (admin namespace)
637
+ * FileBird 側で子フォルダへ再帰カスケード削除する (ids は単一で渡す)。
638
+ * 添付ファイル本体は削除されず Uncategorized に残る (fbv / fbv_attachment_folder のみ削除)。
639
+ * @param {number} folderId 対象フォルダ id
640
+ * @returns {object} countAttachments 構造 (削除後の件数)
641
+ */
642
+ async deleteFileBirdFolder(folderId) {
643
+ return this.wpCoreRequest('/filebird/v1/delete-folder', {
644
+ method: 'POST',
645
+ body: JSON.stringify({ ids: [folderId] }),
646
+ });
647
+ }
648
+
494
649
  // ========================================
495
650
  // ASP Link Management
496
651
  // ========================================
@@ -512,6 +667,28 @@ export class FridayWPClient {
512
667
  });
513
668
  }
514
669
 
670
+ async updateAspLink(id, data) {
671
+ return this.request(`/asp-links/${id}`, {
672
+ method: 'PUT',
673
+ body: JSON.stringify(data),
674
+ });
675
+ }
676
+
677
+ async bulkCreateAspLinks(items) {
678
+ return this.request('/asp-links/bulk', {
679
+ method: 'POST',
680
+ body: JSON.stringify({ items }),
681
+ });
682
+ }
683
+
684
+ async resolveAspLinksBulk(aspIds) {
685
+ const list = Array.isArray(aspIds) ? aspIds : [];
686
+ if (list.length === 0) return { items: {} };
687
+ const query = new URLSearchParams();
688
+ query.set('asp_ids', list.join(','));
689
+ return this.request(`/asp-links/bulk?${query.toString()}`);
690
+ }
691
+
515
692
  /**
516
693
  * 設定オブジェクトからインスタンスを生成(ConnectionRegistry 用)
517
694
  * @param {{ url: string, user: string, pass: string }} config
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "friday-mcp-v2",
3
- "version": "3.1.2",
3
+ "version": "3.1.3",
4
4
  "description": "WordPress MCP Server for Claude Code - REST API direct communication",
5
5
  "type": "module",
6
6
  "main": "dist/mcp-server.js",
@@ -21,11 +21,14 @@
21
21
  ],
22
22
  "author": "",
23
23
  "license": "MIT",
24
+ "engines": {
25
+ "node": ">=20"
26
+ },
24
27
  "dependencies": {
25
- "@modelcontextprotocol/sdk": "^1.0.0",
26
- "marked": "^15.0.0",
28
+ "@modelcontextprotocol/sdk": "^1.29.0",
29
+ "marked": "^18.0.3",
27
30
  "node-fetch": "^3.3.2",
28
31
  "node-html-parser": "^7.1.0",
29
- "ws": "^8.19.0"
32
+ "ws": "^8.20.0"
30
33
  }
31
34
  }