@ourroadmaps/mcp 0.15.0 → 0.17.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 +247 -15
  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),
@@ -509,8 +536,14 @@ class ApiClient {
509
536
  searchParams.set("limit", String(params.limit));
510
537
  if (params?.offset)
511
538
  searchParams.set("offset", String(params.offset));
512
- const query = searchParams.toString();
513
- const path = query ? `/v1/roadmaps?${query}` : "/v1/roadmaps";
539
+ if (params?.horizon)
540
+ searchParams.set("horizon", params.horizon);
541
+ if (params?.status)
542
+ searchParams.set("status", params.status);
543
+ if (params?.query)
544
+ searchParams.set("query", params.query);
545
+ const queryString = searchParams.toString();
546
+ const path = queryString ? `/v1/roadmaps?${queryString}` : "/v1/roadmaps";
514
547
  return await this.request(path);
515
548
  }
516
549
  async getRoadmap(id) {
@@ -896,6 +929,32 @@ class ApiClient {
896
929
  });
897
930
  return response.data;
898
931
  }
932
+ async getPrototypeUploadUrls(roadmapId, files) {
933
+ const response = await this.request(`/v1/roadmaps/${roadmapId}/prototypes/upload-urls`, {
934
+ method: "POST",
935
+ body: JSON.stringify({ files })
936
+ });
937
+ return response.data;
938
+ }
939
+ async createPrototype(roadmapId, data) {
940
+ const response = await this.request(`/v1/roadmaps/${roadmapId}/prototypes`, {
941
+ method: "POST",
942
+ body: JSON.stringify(data)
943
+ });
944
+ return response.data;
945
+ }
946
+ async uploadFile(uploadUrl, content, contentType) {
947
+ const response = await fetch(uploadUrl, {
948
+ method: "PUT",
949
+ body: content,
950
+ headers: {
951
+ "Content-Type": contentType
952
+ }
953
+ });
954
+ if (!response.ok) {
955
+ throw new Error(`Upload failed: ${response.status} ${response.statusText}`);
956
+ }
957
+ }
899
958
  }
900
959
  var client = null;
