@ourroadmaps/mcp 0.15.0 → 0.16.0

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.
Files changed (2) hide show
  1. package/dist/index.js +231 -0
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,4 +1,22 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ var __create = Object.create;
4
+ var __getProtoOf = Object.getPrototypeOf;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __toESM = (mod, isNodeMode, target) => {
9
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
10
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
+ for (let key of __getOwnPropNames(mod))
12
+ if (!__hasOwnProp.call(to, key))
13
+ __defProp(to, key, {
14
+ get: () => mod[key],
15
+ enumerable: true
16
+ });
17
+ return to;
18
+ };
19
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
2
20
 
3
21
  // src/index.ts
4
22
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -370,6 +388,14 @@ var wireframeSchema = z8.object({
370
388
  imageUrl: z8.string().nullable(),
371
389
  order: z8.number().int()
372
390
  });
391
+ var prototypeSchema = z8.object({
392
+ id: z8.string().uuid(),
393
+ publicUrl: z8.string().url(),
394
+ expiresAt: z8.string().datetime(),
395
+ fileCount: z8.number().int().positive(),
396
+ entryPoint: z8.string(),
397
+ createdAt: z8.string().datetime()
398
+ });
373
399
  var featureLinkSchema = z8.object({
374
400
  id: z8.string().uuid(),
375
401
  featureId: z8.string().uuid(),
@@ -400,6 +426,7 @@ var phaseReleaseSchema = z8.object({
400
426
  var roadmapDetailSchema = roadmapSchema.extend({
401
427
  prd: prdSchema.nullable(),
402
428
  wireframes: z8.array(wireframeSchema),
429
+ prototypes: z8.array(prototypeSchema),
403
430
  brainstormMedia: z8.array(brainstormMediaSchema),
404
431
  brainstormNotes: z8.string().nullable(),
405
432
  featureLinks: z8.array(featureLinkSchema),
@@ -896,6 +923,32 @@ class ApiClient {
896
923
  });
897
924
  return response.data;
898
925
  }
926
+ async getPrototypeUploadUrls(roadmapId, files) {
927
+ const response = await this.request(`/v1/roadmaps/${roadmapId}/prototypes/upload-urls`, {
928
+ method: "POST",
929
+ body: JSON.stringify({ files })
930
+ });
931
+ return response.data;
932
+ }
933
+ async createPrototype(roadmapId, data) {
934
+ const response = await this.request(`/v1/roadmaps/${roadmapId}/prototypes`, {
935
+ method: "POST",
936
+ body: JSON.stringify(data)
937
+ });
938
+ return response.data;
939
+ }
940
+ async uploadFile(uploadUrl, content, contentType) {
941
+ const response = await fetch(uploadUrl, {
942
+ method: "PUT",
943
+ body: content,
944
+ headers: {
945
+ "Content-Type": contentType
946
+ }
947
+ });
948
+ if (!response.ok) {
949
+ throw new Error(`Upload failed: ${response.status} ${response.statusText}`);
950
+ }
951
+ }
899
952
  }
900
953
  var client = null;
901
954
  function getApiClient() {
@@ -972,6 +1025,7 @@ function registerAllTools(server) {
972
1025
  registerDeleteProductDesignMedia(server);
973
1026
  registerUploadDesignWalkthrough(server);
974
1027
  registerUploadReleaseWalkthrough(server);
1028
+ registerPublishPrototype(server);
975
1029
  }
976
1030
  function registerSearchRoadmaps(server) {
977
1031
  server.registerTool("search_roadmaps", {
@@ -2840,6 +2894,183 @@ function registerUploadReleaseWalkthrough(server) {
2840
2894
  };
2841
2895
  });
2842
2896
  }
2897
+ function getPrototypeContentType(filename) {
2898
+ const ext = filename.split(".").pop()?.toLowerCase() || "";
2899
+ const types = {
2900
+ html: "text/html",
2901
+ css: "text/css",
2902
+ js: "application/javascript",
2903
+ json: "application/json",
2904
+ png: "image/png",
2905
+ jpg: "image/jpeg",
2906
+ jpeg: "image/jpeg",
2907
+ gif: "image/gif",
2908
+ svg: "image/svg+xml",
2909
+ webp: "image/webp"
2910
+ };
2911
+ return types[ext] || "application/octet-stream";
2912
+ }
2913
+ function registerPublishPrototype(server) {
2914
+ server.registerTool("publish_prototype", {
2915
+ description: "Publish a local prototype folder to a shareable URL. Uploads all files from the folder and returns a time-limited shareable link.",
2916
+ inputSchema: {
2917
+ roadmapId: z11.string().uuid().describe("The UUID of the roadmap item to attach the prototype to"),
2918
+ folderPath: z11.string().describe("Absolute path to the local mockup folder"),
2919
+ entryPoint: z11.string().default("index.html").describe("Main HTML file to open (defaults to index.html)")
2920
+ }
2921
+ }, async ({
2922
+ roadmapId,
2923
+ folderPath,
2924
+ entryPoint
2925
+ }) => {
2926
+ const fs = await import("node:fs/promises");
2927
+ const path = await import("node:path");
2928
+ const client2 = getApiClient();
2929
+ const ignoredNames = new Set([
2930
+ ".DS_Store",
2931
+ ".git",
2932
+ "node_modules",
2933
+ ".env",
2934
+ ".gitignore",
2935
+ ".npmrc",
2936
+ "Thumbs.db",
2937
+ ".idea",
2938
+ ".vscode"
2939
+ ]);
2940
+ async function scanFolder(dir, base = "") {
2941
+ const entries = await fs.readdir(dir, { withFileTypes: true });
2942
+ const files2 = [];
2943
+ for (const entry of entries) {
2944
+ if (entry.name.startsWith(".") || ignoredNames.has(entry.name)) {
2945
+ continue;
2946
+ }
2947
+ const relativePath = base ? `${base}/${entry.name}` : entry.name;
2948
+ const fullPath = path.join(dir, entry.name);
2949
+ if (entry.isDirectory()) {
2950
+ files2.push(...await scanFolder(fullPath, relativePath));
2951
+ } else {
2952
+ files2.push(relativePath);
2953
+ }
2954
+ }
2955
+ return files2;
2956
+ }
2957
+ try {
2958
+ await fs.access(folderPath);
2959
+ } catch {
2960
+ return {
2961
+ content: [
2962
+ {
2963
+ type: "text",
2964
+ text: `Error: Folder not found: ${folderPath}`
2965
+ }
2966
+ ]
2967
+ };
2968
+ }
2969
+ const files = await scanFolder(folderPath);
2970
+ if (files.length === 0) {
2971
+ return {
2972
+ content: [
2973
+ {
2974
+ type: "text",
2975
+ text: `Error: No files found in ${folderPath}`
2976
+ }
2977
+ ]
2978
+ };
2979
+ }
2980
+ if (!files.includes(entryPoint)) {
2981
+ return {
2982
+ content: [
2983
+ {
2984
+ type: "text",
2985
+ text: `Error: Entry point "${entryPoint}" not found. Available files: ${files.slice(0, 10).join(", ")}${files.length > 10 ? "..." : ""}`
2986
+ }
2987
+ ]
2988
+ };
2989
+ }
2990
+ const filesWithTypes = files.map((filename) => ({
2991
+ filename,
2992
+ contentType: getPrototypeContentType(filename)
2993
+ }));
2994
+ let uploadData;
2995
+ try {
2996
+ uploadData = await client2.getPrototypeUploadUrls(roadmapId, filesWithTypes);
2997
+ } catch (error) {
2998
+ return {
2999
+ content: [
3000
+ {
3001
+ type: "text",
3002
+ text: `Error getting upload URLs: ${error instanceof Error ? error.message : String(error)}`
3003
+ }
3004
+ ]
3005
+ };
3006
+ }
3007
+ const uploadUrlMap = new Map(uploadData.files.map((f) => [f.filename, f.uploadUrl]));
3008
+ let uploaded = 0;
3009
+ for (const file of files) {
3010
+ const filePath = path.join(folderPath, file);
3011
+ const fileContent = await fs.readFile(filePath);
3012
+ const uploadUrl = uploadUrlMap.get(file);
3013
+ if (!uploadUrl) {
3014
+ return {
3015
+ content: [
3016
+ {
3017
+ type: "text",
3018
+ text: `Error: No upload URL found for ${file}`
3019
+ }
3020
+ ]
3021
+ };
3022
+ }
3023
+ try {
3024
+ await client2.uploadFile(uploadUrl, fileContent, getPrototypeContentType(file));
3025
+ uploaded++;
3026
+ } catch (error) {
3027
+ return {
3028
+ content: [
3029
+ {
3030
+ type: "text",
3031
+ text: `Error uploading ${file}: ${error instanceof Error ? error.message : String(error)}`
3032
+ }
3033
+ ]
3034
+ };
3035
+ }
3036
+ }
3037
+ let prototype;
3038
+ try {
3039
+ prototype = await client2.createPrototype(roadmapId, {
3040
+ token: uploadData.token,
3041
+ fileCount: files.length,
3042
+ entryPoint
3043
+ });
3044
+ } catch (error) {
3045
+ return {
3046
+ content: [
3047
+ {
3048
+ type: "text",
3049
+ text: `Error creating prototype record: ${error instanceof Error ? error.message : String(error)}`
3050
+ }
3051
+ ]
3052
+ };
3053
+ }
3054
+ const expiryDate = new Date(prototype.expiresAt).toLocaleDateString("en-US", {
3055
+ month: "long",
3056
+ day: "numeric",
3057
+ year: "numeric"
3058
+ });
3059
+ return {
3060
+ content: [
3061
+ {
3062
+ type: "text",
3063
+ text: `Published ${uploaded} files!
3064
+
3065
+ Share this link:
3066
+ ${prototype.publicUrl}
3067
+
3068
+ Expires: ${expiryDate}`
3069
+ }
3070
+ ]
3071
+ };
3072
+ });
3073
+ }
2843
3074
 
2844
3075
  // src/index.ts
2845
3076
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ourroadmaps/mcp",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "MCP server for OurRoadmaps - manage roadmaps, features, and ideas from Claude Code",
5
5
  "type": "module",
6
6
  "bin": {