@ourroadmaps/mcp 0.10.0 → 0.12.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 +426 -1
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -735,6 +735,91 @@ class ApiClient {
735
735
  });
736
736
  return response.data;
737
737
  }
738
+ async getWireframeUploadUrl(data) {
739
+ const response = await this.request("/v1/wireframes/upload-url", {
740
+ method: "POST",
741
+ body: JSON.stringify(data)
742
+ });
743
+ return response.data;
744
+ }
745
+ async createWireframe(roadmapId, data) {
746
+ const response = await this.request(`/v1/wireframes/roadmaps/${roadmapId}/wireframes`, {
747
+ method: "POST",
748
+ body: JSON.stringify(data)
749
+ });
750
+ return response.data;
751
+ }
752
+ async updateWireframe(id, data) {
753
+ const response = await this.request(`/v1/wireframes/${id}`, {
754
+ method: "PATCH",
755
+ body: JSON.stringify(data)
756
+ });
757
+ return response.data;
758
+ }
759
+ async updateWireframeOutcomes(id, outcomeIds) {
760
+ const response = await this.request(`/v1/wireframes/${id}/outcomes`, {
761
+ method: "POST",
762
+ body: JSON.stringify({ outcomeIds })
763
+ });
764
+ return response.data;
765
+ }
766
+ async deleteWireframe(id) {
767
+ await this.request(`/v1/wireframes/${id}`, {
768
+ method: "DELETE"
769
+ });
770
+ }
771
+ async getBrainstormUploadUrl(data) {
772
+ const response = await this.request("/v1/brainstorm/upload-url", {
773
+ method: "POST",
774
+ body: JSON.stringify(data)
775
+ });
776
+ return response.data;
777
+ }
778
+ async createBrainstormMedia(roadmapId, data) {
779
+ const response = await this.request(`/v1/brainstorm/roadmaps/${roadmapId}/media`, {
780
+ method: "POST",
781
+ body: JSON.stringify(data)
782
+ });
783
+ return response.data;
784
+ }
785
+ async updateBrainstormMedia(id, data) {
786
+ const response = await this.request(`/v1/brainstorm/media/${id}`, {
787
+ method: "PATCH",
788
+ body: JSON.stringify(data)
789
+ });
790
+ return response.data;
791
+ }
792
+ async deleteBrainstormMedia(id) {
793
+ await this.request(`/v1/brainstorm/media/${id}`, {
794
+ method: "DELETE"
795
+ });
796
+ }
797
+ async getProductDesignUploadUrl(productId, data) {
798
+ const response = await this.request(`/v1/products/${productId}/design-media/upload-url`, {
799
+ method: "POST",
800
+ body: JSON.stringify(data)
801
+ });
802
+ return response.data;
803
+ }
804
+ async createProductDesignMedia(productId, data) {
805
+ const response = await this.request(`/v1/products/${productId}/design-media`, {
806
+ method: "POST",
807
+ body: JSON.stringify(data)
808
+ });
809
+ return response.data;
810
+ }
811
+ async updateProductDesignMedia(productId, mediaId, data) {
812
+ const response = await this.request(`/v1/products/${productId}/design-media/${mediaId}`, {
813
+ method: "PATCH",
814
+ body: JSON.stringify(data)
815
+ });
816
+ return response.data;
817
+ }
818
+ async deleteProductDesignMedia(productId, mediaId) {
819
+ await this.request(`/v1/products/${productId}/design-media/${mediaId}`, {
820
+ method: "DELETE"
821
+ });
822
+ }
738
823
  async updateDesignDescription(roadmapId, description) {
739
824
  const response = await this.request(`/v1/designs/roadmaps/${roadmapId}/design`, {
740
825
  method: "PATCH",
@@ -773,6 +858,20 @@ function getApiClient() {
773
858
  }
774
859
 
775
860
  // src/tools/index.ts
861
+ function inferContentType(filename) {
862
+ const ext = filename.split(".").pop()?.toLowerCase();
863
+ const mapping = {
864
+ png: "image/png",
865
+ jpg: "image/jpeg",
866
+ jpeg: "image/jpeg",
867
+ gif: "image/gif",
868
+ webp: "image/webp",
869
+ mp4: "video/mp4",
870
+ webm: "video/webm",
871
+ mov: "video/quicktime"
872
+ };
873
+ return ext ? mapping[ext] ?? null : null;
874
+ }
776
875
  function registerAllTools(server) {
777
876
  registerSearchRoadmaps(server);
778
877
  registerGetRoadmap(server);
@@ -814,6 +913,15 @@ function registerAllTools(server) {
814
913
  registerGetHistorySummary(server);
815
914
  registerCreateStatusUpdate(server);
816
915
  registerSaveStories(server);
916
+ registerUploadWireframe(server);
917
+ registerUpdateWireframe(server);
918
+ registerDeleteWireframe(server);
919
+ registerUploadBrainstormMedia(server);
920
+ registerUpdateBrainstormMedia(server);
921
+ registerDeleteBrainstormMedia(server);
922
+ registerUploadProductDesignMedia(server);
923
+ registerUpdateProductDesignMedia(server);
924
+ registerDeleteProductDesignMedia(server);
817
925
  }
818
926
  function registerSearchRoadmaps(server) {
819
927
  server.registerTool("search_roadmaps", {
@@ -1211,7 +1319,7 @@ function registerUpdateRoadmapItem(server) {
1211
1319
  updates.effort = effort;
1212
1320
  if (order !== undefined)
1213
1321
  updates.order = order;
1214
- const roadmap2 = await client2.updateRoadmap(id, updates);
1322
+ const roadmap2 = Object.keys(updates).length > 0 ? await client2.updateRoadmap(id, updates) : await client2.getRoadmap(id);
1215
1323
  const phaseUpdates = {};
1216
1324
  if (designDescription !== undefined) {
1217
1325
  await client2.updateDesignDescription(id, designDescription);
@@ -2260,6 +2368,323 @@ function registerSaveStories(server) {
2260
2368
  };
2261
2369
  });
2262
2370
  }
2371
+ function registerUploadWireframe(server) {
2372
+ server.registerTool("upload_wireframe", {
2373
+ description: "Upload an image to a roadmap item's wireframes. Returns a curl command to execute for the upload, then creates the wireframe record. After running the curl command, the wireframe will be visible in the web app.",
2374
+ inputSchema: {
2375
+ roadmapId: z10.string().describe("The UUID of the roadmap item"),
2376
+ filePath: z10.string().describe("Absolute path to the image file on disk"),
2377
+ description: z10.string().optional().describe("Optional description of the wireframe"),
2378
+ outcomeIds: z10.array(z10.string()).optional().describe("Optional array of PRD outcome IDs this wireframe addresses")
2379
+ }
2380
+ }, async ({
2381
+ roadmapId,
2382
+ filePath,
2383
+ description,
2384
+ outcomeIds
2385
+ }) => {
2386
+ const client2 = getApiClient();
2387
+ const filename = filePath.split("/").pop() || "image.png";
2388
+ const contentType = inferContentType(filename);
2389
+ if (!contentType || !contentType.startsWith("image/")) {
2390
+ return {
2391
+ content: [
2392
+ {
2393
+ type: "text",
2394
+ text: JSON.stringify({
2395
+ error: "Invalid file type. Wireframes only accept image files (png, jpeg, gif, webp)."
2396
+ })
2397
+ }
2398
+ ]
2399
+ };
2400
+ }
2401
+ const { uploadUrl, imageUrl } = await client2.getWireframeUploadUrl({
2402
+ filename,
2403
+ contentType
2404
+ });
2405
+ const wireframe = await client2.createWireframe(roadmapId, { imageUrl });
2406
+ if (description) {
2407
+ await client2.updateWireframe(wireframe.id, { description });
2408
+ }
2409
+ if (outcomeIds && outcomeIds.length > 0) {
2410
+ await client2.updateWireframeOutcomes(wireframe.id, outcomeIds);
2411
+ }
2412
+ const curlCommand = `curl -X PUT -H "Content-Type: ${contentType}" --data-binary @"${filePath}" "${uploadUrl}"`;
2413
+ return {
2414
+ content: [
2415
+ {
2416
+ type: "text",
2417
+ text: JSON.stringify({
2418
+ wireframeId: wireframe.id,
2419
+ imageUrl,
2420
+ curlCommand,
2421
+ instructions: "Execute the curlCommand to upload the file. The wireframe record has been created and will display the image once uploaded."
2422
+ }, null, 2)
2423
+ }
2424
+ ]
2425
+ };
2426
+ });
2427
+ }
2428
+ function registerUpdateWireframe(server) {
2429
+ server.registerTool("update_wireframe", {
2430
+ description: "Update a wireframe's description or outcome tags.",
2431
+ inputSchema: {
2432
+ id: z10.string().describe("The UUID of the wireframe"),
2433
+ description: z10.string().nullable().optional().describe("New description for the wireframe"),
2434
+ outcomeIds: z10.array(z10.string()).optional().describe("Array of PRD outcome IDs this wireframe addresses (replaces existing)")
2435
+ }
2436
+ }, async ({
2437
+ id,
2438
+ description,
2439
+ outcomeIds
2440
+ }) => {
2441
+ const client2 = getApiClient();
2442
+ let wireframe;
2443
+ if (description !== undefined) {
2444
+ wireframe = await client2.updateWireframe(id, { description });
2445
+ } else {
2446
+ wireframe = await client2.updateWireframe(id, {});
2447
+ }
2448
+ if (outcomeIds !== undefined) {
2449
+ wireframe = await client2.updateWireframeOutcomes(id, outcomeIds);
2450
+ }
2451
+ return {
2452
+ content: [
2453
+ {
2454
+ type: "text",
2455
+ text: JSON.stringify({
2456
+ id: wireframe.id,
2457
+ imageUrl: wireframe.imageUrl,
2458
+ description: wireframe.description,
2459
+ outcomes: wireframe.outcomes.map((o) => o.prdOutcomeId)
2460
+ }, null, 2)
2461
+ }
2462
+ ]
2463
+ };
2464
+ });
2465
+ }
2466
+ function registerDeleteWireframe(server) {
2467
+ server.registerTool("delete_wireframe", {
2468
+ description: "Delete a wireframe (soft delete).",
2469
+ inputSchema: {
2470
+ id: z10.string().describe("The UUID of the wireframe to delete")
2471
+ }
2472
+ }, async ({ id }) => {
2473
+ const client2 = getApiClient();
2474
+ await client2.deleteWireframe(id);
2475
+ return {
2476
+ content: [
2477
+ {
2478
+ type: "text",
2479
+ text: JSON.stringify({ success: true, deleted: id })
2480
+ }
2481
+ ]
2482
+ };
2483
+ });
2484
+ }
2485
+ function registerUploadBrainstormMedia(server) {
2486
+ server.registerTool("upload_brainstorm_media", {
2487
+ description: "Upload an image or video to a roadmap item's brainstorming section. Returns a curl command to execute for the upload.",
2488
+ inputSchema: {
2489
+ roadmapId: z10.string().describe("The UUID of the roadmap item"),
2490
+ filePath: z10.string().describe("Absolute path to the image or video file on disk"),
2491
+ description: z10.string().optional().describe("Optional description of the media")
2492
+ }
2493
+ }, async ({
2494
+ roadmapId,
2495
+ filePath,
2496
+ description
2497
+ }) => {
2498
+ const client2 = getApiClient();
2499
+ const filename = filePath.split("/").pop() || "file.bin";
2500
+ const contentType = inferContentType(filename);
2501
+ if (!contentType) {
2502
+ return {
2503
+ content: [
2504
+ {
2505
+ type: "text",
2506
+ text: JSON.stringify({
2507
+ error: "Invalid file type. Brainstorm media accepts images (png, jpeg, gif, webp) and videos (mp4, webm, mov)."
2508
+ })
2509
+ }
2510
+ ]
2511
+ };
2512
+ }
2513
+ const mediaType = contentType.startsWith("image/") ? "image" : "video";
2514
+ const { uploadUrl, mediaUrl } = await client2.getBrainstormUploadUrl({
2515
+ filename,
2516
+ contentType
2517
+ });
2518
+ let media = await client2.createBrainstormMedia(roadmapId, { mediaUrl, mediaType });
2519
+ if (description) {
2520
+ media = await client2.updateBrainstormMedia(media.id, { description });
2521
+ }
2522
+ const curlCommand = `curl -X PUT -H "Content-Type: ${contentType}" --data-binary @"${filePath}" "${uploadUrl}"`;
2523
+ return {
2524
+ content: [
2525
+ {
2526
+ type: "text",
2527
+ text: JSON.stringify({
2528
+ mediaId: media.id,
2529
+ mediaUrl,
2530
+ mediaType,
2531
+ curlCommand,
2532
+ instructions: "Execute the curlCommand to upload the file. The media record has been created and will display once uploaded."
2533
+ }, null, 2)
2534
+ }
2535
+ ]
2536
+ };
2537
+ });
2538
+ }
2539
+ function registerUpdateBrainstormMedia(server) {
2540
+ server.registerTool("update_brainstorm_media", {
2541
+ description: "Update a brainstorm media item's description.",
2542
+ inputSchema: {
2543
+ id: z10.string().describe("The UUID of the brainstorm media"),
2544
+ description: z10.string().nullable().describe("New description for the media")
2545
+ }
2546
+ }, async ({ id, description }) => {
2547
+ const client2 = getApiClient();
2548
+ const media = await client2.updateBrainstormMedia(id, { description });
2549
+ return {
2550
+ content: [
2551
+ {
2552
+ type: "text",
2553
+ text: JSON.stringify({
2554
+ id: media.id,
2555
+ mediaUrl: media.mediaUrl,
2556
+ mediaType: media.mediaType,
2557
+ description: media.description
2558
+ }, null, 2)
2559
+ }
2560
+ ]
2561
+ };
2562
+ });
2563
+ }
2564
+ function registerDeleteBrainstormMedia(server) {
2565
+ server.registerTool("delete_brainstorm_media", {
2566
+ description: "Delete a brainstorm media item (soft delete).",
2567
+ inputSchema: {
2568
+ id: z10.string().describe("The UUID of the brainstorm media to delete")
2569
+ }
2570
+ }, async ({ id }) => {
2571
+ const client2 = getApiClient();
2572
+ await client2.deleteBrainstormMedia(id);
2573
+ return {
2574
+ content: [
2575
+ {
2576
+ type: "text",
2577
+ text: JSON.stringify({ success: true, deleted: id })
2578
+ }
2579
+ ]
2580
+ };
2581
+ });
2582
+ }
2583
+ function registerUploadProductDesignMedia(server) {
2584
+ server.registerTool("upload_product_design_media", {
2585
+ description: "Upload an image or video to a product's design media section. Returns a curl command to execute for the upload.",
2586
+ inputSchema: {
2587
+ productId: z10.string().describe("The UUID of the product"),
2588
+ filePath: z10.string().describe("Absolute path to the image or video file on disk"),
2589
+ description: z10.string().optional().describe("Optional description of the media")
2590
+ }
2591
+ }, async ({
2592
+ productId,
2593
+ filePath,
2594
+ description
2595
+ }) => {
2596
+ const client2 = getApiClient();
2597
+ const filename = filePath.split("/").pop() || "file.bin";
2598
+ const contentType = inferContentType(filename);
2599
+ if (!contentType) {
2600
+ return {
2601
+ content: [
2602
+ {
2603
+ type: "text",
2604
+ text: JSON.stringify({
2605
+ error: "Invalid file type. Design media accepts images (png, jpeg, gif, webp) and videos (mp4, webm, mov)."
2606
+ })
2607
+ }
2608
+ ]
2609
+ };
2610
+ }
2611
+ const mediaType = contentType.startsWith("image/") ? "image" : "video";
2612
+ const { uploadUrl, mediaUrl } = await client2.getProductDesignUploadUrl(productId, {
2613
+ filename,
2614
+ contentType
2615
+ });
2616
+ let media = await client2.createProductDesignMedia(productId, { mediaUrl, mediaType });
2617
+ if (description) {
2618
+ media = await client2.updateProductDesignMedia(productId, media.id, { description });
2619
+ }
2620
+ const curlCommand = `curl -X PUT -H "Content-Type: ${contentType}" --data-binary @"${filePath}" "${uploadUrl}"`;
2621
+ return {
2622
+ content: [
2623
+ {
2624
+ type: "text",
2625
+ text: JSON.stringify({
2626
+ mediaId: media.id,
2627
+ productId,
2628
+ mediaUrl,
2629
+ mediaType,
2630
+ curlCommand,
2631
+ instructions: "Execute the curlCommand to upload the file. The media record has been created and will display once uploaded."
2632
+ }, null, 2)
2633
+ }
2634
+ ]
2635
+ };
2636
+ });
2637
+ }
2638
+ function registerUpdateProductDesignMedia(server) {
2639
+ server.registerTool("update_product_design_media", {
2640
+ description: "Update a product design media item's description.",
2641
+ inputSchema: {
2642
+ productId: z10.string().describe("The UUID of the product"),
2643
+ mediaId: z10.string().describe("The UUID of the design media"),
2644
+ description: z10.string().nullable().describe("New description for the media")
2645
+ }
2646
+ }, async ({
2647
+ productId,
2648
+ mediaId,
2649
+ description
2650
+ }) => {
2651
+ const client2 = getApiClient();
2652
+ const media = await client2.updateProductDesignMedia(productId, mediaId, { description });
2653
+ return {
2654
+ content: [
2655
+ {
2656
+ type: "text",
2657
+ text: JSON.stringify({
2658
+ id: media.id,
2659
+ mediaUrl: media.mediaUrl,
2660
+ mediaType: media.mediaType,
2661
+ description: media.description
2662
+ }, null, 2)
2663
+ }
2664
+ ]
2665
+ };
2666
+ });
2667
+ }
2668
+ function registerDeleteProductDesignMedia(server) {
2669
+ server.registerTool("delete_product_design_media", {
2670
+ description: "Delete a product design media item (soft delete).",
2671
+ inputSchema: {
2672
+ productId: z10.string().describe("The UUID of the product"),
2673
+ mediaId: z10.string().describe("The UUID of the design media to delete")
2674
+ }
2675
+ }, async ({ productId, mediaId }) => {
2676
+ const client2 = getApiClient();
2677
+ await client2.deleteProductDesignMedia(productId, mediaId);
2678
+ return {
2679
+ content: [
2680
+ {
2681
+ type: "text",
2682
+ text: JSON.stringify({ success: true, deleted: mediaId })
2683
+ }
2684
+ ]
2685
+ };
2686
+ });
2687
+ }
2263
2688
 
2264
2689
  // src/index.ts
2265
2690
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ourroadmaps/mcp",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "MCP server for OurRoadmaps - manage roadmaps, features, and ideas from Claude Code",
5
5
  "type": "module",
6
6
  "bin": {