@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.
- package/dist/index.js +231 -0
- 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() {
|