@masaki39/marp-mcp 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.
- package/LICENSE +21 -0
- package/README.md +347 -0
- package/build/index.js +36 -0
- package/build/layouts/academic.js +258 -0
- package/build/templates/README.template.md +67 -0
- package/build/templates/academic_custom.css +160 -0
- package/build/templates/gitignore.template +20 -0
- package/build/templates/slides.template.md +30 -0
- package/build/tools/generate.js +127 -0
- package/build/tools/init.js +100 -0
- package/build/types.js +4 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 masaki39
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# Marp MCP Server
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server for managing [Marp](https://marp.app/) presentation projects with academic theme support. Optimized for use with Claude Code, Cursor, and other AI-powered editors.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎨 **Academic Theme Support** - Pre-configured academic_custom.css theme
|
|
8
|
+
- 📁 **Project Initialization** - Automatic directory structure setup
|
|
9
|
+
- 🎯 **Structured Slide Generation** - 6 layout templates for consistent design
|
|
10
|
+
- 🔧 **Editor Integration** - Designed for Claude Code and Cursor
|
|
11
|
+
- 📝 **Markdown Output** - Generate slides as markdown strings for easy editing
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
### Via npx (Recommended)
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx @masaki39/marp-mcp
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Global Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install -g @masaki39/marp-mcp
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Local Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install @masaki39/marp-mcp
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Configuration
|
|
34
|
+
|
|
35
|
+
### Claude Desktop
|
|
36
|
+
|
|
37
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"mcpServers": {
|
|
42
|
+
"marp": {
|
|
43
|
+
"command": "npx",
|
|
44
|
+
"args": ["-y", "@masaki39/marp-mcp"]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or with global installation:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"mcpServers": {
|
|
55
|
+
"marp": {
|
|
56
|
+
"command": "marp-mcp"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Claude Code / Cursor
|
|
63
|
+
|
|
64
|
+
The server works seamlessly with Claude Code and Cursor. The generated markdown can be directly inserted into your editor.
|
|
65
|
+
|
|
66
|
+
## Available Tools
|
|
67
|
+
|
|
68
|
+
### 1. `init_presentation`
|
|
69
|
+
|
|
70
|
+
Initialize a new Marp presentation project with complete directory structure.
|
|
71
|
+
|
|
72
|
+
**Parameters:**
|
|
73
|
+
- `projectPath` (string) - Directory where project will be created
|
|
74
|
+
- `projectName` (string) - Name of the presentation project
|
|
75
|
+
- `presentationTitle` (string) - Title of the presentation
|
|
76
|
+
- `presentationSubtitle` (string, optional) - Subtitle
|
|
77
|
+
- `description` (string, optional) - Brief description
|
|
78
|
+
|
|
79
|
+
**Generated Structure:**
|
|
80
|
+
```
|
|
81
|
+
my-presentation/
|
|
82
|
+
├── slides.md # Main presentation file
|
|
83
|
+
├── themes/
|
|
84
|
+
│ └── academic_custom.css # Academic theme
|
|
85
|
+
├── attachments/
|
|
86
|
+
│ ├── images/ # Image files
|
|
87
|
+
│ ├── videos/ # Video files
|
|
88
|
+
│ └── data/ # Data files
|
|
89
|
+
├── .gitignore
|
|
90
|
+
└── README.md
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Example:**
|
|
94
|
+
```javascript
|
|
95
|
+
{
|
|
96
|
+
"projectPath": "/Users/yourname/presentations",
|
|
97
|
+
"projectName": "research-2024",
|
|
98
|
+
"presentationTitle": "Research Findings",
|
|
99
|
+
"presentationSubtitle": "Annual Report 2024",
|
|
100
|
+
"description": "Research results presentation"
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 2. `generate_slide`
|
|
105
|
+
|
|
106
|
+
Generate a slide using academic theme layouts. Returns markdown string for copy-paste.
|
|
107
|
+
|
|
108
|
+
**Parameters:**
|
|
109
|
+
- `layoutType` (string) - Layout type (title, lead, content, table, multi-column, quote)
|
|
110
|
+
- `params` (object) - Layout-specific parameters
|
|
111
|
+
|
|
112
|
+
**Example:**
|
|
113
|
+
```javascript
|
|
114
|
+
{
|
|
115
|
+
"layoutType": "title",
|
|
116
|
+
"params": {
|
|
117
|
+
"title": "Presentation Title",
|
|
118
|
+
"subtitle": "Subtitle"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 3. `list_slide_layouts`
|
|
124
|
+
|
|
125
|
+
List all available slide layouts with their parameters and descriptions.
|
|
126
|
+
|
|
127
|
+
**No parameters required.**
|
|
128
|
+
|
|
129
|
+
## Available Layouts
|
|
130
|
+
|
|
131
|
+
### Title Slide (`title`)
|
|
132
|
+
Centered title and subtitle with `.title` class.
|
|
133
|
+
|
|
134
|
+
**Parameters:**
|
|
135
|
+
- `title` (required, max 60 chars)
|
|
136
|
+
- `subtitle` (optional, max 100 chars)
|
|
137
|
+
|
|
138
|
+
**Output:**
|
|
139
|
+
```markdown
|
|
140
|
+
# Presentation Title
|
|
141
|
+
## Subtitle
|
|
142
|
+
|
|
143
|
+
<!-- _class: title -->
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Lead Slide (`lead`)
|
|
147
|
+
Left-aligned with maroon color headings using `.lead` class.
|
|
148
|
+
|
|
149
|
+
**Parameters:**
|
|
150
|
+
- `heading` (required, max 80 chars)
|
|
151
|
+
- `content` (optional, markdown supported)
|
|
152
|
+
|
|
153
|
+
**Output:**
|
|
154
|
+
```markdown
|
|
155
|
+
# Main Heading
|
|
156
|
+
|
|
157
|
+
Content goes here
|
|
158
|
+
|
|
159
|
+
<!-- _class: lead -->
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Content Slide (`content`)
|
|
163
|
+
Standard content slide with optional heading.
|
|
164
|
+
|
|
165
|
+
**Parameters:**
|
|
166
|
+
- `heading` (optional, max 80 chars)
|
|
167
|
+
- `content` (required, markdown supported)
|
|
168
|
+
|
|
169
|
+
**Output:**
|
|
170
|
+
```markdown
|
|
171
|
+
# Heading
|
|
172
|
+
|
|
173
|
+
Content with markdown support
|
|
174
|
+
- List item 1
|
|
175
|
+
- List item 2
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Table Slide (`table`)
|
|
179
|
+
Table with customizable size and alignment.
|
|
180
|
+
|
|
181
|
+
**Parameters:**
|
|
182
|
+
- `heading` (optional, max 80 chars)
|
|
183
|
+
- `tableMarkdown` (required, markdown table)
|
|
184
|
+
- `tableClass` (optional: "center", "100", "tiny", "small", "large")
|
|
185
|
+
|
|
186
|
+
**Output:**
|
|
187
|
+
```markdown
|
|
188
|
+
# Table Heading
|
|
189
|
+
|
|
190
|
+
| Column 1 | Column 2 |
|
|
191
|
+
|----------|----------|
|
|
192
|
+
| Data 1 | Data 2 |
|
|
193
|
+
|
|
194
|
+
<!-- _class: table-center table-100 -->
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Multi-Column Slide (`multi-column`)
|
|
198
|
+
2-3 column layout using double blockquote syntax.
|
|
199
|
+
|
|
200
|
+
**Parameters:**
|
|
201
|
+
- `heading` (optional, max 80 chars)
|
|
202
|
+
- `columns` (required, array of strings)
|
|
203
|
+
|
|
204
|
+
**Output:**
|
|
205
|
+
```markdown
|
|
206
|
+
# Comparison
|
|
207
|
+
|
|
208
|
+
> > Column 1 content
|
|
209
|
+
> > - Point 1
|
|
210
|
+
>
|
|
211
|
+
> > Column 2 content
|
|
212
|
+
> > - Point 2
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Quote Slide (`quote`)
|
|
216
|
+
Quote with citation in footer.
|
|
217
|
+
|
|
218
|
+
**Parameters:**
|
|
219
|
+
- `heading` (optional, max 80 chars)
|
|
220
|
+
- `content` (optional)
|
|
221
|
+
- `quote` (required, max 300 chars)
|
|
222
|
+
- `citation` (optional, max 100 chars)
|
|
223
|
+
|
|
224
|
+
**Output:**
|
|
225
|
+
```markdown
|
|
226
|
+
# Heading
|
|
227
|
+
|
|
228
|
+
Main content
|
|
229
|
+
|
|
230
|
+
> Quote text here — Citation
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Academic Theme Features
|
|
234
|
+
|
|
235
|
+
The included `academic_custom.css` theme provides:
|
|
236
|
+
|
|
237
|
+
- **Page numbering** - Automatic slide numbers
|
|
238
|
+
- **Custom fonts** - Noto Sans JP and Source Code Pro
|
|
239
|
+
- **Color scheme** - Maroon highlights (#800000)
|
|
240
|
+
- **Table styles** - Multiple size and alignment options
|
|
241
|
+
- **Multi-column support** - Flexible column layouts
|
|
242
|
+
- **Header support** - Customizable presentation headers
|
|
243
|
+
|
|
244
|
+
### CSS Classes
|
|
245
|
+
|
|
246
|
+
- `.title` - Title slide (centered)
|
|
247
|
+
- `.lead` - Lead slide (left-aligned, maroon)
|
|
248
|
+
- `.table-center` - Centered table
|
|
249
|
+
- `.table-100` - Full-width table
|
|
250
|
+
- `.table-tiny` - Small font table (0.7em)
|
|
251
|
+
- `.table-small` - Small font table (0.8em)
|
|
252
|
+
- `.table-large` - Large font table (1.1em)
|
|
253
|
+
|
|
254
|
+
## Building Presentations
|
|
255
|
+
|
|
256
|
+
### Prerequisites
|
|
257
|
+
|
|
258
|
+
Install Marp CLI:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
npm install -g @marp-team/marp-cli
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Build to PDF
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
marp slides.md -o slides.pdf
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Build to HTML
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
marp slides.md -o slides.html
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Build to PowerPoint
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
marp slides.md -o slides.pptx
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Preview in VS Code
|
|
283
|
+
|
|
284
|
+
Install [Marp for VS Code](https://marketplace.visualstudio.com/items?itemName=marp-team.marp-vscode) extension for live preview.
|
|
285
|
+
|
|
286
|
+
## Development
|
|
287
|
+
|
|
288
|
+
### Building from Source
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
git clone https://github.com/masaki39/marp-mcp.git
|
|
292
|
+
cd marp-mcp
|
|
293
|
+
npm install
|
|
294
|
+
npm run build
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Testing Locally
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
npm link
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Then configure Claude Desktop to use the local version.
|
|
304
|
+
|
|
305
|
+
## Troubleshooting
|
|
306
|
+
|
|
307
|
+
### Server Not Starting
|
|
308
|
+
|
|
309
|
+
Check logs in `~/Library/Logs/Claude/`:
|
|
310
|
+
```bash
|
|
311
|
+
tail -f ~/Library/Logs/Claude/mcp*.log
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Template Files Not Found
|
|
315
|
+
|
|
316
|
+
Ensure the package was built correctly:
|
|
317
|
+
```bash
|
|
318
|
+
npm run build
|
|
319
|
+
ls build/templates/
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Should show:
|
|
323
|
+
- `academic_custom.css`
|
|
324
|
+
- `slides.template.md`
|
|
325
|
+
- `README.template.md`
|
|
326
|
+
- `gitignore.template`
|
|
327
|
+
|
|
328
|
+
## Contributing
|
|
329
|
+
|
|
330
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
331
|
+
|
|
332
|
+
## License
|
|
333
|
+
|
|
334
|
+
MIT License - see LICENSE file for details
|
|
335
|
+
|
|
336
|
+
## Credits
|
|
337
|
+
|
|
338
|
+
- Academic theme based on [marp-theme-academic](https://github.com/kaisugi/marp-theme-academic) by kaisugi
|
|
339
|
+
- Built on [Model Context Protocol](https://modelcontextprotocol.io)
|
|
340
|
+
- Powered by [Marp](https://marp.app/)
|
|
341
|
+
|
|
342
|
+
## Links
|
|
343
|
+
|
|
344
|
+
- [GitHub Repository](https://github.com/masaki39/marp-mcp)
|
|
345
|
+
- [npm Package](https://www.npmjs.com/package/@masaki39/marp-mcp)
|
|
346
|
+
- [Marp Documentation](https://marpit.marp.app/)
|
|
347
|
+
- [MCP Documentation](https://modelcontextprotocol.io)
|
package/build/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Marp MCP Server
|
|
4
|
+
* A Model Context Protocol server for managing Marp presentation projects
|
|
5
|
+
* Optimized for Claude Code and Cursor
|
|
6
|
+
*/
|
|
7
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
|
+
// Import tools
|
|
10
|
+
import { initPresentationSchema, initPresentation } from "./tools/init.js";
|
|
11
|
+
import { generateSlideSchema, generateSlide, listSlideLayoutsSchema, listSlideLayouts, } from "./tools/generate.js";
|
|
12
|
+
// Create server instance
|
|
13
|
+
const server = new McpServer({
|
|
14
|
+
name: "marp-mcp",
|
|
15
|
+
version: "2.0.0",
|
|
16
|
+
capabilities: {
|
|
17
|
+
resources: {},
|
|
18
|
+
tools: {},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
// Register tools
|
|
22
|
+
server.tool("init_presentation", "Initialize a new Marp presentation project with directory structure, theme, and templates", initPresentationSchema.shape, initPresentation);
|
|
23
|
+
server.tool("generate_slide", "Generate a slide using academic theme layouts (returns markdown string for copy-paste)", generateSlideSchema.shape, generateSlide);
|
|
24
|
+
server.tool("list_slide_layouts", "List all available slide layouts with their parameters and descriptions", listSlideLayoutsSchema.shape, listSlideLayouts);
|
|
25
|
+
// Start the server
|
|
26
|
+
async function main() {
|
|
27
|
+
const transport = new StdioServerTransport();
|
|
28
|
+
await server.connect(transport);
|
|
29
|
+
console.error("Marp MCP Server v2.0 running on stdio");
|
|
30
|
+
console.error("Optimized for Claude Code and Cursor");
|
|
31
|
+
console.error("Tools: init_presentation, generate_slide, list_slide_layouts");
|
|
32
|
+
}
|
|
33
|
+
main().catch((error) => {
|
|
34
|
+
console.error("Fatal error in main():", error);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
});
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Academic theme layout definitions
|
|
3
|
+
* Based on academic_custom.css theme
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Title slide layout - centered title and subtitle
|
|
7
|
+
*/
|
|
8
|
+
export const titleLayout = {
|
|
9
|
+
name: "title",
|
|
10
|
+
description: "Title slide with centered title and subtitle",
|
|
11
|
+
className: "title",
|
|
12
|
+
params: {
|
|
13
|
+
title: {
|
|
14
|
+
type: "string",
|
|
15
|
+
description: "Main title",
|
|
16
|
+
required: true,
|
|
17
|
+
maxLength: 60,
|
|
18
|
+
},
|
|
19
|
+
subtitle: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "Subtitle",
|
|
22
|
+
required: false,
|
|
23
|
+
maxLength: 100,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
template: (params) => {
|
|
27
|
+
let slide = `# ${params.title}\n`;
|
|
28
|
+
if (params.subtitle) {
|
|
29
|
+
slide += `## ${params.subtitle}\n`;
|
|
30
|
+
}
|
|
31
|
+
slide += `\n<!-- _class: title -->`;
|
|
32
|
+
return slide;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Lead slide layout - left-aligned with maroon color
|
|
37
|
+
*/
|
|
38
|
+
export const leadLayout = {
|
|
39
|
+
name: "lead",
|
|
40
|
+
description: "Lead slide with left-aligned maroon headings",
|
|
41
|
+
className: "lead",
|
|
42
|
+
params: {
|
|
43
|
+
heading: {
|
|
44
|
+
type: "string",
|
|
45
|
+
description: "Main heading",
|
|
46
|
+
required: true,
|
|
47
|
+
maxLength: 80,
|
|
48
|
+
},
|
|
49
|
+
content: {
|
|
50
|
+
type: "string",
|
|
51
|
+
description: "Content (markdown supported)",
|
|
52
|
+
required: false,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
template: (params) => {
|
|
56
|
+
let slide = `# ${params.heading}\n`;
|
|
57
|
+
if (params.content) {
|
|
58
|
+
slide += `\n${params.content}\n`;
|
|
59
|
+
}
|
|
60
|
+
slide += `\n<!-- _class: lead -->`;
|
|
61
|
+
return slide;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Default content slide
|
|
66
|
+
*/
|
|
67
|
+
export const contentLayout = {
|
|
68
|
+
name: "content",
|
|
69
|
+
description: "Standard content slide with optional heading",
|
|
70
|
+
params: {
|
|
71
|
+
heading: {
|
|
72
|
+
type: "string",
|
|
73
|
+
description: "Slide heading",
|
|
74
|
+
required: false,
|
|
75
|
+
maxLength: 80,
|
|
76
|
+
},
|
|
77
|
+
content: {
|
|
78
|
+
type: "string",
|
|
79
|
+
description: "Content (markdown supported)",
|
|
80
|
+
required: true,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
template: (params) => {
|
|
84
|
+
let slide = "";
|
|
85
|
+
if (params.heading) {
|
|
86
|
+
slide += `# ${params.heading}\n\n`;
|
|
87
|
+
}
|
|
88
|
+
slide += params.content;
|
|
89
|
+
return slide;
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Table slide layout with various size/alignment options
|
|
94
|
+
*/
|
|
95
|
+
export const tableLayout = {
|
|
96
|
+
name: "table",
|
|
97
|
+
description: "Table slide with customizable size and alignment",
|
|
98
|
+
params: {
|
|
99
|
+
heading: {
|
|
100
|
+
type: "string",
|
|
101
|
+
description: "Slide heading",
|
|
102
|
+
required: false,
|
|
103
|
+
maxLength: 80,
|
|
104
|
+
},
|
|
105
|
+
tableMarkdown: {
|
|
106
|
+
type: "string",
|
|
107
|
+
description: "Table in markdown format",
|
|
108
|
+
required: true,
|
|
109
|
+
},
|
|
110
|
+
tableClass: {
|
|
111
|
+
type: "string",
|
|
112
|
+
description: "Table class: center, 100, tiny, small, large (can combine with spaces)",
|
|
113
|
+
required: false,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
template: (params) => {
|
|
117
|
+
let slide = "";
|
|
118
|
+
if (params.heading) {
|
|
119
|
+
slide += `# ${params.heading}\n\n`;
|
|
120
|
+
}
|
|
121
|
+
slide += params.tableMarkdown;
|
|
122
|
+
// Build class string
|
|
123
|
+
const classes = [];
|
|
124
|
+
if (params.tableClass) {
|
|
125
|
+
const tableOpts = params.tableClass.split(/\s+/);
|
|
126
|
+
tableOpts.forEach((opt) => {
|
|
127
|
+
if (opt === "center")
|
|
128
|
+
classes.push("table-center");
|
|
129
|
+
else if (opt === "100")
|
|
130
|
+
classes.push("table-100");
|
|
131
|
+
else if (["tiny", "small", "large"].includes(opt))
|
|
132
|
+
classes.push(`table-${opt}`);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (classes.length > 0) {
|
|
136
|
+
slide += `\n\n<!-- _class: ${classes.join(" ")} -->`;
|
|
137
|
+
}
|
|
138
|
+
return slide;
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Multi-column layout using double blockquote syntax
|
|
143
|
+
*/
|
|
144
|
+
export const multiColumnLayout = {
|
|
145
|
+
name: "multi-column",
|
|
146
|
+
description: "Multi-column layout (2-3 columns) using double blockquote",
|
|
147
|
+
params: {
|
|
148
|
+
heading: {
|
|
149
|
+
type: "string",
|
|
150
|
+
description: "Slide heading",
|
|
151
|
+
required: false,
|
|
152
|
+
maxLength: 80,
|
|
153
|
+
},
|
|
154
|
+
columns: {
|
|
155
|
+
type: "array",
|
|
156
|
+
description: "Array of column contents (markdown supported)",
|
|
157
|
+
required: true,
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
template: (params) => {
|
|
161
|
+
let slide = "";
|
|
162
|
+
if (params.heading) {
|
|
163
|
+
slide += `# ${params.heading}\n\n`;
|
|
164
|
+
}
|
|
165
|
+
// Generate double blockquote for each column
|
|
166
|
+
const columns = params.columns;
|
|
167
|
+
const columnBlocks = columns.map((col) => {
|
|
168
|
+
const lines = col.trim().split("\n");
|
|
169
|
+
return lines.map((line) => `> > ${line}`).join("\n");
|
|
170
|
+
});
|
|
171
|
+
slide += columnBlocks.join("\n>\n");
|
|
172
|
+
return slide;
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Quote/citation slide with footer blockquote
|
|
177
|
+
*/
|
|
178
|
+
export const quoteLayout = {
|
|
179
|
+
name: "quote",
|
|
180
|
+
description: "Quote slide with citation in footer",
|
|
181
|
+
params: {
|
|
182
|
+
heading: {
|
|
183
|
+
type: "string",
|
|
184
|
+
description: "Slide heading",
|
|
185
|
+
required: false,
|
|
186
|
+
maxLength: 80,
|
|
187
|
+
},
|
|
188
|
+
content: {
|
|
189
|
+
type: "string",
|
|
190
|
+
description: "Main content before quote",
|
|
191
|
+
required: false,
|
|
192
|
+
},
|
|
193
|
+
quote: {
|
|
194
|
+
type: "string",
|
|
195
|
+
description: "Quote text",
|
|
196
|
+
required: true,
|
|
197
|
+
maxLength: 300,
|
|
198
|
+
},
|
|
199
|
+
citation: {
|
|
200
|
+
type: "string",
|
|
201
|
+
description: "Citation/source",
|
|
202
|
+
required: false,
|
|
203
|
+
maxLength: 100,
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
template: (params) => {
|
|
207
|
+
let slide = "";
|
|
208
|
+
if (params.heading) {
|
|
209
|
+
slide += `# ${params.heading}\n\n`;
|
|
210
|
+
}
|
|
211
|
+
if (params.content) {
|
|
212
|
+
slide += `${params.content}\n\n`;
|
|
213
|
+
}
|
|
214
|
+
// Footer blockquote
|
|
215
|
+
slide += `> ${params.quote}`;
|
|
216
|
+
if (params.citation) {
|
|
217
|
+
slide += ` — ${params.citation}`;
|
|
218
|
+
}
|
|
219
|
+
return slide;
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* All available layouts
|
|
224
|
+
*/
|
|
225
|
+
export const academicLayouts = {
|
|
226
|
+
title: titleLayout,
|
|
227
|
+
lead: leadLayout,
|
|
228
|
+
content: contentLayout,
|
|
229
|
+
table: tableLayout,
|
|
230
|
+
"multi-column": multiColumnLayout,
|
|
231
|
+
quote: quoteLayout,
|
|
232
|
+
};
|
|
233
|
+
/**
|
|
234
|
+
* Get layout by name
|
|
235
|
+
*/
|
|
236
|
+
export function getLayout(name) {
|
|
237
|
+
return academicLayouts[name] || null;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Get all layout names
|
|
241
|
+
*/
|
|
242
|
+
export function getLayoutNames() {
|
|
243
|
+
return Object.keys(academicLayouts);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Get all layouts info
|
|
247
|
+
*/
|
|
248
|
+
export function getAllLayoutsInfo() {
|
|
249
|
+
return Object.entries(academicLayouts).map(([name, layout]) => ({
|
|
250
|
+
name,
|
|
251
|
+
description: layout.description,
|
|
252
|
+
className: layout.className,
|
|
253
|
+
params: Object.entries(layout.params).map(([paramName, paramDef]) => ({
|
|
254
|
+
name: paramName,
|
|
255
|
+
...paramDef,
|
|
256
|
+
})),
|
|
257
|
+
}));
|
|
258
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# {{PRESENTATION_TITLE}}
|
|
2
|
+
|
|
3
|
+
{{PRESENTATION_DESCRIPTION}}
|
|
4
|
+
|
|
5
|
+
## ディレクトリ構造
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
.
|
|
9
|
+
├── slides.md # メインスライド
|
|
10
|
+
├── themes/
|
|
11
|
+
│ └── academic_custom.css # カスタムテーマ
|
|
12
|
+
├── attachments/
|
|
13
|
+
│ ├── images/ # 画像ファイル
|
|
14
|
+
│ ├── videos/ # 動画ファイル
|
|
15
|
+
│ └── data/ # データファイル
|
|
16
|
+
└── README.md # このファイル
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## ビルド方法
|
|
20
|
+
|
|
21
|
+
### PDF出力
|
|
22
|
+
\`\`\`bash
|
|
23
|
+
marp slides.md -o slides.pdf
|
|
24
|
+
\`\`\`
|
|
25
|
+
|
|
26
|
+
### HTML出力
|
|
27
|
+
\`\`\`bash
|
|
28
|
+
marp slides.md -o slides.html
|
|
29
|
+
\`\`\`
|
|
30
|
+
|
|
31
|
+
### プレビュー(VSCode拡張)
|
|
32
|
+
Marp for VS Code拡張をインストールして、`slides.md`を開くとプレビューが表示されます。
|
|
33
|
+
|
|
34
|
+
## テーマについて
|
|
35
|
+
|
|
36
|
+
このプロジェクトでは`academic_custom`テーマを使用しています。
|
|
37
|
+
|
|
38
|
+
### 利用可能なクラス
|
|
39
|
+
|
|
40
|
+
- `.title` - タイトルスライド(中央揃え)
|
|
41
|
+
- `.lead` - リードスライド(左揃え、マルーン色)
|
|
42
|
+
- `.table-center` - テーブル中央揃え
|
|
43
|
+
- `.table-100` - テーブル幅100%
|
|
44
|
+
- `.table-tiny/small/large` - テーブルサイズ調整
|
|
45
|
+
|
|
46
|
+
### マルチカラムの使い方
|
|
47
|
+
|
|
48
|
+
二重blockquote記法を使用します:
|
|
49
|
+
|
|
50
|
+
\`\`\`markdown
|
|
51
|
+
> > 左カラムの内容
|
|
52
|
+
> > - リスト1
|
|
53
|
+
>
|
|
54
|
+
> > 右カラムの内容
|
|
55
|
+
> > - リスト2
|
|
56
|
+
\`\`\`
|
|
57
|
+
|
|
58
|
+
## アセットの配置
|
|
59
|
+
|
|
60
|
+
- 画像: `attachments/images/`
|
|
61
|
+
- 動画: `attachments/videos/`
|
|
62
|
+
- データ: `attachments/data/`
|
|
63
|
+
|
|
64
|
+
画像の参照例:
|
|
65
|
+
\`\`\`markdown
|
|
66
|
+

|
|
67
|
+
\`\`\`
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/* @theme academic */
|
|
2
|
+
/* Author: kaisugi https://github.com/kaisugi/ */
|
|
3
|
+
/* MIT license https://github.com/kaisugi/marp-theme-academic/blob/main/LICENSE */
|
|
4
|
+
|
|
5
|
+
@import 'gaia';
|
|
6
|
+
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap');
|
|
7
|
+
@import url('https://fonts.googleapis.com/css2?family=Source+Code+Pro&display=swap');
|
|
8
|
+
|
|
9
|
+
:root {
|
|
10
|
+
--color-background: #fff;
|
|
11
|
+
--color-foreground: #333;
|
|
12
|
+
--color-highlight: #800000;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
section {
|
|
16
|
+
background-image: none;
|
|
17
|
+
font-family: 'Noto Sans JP', sans-serif;
|
|
18
|
+
padding-top: 90px;
|
|
19
|
+
padding-left: 40px;
|
|
20
|
+
padding-right: 40px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* https://github.com/marp-team/marpit/issues/271 */
|
|
24
|
+
section::after {
|
|
25
|
+
font-weight: 700;
|
|
26
|
+
content: attr(data-marpit-pagination) '/' attr(data-marpit-pagination-total);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
ul ul {
|
|
30
|
+
font-size: 0.9em;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
section.lead h1 {
|
|
34
|
+
color: #800000;
|
|
35
|
+
text-align: left;
|
|
36
|
+
}
|
|
37
|
+
section.lead h1 strong {
|
|
38
|
+
-webkit-text-stroke: 1px #800000;
|
|
39
|
+
}
|
|
40
|
+
section.lead h2 {
|
|
41
|
+
color: #800000;
|
|
42
|
+
text-align: left;
|
|
43
|
+
}
|
|
44
|
+
section.lead h2 strong {
|
|
45
|
+
-webkit-text-stroke: 1px #800000;
|
|
46
|
+
}
|
|
47
|
+
section.lead h3 {
|
|
48
|
+
color: #800000;
|
|
49
|
+
text-align: left;
|
|
50
|
+
}
|
|
51
|
+
section.lead h3 strong {
|
|
52
|
+
-webkit-text-stroke: 1px #800000;
|
|
53
|
+
}
|
|
54
|
+
section.lead h4 {
|
|
55
|
+
color: #800000;
|
|
56
|
+
text-align: left;
|
|
57
|
+
}
|
|
58
|
+
section.lead h4 strong {
|
|
59
|
+
-webkit-text-stroke: 1px #800000;
|
|
60
|
+
}
|
|
61
|
+
section.lead h5 {
|
|
62
|
+
color: #800000;
|
|
63
|
+
text-align: left;
|
|
64
|
+
}
|
|
65
|
+
section.lead h5 strong {
|
|
66
|
+
-webkit-text-stroke: 1px #800000;
|
|
67
|
+
}
|
|
68
|
+
section.lead p {
|
|
69
|
+
text-align: right;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
header {
|
|
73
|
+
background-color: #800000;
|
|
74
|
+
color: #fff;
|
|
75
|
+
font-size: 1em;
|
|
76
|
+
font-weight: 700;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
section > blockquote:not(:has(blockquote)) {
|
|
80
|
+
max-width: 90%;
|
|
81
|
+
border-top: 0.1em dashed #555;
|
|
82
|
+
font-size: 60%;
|
|
83
|
+
position: absolute;
|
|
84
|
+
bottom: 20px;
|
|
85
|
+
}
|
|
86
|
+
blockquote::before {
|
|
87
|
+
content: "";
|
|
88
|
+
}
|
|
89
|
+
blockquote::after {
|
|
90
|
+
content: "";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
img[alt~="center"] {
|
|
94
|
+
display: block;
|
|
95
|
+
margin: 0 auto;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
code {
|
|
99
|
+
font-family: 'Source Code Pro', monospace;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/*academicに追記*/
|
|
103
|
+
|
|
104
|
+
/* タイトルクラス */
|
|
105
|
+
section.title {
|
|
106
|
+
display: flex;
|
|
107
|
+
flex-direction: column;
|
|
108
|
+
justify-content: center;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
section.title h1 {
|
|
112
|
+
text-align: center;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
section.title h2 {
|
|
116
|
+
text-align: center;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* tableクラス */
|
|
120
|
+
section.table-100 table {
|
|
121
|
+
width: 100%;
|
|
122
|
+
text-align: center;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
section.table-center table {
|
|
126
|
+
margin: 0 auto;
|
|
127
|
+
margin-top: 15px;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
section.table-tiny table {
|
|
131
|
+
font-size: 0.7em;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
section.table-small table {
|
|
135
|
+
font-size: 0.8em;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
section.table-large table {
|
|
139
|
+
font-size: 1.1em;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* 二重ブロッククオート記法でのマルチコラム設定 */
|
|
143
|
+
section blockquote:has(blockquote) {
|
|
144
|
+
display: flex;
|
|
145
|
+
flex-direction: row;
|
|
146
|
+
gap: 10px;
|
|
147
|
+
align-items: flex-start;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* blockquoteのみをflexアイテムに(安全な方法) */
|
|
151
|
+
section blockquote > blockquote {
|
|
152
|
+
flex: 1;
|
|
153
|
+
margin: 0;
|
|
154
|
+
padding: 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* 内側blockquoteの中身を通常のブロック表示に */
|
|
158
|
+
section blockquote > blockquote * {
|
|
159
|
+
display: revert; /* 元の表示方式に戻す */
|
|
160
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
marp: true
|
|
3
|
+
theme: academic_custom
|
|
4
|
+
size: 16:9
|
|
5
|
+
paginate: true
|
|
6
|
+
header: '{{PRESENTATION_TITLE}}'
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<!-- _class: title -->
|
|
10
|
+
|
|
11
|
+
# {{PRESENTATION_TITLE}}
|
|
12
|
+
## {{PRESENTATION_SUBTITLE}}
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# はじめに
|
|
17
|
+
|
|
18
|
+
プレゼンテーションの内容をここに記述します。
|
|
19
|
+
|
|
20
|
+
- ポイント1
|
|
21
|
+
- ポイント2
|
|
22
|
+
- ポイント3
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
# まとめ
|
|
27
|
+
|
|
28
|
+
まとめの内容をここに記述します。
|
|
29
|
+
|
|
30
|
+
> 引用や注釈はこのように記述します
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool: generate_slide
|
|
3
|
+
* Generate a slide using academic theme layouts
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { getLayout, getLayoutNames } from "../layouts/academic.js";
|
|
7
|
+
export const generateSlideSchema = z.object({
|
|
8
|
+
layoutType: z.string().describe("Layout type to use (title, lead, content, table, multi-column, quote)"),
|
|
9
|
+
params: z.record(z.any()).describe("Parameters for the layout template"),
|
|
10
|
+
});
|
|
11
|
+
export async function generateSlide({ layoutType, params, }) {
|
|
12
|
+
const layout = getLayout(layoutType);
|
|
13
|
+
if (!layout) {
|
|
14
|
+
return {
|
|
15
|
+
content: [
|
|
16
|
+
{
|
|
17
|
+
type: "text",
|
|
18
|
+
text: `Error: Unknown layout type "${layoutType}". Available layouts: ${getLayoutNames().join(", ")}`,
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
// Validate required parameters
|
|
24
|
+
const missingParams = [];
|
|
25
|
+
for (const [paramName, paramDef] of Object.entries(layout.params)) {
|
|
26
|
+
if (paramDef.required && !params[paramName]) {
|
|
27
|
+
missingParams.push(paramName);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (missingParams.length > 0) {
|
|
31
|
+
return {
|
|
32
|
+
content: [
|
|
33
|
+
{
|
|
34
|
+
type: "text",
|
|
35
|
+
text: `Error: Missing required parameters: ${missingParams.join(", ")}`,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// Validate parameter types and lengths
|
|
41
|
+
for (const [paramName, value] of Object.entries(params)) {
|
|
42
|
+
const paramDef = layout.params[paramName];
|
|
43
|
+
if (!paramDef)
|
|
44
|
+
continue;
|
|
45
|
+
// Check type for string parameters
|
|
46
|
+
if (paramDef.type === "string" && typeof value !== "string") {
|
|
47
|
+
return {
|
|
48
|
+
content: [
|
|
49
|
+
{
|
|
50
|
+
type: "text",
|
|
51
|
+
text: `Error: Parameter "${paramName}" must be a string`,
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// Check type for array parameters
|
|
57
|
+
if (paramDef.type === "array" && !Array.isArray(value)) {
|
|
58
|
+
return {
|
|
59
|
+
content: [
|
|
60
|
+
{
|
|
61
|
+
type: "text",
|
|
62
|
+
text: `Error: Parameter "${paramName}" must be an array`,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
// Check max length for string parameters
|
|
68
|
+
if (paramDef.type === "string" && paramDef.maxLength && typeof value === "string") {
|
|
69
|
+
if (value.length > paramDef.maxLength) {
|
|
70
|
+
return {
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
type: "text",
|
|
74
|
+
text: `Error: Parameter "${paramName}" exceeds maximum length of ${paramDef.maxLength} characters (current: ${value.length})`,
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Generate slide content
|
|
82
|
+
try {
|
|
83
|
+
const slideContent = layout.template(params);
|
|
84
|
+
return {
|
|
85
|
+
content: [
|
|
86
|
+
{
|
|
87
|
+
type: "text",
|
|
88
|
+
text: JSON.stringify({
|
|
89
|
+
success: true,
|
|
90
|
+
layoutType,
|
|
91
|
+
markdown: slideContent,
|
|
92
|
+
}, null, 2),
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
return {
|
|
99
|
+
content: [
|
|
100
|
+
{
|
|
101
|
+
type: "text",
|
|
102
|
+
text: `Error generating slide: ${error instanceof Error ? error.message : String(error)}`,
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Tool: list_slide_layouts
|
|
110
|
+
* List all available slide layouts with their parameters
|
|
111
|
+
*/
|
|
112
|
+
export const listSlideLayoutsSchema = z.object({});
|
|
113
|
+
export async function listSlideLayouts() {
|
|
114
|
+
const { getAllLayoutsInfo } = await import("../layouts/academic.js");
|
|
115
|
+
const layoutsInfo = getAllLayoutsInfo();
|
|
116
|
+
return {
|
|
117
|
+
content: [
|
|
118
|
+
{
|
|
119
|
+
type: "text",
|
|
120
|
+
text: JSON.stringify({
|
|
121
|
+
theme: "academic",
|
|
122
|
+
layouts: layoutsInfo,
|
|
123
|
+
}, null, 2),
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool: init_presentation
|
|
3
|
+
* Initialize a Marp presentation project directory structure
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { promises as fs } from "fs";
|
|
7
|
+
import path from "path";
|
|
8
|
+
// Import templates as strings
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
import { dirname } from "path";
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
export const initPresentationSchema = z.object({
|
|
14
|
+
projectPath: z.string().describe("Path where the project directory will be created"),
|
|
15
|
+
projectName: z.string().describe("Name of the presentation project"),
|
|
16
|
+
presentationTitle: z.string().describe("Title of the presentation"),
|
|
17
|
+
presentationSubtitle: z.string().optional().describe("Subtitle of the presentation"),
|
|
18
|
+
description: z.string().optional().describe("Brief description of the presentation"),
|
|
19
|
+
});
|
|
20
|
+
export async function initPresentation({ projectPath, projectName, presentationTitle, presentationSubtitle, description, }) {
|
|
21
|
+
try {
|
|
22
|
+
// Create project directory
|
|
23
|
+
const fullPath = path.resolve(projectPath, projectName);
|
|
24
|
+
// Check if directory already exists
|
|
25
|
+
try {
|
|
26
|
+
await fs.access(fullPath);
|
|
27
|
+
return {
|
|
28
|
+
content: [
|
|
29
|
+
{
|
|
30
|
+
type: "text",
|
|
31
|
+
text: `Error: Directory already exists at ${fullPath}`,
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Directory doesn't exist, proceed
|
|
38
|
+
}
|
|
39
|
+
// Create directory structure
|
|
40
|
+
await fs.mkdir(fullPath, { recursive: true });
|
|
41
|
+
await fs.mkdir(path.join(fullPath, "themes"), { recursive: true });
|
|
42
|
+
await fs.mkdir(path.join(fullPath, "attachments", "images"), { recursive: true });
|
|
43
|
+
await fs.mkdir(path.join(fullPath, "attachments", "videos"), { recursive: true });
|
|
44
|
+
await fs.mkdir(path.join(fullPath, "attachments", "data"), { recursive: true });
|
|
45
|
+
// Read template files
|
|
46
|
+
const templatesDir = path.join(__dirname, "..", "templates");
|
|
47
|
+
const cssTemplate = await fs.readFile(path.join(templatesDir, "academic_custom.css"), "utf-8");
|
|
48
|
+
let slidesTemplate = await fs.readFile(path.join(templatesDir, "slides.template.md"), "utf-8");
|
|
49
|
+
let readmeTemplate = await fs.readFile(path.join(templatesDir, "README.template.md"), "utf-8");
|
|
50
|
+
const gitignoreTemplate = await fs.readFile(path.join(templatesDir, "gitignore.template"), "utf-8");
|
|
51
|
+
// Replace placeholders
|
|
52
|
+
slidesTemplate = slidesTemplate
|
|
53
|
+
.replace(/\{\{PRESENTATION_TITLE\}\}/g, presentationTitle)
|
|
54
|
+
.replace(/\{\{PRESENTATION_SUBTITLE\}\}/g, presentationSubtitle || "");
|
|
55
|
+
readmeTemplate = readmeTemplate
|
|
56
|
+
.replace(/\{\{PRESENTATION_TITLE\}\}/g, presentationTitle)
|
|
57
|
+
.replace(/\{\{PRESENTATION_DESCRIPTION\}\}/g, description || "");
|
|
58
|
+
// Write files
|
|
59
|
+
await fs.writeFile(path.join(fullPath, "themes", "academic_custom.css"), cssTemplate);
|
|
60
|
+
await fs.writeFile(path.join(fullPath, "slides.md"), slidesTemplate);
|
|
61
|
+
await fs.writeFile(path.join(fullPath, "README.md"), readmeTemplate);
|
|
62
|
+
await fs.writeFile(path.join(fullPath, ".gitignore"), gitignoreTemplate);
|
|
63
|
+
return {
|
|
64
|
+
content: [
|
|
65
|
+
{
|
|
66
|
+
type: "text",
|
|
67
|
+
text: JSON.stringify({
|
|
68
|
+
success: true,
|
|
69
|
+
message: `Successfully initialized presentation project at ${fullPath}`,
|
|
70
|
+
structure: {
|
|
71
|
+
root: fullPath,
|
|
72
|
+
files: [
|
|
73
|
+
"slides.md",
|
|
74
|
+
"README.md",
|
|
75
|
+
".gitignore",
|
|
76
|
+
"themes/academic_custom.css",
|
|
77
|
+
],
|
|
78
|
+
directories: [
|
|
79
|
+
"themes/",
|
|
80
|
+
"attachments/images/",
|
|
81
|
+
"attachments/videos/",
|
|
82
|
+
"attachments/data/",
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
}, null, 2),
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
return {
|
|
92
|
+
content: [
|
|
93
|
+
{
|
|
94
|
+
type: "text",
|
|
95
|
+
text: `Error initializing presentation: ${error instanceof Error ? error.message : String(error)}`,
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
package/build/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@masaki39/marp-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Marp presentation management with academic theme support",
|
|
5
|
+
"main": "./build/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"marp-mcp": "./build/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc && cp -r src/templates build/ && chmod 755 build/index.js",
|
|
12
|
+
"prepublishOnly": "npm run build",
|
|
13
|
+
"prepare": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"build",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"mcp",
|
|
22
|
+
"marp",
|
|
23
|
+
"presentation",
|
|
24
|
+
"slides",
|
|
25
|
+
"markdown",
|
|
26
|
+
"model-context-protocol",
|
|
27
|
+
"claude",
|
|
28
|
+
"academic-theme"
|
|
29
|
+
],
|
|
30
|
+
"author": "masaki39",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/masaki39/marp-mcp.git"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/masaki39/marp-mcp#readme",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/masaki39/marp-mcp/issues"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18.0.0"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@modelcontextprotocol/sdk": "^1.19.1",
|
|
45
|
+
"zod": "^3.25.76"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^24.6.2",
|
|
49
|
+
"typescript": "^5.9.3"
|
|
50
|
+
}
|
|
51
|
+
}
|