@voteship/mcp-server 0.1.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/README.md +114 -0
- package/dist/auth.d.ts +5 -0
- package/dist/auth.js +17 -0
- package/dist/client.d.ts +85 -0
- package/dist/client.js +136 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +62 -0
- package/dist/prompts/changelog.d.ts +1 -0
- package/dist/prompts/changelog.js +32 -0
- package/dist/prompts/sprint.d.ts +1 -0
- package/dist/prompts/sprint.js +36 -0
- package/dist/prompts/summary.d.ts +1 -0
- package/dist/prompts/summary.js +31 -0
- package/dist/prompts/triage.d.ts +1 -0
- package/dist/prompts/triage.js +23 -0
- package/dist/resources/analytics.d.ts +2 -0
- package/dist/resources/analytics.js +14 -0
- package/dist/resources/changelog.d.ts +2 -0
- package/dist/resources/changelog.js +14 -0
- package/dist/resources/project.d.ts +2 -0
- package/dist/resources/project.js +35 -0
- package/dist/resources/roadmap.d.ts +2 -0
- package/dist/resources/roadmap.js +14 -0
- package/dist/tools/ai.d.ts +2 -0
- package/dist/tools/ai.js +29 -0
- package/dist/tools/analytics.d.ts +2 -0
- package/dist/tools/analytics.js +8 -0
- package/dist/tools/comments.d.ts +2 -0
- package/dist/tools/comments.js +19 -0
- package/dist/tools/posts.d.ts +2 -0
- package/dist/tools/posts.js +54 -0
- package/dist/tools/releases.d.ts +2 -0
- package/dist/tools/releases.js +20 -0
- package/dist/tools/roadmap.d.ts +2 -0
- package/dist/tools/roadmap.js +6 -0
- package/dist/tools/search.d.ts +2 -0
- package/dist/tools/search.js +10 -0
- package/dist/tools/tags.d.ts +2 -0
- package/dist/tools/tags.js +13 -0
- package/dist/tools/users.d.ts +2 -0
- package/dist/tools/users.js +9 -0
- package/dist/tools/votes.d.ts +2 -0
- package/dist/tools/votes.js +19 -0
- package/dist/tools/webhooks.d.ts +2 -0
- package/dist/tools/webhooks.js +15 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# @voteship/mcp-server
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for [VoteShip](https://voteship.app) — manage feature requests, votes, roadmaps, and AI workflows from any MCP-compatible client.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### Claude Code
|
|
8
|
+
|
|
9
|
+
Add to your project's `.mcp.json`:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"mcpServers": {
|
|
14
|
+
"voteship": {
|
|
15
|
+
"command": "npx",
|
|
16
|
+
"args": ["@voteship/mcp-server"],
|
|
17
|
+
"env": {
|
|
18
|
+
"VOTESHIP_API_KEY": "sk_..."
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Cursor / Windsurf
|
|
26
|
+
|
|
27
|
+
Add to your MCP settings:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"voteship": {
|
|
33
|
+
"command": "npx",
|
|
34
|
+
"args": ["@voteship/mcp-server"],
|
|
35
|
+
"env": {
|
|
36
|
+
"VOTESHIP_API_KEY": "sk_..."
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Find your API key in VoteShip → Settings → Share & Embed.
|
|
44
|
+
|
|
45
|
+
## Tools (22)
|
|
46
|
+
|
|
47
|
+
| Tool | Description |
|
|
48
|
+
|------|-------------|
|
|
49
|
+
| `list_posts` | List feature requests with optional filters |
|
|
50
|
+
| `get_post` | Get a single post with votes, comments, tags |
|
|
51
|
+
| `create_post` | Create a new feature request |
|
|
52
|
+
| `update_post` | Update a post's title, description, status, or tags |
|
|
53
|
+
| `delete_post` | Delete a feature request |
|
|
54
|
+
| `search_similar` | Find similar posts using AI semantic search |
|
|
55
|
+
| `add_vote` | Vote on a feature request |
|
|
56
|
+
| `get_voters` | List who voted on a post |
|
|
57
|
+
| `add_comment` | Add a comment to a post |
|
|
58
|
+
| `get_comments` | List comments on a post |
|
|
59
|
+
| `list_tags` | List all available tags |
|
|
60
|
+
| `create_tag` | Create a new tag |
|
|
61
|
+
| `list_users` | List board users |
|
|
62
|
+
| `get_roadmap` | Get the product roadmap grouped by status |
|
|
63
|
+
| `get_analytics` | Get analytics summary for a time period |
|
|
64
|
+
| `list_releases` | List published changelog releases |
|
|
65
|
+
| `create_release` | Create a changelog release |
|
|
66
|
+
| `submit_feedback` | Submit unstructured text as a feature request (AI processes it) |
|
|
67
|
+
| `triage_inbox` | AI-powered triage of unreviewed posts |
|
|
68
|
+
| `get_summary` | AI-generated summary of recent feedback |
|
|
69
|
+
| `plan_sprint` | AI-suggested sprint based on votes and themes |
|
|
70
|
+
| `configure_webhook` | Set up a webhook for real-time events |
|
|
71
|
+
|
|
72
|
+
## Resources (5)
|
|
73
|
+
|
|
74
|
+
| URI | Description |
|
|
75
|
+
|-----|-------------|
|
|
76
|
+
| `voteship://project/overview` | Project info and summary stats |
|
|
77
|
+
| `voteship://project/board` | Full board state with all posts |
|
|
78
|
+
| `voteship://project/roadmap` | Public roadmap view |
|
|
79
|
+
| `voteship://project/changelog` | Published releases |
|
|
80
|
+
| `voteship://project/analytics` | Analytics snapshot |
|
|
81
|
+
|
|
82
|
+
## Prompts (4)
|
|
83
|
+
|
|
84
|
+
| Name | Description |
|
|
85
|
+
|------|-------------|
|
|
86
|
+
| `triage_inbox` | Review and categorize unprocessed feature requests |
|
|
87
|
+
| `sprint_planning` | Suggest what to build next based on data |
|
|
88
|
+
| `generate_changelog` | Draft release notes from recently completed posts |
|
|
89
|
+
| `feedback_summary` | Summarize feedback trends and highlights |
|
|
90
|
+
|
|
91
|
+
## Example Usage
|
|
92
|
+
|
|
93
|
+
In Claude Code:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
> Triage my VoteShip inbox and suggest what to build this sprint
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The agent will:
|
|
100
|
+
1. Fetch unreviewed posts and analyze them
|
|
101
|
+
2. Flag duplicates and suggest statuses
|
|
102
|
+
3. Recommend a sprint plan based on votes and themes
|
|
103
|
+
4. Generate changelog drafts for completed features
|
|
104
|
+
|
|
105
|
+
## Environment Variables
|
|
106
|
+
|
|
107
|
+
| Variable | Required | Description |
|
|
108
|
+
|----------|----------|-------------|
|
|
109
|
+
| `VOTESHIP_API_KEY` | Yes | Your VoteShip API secret key (`sk_...`) |
|
|
110
|
+
| `VOTESHIP_API_URL` | No | Custom API URL (default: `https://app.voteship.app`) |
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
MIT
|
package/dist/auth.d.ts
ADDED
package/dist/auth.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API key configuration — reads from VOTESHIP_API_KEY environment variable.
|
|
3
|
+
*/
|
|
4
|
+
export function getApiKey() {
|
|
5
|
+
const key = process.env.VOTESHIP_API_KEY;
|
|
6
|
+
if (!key) {
|
|
7
|
+
console.error("VOTESHIP_API_KEY environment variable is required.\n" +
|
|
8
|
+
"Set it in your MCP config:\n" +
|
|
9
|
+
' "env": { "VOTESHIP_API_KEY": "sk_..." }\n' +
|
|
10
|
+
"Find your API key in VoteShip → Settings → Share & Embed");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
return key;
|
|
14
|
+
}
|
|
15
|
+
export function getBaseUrl() {
|
|
16
|
+
return process.env.VOTESHIP_API_URL || "https://app.voteship.app";
|
|
17
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client wrapping the VoteShip REST API v1.
|
|
3
|
+
*/
|
|
4
|
+
export declare class VoteShipClient {
|
|
5
|
+
private apiKey;
|
|
6
|
+
private baseUrl;
|
|
7
|
+
constructor();
|
|
8
|
+
private request;
|
|
9
|
+
listPosts(params?: {
|
|
10
|
+
status?: string;
|
|
11
|
+
sort?: string;
|
|
12
|
+
limit?: number;
|
|
13
|
+
page?: number;
|
|
14
|
+
}): Promise<unknown>;
|
|
15
|
+
getPost(postId: string): Promise<unknown>;
|
|
16
|
+
createPost(data: {
|
|
17
|
+
title: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
status?: string;
|
|
20
|
+
tagIds?: string[];
|
|
21
|
+
}): Promise<unknown>;
|
|
22
|
+
updatePost(postId: string, data: {
|
|
23
|
+
title?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
status?: string;
|
|
26
|
+
tagIds?: string[];
|
|
27
|
+
}): Promise<unknown>;
|
|
28
|
+
deletePost(postId: string): Promise<unknown>;
|
|
29
|
+
searchSimilar(params: {
|
|
30
|
+
query: string;
|
|
31
|
+
threshold?: number;
|
|
32
|
+
limit?: number;
|
|
33
|
+
}): Promise<unknown>;
|
|
34
|
+
addVote(postId: string, data?: {
|
|
35
|
+
boardUserId?: string;
|
|
36
|
+
anonymousId?: string;
|
|
37
|
+
}): Promise<unknown>;
|
|
38
|
+
getVoters(postId: string): Promise<unknown>;
|
|
39
|
+
addComment(postId: string, data: {
|
|
40
|
+
body: string;
|
|
41
|
+
authorName?: string;
|
|
42
|
+
}): Promise<unknown>;
|
|
43
|
+
getComments(postId: string): Promise<unknown>;
|
|
44
|
+
listTags(): Promise<unknown>;
|
|
45
|
+
createTag(data: {
|
|
46
|
+
label: string;
|
|
47
|
+
theme?: string;
|
|
48
|
+
}): Promise<unknown>;
|
|
49
|
+
listUsers(params?: {
|
|
50
|
+
limit?: number;
|
|
51
|
+
page?: number;
|
|
52
|
+
}): Promise<unknown>;
|
|
53
|
+
getRoadmap(): Promise<unknown>;
|
|
54
|
+
listReleases(params?: {
|
|
55
|
+
limit?: number;
|
|
56
|
+
}): Promise<unknown>;
|
|
57
|
+
createRelease(data: {
|
|
58
|
+
title: string;
|
|
59
|
+
content: string;
|
|
60
|
+
isDraft?: boolean;
|
|
61
|
+
}): Promise<unknown>;
|
|
62
|
+
getAnalytics(params?: {
|
|
63
|
+
period?: string;
|
|
64
|
+
}): Promise<unknown>;
|
|
65
|
+
submitFeedback(data: {
|
|
66
|
+
text: string;
|
|
67
|
+
source?: string;
|
|
68
|
+
source_url?: string;
|
|
69
|
+
}): Promise<unknown>;
|
|
70
|
+
triageInbox(params?: {
|
|
71
|
+
limit?: number;
|
|
72
|
+
}): Promise<unknown>;
|
|
73
|
+
getSummary(params?: {
|
|
74
|
+
period?: string;
|
|
75
|
+
}): Promise<unknown>;
|
|
76
|
+
planSprint(params?: {
|
|
77
|
+
capacity?: number;
|
|
78
|
+
strategy?: string;
|
|
79
|
+
}): Promise<unknown>;
|
|
80
|
+
configureWebhook(data: {
|
|
81
|
+
url: string;
|
|
82
|
+
events: string[];
|
|
83
|
+
description?: string;
|
|
84
|
+
}): Promise<unknown>;
|
|
85
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { getApiKey, getBaseUrl } from "./auth.js";
|
|
2
|
+
/**
|
|
3
|
+
* HTTP client wrapping the VoteShip REST API v1.
|
|
4
|
+
*/
|
|
5
|
+
export class VoteShipClient {
|
|
6
|
+
apiKey;
|
|
7
|
+
baseUrl;
|
|
8
|
+
constructor() {
|
|
9
|
+
this.apiKey = getApiKey();
|
|
10
|
+
this.baseUrl = `${getBaseUrl()}/api/v1`;
|
|
11
|
+
}
|
|
12
|
+
async request(method, path, body) {
|
|
13
|
+
const url = `${this.baseUrl}${path}`;
|
|
14
|
+
const headers = {
|
|
15
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
16
|
+
"Content-Type": "application/json",
|
|
17
|
+
};
|
|
18
|
+
const response = await fetch(url, {
|
|
19
|
+
method,
|
|
20
|
+
headers,
|
|
21
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
22
|
+
});
|
|
23
|
+
const data = await response.json();
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
const errorMsg = data.message || data.error || `HTTP ${response.status}`;
|
|
26
|
+
throw new Error(errorMsg);
|
|
27
|
+
}
|
|
28
|
+
return data;
|
|
29
|
+
}
|
|
30
|
+
// --- Posts ---
|
|
31
|
+
async listPosts(params) {
|
|
32
|
+
const query = new URLSearchParams();
|
|
33
|
+
if (params?.status)
|
|
34
|
+
query.set("status", params.status);
|
|
35
|
+
if (params?.sort)
|
|
36
|
+
query.set("sort", params.sort);
|
|
37
|
+
if (params?.limit)
|
|
38
|
+
query.set("limit", String(params.limit));
|
|
39
|
+
if (params?.page)
|
|
40
|
+
query.set("page", String(params.page));
|
|
41
|
+
const qs = query.toString();
|
|
42
|
+
return this.request("GET", `/posts${qs ? `?${qs}` : ""}`);
|
|
43
|
+
}
|
|
44
|
+
async getPost(postId) {
|
|
45
|
+
return this.request("GET", `/posts/${postId}`);
|
|
46
|
+
}
|
|
47
|
+
async createPost(data) {
|
|
48
|
+
return this.request("POST", "/posts", data);
|
|
49
|
+
}
|
|
50
|
+
async updatePost(postId, data) {
|
|
51
|
+
return this.request("PATCH", `/posts/${postId}`, data);
|
|
52
|
+
}
|
|
53
|
+
async deletePost(postId) {
|
|
54
|
+
return this.request("DELETE", `/posts/${postId}`);
|
|
55
|
+
}
|
|
56
|
+
// --- Similar / Search ---
|
|
57
|
+
async searchSimilar(params) {
|
|
58
|
+
const query = new URLSearchParams({ query: params.query });
|
|
59
|
+
if (params.threshold)
|
|
60
|
+
query.set("threshold", String(params.threshold));
|
|
61
|
+
if (params.limit)
|
|
62
|
+
query.set("limit", String(params.limit));
|
|
63
|
+
return this.request("GET", `/posts/similar?${query}`);
|
|
64
|
+
}
|
|
65
|
+
// --- Votes ---
|
|
66
|
+
async addVote(postId, data) {
|
|
67
|
+
return this.request("POST", `/posts/${postId}/votes`, data);
|
|
68
|
+
}
|
|
69
|
+
async getVoters(postId) {
|
|
70
|
+
return this.request("GET", `/posts/${postId}/votes`);
|
|
71
|
+
}
|
|
72
|
+
// --- Comments ---
|
|
73
|
+
async addComment(postId, data) {
|
|
74
|
+
return this.request("POST", `/posts/${postId}/comments`, data);
|
|
75
|
+
}
|
|
76
|
+
async getComments(postId) {
|
|
77
|
+
return this.request("GET", `/posts/${postId}/comments`);
|
|
78
|
+
}
|
|
79
|
+
// --- Tags ---
|
|
80
|
+
async listTags() {
|
|
81
|
+
return this.request("GET", "/tags");
|
|
82
|
+
}
|
|
83
|
+
async createTag(data) {
|
|
84
|
+
return this.request("POST", "/tags", data);
|
|
85
|
+
}
|
|
86
|
+
// --- Users ---
|
|
87
|
+
async listUsers(params) {
|
|
88
|
+
const query = new URLSearchParams();
|
|
89
|
+
if (params?.limit)
|
|
90
|
+
query.set("limit", String(params.limit));
|
|
91
|
+
if (params?.page)
|
|
92
|
+
query.set("page", String(params.page));
|
|
93
|
+
const qs = query.toString();
|
|
94
|
+
return this.request("GET", `/users${qs ? `?${qs}` : ""}`);
|
|
95
|
+
}
|
|
96
|
+
// --- Roadmap ---
|
|
97
|
+
async getRoadmap() {
|
|
98
|
+
return this.request("GET", "/roadmap");
|
|
99
|
+
}
|
|
100
|
+
// --- Releases ---
|
|
101
|
+
async listReleases(params) {
|
|
102
|
+
const query = new URLSearchParams();
|
|
103
|
+
if (params?.limit)
|
|
104
|
+
query.set("limit", String(params.limit));
|
|
105
|
+
const qs = query.toString();
|
|
106
|
+
return this.request("GET", `/releases${qs ? `?${qs}` : ""}`);
|
|
107
|
+
}
|
|
108
|
+
async createRelease(data) {
|
|
109
|
+
return this.request("POST", "/releases", data);
|
|
110
|
+
}
|
|
111
|
+
// --- Analytics ---
|
|
112
|
+
async getAnalytics(params) {
|
|
113
|
+
const query = new URLSearchParams();
|
|
114
|
+
if (params?.period)
|
|
115
|
+
query.set("period", params.period);
|
|
116
|
+
const qs = query.toString();
|
|
117
|
+
return this.request("GET", `/analytics/summary${qs ? `?${qs}` : ""}`);
|
|
118
|
+
}
|
|
119
|
+
// --- AI Workflows ---
|
|
120
|
+
async submitFeedback(data) {
|
|
121
|
+
return this.request("POST", "/posts/from-text", data);
|
|
122
|
+
}
|
|
123
|
+
async triageInbox(params) {
|
|
124
|
+
return this.request("POST", "/ai/triage", params);
|
|
125
|
+
}
|
|
126
|
+
async getSummary(params) {
|
|
127
|
+
return this.request("POST", "/ai/summary", params);
|
|
128
|
+
}
|
|
129
|
+
async planSprint(params) {
|
|
130
|
+
return this.request("POST", "/ai/sprint-plan", params);
|
|
131
|
+
}
|
|
132
|
+
// --- Webhooks ---
|
|
133
|
+
async configureWebhook(data) {
|
|
134
|
+
return this.request("POST", "/integrations/webhooks", data);
|
|
135
|
+
}
|
|
136
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { VoteShipClient } from "./client.js";
|
|
5
|
+
// Tools
|
|
6
|
+
import { registerPostTools } from "./tools/posts.js";
|
|
7
|
+
import { registerSearchTools } from "./tools/search.js";
|
|
8
|
+
import { registerVoteTools } from "./tools/votes.js";
|
|
9
|
+
import { registerCommentTools } from "./tools/comments.js";
|
|
10
|
+
import { registerTagTools } from "./tools/tags.js";
|
|
11
|
+
import { registerUserTools } from "./tools/users.js";
|
|
12
|
+
import { registerRoadmapTools } from "./tools/roadmap.js";
|
|
13
|
+
import { registerReleaseTools } from "./tools/releases.js";
|
|
14
|
+
import { registerAnalyticsTools } from "./tools/analytics.js";
|
|
15
|
+
import { registerAiTools } from "./tools/ai.js";
|
|
16
|
+
import { registerWebhookTools } from "./tools/webhooks.js";
|
|
17
|
+
// Resources
|
|
18
|
+
import { registerProjectResources } from "./resources/project.js";
|
|
19
|
+
import { registerRoadmapResources } from "./resources/roadmap.js";
|
|
20
|
+
import { registerChangelogResources } from "./resources/changelog.js";
|
|
21
|
+
import { registerAnalyticsResources } from "./resources/analytics.js";
|
|
22
|
+
// Prompts
|
|
23
|
+
import { registerTriagePrompt } from "./prompts/triage.js";
|
|
24
|
+
import { registerSprintPrompt } from "./prompts/sprint.js";
|
|
25
|
+
import { registerChangelogPrompt } from "./prompts/changelog.js";
|
|
26
|
+
import { registerSummaryPrompt } from "./prompts/summary.js";
|
|
27
|
+
async function main() {
|
|
28
|
+
const server = new McpServer({
|
|
29
|
+
name: "VoteShip",
|
|
30
|
+
version: "0.1.0",
|
|
31
|
+
});
|
|
32
|
+
const client = new VoteShipClient();
|
|
33
|
+
// Register all tools (22 total)
|
|
34
|
+
registerPostTools(server, client);
|
|
35
|
+
registerSearchTools(server, client);
|
|
36
|
+
registerVoteTools(server, client);
|
|
37
|
+
registerCommentTools(server, client);
|
|
38
|
+
registerTagTools(server, client);
|
|
39
|
+
registerUserTools(server, client);
|
|
40
|
+
registerRoadmapTools(server, client);
|
|
41
|
+
registerReleaseTools(server, client);
|
|
42
|
+
registerAnalyticsTools(server, client);
|
|
43
|
+
registerAiTools(server, client);
|
|
44
|
+
registerWebhookTools(server, client);
|
|
45
|
+
// Register all resources (5 total)
|
|
46
|
+
registerProjectResources(server, client);
|
|
47
|
+
registerRoadmapResources(server, client);
|
|
48
|
+
registerChangelogResources(server, client);
|
|
49
|
+
registerAnalyticsResources(server, client);
|
|
50
|
+
// Register all prompts (4 total)
|
|
51
|
+
registerTriagePrompt(server);
|
|
52
|
+
registerSprintPrompt(server);
|
|
53
|
+
registerChangelogPrompt(server);
|
|
54
|
+
registerSummaryPrompt(server);
|
|
55
|
+
// Start the server on stdio transport
|
|
56
|
+
const transport = new StdioServerTransport();
|
|
57
|
+
await server.connect(transport);
|
|
58
|
+
}
|
|
59
|
+
main().catch((error) => {
|
|
60
|
+
console.error("Failed to start VoteShip MCP server:", error);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function registerChangelogPrompt(server: any): void;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function registerChangelogPrompt(server) {
|
|
2
|
+
server.prompt("generate_changelog", "Draft release notes from recently completed feature requests", [
|
|
3
|
+
{
|
|
4
|
+
name: "period",
|
|
5
|
+
description: "Time period to look back: week, month, or quarter (default: month)",
|
|
6
|
+
required: false,
|
|
7
|
+
},
|
|
8
|
+
], async (params) => ({
|
|
9
|
+
messages: [
|
|
10
|
+
{
|
|
11
|
+
role: "user",
|
|
12
|
+
content: {
|
|
13
|
+
type: "text",
|
|
14
|
+
text: `You are a product marketer writing changelog entries for VoteShip. Follow this workflow:
|
|
15
|
+
|
|
16
|
+
1. Use \`list_posts\` with status=COMPLETE to find recently completed features
|
|
17
|
+
2. Use \`list_releases\` to see the most recent published release (to avoid duplicates)
|
|
18
|
+
3. Draft a professional, user-facing changelog entry that:
|
|
19
|
+
- Groups features into logical categories
|
|
20
|
+
- Uses clear, non-technical language
|
|
21
|
+
- Highlights the user benefit of each feature
|
|
22
|
+
- Mentions vote counts to show community impact
|
|
23
|
+
4. Use \`create_release\` to publish the changelog with is_draft=true so it can be reviewed
|
|
24
|
+
|
|
25
|
+
Period to cover: ${params.period || "month"}
|
|
26
|
+
|
|
27
|
+
Start by calling \`list_posts\` with status=COMPLETE.`,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function registerSprintPrompt(server: any): void;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export function registerSprintPrompt(server) {
|
|
2
|
+
server.prompt("sprint_planning", "Suggest what to build next based on votes, themes, and prioritization strategy", [
|
|
3
|
+
{
|
|
4
|
+
name: "capacity",
|
|
5
|
+
description: "Number of features for the sprint (default: 5)",
|
|
6
|
+
required: false,
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
name: "strategy",
|
|
10
|
+
description: "Prioritization: balanced, revenue, popular, or quick-wins (default: balanced)",
|
|
11
|
+
required: false,
|
|
12
|
+
},
|
|
13
|
+
], async (params) => ({
|
|
14
|
+
messages: [
|
|
15
|
+
{
|
|
16
|
+
role: "user",
|
|
17
|
+
content: {
|
|
18
|
+
type: "text",
|
|
19
|
+
text: `You are a product manager planning the next sprint using VoteShip data. Follow this workflow:
|
|
20
|
+
|
|
21
|
+
1. Use \`get_roadmap\` to see what's currently planned and in progress
|
|
22
|
+
2. Use \`plan_sprint\` with capacity=${params.capacity || "5"} and strategy="${params.strategy || "balanced"}" to get AI recommendations
|
|
23
|
+
3. Review the suggestions — each has effort/impact estimates and reasoning
|
|
24
|
+
4. Use \`get_analytics\` to check recent trends and validate the plan
|
|
25
|
+
5. Present the final sprint plan with:
|
|
26
|
+
- Selected features with their vote counts and tags
|
|
27
|
+
- Reasoning for each selection
|
|
28
|
+
- Any features that were close but didn't make the cut
|
|
29
|
+
- Suggested order of implementation
|
|
30
|
+
|
|
31
|
+
Start by calling \`get_roadmap\` to understand the current state.`,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function registerSummaryPrompt(server: any): void;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function registerSummaryPrompt(server) {
|
|
2
|
+
server.prompt("feedback_summary", "Summarize feedback trends, highlights, and actionable insights for a time period", [
|
|
3
|
+
{
|
|
4
|
+
name: "period",
|
|
5
|
+
description: "Time period: week, month, or quarter (default: week)",
|
|
6
|
+
required: false,
|
|
7
|
+
},
|
|
8
|
+
], async (params) => ({
|
|
9
|
+
messages: [
|
|
10
|
+
{
|
|
11
|
+
role: "user",
|
|
12
|
+
content: {
|
|
13
|
+
type: "text",
|
|
14
|
+
text: `You are a product analyst summarizing feedback from VoteShip. Follow this workflow:
|
|
15
|
+
|
|
16
|
+
1. Use \`get_summary\` with period="${params.period || "week"}" to get an AI-generated summary with stats
|
|
17
|
+
2. Use \`get_analytics\` with period="${params.period || "week"}" to get detailed metrics
|
|
18
|
+
3. Use \`get_roadmap\` to see how current plans align with feedback
|
|
19
|
+
4. Present a comprehensive report covering:
|
|
20
|
+
- Key metrics (new posts, votes, comments, page views)
|
|
21
|
+
- Top trending features and themes
|
|
22
|
+
- Notable patterns or shifts in user sentiment
|
|
23
|
+
- Recommended actions for the product team
|
|
24
|
+
- Features that may need reprioritization based on new data
|
|
25
|
+
|
|
26
|
+
Start by calling \`get_summary\`.`,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function registerTriagePrompt(server: any): void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function registerTriagePrompt(server) {
|
|
2
|
+
server.prompt("triage_inbox", "Review and categorize unprocessed feature requests — detect duplicates, suggest statuses and tags, flag spam", async () => ({
|
|
3
|
+
messages: [
|
|
4
|
+
{
|
|
5
|
+
role: "user",
|
|
6
|
+
content: {
|
|
7
|
+
type: "text",
|
|
8
|
+
text: `You are a product manager triaging VoteShip feature requests. Follow this workflow:
|
|
9
|
+
|
|
10
|
+
1. First, use the \`triage_inbox\` tool to get AI-powered recommendations for pending posts
|
|
11
|
+
2. Review the recommendations — they include suggested status, tags, priority, and duplicate detection
|
|
12
|
+
3. For each recommendation:
|
|
13
|
+
- If the suggestion looks correct, use \`update_post\` to apply the status and tags
|
|
14
|
+
- If a duplicate is detected (similarity > 0.85), consider merging or closing the duplicate
|
|
15
|
+
- If the post looks like spam, update status to CLOSED
|
|
16
|
+
4. After processing, use \`get_summary\` to generate a summary of what was triaged
|
|
17
|
+
|
|
18
|
+
Start by calling the \`triage_inbox\` tool now.`,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function registerAnalyticsResources(server, client) {
|
|
2
|
+
server.resource("project-analytics", "voteship://project/analytics", "Analytics snapshot — daily stats, top posts, and trending tags for the past week", async () => {
|
|
3
|
+
const result = await client.getAnalytics({ period: "week" });
|
|
4
|
+
return {
|
|
5
|
+
contents: [
|
|
6
|
+
{
|
|
7
|
+
uri: "voteship://project/analytics",
|
|
8
|
+
mimeType: "application/json",
|
|
9
|
+
text: JSON.stringify(result, null, 2),
|
|
10
|
+
},
|
|
11
|
+
],
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function registerChangelogResources(server, client) {
|
|
2
|
+
server.resource("project-changelog", "voteship://project/changelog", "Published changelog releases — recent product updates and shipped features", async () => {
|
|
3
|
+
const result = await client.listReleases({ limit: 20 });
|
|
4
|
+
return {
|
|
5
|
+
contents: [
|
|
6
|
+
{
|
|
7
|
+
uri: "voteship://project/changelog",
|
|
8
|
+
mimeType: "application/json",
|
|
9
|
+
text: JSON.stringify(result, null, 2),
|
|
10
|
+
},
|
|
11
|
+
],
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export function registerProjectResources(server, client) {
|
|
2
|
+
server.resource("project-overview", "voteship://project/overview", "Project summary with key stats — post counts, vote counts, and configuration", async () => {
|
|
3
|
+
const [postsData, tagsData, usersData] = await Promise.all([
|
|
4
|
+
client.listPosts({ limit: 1 }),
|
|
5
|
+
client.listTags(),
|
|
6
|
+
client.listUsers({ limit: 1 }),
|
|
7
|
+
]);
|
|
8
|
+
const summary = {
|
|
9
|
+
total_posts: postsData.pagination?.total ?? 0,
|
|
10
|
+
total_users: usersData.pagination?.total ?? 0,
|
|
11
|
+
tags: (tagsData.data ?? []).map((t) => t.label),
|
|
12
|
+
};
|
|
13
|
+
return {
|
|
14
|
+
contents: [
|
|
15
|
+
{
|
|
16
|
+
uri: "voteship://project/overview",
|
|
17
|
+
mimeType: "application/json",
|
|
18
|
+
text: JSON.stringify(summary, null, 2),
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
server.resource("project-board", "voteship://project/board", "Full board state — all posts grouped by status with tags and vote counts", async () => {
|
|
24
|
+
const result = await client.listPosts({ limit: 100 });
|
|
25
|
+
return {
|
|
26
|
+
contents: [
|
|
27
|
+
{
|
|
28
|
+
uri: "voteship://project/board",
|
|
29
|
+
mimeType: "application/json",
|
|
30
|
+
text: JSON.stringify(result, null, 2),
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function registerRoadmapResources(server, client) {
|
|
2
|
+
server.resource("project-roadmap", "voteship://project/roadmap", "Public roadmap — approved, in-progress, and completed features sorted by votes", async () => {
|
|
3
|
+
const result = await client.getRoadmap();
|
|
4
|
+
return {
|
|
5
|
+
contents: [
|
|
6
|
+
{
|
|
7
|
+
uri: "voteship://project/roadmap",
|
|
8
|
+
mimeType: "application/json",
|
|
9
|
+
text: JSON.stringify(result, null, 2),
|
|
10
|
+
},
|
|
11
|
+
],
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
}
|
package/dist/tools/ai.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function registerAiTools(server, client) {
|
|
2
|
+
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.", {
|
|
3
|
+
text: { type: "string", description: "Raw text describing a feature request, e.g. 'Multiple customers asked for PDF export'" },
|
|
4
|
+
source: { type: "string", description: "Where the feedback came from: slack, email, support, intercom, etc." },
|
|
5
|
+
source_url: { type: "string", description: "URL to the original message (e.g. Slack permalink)" },
|
|
6
|
+
}, async (params) => {
|
|
7
|
+
const result = await client.submitFeedback(params);
|
|
8
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
9
|
+
});
|
|
10
|
+
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.", {
|
|
11
|
+
limit: { type: "number", description: "Maximum posts to triage (default: 20, max: 50)" },
|
|
12
|
+
}, async (params) => {
|
|
13
|
+
const result = await client.triageInbox(params);
|
|
14
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
15
|
+
});
|
|
16
|
+
server.tool("get_summary", "Generate an AI-powered natural language summary of recent feedback trends, highlights, and recommended actions. Requires Growth plan.", {
|
|
17
|
+
period: { type: "string", description: "Time period: week, month, or quarter (default: week)" },
|
|
18
|
+
}, async (params) => {
|
|
19
|
+
const result = await client.getSummary(params);
|
|
20
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
21
|
+
});
|
|
22
|
+
server.tool("plan_sprint", "AI-suggested sprint plan based on approved feature requests, votes, tags, and the selected prioritization strategy. Requires Growth plan.", {
|
|
23
|
+
capacity: { type: "number", description: "Number of features to include in the sprint (default: 5, max: 20)" },
|
|
24
|
+
strategy: { type: "string", description: "Prioritization strategy: balanced, revenue, popular, or quick-wins (default: balanced)" },
|
|
25
|
+
}, async (params) => {
|
|
26
|
+
const result = await client.planSprint(params);
|
|
27
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function registerAnalyticsTools(server, client) {
|
|
2
|
+
server.tool("get_analytics", "Get analytics summary — new posts, votes, comments, page views, top posts, and trending tags for a given time period", {
|
|
3
|
+
period: { type: "string", description: "Time period: week, month, or quarter (default: week)" },
|
|
4
|
+
}, async (params) => {
|
|
5
|
+
const result = await client.getAnalytics(params);
|
|
6
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
7
|
+
});
|
|
8
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function registerCommentTools(server, client) {
|
|
2
|
+
server.tool("add_comment", "Add a comment to a feature request post", {
|
|
3
|
+
post_id: { type: "string", description: "The post ID to comment on" },
|
|
4
|
+
body: { type: "string", description: "Comment text content" },
|
|
5
|
+
author_name: { type: "string", description: "Display name for the comment author" },
|
|
6
|
+
}, async (params) => {
|
|
7
|
+
const result = await client.addComment(params.post_id, {
|
|
8
|
+
body: params.body,
|
|
9
|
+
authorName: params.author_name,
|
|
10
|
+
});
|
|
11
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
12
|
+
});
|
|
13
|
+
server.tool("get_comments", "List all comments on a feature request post", {
|
|
14
|
+
post_id: { type: "string", description: "The post ID to get comments for" },
|
|
15
|
+
}, async (params) => {
|
|
16
|
+
const result = await client.getComments(params.post_id);
|
|
17
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export function registerPostTools(server, client) {
|
|
2
|
+
server.tool("list_posts", "List feature requests with optional filters for status, sorting, and pagination", {
|
|
3
|
+
status: { type: "string", description: "Filter by status: PENDING, APPROVED, IN_PROGRESS, COMPLETE, CLOSED" },
|
|
4
|
+
sort: { type: "string", description: "Sort order: votes (default) or date" },
|
|
5
|
+
limit: { type: "number", description: "Results per page (default: 50, max: 100)" },
|
|
6
|
+
page: { type: "number", description: "Page number (default: 1)" },
|
|
7
|
+
}, async (params) => {
|
|
8
|
+
const result = await client.listPosts(params);
|
|
9
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
10
|
+
});
|
|
11
|
+
server.tool("get_post", "Get a single feature request post with its votes, comments, and tags", {
|
|
12
|
+
post_id: { type: "string", description: "The post ID to retrieve" },
|
|
13
|
+
}, async (params) => {
|
|
14
|
+
const result = await client.getPost(params.post_id);
|
|
15
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
16
|
+
});
|
|
17
|
+
server.tool("create_post", "Create a new feature request post. Title is required. Status defaults to PENDING if not provided.", {
|
|
18
|
+
title: { type: "string", description: "Feature request title (max 200 chars)" },
|
|
19
|
+
description: { type: "string", description: "Detailed description of the feature request" },
|
|
20
|
+
status: { type: "string", description: "Initial status: PENDING, APPROVED, IN_PROGRESS, COMPLETE, CLOSED" },
|
|
21
|
+
tag_ids: { type: "string", description: "Comma-separated tag IDs to attach" },
|
|
22
|
+
}, async (params) => {
|
|
23
|
+
const tagIds = params.tag_ids ? params.tag_ids.split(",").map((s) => s.trim()) : undefined;
|
|
24
|
+
const result = await client.createPost({
|
|
25
|
+
title: params.title,
|
|
26
|
+
description: params.description,
|
|
27
|
+
status: params.status,
|
|
28
|
+
tagIds,
|
|
29
|
+
});
|
|
30
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
31
|
+
});
|
|
32
|
+
server.tool("update_post", "Update an existing post's title, description, status, or tags. Only include fields you want to change.", {
|
|
33
|
+
post_id: { type: "string", description: "The post ID to update" },
|
|
34
|
+
title: { type: "string", description: "New title (max 200 chars)" },
|
|
35
|
+
description: { type: "string", description: "New description" },
|
|
36
|
+
status: { type: "string", description: "New status: PENDING, APPROVED, IN_PROGRESS, COMPLETE, CLOSED" },
|
|
37
|
+
tag_ids: { type: "string", description: "Comma-separated tag IDs (replaces all existing tags)" },
|
|
38
|
+
}, async (params) => {
|
|
39
|
+
const tagIds = params.tag_ids ? params.tag_ids.split(",").map((s) => s.trim()) : undefined;
|
|
40
|
+
const result = await client.updatePost(params.post_id, {
|
|
41
|
+
title: params.title,
|
|
42
|
+
description: params.description,
|
|
43
|
+
status: params.status,
|
|
44
|
+
tagIds,
|
|
45
|
+
});
|
|
46
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
47
|
+
});
|
|
48
|
+
server.tool("delete_post", "Permanently delete a feature request and all its associated votes and comments", {
|
|
49
|
+
post_id: { type: "string", description: "The post ID to delete" },
|
|
50
|
+
}, async (params) => {
|
|
51
|
+
const result = await client.deletePost(params.post_id);
|
|
52
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
53
|
+
});
|
|
54
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function registerReleaseTools(server, client) {
|
|
2
|
+
server.tool("list_releases", "List published changelog releases", {
|
|
3
|
+
limit: { type: "number", description: "Number of releases to return (default: 20)" },
|
|
4
|
+
}, async (params) => {
|
|
5
|
+
const result = await client.listReleases(params);
|
|
6
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
7
|
+
});
|
|
8
|
+
server.tool("create_release", "Create a new changelog release. Publishes immediately unless is_draft is true.", {
|
|
9
|
+
title: { type: "string", description: "Release title, e.g. 'January 2026 Update'" },
|
|
10
|
+
content: { type: "string", description: "Release notes content (HTML supported)" },
|
|
11
|
+
is_draft: { type: "boolean", description: "Save as draft (true) or publish immediately (false, default)" },
|
|
12
|
+
}, async (params) => {
|
|
13
|
+
const result = await client.createRelease({
|
|
14
|
+
title: params.title,
|
|
15
|
+
content: params.content,
|
|
16
|
+
isDraft: params.is_draft,
|
|
17
|
+
});
|
|
18
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export function registerRoadmapTools(server, client) {
|
|
2
|
+
server.tool("get_roadmap", "Get the product roadmap — feature requests grouped by status (Approved, In Progress, Complete), sorted by vote count", {}, async () => {
|
|
3
|
+
const result = await client.getRoadmap();
|
|
4
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
5
|
+
});
|
|
6
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function registerSearchTools(server, client) {
|
|
2
|
+
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.", {
|
|
3
|
+
query: { type: "string", description: "Natural language search query, e.g. 'export data as PDF'" },
|
|
4
|
+
threshold: { type: "number", description: "Minimum similarity score 0-1 (default: 0.7)" },
|
|
5
|
+
limit: { type: "number", description: "Maximum results to return (default: 5, max: 20)" },
|
|
6
|
+
}, async (params) => {
|
|
7
|
+
const result = await client.searchSimilar(params);
|
|
8
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function registerTagTools(server, client) {
|
|
2
|
+
server.tool("list_tags", "List all available tags for categorizing feature requests", {}, async () => {
|
|
3
|
+
const result = await client.listTags();
|
|
4
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
5
|
+
});
|
|
6
|
+
server.tool("create_tag", "Create a new tag for categorizing feature requests", {
|
|
7
|
+
label: { type: "string", description: "Tag name, e.g. 'Mobile', 'Performance', 'UI'" },
|
|
8
|
+
theme: { type: "string", description: "Hex color for the tag, e.g. '#6366f1'" },
|
|
9
|
+
}, async (params) => {
|
|
10
|
+
const result = await client.createTag(params);
|
|
11
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
12
|
+
});
|
|
13
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function registerUserTools(server, client) {
|
|
2
|
+
server.tool("list_users", "List board users who have submitted feedback or voted on feature requests", {
|
|
3
|
+
limit: { type: "number", description: "Results per page (default: 50, max: 100)" },
|
|
4
|
+
page: { type: "number", description: "Page number (default: 1)" },
|
|
5
|
+
}, async (params) => {
|
|
6
|
+
const result = await client.listUsers(params);
|
|
7
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
8
|
+
});
|
|
9
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function registerVoteTools(server, client) {
|
|
2
|
+
server.tool("add_vote", "Add a vote to a feature request. Optionally specify a board user ID or anonymous ID.", {
|
|
3
|
+
post_id: { type: "string", description: "The post ID to vote on" },
|
|
4
|
+
board_user_id: { type: "string", description: "Board user ID (for identified users)" },
|
|
5
|
+
anonymous_id: { type: "string", description: "Anonymous ID (for unidentified users)" },
|
|
6
|
+
}, async (params) => {
|
|
7
|
+
const result = await client.addVote(params.post_id, {
|
|
8
|
+
boardUserId: params.board_user_id,
|
|
9
|
+
anonymousId: params.anonymous_id,
|
|
10
|
+
});
|
|
11
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
12
|
+
});
|
|
13
|
+
server.tool("get_voters", "List all users who voted on a specific feature request", {
|
|
14
|
+
post_id: { type: "string", description: "The post ID to get voters for" },
|
|
15
|
+
}, async (params) => {
|
|
16
|
+
const result = await client.getVoters(params.post_id);
|
|
17
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function registerWebhookTools(server, client) {
|
|
2
|
+
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.", {
|
|
3
|
+
url: { type: "string", description: "The HTTPS URL to receive webhook POST requests" },
|
|
4
|
+
events: { type: "string", description: "Comma-separated event types to subscribe to, e.g. 'post.created,vote.created' or '*' for all" },
|
|
5
|
+
description: { type: "string", description: "Human-readable description of this webhook" },
|
|
6
|
+
}, async (params) => {
|
|
7
|
+
const events = params.events.split(",").map((e) => e.trim());
|
|
8
|
+
const result = await client.configureWebhook({
|
|
9
|
+
url: params.url,
|
|
10
|
+
events,
|
|
11
|
+
description: params.description,
|
|
12
|
+
});
|
|
13
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
14
|
+
});
|
|
15
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@voteship/mcp-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for VoteShip — manage feature requests, votes, roadmaps, and AI workflows from any MCP client",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"voteship-mcp": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"dev": "tsc --watch",
|
|
17
|
+
"typecheck": "tsc --noEmit"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.12.1"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^22.0.0",
|
|
24
|
+
"typescript": "^5.7.0"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"mcp",
|
|
28
|
+
"model-context-protocol",
|
|
29
|
+
"voteship",
|
|
30
|
+
"feature-requests",
|
|
31
|
+
"ai-agent"
|
|
32
|
+
],
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/voteship/voteship"
|
|
40
|
+
}
|
|
41
|
+
}
|