@ssemble/mcp-server 1.0.6 → 1.1.1
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/package.json +8 -1
- package/server.json +3 -3
- package/src/apps/dist/game-videos-app.html +163 -0
- package/src/apps/dist/meme-hooks-app.html +165 -0
- package/src/apps/dist/music-app.html +174 -0
- package/src/apps/dist/shorts-app.html +218 -0
- package/src/apps/dist/templates-app.html +157 -0
- package/src/apps/game-videos-app.html +92 -0
- package/src/apps/game-videos-app.js +102 -0
- package/src/apps/meme-hooks-app.html +95 -0
- package/src/apps/meme-hooks-app.js +94 -0
- package/src/apps/music-app.html +96 -0
- package/src/apps/music-app.js +151 -0
- package/src/apps/shorts-app.html +135 -0
- package/src/apps/shorts-app.js +102 -0
- package/src/apps/templates-app.html +87 -0
- package/src/apps/templates-app.js +74 -0
- package/src/assets/thumbnails/ali.jpg +0 -0
- package/src/assets/thumbnails/beast.jpg +0 -0
- package/src/assets/thumbnails/box.jpg +0 -0
- package/src/assets/thumbnails/hormozi1.jpg +0 -0
- package/src/assets/thumbnails/hormozi2.jpg +0 -0
- package/src/assets/thumbnails/iman.jpg +0 -0
- package/src/assets/thumbnails/iman2.jpg +0 -0
- package/src/assets/thumbnails/karaoke.jpg +0 -0
- package/src/assets/thumbnails/simple.jpg +0 -0
- package/src/index.js +2 -0
- package/src/resources/app-resources.js +149 -0
- package/src/tools/create-short.js +10 -0
- package/src/tools/get-shorts.js +57 -6
- package/src/tools/get-status.js +20 -3
- package/src/tools/list-game-videos.js +34 -4
- package/src/tools/list-meme-hooks.js +34 -4
- package/src/tools/list-music.js +34 -4
- package/src/tools/list-templates.js +64 -5
- package/src/utils/format.js +41 -9
- package/src/utils/session-store.js +30 -0
|
@@ -1,14 +1,73 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { registerAppTool } from '@modelcontextprotocol/ext-apps/server';
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { formatError, formatRateLimits } from '../utils/format.js';
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const THUMB_DIR = join(__dirname, '..', 'assets', 'thumbnails');
|
|
9
|
+
|
|
10
|
+
function loadThumbnail(templateName) {
|
|
11
|
+
try {
|
|
12
|
+
const data = readFileSync(join(THUMB_DIR, `${templateName}.jpg`));
|
|
13
|
+
return { data: data.toString('base64'), mimeType: 'image/jpeg' };
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
2
18
|
|
|
3
19
|
export function registerListTemplates(server, client) {
|
|
4
|
-
|
|
20
|
+
registerAppTool(
|
|
21
|
+
server,
|
|
5
22
|
'list_templates',
|
|
6
|
-
|
|
7
|
-
|
|
23
|
+
{
|
|
24
|
+
title: 'List Templates',
|
|
25
|
+
description:
|
|
26
|
+
'List all available caption style templates with preview thumbnails, names, and IDs. Use the templateId when creating shorts.',
|
|
27
|
+
_meta: {
|
|
28
|
+
ui: { resourceUri: 'ui://ssemble/templates-app.html' },
|
|
29
|
+
},
|
|
30
|
+
},
|
|
8
31
|
async () => {
|
|
9
32
|
try {
|
|
10
33
|
const result = await client.listTemplates();
|
|
11
|
-
|
|
34
|
+
const d = result.data;
|
|
35
|
+
const templates = d.templates || [];
|
|
36
|
+
|
|
37
|
+
// Text + image content for non-app clients (Claude Code, Cursor, etc.)
|
|
38
|
+
const content = [];
|
|
39
|
+
content.push({
|
|
40
|
+
type: 'text',
|
|
41
|
+
text: `## Caption Templates (${templates.length} available)\n`,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
templates.forEach((t) => {
|
|
45
|
+
const isDefault = t.templateId === d.defaultTemplateId;
|
|
46
|
+
const thumb = loadThumbnail(t.name);
|
|
47
|
+
if (thumb) {
|
|
48
|
+
content.push({ type: 'image', data: thumb.data, mimeType: thumb.mimeType });
|
|
49
|
+
}
|
|
50
|
+
const label = `**${t.displayName || t.name}**${isDefault ? ' _(default)_' : ''}`;
|
|
51
|
+
content.push({ type: 'text', text: label });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
let footer = `\nUse the \`templateId\` field when calling \`create_short\` to pick a style.`;
|
|
55
|
+
footer += formatRateLimits(result.rateLimits);
|
|
56
|
+
content.push({ type: 'text', text: footer });
|
|
57
|
+
|
|
58
|
+
// Structured content for the MCP App
|
|
59
|
+
const structuredContent = {
|
|
60
|
+
templates: templates.map((t) => ({
|
|
61
|
+
templateId: t.templateId,
|
|
62
|
+
name: t.name,
|
|
63
|
+
displayName: t.displayName || t.name,
|
|
64
|
+
gif: t.gif || '',
|
|
65
|
+
preview: t.preview || '',
|
|
66
|
+
})),
|
|
67
|
+
defaultTemplateId: d.defaultTemplateId,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return { content, structuredContent };
|
|
12
71
|
} catch (error) {
|
|
13
72
|
return { content: [{ type: 'text', text: formatError(error) }], isError: true };
|
|
14
73
|
}
|
package/src/utils/format.js
CHANGED
|
@@ -99,23 +99,51 @@ export function formatListRequestsResponse(result) {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
let text = `## Your Requests (Page ${p.page}/${p.totalPages}, ${p.totalCount} total)\n\n`;
|
|
102
|
-
text += `| # | Request ID | Status | Progress | Created | Duration |\n`;
|
|
103
|
-
text += `|---|-----------|--------|----------|---------|----------|\n`;
|
|
104
102
|
|
|
105
103
|
requests.forEach((req, i) => {
|
|
104
|
+
const num = (p.page - 1) * p.limit + i + 1;
|
|
106
105
|
const created = req.createdAt ? new Date(req.createdAt).toLocaleDateString() : 'N/A';
|
|
107
|
-
const
|
|
108
|
-
|
|
106
|
+
const label = req.title || req.url || req.fileUrl || 'Unknown source';
|
|
107
|
+
const short = label.length > 60 ? label.substring(0, 57) + '...' : label;
|
|
108
|
+
text += `**${num}. ${short}**\n`;
|
|
109
|
+
text += ` ID: \`${req.requestId}\` | Status: ${req.status} | Progress: ${req.progress || 0}% | Created: ${created}\n\n`;
|
|
109
110
|
});
|
|
110
111
|
|
|
111
112
|
if (p.totalPages > 1) {
|
|
112
|
-
text +=
|
|
113
|
+
text += `Page ${p.page} of ${p.totalPages}. Use \`page\` parameter to browse more.`;
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
text += formatRateLimits(result.rateLimits);
|
|
116
117
|
return text;
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Formats recent requests for the "no requestId given" UX.
|
|
122
|
+
* Used by get_status and get_shorts when called without a requestId.
|
|
123
|
+
* @param {Array} requests - from session store or API list_requests
|
|
124
|
+
* @param {string} tool - 'get_status' or 'get_shorts'
|
|
125
|
+
* @param {boolean} fromApi - true if coming from list_requests API (different field names)
|
|
126
|
+
*/
|
|
127
|
+
export function formatRecentRequests(requests, tool, fromApi = false) {
|
|
128
|
+
if (requests.length === 0) {
|
|
129
|
+
return `No recent requests found. Use \`create_short\` to create one.`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let text = `## Your Recent Requests\n\nWhich request would you like to check?\n\n`;
|
|
133
|
+
|
|
134
|
+
requests.forEach((req, i) => {
|
|
135
|
+
const id = fromApi ? req.requestId : req.requestId;
|
|
136
|
+
const label = req.title || req.url || req.fileUrl || 'Unknown source';
|
|
137
|
+
const short = label.length > 70 ? label.substring(0, 67) + '...' : label;
|
|
138
|
+
const status = req.status || 'unknown';
|
|
139
|
+
text += `**${i + 1}. ${short}**\n`;
|
|
140
|
+
text += ` ID: \`${id}\` | Status: ${status}\n\n`;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
text += `Reply with the number or paste the request ID, and I'll call \`${tool}\` for you.`;
|
|
144
|
+
return text;
|
|
145
|
+
}
|
|
146
|
+
|
|
119
147
|
export function formatTemplatesResponse(result) {
|
|
120
148
|
const d = result.data;
|
|
121
149
|
const templates = d.templates || [];
|
|
@@ -144,14 +172,18 @@ export function formatPaginatedAssets(result, assetKey, assetLabel) {
|
|
|
144
172
|
return `No ${assetLabel} found.${formatRateLimits(result.rateLimits)}`;
|
|
145
173
|
}
|
|
146
174
|
|
|
175
|
+
const hasUrl = items.some((item) => item.url);
|
|
176
|
+
const hasDuration = items[0]?.duration != null;
|
|
177
|
+
|
|
147
178
|
let text = `## ${assetLabel} (Page ${p.page}/${p.totalPages}, ${p.totalCount} total)\n\n`;
|
|
148
|
-
text += `| # | Name |${
|
|
149
|
-
text += `|---|------|${
|
|
179
|
+
text += `| # | Name |${hasDuration ? ' Duration |' : ''}${hasUrl ? ' Listen |' : ''}\n`;
|
|
180
|
+
text += `|---|------|${hasDuration ? '----------|' : ''}${hasUrl ? '--------|' : ''}\n`;
|
|
150
181
|
|
|
151
182
|
items.forEach((item, i) => {
|
|
152
183
|
const num = (p.page - 1) * p.limit + i + 1;
|
|
153
|
-
const durationCol =
|
|
154
|
-
|
|
184
|
+
const durationCol = hasDuration ? ` ${Math.round(item.duration)}s |` : '';
|
|
185
|
+
const listenCol = hasUrl ? ` ${item.url ? `[▶ Play](${item.url})` : ''} |` : '';
|
|
186
|
+
text += `| ${num} | ${item.name} |${durationCol}${listenCol}\n`;
|
|
155
187
|
});
|
|
156
188
|
|
|
157
189
|
if (p.totalPages > 1) {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory session store for request metadata.
|
|
3
|
+
* Maps requestId → { url, title, status, createdAt }
|
|
4
|
+
* Title is populated from get_shorts response once available.
|
|
5
|
+
* Lost when the MCP server process restarts (by design — use list_requests for history).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const store = new Map();
|
|
9
|
+
|
|
10
|
+
export function saveRequest(requestId, meta) {
|
|
11
|
+
store.set(requestId, { ...meta, savedAt: Date.now() });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function updateRequest(requestId, updates) {
|
|
15
|
+
const existing = store.get(requestId);
|
|
16
|
+
if (existing) {
|
|
17
|
+
store.set(requestId, { ...existing, ...updates });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getRecentRequests(limit = 5) {
|
|
22
|
+
return [...store.entries()]
|
|
23
|
+
.sort((a, b) => b[1].savedAt - a[1].savedAt)
|
|
24
|
+
.slice(0, limit)
|
|
25
|
+
.map(([requestId, meta]) => ({ requestId, ...meta }));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function hasRequests() {
|
|
29
|
+
return store.size > 0;
|
|
30
|
+
}
|