friday-mcp-v2 3.0.6 → 3.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/dist/mcp-server.js +967 -34
- package/dist/wordpress-api.js +129 -0
- package/package.json +2 -1
package/dist/mcp-server.js
CHANGED
|
@@ -14,7 +14,8 @@ import fetch from "node-fetch";
|
|
|
14
14
|
import { randomUUID } from "node:crypto";
|
|
15
15
|
import { readFileSync, statSync, realpathSync, appendFileSync, mkdirSync, existsSync, writeFileSync, renameSync } from "node:fs";
|
|
16
16
|
import { execFile } from "node:child_process";
|
|
17
|
-
import { resolve as resolvePath, sep, dirname, join as joinPath } from "node:path";
|
|
17
|
+
import { resolve as resolvePath, sep, dirname, join as joinPath, extname } from "node:path";
|
|
18
|
+
import { marked } from "marked";
|
|
18
19
|
import os from "node:os";
|
|
19
20
|
import { fileURLToPath } from "node:url";
|
|
20
21
|
// package.json からバージョンを取得
|
|
@@ -456,6 +457,159 @@ function readHTMLFromFile(filePath, maxSizeBytes = 2 * 1024 * 1024) {
|
|
|
456
457
|
return { html };
|
|
457
458
|
}
|
|
458
459
|
|
|
460
|
+
// ========================================
|
|
461
|
+
// Media Utilities
|
|
462
|
+
// ========================================
|
|
463
|
+
|
|
464
|
+
const ALLOWED_IMAGE_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.avif']);
|
|
465
|
+
const MAX_IMAGE_SIZE_BYTES = 10 * 1024 * 1024; // 10MB
|
|
466
|
+
|
|
467
|
+
const MIME_MAP = {
|
|
468
|
+
'.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png',
|
|
469
|
+
'.gif': 'image/gif', '.webp': 'image/webp', '.svg': 'image/svg+xml', '.avif': 'image/avif',
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
function validateImageFile(filePath) {
|
|
473
|
+
const resolved = resolvePath(filePath);
|
|
474
|
+
let stat;
|
|
475
|
+
try { stat = statSync(resolved); }
|
|
476
|
+
catch (e) {
|
|
477
|
+
if (e.code === 'ENOENT') throw new Error(`File not found: ${resolved}`);
|
|
478
|
+
throw new Error(`File access error: ${resolved} (${e.message})`);
|
|
479
|
+
}
|
|
480
|
+
if (stat.size > MAX_IMAGE_SIZE_BYTES) {
|
|
481
|
+
throw new Error(`File too large: ${(stat.size / 1048576).toFixed(1)}MB (max: 10MB)`);
|
|
482
|
+
}
|
|
483
|
+
const ext = resolved.slice(resolved.lastIndexOf('.')).toLowerCase();
|
|
484
|
+
if (!ALLOWED_IMAGE_EXTENSIONS.has(ext)) {
|
|
485
|
+
throw new Error(`Unsupported image format: ${ext} (allowed: ${[...ALLOWED_IMAGE_EXTENSIONS].join(', ')})`);
|
|
486
|
+
}
|
|
487
|
+
const contentType = MIME_MAP[ext] || 'application/octet-stream';
|
|
488
|
+
return { resolved, stat, ext, contentType };
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// ========================================
|
|
492
|
+
// Search Variant Generation (ひらがな⇔カタカナ⇔ローマ字)
|
|
493
|
+
// ========================================
|
|
494
|
+
|
|
495
|
+
const ROMAJI_MAP = {
|
|
496
|
+
'キャ': 'kya', 'キュ': 'kyu', 'キョ': 'kyo',
|
|
497
|
+
'シャ': 'sha', 'シュ': 'shu', 'ショ': 'sho',
|
|
498
|
+
'チャ': 'cha', 'チュ': 'chu', 'チョ': 'cho',
|
|
499
|
+
'ニャ': 'nya', 'ニュ': 'nyu', 'ニョ': 'nyo',
|
|
500
|
+
'ヒャ': 'hya', 'ヒュ': 'hyu', 'ヒョ': 'hyo',
|
|
501
|
+
'ミャ': 'mya', 'ミュ': 'myu', 'ミョ': 'myo',
|
|
502
|
+
'リャ': 'rya', 'リュ': 'ryu', 'リョ': 'ryo',
|
|
503
|
+
'ギャ': 'gya', 'ギュ': 'gyu', 'ギョ': 'gyo',
|
|
504
|
+
'ジャ': 'ja', 'ジュ': 'ju', 'ジョ': 'jo',
|
|
505
|
+
'ビャ': 'bya', 'ビュ': 'byu', 'ビョ': 'byo',
|
|
506
|
+
'ピャ': 'pya', 'ピュ': 'pyu', 'ピョ': 'pyo',
|
|
507
|
+
'ティ': 'ti', 'ディ': 'di', 'ファ': 'fa', 'フィ': 'fi', 'フェ': 'fe', 'フォ': 'fo',
|
|
508
|
+
'ア': 'a', 'イ': 'i', 'ウ': 'u', 'エ': 'e', 'オ': 'o',
|
|
509
|
+
'カ': 'ka', 'キ': 'ki', 'ク': 'ku', 'ケ': 'ke', 'コ': 'ko',
|
|
510
|
+
'サ': 'sa', 'シ': 'shi', 'ス': 'su', 'セ': 'se', 'ソ': 'so',
|
|
511
|
+
'タ': 'ta', 'チ': 'chi', 'ツ': 'tsu', 'テ': 'te', 'ト': 'to',
|
|
512
|
+
'ナ': 'na', 'ニ': 'ni', 'ヌ': 'nu', 'ネ': 'ne', 'ノ': 'no',
|
|
513
|
+
'ハ': 'ha', 'ヒ': 'hi', 'フ': 'fu', 'ヘ': 'he', 'ホ': 'ho',
|
|
514
|
+
'マ': 'ma', 'ミ': 'mi', 'ム': 'mu', 'メ': 'me', 'モ': 'mo',
|
|
515
|
+
'ヤ': 'ya', 'ユ': 'yu', 'ヨ': 'yo',
|
|
516
|
+
'ラ': 'ra', 'リ': 'ri', 'ル': 'ru', 'レ': 're', 'ロ': 'ro',
|
|
517
|
+
'ワ': 'wa', 'ヲ': 'wo', 'ン': 'n',
|
|
518
|
+
'ガ': 'ga', 'ギ': 'gi', 'グ': 'gu', 'ゲ': 'ge', 'ゴ': 'go',
|
|
519
|
+
'ザ': 'za', 'ジ': 'ji', 'ズ': 'zu', 'ゼ': 'ze', 'ゾ': 'zo',
|
|
520
|
+
'ダ': 'da', 'ヂ': 'di', 'ヅ': 'du', 'デ': 'de', 'ド': 'do',
|
|
521
|
+
'バ': 'ba', 'ビ': 'bi', 'ブ': 'bu', 'ベ': 'be', 'ボ': 'bo',
|
|
522
|
+
'パ': 'pa', 'ピ': 'pi', 'プ': 'pu', 'ペ': 'pe', 'ポ': 'po',
|
|
523
|
+
'ァ': 'a', 'ィ': 'i', 'ゥ': 'u', 'ェ': 'e', 'ォ': 'o',
|
|
524
|
+
'ヴ': 'v', 'ー': '', 'ッ': '',
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
function hiraToKata(text) {
|
|
528
|
+
return text.replace(/[ぁ-ゖ]/g, c => String.fromCharCode(c.charCodeAt(0) + 96));
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function kataToHira(text) {
|
|
532
|
+
return text.replace(/[ァ-ヶ]/g, c => String.fromCharCode(c.charCodeAt(0) - 96));
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function kataToRomaji(text) {
|
|
536
|
+
const result = [];
|
|
537
|
+
let i = 0;
|
|
538
|
+
while (i < text.length) {
|
|
539
|
+
if (i + 1 < text.length && ROMAJI_MAP[text.slice(i, i + 2)]) {
|
|
540
|
+
result.push(ROMAJI_MAP[text.slice(i, i + 2)]);
|
|
541
|
+
i += 2;
|
|
542
|
+
} else if (text[i] === 'ッ' && i + 1 < text.length) {
|
|
543
|
+
// 促音: 次の子音を重ねる
|
|
544
|
+
const next2 = (i + 2 < text.length) ? ROMAJI_MAP[text.slice(i + 1, i + 3)] : null;
|
|
545
|
+
const next1 = ROMAJI_MAP[text[i + 1]];
|
|
546
|
+
const nextRoma = next2 || next1;
|
|
547
|
+
if (nextRoma && nextRoma.length > 0) result.push(nextRoma[0]);
|
|
548
|
+
i += 1;
|
|
549
|
+
} else if (ROMAJI_MAP[text[i]] !== undefined) {
|
|
550
|
+
result.push(ROMAJI_MAP[text[i]]);
|
|
551
|
+
i += 1;
|
|
552
|
+
} else {
|
|
553
|
+
result.push(text[i]);
|
|
554
|
+
i += 1;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return result.join('');
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function toRomaji(text) {
|
|
561
|
+
const kata = hiraToKata(text);
|
|
562
|
+
const romaji = kataToRomaji(kata);
|
|
563
|
+
if (/[一-鿿]/.test(romaji)) return '';
|
|
564
|
+
return romaji;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function generateSearchVariants(query, strip = [], prefixes = []) {
|
|
568
|
+
const variants = new Set();
|
|
569
|
+
let name = query;
|
|
570
|
+
variants.add(name);
|
|
571
|
+
|
|
572
|
+
if (strip && strip.length > 0) {
|
|
573
|
+
for (const suffix of strip) {
|
|
574
|
+
if (name.endsWith(suffix)) {
|
|
575
|
+
name = name.slice(0, -suffix.length);
|
|
576
|
+
variants.add(name);
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const bracketMatch = name.match(/[((]([^))]+)[))]/);
|
|
583
|
+
if (bracketMatch) {
|
|
584
|
+
const reading = bracketMatch[1];
|
|
585
|
+
const base = name.replace(/[((][^))]+[))]/, '').trim();
|
|
586
|
+
variants.add(base);
|
|
587
|
+
variants.add(reading);
|
|
588
|
+
variants.add(hiraToKata(reading));
|
|
589
|
+
variants.add(kataToHira(reading));
|
|
590
|
+
const romaji = toRomaji(reading);
|
|
591
|
+
if (romaji) variants.add(romaji);
|
|
592
|
+
name = base;
|
|
593
|
+
} else {
|
|
594
|
+
variants.add(hiraToKata(name));
|
|
595
|
+
variants.add(kataToHira(name));
|
|
596
|
+
const romaji = toRomaji(name);
|
|
597
|
+
if (romaji) variants.add(romaji);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (prefixes && prefixes.length > 0) {
|
|
601
|
+
for (const prefix of prefixes) {
|
|
602
|
+
variants.add(`${prefix}${name}`);
|
|
603
|
+
if (bracketMatch) {
|
|
604
|
+
variants.add(`${prefix}${bracketMatch[1]}`);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
variants.delete('');
|
|
610
|
+
return [...variants];
|
|
611
|
+
}
|
|
612
|
+
|
|
459
613
|
/**
|
|
460
614
|
* 単一クライアントの Editor/Headless 判定
|
|
461
615
|
* @returns {{ mode: 'editor'|'headless'|'error', postId?: number, message?: string, client?: FridayWPClient }}
|
|
@@ -679,10 +833,15 @@ async function resolvePostId(rawPostId, client, { skipConflictCheck = false } =
|
|
|
679
833
|
}
|
|
680
834
|
if (!slug) return { error: '❌ URL からスラッグを抽出できませんでした。' };
|
|
681
835
|
|
|
682
|
-
// slug → ID
|
|
683
|
-
const
|
|
836
|
+
// slug → ID 解決(投稿 + 固定ページを統合検索)
|
|
837
|
+
const searchResult = await client.searchPosts({ slug, status: 'any', post_type: 'any' });
|
|
838
|
+
const posts = searchResult.items || [];
|
|
684
839
|
if (!posts || posts.length === 0) {
|
|
685
|
-
return { error: `❌ スラッグ "${slug}"
|
|
840
|
+
return { error: `❌ スラッグ "${slug}" に一致する投稿・固定ページが見つかりません。` };
|
|
841
|
+
}
|
|
842
|
+
if (posts.length > 1) {
|
|
843
|
+
const candidates = posts.map(p => ` [${p.id}] ${p.post_type === 'page' ? '[固定ページ]' : '[投稿]'} ${p.title || '(無題)'} — ${p.link || '-'}`).join('\n');
|
|
844
|
+
return { error: `❌ スラッグ "${slug}" に複数の候補が見つかりました。postId を数値で指定してください:\n${candidates}` };
|
|
686
845
|
}
|
|
687
846
|
const resolvedId = posts[0].id;
|
|
688
847
|
|
|
@@ -2056,7 +2215,7 @@ const tools = [
|
|
|
2056
2215
|
},
|
|
2057
2216
|
{
|
|
2058
2217
|
name: "select_block",
|
|
2059
|
-
description: "Select block(s)
|
|
2218
|
+
description: "Select block(s) by ref, section, blockType, or contains.",
|
|
2060
2219
|
inputSchema: {
|
|
2061
2220
|
type: "object",
|
|
2062
2221
|
properties: {
|
|
@@ -2161,7 +2320,7 @@ const tools = [
|
|
|
2161
2320
|
},
|
|
2162
2321
|
{
|
|
2163
2322
|
name: "update_post_meta",
|
|
2164
|
-
description: "Update post metadata.",
|
|
2323
|
+
description: "Update post or page metadata.",
|
|
2165
2324
|
inputSchema: {
|
|
2166
2325
|
type: "object",
|
|
2167
2326
|
properties: {
|
|
@@ -2170,16 +2329,39 @@ const tools = [
|
|
|
2170
2329
|
title: { type: "string", description: "Title" },
|
|
2171
2330
|
status: { type: "string", enum: ["publish", "draft", "pending", "private"], description: "Status" },
|
|
2172
2331
|
slug: { type: "string", description: "Slug (URL)" },
|
|
2173
|
-
categories: { type: "array", items: { type: "number" }, description: "Category IDs" },
|
|
2174
|
-
tags: { type: "array", items: { type: "number" }, description: "Tag IDs" },
|
|
2332
|
+
categories: { type: "array", items: { type: "number" }, description: "Category IDs (posts only)" },
|
|
2333
|
+
tags: { type: "array", items: { type: "number" }, description: "Tag IDs (posts only)" },
|
|
2334
|
+
parent: { type: "number", description: "Parent page ID (pages only, 0 to remove)" },
|
|
2175
2335
|
excerpt: { type: "string", description: "Excerpt" },
|
|
2176
2336
|
featured_media: { type: "number", description: "Featured image ID (0 to remove)" },
|
|
2177
2337
|
},
|
|
2178
2338
|
},
|
|
2179
2339
|
},
|
|
2340
|
+
{
|
|
2341
|
+
name: "create_post",
|
|
2342
|
+
description: "Create a new WordPress post or page (draft by default). Returns post ID and editor URL.",
|
|
2343
|
+
inputSchema: {
|
|
2344
|
+
type: "object",
|
|
2345
|
+
properties: {
|
|
2346
|
+
title: { type: "string", description: "Post title" },
|
|
2347
|
+
post_type: { type: "string", enum: ["post", "page"], description: "Content type (default: post)" },
|
|
2348
|
+
slug: { type: "string", description: "URL slug" },
|
|
2349
|
+
status: { type: "string", enum: ["publish", "draft", "pending", "private"], description: "Status (default: draft)" },
|
|
2350
|
+
categories: { type: "array", items: { type: "number" }, description: "Category IDs (posts only)" },
|
|
2351
|
+
tags: { type: "array", items: { type: "number" }, description: "Tag IDs (posts only)" },
|
|
2352
|
+
parent: { type: "number", description: "Parent page ID (pages only)" },
|
|
2353
|
+
featured_media: { type: "number", description: "Featured image media ID" },
|
|
2354
|
+
excerpt: { type: "string", description: "Excerpt" },
|
|
2355
|
+
content: { type: "string", description: "HTML content (exclusive with filePath)" },
|
|
2356
|
+
filePath: { type: "string", description: "Local file path (.md or .html, exclusive with content)" },
|
|
2357
|
+
site: siteParam,
|
|
2358
|
+
},
|
|
2359
|
+
required: ["title"],
|
|
2360
|
+
},
|
|
2361
|
+
},
|
|
2180
2362
|
{
|
|
2181
2363
|
name: "list_posts",
|
|
2182
|
-
description: "Search posts. Use slug for exact match, search for keyword. Mutually exclusive.",
|
|
2364
|
+
description: "Search posts and pages. Use slug for exact match, search for keyword. Mutually exclusive.",
|
|
2183
2365
|
inputSchema: {
|
|
2184
2366
|
type: "object",
|
|
2185
2367
|
properties: {
|
|
@@ -2187,6 +2369,7 @@ const tools = [
|
|
|
2187
2369
|
slug: { type: "string", description: "Slug for exact match (exclusive with search)" },
|
|
2188
2370
|
search: { type: "string", description: "Keyword (exclusive with slug)" },
|
|
2189
2371
|
status: { type: "string", enum: ["publish", "draft", "pending", "private", "any"], description: "Status filter (default: publish)" },
|
|
2372
|
+
post_type: { type: "string", enum: ["post", "page", "any"], description: "Filter by type: post, page, or any (default: any)" },
|
|
2190
2373
|
categories: { type: "array", items: { type: "number" }, description: "Category IDs" },
|
|
2191
2374
|
tags: { type: "array", items: { type: "number" }, description: "Tag IDs" },
|
|
2192
2375
|
per_page: { type: "integer", description: "Per page (1-100)", minimum: 1, maximum: 100 },
|
|
@@ -2196,6 +2379,83 @@ const tools = [
|
|
|
2196
2379
|
},
|
|
2197
2380
|
},
|
|
2198
2381
|
},
|
|
2382
|
+
{
|
|
2383
|
+
name: "list_blog_parts",
|
|
2384
|
+
description: "List or search blog parts (reusable blocks). SWELL theme only.",
|
|
2385
|
+
inputSchema: {
|
|
2386
|
+
type: "object",
|
|
2387
|
+
properties: {
|
|
2388
|
+
site: siteParam,
|
|
2389
|
+
search: { type: "string", description: "Keyword search" },
|
|
2390
|
+
status: { type: "string", enum: ["publish", "draft", "private", "any"], description: "Status filter (default: publish)" },
|
|
2391
|
+
per_page: { type: "integer", description: "Per page (1-100)", minimum: 1, maximum: 100 },
|
|
2392
|
+
page: { type: "integer", description: "Page number", minimum: 1 },
|
|
2393
|
+
},
|
|
2394
|
+
},
|
|
2395
|
+
},
|
|
2396
|
+
{
|
|
2397
|
+
name: "get_blog_parts",
|
|
2398
|
+
description: "Get blog parts structure and blocks by partsId. Opens a blog parts post for viewing/editing.",
|
|
2399
|
+
inputSchema: {
|
|
2400
|
+
type: "object",
|
|
2401
|
+
properties: {
|
|
2402
|
+
partsId: { type: "integer", description: "Blog parts ID (required)" },
|
|
2403
|
+
site: siteParam,
|
|
2404
|
+
full: { type: "boolean", description: "Full block data" },
|
|
2405
|
+
section: { type: "string", description: "Zoom into section" },
|
|
2406
|
+
blockType: { type: "string", description: "Filter by block type" },
|
|
2407
|
+
contains: { type: "string", description: "Text search" },
|
|
2408
|
+
},
|
|
2409
|
+
required: ["partsId"],
|
|
2410
|
+
},
|
|
2411
|
+
},
|
|
2412
|
+
{
|
|
2413
|
+
name: "create_blog_parts",
|
|
2414
|
+
description: "Create a new blog parts (reusable block). SWELL theme only. Returns ID and editor URL.",
|
|
2415
|
+
inputSchema: {
|
|
2416
|
+
type: "object",
|
|
2417
|
+
properties: {
|
|
2418
|
+
title: { type: "string", description: "Blog parts title" },
|
|
2419
|
+
status: { type: "string", enum: ["publish", "draft", "pending", "private"], description: "Status (default: draft)" },
|
|
2420
|
+
slug: { type: "string", description: "URL slug" },
|
|
2421
|
+
content: { type: "string", description: "HTML content (exclusive with filePath)" },
|
|
2422
|
+
filePath: { type: "string", description: "Local file path (.md or .html, exclusive with content)" },
|
|
2423
|
+
parts_use: { type: "array", items: { type: "number" }, description: "parts_use taxonomy term IDs (SWELL usage category)" },
|
|
2424
|
+
site: siteParam,
|
|
2425
|
+
},
|
|
2426
|
+
required: ["title"],
|
|
2427
|
+
},
|
|
2428
|
+
},
|
|
2429
|
+
{
|
|
2430
|
+
name: "update_blog_parts",
|
|
2431
|
+
description: "Update blog parts blocks. Same as update_blocks but for blog parts posts. Headless only.",
|
|
2432
|
+
inputSchema: {
|
|
2433
|
+
type: "object",
|
|
2434
|
+
properties: {
|
|
2435
|
+
partsId: { type: "integer", description: "Blog parts ID (required)" },
|
|
2436
|
+
site: siteParam,
|
|
2437
|
+
snapshotId: { type: "string", description: "Snapshot ID from get_blog_parts." },
|
|
2438
|
+
ref: { type: "string", description: "Block ref from snapshot." },
|
|
2439
|
+
refs: { type: "array", items: { type: "string" }, description: "Multiple block refs." },
|
|
2440
|
+
expectedRevision: { type: "string", description: "Revision check." },
|
|
2441
|
+
newHTML: { type: "string", description: "New block HTML." },
|
|
2442
|
+
replacements: {
|
|
2443
|
+
type: "array",
|
|
2444
|
+
items: {
|
|
2445
|
+
type: "object",
|
|
2446
|
+
properties: {
|
|
2447
|
+
old: { type: "string" },
|
|
2448
|
+
new: { type: "string" },
|
|
2449
|
+
regex: { type: "boolean" },
|
|
2450
|
+
},
|
|
2451
|
+
required: ["old", "new"],
|
|
2452
|
+
},
|
|
2453
|
+
description: "Text replacements.",
|
|
2454
|
+
},
|
|
2455
|
+
},
|
|
2456
|
+
required: ["partsId"],
|
|
2457
|
+
},
|
|
2458
|
+
},
|
|
2199
2459
|
{
|
|
2200
2460
|
name: "list_taxonomies",
|
|
2201
2461
|
description: "List categories or tags.",
|
|
@@ -2418,6 +2678,36 @@ const tools = [
|
|
|
2418
2678
|
},
|
|
2419
2679
|
},
|
|
2420
2680
|
},
|
|
2681
|
+
{
|
|
2682
|
+
name: "upload_media",
|
|
2683
|
+
description: "Upload image to WordPress media library. Returns media ID, URL, dimensions.",
|
|
2684
|
+
inputSchema: {
|
|
2685
|
+
type: "object",
|
|
2686
|
+
properties: {
|
|
2687
|
+
site: siteParam,
|
|
2688
|
+
filePath: { type: "string", description: "Absolute path to image file on local machine" },
|
|
2689
|
+
name: { type: "string", description: "New filename (without extension, ASCII/hyphens recommended)" },
|
|
2690
|
+
alt: { type: "string", description: "Alt text and title (Japanese OK)" },
|
|
2691
|
+
title: { type: "string", description: "Title (defaults to alt if omitted)" },
|
|
2692
|
+
},
|
|
2693
|
+
required: ["filePath", "name", "alt"],
|
|
2694
|
+
},
|
|
2695
|
+
},
|
|
2696
|
+
{
|
|
2697
|
+
name: "search_media",
|
|
2698
|
+
description: "Search WordPress media library. Auto-generates hiragana/katakana/romaji variants for broader matching.",
|
|
2699
|
+
inputSchema: {
|
|
2700
|
+
type: "object",
|
|
2701
|
+
properties: {
|
|
2702
|
+
site: siteParam,
|
|
2703
|
+
query: { type: "string", description: "Search keyword" },
|
|
2704
|
+
strip: { type: "array", items: { type: "string" }, description: "Suffixes to strip (e.g. ['先生', 'さん'])" },
|
|
2705
|
+
prefix: { type: "array", items: { type: "string" }, description: "Prefixes to add (e.g. ['エキサイト-'])" },
|
|
2706
|
+
per_page: { type: "integer", description: "Results per variant (1-20, default: 10)", minimum: 1, maximum: 20 },
|
|
2707
|
+
},
|
|
2708
|
+
required: ["query"],
|
|
2709
|
+
},
|
|
2710
|
+
},
|
|
2421
2711
|
{
|
|
2422
2712
|
name: "list_connections",
|
|
2423
2713
|
description: "List connections.",
|
|
@@ -2449,6 +2739,38 @@ const tools = [
|
|
|
2449
2739
|
required: ["content"],
|
|
2450
2740
|
},
|
|
2451
2741
|
},
|
|
2742
|
+
{
|
|
2743
|
+
name: "search_asp_link",
|
|
2744
|
+
description: "Search registered ASP affiliate links. Search by title, slug, or keyword across title and asp fields.",
|
|
2745
|
+
inputSchema: {
|
|
2746
|
+
type: "object",
|
|
2747
|
+
properties: {
|
|
2748
|
+
site: siteParam,
|
|
2749
|
+
search: { type: "string", description: "Keyword search (title, asp_id, asp_name)" },
|
|
2750
|
+
slug: { type: "string", description: "Exact post slug match (exclusive with search)" },
|
|
2751
|
+
per_page: { type: "integer", minimum: 1, maximum: 100, description: "Results per page (default: 20)" },
|
|
2752
|
+
page: { type: "integer", minimum: 1, description: "Page number (default: 1)" },
|
|
2753
|
+
},
|
|
2754
|
+
},
|
|
2755
|
+
},
|
|
2756
|
+
{
|
|
2757
|
+
name: "register_asp_link",
|
|
2758
|
+
description: "Register a new ASP affiliate link. Creates a jump post with ASP metadata.",
|
|
2759
|
+
inputSchema: {
|
|
2760
|
+
type: "object",
|
|
2761
|
+
properties: {
|
|
2762
|
+
site: siteParam,
|
|
2763
|
+
title: { type: "string", description: "Display name (e.g. 'モコム')" },
|
|
2764
|
+
slug: { type: "string", description: "WordPress post slug for URL (e.g. 'mocom')" },
|
|
2765
|
+
asp_id: { type: "string", description: "ASP案件ID — data-id attribute used by JSLinkHelper (defaults to slug if omitted)" },
|
|
2766
|
+
asp_name: { type: "string", description: "案件名 (defaults to title if omitted)" },
|
|
2767
|
+
asp_url: { type: "string", description: "ASP affiliate URL (required)" },
|
|
2768
|
+
asp_url_sp: { type: "string", description: "ASP URL (B) for A/B testing (optional)" },
|
|
2769
|
+
direct_url: { type: "string", description: "Direct URL bypassing ASP (optional)" },
|
|
2770
|
+
},
|
|
2771
|
+
required: ["title", "slug", "asp_url"],
|
|
2772
|
+
},
|
|
2773
|
+
},
|
|
2452
2774
|
];
|
|
2453
2775
|
|
|
2454
2776
|
// ツールリストのハンドラ
|
|
@@ -2599,7 +2921,7 @@ async function handleUpdateBlocksTool(args, toolName) {
|
|
|
2599
2921
|
// Headless モードで target: "selected" はエラー
|
|
2600
2922
|
if (mode === 'headless' && target === 'selected') {
|
|
2601
2923
|
return {
|
|
2602
|
-
content: [{ type: "text", text: "❌ target:'selected' はエディタ接続時のみ使用可能です。
|
|
2924
|
+
content: [{ type: "text", text: "❌ target:'selected' はエディタ接続時のみ使用可能です。ref+snapshotId, section, blockType, contains 等を指定してください。" }],
|
|
2603
2925
|
isError: true,
|
|
2604
2926
|
};
|
|
2605
2927
|
}
|
|
@@ -2610,7 +2932,7 @@ async function handleUpdateBlocksTool(args, toolName) {
|
|
|
2610
2932
|
(headingLevel && headingContains);
|
|
2611
2933
|
if (!hasTarget && !(appendToEnd && insertOnly)) {
|
|
2612
2934
|
return {
|
|
2613
|
-
content: [{ type: "text", text: "❌ ターゲットが指定されていません。\
|
|
2935
|
+
content: [{ type: "text", text: "❌ ターゲットが指定されていません。\nref+snapshotId, section, blockType, contains 等のいずれかを指定してください。" }],
|
|
2614
2936
|
isError: true,
|
|
2615
2937
|
};
|
|
2616
2938
|
}
|
|
@@ -2846,7 +3168,7 @@ async function handleUpdateBlocksTool(args, toolName) {
|
|
|
2846
3168
|
}
|
|
2847
3169
|
|
|
2848
3170
|
// ツール実行のハンドラ
|
|
2849
|
-
const _WRITE_TOOLS = new Set(['update_blocks', 'delete_block', 'move_block', 'duplicate_block', 'insert_block', 'table_operations']);
|
|
3171
|
+
const _WRITE_TOOLS = new Set(['update_blocks', 'delete_block', 'move_block', 'duplicate_block', 'insert_block', 'table_operations', 'update_blog_parts']);
|
|
2850
3172
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
2851
3173
|
const { name, arguments: args } = request.params;
|
|
2852
3174
|
const _toolLogStart = Date.now();
|
|
@@ -2967,10 +3289,49 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2967
3289
|
const endIdx = (limit && limit > 0) ? startIdx + limit : blocks.length;
|
|
2968
3290
|
blocks = blocks.slice(startIdx, endIdx);
|
|
2969
3291
|
|
|
3292
|
+
// エディタモード時、ブロパの展開情報をヘッドレス API で補完
|
|
3293
|
+
if (mode === 'editor' && client) {
|
|
3294
|
+
for (const b of blocks) {
|
|
3295
|
+
if (b.type === 'loos/blog-parts' && !b.blogParts) {
|
|
3296
|
+
const pid = parseInt(b.attributes?.partsID, 10);
|
|
3297
|
+
if (pid > 0) {
|
|
3298
|
+
try {
|
|
3299
|
+
const bpBlocks = await client.headlessGetBlocks(pid);
|
|
3300
|
+
const meta = await client.headlessGetMeta(pid);
|
|
3301
|
+
b.blogParts = {
|
|
3302
|
+
partsId: pid,
|
|
3303
|
+
partsTitle: meta?.title || '',
|
|
3304
|
+
blocks: (bpBlocks?.blocks || []).map(bl => ({ type: bl.type, depth: bl.depth || 0 })),
|
|
3305
|
+
};
|
|
3306
|
+
} catch (_) {
|
|
3307
|
+
b.blogPartsError = 'not_found';
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
|
|
2970
3314
|
const blockList = blocks.map(b => {
|
|
2971
3315
|
const attrPreview = JSON.stringify(b.attributes || {}).slice(0, 200);
|
|
2972
3316
|
const t = formatTreeLine(b);
|
|
2973
|
-
|
|
3317
|
+
let line = `${t.indent}[${b.index}${_refTag(b.index)}] ${t.marker}${b.type}${t.childInfo} - ${b.section || "(記事冒頭)"} ${t.depthInfo}\n${t.indent} ${attrPreview}${attrPreview.length >= 200 ? '...' : ''}`;
|
|
3318
|
+
|
|
3319
|
+
// ブロパ展開表示(読み取り専用、ref なし)
|
|
3320
|
+
if (b.blogParts) {
|
|
3321
|
+
line += `\n${t.indent} 📦 "${b.blogParts.partsTitle}" [ID:${b.blogParts.partsId}]`;
|
|
3322
|
+
const innerBlocks = b.blogParts.blocks || [];
|
|
3323
|
+
for (const inner of innerBlocks.slice(0, 10)) {
|
|
3324
|
+
const innerIndent = ' '.repeat((inner.depth || 0) + 1);
|
|
3325
|
+
line += `\n${t.indent} ${innerIndent}└ ${inner.type}`;
|
|
3326
|
+
}
|
|
3327
|
+
if (innerBlocks.length > 10) {
|
|
3328
|
+
line += `\n${t.indent} ... 他 ${innerBlocks.length - 10} ブロック`;
|
|
3329
|
+
}
|
|
3330
|
+
} else if (b.blogPartsError) {
|
|
3331
|
+
line += `\n${t.indent} ⚠️ ブログパーツ展開失敗: ${b.blogPartsError}`;
|
|
3332
|
+
}
|
|
3333
|
+
|
|
3334
|
+
return line;
|
|
2974
3335
|
}).join("\n\n");
|
|
2975
3336
|
|
|
2976
3337
|
let paginationInfo = '';
|
|
@@ -3313,6 +3674,32 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3313
3674
|
output += `${indent}${prefix} ${heading.text} (H${heading.level}, index:${heading.index}${_refTag(heading.index)})\n`;
|
|
3314
3675
|
}
|
|
3315
3676
|
|
|
3677
|
+
// ブログパーツ一覧
|
|
3678
|
+
if (state.blockSummary && state.blockSummary['loos/blog-parts']) {
|
|
3679
|
+
const bpEntries = state.blockSummary['loos/blog-parts'].blocks || [];
|
|
3680
|
+
const bpItems = [];
|
|
3681
|
+
for (const bp of bpEntries) {
|
|
3682
|
+
const pid = bp.partsId || (bp.attributes?.partsID ? parseInt(bp.attributes.partsID, 10) : 0);
|
|
3683
|
+
if (!pid) continue;
|
|
3684
|
+
let title = bp.partsTitle || '';
|
|
3685
|
+
// エディタモード等でタイトルが空の場合、ヘッドレス API で補完
|
|
3686
|
+
if (!title && client) {
|
|
3687
|
+
try {
|
|
3688
|
+
const meta = await client.headlessGetMeta(pid);
|
|
3689
|
+
title = meta?.title || '';
|
|
3690
|
+
} catch (_) {}
|
|
3691
|
+
}
|
|
3692
|
+
bpItems.push({ index: bp.index, partsId: pid, title });
|
|
3693
|
+
}
|
|
3694
|
+
if (bpItems.length > 0) {
|
|
3695
|
+
output += `\nブログパーツ:\n`;
|
|
3696
|
+
for (const bp of bpItems) {
|
|
3697
|
+
output += ` [${bp.index}] 📦 "${bp.title || '(不明)'}" [ID:${bp.partsId}]\n`;
|
|
3698
|
+
}
|
|
3699
|
+
output += ` ※ 中身の確認は get_blog_parts(partsId) を使用\n`;
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
|
|
3316
3703
|
return {
|
|
3317
3704
|
content: [{ type: "text", text: output + _snapshotLine + _modeTag_gas }],
|
|
3318
3705
|
};
|
|
@@ -3333,7 +3720,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3333
3720
|
// BUG-3: select_block では selected は使用不可
|
|
3334
3721
|
if (tp.target === "selected") {
|
|
3335
3722
|
return {
|
|
3336
|
-
content: [{ type: "text", text: "❌ select_block では selected は使用できません。
|
|
3723
|
+
content: [{ type: "text", text: "❌ select_block では selected は使用できません。ref+snapshotId, blockType, contains 等を指定してください。" }],
|
|
3337
3724
|
isError: true,
|
|
3338
3725
|
};
|
|
3339
3726
|
}
|
|
@@ -3346,7 +3733,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3346
3733
|
(headingLevel && headingContains);
|
|
3347
3734
|
if (!hasTarget) {
|
|
3348
3735
|
return {
|
|
3349
|
-
content: [{ type: "text", text: "❌ ターゲットが指定されていません。
|
|
3736
|
+
content: [{ type: "text", text: "❌ ターゲットが指定されていません。ref+snapshotId, blockType, heading, contains 等のいずれかを指定してください。" }],
|
|
3350
3737
|
isError: true,
|
|
3351
3738
|
};
|
|
3352
3739
|
}
|
|
@@ -3751,21 +4138,29 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3751
4138
|
const statusInfo = mode === 'editor'
|
|
3752
4139
|
? `モード: Editor (接続中)`
|
|
3753
4140
|
: `モード: Headless (postId: ${postId})`;
|
|
3754
|
-
const
|
|
3755
|
-
|
|
4141
|
+
const isPage = m.post_type === 'page';
|
|
4142
|
+
const typeLabel = isPage ? '📄 固定ページメタ情報' : '📄 投稿メタ情報';
|
|
4143
|
+
const lines = [
|
|
4144
|
+
typeLabel,
|
|
3756
4145
|
``,
|
|
3757
4146
|
statusInfo,
|
|
3758
4147
|
`ID: ${m.id}`,
|
|
4148
|
+
`タイプ: ${m.post_type || 'post'}`,
|
|
3759
4149
|
`タイトル: ${m.title}`,
|
|
3760
4150
|
`ステータス: ${m.status}`,
|
|
3761
4151
|
`スラッグ: ${m.slug}`,
|
|
3762
4152
|
`URL: ${m.link}`,
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
4153
|
+
];
|
|
4154
|
+
if (isPage) {
|
|
4155
|
+
lines.push(`親ページ: ${m.parent || 'なし'}`);
|
|
4156
|
+
} else {
|
|
4157
|
+
lines.push(`カテゴリ: ${JSON.stringify(m.categories)}`);
|
|
4158
|
+
lines.push(`タグ: ${JSON.stringify(m.tags)}`);
|
|
4159
|
+
}
|
|
4160
|
+
lines.push(`アイキャッチ: ${m.featuredImage || 'なし'}`);
|
|
4161
|
+
lines.push(`抜粋: ${m.excerpt || 'なし'}`);
|
|
4162
|
+
lines.push(`更新日: ${m.modified}`);
|
|
4163
|
+
const text = lines.join('\n');
|
|
3769
4164
|
return { content: [{ type: "text", text }] };
|
|
3770
4165
|
}
|
|
3771
4166
|
|
|
@@ -3791,6 +4186,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3791
4186
|
if (args.slug !== undefined) updateData.slug = args.slug;
|
|
3792
4187
|
if (args.categories !== undefined) updateData.categories = args.categories;
|
|
3793
4188
|
if (args.tags !== undefined) updateData.tags = args.tags;
|
|
4189
|
+
if (args.parent !== undefined) updateData.parent = args.parent;
|
|
3794
4190
|
if (args.excerpt !== undefined) updateData.excerpt = args.excerpt;
|
|
3795
4191
|
if (args.featured_media !== undefined) updateData.featured_media = args.featured_media;
|
|
3796
4192
|
|
|
@@ -3812,6 +4208,85 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3812
4208
|
return { content: [{ type: "text", text: updateText }] };
|
|
3813
4209
|
}
|
|
3814
4210
|
|
|
4211
|
+
case "create_post": {
|
|
4212
|
+
let client;
|
|
4213
|
+
try {
|
|
4214
|
+
client = registry.get(args?.site);
|
|
4215
|
+
} catch (e) {
|
|
4216
|
+
return { content: [{ type: "text", text: `❌ ${e.message}` }], isError: true };
|
|
4217
|
+
}
|
|
4218
|
+
|
|
4219
|
+
const title = args?.title?.trim();
|
|
4220
|
+
if (!title) {
|
|
4221
|
+
return { content: [{ type: "text", text: "❌ title は必須です。" }], isError: true };
|
|
4222
|
+
}
|
|
4223
|
+
|
|
4224
|
+
let content = args?.content;
|
|
4225
|
+
const filePath = args?.filePath;
|
|
4226
|
+
|
|
4227
|
+
if (content !== undefined && filePath !== undefined) {
|
|
4228
|
+
return { content: [{ type: "text", text: "❌ content と filePath は同時に指定できません。どちらか一方を指定してください。" }], isError: true };
|
|
4229
|
+
}
|
|
4230
|
+
|
|
4231
|
+
if (filePath) {
|
|
4232
|
+
try {
|
|
4233
|
+
const ext = extname(filePath).toLowerCase();
|
|
4234
|
+
const raw = readHTMLFromFile(filePath).html;
|
|
4235
|
+
if (ext === '.md') {
|
|
4236
|
+
content = await marked.parse(raw);
|
|
4237
|
+
} else if (ext === '.html' || ext === '.htm') {
|
|
4238
|
+
content = raw;
|
|
4239
|
+
} else {
|
|
4240
|
+
return { content: [{ type: "text", text: `❌ 対応していないファイル形式です: ${ext} (.md または .html のみ)` }], isError: true };
|
|
4241
|
+
}
|
|
4242
|
+
} catch (e) {
|
|
4243
|
+
return { content: [{ type: "text", text: `❌ ${e.message}` }], isError: true };
|
|
4244
|
+
}
|
|
4245
|
+
}
|
|
4246
|
+
|
|
4247
|
+
const postType = args?.post_type || 'post';
|
|
4248
|
+
|
|
4249
|
+
if (postType === 'page' && (args?.categories !== undefined || args?.tags !== undefined)) {
|
|
4250
|
+
return { content: [{ type: "text", text: "❌ 固定ページにカテゴリ・タグは設定できません。" }], isError: true };
|
|
4251
|
+
}
|
|
4252
|
+
|
|
4253
|
+
const postData = {
|
|
4254
|
+
title,
|
|
4255
|
+
status: args?.status || 'draft',
|
|
4256
|
+
post_type: postType,
|
|
4257
|
+
};
|
|
4258
|
+
if (args?.slug !== undefined) postData.slug = args.slug;
|
|
4259
|
+
if (postType === 'page') {
|
|
4260
|
+
if (args?.parent !== undefined) postData.parent = args.parent;
|
|
4261
|
+
} else {
|
|
4262
|
+
if (args?.categories !== undefined) postData.categories = args.categories;
|
|
4263
|
+
if (args?.tags !== undefined) postData.tags = args.tags;
|
|
4264
|
+
}
|
|
4265
|
+
if (args?.excerpt !== undefined) postData.excerpt = args.excerpt;
|
|
4266
|
+
if (args?.featured_media !== undefined) postData.featured_media = args.featured_media;
|
|
4267
|
+
if (content && content.trim()) postData.content = content;
|
|
4268
|
+
|
|
4269
|
+
const typeLabel = postType === 'page' ? '固定ページ' : '投稿';
|
|
4270
|
+
|
|
4271
|
+
try {
|
|
4272
|
+
const created = await client.createPost(postData);
|
|
4273
|
+
const postId = created.id;
|
|
4274
|
+
const editUrl = `${client.wpUrl.replace(/\/$/, '')}/wp-admin/post.php?post=${postId}&action=edit`;
|
|
4275
|
+
const resultText = [
|
|
4276
|
+
`✅ ${typeLabel}を作成しました`,
|
|
4277
|
+
``,
|
|
4278
|
+
` ID: ${postId}`,
|
|
4279
|
+
` タイプ: ${postType}`,
|
|
4280
|
+
` タイトル: ${created.title?.raw || title}`,
|
|
4281
|
+
` ステータス: ${created.status}`,
|
|
4282
|
+
` 編集URL: ${editUrl}`,
|
|
4283
|
+
].join('\n');
|
|
4284
|
+
return { content: [{ type: "text", text: resultText }] };
|
|
4285
|
+
} catch (e) {
|
|
4286
|
+
return errorResponse(name, `${typeLabel}作成に失敗しました: ${e.message}`, args?.site);
|
|
4287
|
+
}
|
|
4288
|
+
}
|
|
4289
|
+
|
|
3815
4290
|
case "list_posts": {
|
|
3816
4291
|
let client;
|
|
3817
4292
|
try {
|
|
@@ -3829,26 +4304,30 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3829
4304
|
return { content: [{ type: "text", text: "❌ slug と search は同時に指定できません。slug(完全一致)または search(キーワード検索)のいずれかを使用してください。" }], isError: true };
|
|
3830
4305
|
}
|
|
3831
4306
|
|
|
3832
|
-
|
|
3833
|
-
const listArgs = { ...(args || {}), slug, search };
|
|
4307
|
+
const postType = args?.post_type || 'any';
|
|
4308
|
+
const listArgs = { ...(args || {}), slug, search, post_type: postType };
|
|
3834
4309
|
if (slug && !listArgs.status) {
|
|
3835
4310
|
listArgs.status = 'any';
|
|
3836
4311
|
}
|
|
3837
4312
|
|
|
3838
|
-
const
|
|
4313
|
+
const result = await client.searchPosts(listArgs);
|
|
4314
|
+
const posts = result.items || [];
|
|
4315
|
+
const total = result.total || 0;
|
|
4316
|
+
const totalPages = result.total_pages || 0;
|
|
3839
4317
|
|
|
3840
|
-
if (
|
|
3841
|
-
return { content: [{ type: "text", text: "📋
|
|
4318
|
+
if (posts.length === 0) {
|
|
4319
|
+
return { content: [{ type: "text", text: "📋 該当する投稿・固定ページはありません。" }] };
|
|
3842
4320
|
}
|
|
3843
4321
|
|
|
3844
4322
|
const currentPage = args?.page || 1;
|
|
3845
4323
|
const list = posts.map(p => {
|
|
3846
|
-
|
|
4324
|
+
const typeLabel = p.post_type === 'page' ? '[固定ページ]' : '[投稿]';
|
|
4325
|
+
return ` [${p.id}] ${typeLabel} ${p.title || '(無題)'}\n` +
|
|
3847
4326
|
` ステータス: ${p.status} | 更新: ${p.modified?.split('T')[0] || '-'}\n` +
|
|
3848
4327
|
` URL: ${p.link || '-'}`;
|
|
3849
4328
|
}).join('\n\n');
|
|
3850
4329
|
|
|
3851
|
-
let listText = `📋
|
|
4330
|
+
let listText = `📋 一覧 (${posts.length}件 / 全${total}件)\n\n${list}`;
|
|
3852
4331
|
if (totalPages > 1) {
|
|
3853
4332
|
listText += `\n\n📄 ページ: ${currentPage} / ${totalPages}`;
|
|
3854
4333
|
if (currentPage < totalPages) {
|
|
@@ -3859,6 +4338,259 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3859
4338
|
return { content: [{ type: "text", text: listText }] };
|
|
3860
4339
|
}
|
|
3861
4340
|
|
|
4341
|
+
case "create_blog_parts": {
|
|
4342
|
+
let client;
|
|
4343
|
+
try {
|
|
4344
|
+
client = registry.get(args?.site);
|
|
4345
|
+
} catch (e) {
|
|
4346
|
+
return { content: [{ type: "text", text: `❌ ${e.message}` }], isError: true };
|
|
4347
|
+
}
|
|
4348
|
+
|
|
4349
|
+
const title = args?.title?.trim();
|
|
4350
|
+
if (!title) {
|
|
4351
|
+
return { content: [{ type: "text", text: "❌ title は必須です。" }], isError: true };
|
|
4352
|
+
}
|
|
4353
|
+
|
|
4354
|
+
let content = args?.content;
|
|
4355
|
+
const filePath = args?.filePath;
|
|
4356
|
+
|
|
4357
|
+
if (content !== undefined && filePath !== undefined) {
|
|
4358
|
+
return { content: [{ type: "text", text: "❌ content と filePath は同時に指定できません。" }], isError: true };
|
|
4359
|
+
}
|
|
4360
|
+
|
|
4361
|
+
if (filePath) {
|
|
4362
|
+
try {
|
|
4363
|
+
const ext = extname(filePath).toLowerCase();
|
|
4364
|
+
const raw = readHTMLFromFile(filePath).html;
|
|
4365
|
+
if (ext === '.md') {
|
|
4366
|
+
content = await marked.parse(raw);
|
|
4367
|
+
} else if (ext === '.html' || ext === '.htm') {
|
|
4368
|
+
content = raw;
|
|
4369
|
+
} else {
|
|
4370
|
+
return { content: [{ type: "text", text: `❌ 対応していないファイル形式です: ${ext} (.md または .html のみ)` }], isError: true };
|
|
4371
|
+
}
|
|
4372
|
+
} catch (e) {
|
|
4373
|
+
return { content: [{ type: "text", text: `❌ ${e.message}` }], isError: true };
|
|
4374
|
+
}
|
|
4375
|
+
}
|
|
4376
|
+
|
|
4377
|
+
const postData = {
|
|
4378
|
+
title,
|
|
4379
|
+
status: args?.status || 'draft',
|
|
4380
|
+
post_type: 'blog_parts',
|
|
4381
|
+
};
|
|
4382
|
+
if (args?.slug !== undefined) postData.slug = args.slug;
|
|
4383
|
+
if (args?.parts_use !== undefined) postData.parts_use = args.parts_use;
|
|
4384
|
+
if (content && content.trim()) postData.content = content;
|
|
4385
|
+
|
|
4386
|
+
try {
|
|
4387
|
+
const created = await client.createPost(postData);
|
|
4388
|
+
const postId = created.id;
|
|
4389
|
+
const editUrl = `${client.wpUrl.replace(/\/$/, '')}/wp-admin/post.php?post=${postId}&action=edit`;
|
|
4390
|
+
const resultText = [
|
|
4391
|
+
`✅ ブログパーツを作成しました`,
|
|
4392
|
+
``,
|
|
4393
|
+
` ID: ${postId}`,
|
|
4394
|
+
` タイトル: ${created.title?.raw || title}`,
|
|
4395
|
+
` ステータス: ${created.status}`,
|
|
4396
|
+
` 編集URL: ${editUrl}`,
|
|
4397
|
+
].join('\n');
|
|
4398
|
+
return { content: [{ type: "text", text: resultText }] };
|
|
4399
|
+
} catch (e) {
|
|
4400
|
+
return errorResponse(name, `ブログパーツ作成に失敗しました: ${e.message}`, args?.site);
|
|
4401
|
+
}
|
|
4402
|
+
}
|
|
4403
|
+
|
|
4404
|
+
case "list_blog_parts": {
|
|
4405
|
+
let client;
|
|
4406
|
+
try {
|
|
4407
|
+
client = registry.get(args?.site);
|
|
4408
|
+
} catch (e) {
|
|
4409
|
+
return { content: [{ type: "text", text: `❌ ${e.message}` }], isError: true };
|
|
4410
|
+
}
|
|
4411
|
+
|
|
4412
|
+
const bpResult = await client.searchBlogParts(args || {});
|
|
4413
|
+
const bpParts = bpResult.items || [];
|
|
4414
|
+
const bpTotal = bpResult.total || 0;
|
|
4415
|
+
const bpTotalPages = bpResult.total_pages || 0;
|
|
4416
|
+
|
|
4417
|
+
if (bpParts.length === 0) {
|
|
4418
|
+
return { content: [{ type: "text", text: "📋 該当するブログパーツはありません。" }] };
|
|
4419
|
+
}
|
|
4420
|
+
|
|
4421
|
+
const bpPage = args?.page || 1;
|
|
4422
|
+
const bpList = bpParts.map(p => {
|
|
4423
|
+
return ` [${p.id}] ${p.title || '(無題)'}\n ステータス: ${p.status} | 更新: ${p.modified || '-'}`;
|
|
4424
|
+
}).join('\n\n');
|
|
4425
|
+
|
|
4426
|
+
let bpText = `📋 ブログ��ーツ一覧 (${bpParts.length}件 / 全${bpTotal}件)\n\n${bpList}`;
|
|
4427
|
+
if (bpTotalPages > 1) {
|
|
4428
|
+
bpText += `\n\n📄 ページ: ${bpPage} / ${bpTotalPages}`;
|
|
4429
|
+
if (bpPage < bpTotalPages) {
|
|
4430
|
+
bpText += ` (次: page=${bpPage + 1})`;
|
|
4431
|
+
}
|
|
4432
|
+
}
|
|
4433
|
+
return { content: [{ type: "text", text: bpText }] };
|
|
4434
|
+
}
|
|
4435
|
+
|
|
4436
|
+
case "get_blog_parts": {
|
|
4437
|
+
let client;
|
|
4438
|
+
try {
|
|
4439
|
+
client = registry.get(args?.site);
|
|
4440
|
+
} catch (e) {
|
|
4441
|
+
return { content: [{ type: "text", text: `❌ ${e.message}` }], isError: true };
|
|
4442
|
+
}
|
|
4443
|
+
|
|
4444
|
+
const bpPartsId = args?.partsId;
|
|
4445
|
+
if (!bpPartsId) {
|
|
4446
|
+
return { content: [{ type: "text", text: "❌ partsId は必須です" }], isError: true };
|
|
4447
|
+
}
|
|
4448
|
+
|
|
4449
|
+
const siteName = args?.site || registry.defaultName();
|
|
4450
|
+
const { full: bpFull, contains: bpContains, section: bpSection, blockType: bpBlockType } = (args || {});
|
|
4451
|
+
|
|
4452
|
+
let bpState;
|
|
4453
|
+
try {
|
|
4454
|
+
bpState = await client.headlessGetStructure(bpPartsId);
|
|
4455
|
+
if (bpFull || bpContains || bpSection || bpBlockType) {
|
|
4456
|
+
const blocksData = await client.headlessGetBlocks(bpPartsId);
|
|
4457
|
+
bpState.allBlocks = blocksData.blocks;
|
|
4458
|
+
}
|
|
4459
|
+
} catch (e) {
|
|
4460
|
+
const formatted = formatHeadlessConflictError(e);
|
|
4461
|
+
if (formatted) return formatted;
|
|
4462
|
+
throw e;
|
|
4463
|
+
}
|
|
4464
|
+
|
|
4465
|
+
// snapshot 作成(編集用 ref を付与)
|
|
4466
|
+
let _bpRefTag = (_idx) => '';
|
|
4467
|
+
let _bpSnapshotLine = '';
|
|
4468
|
+
const bpBlocks = bpState.allBlocks || [];
|
|
4469
|
+
if (bpBlocks.length > 0) {
|
|
4470
|
+
const _bpSnapshotId = generateSnapshotId();
|
|
4471
|
+
const _bpRevision = `rev_${Date.now().toString(36)}`;
|
|
4472
|
+
const _bpSnapshotBlocks = bpBlocks.map((b, i) => ({ ...b, ref: `r${i}` }));
|
|
4473
|
+
snapshotCache.set({
|
|
4474
|
+
snapshotId: _bpSnapshotId, postId: bpPartsId, mode: 'headless',
|
|
4475
|
+
sessionId: null, siteName,
|
|
4476
|
+
createdAt: Date.now(), revision: _bpRevision,
|
|
4477
|
+
blocks: _bpSnapshotBlocks, displayMode: bpFull ? 'full' : 'default',
|
|
4478
|
+
});
|
|
4479
|
+
const _bpRefMap = new Map(_bpSnapshotBlocks.map(b => [b.index, b.ref]));
|
|
4480
|
+
_bpRefTag = (idx) => { const r = _bpRefMap.get(idx); return r ? `|${r}` : ''; };
|
|
4481
|
+
_bpSnapshotLine = `\n[snapshot:${_bpSnapshotId} rev:${_bpRevision}]`;
|
|
4482
|
+
}
|
|
4483
|
+
|
|
4484
|
+
if ((bpFull || bpContains || bpSection || bpBlockType) && bpBlocks.length > 0) {
|
|
4485
|
+
let filteredBlocks = bpBlocks;
|
|
4486
|
+
if (bpBlockType) filteredBlocks = filteredBlocks.filter(b => b.type === bpBlockType);
|
|
4487
|
+
if (bpContains) filteredBlocks = filteredBlocks.filter(b => (b.html || '').includes(bpContains));
|
|
4488
|
+
|
|
4489
|
+
const bpBlockList = filteredBlocks.map(b => {
|
|
4490
|
+
const indent = ' '.repeat((b.depth || 0) + 1);
|
|
4491
|
+
const hasChildren = b.isContainer ? ` ▼ [${b.childCount}子]` : '';
|
|
4492
|
+
return `${indent}[${b.index}${_bpRefTag(b.index)}] ${b.type}${hasChildren}`;
|
|
4493
|
+
}).join('\n');
|
|
4494
|
+
|
|
4495
|
+
return { content: [{ type: "text", text: `📦 ブログパーツ [${bpPartsId}]\n\n全ブロック (${filteredBlocks.length}件):\n\n${bpBlockList}${_bpSnapshotLine}\n[DEBUG] mode=headless` }] };
|
|
4496
|
+
}
|
|
4497
|
+
|
|
4498
|
+
// デフォルト: 見出し + サマリー
|
|
4499
|
+
const bpHeadings = (bpState.headings || [])
|
|
4500
|
+
.map(h => ` ${"#".repeat(h.level)} ${h.text} (index:${h.index})`)
|
|
4501
|
+
.join("\n");
|
|
4502
|
+
const bpSummaryLines = Object.entries(bpState.blockSummary || {})
|
|
4503
|
+
.map(([type, data]) => ` ${type}: ${data.count}個`)
|
|
4504
|
+
.join("\n");
|
|
4505
|
+
|
|
4506
|
+
let bpOutText = `📦 ブログパーツ [${bpPartsId}]\n\n`;
|
|
4507
|
+
if (bpHeadings) bpOutText += `見出し構造:\n${bpHeadings}\n\n`;
|
|
4508
|
+
bpOutText += `ブロック概要:\n${bpSummaryLines}`;
|
|
4509
|
+
bpOutText += _bpSnapshotLine;
|
|
4510
|
+
bpOutText += '\n[DEBUG] mode=headless';
|
|
4511
|
+
|
|
4512
|
+
return { content: [{ type: "text", text: bpOutText }] };
|
|
4513
|
+
}
|
|
4514
|
+
|
|
4515
|
+
case "update_blog_parts": {
|
|
4516
|
+
let client;
|
|
4517
|
+
try {
|
|
4518
|
+
client = registry.get(args?.site);
|
|
4519
|
+
} catch (e) {
|
|
4520
|
+
return { content: [{ type: "text", text: `❌ ${e.message}` }], isError: true };
|
|
4521
|
+
}
|
|
4522
|
+
|
|
4523
|
+
const ubpPartsId = args?.partsId;
|
|
4524
|
+
if (!ubpPartsId) {
|
|
4525
|
+
return { content: [{ type: "text", text: "❌ partsId は必須です" }], isError: true };
|
|
4526
|
+
}
|
|
4527
|
+
|
|
4528
|
+
const siteName = args?.site || registry.defaultName();
|
|
4529
|
+
|
|
4530
|
+
// ref/refs → index/indices 解決
|
|
4531
|
+
const ubpParams = { ...args };
|
|
4532
|
+
delete ubpParams.partsId;
|
|
4533
|
+
delete ubpParams.site;
|
|
4534
|
+
|
|
4535
|
+
if (ubpParams.snapshotId && (ubpParams.ref || ubpParams.refs)) {
|
|
4536
|
+
const snap = snapshotCache.get(ubpParams.snapshotId);
|
|
4537
|
+
if (!snap) {
|
|
4538
|
+
return { content: [{ type: "text", text: "❌ snapshotId が無効または期限切れです。get_blog_parts で再取得してください。" }], isError: true };
|
|
4539
|
+
}
|
|
4540
|
+
if (snap.postId !== ubpPartsId) {
|
|
4541
|
+
return { content: [{ type: "text", text: `❌ snapshot の postId (${snap.postId}) と partsId (${ubpPartsId}) が一致しません。` }], isError: true };
|
|
4542
|
+
}
|
|
4543
|
+
|
|
4544
|
+
if (ubpParams.ref) {
|
|
4545
|
+
const refBlock = snap.blocks.find(b => b.ref === ubpParams.ref);
|
|
4546
|
+
if (!refBlock) {
|
|
4547
|
+
return { content: [{ type: "text", text: `❌ ref "${ubpParams.ref}" が snapshot 内に見つかりません。` }], isError: true };
|
|
4548
|
+
}
|
|
4549
|
+
ubpParams.index = refBlock.index;
|
|
4550
|
+
delete ubpParams.ref;
|
|
4551
|
+
}
|
|
4552
|
+
if (ubpParams.refs) {
|
|
4553
|
+
const indices = [];
|
|
4554
|
+
for (const r of ubpParams.refs) {
|
|
4555
|
+
const refBlock = snap.blocks.find(b => b.ref === r);
|
|
4556
|
+
if (!refBlock) {
|
|
4557
|
+
return { content: [{ type: "text", text: `❌ ref "${r}" が snapshot 内に見つかり���せん。` }], isError: true };
|
|
4558
|
+
}
|
|
4559
|
+
indices.push(refBlock.index);
|
|
4560
|
+
}
|
|
4561
|
+
ubpParams.indices = indices;
|
|
4562
|
+
delete ubpParams.refs;
|
|
4563
|
+
}
|
|
4564
|
+
delete ubpParams.snapshotId;
|
|
4565
|
+
}
|
|
4566
|
+
delete ubpParams.expectedRevision;
|
|
4567
|
+
|
|
4568
|
+
try {
|
|
4569
|
+
const ubpResult = await client.headlessUpdate(ubpPartsId, ubpParams);
|
|
4570
|
+
|
|
4571
|
+
// 新 snapshot 生成
|
|
4572
|
+
let ubpSnapshotLine = '';
|
|
4573
|
+
if (ubpResult.blocks && ubpResult.blocks.length > 0) {
|
|
4574
|
+
const _ubpSnapId = generateSnapshotId();
|
|
4575
|
+
const _ubpRev = `rev_${Date.now().toString(36)}`;
|
|
4576
|
+
const _ubpSnapBlocks = ubpResult.blocks.map((b, i) => ({ ...b, ref: `r${i}` }));
|
|
4577
|
+
snapshotCache.set({
|
|
4578
|
+
snapshotId: _ubpSnapId, postId: ubpPartsId, mode: 'headless',
|
|
4579
|
+
sessionId: null, siteName,
|
|
4580
|
+
createdAt: Date.now(), revision: _ubpRev,
|
|
4581
|
+
blocks: _ubpSnapBlocks, displayMode: 'full',
|
|
4582
|
+
});
|
|
4583
|
+
ubpSnapshotLine = `\n[snapshot:${_ubpSnapId} rev:${_ubpRev}]`;
|
|
4584
|
+
}
|
|
4585
|
+
|
|
4586
|
+
return { content: [{ type: "text", text: `✅ ブログパーツ [${ubpPartsId}] を更新しました。${ubpSnapshotLine}` }] };
|
|
4587
|
+
} catch (e) {
|
|
4588
|
+
const formatted = formatHeadlessConflictError(e);
|
|
4589
|
+
if (formatted) return formatted;
|
|
4590
|
+
throw e;
|
|
4591
|
+
}
|
|
4592
|
+
}
|
|
4593
|
+
|
|
3862
4594
|
case "list_taxonomies": {
|
|
3863
4595
|
let client;
|
|
3864
4596
|
try {
|
|
@@ -3981,7 +4713,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3981
4713
|
|
|
3982
4714
|
if (mode === 'headless' && target === 'selected') {
|
|
3983
4715
|
return {
|
|
3984
|
-
content: [{ type: "text", text: "❌ target:'selected' はエディタ接続時のみ使用可能です。
|
|
4716
|
+
content: [{ type: "text", text: "❌ target:'selected' はエディタ接続時のみ使用可能です。ref+snapshotId, section, blockType, contains 等を指定してください。" }],
|
|
3985
4717
|
isError: true,
|
|
3986
4718
|
};
|
|
3987
4719
|
}
|
|
@@ -3991,7 +4723,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3991
4723
|
(headingLevel && headingContains);
|
|
3992
4724
|
if (!hasTarget) {
|
|
3993
4725
|
return {
|
|
3994
|
-
content: [{ type: "text", text: "❌ ターゲットが指定されていません。\
|
|
4726
|
+
content: [{ type: "text", text: "❌ ターゲットが指定されていません。\nref+snapshotId, section, blockType, contains 等のいずれかを指定してください。" }],
|
|
3995
4727
|
isError: true,
|
|
3996
4728
|
};
|
|
3997
4729
|
}
|
|
@@ -4289,8 +5021,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4289
5021
|
|
|
4290
5022
|
let url;
|
|
4291
5023
|
if (target === 'front') {
|
|
4292
|
-
|
|
4293
|
-
|
|
5024
|
+
try {
|
|
5025
|
+
const meta = await client.headlessGetMeta(postId);
|
|
5026
|
+
url = meta.link;
|
|
5027
|
+
} catch {
|
|
5028
|
+
try {
|
|
5029
|
+
const postData = await client.wpCoreRequest(`/wp/v2/posts/${postId}?_fields=link`);
|
|
5030
|
+
url = postData.link;
|
|
5031
|
+
} catch {
|
|
5032
|
+
const pageData = await client.wpCoreRequest(`/wp/v2/pages/${postId}?_fields=link`);
|
|
5033
|
+
url = pageData.link;
|
|
5034
|
+
}
|
|
5035
|
+
}
|
|
4294
5036
|
} else {
|
|
4295
5037
|
url = `${client.wpUrl.replace(/\/$/, '')}/wp-admin/post.php?post=${postId}&action=edit`;
|
|
4296
5038
|
}
|
|
@@ -4335,6 +5077,102 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4335
5077
|
}
|
|
4336
5078
|
}
|
|
4337
5079
|
|
|
5080
|
+
case "upload_media": {
|
|
5081
|
+
const siteName = args?.site || 'default';
|
|
5082
|
+
let client;
|
|
5083
|
+
try { client = registry.get(siteName); }
|
|
5084
|
+
catch (e) { return errorResponse("upload_media", e.message, siteName); }
|
|
5085
|
+
|
|
5086
|
+
if (!args?.filePath) return errorResponse("upload_media", "filePath is required", siteName);
|
|
5087
|
+
if (!args?.name) return errorResponse("upload_media", "name is required", siteName);
|
|
5088
|
+
if (!args?.alt) return errorResponse("upload_media", "alt is required", siteName);
|
|
5089
|
+
|
|
5090
|
+
let fileInfo;
|
|
5091
|
+
try { fileInfo = validateImageFile(args.filePath); }
|
|
5092
|
+
catch (e) { return errorResponse("upload_media", e.message, siteName); }
|
|
5093
|
+
|
|
5094
|
+
const uploadFilename = `${args.name}${fileInfo.ext}`;
|
|
5095
|
+
const fileBuffer = readFileSync(fileInfo.resolved);
|
|
5096
|
+
|
|
5097
|
+
let media;
|
|
5098
|
+
try {
|
|
5099
|
+
media = await client.uploadMedia(fileBuffer, uploadFilename, fileInfo.contentType);
|
|
5100
|
+
} catch (e) {
|
|
5101
|
+
return errorResponse("upload_media", `Upload failed: ${e.message}`, siteName);
|
|
5102
|
+
}
|
|
5103
|
+
|
|
5104
|
+
const titleText = args?.title || args.alt;
|
|
5105
|
+
try {
|
|
5106
|
+
await client.updateMediaMeta(media.id, { title: titleText, alt_text: args.alt });
|
|
5107
|
+
} catch (e) {
|
|
5108
|
+
// meta 更新失敗は警告のみ(アップロード自体は成功)
|
|
5109
|
+
}
|
|
5110
|
+
|
|
5111
|
+
const url = media.source_url || '';
|
|
5112
|
+
const width = media.media_details?.width || null;
|
|
5113
|
+
const height = media.media_details?.height || null;
|
|
5114
|
+
const text = `✅ Uploaded: ID=${media.id}\n` +
|
|
5115
|
+
` Title: ${titleText}\n` +
|
|
5116
|
+
` Alt: ${args.alt}\n` +
|
|
5117
|
+
` URL: ${url}\n` +
|
|
5118
|
+
(width && height ? ` Size: ${width}x${height}\n` : '') +
|
|
5119
|
+
` Filename: ${uploadFilename}`;
|
|
5120
|
+
return { content: [{ type: "text", text }] };
|
|
5121
|
+
}
|
|
5122
|
+
|
|
5123
|
+
case "search_media": {
|
|
5124
|
+
const siteName = args?.site || 'default';
|
|
5125
|
+
let client;
|
|
5126
|
+
try { client = registry.get(siteName); }
|
|
5127
|
+
catch (e) { return errorResponse("search_media", e.message, siteName); }
|
|
5128
|
+
|
|
5129
|
+
if (!args?.query) return errorResponse("search_media", "query is required", siteName);
|
|
5130
|
+
|
|
5131
|
+
const variants = generateSearchVariants(args.query, args?.strip, args?.prefix);
|
|
5132
|
+
const perPage = args?.per_page || 10;
|
|
5133
|
+
|
|
5134
|
+
const results = await Promise.allSettled(
|
|
5135
|
+
variants.map(v => client.searchMedia(v, perPage))
|
|
5136
|
+
);
|
|
5137
|
+
|
|
5138
|
+
const seenIds = new Set();
|
|
5139
|
+
const hits = [];
|
|
5140
|
+
for (let i = 0; i < results.length; i++) {
|
|
5141
|
+
if (results[i].status !== 'fulfilled') continue;
|
|
5142
|
+
for (const item of results[i].value) {
|
|
5143
|
+
if (seenIds.has(item.id)) continue;
|
|
5144
|
+
seenIds.add(item.id);
|
|
5145
|
+
hits.push({
|
|
5146
|
+
id: item.id,
|
|
5147
|
+
title: item.title?.rendered || '',
|
|
5148
|
+
alt: item.alt_text || '',
|
|
5149
|
+
url: item.source_url || '',
|
|
5150
|
+
width: item.media_details?.width || null,
|
|
5151
|
+
height: item.media_details?.height || null,
|
|
5152
|
+
matched: variants[i],
|
|
5153
|
+
});
|
|
5154
|
+
}
|
|
5155
|
+
}
|
|
5156
|
+
|
|
5157
|
+
let text = `🔍 Search: "${args.query}"\n`;
|
|
5158
|
+
text += `🔄 Variants (${variants.length}): ${variants.join(', ')}\n\n`;
|
|
5159
|
+
|
|
5160
|
+
if (hits.length === 0) {
|
|
5161
|
+
text += '❌ No results found.';
|
|
5162
|
+
} else {
|
|
5163
|
+
text += `📷 ${hits.length} result(s):\n\n`;
|
|
5164
|
+
for (const h of hits) {
|
|
5165
|
+
text += ` [ID: ${h.id}] ${h.title}\n`;
|
|
5166
|
+
text += ` Alt: ${h.alt}\n`;
|
|
5167
|
+
text += ` URL: ${h.url}\n`;
|
|
5168
|
+
if (h.width && h.height) text += ` Size: ${h.width}x${h.height}\n`;
|
|
5169
|
+
text += ` Matched: "${h.matched}"\n\n`;
|
|
5170
|
+
}
|
|
5171
|
+
}
|
|
5172
|
+
|
|
5173
|
+
return { content: [{ type: "text", text }] };
|
|
5174
|
+
}
|
|
5175
|
+
|
|
4338
5176
|
case "list_connections": {
|
|
4339
5177
|
const conns = registry.list();
|
|
4340
5178
|
const text = conns.map(c => {
|
|
@@ -4375,6 +5213,101 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4375
5213
|
return { content: [{ type: "text", text: `✅ フィードバックを送信しました` }] };
|
|
4376
5214
|
}
|
|
4377
5215
|
|
|
5216
|
+
case "search_asp_link": {
|
|
5217
|
+
let client;
|
|
5218
|
+
try {
|
|
5219
|
+
client = registry.get(args?.site);
|
|
5220
|
+
} catch (e) {
|
|
5221
|
+
return { content: [{ type: "text", text: `❌ ${e.message}` }], isError: true };
|
|
5222
|
+
}
|
|
5223
|
+
|
|
5224
|
+
const slug = args?.slug?.trim() || undefined;
|
|
5225
|
+
const search = args?.search?.trim() || undefined;
|
|
5226
|
+
|
|
5227
|
+
if (slug && search) {
|
|
5228
|
+
return { content: [{ type: "text", text: "❌ slug と search は同時に指定できません。どちらか一方を使用してください。" }], isError: true };
|
|
5229
|
+
}
|
|
5230
|
+
|
|
5231
|
+
let result;
|
|
5232
|
+
try {
|
|
5233
|
+
result = await client.searchAspLinks({
|
|
5234
|
+
search,
|
|
5235
|
+
slug,
|
|
5236
|
+
per_page: args?.per_page,
|
|
5237
|
+
page: args?.page,
|
|
5238
|
+
});
|
|
5239
|
+
} catch (e) {
|
|
5240
|
+
return { content: [{ type: "text", text: `❌ ASPリンク検索エラー: ${e.message}` }], isError: true };
|
|
5241
|
+
}
|
|
5242
|
+
|
|
5243
|
+
const items = result.items || [];
|
|
5244
|
+
if (items.length === 0) {
|
|
5245
|
+
return { content: [{ type: "text", text: "🔗 該当するASPリンクはありません。" }] };
|
|
5246
|
+
}
|
|
5247
|
+
|
|
5248
|
+
const list = items.map(item => {
|
|
5249
|
+
const lines = [
|
|
5250
|
+
` [${item.id}] ${item.title}`,
|
|
5251
|
+
` 案件ID: ${item.data_id} | slug: ${item.slug}`,
|
|
5252
|
+
];
|
|
5253
|
+
if (item.asp_url) lines.push(` ASP URL: ${item.asp_url}`);
|
|
5254
|
+
if (item.asp_url_sp) lines.push(` ASP URL(B): ${item.asp_url_sp}`);
|
|
5255
|
+
if (item.direct_url) lines.push(` 直リンク: ${item.direct_url}`);
|
|
5256
|
+
lines.push(` tag: ${item.tag}`);
|
|
5257
|
+
return lines.join('\n');
|
|
5258
|
+
}).join('\n\n');
|
|
5259
|
+
|
|
5260
|
+
let text = `🔗 ASPリンク検索結果 (${items.length}件 / 全${result.total}件)\n\n${list}`;
|
|
5261
|
+
if (result.total_pages > 1) {
|
|
5262
|
+
text += `\n\n📄 ページ: ${result.page} / ${result.total_pages}`;
|
|
5263
|
+
}
|
|
5264
|
+
|
|
5265
|
+
return { content: [{ type: "text", text }] };
|
|
5266
|
+
}
|
|
5267
|
+
|
|
5268
|
+
case "register_asp_link": {
|
|
5269
|
+
let client;
|
|
5270
|
+
try {
|
|
5271
|
+
client = registry.get(args?.site);
|
|
5272
|
+
} catch (e) {
|
|
5273
|
+
return { content: [{ type: "text", text: `❌ ${e.message}` }], isError: true };
|
|
5274
|
+
}
|
|
5275
|
+
|
|
5276
|
+
if (!args?.title || !args?.slug || !args?.asp_url) {
|
|
5277
|
+
return { content: [{ type: "text", text: "❌ title, slug, asp_url は必須です。" }], isError: true };
|
|
5278
|
+
}
|
|
5279
|
+
|
|
5280
|
+
let result;
|
|
5281
|
+
try {
|
|
5282
|
+
result = await client.createAspLink({
|
|
5283
|
+
title: args.title,
|
|
5284
|
+
slug: args.slug,
|
|
5285
|
+
asp_id: args?.asp_id,
|
|
5286
|
+
asp_name: args?.asp_name,
|
|
5287
|
+
asp_url: args.asp_url,
|
|
5288
|
+
asp_url_sp: args?.asp_url_sp,
|
|
5289
|
+
direct_url: args?.direct_url,
|
|
5290
|
+
});
|
|
5291
|
+
} catch (e) {
|
|
5292
|
+
return { content: [{ type: "text", text: `❌ ASPリンク登録エラー: ${e.message}` }], isError: true };
|
|
5293
|
+
}
|
|
5294
|
+
|
|
5295
|
+
const lines = [
|
|
5296
|
+
`✅ ASPリンク登録完了`,
|
|
5297
|
+
``,
|
|
5298
|
+
` ID: ${result.id}`,
|
|
5299
|
+
` タイトル: ${result.title}`,
|
|
5300
|
+
` slug: ${result.slug}`,
|
|
5301
|
+
` 案件ID: ${result.data_id}`,
|
|
5302
|
+
];
|
|
5303
|
+
if (result.asp_url) lines.push(` ASP URL: ${result.asp_url}`);
|
|
5304
|
+
if (result.asp_url_sp) lines.push(` ASP URL(B): ${result.asp_url_sp}`);
|
|
5305
|
+
if (result.direct_url) lines.push(` 直リンク: ${result.direct_url}`);
|
|
5306
|
+
lines.push(` tag: ${result.tag}`);
|
|
5307
|
+
|
|
5308
|
+
return { content: [{ type: "text", text: lines.join('\n') }] };
|
|
5309
|
+
}
|
|
5310
|
+
|
|
4378
5311
|
default:
|
|
4379
5312
|
throw new Error(`Unknown tool: ${name}`);
|
|
4380
5313
|
} })();
|