html2pptx-local-mcp 1.1.17 → 1.1.19
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/app/docs/content.js +76 -60
- package/cli/dist/commands/edit.d.ts +1 -0
- package/cli/dist/commands/edit.js +10 -8
- package/cli/dist/index.js +1 -1
- package/cli/package.json +1 -1
- package/lib/local-slide-editor-launcher.js +31 -2
- package/lib/pptx-studio-mcp-core.js +30 -19
- package/mcp/pptx-studio-mcp-server.mjs +48 -13
- package/package.json +1 -1
- package/scripts/install-mcp.mjs +136 -264
package/app/docs/content.js
CHANGED
|
@@ -185,8 +185,8 @@ npx skills add https://html2pptx.app --list
|
|
|
185
185
|
# More agents / multiple targets: use the interactive selector
|
|
186
186
|
npx skills add https://html2pptx.app
|
|
187
187
|
|
|
188
|
-
#
|
|
189
|
-
npx -y html2pptx-local-mcp@latest html2pptx-install-mcp claude`;
|
|
188
|
+
# Claude Code users: run this one line
|
|
189
|
+
npx -y -p html2pptx-local-mcp@latest html2pptx-install-mcp claude`;
|
|
190
190
|
|
|
191
191
|
const SKILL_INSTALL_COMMAND_JA = `# 使うエージェントのコマンドを選択
|
|
192
192
|
# Claude Code
|
|
@@ -207,8 +207,8 @@ npx skills add https://html2pptx.app --list
|
|
|
207
207
|
# その他のエージェント / 複数指定は対話形式で選択
|
|
208
208
|
npx skills add https://html2pptx.app
|
|
209
209
|
|
|
210
|
-
#
|
|
211
|
-
npx -y html2pptx-local-mcp@latest html2pptx-install-mcp claude`;
|
|
210
|
+
# Claude Codeユーザーはこの1行でOK
|
|
211
|
+
npx -y -p html2pptx-local-mcp@latest html2pptx-install-mcp claude`;
|
|
212
212
|
|
|
213
213
|
const SKILL_INVOCATION_EXAMPLE = `# 例: Claude Code でスライド作成 → PPTX出力
|
|
214
214
|
|
|
@@ -239,12 +239,12 @@ const SKILL_AVAILABLE_LIST = [
|
|
|
239
239
|
{
|
|
240
240
|
name: 'edit-slide',
|
|
241
241
|
description: 'ローカルHTMLスライドをlocalhost上のvisual editorで開き、localhost bridge 経由で同じファイルに保存。',
|
|
242
|
-
capabilities: ['
|
|
242
|
+
capabilities: ['html2pptx edit <file>', 'PowerPoint風UIで視覚編集', 'ローカルHTMLへの自動保存', '変更検知と競合防止'],
|
|
243
243
|
},
|
|
244
244
|
];
|
|
245
245
|
|
|
246
|
-
const MCP_INSTALL_EXAMPLE = `#
|
|
247
|
-
npx -y html2pptx-local-mcp@latest html2pptx-install-mcp claude`;
|
|
246
|
+
const MCP_INSTALL_EXAMPLE = `# Claude Codeユーザーはこの1行でOK
|
|
247
|
+
npx -y -p html2pptx-local-mcp@latest html2pptx-install-mcp claude`;
|
|
248
248
|
|
|
249
249
|
const MCP_CONFIG_EXAMPLE = `// 方法2: 設定ファイルに手動追加
|
|
250
250
|
|
|
@@ -254,13 +254,13 @@ const MCP_CONFIG_EXAMPLE = `// 方法2: 設定ファイルに手動追加
|
|
|
254
254
|
|
|
255
255
|
{
|
|
256
256
|
"mcpServers": {
|
|
257
|
-
"html2pptx
|
|
258
|
-
"
|
|
259
|
-
"
|
|
260
|
-
"
|
|
261
|
-
"
|
|
262
|
-
|
|
263
|
-
|
|
257
|
+
"html2pptx": {
|
|
258
|
+
"command": "npx",
|
|
259
|
+
"args": ["-y", "-p", "html2pptx-local-mcp@latest", "html2pptx-mcp"],
|
|
260
|
+
"env": {
|
|
261
|
+
"PPTX_STUDIO_API_KEY": "sk_live_xxxx",
|
|
262
|
+
"PPTX_STUDIO_BASE_URL": "http://127.0.0.1:<app-port>"
|
|
263
|
+
}
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
266
|
}`;
|
|
@@ -495,7 +495,7 @@ export const DOCS_COPY = {
|
|
|
495
495
|
{
|
|
496
496
|
title: 'MCP (Model Context Protocol)',
|
|
497
497
|
icon: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="2"/><path d="M16.24 7.76a6 6 0 010 8.49m-8.48-.01a6 6 0 010-8.49m11.31-2.82a10 10 0 010 14.14m-14.14 0a10 10 0 010-14.14"/></svg>',
|
|
498
|
-
body: 'Expose the backend to AI agents via the MCP protocol. Use the remote HTTP endpoint at /mcp for export, docs, catalog, and template publishing tools; use local stdio MCP
|
|
498
|
+
body: 'Expose the backend to AI agents via the MCP protocol. Use the remote HTTP endpoint at /mcp for export, docs, catalog, and template publishing tools; use local stdio MCP only for opening local HTML in edit-slide. HTML template drafts must be created through remote MCP after validation.',
|
|
499
499
|
},
|
|
500
500
|
],
|
|
501
501
|
overviewCssTitle: 'Supported CSS Features',
|
|
@@ -747,8 +747,8 @@ export const DOCS_COPY = {
|
|
|
747
747
|
skillsTitle: 'Skills Integration',
|
|
748
748
|
skillsSubtitle: 'What are Skills',
|
|
749
749
|
skillsDescription:
|
|
750
|
-
'Skills are packaged capabilities that extend AI coding agents with domain-specific knowledge and workflows. The html2pptx.app skill teaches your agent how to author slide-safe HTML, validate it against the PPTX conversion contract, optionally open the local visual editor, export through
|
|
751
|
-
skillsHowItWorks: 'The skill bundles four core capabilities: (1) HTML authoring knowledge -- the rules for writing HTML/CSS that converts cleanly to editable PowerPoint, (2) MCP-based export automation -- connecting to the remote html2pptx.app MCP server to create jobs, poll status, and retrieve results, (3) local visual editing -- opening edit-slide through a localhost bridge when the user wants to inspect or tweak the HTML before export, and (4) template publishing -- requiring HTML drafts to go through the remote MCP validate/publish loop. The agent should ask before adding a local MCP server because that changes the user’s MCP configuration.',
|
|
750
|
+
'Skills are packaged capabilities that extend AI coding agents with domain-specific knowledge and workflows. The html2pptx.app skill teaches your agent how to author slide-safe HTML, validate it against the PPTX conversion contract, optionally open the local visual editor, export through remote or local MCP workflows, and publish HTML template drafts through its built-in remote MCP publishing workflow. Install once, and your agent can convert natural language instructions into production-ready PowerPoint files or creator-owned HTML drafts.',
|
|
751
|
+
skillsHowItWorks: 'The skill bundles four core capabilities: (1) HTML authoring knowledge -- the rules for writing HTML/CSS that converts cleanly to editable PowerPoint, (2) MCP-based export automation -- connecting to the remote html2pptx.app MCP server or a local stdio MCP server to create jobs, poll status, and retrieve results, (3) local visual editing -- opening edit-slide through a localhost bridge when the user wants to inspect or tweak the HTML before export, and (4) template publishing -- requiring HTML drafts to go through the remote MCP validate/publish loop. The agent should ask before adding a local MCP server because that changes the user’s MCP configuration.',
|
|
752
752
|
skillsCompatibility: 'Works with 18+ AI agents including Claude Code, Cursor, GitHub Copilot, Windsurf, Cline, Codex, and more. Use the skills CLI command for the specific agent you use, or the interactive selector for other supported agents.',
|
|
753
753
|
skillsWorkflowTitle: 'Workflow',
|
|
754
754
|
skillsWorkflow: [
|
|
@@ -756,24 +756,31 @@ export const DOCS_COPY = {
|
|
|
756
756
|
'Agent reads the skill definition to understand the html2pptx.app HTML contract',
|
|
757
757
|
'Agent generates slide-safe HTML with .slide class elements and explicit dimensions. 1600x900 is the default example',
|
|
758
758
|
'Agent validates the markup against the conversion contract',
|
|
759
|
-
'If visual review is needed, the agent opens the local edit-slide editor through local stdio MCP, then re-reads the edited HTML from disk',
|
|
760
|
-
'Agent connects to remote MCP and calls html2pptx_create_export_job',
|
|
759
|
+
'If visual review is needed, the agent opens the local edit-slide editor through the CLI or local stdio MCP, then re-reads the edited HTML from disk',
|
|
760
|
+
'Agent connects to remote MCP or local stdio MCP and calls html2pptx_create_export_job',
|
|
761
761
|
'Agent polls with html2pptx_wait_for_export_job until completed',
|
|
762
762
|
'If the user wants to create an HTML template draft, the agent uses the html2pptx skill and remote MCP: infer title/tags from the HTML, run AI security preflight, validate, fix errors, save a draft, then return draftUrl for dashboard review',
|
|
763
763
|
'Agent returns the completed job summary to the user',
|
|
764
764
|
],
|
|
765
765
|
skillsLocalEditorTitle: 'Local Visual Editor',
|
|
766
766
|
skillsLocalEditorLead:
|
|
767
|
-
'The edit-slide skill lets an agent open a local HTML slide deck in a PowerPoint-style visual editor served from an available loopback origin such as http://localhost:<port>. Hosted edit-slide is not allowed for local file editing. The local dev script chooses a free port and registers it in .html2pptx/edit-slide/editor-server.json inside the current project. The deck file stays on the user machine and is read/written through a localhost bridge started by the local stdio MCP tool. If the local MCP server is not already configured, the agent should explain that it will change MCP settings and ask the user before adding it.',
|
|
768
|
-
skillsLocalEditorCode: `#
|
|
769
|
-
|
|
767
|
+
'The edit-slide skill lets an agent open a local HTML slide deck in a PowerPoint-style visual editor served from an available loopback origin such as http://localhost:<port>. Hosted edit-slide is not allowed for local file editing. The local dev script chooses a free port and registers it in .html2pptx/edit-slide/editor-server.json inside the current project. The deck file stays on the user machine and is read/written through a localhost bridge started by the CLI or by the local stdio MCP tool. If the local MCP server is not already configured, the agent should explain that it will change MCP settings and ask the user before adding it.',
|
|
768
|
+
skillsLocalEditorCode: `# From the project that contains your slide HTML
|
|
769
|
+
node scripts/dev-studio.mjs
|
|
770
|
+
npx --yes https://html2pptx.app/downloads/html2pptx-cli-0.4.0.tgz edit ./html2pptx/slides.html
|
|
771
|
+
|
|
772
|
+
# If html2pptx-cli@0.4.0+ is available from npm
|
|
773
|
+
npx --yes html2pptx-cli edit ./html2pptx/slides.html
|
|
774
|
+
|
|
775
|
+
# If the CLI is installed globally
|
|
776
|
+
html2pptx edit ./html2pptx/slides.html
|
|
770
777
|
|
|
771
|
-
#
|
|
772
|
-
|
|
778
|
+
# Print the URL instead of opening a browser
|
|
779
|
+
html2pptx edit ./html2pptx/slides.html --no-open`,
|
|
773
780
|
skillsLocalEditorFlowTitle: 'How the local editor flow works',
|
|
774
781
|
skillsLocalEditorFlow: [
|
|
775
|
-
{ label: '1. Choose remote or local MCP', detail: 'Use remote MCP for normal PPTX export and catalog/docs tools. Use local stdio MCP
|
|
776
|
-
{ label: '2. Start bridge', detail: '`html2pptx_open_local_slide_editor` starts a tiny HTTP server on `127.0.0.1`. Unless a port is specified, the OS assigns a free port. Access is scoped to the current working directory.' },
|
|
782
|
+
{ label: '1. Choose remote or local MCP', detail: 'Use remote MCP for normal PPTX export and catalog/docs tools. Use local stdio MCP only when the agent must open or edit a local `.html` / `.htm` file through edit-slide. If local MCP is missing, ask the user before installing it.' },
|
|
783
|
+
{ label: '2. Start bridge', detail: '`html2pptx edit <file>` or `html2pptx_open_local_slide_editor` starts a tiny HTTP server on `127.0.0.1`. Unless a port is specified, the OS assigns a free port. Access is scoped to the current working directory.' },
|
|
777
784
|
{ label: '3. Open editor', detail: 'The command opens `http://localhost:<editor-port>/edit-slide?file=...&bridge=http://127.0.0.1:<bridge-port>#bridgeToken=...`. The one-time token is kept in the URL fragment and the editor removes it from the address bar after startup.' },
|
|
778
785
|
{ label: '4. Load local file', detail: 'The browser editor presents the token and fetches the selected `.html` or `.htm` file from the localhost bridge. No marketplace draft or public page is created.' },
|
|
779
786
|
{ label: '5. Edit visually', detail: 'Users can click slide elements and adjust text, typography, color, size, padding, margin, radius, border, opacity, and other properties from the right panel.' },
|
|
@@ -793,10 +800,10 @@ npx -y html2pptx-local-mcp@latest html2pptx-install-mcp claude`,
|
|
|
793
800
|
],
|
|
794
801
|
skillsLocalEditorTroubleshootingTitle: 'Common issues',
|
|
795
802
|
skillsLocalEditorTroubleshooting: [
|
|
796
|
-
{ issue: 'The
|
|
797
|
-
{ issue: '`
|
|
803
|
+
{ issue: 'The command is not found', fix: 'Start `node scripts/dev-studio.mjs`, then use `npx --yes https://html2pptx.app/downloads/html2pptx-cli-0.4.0.tgz edit ./path/to/slides.html`, or install `html2pptx-cli@0.4.0+` globally.' },
|
|
804
|
+
{ issue: '`npx` is not available', fix: 'Use the already-configured local stdio MCP tool `html2pptx_open_local_slide_editor`, or ask before adding local MCP. If local MCP is unavailable, install/expose npm or a global `html2pptx` CLI first.' },
|
|
798
805
|
{ issue: 'The editor opens but the deck does not load', fix: 'Confirm the path is relative to the directory where the command was run and the file extension is `.html` or `.htm`.' },
|
|
799
|
-
{ issue: 'Changes do not save', fix: 'Keep the
|
|
806
|
+
{ issue: 'Changes do not save', fix: 'Keep the terminal running. Closing the `html2pptx edit` process stops the localhost bridge.' },
|
|
800
807
|
{ issue: 'The agent wants to add local MCP', fix: 'Confirm with the user first. Local MCP is optional and only needed for MCP-driven local edit-slide sessions; remote MCP can still export PPTX without local file access.' },
|
|
801
808
|
{ issue: 'Another tab is read-only', fix: 'The editor uses a local tab lock to avoid two tabs writing to the same file. Use the active tab or click the transfer edit control.' },
|
|
802
809
|
],
|
|
@@ -841,9 +848,9 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
|
|
|
841
848
|
mcpTitle: 'MCP Integration',
|
|
842
849
|
mcpSubtitle: 'What is MCP',
|
|
843
850
|
mcpDescription:
|
|
844
|
-
'MCP (Model Context Protocol) is an open protocol that exposes backend capabilities to AI agents through a standardized tool interface. html2pptx.app supports two MCP surfaces: the remote HTTP MCP endpoint at /mcp for export, usage, docs, templates, catalog, and HTML template publishing workflows, and the local stdio MCP server for local edit-slide sessions through a localhost bridge. Use remote MCP when the agent needs to convert HTML to PPTX or create an HTML template draft. Use local stdio MCP
|
|
851
|
+
'MCP (Model Context Protocol) is an open protocol that exposes backend capabilities to AI agents through a standardized tool interface. html2pptx.app supports two MCP surfaces: the remote HTTP MCP endpoint at /mcp for export, usage, docs, templates, catalog, and HTML template publishing workflows, and the local stdio MCP server for export tools plus local edit-slide sessions through a localhost bridge. Use remote MCP when the agent needs to convert HTML to PPTX or create an HTML template draft. Use local stdio MCP only when the agent must open, preview, or edit a local HTML file on the user machine; local MCP does not publish templates.',
|
|
845
852
|
mcpSetupTitle: 'Installation & Setup',
|
|
846
|
-
mcpSetupLead: 'Claude Code users can run
|
|
853
|
+
mcpSetupLead: 'For Claude Code, users can run one command to register both remote export tools and local edit-slide. Remote MCP follows the hosted service; local MCP uses html2pptx-local-mcp@latest so future starts pick up the newest published package.',
|
|
847
854
|
mcpEditorTabs: [
|
|
848
855
|
{
|
|
849
856
|
id: 'claude-code',
|
|
@@ -851,19 +858,20 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
|
|
|
851
858
|
icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/claude/default.svg',
|
|
852
859
|
recommended: true,
|
|
853
860
|
steps: [
|
|
854
|
-
{ title: '
|
|
855
|
-
{ title: 'Manual setup
|
|
856
|
-
{ title: '
|
|
861
|
+
{ title: 'Claude Code users: run this one line', body: 'This registers remote export as `html2pptx`, then writes local edit-slide as `html2pptx-local` directly into Claude Code user config.', code: `npx -y -p html2pptx-local-mcp@latest html2pptx-install-mcp claude` },
|
|
862
|
+
{ title: 'Manual remote setup', body: 'If you only need hosted export tools, add the remote MCP server directly.', code: `claude mcp add --scope user --transport http html2pptx https://html2pptx.app/mcp` },
|
|
863
|
+
{ title: 'Compatible local edit-slide setup', body: 'If Claude Code stdio registration is unreliable, use the installer because it writes the local server entry directly.', code: `npx -y -p html2pptx-local-mcp@latest html2pptx-install-mcp claude` },
|
|
857
864
|
],
|
|
858
|
-
tip: '
|
|
865
|
+
tip: 'The installer registers the remote MCP with user scope and writes the local stdio MCP directly to Claude Code user config for better compatibility.',
|
|
859
866
|
},
|
|
860
867
|
{
|
|
861
868
|
id: 'codex',
|
|
862
869
|
label: 'Codex',
|
|
863
870
|
icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/openai/light.svg',
|
|
864
871
|
steps: [
|
|
865
|
-
{ title: 'Run the CLI command (recommended)', body: '
|
|
866
|
-
{ title: '
|
|
872
|
+
{ title: 'Run the CLI command (recommended)', body: 'Add the hosted remote MCP server. OAuth authentication will start automatically.', code: `codex mcp add html2pptx --url https://html2pptx.app/mcp` },
|
|
873
|
+
{ title: 'Manual remote setup', body: 'If you only need hosted export tools, add the remote MCP server directly.', code: `codex mcp add html2pptx --url https://html2pptx.app/mcp` },
|
|
874
|
+
{ title: 'Optional local stdio MCP for edit-slide', body: 'Use this only when Codex needs to launch local edit-slide for a `.html` file. It runs from the published package, so no repository checkout is required.', code: `codex mcp add html2pptx-local -- npx -y -p html2pptx-local-mcp@latest html2pptx-mcp` },
|
|
867
875
|
{ title: 'Manual setup via codex.json', body: 'Alternatively, create or edit codex.json in your project root.', code: `{\n "mcpServers": {\n "html2pptx": {\n "type": "url",\n "url": "https://html2pptx.app/mcp"\n }\n }\n}` },
|
|
868
876
|
],
|
|
869
877
|
},
|
|
@@ -1583,8 +1591,8 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
|
|
|
1583
1591
|
skillsTitle: 'Skills統合ガイド',
|
|
1584
1592
|
skillsSubtitle: 'Skillsとは',
|
|
1585
1593
|
skillsDescription:
|
|
1586
|
-
'Skillsは、AIコーディングエージェントにドメイン固有の知識とワークフローを追加するパッケージ機能です。html2pptx.app のスキルをインストールすると、エージェントがスライド用HTMLの作成方法を理解し、PPTX変換契約に対する検証を行い、必要に応じてローカル Visual Editor を開き、remote MCP のワークフローでエクスポートし、内蔵のremote MCP公開ワークフローでHTMLテンプレートdraftを作成できます。自然言語の指示だけで、本番品質のPowerPointファイルや作成者所有のHTML draftを生成できます。',
|
|
1587
|
-
skillsHowItWorks: 'スキルには4つのコア機能がバンドルされています: (1) HTMLオーサリング知識 -- 高品質なPowerPointに変換されるHTML/CSSの書き方ルール、(2) MCPベースのエクスポート自動化 -- remote MCP に接続してジョブ作成・ステータスポーリング・結果取得を行う仕組み、(3) ローカル Visual Editor -- ユーザーがPPTX出力前にHTMLを目視確認・微調整したい場合に edit-slide を localhost bridge 経由で開く仕組み、(4) テンプレート公開 -- HTML draft を remote MCP の validate/publish ループに限定する仕組みです。local MCP の追加はユーザーのMCP設定を変更するため、エージェントは必ず事前に確認します。',
|
|
1594
|
+
'Skillsは、AIコーディングエージェントにドメイン固有の知識とワークフローを追加するパッケージ機能です。html2pptx.app のスキルをインストールすると、エージェントがスライド用HTMLの作成方法を理解し、PPTX変換契約に対する検証を行い、必要に応じてローカル Visual Editor を開き、remote MCP または local MCP のワークフローでエクスポートし、内蔵のremote MCP公開ワークフローでHTMLテンプレートdraftを作成できます。自然言語の指示だけで、本番品質のPowerPointファイルや作成者所有のHTML draftを生成できます。',
|
|
1595
|
+
skillsHowItWorks: 'スキルには4つのコア機能がバンドルされています: (1) HTMLオーサリング知識 -- 高品質なPowerPointに変換されるHTML/CSSの書き方ルール、(2) MCPベースのエクスポート自動化 -- remote MCP または local stdio MCP に接続してジョブ作成・ステータスポーリング・結果取得を行う仕組み、(3) ローカル Visual Editor -- ユーザーがPPTX出力前にHTMLを目視確認・微調整したい場合に edit-slide を localhost bridge 経由で開く仕組み、(4) テンプレート公開 -- HTML draft を remote MCP の validate/publish ループに限定する仕組みです。local MCP の追加はユーザーのMCP設定を変更するため、エージェントは必ず事前に確認します。',
|
|
1588
1596
|
skillsCompatibility: 'Claude Code、Cursor、GitHub Copilot、Windsurf、Cline、Codex 等 18以上のAIエージェントに対応。使うエージェントに対応する skills CLI コマンドを選ぶか、その他の対応エージェントは対話形式で選択します。',
|
|
1589
1597
|
skillsWorkflowTitle: 'ワークフロー',
|
|
1590
1598
|
skillsWorkflow: [
|
|
@@ -1592,24 +1600,31 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
|
|
|
1592
1600
|
'エージェントがスキル定義を読み取り、html2pptx.appのHTML契約を理解',
|
|
1593
1601
|
'エージェントが .slide クラス要素と明示サイズ付きのスライドセーフHTMLを生成。1600x900 はデフォルト例',
|
|
1594
1602
|
'エージェントが変換契約に対してマークアップを検証',
|
|
1595
|
-
'目視確認が必要な場合は、local stdio MCP で edit-slide を開き、編集後のHTMLをディスクから再読み込み',
|
|
1596
|
-
'エージェントが remote MCP に接続し html2pptx_create_export_job を呼び出し',
|
|
1603
|
+
'目視確認が必要な場合は、CLI または local stdio MCP で edit-slide を開き、編集後のHTMLをディスクから再読み込み',
|
|
1604
|
+
'エージェントが remote MCP または local stdio MCP に接続し html2pptx_create_export_job を呼び出し',
|
|
1597
1605
|
'エージェントが html2pptx_wait_for_export_job で完了をポーリング',
|
|
1598
1606
|
'HTMLテンプレートdraftを作る場合は、html2pptx スキルと remote MCP でHTMLから題名・タグを推定し、AIセキュリティ事前診断、検証、エラー修正、下書き保存を行い、draftUrlを返却',
|
|
1599
1607
|
'エージェントが completed 状態のジョブサマリーをユーザーに返却',
|
|
1600
1608
|
],
|
|
1601
1609
|
skillsLocalEditorTitle: 'ローカル Visual Editor',
|
|
1602
1610
|
skillsLocalEditorLead:
|
|
1603
|
-
'edit-slide スキルを使うと、エージェントが作成したローカルHTMLスライドを、http://localhost:<port> のような空き loopback origin で動くPowerPoint風のノーコード編集画面で開けます。ローカルファイル編集に hosted edit-slide は使えません。ローカル dev script が空きポートを選び、現在のプロジェクト内の .html2pptx/edit-slide/editor-server.json に登録します。スライドHTMLファイルはユーザーのPC上に残り、CLI または local stdio MCP が起動する localhost bridge 経由で読み書きされます。local MCP
|
|
1604
|
-
skillsLocalEditorCode: `#
|
|
1605
|
-
|
|
1611
|
+
'edit-slide スキルを使うと、エージェントが作成したローカルHTMLスライドを、http://localhost:<port> のような空き loopback origin で動くPowerPoint風のノーコード編集画面で開けます。ローカルファイル編集に hosted edit-slide は使えません。ローカル dev script が空きポートを選び、現在のプロジェクト内の .html2pptx/edit-slide/editor-server.json に登録します。スライドHTMLファイルはユーザーのPC上に残り、CLI または local stdio MCP が起動する localhost bridge 経由で読み書きされます。local MCP が未設定の場合、エージェントはMCP設定を変更することを説明し、ユーザー確認を取ってから追加します。',
|
|
1612
|
+
skillsLocalEditorCode: `# スライドHTMLがあるプロジェクトで実行
|
|
1613
|
+
node scripts/dev-studio.mjs
|
|
1614
|
+
npx --yes https://html2pptx.app/downloads/html2pptx-cli-0.4.0.tgz edit ./html2pptx/slides.html
|
|
1615
|
+
|
|
1616
|
+
# npm から html2pptx-cli@0.4.0+ を使える場合
|
|
1617
|
+
npx --yes html2pptx-cli edit ./html2pptx/slides.html
|
|
1618
|
+
|
|
1619
|
+
# CLIをグローバルインストール済みの場合
|
|
1620
|
+
html2pptx edit ./html2pptx/slides.html
|
|
1606
1621
|
|
|
1607
|
-
#
|
|
1608
|
-
|
|
1622
|
+
# ブラウザを自動で開かず、URLだけ表示
|
|
1623
|
+
html2pptx edit ./html2pptx/slides.html --no-open`,
|
|
1609
1624
|
skillsLocalEditorFlowTitle: 'ローカル編集の仕組み',
|
|
1610
1625
|
skillsLocalEditorFlow: [
|
|
1611
|
-
{ label: '1. remote / local MCP を選ぶ', detail: '通常のPPTX出力、docs、template、usage 取得は remote MCP を使えます。ローカル `.html` / `.htm` を edit-slide
|
|
1612
|
-
{ label: '2. bridge を起動', detail: '`html2pptx_open_local_slide_editor` が `127.0.0.1` に小さなHTTPサーバーを起動します。ポートは指定しない限りOSが選ぶ空きポートになります。アクセス範囲はコマンドを実行したカレントディレクトリ内に限定されます。' },
|
|
1626
|
+
{ label: '1. remote / local MCP を選ぶ', detail: '通常のPPTX出力、docs、template、usage 取得は remote MCP を使えます。ローカル `.html` / `.htm` を edit-slide で開いて編集する必要がある場合だけ local stdio MCP を使います。local MCP が未設定なら、追加前にユーザーへ確認します。' },
|
|
1627
|
+
{ label: '2. bridge を起動', detail: '`html2pptx edit <file>` または `html2pptx_open_local_slide_editor` が `127.0.0.1` に小さなHTTPサーバーを起動します。ポートは指定しない限りOSが選ぶ空きポートになります。アクセス範囲はコマンドを実行したカレントディレクトリ内に限定されます。' },
|
|
1613
1628
|
{ label: '3. エディタを開く', detail: 'コマンドが `http://localhost:<editor-port>/edit-slide?file=...&bridge=http://127.0.0.1:<bridge-port>#bridgeToken=...` を開きます。ワンタイム token は URL fragment に置かれ、起動後にエディタがアドレスバーから消します。' },
|
|
1614
1629
|
{ label: '4. ローカルHTMLを読み込む', detail: 'ブラウザの編集画面が token を提示し、localhost bridge から対象の `.html` / `.htm` ファイルを取得します。マーケットプレイス下書きや公開ページは作成されません。' },
|
|
1615
1630
|
{ label: '5. ノーコードで編集', detail: 'スライド上の要素をクリックし、右パネルからテキスト、タイポグラフィ、色、サイズ、余白、角丸、ボーダー、不透明度などを調整できます。' },
|
|
@@ -1629,11 +1644,11 @@ npx -y html2pptx-local-mcp@latest html2pptx-install-mcp claude`,
|
|
|
1629
1644
|
],
|
|
1630
1645
|
skillsLocalEditorTroubleshootingTitle: 'よくあるつまずき',
|
|
1631
1646
|
skillsLocalEditorTroubleshooting: [
|
|
1632
|
-
{ issue: '
|
|
1633
|
-
{ issue: '`
|
|
1647
|
+
{ issue: 'コマンドが見つからない', fix: '`node scripts/dev-studio.mjs` を起動してから `npx --yes https://html2pptx.app/downloads/html2pptx-cli-0.4.0.tgz edit ./path/to/slides.html` を使うか、`html2pptx-cli@0.4.0+` をグローバルインストールしてください。' },
|
|
1648
|
+
{ issue: '`npx` が使えない', fix: 'すでに設定済みの local stdio MCP ツール `html2pptx_open_local_slide_editor` を使うか、local MCP を追加する前にユーザーへ確認してください。local MCP も使えない場合は、npm またはグローバル `html2pptx` CLI を先に使える状態にします。' },
|
|
1634
1649
|
{ issue: '画面は開くがスライドが読み込まれない', fix: 'コマンドを実行したディレクトリからの相対パスになっているか、拡張子が `.html` / `.htm` かを確認してください。' },
|
|
1635
|
-
{ issue: '編集内容が保存されない', fix: '
|
|
1636
|
-
{ issue: 'エージェントが local MCP を追加しようとする', fix: '先にユーザーへ確認してください。local MCP は MCP 経由でローカル edit-slide を開く場合だけ必要です。
|
|
1650
|
+
{ issue: '編集内容が保存されない', fix: 'ターミナルで起動している `html2pptx edit` を終了しないでください。bridge が止まると保存できません。' },
|
|
1651
|
+
{ issue: 'エージェントが local MCP を追加しようとする', fix: '先にユーザーへ確認してください。local MCP は MCP 経由でローカル edit-slide を開く場合だけ必要です。remote MCP だけでもPPTX出力はできます。' },
|
|
1637
1652
|
{ issue: '別タブが読み取り専用になる', fix: '同じHTMLを複数タブで編集して競合しないよう、エディタはタブロックを使います。編集権限のあるタブを使うか、画面上の移譲操作を行ってください。' },
|
|
1638
1653
|
],
|
|
1639
1654
|
skillsSetupTitle: 'セットアップ',
|
|
@@ -1677,9 +1692,9 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
|
|
|
1677
1692
|
mcpTitle: 'MCP統合ガイド',
|
|
1678
1693
|
mcpSubtitle: 'MCPとは',
|
|
1679
1694
|
mcpDescription:
|
|
1680
|
-
'MCP (Model Context Protocol) は、標準化されたツールインターフェースを通じてAIエージェントにバックエンド機能を公開するオープンプロトコルです。html2pptx.app は2種類のMCPサーフェスを提供します。remote HTTP MCP (`/mcp`) はエクスポート、usage、docs、templates、catalog、HTMLテンプレート公開に使います。local stdio MCP
|
|
1695
|
+
'MCP (Model Context Protocol) は、標準化されたツールインターフェースを通じてAIエージェントにバックエンド機能を公開するオープンプロトコルです。html2pptx.app は2種類のMCPサーフェスを提供します。remote HTTP MCP (`/mcp`) はエクスポート、usage、docs、templates、catalog、HTMLテンプレート公開に使います。local stdio MCP は同じエクスポート系ツールに加えて、localhost bridge 経由でローカルHTMLを edit-slide で開くために使います。HTMLをPPTXに変換する、またはHTMLテンプレートdraftを作るなら remote MCP、ユーザーPC上の `.html` を開いてプレビュー・編集するなら local MCP を選びます。local MCP はテンプレート公開には使いません。',
|
|
1681
1696
|
mcpSetupTitle: 'インストール & セットアップ',
|
|
1682
|
-
mcpSetupLead: 'Claude Code
|
|
1697
|
+
mcpSetupLead: 'Claude Codeユーザーは1コマンドで remote export と local edit-slide の両方を登録できます。remote MCP は hosted service 側の更新が反映され、local MCP は html2pptx-local-mcp@latest 経由で起動するため、次回起動時に新しい公開パッケージへ追従できます。',
|
|
1683
1698
|
mcpEditorTabs: [
|
|
1684
1699
|
{
|
|
1685
1700
|
id: 'claude-code',
|
|
@@ -1687,19 +1702,20 @@ echo 'HTML2PPTX_API_KEY=sk_live_xxxx' >> .env`,
|
|
|
1687
1702
|
icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/claude/default.svg',
|
|
1688
1703
|
recommended: true,
|
|
1689
1704
|
steps: [
|
|
1690
|
-
{ title: '
|
|
1691
|
-
{ title: '
|
|
1692
|
-
{ title: '
|
|
1705
|
+
{ title: 'Claude Codeユーザーはこの1行でOK', body: '`html2pptx` として remote export を登録し、`html2pptx-local` として local edit-slide を Claude Code の user config に直接書き込みます。', code: `npx -y -p html2pptx-local-mcp@latest html2pptx-install-mcp claude` },
|
|
1706
|
+
{ title: 'remote だけ手動セットアップ', body: 'PPTX出力などのホスト側ツールだけ必要な場合はこちらを使います。', code: `claude mcp add --scope user --transport http html2pptx https://html2pptx.app/mcp` },
|
|
1707
|
+
{ title: 'local edit-slide 互換セットアップ', body: 'Claude Code の stdio 登録が不安定な場合も、このインストーラーが local server 設定を直接書き込みます。', code: `npx -y -p html2pptx-local-mcp@latest html2pptx-install-mcp claude` },
|
|
1693
1708
|
],
|
|
1694
|
-
tip: '
|
|
1709
|
+
tip: 'このインストーラーは remote MCP を user scope で登録し、互換性のため local stdio MCP は Claude Code の user config に直接書き込みます。',
|
|
1695
1710
|
},
|
|
1696
1711
|
{
|
|
1697
1712
|
id: 'codex',
|
|
1698
1713
|
label: 'Codex',
|
|
1699
1714
|
icon: 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/openai/light.svg',
|
|
1700
1715
|
steps: [
|
|
1701
|
-
{ title: 'CLIコマンドで追加(推奨)', body: '
|
|
1702
|
-
{ title: '
|
|
1716
|
+
{ title: 'CLIコマンドで追加(推奨)', body: 'hosted remote MCP server を追加します。OAuth認証が自動的に開始されます。', code: `codex mcp add html2pptx --url https://html2pptx.app/mcp` },
|
|
1717
|
+
{ title: 'remote だけ手動セットアップ', body: 'PPTX出力などのホスト側ツールだけ必要な場合はこちらを使います。', code: `codex mcp add html2pptx --url https://html2pptx.app/mcp` },
|
|
1718
|
+
{ title: 'edit-slide 用 local stdio MCP(任意)', body: 'Codex がローカル `.html` を edit-slide で開く必要がある場合だけ追加します。公開パッケージから起動するため、リポジトリの checkout は不要です。', code: `codex mcp add html2pptx-local -- npx -y -p html2pptx-local-mcp@latest html2pptx-mcp` },
|
|
1703
1719
|
{ title: 'codex.json で手動セットアップ', body: 'コマンドが使えない場合は、プロジェクトルートの codex.json に以下を追加してください。', code: `{\n "mcpServers": {\n "html2pptx": {\n "type": "url",\n "url": "https://html2pptx.app/mcp"\n }\n }\n}` },
|
|
1704
1720
|
],
|
|
1705
1721
|
},
|
|
@@ -506,24 +506,26 @@ function bail(message, options) {
|
|
|
506
506
|
process.exit(1);
|
|
507
507
|
}
|
|
508
508
|
export async function editCommand(input, options = {}) {
|
|
509
|
+
const noOpen = options.noOpen === true || options.open === false;
|
|
510
|
+
const normalizedOptions = { ...options, noOpen };
|
|
509
511
|
if (!input) {
|
|
510
|
-
bail("HTML file path is required. Example: html2pptx edit ./html2pptx/slides.html",
|
|
512
|
+
bail("HTML file path is required. Example: html2pptx edit ./html2pptx/slides.html", normalizedOptions);
|
|
511
513
|
}
|
|
512
514
|
const root = await realpath(process.cwd());
|
|
513
515
|
const abs = resolve(root, input);
|
|
514
516
|
const rel = relativeToRoot(root, abs);
|
|
515
517
|
const ext = extname(abs).toLowerCase();
|
|
516
518
|
if (!ALLOWED_EXT.has(ext)) {
|
|
517
|
-
bail("Only .html/.htm files can be opened in the editor.",
|
|
519
|
+
bail("Only .html/.htm files can be opened in the editor.", normalizedOptions);
|
|
518
520
|
}
|
|
519
521
|
if (abs !== root && !abs.startsWith(root + sep)) {
|
|
520
|
-
bail("The file must be inside the current working directory.",
|
|
522
|
+
bail("The file must be inside the current working directory.", normalizedOptions);
|
|
521
523
|
}
|
|
522
524
|
try {
|
|
523
525
|
await stat(abs);
|
|
524
526
|
}
|
|
525
527
|
catch {
|
|
526
|
-
bail(`File not found: ${rel}`,
|
|
528
|
+
bail(`File not found: ${rel}`, normalizedOptions);
|
|
527
529
|
}
|
|
528
530
|
let baseUrl;
|
|
529
531
|
let requestedPort;
|
|
@@ -532,7 +534,7 @@ export async function editCommand(input, options = {}) {
|
|
|
532
534
|
requestedPort = parsePort(options.port);
|
|
533
535
|
}
|
|
534
536
|
catch (error) {
|
|
535
|
-
bail(error.message,
|
|
537
|
+
bail(error.message, normalizedOptions);
|
|
536
538
|
}
|
|
537
539
|
const ctx = {
|
|
538
540
|
root,
|
|
@@ -550,7 +552,7 @@ export async function editCommand(input, options = {}) {
|
|
|
550
552
|
}
|
|
551
553
|
const bridgeUrl = `http://127.0.0.1:${bridgePort}`;
|
|
552
554
|
const editorUrl = buildEditorUrl(baseUrl, rel, bridgeUrl, ctx.sessionToken);
|
|
553
|
-
if (
|
|
555
|
+
if (normalizedOptions.json) {
|
|
554
556
|
console.log(JSON.stringify({
|
|
555
557
|
success: true,
|
|
556
558
|
editorUrl: editorUrl.toString(),
|
|
@@ -562,7 +564,7 @@ export async function editCommand(input, options = {}) {
|
|
|
562
564
|
}
|
|
563
565
|
else {
|
|
564
566
|
p.log.success(`Local edit bridge listening on ${pc.cyan(bridgeUrl)} ${pc.dim("(session token required)")}`);
|
|
565
|
-
if (
|
|
567
|
+
if (normalizedOptions.noOpen) {
|
|
566
568
|
p.log.info(`Open in editor: ${pc.cyan(editorUrl.toString())}`);
|
|
567
569
|
}
|
|
568
570
|
else {
|
|
@@ -570,7 +572,7 @@ export async function editCommand(input, options = {}) {
|
|
|
570
572
|
}
|
|
571
573
|
p.log.info(pc.dim("Press Ctrl+C to stop the bridge."));
|
|
572
574
|
}
|
|
573
|
-
if (!
|
|
575
|
+
if (!normalizedOptions.noOpen) {
|
|
574
576
|
openUrl(editorUrl.toString());
|
|
575
577
|
}
|
|
576
578
|
stopOnSignal(server);
|
package/cli/dist/index.js
CHANGED
|
@@ -41,7 +41,7 @@ program
|
|
|
41
41
|
.action(convertCommand);
|
|
42
42
|
program
|
|
43
43
|
.command("edit")
|
|
44
|
-
.description("Open a local HTML
|
|
44
|
+
.description("Open a local slide HTML file in the html2pptx visual editor")
|
|
45
45
|
.argument("<input>", "Path to HTML file")
|
|
46
46
|
.option("--port <port>", "Local bridge port. Defaults to auto.")
|
|
47
47
|
.option("--no-open", "Print the editor URL without opening a browser")
|
package/cli/package.json
CHANGED
|
@@ -21,11 +21,22 @@ export function createLocalSlideEditorManager(options = {}) {
|
|
|
21
21
|
|
|
22
22
|
async function open(input = {}) {
|
|
23
23
|
const file = await resolveEditableFile(input.filePath, rootCwd);
|
|
24
|
+
const reuseExisting = input.reuseExisting !== false;
|
|
25
|
+
if (reuseExisting) {
|
|
26
|
+
const existing = findReusableSession(file);
|
|
27
|
+
if (existing) {
|
|
28
|
+
return {
|
|
29
|
+
...redactSessionForResponse(existing),
|
|
30
|
+
reused: true,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
24
35
|
const baseUrl = normalizeEditorBaseUrl(
|
|
25
36
|
input.baseUrl || process.env.HTML2PPTX_EDITOR_BASE_URL || await readRegisteredEditorBaseUrl(file.root),
|
|
26
37
|
);
|
|
27
38
|
const port = normalizePort(input.port, 0);
|
|
28
|
-
const openBrowser = input.openBrowser
|
|
39
|
+
const openBrowser = input.openBrowser === true;
|
|
29
40
|
const invocation = resolveCliInvocation(options);
|
|
30
41
|
const childArgs = [
|
|
31
42
|
...invocation.baseArgs,
|
|
@@ -78,7 +89,25 @@ export function createLocalSlideEditorManager(options = {}) {
|
|
|
78
89
|
|
|
79
90
|
const details = await waitForBridgeDetails(session, launchTimeoutMs);
|
|
80
91
|
session.details = details;
|
|
81
|
-
return
|
|
92
|
+
return {
|
|
93
|
+
...redactSessionForResponse(session),
|
|
94
|
+
reused: false,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function findReusableSession(file) {
|
|
99
|
+
for (const session of sessions.values()) {
|
|
100
|
+
if (
|
|
101
|
+
session.filePath === file.absolutePath &&
|
|
102
|
+
session.root === file.root &&
|
|
103
|
+
session.details &&
|
|
104
|
+
!session.child.killed &&
|
|
105
|
+
session.child.exitCode == null
|
|
106
|
+
) {
|
|
107
|
+
return session;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
82
111
|
}
|
|
83
112
|
|
|
84
113
|
async function stop(sessionId) {
|
|
@@ -416,7 +416,7 @@ export const LOCAL_TOOL_DEFINITIONS = [
|
|
|
416
416
|
name: 'html2pptx_open_local_slide_editor',
|
|
417
417
|
title: 'Open Local Slide Editor',
|
|
418
418
|
description:
|
|
419
|
-
'Local stdio MCP only. Start the
|
|
419
|
+
'Local stdio MCP only. Start the html2pptx CLI local edit bridge for a .html/.htm slide file inside the MCP server working directory, then return the tokenized editor URL. This does not publish or upload the HTML file.',
|
|
420
420
|
inputSchema: {
|
|
421
421
|
type: 'object',
|
|
422
422
|
properties: {
|
|
@@ -437,7 +437,11 @@ export const LOCAL_TOOL_DEFINITIONS = [
|
|
|
437
437
|
},
|
|
438
438
|
openBrowser: {
|
|
439
439
|
type: 'boolean',
|
|
440
|
-
description: 'When true
|
|
440
|
+
description: 'When true, open the editor in the browser. Defaults to false for MCP calls so agents can return one URL without spawning extra tabs.',
|
|
441
|
+
},
|
|
442
|
+
reuseExisting: {
|
|
443
|
+
type: 'boolean',
|
|
444
|
+
description: 'When true (default), reuse an active editor bridge for the same file instead of starting another bridge and browser tab.',
|
|
441
445
|
},
|
|
442
446
|
},
|
|
443
447
|
required: ['filePath'],
|
|
@@ -595,7 +599,7 @@ function buildPromptMessages(name, args) {
|
|
|
595
599
|
`3. Save the complete HTML document to a project-local .html file, preferably "${fileName}" or another clear path under html2pptx/.`,
|
|
596
600
|
'4. Open only a loopback editor UI. First ensure the local editor app is running with node scripts/dev-studio.mjs; it will choose an available port and register the editor URL under .html2pptx/edit-slide/editor-server.json in the current project.',
|
|
597
601
|
'5. Call html2pptx_open_local_slide_editor with { filePath: <path> }. Pass baseUrl only when the user supplied a specific loopback editor URL. Do not use https://html2pptx.app/edit-slide for local file editing.',
|
|
598
|
-
'6. If that local editor tool is not available,
|
|
602
|
+
'6. If that local editor tool is not available, do not pretend the editor was opened. Return the saved HTML path and the local-UI CLI fallback command: npx --yes https://html2pptx.app/downloads/html2pptx-cli-0.4.0.tgz edit <path>. If the CLI reports that the editor is not registered, start node scripts/dev-studio.mjs first.',
|
|
599
603
|
'7. Do not export PPTX from this prompt. In the editor, the export button should only show a prompt telling the user to ask Claude Code or another agent to use the html2pptx skills for PowerPoint export.',
|
|
600
604
|
].join('\n'),
|
|
601
605
|
},
|
|
@@ -647,7 +651,18 @@ export function negotiateProtocol(requestedProtocol) {
|
|
|
647
651
|
}
|
|
648
652
|
|
|
649
653
|
export function getToolDefinitions(client = {}, { localOnly = false } = {}) {
|
|
650
|
-
|
|
654
|
+
if (localOnly) {
|
|
655
|
+
const localTools = [];
|
|
656
|
+
if (typeof client.openLocalSlideEditor === 'function') {
|
|
657
|
+
localTools.push(LOCAL_TOOL_DEFINITIONS[0]);
|
|
658
|
+
}
|
|
659
|
+
if (typeof client.stopLocalSlideEditor === 'function') {
|
|
660
|
+
localTools.push(LOCAL_TOOL_DEFINITIONS[1]);
|
|
661
|
+
}
|
|
662
|
+
return localTools;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const tools = [...TOOL_DEFINITIONS];
|
|
651
666
|
if (typeof client.openLocalSlideEditor === 'function') {
|
|
652
667
|
tools.push(LOCAL_TOOL_DEFINITIONS[0]);
|
|
653
668
|
}
|
|
@@ -660,12 +675,11 @@ export function getToolDefinitions(client = {}, { localOnly = false } = {}) {
|
|
|
660
675
|
function buildServerInstructions(client = {}, { localOnly = false } = {}) {
|
|
661
676
|
if (localOnly) {
|
|
662
677
|
return [
|
|
663
|
-
'html2pptx local MCP opens local slide HTML in the
|
|
664
|
-
'',
|
|
665
|
-
'
|
|
666
|
-
'',
|
|
667
|
-
'
|
|
668
|
-
'The tool starts a localhost bridge and does not publish or upload the HTML file.',
|
|
678
|
+
'html2pptx local MCP opens local slide HTML files in the no-code edit-slide editor.',
|
|
679
|
+
'Use this stdio server only for local visual editing. Use the remote html2pptx MCP server for PPTX export, docs, usage, templates, and publishing workflows.',
|
|
680
|
+
'When the user asks to open, preview, no-code edit, visually edit, or launch an editing screen for generated slides, save the deck as a local .html/.htm file first, usually under html2pptx/<name>.html.',
|
|
681
|
+
'Then call html2pptx_open_local_slide_editor with the project-relative file path.',
|
|
682
|
+
'The tool starts the html2pptx CLI localhost bridge and does not publish or upload the HTML file.',
|
|
669
683
|
].join('\n');
|
|
670
684
|
}
|
|
671
685
|
|
|
@@ -715,11 +729,7 @@ function buildServerInstructions(client = {}, { localOnly = false } = {}) {
|
|
|
715
729
|
return lines.join('\n');
|
|
716
730
|
}
|
|
717
731
|
|
|
718
|
-
export async function executeTool(name, args, client, { sendNotification, progressToken
|
|
719
|
-
if (localOnly && !LOCAL_TOOL_DEFINITIONS.some((tool) => tool.name === name)) {
|
|
720
|
-
throw new Error(`${name} is available from the remote html2pptx MCP server, not the local stdio MCP server.`);
|
|
721
|
-
}
|
|
722
|
-
|
|
732
|
+
export async function executeTool(name, args, client, { sendNotification, progressToken } = {}) {
|
|
723
733
|
switch (name) {
|
|
724
734
|
case 'html2pptx_list_export_plans': {
|
|
725
735
|
const data = await client.listExportPlans();
|
|
@@ -902,7 +912,8 @@ export async function executeTool(name, args, client, { sendNotification, progre
|
|
|
902
912
|
filePath: args.filePath,
|
|
903
913
|
baseUrl: typeof args.baseUrl === 'string' ? args.baseUrl : undefined,
|
|
904
914
|
port: Number.isFinite(args.port) ? args.port : undefined,
|
|
905
|
-
openBrowser: args.openBrowser
|
|
915
|
+
openBrowser: args.openBrowser === true,
|
|
916
|
+
reuseExisting: args.reuseExisting !== false,
|
|
906
917
|
});
|
|
907
918
|
return buildToolResponse(renderLocalSlideEditorText(data), data);
|
|
908
919
|
}
|
|
@@ -919,7 +930,7 @@ export async function executeTool(name, args, client, { sendNotification, progre
|
|
|
919
930
|
}
|
|
920
931
|
}
|
|
921
932
|
|
|
922
|
-
export async function handleMcpMessage(message, { protocolVersion = DEFAULT_PROTOCOL, client, sendNotification,
|
|
933
|
+
export async function handleMcpMessage(message, { protocolVersion = DEFAULT_PROTOCOL, client, sendNotification, serverInfo = SERVER_INFO, localOnly = false }) {
|
|
923
934
|
if (!message || typeof message !== 'object') {
|
|
924
935
|
return { protocolVersion, response: null };
|
|
925
936
|
}
|
|
@@ -1151,7 +1162,7 @@ export async function handleMcpMessage(message, { protocolVersion = DEFAULT_PROT
|
|
|
1151
1162
|
try {
|
|
1152
1163
|
const toolArgs = params?.arguments ?? {};
|
|
1153
1164
|
const progressToken = toolArgs._meta?.progressToken ?? params?._meta?.progressToken;
|
|
1154
|
-
const payload = await executeTool(params?.name, toolArgs, client, { sendNotification, progressToken
|
|
1165
|
+
const payload = await executeTool(params?.name, toolArgs, client, { sendNotification, progressToken });
|
|
1155
1166
|
return {
|
|
1156
1167
|
protocolVersion,
|
|
1157
1168
|
response: {
|
|
@@ -1438,7 +1449,7 @@ function renderAnimationCatalogText(catalog) {
|
|
|
1438
1449
|
|
|
1439
1450
|
function renderLocalSlideEditorText(data) {
|
|
1440
1451
|
const lines = [
|
|
1441
|
-
'# Local slide editor started',
|
|
1452
|
+
data.reused ? '# Local slide editor reused' : '# Local slide editor started',
|
|
1442
1453
|
'',
|
|
1443
1454
|
`File: ${data.file || 'unknown'}`,
|
|
1444
1455
|
`Bridge: ${data.bridgeUrl || 'unknown'}`,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
|
|
14
14
|
let inputBuffer = Buffer.alloc(0);
|
|
15
15
|
let negotiatedProtocol = DEFAULT_PROTOCOL;
|
|
16
|
-
let
|
|
16
|
+
let wireMode = 'content-length';
|
|
17
17
|
|
|
18
18
|
process.stdin.on('data', (chunk) => {
|
|
19
19
|
inputBuffer = Buffer.concat([inputBuffer, chunk]);
|
|
@@ -36,16 +36,22 @@ process.stdout.on('error', (error) => {
|
|
|
36
36
|
function drainMessages() {
|
|
37
37
|
while (true) {
|
|
38
38
|
const headerEnd = inputBuffer.indexOf('\r\n\r\n');
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
const lineEnd = inputBuffer.indexOf('\n');
|
|
40
|
+
|
|
41
|
+
if (headerEnd === -1 || (lineEnd !== -1 && lineEnd < headerEnd)) {
|
|
42
|
+
if (lineEnd === -1) return;
|
|
43
|
+
|
|
44
|
+
const rawLine = inputBuffer.slice(0, lineEnd).toString('utf8').trim();
|
|
45
|
+
inputBuffer = inputBuffer.slice(lineEnd + 1);
|
|
44
46
|
if (!rawLine) continue;
|
|
47
|
+
|
|
48
|
+
wireMode = 'line';
|
|
45
49
|
handleRawMessage(rawLine);
|
|
46
50
|
continue;
|
|
47
51
|
}
|
|
48
52
|
|
|
53
|
+
wireMode = 'content-length';
|
|
54
|
+
|
|
49
55
|
const headerText = inputBuffer.slice(0, headerEnd).toString('utf8');
|
|
50
56
|
const contentLengthMatch = headerText.match(/Content-Length:\s*(\d+)/i);
|
|
51
57
|
if (!contentLengthMatch) {
|
|
@@ -60,7 +66,6 @@ function drainMessages() {
|
|
|
60
66
|
|
|
61
67
|
const rawBody = inputBuffer.slice(headerEnd + 4, messageEnd).toString('utf8');
|
|
62
68
|
inputBuffer = inputBuffer.slice(messageEnd);
|
|
63
|
-
outputFraming = 'headers';
|
|
64
69
|
|
|
65
70
|
handleRawMessage(rawBody);
|
|
66
71
|
}
|
|
@@ -85,6 +90,35 @@ function handleRawMessage(rawBody) {
|
|
|
85
90
|
|
|
86
91
|
async function handleMessage(message) {
|
|
87
92
|
const client = {
|
|
93
|
+
listExportPlans: async () => requestJson('/api/export/plans'),
|
|
94
|
+
createExportJob: async (body) =>
|
|
95
|
+
requestJson('/api/export/jobs', {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
requireApiKey: true,
|
|
98
|
+
body,
|
|
99
|
+
}),
|
|
100
|
+
getExportJob: async (jobId) =>
|
|
101
|
+
requestJson(`/api/export/jobs/${encodeURIComponent(jobId)}`, {
|
|
102
|
+
requireApiKey: true,
|
|
103
|
+
}),
|
|
104
|
+
getUsage: async () =>
|
|
105
|
+
requestJson('/api/export/usage', {
|
|
106
|
+
requireApiKey: true,
|
|
107
|
+
}),
|
|
108
|
+
listTemplates: async (category) => {
|
|
109
|
+
const url = category
|
|
110
|
+
? `/api/templates?category=${encodeURIComponent(category)}`
|
|
111
|
+
: '/api/templates';
|
|
112
|
+
// requireApiKey so the API is treated as authenticated and returns
|
|
113
|
+
// template prompts (anonymous requests get hasPrompt boolean only).
|
|
114
|
+
return requestJson(url, { requireApiKey: true });
|
|
115
|
+
},
|
|
116
|
+
getTemplateHtml: async (templateId) =>
|
|
117
|
+
requestJson(`/api/templates/html?id=${encodeURIComponent(templateId)}`, {
|
|
118
|
+
// /api/templates/html now requires auth (browser session, API key,
|
|
119
|
+
// or WorkOS JWT) so anonymous curl/browser can't scrape prompts.
|
|
120
|
+
requireApiKey: true,
|
|
121
|
+
}),
|
|
88
122
|
openLocalSlideEditor: async (args) => localSlideEditorManager.open(args),
|
|
89
123
|
stopLocalSlideEditor: async (sessionId) => localSlideEditorManager.stop(sessionId),
|
|
90
124
|
};
|
|
@@ -92,11 +126,11 @@ async function handleMessage(message) {
|
|
|
92
126
|
const handled = await handleMcpMessage(message, {
|
|
93
127
|
protocolVersion: negotiatedProtocol,
|
|
94
128
|
client,
|
|
95
|
-
localOnly: true,
|
|
96
129
|
serverInfo: {
|
|
97
130
|
...SERVER_INFO,
|
|
98
131
|
name: 'html2pptx-local',
|
|
99
132
|
},
|
|
133
|
+
localOnly: true,
|
|
100
134
|
sendNotification: (notification) => {
|
|
101
135
|
sendMessage(notification);
|
|
102
136
|
},
|
|
@@ -182,13 +216,14 @@ function sendError(id, code, message) {
|
|
|
182
216
|
|
|
183
217
|
function sendMessage(message) {
|
|
184
218
|
const json = JSON.stringify(message);
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
process.stdout.write(headers);
|
|
188
|
-
process.stdout.write(json);
|
|
219
|
+
if (wireMode === 'line') {
|
|
220
|
+
process.stdout.write(`${json}\n`);
|
|
189
221
|
return;
|
|
190
222
|
}
|
|
191
|
-
|
|
223
|
+
|
|
224
|
+
const headers = `Content-Length: ${Buffer.byteLength(json, 'utf8')}\r\n\r\n`;
|
|
225
|
+
process.stdout.write(headers);
|
|
226
|
+
process.stdout.write(json);
|
|
192
227
|
}
|
|
193
228
|
|
|
194
229
|
function logError(context, error) {
|
package/package.json
CHANGED
package/scripts/install-mcp.mjs
CHANGED
|
@@ -1,316 +1,188 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { spawnSync } from 'node:child_process';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { homedir } from 'node:os';
|
|
7
|
-
import { dirname, join } from 'node:path';
|
|
8
|
-
import process from 'node:process';
|
|
9
|
-
import packageJson from '../package.json' with { type: 'json' };
|
|
10
|
-
|
|
11
|
-
const VERSION = packageJson.version;
|
|
12
|
-
const PACKAGE_SPEC = 'html2pptx-local-mcp@latest';
|
|
13
|
-
const VERSIONED_PACKAGE_SPEC = `html2pptx-local-mcp@${VERSION}`;
|
|
14
|
-
const PACKAGE_URL = 'https://html2pptx.app/downloads/html2pptx-local-mcp-latest.tgz';
|
|
15
|
-
const VERSIONED_PACKAGE_URL = `https://html2pptx.app/downloads/html2pptx-local-mcp-${VERSION}.tgz`;
|
|
16
|
-
const MANIFEST_URL = 'https://html2pptx.app/downloads/latest.json';
|
|
17
|
-
const REMOTE_MCP_URL = 'https://html2pptx.app/mcp';
|
|
18
|
-
const LOCAL_SERVER_NAME = 'html2pptx-local';
|
|
19
|
-
const REMOTE_SERVER_NAME = 'html2pptx';
|
|
20
|
-
const LAUNCHER_PATH = join(homedir(), '.html2pptx', 'bin', 'html2pptx-local-mcp-launcher.mjs');
|
|
21
|
-
const INSTALL_ROOT = join(homedir(), '.html2pptx', 'local-mcp', VERSION);
|
|
22
|
-
const LOCAL_PACKAGE_ROOT = join(INSTALL_ROOT, 'node_modules', 'html2pptx-local-mcp');
|
|
23
|
-
const LOCAL_SERVER_ENTRY = join(LOCAL_PACKAGE_ROOT, 'mcp', 'pptx-studio-mcp-server.mjs');
|
|
24
|
-
|
|
25
|
-
if (isMain()) {
|
|
26
|
-
main();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function main() {
|
|
30
|
-
const target = process.argv[2] || 'claude';
|
|
31
|
-
|
|
32
|
-
if (!['claude'].includes(target)) {
|
|
33
|
-
fail(`Unsupported target "${target}". Usage: html2pptx-install-mcp claude`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
console.log(`Installing html2pptx MCP for ${target}.`);
|
|
37
|
-
console.log(`Remote server: ${REMOTE_SERVER_NAME} -> ${REMOTE_MCP_URL}`);
|
|
38
|
-
console.log(`Local server: ${LOCAL_SERVER_NAME} -> ${PACKAGE_SPEC}`);
|
|
39
|
-
console.log(`Local launcher: ${LAUNCHER_PATH}`);
|
|
40
|
-
console.log('');
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
41
6
|
|
|
42
|
-
|
|
7
|
+
const REMOTE_SERVER_NAME = 'html2pptx';
|
|
8
|
+
const LOCAL_SERVER_NAME = 'html2pptx-local';
|
|
9
|
+
const REMOTE_MCP_URL = 'https://html2pptx.app/mcp';
|
|
10
|
+
const LOCAL_PACKAGE_SPEC = process.env.HTML2PPTX_LOCAL_MCP_PACKAGE_SPEC || 'html2pptx-local-mcp@latest';
|
|
43
11
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
const dryRun = args.includes('--dry-run');
|
|
14
|
+
const help = args.includes('--help') || args.includes('-h');
|
|
15
|
+
const client = resolveClient(args);
|
|
48
16
|
|
|
49
|
-
|
|
50
|
-
|
|
17
|
+
if (help) {
|
|
18
|
+
printHelp();
|
|
19
|
+
process.exit(0);
|
|
51
20
|
}
|
|
52
21
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
writeClaudeLocalConfig();
|
|
22
|
+
if (!client) {
|
|
23
|
+
console.error('Unknown MCP client. Use one of: claude, codex.');
|
|
24
|
+
printHelp();
|
|
25
|
+
process.exit(1);
|
|
58
26
|
}
|
|
59
27
|
|
|
60
|
-
|
|
61
|
-
const args = [
|
|
62
|
-
'mcp',
|
|
63
|
-
'add',
|
|
64
|
-
'--scope',
|
|
65
|
-
'user',
|
|
66
|
-
'--transport',
|
|
67
|
-
'http',
|
|
68
|
-
REMOTE_SERVER_NAME,
|
|
69
|
-
REMOTE_MCP_URL,
|
|
70
|
-
];
|
|
71
|
-
console.log(`> claude ${args.join(' ')}`);
|
|
72
|
-
const result = spawnSync(commandForPlatform('claude'), args, { encoding: 'utf8' });
|
|
73
|
-
|
|
74
|
-
if (result.status === 0) {
|
|
75
|
-
printOutput(result);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const combined = `${result.stdout || ''}\n${result.stderr || ''}`;
|
|
80
|
-
if (/already exists|already registered|duplicate/i.test(combined)) {
|
|
81
|
-
console.log('claude reported that this MCP server is already registered; continuing.');
|
|
82
|
-
printOutput(result);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
28
|
+
const steps = buildSteps(client);
|
|
85
29
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
30
|
+
console.log(`Installing html2pptx MCP for ${client}.`);
|
|
31
|
+
console.log(`Remote server: ${REMOTE_SERVER_NAME} -> ${REMOTE_MCP_URL}`);
|
|
32
|
+
console.log(`Local server: ${LOCAL_SERVER_NAME} -> npx -y -p ${LOCAL_PACKAGE_SPEC} html2pptx-mcp`);
|
|
33
|
+
console.log('');
|
|
89
34
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (result.status !== 0) {
|
|
96
|
-
fail(`Failed to install ${PACKAGE_URL}.`);
|
|
97
|
-
}
|
|
98
|
-
if (!existsSync(LOCAL_SERVER_ENTRY)) {
|
|
99
|
-
fail(`Installed package is missing ${LOCAL_SERVER_ENTRY}.`);
|
|
35
|
+
for (const step of steps) {
|
|
36
|
+
if (step.kind === 'claude-local-config') {
|
|
37
|
+
writeClaudeLocalMcpConfig();
|
|
38
|
+
} else {
|
|
39
|
+
runStep(step);
|
|
100
40
|
}
|
|
101
41
|
}
|
|
102
42
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const config = readJsonObject(configPath);
|
|
106
|
-
config.mcpServers = isPlainObject(config.mcpServers) ? config.mcpServers : {};
|
|
107
|
-
config.mcpServers[LOCAL_SERVER_NAME] = {
|
|
108
|
-
type: 'stdio',
|
|
109
|
-
command: process.execPath,
|
|
110
|
-
args: [LAUNCHER_PATH],
|
|
111
|
-
env: {},
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
mkdirSync(dirname(configPath), { recursive: true });
|
|
115
|
-
writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
116
|
-
console.log(`Updated ${configPath}`);
|
|
117
|
-
}
|
|
43
|
+
console.log('');
|
|
44
|
+
console.log('html2pptx MCP setup finished.');
|
|
118
45
|
|
|
119
|
-
function
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
46
|
+
function resolveClient(argv) {
|
|
47
|
+
const clientFlagIndex = argv.findIndex((value) => value === '--client');
|
|
48
|
+
if (clientFlagIndex !== -1) {
|
|
49
|
+
return normalizeClient(argv[clientFlagIndex + 1]);
|
|
50
|
+
}
|
|
125
51
|
|
|
126
|
-
|
|
127
|
-
if (
|
|
128
|
-
|
|
129
|
-
if (!raw) return {};
|
|
130
|
-
try {
|
|
131
|
-
const parsed = JSON.parse(raw);
|
|
132
|
-
return isPlainObject(parsed) ? parsed : {};
|
|
133
|
-
} catch (error) {
|
|
134
|
-
fail(`Could not parse ${path}: ${error.message}`);
|
|
52
|
+
const clientEquals = argv.find((value) => value.startsWith('--client='));
|
|
53
|
+
if (clientEquals) {
|
|
54
|
+
return normalizeClient(clientEquals.slice('--client='.length));
|
|
135
55
|
}
|
|
136
|
-
}
|
|
137
56
|
|
|
138
|
-
|
|
139
|
-
return
|
|
57
|
+
const positional = argv.find((value) => !value.startsWith('-'));
|
|
58
|
+
return normalizeClient(positional || 'claude');
|
|
140
59
|
}
|
|
141
60
|
|
|
142
|
-
function
|
|
143
|
-
if (
|
|
144
|
-
if (
|
|
61
|
+
function normalizeClient(value) {
|
|
62
|
+
if (value === 'claude' || value === 'claude-code') return 'claude';
|
|
63
|
+
if (value === 'codex') return 'codex';
|
|
64
|
+
return null;
|
|
145
65
|
}
|
|
146
66
|
|
|
147
|
-
function
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
67
|
+
function buildSteps(targetClient) {
|
|
68
|
+
if (targetClient === 'claude') {
|
|
69
|
+
return [
|
|
70
|
+
{
|
|
71
|
+
command: 'claude',
|
|
72
|
+
args: ['mcp', 'add', '--scope', 'user', '--transport', 'http', REMOTE_SERVER_NAME, REMOTE_MCP_URL],
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
kind: 'claude-local-config',
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
}
|
|
151
79
|
|
|
152
|
-
|
|
153
|
-
|
|
80
|
+
return [
|
|
81
|
+
{
|
|
82
|
+
command: 'codex',
|
|
83
|
+
args: ['mcp', 'add', REMOTE_SERVER_NAME, '--url', REMOTE_MCP_URL],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
command: 'codex',
|
|
87
|
+
args: [
|
|
88
|
+
'mcp',
|
|
89
|
+
'add',
|
|
90
|
+
LOCAL_SERVER_NAME,
|
|
91
|
+
'--',
|
|
92
|
+
'npx',
|
|
93
|
+
'-y',
|
|
94
|
+
'-p',
|
|
95
|
+
LOCAL_PACKAGE_SPEC,
|
|
96
|
+
'html2pptx-mcp',
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
];
|
|
154
100
|
}
|
|
155
101
|
|
|
156
|
-
function
|
|
157
|
-
|
|
102
|
+
function writeClaudeLocalMcpConfig() {
|
|
103
|
+
const configPath = join(process.env.HOME || process.cwd(), '.claude.json');
|
|
104
|
+
const serverConfig = {
|
|
105
|
+
type: 'stdio',
|
|
106
|
+
command: 'npx',
|
|
107
|
+
args: ['-y', '-p', LOCAL_PACKAGE_SPEC, 'html2pptx-mcp'],
|
|
108
|
+
env: {},
|
|
109
|
+
};
|
|
158
110
|
|
|
159
|
-
|
|
160
|
-
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
161
|
-
import { mkdirSync } from 'node:fs';
|
|
162
|
-
import { homedir } from 'node:os';
|
|
163
|
-
import { join } from 'node:path';
|
|
164
|
-
import process from 'node:process';
|
|
165
|
-
|
|
166
|
-
const FALLBACK_VERSION = ${JSON.stringify(VERSION)};
|
|
167
|
-
const FALLBACK_PACKAGE_SPEC = ${JSON.stringify(VERSIONED_PACKAGE_SPEC)};
|
|
168
|
-
const FALLBACK_PACKAGE_URL = ${JSON.stringify(VERSIONED_PACKAGE_URL)};
|
|
169
|
-
const FALLBACK_LATEST_PACKAGE_URL = ${JSON.stringify(PACKAGE_URL)};
|
|
170
|
-
const MANIFEST_URL = process.env.HTML2PPTX_LOCAL_MCP_MANIFEST_URL || ${JSON.stringify(MANIFEST_URL)};
|
|
171
|
-
const INSTALL_BASE = join(homedir(), '.html2pptx', 'local-mcp');
|
|
172
|
-
const PACKAGE_NAME = 'html2pptx-local-mcp';
|
|
173
|
-
|
|
174
|
-
main().catch((error) => {
|
|
175
|
-
log('launcher failed: ' + (error?.message || String(error)));
|
|
176
|
-
process.exit(1);
|
|
177
|
-
});
|
|
111
|
+
console.log(`> update ${configPath} mcpServers.${LOCAL_SERVER_NAME}`);
|
|
178
112
|
|
|
179
|
-
|
|
180
|
-
const manifest = await fetchManifest();
|
|
181
|
-
const desired = normalizeManifest(manifest) || {
|
|
182
|
-
version: FALLBACK_VERSION,
|
|
183
|
-
packageSpec: FALLBACK_PACKAGE_SPEC || FALLBACK_PACKAGE_URL || FALLBACK_LATEST_PACKAGE_URL,
|
|
184
|
-
};
|
|
113
|
+
if (dryRun) return;
|
|
185
114
|
|
|
186
|
-
let
|
|
187
|
-
if (
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
115
|
+
let config = {};
|
|
116
|
+
if (existsSync(configPath)) {
|
|
117
|
+
try {
|
|
118
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error(`Failed to parse ${configPath}: ${error.message}`);
|
|
121
|
+
process.exit(1);
|
|
191
122
|
}
|
|
192
123
|
}
|
|
193
124
|
|
|
194
|
-
if (!
|
|
195
|
-
|
|
125
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
126
|
+
config = {};
|
|
196
127
|
}
|
|
197
128
|
|
|
198
|
-
if (!
|
|
199
|
-
|
|
129
|
+
if (!config.mcpServers || typeof config.mcpServers !== 'object' || Array.isArray(config.mcpServers)) {
|
|
130
|
+
config.mcpServers = {};
|
|
200
131
|
}
|
|
201
132
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
child.on('exit', (code, signal) => {
|
|
208
|
-
if (signal) {
|
|
209
|
-
process.kill(process.pid, signal);
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
process.exit(code ?? 0);
|
|
213
|
-
});
|
|
133
|
+
config.mcpServers[LOCAL_SERVER_NAME] = serverConfig;
|
|
134
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
135
|
+
console.log(`Registered local MCP server ${LOCAL_SERVER_NAME} in Claude user config.`);
|
|
214
136
|
}
|
|
215
137
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
headers: { accept: 'application/json' },
|
|
220
|
-
signal: AbortSignal.timeout(2500),
|
|
221
|
-
});
|
|
222
|
-
if (response.ok) {
|
|
223
|
-
const payload = await response.json();
|
|
224
|
-
if (payload && typeof payload.version === 'string' && payload.version.trim()) {
|
|
225
|
-
return {
|
|
226
|
-
version: payload.version.trim(),
|
|
227
|
-
packageSpec: PACKAGE_NAME + '@' + payload.version.trim(),
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
} catch {
|
|
232
|
-
// Fall back to html2pptx.app manifest.
|
|
233
|
-
}
|
|
138
|
+
function runStep(step) {
|
|
139
|
+
const rendered = [step.command, ...step.args].join(' ');
|
|
140
|
+
console.log(`> ${rendered}`);
|
|
234
141
|
|
|
235
|
-
|
|
236
|
-
const response = await fetch(MANIFEST_URL, {
|
|
237
|
-
headers: { accept: 'application/json' },
|
|
238
|
-
signal: AbortSignal.timeout(2500),
|
|
239
|
-
});
|
|
240
|
-
if (!response.ok) return null;
|
|
241
|
-
return await response.json();
|
|
242
|
-
} catch {
|
|
243
|
-
return null;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function normalizeManifest(value) {
|
|
248
|
-
if (!value || typeof value !== 'object') return null;
|
|
249
|
-
const version = typeof value.version === 'string' ? value.version.trim() : '';
|
|
250
|
-
const packageSpec =
|
|
251
|
-
typeof value.packageSpec === 'string' ? value.packageSpec.trim()
|
|
252
|
-
: typeof value.packageUrl === 'string' ? value.packageUrl.trim()
|
|
253
|
-
: typeof value.latestPackageUrl === 'string' ? value.latestPackageUrl.trim()
|
|
254
|
-
: '';
|
|
255
|
-
if (!version || !packageSpec) return null;
|
|
256
|
-
if (
|
|
257
|
-
!packageSpec.startsWith(PACKAGE_NAME + '@') &&
|
|
258
|
-
!/^https:\\/\\/html2pptx\\.app\\/downloads\\//.test(packageSpec)
|
|
259
|
-
) return null;
|
|
260
|
-
return { version, packageSpec };
|
|
261
|
-
}
|
|
142
|
+
if (dryRun) return;
|
|
262
143
|
|
|
263
|
-
|
|
264
|
-
mkdirSync(join(INSTALL_BASE, version), { recursive: true });
|
|
265
|
-
log('installing html2pptx local MCP ' + version);
|
|
266
|
-
const result = spawnSync(commandForPlatform('npm'), [
|
|
267
|
-
'install',
|
|
268
|
-
'--silent',
|
|
269
|
-
'--prefix',
|
|
270
|
-
join(INSTALL_BASE, version),
|
|
271
|
-
packageSpec,
|
|
272
|
-
], {
|
|
144
|
+
const result = spawnSync(step.command, step.args, {
|
|
273
145
|
encoding: 'utf8',
|
|
146
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
274
147
|
});
|
|
275
|
-
if (result.stdout) process.stderr.write(result.stdout);
|
|
276
|
-
if (result.stderr) process.stderr.write(result.stderr);
|
|
277
|
-
return result.status === 0 && existsSync(serverEntryForVersion(version));
|
|
278
|
-
}
|
|
279
148
|
|
|
280
|
-
|
|
281
|
-
return join(INSTALL_BASE, version, 'node_modules', PACKAGE_NAME, 'mcp', 'pptx-studio-mcp-server.mjs');
|
|
282
|
-
}
|
|
149
|
+
const output = [result.stdout, result.stderr].filter(Boolean).join('\n').trim();
|
|
283
150
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
versions = readdirSync(INSTALL_BASE, { withFileTypes: true })
|
|
288
|
-
.filter((entry) => entry.isDirectory())
|
|
289
|
-
.map((entry) => entry.name)
|
|
290
|
-
.filter((version) => existsSync(serverEntryForVersion(version)));
|
|
291
|
-
} catch {
|
|
292
|
-
return '';
|
|
151
|
+
if (result.error?.code === 'ENOENT') {
|
|
152
|
+
console.error(`Command not found: ${step.command}`);
|
|
153
|
+
process.exit(1);
|
|
293
154
|
}
|
|
294
|
-
versions.sort(compareVersions).reverse();
|
|
295
|
-
return versions.length ? serverEntryForVersion(versions[0]) : '';
|
|
296
|
-
}
|
|
297
155
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
156
|
+
if (result.status !== 0) {
|
|
157
|
+
if (isAlreadyRegistered(output)) {
|
|
158
|
+
console.log(`${step.command} reported that this MCP server is already registered; continuing.`);
|
|
159
|
+
if (output) console.log(output);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (output) console.error(output);
|
|
164
|
+
process.exit(result.status || 1);
|
|
304
165
|
}
|
|
305
|
-
return String(a).localeCompare(String(b));
|
|
306
|
-
}
|
|
307
166
|
|
|
308
|
-
|
|
309
|
-
return process.platform === 'win32' ? command + '.cmd' : command;
|
|
167
|
+
if (output) console.log(output);
|
|
310
168
|
}
|
|
311
169
|
|
|
312
|
-
function
|
|
313
|
-
|
|
170
|
+
function isAlreadyRegistered(output) {
|
|
171
|
+
return /already\s+(exists|registered|configured)|duplicate|server.*exists|同名|既に|すでに/i.test(output || '');
|
|
314
172
|
}
|
|
315
|
-
|
|
173
|
+
|
|
174
|
+
function printHelp() {
|
|
175
|
+
console.log(`
|
|
176
|
+
Usage:
|
|
177
|
+
html2pptx-install-mcp [claude|codex] [--dry-run]
|
|
178
|
+
html2pptx-install-mcp --client claude
|
|
179
|
+
html2pptx-install-mcp --client codex
|
|
180
|
+
|
|
181
|
+
Examples:
|
|
182
|
+
npx -y -p html2pptx-local-mcp@latest html2pptx-install-mcp claude
|
|
183
|
+
npx -y -p html2pptx-local-mcp@latest html2pptx-install-mcp codex
|
|
184
|
+
|
|
185
|
+
Optional:
|
|
186
|
+
HTML2PPTX_LOCAL_MCP_PACKAGE_SPEC=<package-or-tarball> html2pptx-install-mcp claude
|
|
187
|
+
`.trim());
|
|
316
188
|
}
|