html2pptx-local-mcp 1.1.20 → 1.1.21
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 +50 -16
- package/cli/dist/commands/edit.d.ts +1 -1
- package/cli/dist/commands/edit.js +30 -13
- package/cli/dist/index.js +0 -0
- package/lib/local-editor-server.js +316 -0
- package/lib/local-editor-state.js +45 -0
- package/lib/local-slide-editor-launcher.js +19 -18
- package/lib/pptx-studio-mcp-core.js +15 -9
- package/local-editor-app/app/api/edit-slide/local-health/route.js +16 -0
- package/local-editor-app/app/edit-slide/edit-slide-client.jsx +13153 -0
- package/local-editor-app/app/edit-slide/page.jsx +13 -0
- package/local-editor-app/app/globals.css +4 -0
- package/local-editor-app/app/layout.jsx +14 -0
- package/local-editor-app/components/studio/edit-property-panel.jsx +1061 -0
- package/local-editor-app/lib/edit-panel-value-normalizer.js +97 -0
- package/local-editor-app/lib/edit-slide-editor-helpers.js +120 -0
- package/local-editor-app/lib/edit-slide-url-security.js +247 -0
- package/local-editor-app/next.config.mjs +31 -0
- package/local-editor-app/package.json +7 -0
- package/mcp/pptx-studio-mcp-server.mjs +1 -1
- package/package.json +13 -2
- package/public/skills/html2pptx/SKILL.md +635 -0
- package/public/skills/html2pptx/references/automation-contract.md +68 -0
- package/public/skills/html2pptx/references/input-contract.md +107 -0
- package/public/skills/html2pptx/references/japanese-slide-design.md +273 -0
- package/public/skills/html2pptx/references/rewrite-patterns.md +218 -0
- package/public/skills/icon-generator/SKILL.md +133 -0
- package/public/skills/open-slide/SKILL.md +160 -0
- package/public/skills/publish-template/SKILL.md +215 -0
- package/public/skills/register-template/SKILL.md +142 -0
- package/scripts/install-mcp.mjs +28 -2
- package/scripts/install-skills.mjs +82 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export const EDITOR_SERVER_STATE_FILE = '.html2pptx/edit-slide/editor-server.json';
|
|
5
|
+
export const LEGACY_EDITOR_SERVER_STATE_FILE = '.open-slide/editor-server.json';
|
|
6
|
+
|
|
7
|
+
export async function readEditorServerState(root = process.cwd()) {
|
|
8
|
+
for (const stateFile of [EDITOR_SERVER_STATE_FILE, LEGACY_EDITOR_SERVER_STATE_FILE]) {
|
|
9
|
+
try {
|
|
10
|
+
const raw = await readFile(join(root, stateFile), 'utf8');
|
|
11
|
+
const state = JSON.parse(raw);
|
|
12
|
+
const port = Number.parseInt(state?.port, 10);
|
|
13
|
+
const baseUrl = typeof state?.baseUrl === 'string' ? state.baseUrl : '';
|
|
14
|
+
if (!Number.isInteger(port) || port <= 0 || !baseUrl.startsWith('http://')) continue;
|
|
15
|
+
return {
|
|
16
|
+
...state,
|
|
17
|
+
port,
|
|
18
|
+
baseUrl,
|
|
19
|
+
};
|
|
20
|
+
} catch {
|
|
21
|
+
// Try the next known state location.
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function readRegisteredEditorBaseUrl(root = process.cwd()) {
|
|
28
|
+
const state = await readEditorServerState(root);
|
|
29
|
+
return state?.baseUrl || null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function writeEditorServerState(root, { baseUrl, port, pid, managedBy = 'html2pptx-local-mcp' }) {
|
|
33
|
+
await mkdir(join(root, '.html2pptx', 'edit-slide'), { recursive: true });
|
|
34
|
+
await writeFile(
|
|
35
|
+
join(root, EDITOR_SERVER_STATE_FILE),
|
|
36
|
+
`${JSON.stringify({
|
|
37
|
+
baseUrl,
|
|
38
|
+
port,
|
|
39
|
+
pid,
|
|
40
|
+
managedBy,
|
|
41
|
+
updatedAt: new Date().toISOString(),
|
|
42
|
+
}, null, 2)}\n`,
|
|
43
|
+
'utf8',
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
|
-
import { access,
|
|
5
|
-
import { dirname, extname,
|
|
4
|
+
import { access, realpath, stat } from 'node:fs/promises';
|
|
5
|
+
import { dirname, extname, relative, resolve, sep } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { ensureLocalEditorServer } from './local-editor-server.js';
|
|
7
8
|
|
|
8
9
|
const ALLOWED_EXTENSIONS = new Set(['.html', '.htm']);
|
|
9
|
-
const EDITOR_SERVER_STATE_FILE = '.html2pptx/edit-slide/editor-server.json';
|
|
10
|
-
const LEGACY_EDITOR_SERVER_STATE_FILE = '.open-slide/editor-server.json';
|
|
11
10
|
const DEFAULT_LAUNCH_TIMEOUT_MS = 10000;
|
|
12
11
|
|
|
13
12
|
export function createLocalSlideEditorManager(options = {}) {
|
|
@@ -33,7 +32,12 @@ export function createLocalSlideEditorManager(options = {}) {
|
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
const baseUrl = normalizeEditorBaseUrl(
|
|
36
|
-
input.baseUrl ||
|
|
35
|
+
input.baseUrl ||
|
|
36
|
+
process.env.HTML2PPTX_EDITOR_BASE_URL ||
|
|
37
|
+
await resolveEditorBaseUrl(file.root, {
|
|
38
|
+
...input,
|
|
39
|
+
ensureEditorServer: options.ensureEditorServer,
|
|
40
|
+
}),
|
|
37
41
|
);
|
|
38
42
|
const port = normalizePort(input.port, 0);
|
|
39
43
|
const openBrowser = input.openBrowser === true;
|
|
@@ -148,6 +152,14 @@ export function createLocalSlideEditorManager(options = {}) {
|
|
|
148
152
|
|
|
149
153
|
export const localSlideEditorManager = createLocalSlideEditorManager();
|
|
150
154
|
|
|
155
|
+
async function resolveEditorBaseUrl(root, input = {}) {
|
|
156
|
+
const ensureEditorServer = typeof input.ensureEditorServer === 'function'
|
|
157
|
+
? input.ensureEditorServer
|
|
158
|
+
: ensureLocalEditorServer;
|
|
159
|
+
const ensured = await ensureEditorServer(root, input);
|
|
160
|
+
return ensured.baseUrl;
|
|
161
|
+
}
|
|
162
|
+
|
|
151
163
|
export async function resolveEditableFile(filePath, cwd = process.cwd()) {
|
|
152
164
|
if (typeof filePath !== 'string' || filePath.trim() === '') {
|
|
153
165
|
throw new Error('filePath must be a non-empty .html or .htm path.');
|
|
@@ -305,7 +317,7 @@ export function normalizeEditorBaseUrl(raw) {
|
|
|
305
317
|
try {
|
|
306
318
|
if (!raw) {
|
|
307
319
|
throw new Error(
|
|
308
|
-
'Local editor UI is not
|
|
320
|
+
'Local editor UI is not available. Reinstall html2pptx-local-mcp or pass baseUrl as a loopback editor URL.',
|
|
309
321
|
);
|
|
310
322
|
}
|
|
311
323
|
const url = new URL(raw);
|
|
@@ -330,18 +342,7 @@ function isAllowedEditorBaseUrl(url) {
|
|
|
330
342
|
return isLoopbackHostname(url.hostname);
|
|
331
343
|
}
|
|
332
344
|
|
|
333
|
-
export
|
|
334
|
-
for (const stateFile of [EDITOR_SERVER_STATE_FILE, LEGACY_EDITOR_SERVER_STATE_FILE]) {
|
|
335
|
-
try {
|
|
336
|
-
const raw = await readFile(join(root, stateFile), 'utf8');
|
|
337
|
-
const state = JSON.parse(raw);
|
|
338
|
-
if (typeof state?.baseUrl === 'string') return state.baseUrl;
|
|
339
|
-
} catch {
|
|
340
|
-
// Try the next known state location.
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
return null;
|
|
344
|
-
}
|
|
345
|
+
export { readRegisteredEditorBaseUrl } from './local-editor-state.js';
|
|
345
346
|
|
|
346
347
|
function isLoopbackHostname(hostname) {
|
|
347
348
|
const host = String(hostname || '').replace(/^\[|\]$/g, '').toLowerCase();
|
|
@@ -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.
|
|
419
|
+
'Local stdio MCP only. Ensure the loopback html2pptx editor UI is running, start the local file 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: {
|
|
@@ -427,7 +427,13 @@ export const LOCAL_TOOL_DEFINITIONS = [
|
|
|
427
427
|
},
|
|
428
428
|
baseUrl: {
|
|
429
429
|
type: 'string',
|
|
430
|
-
description: 'Optional editor base URL. Must be a loopback http(s) origin such as http://localhost:<port>. When omitted, the
|
|
430
|
+
description: 'Optional editor base URL. Must be a loopback http(s) origin such as http://localhost:<port>. When omitted, the local MCP reuses or starts its bundled local editor UI. Hosted editor URLs are not allowed for local file editing.',
|
|
431
|
+
},
|
|
432
|
+
editorPort: {
|
|
433
|
+
type: 'integer',
|
|
434
|
+
minimum: 0,
|
|
435
|
+
maximum: 65535,
|
|
436
|
+
description: 'Optional editor UI port. Defaults to 0 so the OS picks an available loopback port when the local MCP starts the editor UI.',
|
|
431
437
|
},
|
|
432
438
|
port: {
|
|
433
439
|
type: 'integer',
|
|
@@ -597,9 +603,9 @@ function buildPromptMessages(name, args) {
|
|
|
597
603
|
'1. Read html2pptx_get_docs with section="html-contract" to confirm the slide HTML contract.',
|
|
598
604
|
'2. Generate slide-safe HTML. Each slide must be a <section class="slide"> element with explicit dimensions; 1600x900 is the default example unless the request needs a different size.',
|
|
599
605
|
`3. Save the complete HTML document to a project-local .html file, preferably "${fileName}" or another clear path under html2pptx/.`,
|
|
600
|
-
'4.
|
|
601
|
-
'5.
|
|
602
|
-
'6. If that
|
|
606
|
+
'4. Call html2pptx_open_local_slide_editor with { filePath: <path> }. The local MCP will reuse or start the loopback editor UI and the localhost file bridge. Pass baseUrl only when the user supplied a specific loopback editor URL. Do not use https://html2pptx.app/edit-slide for local file editing.',
|
|
607
|
+
'5. 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>.',
|
|
608
|
+
'6. If the CLI fallback reports that the editor is not available, use the local MCP package or run the command from an html2pptx source checkout.',
|
|
603
609
|
'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.',
|
|
604
610
|
].join('\n'),
|
|
605
611
|
},
|
|
@@ -679,7 +685,7 @@ function buildServerInstructions(client = {}, { localOnly = false } = {}) {
|
|
|
679
685
|
'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
686
|
'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
687
|
'Then call html2pptx_open_local_slide_editor with the project-relative file path.',
|
|
682
|
-
'The tool starts the html2pptx
|
|
688
|
+
'The tool starts or reuses the loopback editor UI, starts the html2pptx localhost file bridge, and does not publish or upload the HTML file.',
|
|
683
689
|
].join('\n');
|
|
684
690
|
}
|
|
685
691
|
|
|
@@ -715,9 +721,8 @@ function buildServerInstructions(client = {}, { localOnly = false } = {}) {
|
|
|
715
721
|
'',
|
|
716
722
|
'Local visual editing is available in this stdio MCP server.',
|
|
717
723
|
'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.',
|
|
718
|
-
'Use only loopback editor UI for local files.
|
|
719
|
-
'
|
|
720
|
-
'The tool starts the existing html2pptx CLI localhost bridge and does not publish or upload the HTML file.',
|
|
724
|
+
'Use only loopback editor UI for local files. Call html2pptx_open_local_slide_editor with the project-relative file path; the local MCP will reuse or start the local editor UI and localhost bridge. Hosted https://html2pptx.app/edit-slide is not allowed for local file editing.',
|
|
725
|
+
'The tool does not publish or upload the HTML file.',
|
|
721
726
|
);
|
|
722
727
|
} else {
|
|
723
728
|
lines.push(
|
|
@@ -911,6 +916,7 @@ export async function executeTool(name, args, client, { sendNotification, progre
|
|
|
911
916
|
const data = await client.openLocalSlideEditor({
|
|
912
917
|
filePath: args.filePath,
|
|
913
918
|
baseUrl: typeof args.baseUrl === 'string' ? args.baseUrl : undefined,
|
|
919
|
+
editorPort: Number.isFinite(args.editorPort) ? args.editorPort : undefined,
|
|
914
920
|
port: Number.isFinite(args.port) ? args.port : undefined,
|
|
915
921
|
openBrowser: args.openBrowser === true,
|
|
916
922
|
reuseExisting: args.reuseExisting !== false,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { isLoopbackRequest } from '../../../../lib/edit-slide-url-security.js';
|
|
3
|
+
|
|
4
|
+
export const dynamic = 'force-dynamic';
|
|
5
|
+
|
|
6
|
+
export function GET(request) {
|
|
7
|
+
if (!isLoopbackRequest(request.headers)) {
|
|
8
|
+
return NextResponse.json({ ok: false }, { status: 404 });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return NextResponse.json({
|
|
12
|
+
ok: true,
|
|
13
|
+
app: 'html2pptx-local-editor',
|
|
14
|
+
version: 1,
|
|
15
|
+
});
|
|
16
|
+
}
|