@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.
- package/dist/index.js +247 -15
- 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
|
-
|
|
513
|
-
|
|
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({
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
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:
|
|
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() {
|