901
960
  function getApiClient() {
@@ -972,6 +1031,7 @@ function registerAllTools(server) {
972
1031
  registerDeleteProductDesignMedia(server);
973
1032
  registerUploadDesignWalkthrough(server);
974
1033
  registerUploadReleaseWalkthrough(server);
1034
+ registerPublishPrototype(server);
975
1035
  }
976
1036
  function registerSearchRoadmaps(server) {
977
1037
  server.registerTool("search_roadmaps", {
@@ -991,24 +1051,19 @@ function registerSearchRoadmaps(server) {
991
1051
  offset
992
1052
  }) => {
993
1053
  const client2 = getApiClient();
994
- const response = await client2.listRoadmaps({ limit, offset });
995
- let roadmaps = response.items;
996
- if (query) {
997
- const q = query.toLowerCase();
998
- roadmaps = roadmaps.filter((r) => r.title.toLowerCase().includes(q));
999
- }
1000
- if (status) {
1001
- roadmaps = roadmaps.filter((r) => r.status === status);
1002
- }
1003
- if (horizon) {
1004
- roadmaps = roadmaps.filter((r) => r.horizon === horizon);
1005
- }
1054
+ const response = await client2.listRoadmaps({
1055
+ limit,
1056
+ offset,
1057
+ query,
1058
+ status,
1059
+ horizon
1060
+ });
1006
1061
  return {
1007
1062
  content: [
1008
1063
  {
1009
1064
  type: "text",
1010
1065
  text: JSON.stringify({
1011
- items: roadmaps.map((r) => ({
1066
+ items: response.items.map((r) => ({
1012
1067
  id: r.id,
1013
1068
  title: r.title,
1014
1069
  status: r.status,
@@ -2840,6 +2895,183 @@ function registerUploadReleaseWalkthrough(server) {
2840
2895
  };
2841
2896
  });
2842
2897
  }
2898
+ function getPrototypeContentType(filename) {
2899
+ const ext = filename.split(".").pop()?.toLowerCase() || "";
2900
+ const types = {
2901
+ html: "text/html",
2902
+ css: "text/css",
2903
+ js: "application/javascript",
2904
+ json: "application/json",
2905
+ png: "image/png",
2906
+ jpg: "image/jpeg",
2907
+ jpeg: "image/jpeg",
2908
+ gif: "image/gif",
2909
+ svg: "image/svg+xml",
2910
+ webp: "image/webp"
2911
+ };
2912
+ return types[ext] || "application/octet-stream";
2913
+ }
2914
+ function registerPublishPrototype(server) {
2915
+ server.registerTool("publish_prototype", {
2916
+ description: "Publish a local prototype folder to a shareable URL. Uploads all files from the folder and returns a time-limited shareable link.",
2917
+ inputSchema: {
2918
+ roadmapId: z11.string().uuid().describe("The UUID of the roadmap item to attach the prototype to"),
2919
+ folderPath: z11.string().describe("Absolute path to the local mockup folder"),
2920
+ entryPoint: z11.string().default("index.html").describe("Main HTML file to open (defaults to index.html)")
2921
+ }
2922
+ }, async ({
2923
+ roadmapId,
2924
+ folderPath,
2925
+ entryPoint
2926
+ }) => {
2927
+ const fs = await import("node:fs/promises");
2928
+ const path = await import("node:path");
2929
+ const client2 = getApiClient();
2930
+ const ignoredNames = new Set([
2931
+ ".DS_Store",
2932
+ ".git",
2933
+ "node_modules",
2934
+ ".env",
2935
+ ".gitignore",
2936
+ ".npmrc",
2937
+ "Thumbs.db",
2938
+ ".idea",
2939
+ ".vscode"
2940
+ ]);
2941
+ async function scanFolder(dir, base = "") {
2942
+ const entries = await fs.readdir(dir, { withFileTypes: true });
2943
+ const files2 = [];
2944
+ for (const entry of entries) {
2945
+ if (entry.name.startsWith(".") || ignoredNames.has(entry.name)) {
2946
+ continue;
2947
+ }
2948
+ const relativePath = base ? `${base}/${entry.name}` : entry.name;
2949
+ const fullPath = path.join(dir, entry.name);
2950
+ if (entry.isDirectory()) {
2951
+ files2.push(...await scanFolder(fullPath, relativePath));
2952
+ } else {
2953
+ files2.push(relativePath);
2954
+ }
2955
+ }
2956
+ return files2;
2957
+ }
2958
+ try {
2959
+ await fs.access(folderPath);
2960
+ } catch {
2961
+ return {
2962
+ content: [
2963
+ {
2964
+ type: "text",
2965
+ text: `Error: Folder not found: ${folderPath}`
2966
+ }
2967
+ ]
2968
+ };
2969
+ }
2970
+ const files = await scanFolder(folderPath);
2971
+ if (files.length === 0) {
2972
+ return {
2973
+ content: [
2974
+ {
2975
+ type: "text",
2976
+ text: `Error: No files found in ${folderPath}`
2977
+ }
2978
+ ]
2979
+ };
2980
+ }
2981
+ if (!files.includes(entryPoint)) {
2982
+ return {
2983
+ content: [
2984
+ {
2985
+ type: "text",
2986
+ text: `Error: Entry point "${entryPoint}" not found. Available files: ${files.slice(0, 10).join(", ")}${files.length > 10 ? "..." : ""}`
2987
+ }
2988
+ ]
2989
+ };
2990
+ }
2991
+ const filesWithTypes = files.map((filename) => ({
2992
+ filename,
2993
+ contentType: getPrototypeContentType(filename)
2994
+ }));
2995
+ let uploadData;
2996
+ try {
2997
+ uploadData = await client2.getPrototypeUploadUrls(roadmapId, filesWithTypes);
2998
+ } catch (error) {
2999
+ return {
3000
+ content: [
3001
+ {
3002
+ type: "text",
3003
+ text: `Error getting upload URLs: ${error instanceof Error ? error.message : String(error)}`
3004
+ }
3005
+ ]
3006
+ };
3007
+ }
3008
+ const uploadUrlMap = new Map(uploadData.files.map((f) => [f.filename, f.uploadUrl]));
3009
+ let uploaded = 0;
3010
+ for (const file of files) {
3011
+ const filePath = path.join(folderPath, file);
3012
+ const fileContent = await fs.readFile(filePath);
3013
+ const uploadUrl = uploadUrlMap.get(file);
3014
+ if (!uploadUrl) {
3015
+ return {
3016
+ content: [
3017
+ {
3018
+ type: "text",
3019
+ text: `Error: No upload URL found for ${file}`
3020
+ }
3021
+ ]
3022
+ };
3023
+ }
3024
+ try {
3025
+ await client2.uploadFile(uploadUrl, fileContent, getPrototypeContentType(file));
3026
+ uploaded++;
3027
+ } catch (error) {
3028
+ return {
3029
+ content: [
3030
+ {
3031
+ type: "text",
3032
+ text: `Error uploading ${file}: ${error instanceof Error ? error.message : String(error)}`
3033
+ }
3034
+ ]
3035
+ };
3036
+ }
3037
+ }
3038
+ let prototype;
3039
+ try {
3040
+ prototype = await client2.createPrototype(roadmapId, {
3041
+ token: uploadData.token,
3042
+ fileCount: files.length,
3043
+ entryPoint
3044
+ });
3045
+ } catch (error) {
3046
+ return {
3047
+ content: [
3048
+ {
3049
+ type: "text",
3050
+ text: `Error creating prototype record: ${error instanceof Error ? error.message : String(error)}`
3051
+ }
3052
+ ]
3053
+ };
3054
+ }
3055
+ const expiryDate = new Date(prototype.expiresAt).toLocaleDateString("en-US", {
3056
+ month: "long",
3057
+ day: "numeric",
3058
+ year: "numeric"
3059
+ });
3060
+ return {
3061
+ content: [
3062
+ {
3063
+ type: "text",
3064
+ text: `Published ${uploaded} files!
3065
+
3066
+ Share this link:
3067
+ ${prototype.publicUrl}
3068
+
3069
+ Expires: ${expiryDate}`
3070
+ }
3071
+ ]
3072
+ };
3073
+ });
3074
+ }
2843
3075
 
2844
3076
  // src/index.ts
2845
3077
  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.17.0",
4
4
  "description": "MCP server for OurRoadmaps - manage roadmaps, features, and ideas from Claude Code",
5
5
  "type": "module",
6
6
  "bin": {