html2pptx-local-mcp 1.1.17

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.
@@ -0,0 +1,316 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from 'node:child_process';
4
+ import { chmodSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
5
+ import { mkdirSync } from 'node:fs';
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('');
41
+
42
+ installClaude();
43
+
44
+ console.log('');
45
+ console.log('html2pptx MCP setup finished.');
46
+ console.log('Restart Claude Code if it was already running.');
47
+ }
48
+
49
+ function isMain() {
50
+ return import.meta.url === new URL(process.argv[1], 'file:').href;
51
+ }
52
+
53
+ function installClaude() {
54
+ runClaudeRemoteAdd();
55
+ installLocalPackage();
56
+ writeLocalLauncher();
57
+ writeClaudeLocalConfig();
58
+ }
59
+
60
+ function runClaudeRemoteAdd() {
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
+ }
85
+
86
+ printOutput(result);
87
+ fail('Failed to register the remote html2pptx MCP server with Claude Code.');
88
+ }
89
+
90
+ function installLocalPackage() {
91
+ mkdirSync(INSTALL_ROOT, { recursive: true });
92
+ const args = ['install', '--silent', '--prefix', INSTALL_ROOT, VERSIONED_PACKAGE_SPEC];
93
+ console.log(`> npm ${args.join(' ')}`);
94
+ const result = spawnSync(commandForPlatform('npm'), args, { stdio: 'inherit' });
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}.`);
100
+ }
101
+ }
102
+
103
+ function writeClaudeLocalConfig() {
104
+ const configPath = join(homedir(), '.claude.json');
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
+ }
118
+
119
+ function writeLocalLauncher() {
120
+ mkdirSync(dirname(LAUNCHER_PATH), { recursive: true });
121
+ writeFileSync(LAUNCHER_PATH, launcherSource(), 'utf8');
122
+ chmodSync(LAUNCHER_PATH, 0o755);
123
+ console.log(`Updated ${LAUNCHER_PATH}`);
124
+ }
125
+
126
+ function readJsonObject(path) {
127
+ if (!existsSync(path)) return {};
128
+ const raw = readFileSync(path, 'utf8').trim();
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}`);
135
+ }
136
+ }
137
+
138
+ function isPlainObject(value) {
139
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
140
+ }
141
+
142
+ function printOutput(result) {
143
+ if (result.stdout) process.stdout.write(result.stdout);
144
+ if (result.stderr) process.stderr.write(result.stderr);
145
+ }
146
+
147
+ function fail(message) {
148
+ console.error(`error: ${message}`);
149
+ process.exit(1);
150
+ }
151
+
152
+ function commandForPlatform(command) {
153
+ return process.platform === 'win32' ? `${command}.cmd` : command;
154
+ }
155
+
156
+ function launcherSource() {
157
+ return `#!/usr/bin/env node
158
+
159
+ import { spawn, spawnSync } from 'node:child_process';
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
+ });
178
+
179
+ async function main() {
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
+ };
185
+
186
+ let entry = serverEntryForVersion(desired.version);
187
+ if (!existsSync(entry)) {
188
+ const installed = installVersion(desired.version, desired.packageSpec);
189
+ if (installed) {
190
+ entry = serverEntryForVersion(desired.version);
191
+ }
192
+ }
193
+
194
+ if (!existsSync(entry)) {
195
+ entry = findNewestInstalledServer();
196
+ }
197
+
198
+ if (!entry) {
199
+ throw new Error('No installed html2pptx local MCP server found.');
200
+ }
201
+
202
+ const child = spawn(process.execPath, [entry, ...process.argv.slice(2)], {
203
+ stdio: 'inherit',
204
+ env: process.env,
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
+ });
214
+ }
215
+
216
+ async function fetchManifest() {
217
+ try {
218
+ const response = await fetch('https://registry.npmjs.org/' + PACKAGE_NAME + '/latest', {
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
+ }
234
+
235
+ try {
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
+ }
262
+
263
+ function installVersion(version, packageSpec) {
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
+ ], {
273
+ encoding: 'utf8',
274
+ });
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
+
280
+ function serverEntryForVersion(version) {
281
+ return join(INSTALL_BASE, version, 'node_modules', PACKAGE_NAME, 'mcp', 'pptx-studio-mcp-server.mjs');
282
+ }
283
+
284
+ function findNewestInstalledServer() {
285
+ let versions = [];
286
+ try {
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 '';
293
+ }
294
+ versions.sort(compareVersions).reverse();
295
+ return versions.length ? serverEntryForVersion(versions[0]) : '';
296
+ }
297
+
298
+ function compareVersions(a, b) {
299
+ const aa = String(a).split('.').map((part) => Number.parseInt(part, 10) || 0);
300
+ const bb = String(b).split('.').map((part) => Number.parseInt(part, 10) || 0);
301
+ for (let i = 0; i < Math.max(aa.length, bb.length); i += 1) {
302
+ const diff = (aa[i] || 0) - (bb[i] || 0);
303
+ if (diff) return diff;
304
+ }
305
+ return String(a).localeCompare(String(b));
306
+ }
307
+
308
+ function commandForPlatform(command) {
309
+ return process.platform === 'win32' ? command + '.cmd' : command;
310
+ }
311
+
312
+ function log(message) {
313
+ process.stderr.write('[html2pptx-local-mcp] ' + message + '\\n');
314
+ }
315
+ `;
316
+ }