@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ssemble/mcp-server",
3
- "version": "1.1.0",
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.0",
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.0",
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 };
@@ -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. Returns video URLs, AI-generated titles, descriptions, viral scores, and durations sorted by viral score.',
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
 
@@ -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 and progress of a short creation request. Returns status (queued/processing/completed/failed), progress percentage, and current step.',
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 };
@@ -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 duration = req.duration ? `${req.duration}s` : 'N/A';
108
- text += `| ${(p.page - 1) * p.limit + i + 1} | \`${req.requestId}\` | ${req.status} | ${req.progress || 0}% | ${created} | ${duration} |\n`;
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 += `\nPage ${p.page} of ${p.totalPages}. Use \`page\` parameter to browse more.`;
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
+ }