Skip to content

Commit 9f64985

Browse files
authored
adding segment based tools (#33)
changes - bumps version of cloudglue-js sdk - bumps package version - introduces two new tools (segment video chapters and segment video camera shots) which use new segments api - updates readme with description of tools
1 parent d10043a commit 9f64985

6 files changed

Lines changed: 279 additions & 9 deletions

File tree

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ The following Cloudglue tools are available to LLMs through this MCP server:
109109

110110
- **`get_video_metadata`**: Get comprehensive technical metadata about a Cloudglue video file including duration, resolution, file size, processing status, and computed statistics. Use this when you need video specifications, file details, or processing information rather than content analysis. Different from content-focused tools like describe_video.
111111

112+
- **`segment_video_camera_shots`**: Segment videos into camera shots with intelligent cost optimization. Automatically checks for existing shot segmentation jobs before creating new ones. Returns timestamps and metadata for each camera shot detected. Supports Cloudglue URLs and direct HTTP video URLs. Note: YouTube URLs are not supported for segmentation.
113+
114+
- **`segment_video_chapters`**: Segment videos into chapters with intelligent cost optimization. Automatically checks for existing chapter segmentation jobs before creating new ones. Returns timestamps and descriptions for each chapter detected. Supports Cloudglue URLs and direct HTTP video URLs. Note: YouTube URLs are not supported for segmentation.
115+
112116
### **Collection Analysis**
113117

114118
- **`retrieve_summaries`**: Bulk retrieve video summaries and titles from a collection to quickly understand its content and themes. Works with both rich-transcripts and media-descriptions collections. Perfect for getting a high-level overview of what's in a collection, identifying common topics, or determining if a collection contains relevant content for a specific query. Use this as your first step when analyzing a collection - it's more efficient than retrieving full descriptions and helps you determine if you need more detailed information. Only proceed to retrieve_descriptions if you need the full multimodal context for specific videos identified through the summaries. For targeted content discovery, consider using search_video_summaries or search_video_moments instead of browsing through all summaries. **Pagination guidance**: For comprehensive collection analysis, paginate through all summaries (check `has_more` and increment `offset` by `limit`) to ensure complete coverage. Use larger limits (25-50) for efficient bulk analysis, smaller limits (5-10) for targeted exploration.
@@ -147,7 +151,7 @@ The following Cloudglue tools are available to LLMs through this MCP server:
147151
### **When to Use Which Tool**
148152

149153
- **Start exploring**: Use `list_collections` and `list_videos` to explore available content
150-
- **For single videos**: Use `describe_video` or `extract_video_entities`
154+
- **For single videos**: Use `describe_video`, `extract_video_entities`, `segment_video_camera_shots`, or `segment_video_chapters`
151155
- **For collection overview**: Always start with `retrieve_summaries` to efficiently understand what's in a collection
152156
- **For detailed analysis**: Only use `retrieve_descriptions` for specific videos that need full multimodal context, identified through summaries
153157
- **For structured data**: Use `retrieve_entities` for bulk entity extraction

package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@aviaryhq/cloudglue-mcp-server",
33
"type": "module",
4-
"version": "0.1.6",
4+
"version": "0.1.7",
55
"main": "index.js",
66
"bin": {
77
"cloudglue-mcp-server": "build/index.js"
@@ -24,7 +24,7 @@
2424
"license": "Elastic-2.0",
2525
"description": "Cloudglue MCP Server",
2626
"dependencies": {
27-
"@aviaryhq/cloudglue-js": "^0.2.1",
27+
"@aviaryhq/cloudglue-js": "^0.3.0",
2828
"@modelcontextprotocol/sdk": "^1.18.0",
2929
"dotenv": "^16.4.7",
3030
"shx": "^0.4.0",

src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { registerSearchVideoMoments } from "./tools/search-video-moments.js";
1616
import { registerSearchVideoSummaries } from "./tools/search-video-summaries.js";
1717
import { registerGetVideoMetadata } from "./tools/get-video-metadata.js";
1818
import { registerRetrieveSummaries } from "./tools/retrieve-summaries.js";
19+
import { registerSegmentVideoCameraShots } from "./tools/segment-video-camera-shots.js";
20+
import { registerSegmentVideoChapters } from "./tools/segment-video-chapters.js";
1921

2022
// Parse command line arguments
2123
const { values: args } = parseArgs({
@@ -61,6 +63,8 @@ registerRetrieveEntities(server, cgClient);
6163
registerSearchVideoMoments(server, cgClient);
6264
registerSearchVideoSummaries(server, cgClient);
6365
registerGetVideoMetadata(server, cgClient);
66+
registerSegmentVideoCameraShots(server, cgClient);
67+
registerSegmentVideoChapters(server, cgClient);
6468

6569
// Run server
6670
async function main() {
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { z } from "zod";
2+
import { CloudGlue } from "@aviaryhq/cloudglue-js";
3+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4+
5+
export const schema = {
6+
url: z
7+
.string()
8+
.describe("Video URL to segment into camera shots. Supports Cloudglue URLs (cloudglue://files/file-id) or direct HTTP video URLs. For Cloudglue URLs, use the file ID from list_videos. Note: YouTube URLs are not supported for segmentation."),
9+
};
10+
11+
// Helper function to format time in seconds to HH:MM:SS format
12+
function formatTime(seconds: number): string {
13+
const hours = Math.floor(seconds / 3600);
14+
const minutes = Math.floor((seconds % 3600) / 60);
15+
const secs = Math.floor(seconds % 60);
16+
17+
if (hours > 0) {
18+
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
19+
} else {
20+
return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
21+
}
22+
}
23+
24+
export function registerSegmentVideoCameraShots(
25+
server: McpServer,
26+
cgClient: CloudGlue,
27+
) {
28+
server.tool(
29+
"segment_video_camera_shots",
30+
"Segment videos into camera shots with intelligent cost optimization. Automatically checks for existing shot segmentation jobs before creating new ones. Returns timestamps and metadata for each camera shot detected. Supports Cloudglue URLs and direct HTTP video URLs. Note: YouTube URLs are not supported for segmentation.",
31+
schema,
32+
async ({ url }) => {
33+
// Check if it's a YouTube URL (not supported)
34+
if (url.includes('youtube.com') || url.includes('youtu.be')) {
35+
return {
36+
content: [
37+
{
38+
type: "text",
39+
text: "Error: YouTube URLs are not supported for video segmentation. Please use Cloudglue URLs or direct HTTP video URLs instead.",
40+
},
41+
],
42+
};
43+
}
44+
45+
// Step 1: Check for existing shot segmentation jobs for this URL
46+
try {
47+
const existingJobs = await cgClient.segments.listSegmentJobs({
48+
criteria: "shot",
49+
url: url,
50+
status: "completed",
51+
limit: 1
52+
});
53+
54+
if (existingJobs.data && existingJobs.data.length > 0) {
55+
const job = existingJobs.data[0];
56+
if (job.segments && job.segments.length > 0) {
57+
const segmentsText = job.segments.map((segment, index) => {
58+
const startTime = formatTime(segment.start_time);
59+
const endTime = formatTime(segment.end_time);
60+
const duration = (segment.end_time - segment.start_time).toFixed(1);
61+
return `Shot ${index + 1}: ${startTime} - ${endTime} (${duration}s)`;
62+
}).join('\n');
63+
64+
return {
65+
content: [
66+
{
67+
type: "text",
68+
text: `Found existing camera shot segmentation:\n\n${segmentsText}\n\nTotal shots: ${job.segments.length}`,
69+
},
70+
],
71+
};
72+
}
73+
}
74+
} catch (error) {
75+
// Continue to create new job if listing fails
76+
}
77+
78+
// Step 2: Create new shot segmentation job
79+
try {
80+
const segmentJob = await cgClient.segments.createSegmentJob({
81+
url: url,
82+
criteria: "shot",
83+
});
84+
85+
// Wait for completion using SDK's waitForReady method
86+
const completedJob = await cgClient.segments.waitForReady(segmentJob.job_id);
87+
88+
if (completedJob.status === "completed" && completedJob.segments) {
89+
const segmentsText = completedJob.segments.map((segment, index) => {
90+
const startTime = formatTime(segment.start_time);
91+
const endTime = formatTime(segment.end_time);
92+
const duration = (segment.end_time - segment.start_time).toFixed(1);
93+
return `Shot ${index + 1}: ${startTime} - ${endTime} (${duration}s)`;
94+
}).join('\n');
95+
96+
return {
97+
content: [
98+
{
99+
type: "text",
100+
text: `New camera shot segmentation created:\n\n${segmentsText}\n\nTotal shots: ${completedJob.segments.length}`,
101+
},
102+
],
103+
};
104+
}
105+
106+
return {
107+
content: [
108+
{
109+
type: "text",
110+
text: "Error: Failed to create camera shot segmentation - job did not complete successfully",
111+
},
112+
],
113+
};
114+
115+
} catch (error) {
116+
return {
117+
content: [
118+
{
119+
type: "text",
120+
text: `Error creating camera shot segmentation: ${error instanceof Error ? error.message : 'Unknown error'}`,
121+
},
122+
],
123+
};
124+
}
125+
},
126+
);
127+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { z } from "zod";
2+
import { CloudGlue } from "@aviaryhq/cloudglue-js";
3+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4+
5+
export const schema = {
6+
url: z
7+
.string()
8+
.describe("Video URL to segment into chapters. Supports Cloudglue URLs (cloudglue://files/file-id) or direct HTTP video URLs. For Cloudglue URLs, use the file ID from list_videos. Note: YouTube URLs are not supported for segmentation."),
9+
prompt: z
10+
.string()
11+
.describe("Custom prompt to guide chapter detection. Describe what types of chapters or segments you want to identify. Examples: 'Identify main topics and transitions', 'Find scene changes and key moments', 'Segment by speaker changes and topics'. Leave empty to use default chapter detection.")
12+
.optional(),
13+
};
14+
15+
// Helper function to format time in seconds to HH:MM:SS format
16+
function formatTime(seconds: number): string {
17+
const hours = Math.floor(seconds / 3600);
18+
const minutes = Math.floor((seconds % 3600) / 60);
19+
const secs = Math.floor(seconds % 60);
20+
21+
if (hours > 0) {
22+
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
23+
} else {
24+
return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
25+
}
26+
}
27+
28+
export function registerSegmentVideoChapters(
29+
server: McpServer,
30+
cgClient: CloudGlue,
31+
) {
32+
server.tool(
33+
"segment_video_chapters",
34+
"Segment videos into chapters with intelligent cost optimization. Automatically checks for existing chapter segmentation jobs before creating new ones. Returns timestamps and descriptions for each chapter detected. Supports Cloudglue URLs and direct HTTP video URLs. Note: YouTube URLs are not supported for segmentation.",
35+
schema,
36+
async ({ url, prompt }) => {
37+
// Check if it's a YouTube URL (not supported)
38+
if (url.includes('youtube.com') || url.includes('youtu.be')) {
39+
return {
40+
content: [
41+
{
42+
type: "text",
43+
text: "Error: YouTube URLs are not supported for video segmentation. Please use Cloudglue URLs or direct HTTP video URLs instead.",
44+
},
45+
],
46+
};
47+
}
48+
49+
// Step 1: Check for existing narrative segmentation jobs for this URL
50+
try {
51+
const existingJobs = await cgClient.segments.listSegmentJobs({
52+
criteria: "narrative",
53+
url: url,
54+
status: "completed",
55+
limit: 1
56+
});
57+
58+
if (existingJobs.data && existingJobs.data.length > 0) {
59+
const job = existingJobs.data[0];
60+
if (job.segments && job.segments.length > 0) {
61+
const chaptersText = job.segments.map((segment, index) => {
62+
const startTime = formatTime(segment.start_time);
63+
const description = segment.description || `Chapter ${index + 1}`;
64+
return `Chapter ${index + 1}: ${startTime} - ${description}`;
65+
}).join('\n');
66+
67+
return {
68+
content: [
69+
{
70+
type: "text",
71+
text: `Found existing chapter segmentation:\n\n${chaptersText}\n\nTotal chapters: ${job.segments.length}`,
72+
},
73+
],
74+
};
75+
}
76+
}
77+
} catch (error) {
78+
// Continue to create new job if listing fails
79+
}
80+
81+
// Step 2: Create new narrative segmentation job
82+
try {
83+
const narrativeConfig: any = {};
84+
if (prompt) {
85+
narrativeConfig.prompt = prompt;
86+
}
87+
88+
const segmentJob = await cgClient.segments.createSegmentJob({
89+
url: url,
90+
criteria: "narrative",
91+
narrative_config: narrativeConfig,
92+
});
93+
94+
// Wait for completion using SDK's waitForReady method
95+
const completedJob = await cgClient.segments.waitForReady(segmentJob.job_id);
96+
97+
if (completedJob.status === "completed" && completedJob.segments) {
98+
const chaptersText = completedJob.segments.map((segment, index) => {
99+
const startTime = formatTime(segment.start_time);
100+
const description = segment.description || `Chapter ${index + 1}`;
101+
return `Chapter ${index + 1}: ${startTime} - ${description}`;
102+
}).join('\n');
103+
104+
return {
105+
content: [
106+
{
107+
type: "text",
108+
text: `New chapter segmentation created:\n\n${chaptersText}\n\nTotal chapters: ${completedJob.segments.length}`,
109+
},
110+
],
111+
};
112+
}
113+
114+
return {
115+
content: [
116+
{
117+
type: "text",
118+
text: "Error: Failed to create chapter segmentation - job did not complete successfully",
119+
},
120+
],
121+
};
122+
123+
} catch (error) {
124+
return {
125+
content: [
126+
{
127+
type: "text",
128+
text: `Error creating chapter segmentation: ${error instanceof Error ? error.message : 'Unknown error'}`,
129+
},
130+
],
131+
};
132+
}
133+
},
134+
);
135+
}

0 commit comments

Comments
 (0)