Sign In

@voteship/mcp-server

Package Overview
Dependencies
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@voteship/mcp-server - npm Package Compare versions

Comparing version
0.2.1
to
0.2.2
+19
dist/tools/metadata.d.ts
import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
type ToolHandler = (...args: any[]) => any;
export interface VoteShipToolMetadata {
title: string;
whenToUse: string;
annotations: ToolAnnotations;
}
/**
* Central metadata catalog used by tools/list. Keeping this data together makes
* it possible to audit every tool for model selection and safety semantics.
*/
export declare const VOTESHIP_TOOL_METADATA: Record<string, VoteShipToolMetadata>;
/**
* Register a VoteShip tool with consistent model-selection metadata and a
* machine-readable result envelope while preserving the existing text result
* for clients that do not consume structuredContent.
*/
export declare function registerVoteShipTool(server: any, name: string, description: string, inputSchema: Record<string, unknown>, handler: ToolHandler): any;
export {};
import { z } from "zod";
const readOnly = {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false,
};
const aiReadOnly = {
...readOnly,
openWorldHint: true,
};
const additive = {
readOnlyHint: false,
destructiveHint: false,
idempotentHint: false,
openWorldHint: false,
};
const update = {
readOnlyHint: false,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false,
};
const destructive = {
readOnlyHint: false,
destructiveHint: true,
idempotentHint: false,
openWorldHint: false,
};
/**
* Central metadata catalog used by tools/list. Keeping this data together makes
* it possible to audit every tool for model selection and safety semantics.
*/
export const VOTESHIP_TOOL_METADATA = {
submit_feature_request: {
title: "Submit a feature request",
whenToUse: "the user wants to send new product feedback to a public VoteShip board without administering the project",
annotations: additive,
},
browse_board: {
title: "Browse a feature board",
whenToUse: "the user wants to inspect, compare, or interact with approved requests on a public VoteShip board",
annotations: readOnly,
},
upvote_post: {
title: "Toggle a feature-request vote",
whenToUse: "the user explicitly wants to add or remove their agent identity's vote on a known VoteShip post",
annotations: destructive,
},
add_public_comment: {
title: "Comment on a public request",
whenToUse: "the user wants to add public context or a follow-up to a known VoteShip feature request",
annotations: additive,
},
list_posts: {
title: "List feature requests",
whenToUse: "the user wants to review, filter, rank, or select feature requests in their VoteShip project",
annotations: readOnly,
},
get_post: {
title: "Get a feature request",
whenToUse: "the user needs the full details, votes, comments, or tags for a specific VoteShip post",
annotations: readOnly,
},
create_post: {
title: "Create a feature request",
whenToUse: "the user wants to add a structured feature request directly to the project they administer",
annotations: additive,
},
update_post: {
title: "Update a feature request",
whenToUse: "the user wants to change a known request's title, description, workflow status, or tags",
annotations: update,
},
delete_post: {
title: "Delete a feature request",
whenToUse: "the user has explicitly confirmed permanent deletion of a known feature request and its related votes and comments",
annotations: destructive,
},
unarchive_post: {
title: "Restore an archived request",
whenToUse: "the user wants to undo shipping a specific archived request and return it to completed work",
annotations: update,
},
search_similar: {
title: "Find similar requests",
whenToUse: "the user or agent needs to check for duplicate or related feedback before creating or consolidating a request",
annotations: aiReadOnly,
},
add_vote: {
title: "Add a project vote",
whenToUse: "the project administrator wants to record a vote for a known board user or anonymous identity",
annotations: additive,
},
get_voters: {
title: "List request voters",
whenToUse: "the user wants to see who voted for a specific feature request",
annotations: readOnly,
},
add_comment: {
title: "Add a request comment",
whenToUse: "the user wants to add a public comment or internal note to a known feature request",
annotations: additive,
},
get_comments: {
title: "List request comments",
whenToUse: "the user wants to review discussion or internal notes attached to a specific request",
annotations: readOnly,
},
delete_comment: {
title: "Delete a request comment",
whenToUse: "the user has explicitly confirmed deletion of a specific comment from a feature request",
annotations: destructive,
},
list_tags: {
title: "List feedback tags",
whenToUse: "the user or agent needs valid project tags before categorizing, filtering, or creating feedback",
annotations: readOnly,
},
create_tag: {
title: "Create a feedback tag",
whenToUse: "the user wants a new reusable category for feature requests in their project",
annotations: additive,
},
list_users: {
title: "List board users",
whenToUse: "the user wants to inspect customers who submitted feedback or voted, including their revenue context",
annotations: readOnly,
},
update_user: {
title: "Update a board user",
whenToUse: "the user wants to correct a board user's identity details or customer-spend value",
annotations: update,
},
get_roadmap: {
title: "Get the product roadmap",
whenToUse: "the user wants to review planned, in-progress, and completed requests grouped as a roadmap",
annotations: readOnly,
},
list_releases: {
title: "List changelog releases",
whenToUse: "the user wants to review previously published product updates or select a release",
annotations: readOnly,
},
create_release: {
title: "Create a changelog release",
whenToUse: "the user wants to draft or publish a new changelog entry without automatically archiving completed posts",
annotations: additive,
},
get_release: {
title: "Get a changelog release",
whenToUse: "the user needs the complete contents and state of a specific changelog release",
annotations: readOnly,
},
update_release: {
title: "Update a changelog release",
whenToUse: "the user wants to revise a known release or change whether it is a draft",
annotations: update,
},
delete_release: {
title: "Delete a changelog release",
whenToUse: "the user has explicitly confirmed permanent deletion of a specific changelog release",
annotations: destructive,
},
ship_release: {
title: "Ship a product release",
whenToUse: "the user explicitly wants to publish a release, link completed requests, and archive those requests in one operation",
annotations: destructive,
},
get_analytics: {
title: "Get feedback analytics",
whenToUse: "the user wants quantitative feedback, voting, traffic, or trend metrics for a time period",
annotations: readOnly,
},
sync_stripe_revenue: {
title: "Sync customer revenue from Stripe",
whenToUse: "the user explicitly wants to refresh VoteShip board-user revenue from their connected Stripe account",
annotations: { ...update, openWorldHint: true },
},
submit_feedback: {
title: "Turn unstructured text into feedback",
whenToUse: "the user wants VoteShip to extract, deduplicate, and categorize a feature request from a message, email, ticket, or transcript",
annotations: { ...additive, openWorldHint: true },
},
triage_inbox: {
title: "Triage the feedback inbox",
whenToUse: "the user wants AI recommendations for pending feedback, duplicates, tags, priority, and next actions without applying changes",
annotations: aiReadOnly,
},
get_summary: {
title: "Summarize feedback trends",
whenToUse: "the user wants an AI-generated briefing on recent feedback themes, highlights, and recommended actions",
annotations: aiReadOnly,
},
plan_sprint: {
title: "Plan a feedback-driven sprint",
whenToUse: "the user wants a read-only sprint recommendation based on approved requests, votes, tags, and a prioritization strategy",
annotations: aiReadOnly,
},
configure_webhook: {
title: "Create a VoteShip webhook",
whenToUse: "the user explicitly wants VoteShip events delivered to an external HTTPS endpoint",
annotations: { ...additive, openWorldHint: true },
},
};
/**
* Register a VoteShip tool with consistent model-selection metadata and a
* machine-readable result envelope while preserving the existing text result
* for clients that do not consume structuredContent.
*/
export function registerVoteShipTool(server, name, description, inputSchema, handler) {
const metadata = VOTESHIP_TOOL_METADATA[name];
if (!metadata) {
throw new Error(`Missing VoteShip tool metadata for ${name}`);
}
return server.registerTool(name, {
title: metadata.title,
description: `Use this when ${metadata.whenToUse}. ${description}`,
inputSchema,
outputSchema: {
result: z.unknown().describe("The VoteShip API result for this operation"),
},
annotations: metadata.annotations,
}, async (...args) => {
const response = await handler(...args);
if (response?.isError || response?.structuredContent) {
return response;
}
const text = response?.content?.find((item) => item.type === "text")?.text;
let result = text ?? null;
if (text) {
try {
result = JSON.parse(text);
}
catch {
// Some successful operations intentionally return a plain-text result.
}
}
return {
...response,
structuredContent: { result },
};
});
}
+2
-1
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
export declare const VOTESHIP_MCP_VERSION = "0.2.1";
export declare const VOTESHIP_MCP_VERSION = "0.2.2";
export declare const VOTESHIP_MCP_INSTRUCTIONS = "Use VoteShip when the user wants to collect or submit product feedback, browse or prioritize feature requests, manage a roadmap, plan work from customer demand, or publish a changelog. Public tools work without credentials; ask for project_slug when it is missing. Authenticated project administration requires VOTESHIP_API_KEY or a Bearer secret. Before creating admin feedback, use search_similar to check for duplicates. Confirm exact IDs and explicit user intent before destructive tools. Read voteship://service/agent-guide for setup and website installation guidance.";
export interface VoteShipMcpServerOptions {

@@ -4,0 +5,0 @@ /**

@@ -30,3 +30,4 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

import { registerPublicFeedbackPrompt } from "./prompts/public-feedback.js";
export const VOTESHIP_MCP_VERSION = "0.2.1";
export const VOTESHIP_MCP_VERSION = "0.2.2";
export const VOTESHIP_MCP_INSTRUCTIONS = "Use VoteShip when the user wants to collect or submit product feedback, browse or prioritize feature requests, manage a roadmap, plan work from customer demand, or publish a changelog. Public tools work without credentials; ask for project_slug when it is missing. Authenticated project administration requires VOTESHIP_API_KEY or a Bearer secret. Before creating admin feedback, use search_similar to check for duplicates. Confirm exact IDs and explicit user intent before destructive tools. Read voteship://service/agent-guide for setup and website installation guidance.";
/**

@@ -41,2 +42,4 @@ * Build an unconnected VoteShip MCP server for either stdio or HTTP transport.

version: VOTESHIP_MCP_VERSION,
}, {
instructions: VOTESHIP_MCP_INSTRUCTIONS,
});

@@ -43,0 +46,0 @@ const publicClient = new VoteShipPublicClient();

import { z } from "zod";
import { registerVoteShipTool } from "./metadata.js";
export function registerAiTools(server, client) {
server.tool("submit_feedback", "Submit unstructured text as a feature request. AI extracts the title, description, checks for duplicates, and auto-categorizes with tags. Great for processing Slack messages, emails, or support tickets.", {
registerVoteShipTool(server, "submit_feedback", "Submit unstructured text as a feature request. AI extracts the title, description, checks for duplicates, and auto-categorizes with tags. Great for processing Slack messages, emails, or support tickets.", {
text: z.string().describe("Raw text describing a feature request, e.g. 'Multiple customers asked for PDF export'"),

@@ -16,3 +17,3 @@ source: z.string().describe("Where the feedback came from: slack, email, support, intercom, etc.").optional(),

});
server.tool("triage_inbox", "AI-powered triage of unreviewed feature requests. Analyzes pending posts, detects duplicates, suggests status/tags, and provides priority recommendations. Requires Growth plan.", {
registerVoteShipTool(server, "triage_inbox", "AI-powered triage of unreviewed feature requests. Analyzes pending posts, detects duplicates, suggests status/tags, and provides priority recommendations. Requires Growth plan.", {
limit: z.coerce.number().describe("Maximum posts to triage (default: 20, max: 50)").optional(),

@@ -28,3 +29,3 @@ }, async (params) => {

});
server.tool("get_summary", "Generate an AI-powered natural language summary of recent feedback trends, highlights, and recommended actions. Requires Growth plan.", {
registerVoteShipTool(server, "get_summary", "Generate an AI-powered natural language summary of recent feedback trends, highlights, and recommended actions. Requires Growth plan.", {
period: z.string().describe("Time period: week, month, or quarter (default: week)").optional(),

@@ -40,3 +41,3 @@ }, async (params) => {

});
server.tool("plan_sprint", "AI-suggested sprint plan based on approved feature requests, votes, tags, and the selected prioritization strategy. Requires Growth plan.", {
registerVoteShipTool(server, "plan_sprint", "AI-suggested sprint plan based on approved feature requests, votes, tags, and the selected prioritization strategy. Requires Growth plan.", {
capacity: z.coerce.number().describe("Number of features to include in the sprint (default: 5, max: 20)").optional(),

@@ -43,0 +44,0 @@ strategy: z.string().describe("Prioritization strategy: balanced, revenue, popular, or quick-wins (default: balanced)").optional(),

import { z } from "zod";
import { registerVoteShipTool } from "./metadata.js";
export function registerAnalyticsTools(server, client) {
server.tool("get_analytics", "Get analytics summary — new posts, votes, comments, page views, top posts, and trending tags for a given time period", {
registerVoteShipTool(server, "get_analytics", "Get analytics summary — new posts, votes, comments, page views, top posts, and trending tags for a given time period", {
period: z.string().describe("Time period: week, month, or quarter (default: week)").optional(),

@@ -14,3 +15,3 @@ }, async (params) => {

});
server.tool("sync_stripe_revenue", "Sync customer MRR from connected Stripe account to board users. Requires Growth plan.", {}, async () => {
registerVoteShipTool(server, "sync_stripe_revenue", "Sync customer MRR from connected Stripe account to board users. Requires Growth plan.", {}, async () => {
try {

@@ -17,0 +18,0 @@ const result = await client.syncStripeRevenue();

import { z } from "zod";
import { registerVoteShipTool } from "./metadata.js";
export function registerCommentTools(server, client) {
server.tool("add_comment", "Add a comment or internal note to a feature request post", {
registerVoteShipTool(server, "add_comment", "Add a comment or internal note to a feature request post", {
post_id: z.string().describe("The post ID to comment on"),

@@ -21,3 +22,3 @@ body: z.string().describe("Comment text content"),

});
server.tool("get_comments", "List comments on a feature request post. By default returns only public comments.", {
registerVoteShipTool(server, "get_comments", "List comments on a feature request post. By default returns only public comments.", {
post_id: z.string().describe("The post ID to get comments for"),

@@ -34,3 +35,3 @@ internal: z.enum(["true", "all"]).describe("Filter: 'true' for only internal notes, 'all' for both public and internal. Omit for public only.").optional(),

});
server.tool("delete_comment", "Delete a comment from a feature request post. Only works for comments on posts in your project.", {
registerVoteShipTool(server, "delete_comment", "Delete a comment from a feature request post. Only works for comments on posts in your project.", {
post_id: z.string().describe("The post ID the comment belongs to"),

@@ -37,0 +38,0 @@ comment_id: z.string().describe("The comment ID to delete"),

import { z } from "zod";
import { registerVoteShipTool } from "./metadata.js";
export function registerPostTools(server, client) {
server.tool("list_posts", "List feature requests with optional filters for status, sorting, and pagination", {
registerVoteShipTool(server, "list_posts", "List feature requests with optional filters for status, sorting, and pagination", {
status: z.string().describe("Filter by status: PENDING, APPROVED, IN_PROGRESS, COMPLETE, CLOSED, ARCHIVED").optional(),

@@ -17,3 +18,3 @@ sort: z.string().describe("Sort order: votes (default) or date").optional(),

});
server.tool("get_post", "Get a single feature request post with its votes, comments, and tags", {
registerVoteShipTool(server, "get_post", "Get a single feature request post with its votes, comments, and tags", {
post_id: z.string().describe("The post ID to retrieve"),

@@ -29,3 +30,3 @@ }, async (params) => {

});
server.tool("create_post", "Create a new feature request post. Title is required. Status defaults to PENDING if not provided.", {
registerVoteShipTool(server, "create_post", "Create a new feature request post. Title is required. Status defaults to PENDING if not provided.", {
title: z.string().describe("Feature request title (max 200 chars)"),

@@ -50,3 +51,3 @@ description: z.string().describe("Detailed description of the feature request").optional(),

});
server.tool("update_post", "Update an existing post's title, description, status, or tags. Only include fields you want to change.", {
registerVoteShipTool(server, "update_post", "Update an existing post's title, description, status, or tags. Only include fields you want to change.", {
post_id: z.string().describe("The post ID to update"),

@@ -72,3 +73,3 @@ title: z.string().describe("New title (max 200 chars)").optional(),

});
server.tool("delete_post", "Permanently delete a feature request and all its associated votes and comments", {
registerVoteShipTool(server, "delete_post", "Permanently delete a feature request and all its associated votes and comments", {
post_id: z.string().describe("The post ID to delete"),

@@ -84,3 +85,3 @@ }, async (params) => {

});
server.tool("unarchive_post", "Restore an archived post back to COMPLETE status and unlink it from its release. Use this to undo a ship operation for a specific post.", {
registerVoteShipTool(server, "unarchive_post", "Restore an archived post back to COMPLETE status and unlink it from its release. Use this to undo a ship operation for a specific post.", {
post_id: z.string().describe("The ARCHIVED post ID to restore"),

@@ -87,0 +88,0 @@ }, async (params) => {

@@ -7,2 +7,3 @@ import { z } from "zod";

import { BOARD_APP_URI } from "../apps/board.js";
import { registerVoteShipTool, VOTESHIP_TOOL_METADATA, } from "./metadata.js";
/**

@@ -34,3 +35,3 @@ * Generate a deterministic anonymous ID for this machine/user combo.

export function registerPublicTools(server, publicClient) {
server.tool("submit_feature_request", "Submit a feature request to any public VoteShip board. No API key needed. The post is created as PENDING and requires board owner approval. Include metadata.source to identify your agent.", {
registerVoteShipTool(server, "submit_feature_request", "Submit a feature request to any public VoteShip board. No API key needed. The post is created as PENDING and requires board owner approval. Include metadata.source to identify your agent.", {
project_slug: z

@@ -77,4 +78,4 @@ .string()

registerAppTool(server, "browse_board", {
title: "Browse VoteShip board",
description: "View approved feature requests on a public VoteShip board. No API key needed. Returns posts sorted by vote count and renders an interactive board in MCP Apps clients.",
title: VOTESHIP_TOOL_METADATA.browse_board.title,
description: `Use this when ${VOTESHIP_TOOL_METADATA.browse_board.whenToUse}. View approved feature requests on a public VoteShip board. No API key needed. Returns posts sorted by vote count and renders an interactive board in MCP Apps clients.`,
inputSchema: {

@@ -86,2 +87,8 @@ project_slug: z

},
outputSchema: {
projectSlug: z.string(),
boardUrl: z.string().url(),
posts: z.array(z.record(z.unknown())),
},
annotations: VOTESHIP_TOOL_METADATA.browse_board.annotations,
_meta: { ui: { resourceUri: BOARD_APP_URI } },

@@ -112,3 +119,3 @@ }, async (params) => {

});
server.tool("upvote_post", "Vote or unvote on a feature request (toggles). Uses a deterministic anonymous ID so the same agent on the same machine has a consistent identity. No API key needed.", {
registerVoteShipTool(server, "upvote_post", "Vote or unvote on a feature request (toggles). Uses a deterministic anonymous ID so the same agent on the same machine has a consistent identity. No API key needed.", {
post_id: z.string().describe("The post ID to vote on"),

@@ -129,3 +136,3 @@ }, async (params) => {

});
server.tool("add_public_comment", "Add a comment to a feature request on a public VoteShip board. No API key needed. Requires an author name.", {
registerVoteShipTool(server, "add_public_comment", "Add a comment to a feature request on a public VoteShip board. No API key needed. Requires an author name.", {
post_id: z.string().describe("The post ID to comment on"),

@@ -132,0 +139,0 @@ body: z.string().describe("Comment text (max 5000 chars)"),

import { z } from "zod";
import { registerVoteShipTool } from "./metadata.js";
export function registerReleaseTools(server, client) {
server.tool("list_releases", "List published changelog releases", {
registerVoteShipTool(server, "list_releases", "List published changelog releases", {
limit: z.coerce.number().describe("Number of releases to return (default: 20)").optional(),

@@ -14,3 +15,3 @@ }, async (params) => {

});
server.tool("create_release", "Create a new changelog release. Saved as a draft by default unless is_draft is explicitly set to false.", {
registerVoteShipTool(server, "create_release", "Create a new changelog release. Saved as a draft by default unless is_draft is explicitly set to false.", {
title: z.string().describe("Release title, e.g. 'January 2026 Update'"),

@@ -32,3 +33,3 @@ content: z.string().describe("Release notes content (HTML supported)"),

});
server.tool("get_release", "Get a single changelog release by ID", {
registerVoteShipTool(server, "get_release", "Get a single changelog release by ID", {
release_id: z.string().describe("The release ID to retrieve"),

@@ -44,3 +45,3 @@ }, async (params) => {

});
server.tool("update_release", "Update a changelog release's title, content, or draft status. Only include fields you want to change.", {
registerVoteShipTool(server, "update_release", "Update a changelog release's title, content, or draft status. Only include fields you want to change.", {
release_id: z.string().describe("The release ID to update"),

@@ -63,3 +64,3 @@ title: z.string().describe("New release title").optional(),

});
server.tool("delete_release", "Permanently delete a changelog release. Posts linked to this release will be unlinked but not deleted.", {
registerVoteShipTool(server, "delete_release", "Permanently delete a changelog release. Posts linked to this release will be unlinked but not deleted.", {
release_id: z.string().describe("The release ID to delete"),

@@ -75,3 +76,3 @@ }, async (params) => {

});
server.tool("ship_release", "Ship a release: create a published changelog entry, link the specified posts, and archive them. This is the one-click ship flow.", {
registerVoteShipTool(server, "ship_release", "Ship a release: create a published changelog entry, link the specified posts, and archive them. This is the one-click ship flow.", {
title: z.string().describe("Release title, e.g. 'March 2026 Update'"),

@@ -78,0 +79,0 @@ content: z.string().describe("Release notes content (HTML supported)").optional(),

@@ -0,3 +1,4 @@

import { registerVoteShipTool } from "./metadata.js";
export function registerRoadmapTools(server, client) {
server.tool("get_roadmap", "Get the product roadmap — feature requests grouped by status (Approved, In Progress, Complete), sorted by vote count", {}, async () => {
registerVoteShipTool(server, "get_roadmap", "Get the product roadmap — feature requests grouped by status (Approved, In Progress, Complete), sorted by vote count", {}, async () => {
try {

@@ -4,0 +5,0 @@ const result = await client.getRoadmap();

import { z } from "zod";
import { registerVoteShipTool } from "./metadata.js";
export function registerSearchTools(server, client) {
server.tool("search_similar", "Find similar feature requests using AI semantic search (pgvector). Useful for checking if a request already exists before creating a new one.", {
registerVoteShipTool(server, "search_similar", "Find similar feature requests using AI semantic search (pgvector). Useful for checking if a request already exists before creating a new one.", {
query: z.string().describe("Natural language search query, e.g. 'export data as PDF'"),

@@ -5,0 +6,0 @@ threshold: z.coerce.number().describe("Minimum similarity score 0-1 (default: 0.7)").optional(),

import { z } from "zod";
import { registerVoteShipTool } from "./metadata.js";
export function registerTagTools(server, client) {
server.tool("list_tags", "List all available tags for categorizing feature requests", {}, async () => {
registerVoteShipTool(server, "list_tags", "List all available tags for categorizing feature requests", {}, async () => {
try {

@@ -12,3 +13,3 @@ const result = await client.listTags();

});
server.tool("create_tag", "Create a new tag for categorizing feature requests", {
registerVoteShipTool(server, "create_tag", "Create a new tag for categorizing feature requests", {
label: z.string().describe("Tag name, e.g. 'Mobile', 'Performance', 'UI'"),

@@ -15,0 +16,0 @@ theme: z.string().describe("Hex color for the tag, e.g. '#6366f1'").optional(),

import { z } from "zod";
import { registerVoteShipTool } from "./metadata.js";
export function registerUserTools(server, client) {
server.tool("list_users", "List board users who have submitted feedback or voted on feature requests", {
registerVoteShipTool(server, "list_users", "List board users who have submitted feedback or voted on feature requests", {
limit: z.coerce.number().describe("Results per page (default: 50, max: 100)").optional(),

@@ -15,3 +16,3 @@ page: z.coerce.number().describe("Page number (default: 1)").optional(),

});
server.tool("update_user", "Update a board user's spend/revenue value, name, or email", {
registerVoteShipTool(server, "update_user", "Update a board user's spend/revenue value, name, or email", {
user_id: z.string().describe("The board user ID to update"),

@@ -18,0 +19,0 @@ user_spend: z.number().describe("Monthly spend/revenue for prioritization (e.g. 49.99)").optional(),

import { z } from "zod";
import { registerVoteShipTool } from "./metadata.js";
export function registerVoteTools(server, client) {
server.tool("add_vote", "Add a vote to a feature request. You must provide either a board_user_id or anonymous_id to identify the voter.", {
registerVoteShipTool(server, "add_vote", "Add a vote to a feature request. You must provide either a board_user_id or anonymous_id to identify the voter.", {
post_id: z.string().describe("The post ID to vote on"),

@@ -21,3 +22,3 @@ board_user_id: z.string().describe("Board user ID (for identified users)").optional(),

});
server.tool("get_voters", "List all users who voted on a specific feature request", {
registerVoteShipTool(server, "get_voters", "List all users who voted on a specific feature request", {
post_id: z.string().describe("The post ID to get voters for"),

@@ -24,0 +25,0 @@ }, async (params) => {

import { z } from "zod";
import { registerVoteShipTool } from "./metadata.js";
export function registerWebhookTools(server, client) {
server.tool("configure_webhook", "Create a new webhook endpoint to receive real-time notifications. Events: post.created, post.updated, post.deleted, post.status_changed, vote.created, vote.removed, comment.created, comment.deleted, tag.created, tag.deleted, release.published, or * for all.", {
registerVoteShipTool(server, "configure_webhook", "Create a new webhook endpoint to receive real-time notifications. Events: post.created, post.updated, post.deleted, post.status_changed, vote.created, vote.removed, comment.created, comment.deleted, tag.created, tag.deleted, release.published, or * for all.", {
url: z.string().describe("The HTTPS URL to receive webhook POST requests"),

@@ -5,0 +6,0 @@ events: z.string().describe("Comma-separated event types to subscribe to, e.g. 'post.created,vote.created' or '*' for all"),

@@ -61,3 +61,3 @@ import { App, applyDocumentTheme, applyHostFonts, applyHostStyleVariables, } from "@modelcontextprotocol/ext-apps";

}
const app = new App({ name: "VoteShip Board", version: "0.2.1" });
const app = new App({ name: "VoteShip Board", version: "0.2.2" });
app.ontoolinput = () => {

@@ -64,0 +64,0 @@ status.hidden = false;

{
"name": "@voteship/mcp-server",
"version": "0.2.1",
"version": "0.2.2",
"mcpName": "io.github.MattKilmer/voteship",

@@ -5,0 +5,0 @@ "description": "MCP server for VoteShip — manage feature requests, votes, roadmaps, and AI workflows from any MCP client",

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display