@clipform/mcp-server 1.9.2 → 1.9.4

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/README.md CHANGED
@@ -77,15 +77,16 @@ You can also pass the key as a CLI flag: `npx -y @clipform/mcp-server --api-key=
77
77
 
78
78
  | Tool | Description |
79
79
  |------|-------------|
80
- | `clipform_render_composition` | Render a Remotion composition to MP4, PNG, or GIF (async - returns job ID) |
80
+ | `clipform_render_composition` | Render a video composition to MP4, PNG, or GIF (async - returns job ID) |
81
81
  | `clipform_generate_tts` | Generate narration audio with word-level captions |
82
82
  | `clipform_generate_slideshow` | Create slideshow videos from images + audio (async - returns job ID) |
83
83
  | `clipform_generate_video` | Generate video from images, clips, or both synced to audio (async - returns job ID) |
84
84
  | `clipform_check_render` | Check status of an async render job |
85
85
  | `clipform_search_media` | Search royalty-free images and videos |
86
86
  | `clipform_search_music` | Search royalty-free music and ambient sounds |
87
- | `clipform_list_compositions` | List available Remotion compositions and prop schemas |
87
+ | `clipform_list_compositions` | List available video compositions and prop schemas |
88
88
  | `clipform_list_assets` | List available sound effects, animations, and fonts |
89
+ | `clipform_fetch_boundary` | Fetch a GeoJSON boundary polygon for a country, city, or region |
89
90
 
90
91
  ## Example
91
92
 
@@ -23593,7 +23593,7 @@ function registerRenderCompositionTool(server) {
23593
23593
  "clipform_render_composition",
23594
23594
  {
23595
23595
  title: "Render Composition",
23596
- description: `Render a Remotion composition to MP4, PNG, or GIF. MP4 renders use AWS Lambda in production for fast, scalable rendering. PNG and GIF render locally.
23596
+ description: `Render a video composition to MP4, PNG, or GIF.
23597
23597
 
23598
23598
  Output formats:
23599
23599
  - mp4: Video file (H.264 codec, best for social media)
@@ -23685,7 +23685,7 @@ function registerListCompositionsTool(server) {
23685
23685
  "clipform_list_compositions",
23686
23686
  {
23687
23687
  title: "List Compositions",
23688
- description: `List all available Remotion compositions and their expected props schemas. Use clipform_render_composition to render them.`,
23688
+ description: `List all available video compositions and their expected props schemas. Use clipform_render_composition to render them.`,
23689
23689
  inputSchema: {},
23690
23690
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
23691
23691
  },
@@ -23695,7 +23695,7 @@ function registerListCompositionsTool(server) {
23695
23695
  });
23696
23696
  if (!result.ok) {
23697
23697
  if (result.status === 0 || result.status === 404) {
23698
- return errorResult("Remotion compositions are not available (API server may not be running). Start the API with 'npm run dev --workspace=apps/api'.");
23698
+ return errorResult("Video compositions are not available (API server may not be running). Start the API with 'npm run dev --workspace=apps/api'.");
23699
23699
  }
23700
23700
  return errorResult(result.error);
23701
23701
  }
@@ -23947,6 +23947,118 @@ Returns the current status and, when complete, the output URL. If still renderin
23947
23947
  );
23948
23948
  }
23949
23949
 
23950
+ // src/tools/fetch-boundary.ts
23951
+ var NOMINATIM_BASE = "https://nominatim.openstreetmap.org/search";
23952
+ var USER_AGENT = "Clipform/1.0 (https://clipform.io)";
23953
+ function polygonArea(ring) {
23954
+ let area = 0;
23955
+ for (let i = 0; i < ring.length - 1; i++) {
23956
+ area += ring[i][0] * ring[i + 1][1];
23957
+ area -= ring[i + 1][0] * ring[i][1];
23958
+ }
23959
+ return Math.abs(area / 2);
23960
+ }
23961
+ function simplifyRing(ring, maxPoints) {
23962
+ if (ring.length <= maxPoints) return ring;
23963
+ const step = Math.max(1, Math.floor(ring.length / maxPoints));
23964
+ const result = [];
23965
+ for (let i = 0; i < ring.length - 1; i += step) {
23966
+ result.push(ring[i]);
23967
+ }
23968
+ result.push(ring[ring.length - 1]);
23969
+ return result;
23970
+ }
23971
+ function registerFetchBoundaryTool(server) {
23972
+ server.registerTool(
23973
+ "clipform_fetch_boundary",
23974
+ {
23975
+ title: "Fetch Geographic Boundary",
23976
+ description: `Fetch a GeoJSON boundary polygon for a country, city, or region. Returns simplified GeoJSON ready to use as the 'boundary' prop in GlobeToCity/CityToGlobe compositions.
23977
+
23978
+ Use mainlandOnly to exclude small islands and overseas territories (e.g. Corsica for France, Hawaii for USA).`,
23979
+ inputSchema: {
23980
+ query: external_exports.string().describe("Place name (e.g. 'France', 'Paris', 'Tokyo', 'Brazil')"),
23981
+ type: external_exports.enum(["country", "city", "state", "region"]).default("country").describe("Type of place to search for"),
23982
+ mainlandOnly: external_exports.boolean().default(true).describe("Keep only the largest landmass, excluding small islands and overseas territories"),
23983
+ maxPoints: external_exports.number().default(500).describe("Maximum points per polygon ring for simplification (default 500, lower = smaller payload)")
23984
+ },
23985
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
23986
+ },
23987
+ async ({ query, type, mainlandOnly, maxPoints }) => {
23988
+ const params = new URLSearchParams({
23989
+ format: "geojson",
23990
+ polygon_geojson: "1",
23991
+ polygon_threshold: "0.001",
23992
+ limit: "1"
23993
+ });
23994
+ if (type === "country") {
23995
+ params.set("country", query);
23996
+ } else if (type === "city") {
23997
+ params.set("city", query);
23998
+ } else {
23999
+ params.set("q", query);
24000
+ }
24001
+ const url = `${NOMINATIM_BASE}?${params}`;
24002
+ const response = await fetch(url, {
24003
+ headers: { "User-Agent": USER_AGENT }
24004
+ });
24005
+ if (!response.ok) {
24006
+ return errorResult(`Nominatim API error: ${response.status} ${response.statusText}`);
24007
+ }
24008
+ const data = await response.json();
24009
+ if (!data.features?.length) {
24010
+ return errorResult(`No boundary found for "${query}" (type: ${type}). Try a different name or type.`);
24011
+ }
24012
+ const feature = data.features[0];
24013
+ let geometry = feature.geometry;
24014
+ if (geometry.type === "MultiPolygon" && mainlandOnly) {
24015
+ const withArea = geometry.coordinates.map((poly, i) => ({
24016
+ index: i,
24017
+ coords: poly,
24018
+ area: polygonArea(poly[0])
24019
+ }));
24020
+ withArea.sort((a, b) => b.area - a.area);
24021
+ const largestArea = withArea[0].area;
24022
+ const kept = withArea.filter((p) => p.area > largestArea * 0.05);
24023
+ geometry = {
24024
+ type: kept.length === 1 ? "Polygon" : "MultiPolygon",
24025
+ coordinates: kept.length === 1 ? kept[0].coords : kept.map((p) => p.coords)
24026
+ };
24027
+ }
24028
+ if (geometry.type === "Polygon") {
24029
+ geometry.coordinates = geometry.coordinates.map(
24030
+ (ring) => simplifyRing(ring, maxPoints)
24031
+ );
24032
+ } else if (geometry.type === "MultiPolygon") {
24033
+ geometry.coordinates = geometry.coordinates.map(
24034
+ (poly) => poly.map((ring) => simplifyRing(ring, maxPoints))
24035
+ );
24036
+ }
24037
+ const result = {
24038
+ type: "Feature",
24039
+ properties: { name: feature.properties.display_name },
24040
+ geometry
24041
+ };
24042
+ const geojsonStr = JSON.stringify(result);
24043
+ const sizeKb = (geojsonStr.length / 1024).toFixed(1);
24044
+ return textResult(
24045
+ [
24046
+ `Boundary fetched for: ${feature.properties.display_name}`,
24047
+ `Geometry: ${geometry.type}`,
24048
+ `Size: ${sizeKb} KB`,
24049
+ mainlandOnly ? `Mainland filter: applied (islands < 5% of largest landmass removed)` : `Mainland filter: off`,
24050
+ ``,
24051
+ `Use this as the boundary.geojson prop in GlobeToCity:`,
24052
+ ``,
24053
+ "```json",
24054
+ geojsonStr,
24055
+ "```"
24056
+ ].join("\n")
24057
+ );
24058
+ }
24059
+ );
24060
+ }
24061
+
23950
24062
  // src/lib/session-context.ts
23951
24063
  async function getSessionContext() {
23952
24064
  const result = await callApi("/me", { method: "GET" });
@@ -24673,6 +24785,7 @@ function createServer() {
24673
24785
  registerListCompositionsTool(server);
24674
24786
  registerListAssetsTool(server);
24675
24787
  registerCheckRenderTool(server);
24788
+ registerFetchBoundaryTool(server);
24676
24789
  registerPrompts(server);
24677
24790
  registerResources(server);
24678
24791
  return server;
@@ -24683,4 +24796,4 @@ export {
24683
24796
  setApiKey,
24684
24797
  createServer
24685
24798
  };
24686
- //# sourceMappingURL=chunk-ESDD3WVD.js.map
24799
+ //# sourceMappingURL=chunk-62IK3CBP.js.map