@jieunmarslim/server-editable-slides 0.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.
Files changed (2) hide show
  1. package/index.js +186 -0
  2. package/package.json +19 -0
package/index.js ADDED
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { z } from 'zod';
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { execSync } from 'node:child_process';
8
+
9
+ // Redirect console.log to stderr so stdout is reserved strictly for JSON-RPC
10
+ console.log = (...args) => console.error(...args);
11
+
12
+ const BASE_URL =
13
+ process.env.EDITABLE_SLIDES_URL ||
14
+ 'https://editable-slides-mcp-1056428002550.us-central1.run.app';
15
+
16
+ function getAuthToken() {
17
+ if (process.env.EDITABLE_SLIDES_API_KEY) {
18
+ return process.env.EDITABLE_SLIDES_API_KEY;
19
+ }
20
+ try {
21
+ return execSync('gcloud auth print-access-token', {
22
+ stdio: ['ignore', 'pipe', 'ignore'],
23
+ })
24
+ .toString()
25
+ .trim();
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+
31
+ function getProject() {
32
+ if (process.env.EDITABLE_SLIDES_PROJECT) {
33
+ return process.env.EDITABLE_SLIDES_PROJECT;
34
+ }
35
+ try {
36
+ return execSync('gcloud config get-value project', {
37
+ stdio: ['ignore', 'pipe', 'ignore'],
38
+ })
39
+ .toString()
40
+ .trim();
41
+ } catch {
42
+ return 'editnblm-in-ge-2036';
43
+ }
44
+ }
45
+
46
+ async function callRemoteMcp(method, params, apiKey) {
47
+ const token = apiKey || getAuthToken();
48
+ const project = getProject();
49
+ const url = `${BASE_URL}/mcp/${project}`;
50
+
51
+ const headers = {
52
+ 'Content-Type': 'application/json',
53
+ Accept: 'application/json, text/event-stream',
54
+ };
55
+ if (token) {
56
+ headers['Authorization'] = `Bearer ${token}`;
57
+ }
58
+
59
+ const res = await fetch(url, {
60
+ method: 'POST',
61
+ headers,
62
+ body: JSON.stringify({
63
+ jsonrpc: '2.0',
64
+ id: 1,
65
+ method,
66
+ params,
67
+ }),
68
+ });
69
+
70
+ if (!res.ok) {
71
+ throw new Error(`Cloud service returned HTTP ${res.status}: ${res.statusText}`);
72
+ }
73
+
74
+ const text = await res.text();
75
+ const line = text.split('\n').find((l) => l.startsWith('data: '));
76
+ const jsonStr = line ? line.slice(6) : text;
77
+ const data = JSON.parse(jsonStr);
78
+
79
+ if (data.error) {
80
+ throw new Error(data.error.message || JSON.stringify(data.error));
81
+ }
82
+ return data.result;
83
+ }
84
+
85
+ const server = new McpServer({
86
+ name: 'editable-slides',
87
+ version: '1.0.0',
88
+ });
89
+
90
+ server.tool(
91
+ 'create_slides_from_image',
92
+ 'Convert a presentation slide image or PDF into an editable Google Slides presentation or PowerPoint file. Reads local files, securely processes via Editable Slides Cloud, and returns the presentation URL.',
93
+ {
94
+ filePath: z
95
+ .string()
96
+ .optional()
97
+ .describe('Local file path to the slide image or PDF (e.g. /path/to/slide.png)'),
98
+ fileUrl: z
99
+ .string()
100
+ .optional()
101
+ .describe('Public HTTP(S) or gs:// URL of the slide image or PDF'),
102
+ format: z
103
+ .enum(['slides', 'pptx'])
104
+ .optional()
105
+ .describe("Output format: 'slides' (default, Google Slides URL) or 'pptx'"),
106
+ apiKey: z
107
+ .string()
108
+ .optional()
109
+ .describe('Optional API key or access token for authentication'),
110
+ },
111
+ async ({ filePath, fileUrl, format, apiKey }) => {
112
+ let targetUrl = fileUrl;
113
+
114
+ if (filePath && !targetUrl) {
115
+ if (!fs.existsSync(filePath)) {
116
+ throw new Error(`File not found: ${filePath}`);
117
+ }
118
+ const fileName = path.basename(filePath);
119
+ const ext = path.extname(filePath).toLowerCase();
120
+ const mimeType =
121
+ ext === '.pdf'
122
+ ? 'application/pdf'
123
+ : ext === '.jpg' || ext === '.jpeg'
124
+ ? 'image/jpeg'
125
+ : 'image/png';
126
+ const fileBytes = await fs.promises.readFile(filePath);
127
+
128
+ console.error(`[EditableSlides] Requesting secure upload URL for "${fileName}"...`);
129
+ const uploadResp = await callRemoteMcp(
130
+ 'tools/call',
131
+ {
132
+ name: 'request_upload_url',
133
+ arguments: { fileName, mimeType },
134
+ },
135
+ apiKey,
136
+ );
137
+
138
+ const parsed = JSON.parse(uploadResp.content[0].text);
139
+ const { uploadUrl, fileUrl: uploadedFileUrl } = parsed;
140
+
141
+ console.error(
142
+ `[EditableSlides] Uploading image (${(fileBytes.length / 1024).toFixed(1)} KB)...`,
143
+ );
144
+ const putRes = await fetch(uploadUrl, {
145
+ method: 'PUT',
146
+ headers: { 'Content-Type': mimeType },
147
+ body: fileBytes,
148
+ });
149
+
150
+ if (!putRes.ok) {
151
+ throw new Error(`Upload failed: ${putRes.status} ${putRes.statusText}`);
152
+ }
153
+ targetUrl = uploadedFileUrl;
154
+ }
155
+
156
+ if (!targetUrl) {
157
+ throw new Error('Either filePath or fileUrl must be provided.');
158
+ }
159
+
160
+ console.error(`[EditableSlides] Converting to ${format || 'slides'}...`);
161
+ const result = await callRemoteMcp(
162
+ 'tools/call',
163
+ {
164
+ name: 'create_slides_from_image',
165
+ arguments: {
166
+ fileUrl: targetUrl,
167
+ format: format || 'slides',
168
+ },
169
+ },
170
+ apiKey,
171
+ );
172
+
173
+ return result;
174
+ },
175
+ );
176
+
177
+ async function main() {
178
+ const transport = new StdioServerTransport();
179
+ await server.connect(transport);
180
+ console.error('[EditableSlides] Ready and connected to Editable Slides Cloud.');
181
+ }
182
+
183
+ main().catch((err) => {
184
+ console.error('[EditableSlides] Fatal error:', err);
185
+ process.exit(1);
186
+ });
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@jieunmarslim/server-editable-slides",
3
+ "version": "0.1.0",
4
+ "description": "Model Context Protocol (MCP) client for Editable Slides Cloud",
5
+ "type": "module",
6
+ "bin": {
7
+ "server-editable-slides": "index.js"
8
+ },
9
+ "files": [
10
+ "index.js"
11
+ ],
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "dependencies": {
16
+ "@modelcontextprotocol/sdk": "^1.26.0",
17
+ "zod": "^3.24.2"
18
+ }
19
+ }