@ssemble/mcp-server 1.1.0 → 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 +1 -1
- package/server.json +2 -2
- package/src/tools/create-short.js +10 -0
- package/src/tools/get-shorts.js +22 -3
- package/src/tools/get-status.js +20 -3
- package/src/utils/format.js +33 -5
- package/src/utils/session-store.js +30 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ssemble/mcp-server",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"mcpName": "com.ssemble/mcp-server",
|
|
5
5
|
"description": "MCP server for Ssemble AI Clipping — create viral short-form videos from any AI assistant. Webhook callbacks for n8n, Zapier, and Make automation.",
|
|
6
6
|
"type": "module",
|
package/server.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "com.ssemble/mcp-server",
|
|
4
4
|
"title": "Ssemble AI Clipping",
|
|
5
5
|
"description": "Create AI-powered short-form video clips from YouTube videos. Supports webhook callbacks.",
|
|
6
|
-
"version": "1.1.
|
|
6
|
+
"version": "1.1.1",
|
|
7
7
|
"websiteUrl": "https://www.ssemble.com",
|
|
8
8
|
"repository": {
|
|
9
9
|
"url": "https://github.com/ssembleinc/ssemble-mcp-server",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"registryType": "npm",
|
|
15
15
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
16
16
|
"identifier": "@ssemble/mcp-server",
|
|
17
|
-
"version": "1.1.
|
|
17
|
+
"version": "1.1.1",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|
|
20
20
|
},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { formatCreateShortResponse, formatError } from '../utils/format.js';
|
|
3
|
+
import { saveRequest } from '../utils/session-store.js';
|
|
3
4
|
|
|
4
5
|
const SUPPORTED_LANGUAGES = [
|
|
5
6
|
'en', 'es', 'fr', 'de', 'it', 'pt', 'ru', 'ja', 'ko', 'zh',
|
|
@@ -55,6 +56,15 @@ export function registerCreateShort(server, client) {
|
|
|
55
56
|
|
|
56
57
|
try {
|
|
57
58
|
const result = await client.createShort(params);
|
|
59
|
+
const requestId = result.data?.requestId;
|
|
60
|
+
if (requestId) {
|
|
61
|
+
saveRequest(requestId, {
|
|
62
|
+
url: params.url || params.fileUrl || '',
|
|
63
|
+
title: null,
|
|
64
|
+
status: 'queued',
|
|
65
|
+
createdAt: new Date().toISOString(),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
58
68
|
return { content: [{ type: 'text', text: formatCreateShortResponse(result) }] };
|
|
59
69
|
} catch (error) {
|
|
60
70
|
return { content: [{ type: 'text', text: formatError(error) }], isError: true };
|
package/src/tools/get-shorts.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { registerAppTool } from '@modelcontextprotocol/ext-apps/server';
|
|
3
|
-
import { formatShortsResponse, formatError } from '../utils/format.js';
|
|
3
|
+
import { formatShortsResponse, formatRecentRequests, formatError } from '../utils/format.js';
|
|
4
|
+
import { getRecentRequests, hasRequests, updateRequest } from '../utils/session-store.js';
|
|
4
5
|
|
|
5
6
|
const schema = {
|
|
6
|
-
requestId: z.string().regex(/^[0-9a-fA-F]{24}$/).describe('The request ID from create_short (24-char hex)'),
|
|
7
|
+
requestId: z.string().regex(/^[0-9a-fA-F]{24}$/).describe('The request ID from create_short (24-char hex). Omit to see your recent requests.').optional(),
|
|
7
8
|
};
|
|
8
9
|
|
|
9
10
|
export function registerGetShorts(server, client) {
|
|
@@ -13,15 +14,33 @@ export function registerGetShorts(server, client) {
|
|
|
13
14
|
{
|
|
14
15
|
title: 'Get Shorts',
|
|
15
16
|
description:
|
|
16
|
-
'Retrieve all generated short clips for a completed request.
|
|
17
|
+
'Retrieve all generated short clips for a completed request. Provide a request ID, or omit it to see your recent requests and pick one.',
|
|
17
18
|
inputSchema: schema,
|
|
18
19
|
_meta: {
|
|
19
20
|
ui: { resourceUri: 'ui://ssemble/shorts-app.html' },
|
|
20
21
|
},
|
|
21
22
|
},
|
|
22
23
|
async ({ requestId }) => {
|
|
24
|
+
if (!requestId) {
|
|
25
|
+
// No ID given — show recent requests so user can pick
|
|
26
|
+
if (hasRequests()) {
|
|
27
|
+
const recent = getRecentRequests(5);
|
|
28
|
+
return { content: [{ type: 'text', text: formatRecentRequests(recent, 'get_shorts') }] };
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const result = await client.listRequests({ limit: 5, sortOrder: 'desc' });
|
|
32
|
+
return { content: [{ type: 'text', text: formatRecentRequests(result.data?.requests || [], 'get_shorts', true) }] };
|
|
33
|
+
} catch (error) {
|
|
34
|
+
return { content: [{ type: 'text', text: formatError(error) }], isError: true };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
23
38
|
try {
|
|
24
39
|
const result = await client.getShorts(requestId);
|
|
40
|
+
// Save the video title back to session store for future lookups
|
|
41
|
+
if (result.data?.title) {
|
|
42
|
+
updateRequest(requestId, { title: result.data.title, status: result.data.status });
|
|
43
|
+
}
|
|
25
44
|
const textFallback = formatShortsResponse(result);
|
|
26
45
|
const d = result.data;
|
|
27
46
|
|
package/src/tools/get-status.js
CHANGED
|
@@ -1,18 +1,35 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { formatStatusResponse, formatError } from '../utils/format.js';
|
|
2
|
+
import { formatStatusResponse, formatRecentRequests, formatError } from '../utils/format.js';
|
|
3
|
+
import { getRecentRequests, hasRequests, updateRequest } from '../utils/session-store.js';
|
|
3
4
|
|
|
4
5
|
const schema = {
|
|
5
|
-
requestId: z.string().regex(/^[0-9a-fA-F]{24}$/).describe('The request ID from create_short (24-char hex)'),
|
|
6
|
+
requestId: z.string().regex(/^[0-9a-fA-F]{24}$/).describe('The request ID from create_short (24-char hex). Omit to see your recent requests.').optional(),
|
|
6
7
|
};
|
|
7
8
|
|
|
8
9
|
export function registerGetStatus(server, client) {
|
|
9
10
|
server.tool(
|
|
10
11
|
'get_status',
|
|
11
|
-
'Check processing status
|
|
12
|
+
'Check processing status of a short creation request. Provide a request ID, or omit it to see your recent requests and pick one.',
|
|
12
13
|
schema,
|
|
13
14
|
async ({ requestId }) => {
|
|
15
|
+
if (!requestId) {
|
|
16
|
+
// No ID given — show recent requests so user can pick
|
|
17
|
+
if (hasRequests()) {
|
|
18
|
+
const recent = getRecentRequests(5);
|
|
19
|
+
return { content: [{ type: 'text', text: formatRecentRequests(recent, 'get_status') }] };
|
|
20
|
+
}
|
|
21
|
+
// Nothing in session — fall back to API list
|
|
22
|
+
try {
|
|
23
|
+
const result = await client.listRequests({ limit: 5, sortOrder: 'desc' });
|
|
24
|
+
return { content: [{ type: 'text', text: formatRecentRequests(result.data?.requests || [], 'get_status', true) }] };
|
|
25
|
+
} catch (error) {
|
|
26
|
+
return { content: [{ type: 'text', text: formatError(error) }], isError: true };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
14
30
|
try {
|
|
15
31
|
const result = await client.getStatus(requestId);
|
|
32
|
+
updateRequest(requestId, { status: result.data?.status });
|
|
16
33
|
return { content: [{ type: 'text', text: formatStatusResponse(result) }] };
|
|
17
34
|
} catch (error) {
|
|
18
35
|
return { content: [{ type: 'text', text: formatError(error) }], isError: true };
|
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 || [];
|
|
@@ -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
|
+
}
|