@ourroadmaps/mcp 0.27.0 → 0.28.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 +268 -6
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -527,7 +527,8 @@ var SLIDE_TYPES = [
527
527
  "quote",
528
528
  "code",
529
529
  "thank_you",
530
- "mermaid"
530
+ "mermaid",
531
+ "paragraph"
531
532
  ];
532
533
  var slideTypeSchema = z12.enum(SLIDE_TYPES);
533
534
  var titleContentSchema = z12.object({
@@ -586,6 +587,10 @@ var mermaidContentSchema = z12.object({
586
587
  title: z12.string().optional(),
587
588
  code: z12.string().default("")
588
589
  });
590
+ var paragraphContentSchema = z12.object({
591
+ title: z12.string().optional(),
592
+ body: z12.string().default("")
593
+ });
589
594
  var typedSlideContentSchema = z12.discriminatedUnion("type", [
590
595
  z12.object({ type: z12.literal("title"), ...titleContentSchema.shape }),
591
596
  z12.object({ type: z12.literal("section_header"), ...sectionHeaderContentSchema.shape }),
@@ -597,7 +602,8 @@ var typedSlideContentSchema = z12.discriminatedUnion("type", [
597
602
  z12.object({ type: z12.literal("quote"), ...quoteContentSchema.shape }),
598
603
  z12.object({ type: z12.literal("code"), ...codeContentSchema.shape }),
599
604
  z12.object({ type: z12.literal("thank_you"), ...thankYouContentSchema.shape }),
600
- z12.object({ type: z12.literal("mermaid"), ...mermaidContentSchema.shape })
605
+ z12.object({ type: z12.literal("mermaid"), ...mermaidContentSchema.shape }),
606
+ z12.object({ type: z12.literal("paragraph"), ...paragraphContentSchema.shape })
601
607
  ]);
602
608
  var slideContentSchema = z12.object({
603
609
  body: z12.string().optional()
@@ -1131,6 +1137,10 @@ class ApiClient {
1131
1137
  });
1132
1138
  return response.data;
1133
1139
  }
1140
+ async getWorkflows() {
1141
+ const response = await this.request("/v1/workflows");
1142
+ return response.data;
1143
+ }
1134
1144
  async updateReleaseDescription(roadmapId, description) {
1135
1145
  const response = await this.request(`/v1/releases/roadmaps/${roadmapId}/release`, {
1136
1146
  method: "PATCH",
@@ -1314,6 +1324,28 @@ class ApiClient {
1314
1324
  });
1315
1325
  return response.data;
1316
1326
  }
1327
+ async createFeedbackSession(prototypeId, data) {
1328
+ const response = await this.request(`/v1/prototypes/${prototypeId}/feedback/sessions`, {
1329
+ method: "POST",
1330
+ body: JSON.stringify(data)
1331
+ });
1332
+ return response.data;
1333
+ }
1334
+ async listFeedbackSessions(prototypeId) {
1335
+ const response = await this.request(`/v1/prototypes/${prototypeId}/feedback/sessions`);
1336
+ return response.data;
1337
+ }
1338
+ async getFeedbackComments(prototypeId, sessionId, filter) {
1339
+ const params = filter && filter !== "all" ? `?filter=${filter}` : "";
1340
+ const response = await this.request(`/v1/prototypes/${prototypeId}/feedback/sessions/${sessionId}/comments${params}`);
1341
+ return response.data;
1342
+ }
1343
+ async resolveFeedbackComment(prototypeId, sessionId, commentId) {
1344
+ const response = await this.request(`/v1/prototypes/${prototypeId}/feedback/sessions/${sessionId}/comments/${commentId}/resolve`, {
1345
+ method: "PATCH"
1346
+ });
1347
+ return response.data;
1348
+ }
1317
1349
  async getVariantPresentationId(variantId) {
1318
1350
  const presentations = await this.listPresentations();
1319
1351
  for (const presentation2 of presentations.items) {
@@ -1409,6 +1441,11 @@ function registerAllTools(server) {
1409
1441
  registerUploadDesignWalkthrough(server);
1410
1442
  registerUploadReleaseWalkthrough(server);
1411
1443
  registerPublishPrototype(server);
1444
+ registerCreateFeedbackSession(server);
1445
+ registerListFeedbackSessions(server);
1446
+ registerGetFeedbackSummary(server);
1447
+ registerGetFeedbackDetail(server);
1448
+ registerResolveFeedbackComment(server);
1412
1449
  registerSearchExports(server);
1413
1450
  registerGetExport(server);
1414
1451
  registerCreateExport(server);
@@ -1442,7 +1479,7 @@ function registerGetWorkflows(server) {
1442
1479
  inputSchema: {}
1443
1480
  }, async () => {
1444
1481
  const client2 = getApiClient();
1445
- const response = await client2.get("/v1/workflows");
1482
+ const response = await client2.getWorkflows();
1446
1483
  return {
1447
1484
  content: [
1448
1485
  {
@@ -4313,7 +4350,7 @@ function registerGetSlide(server) {
4313
4350
  }
4314
4351
  function registerCreateSlide(server) {
4315
4352
  server.registerTool("create_slide", {
4316
- description: "Create a new slide in a variant. Slide types: title (title + subtitle), section_header (title + subtitle), bullets (title + items array), two_column (title + left/right text), comparison (leftLabel + leftContent + rightLabel + rightContent), timeline (title + steps array of {title, description}), image (title + imageUrl + caption), quote (quote + attribution), code (title + code + language), thank_you (title + subtitle), mermaid (title + code with mermaid diagram syntax).",
4353
+ description: "Create a new slide in a variant. Slide types: title (title + subtitle), section_header (title + subtitle), bullets (title + items array), two_column (title + left/right text), comparison (leftLabel + leftContent + rightLabel + rightContent), timeline (title + steps array of {title, description}), image (title + imageUrl + caption), quote (quote + attribution), code (title + code + language), thank_you (title + subtitle), mermaid (title + code with mermaid diagram syntax), paragraph (title + body with markdown text).",
4317
4354
  inputSchema: {
4318
4355
  variantId: z15.string().describe("The UUID of the variant"),
4319
4356
  slideType: z15.enum([
@@ -4327,9 +4364,10 @@ function registerCreateSlide(server) {
4327
4364
  "quote",
4328
4365
  "code",
4329
4366
  "thank_you",
4330
- "mermaid"
4367
+ "mermaid",
4368
+ "paragraph"
4331
4369
  ]).default("bullets").describe("The type of slide to create"),
4332
- content: z15.record(z15.unknown()).optional().describe('Type-specific content object. For bullets: {title, items: ["..."]}. For title: {title, subtitle}. For two_column: {title, left, right}. For quote: {quote, attribution}. For mermaid: {title, code: "graph TD; A-->B"}. Etc.'),
4370
+ content: z15.record(z15.unknown()).optional().describe('Type-specific content object. For bullets: {title, items: ["..."]}. For title: {title, subtitle}. For two_column: {title, left, right}. For quote: {quote, attribution}. For mermaid: {title, code: "graph TD; A-->B"}. For paragraph: {title, body}. Etc.'),
4333
4371
  speakerNotes: z15.string().nullable().optional().describe("Speaker notes for the slide")
4334
4372
  }
4335
4373
  }, async ({
@@ -4556,6 +4594,230 @@ function registerUploadSlideImage(server) {
4556
4594
  };
4557
4595
  });
4558
4596
  }
4597
+ function registerCreateFeedbackSession(server) {
4598
+ server.registerTool("create_feedback_session", {
4599
+ description: "Create a feedback session for a prototype and get a shareable review link. Reviewers who open the link can optionally enter their name before leaving pin-based feedback.",
4600
+ inputSchema: {
4601
+ prototypeId: z15.string().uuid().describe("The UUID of the prototype to create a feedback session for"),
4602
+ name: z15.string().describe('Name for this feedback session (e.g., "Round 1 Review")'),
4603
+ expiresInDays: z15.number().int().min(1).max(90).optional().describe("Number of days until the session expires (default 7)")
4604
+ }
4605
+ }, async ({
4606
+ prototypeId,
4607
+ name,
4608
+ expiresInDays
4609
+ }) => {
4610
+ const client2 = getApiClient();
4611
+ const days = expiresInDays ?? 7;
4612
+ const expiresAt = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toISOString();
4613
+ const session = await client2.createFeedbackSession(prototypeId, {
4614
+ name,
4615
+ expiresAt,
4616
+ reviewers: [{}]
4617
+ });
4618
+ const invite = session.invites[0];
4619
+ if (!invite) {
4620
+ return {
4621
+ content: [{ type: "text", text: "Error: No invite was created for the session." }]
4622
+ };
4623
+ }
4624
+ const reviewUrl = `https://app.ourroadmaps.com/prototype-review/${invite.token}`;
4625
+ const expiryDate = new Date(session.expiresAt).toLocaleDateString("en-US", {
4626
+ weekday: "long",
4627
+ year: "numeric",
4628
+ month: "long",
4629
+ day: "numeric"
4630
+ });
4631
+ return {
4632
+ content: [
4633
+ {
4634
+ type: "text",
4635
+ text: JSON.stringify({
4636
+ success: true,
4637
+ sessionId: session.id,
4638
+ sessionName: session.name,
4639
+ reviewUrl,
4640
+ expiresAt: session.expiresAt,
4641
+ expiryDate
4642
+ }, null, 2)
4643
+ }
4644
+ ]
4645
+ };
4646
+ });
4647
+ }
4648
+ function registerListFeedbackSessions(server) {
4649
+ server.registerTool("list_feedback_sessions", {
4650
+ description: "List all feedback sessions for a prototype with comment counts and status.",
4651
+ inputSchema: {
4652
+ prototypeId: z15.string().uuid().describe("The UUID of the prototype")
4653
+ }
4654
+ }, async ({ prototypeId }) => {
4655
+ const client2 = getApiClient();
4656
+ const sessions = await client2.listFeedbackSessions(prototypeId);
4657
+ return {
4658
+ content: [
4659
+ {
4660
+ type: "text",
4661
+ text: JSON.stringify({
4662
+ sessions: sessions.map((s) => ({
4663
+ id: s.id,
4664
+ name: s.name,
4665
+ status: s.status,
4666
+ totalComments: s.totalComments,
4667
+ unresolvedComments: s.unresolvedComments,
4668
+ inviteCount: s.inviteCount,
4669
+ expiresAt: s.expiresAt,
4670
+ createdAt: s.createdAt
4671
+ }))
4672
+ }, null, 2)
4673
+ }
4674
+ ]
4675
+ };
4676
+ });
4677
+ }
4678
+ function formatElementDesc(pinData) {
4679
+ if (!pinData || typeof pinData !== "object")
4680
+ return "";
4681
+ const pd = pinData;
4682
+ const el = pd.element;
4683
+ if (!el)
4684
+ return "";
4685
+ const tag = el.tag || "";
4686
+ const text = el.text ? ` "${String(el.text).slice(0, 80)}"` : "";
4687
+ return ` → <${tag}>${text}`;
4688
+ }
4689
+ function formatCommentLine(c) {
4690
+ const pin = c.pinNumber != null ? `Pin #${c.pinNumber}` : "General";
4691
+ const status = c.resolved ? "[RESOLVED]" : "[OPEN]";
4692
+ const reviewer = c.reviewerName || "Anonymous";
4693
+ const elementDesc = formatElementDesc(c.pinData);
4694
+ return `- **${pin}** ${status} by ${reviewer}${elementDesc}
4695
+ ${c.commentText}
4696
+
4697
+ `;
4698
+ }
4699
+ function registerGetFeedbackSummary(server) {
4700
+ server.registerTool("get_feedback_summary", {
4701
+ description: "Get a human-readable summary of feedback for a session, grouped by page. Shows pin numbers, reviewer names, comment text, and which element was pinned. Use this for a quick overview of feedback.",
4702
+ inputSchema: {
4703
+ prototypeId: z15.string().uuid().describe("The UUID of the prototype"),
4704
+ sessionId: z15.string().uuid().describe("The UUID of the feedback session"),
4705
+ filter: z15.enum(["all", "unresolved", "resolved"]).optional().describe("Filter comments by resolved status (default: unresolved)")
4706
+ }
4707
+ }, async ({
4708
+ prototypeId,
4709
+ sessionId,
4710
+ filter
4711
+ }) => {
4712
+ const client2 = getApiClient();
4713
+ const comments = await client2.getFeedbackComments(prototypeId, sessionId, filter ?? "unresolved");
4714
+ if (comments.length === 0) {
4715
+ return {
4716
+ content: [{ type: "text", text: "No comments found matching the filter." }]
4717
+ };
4718
+ }
4719
+ const byPage = new Map;
4720
+ for (const c of comments) {
4721
+ const page = c.pageUrl || "(no page)";
4722
+ if (!byPage.has(page))
4723
+ byPage.set(page, []);
4724
+ byPage.get(page).push(c);
4725
+ }
4726
+ let summary = `## Feedback Summary (${comments.length} comment${comments.length !== 1 ? "s" : ""})
4727
+
4728
+ `;
4729
+ for (const [page, pageComments] of byPage) {
4730
+ summary += `### Page: ${page}
4731
+
4732
+ `;
4733
+ for (const c of pageComments) {
4734
+ summary += formatCommentLine(c);
4735
+ }
4736
+ }
4737
+ return {
4738
+ content: [{ type: "text", text: summary.trim() }]
4739
+ };
4740
+ });
4741
+ }
4742
+ function registerGetFeedbackDetail(server) {
4743
+ server.registerTool("get_feedback_detail", {
4744
+ description: "Get full raw pin data for feedback comments, including CSS selectors, bounding boxes, parent/sibling DOM context, and coordinates. Use this when you need exact element information to make targeted code changes.",
4745
+ inputSchema: {
4746
+ prototypeId: z15.string().uuid().describe("The UUID of the prototype"),
4747
+ sessionId: z15.string().uuid().describe("The UUID of the feedback session"),
4748
+ commentId: z15.string().uuid().optional().describe("Optional: get detail for a single comment"),
4749
+ filter: z15.enum(["all", "unresolved", "resolved"]).optional().describe("Filter comments by resolved status (default: all). Ignored if commentId is provided.")
4750
+ }
4751
+ }, async ({
4752
+ prototypeId,
4753
+ sessionId,
4754
+ commentId,
4755
+ filter
4756
+ }) => {
4757
+ const client2 = getApiClient();
4758
+ const comments = await client2.getFeedbackComments(prototypeId, sessionId, filter ?? "all");
4759
+ let filtered = comments;
4760
+ if (commentId) {
4761
+ filtered = comments.filter((c) => c.id === commentId);
4762
+ if (filtered.length === 0) {
4763
+ return {
4764
+ content: [
4765
+ { type: "text", text: `Comment ${commentId} not found in this session.` }
4766
+ ]
4767
+ };
4768
+ }
4769
+ }
4770
+ return {
4771
+ content: [
4772
+ {
4773
+ type: "text",
4774
+ text: JSON.stringify({
4775
+ comments: filtered.map((c) => ({
4776
+ id: c.id,
4777
+ pinNumber: c.pinNumber,
4778
+ reviewerName: c.reviewerName,
4779
+ commentText: c.commentText,
4780
+ pageUrl: c.pageUrl,
4781
+ resolved: c.resolved,
4782
+ pinData: c.pinData,
4783
+ createdAt: c.createdAt
4784
+ }))
4785
+ }, null, 2)
4786
+ }
4787
+ ]
4788
+ };
4789
+ });
4790
+ }
4791
+ function registerResolveFeedbackComment(server) {
4792
+ server.registerTool("resolve_feedback_comment", {
4793
+ description: "Toggle the resolved status on a feedback comment. If currently unresolved, marks it as resolved. If already resolved, marks it as unresolved.",
4794
+ inputSchema: {
4795
+ prototypeId: z15.string().uuid().describe("The UUID of the prototype"),
4796
+ sessionId: z15.string().uuid().describe("The UUID of the feedback session"),
4797
+ commentId: z15.string().uuid().describe("The UUID of the comment to resolve/unresolve")
4798
+ }
4799
+ }, async ({
4800
+ prototypeId,
4801
+ sessionId,
4802
+ commentId
4803
+ }) => {
4804
+ const client2 = getApiClient();
4805
+ const result = await client2.resolveFeedbackComment(prototypeId, sessionId, commentId);
4806
+ return {
4807
+ content: [
4808
+ {
4809
+ type: "text",
4810
+ text: JSON.stringify({
4811
+ success: true,
4812
+ commentId: result.id,
4813
+ resolved: result.resolved,
4814
+ resolvedAt: result.resolvedAt
4815
+ }, null, 2)
4816
+ }
4817
+ ]
4818
+ };
4819
+ });
4820
+ }
4559
4821
 
4560
4822
  // src/index.ts
4561
4823
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ourroadmaps/mcp",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
4
4
  "description": "MCP server for OurRoadmaps - manage roadmaps, features, and ideas from Claude Code",
5
5
  "type": "module",
6
6
  "bin": {