@vailent/pulse-mcp 1.0.0 → 1.2.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 +113 -0
- package/dist/server.js +89 -10
- package/package.json +4 -3
package/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# @vailent/pulse-mcp
|
|
2
|
+
|
|
3
|
+
Pulse MCP server for [Claude Code](https://docs.anthropic.com/en/docs/claude-code). Manage pods, features, workstreams, bugs, and more — all from your terminal.
|
|
4
|
+
|
|
5
|
+
## Quick Setup (2 minutes)
|
|
6
|
+
|
|
7
|
+
### 1. Create an npm account (skip if you have one)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm adduser
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Follow the prompts to create your account at [npmjs.com](https://www.npmjs.com/signup).
|
|
14
|
+
|
|
15
|
+
### 2. Get added to the Vailent npm org
|
|
16
|
+
|
|
17
|
+
Ask Sal to invite you using your npm username. You'll get an email — accept the invite.
|
|
18
|
+
|
|
19
|
+
### 3. Log in to the Vailent scope
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm login --scope=@vailent
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 4. Add to Claude Code
|
|
26
|
+
|
|
27
|
+
Run this once from any directory:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
claude mcp add pulse-dev -- npx -y @vailent/pulse-mcp
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 5. Verify it works
|
|
34
|
+
|
|
35
|
+
Open Claude Code and ask:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
what pods exist?
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
You should see a list of active pods. That's it — you're connected.
|
|
42
|
+
|
|
43
|
+
## What You Can Do
|
|
44
|
+
|
|
45
|
+
Talk to Claude naturally — the MCP tools handle the rest.
|
|
46
|
+
|
|
47
|
+
| Ask Claude... | What happens |
|
|
48
|
+
|---|---|
|
|
49
|
+
| "What pods exist?" | Lists all active pods with members |
|
|
50
|
+
| "Show me features for this week" | Lists features by pod and week |
|
|
51
|
+
| "Create a feature called Auth Revamp in pod X" | Creates a new feature |
|
|
52
|
+
| "What bugs are open?" | Lists open bugs with priority |
|
|
53
|
+
| "Show me pending suggestions" | Lists AI suggestions needing review |
|
|
54
|
+
| "Give me my daily briefing" | AI-generated focus items for today |
|
|
55
|
+
| "What happened this week?" | Searches activity history |
|
|
56
|
+
| "Show the team roster" | Lists team members and pod assignments |
|
|
57
|
+
| "What feature requests came in?" | Lists feature requests by source |
|
|
58
|
+
|
|
59
|
+
### All 11 Tools
|
|
60
|
+
|
|
61
|
+
| Tool | Description |
|
|
62
|
+
|---|---|
|
|
63
|
+
| `pulse_pods` | List, get, create, update pods |
|
|
64
|
+
| `pulse_pod_members` | Manage pod membership (add, remove, update roles) |
|
|
65
|
+
| `pulse_features` | List, get, create, update features with phase/step tracking |
|
|
66
|
+
| `pulse_workstreams` | Manage workstreams (projects) |
|
|
67
|
+
| `pulse_requests` | View and manage feature requests |
|
|
68
|
+
| `pulse_bugs` | View and manage bug reports |
|
|
69
|
+
| `pulse_suggestions` | Review, approve, reject AI suggestions |
|
|
70
|
+
| `pulse_events` | Query the activity event stream |
|
|
71
|
+
| `pulse_history` | Search across all historical data |
|
|
72
|
+
| `pulse_briefing` | Generate AI daily briefing by role |
|
|
73
|
+
| `pulse_team` | View team members and assignments |
|
|
74
|
+
|
|
75
|
+
## Troubleshooting
|
|
76
|
+
|
|
77
|
+
### "Tool not found" or MCP not connecting
|
|
78
|
+
|
|
79
|
+
Make sure the server is registered:
|
|
80
|
+
```bash
|
|
81
|
+
claude mcp list
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
You should see `pulse-dev` in the list. If not, re-run step 4.
|
|
85
|
+
|
|
86
|
+
### "Missing Supabase credentials" error
|
|
87
|
+
|
|
88
|
+
This shouldn't happen — the dev database is preconfigured. If it does, set these env vars:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
export PULSE_SUPABASE_URL=https://eppugcfjlqovlauykzfe.supabase.co
|
|
92
|
+
export PULSE_SUPABASE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImVwcHVnY2ZqbHFvdmxhdXlremZlIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzQwNTg2NTYsImV4cCI6MjA4OTYzNDY1Nn0.x9u7sSKnSlTFRWCu--D8zt81IuOmoXXHG3fb3kcRvBc
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Need to update to the latest version
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npx -y @vailent/pulse-mcp@latest
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Or clear the npx cache:
|
|
102
|
+
```bash
|
|
103
|
+
npx clear-npx-cache && claude mcp remove pulse-dev && claude mcp add pulse-dev -- npx -y @vailent/pulse-mcp
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Advanced: Custom Supabase Instance
|
|
107
|
+
|
|
108
|
+
To point at a different Supabase project, set these env vars before running Claude Code:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
export PULSE_SUPABASE_URL=https://your-project.supabase.co
|
|
112
|
+
export PULSE_SUPABASE_KEY=your-anon-key
|
|
113
|
+
```
|
package/dist/server.js
CHANGED
|
@@ -40386,12 +40386,11 @@ function shouldShowDeprecationWarning() {
|
|
|
40386
40386
|
if (shouldShowDeprecationWarning()) console.warn("\u26A0\uFE0F Node.js 18 and below are deprecated and will no longer be supported in future versions of @supabase/supabase-js. Please upgrade to Node.js 20 or later. For more information, visit: https://github.com/orgs/supabase/discussions/37217");
|
|
40387
40387
|
|
|
40388
40388
|
// lib/supabase.ts
|
|
40389
|
+
var DEFAULT_URL = "https://eppugcfjlqovlauykzfe.supabase.co";
|
|
40390
|
+
var DEFAULT_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImVwcHVnY2ZqbHFvdmxhdXlremZlIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzQwNTg2NTYsImV4cCI6MjA4OTYzNDY1Nn0.x9u7sSKnSlTFRWCu--D8zt81IuOmoXXHG3fb3kcRvBc";
|
|
40389
40391
|
function getAdminClient() {
|
|
40390
|
-
const url2 = process.env.PULSE_SUPABASE_URL || process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
40391
|
-
const key = process.env.PULSE_SUPABASE_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY;
|
|
40392
|
-
if (!url2 || !key) {
|
|
40393
|
-
throw new Error("Missing Supabase credentials. Set PULSE_SUPABASE_URL + PULSE_SUPABASE_KEY (or NEXT_PUBLIC_SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY)");
|
|
40394
|
-
}
|
|
40392
|
+
const url2 = process.env.PULSE_SUPABASE_URL || process.env.NEXT_PUBLIC_SUPABASE_URL || DEFAULT_URL;
|
|
40393
|
+
const key = process.env.PULSE_SUPABASE_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY || DEFAULT_KEY;
|
|
40395
40394
|
return createClient(url2, key);
|
|
40396
40395
|
}
|
|
40397
40396
|
|
|
@@ -40534,6 +40533,74 @@ async function handlePodMembers(params) {
|
|
|
40534
40533
|
}
|
|
40535
40534
|
}
|
|
40536
40535
|
|
|
40536
|
+
// lib/current-user.ts
|
|
40537
|
+
import { execSync } from "child_process";
|
|
40538
|
+
var cached2;
|
|
40539
|
+
async function getCurrentUser() {
|
|
40540
|
+
if (cached2 !== void 0) return cached2;
|
|
40541
|
+
try {
|
|
40542
|
+
cached2 = await resolveUser();
|
|
40543
|
+
if (cached2) {
|
|
40544
|
+
console.error(`\u2713 Pulse: identified as ${cached2.fullName} (${cached2.email})`);
|
|
40545
|
+
}
|
|
40546
|
+
return cached2;
|
|
40547
|
+
} catch {
|
|
40548
|
+
cached2 = null;
|
|
40549
|
+
return null;
|
|
40550
|
+
}
|
|
40551
|
+
}
|
|
40552
|
+
async function resolveUser() {
|
|
40553
|
+
const supabase = getAdminClient();
|
|
40554
|
+
const envEmail = process.env.PULSE_USER_EMAIL;
|
|
40555
|
+
if (envEmail?.endsWith("@vailent.com")) {
|
|
40556
|
+
const user = await lookupByEmail(supabase, envEmail);
|
|
40557
|
+
if (user) return user;
|
|
40558
|
+
}
|
|
40559
|
+
const localEmail = getGitEmail("local");
|
|
40560
|
+
if (localEmail?.endsWith("@vailent.com")) {
|
|
40561
|
+
const user = await lookupByEmail(supabase, localEmail);
|
|
40562
|
+
if (user) return user;
|
|
40563
|
+
}
|
|
40564
|
+
const globalEmail = getGitEmail("global");
|
|
40565
|
+
if (globalEmail?.endsWith("@vailent.com")) {
|
|
40566
|
+
const user = await lookupByEmail(supabase, globalEmail);
|
|
40567
|
+
if (user) return user;
|
|
40568
|
+
}
|
|
40569
|
+
const anyEmail = localEmail || globalEmail;
|
|
40570
|
+
if (anyEmail) {
|
|
40571
|
+
console.error(
|
|
40572
|
+
`\u26A0 Pulse: Could not identify you.
|
|
40573
|
+
Your git email (${anyEmail}) is not a @vailent.com address.
|
|
40574
|
+
Fix: git config --global user.email yourname@vailent.com
|
|
40575
|
+
Then restart Claude Code.`
|
|
40576
|
+
);
|
|
40577
|
+
} else {
|
|
40578
|
+
console.error(
|
|
40579
|
+
`\u26A0 Pulse: Could not identify you.
|
|
40580
|
+
Git email not configured.
|
|
40581
|
+
Fix: git config --global user.email yourname@vailent.com
|
|
40582
|
+
Then restart Claude Code.`
|
|
40583
|
+
);
|
|
40584
|
+
}
|
|
40585
|
+
return null;
|
|
40586
|
+
}
|
|
40587
|
+
function getGitEmail(scope) {
|
|
40588
|
+
try {
|
|
40589
|
+
const flag = scope === "local" ? "--local" : "--global";
|
|
40590
|
+
return execSync(`git config ${flag} user.email`, {
|
|
40591
|
+
encoding: "utf-8",
|
|
40592
|
+
timeout: 3e3
|
|
40593
|
+
}).trim() || null;
|
|
40594
|
+
} catch {
|
|
40595
|
+
return null;
|
|
40596
|
+
}
|
|
40597
|
+
}
|
|
40598
|
+
async function lookupByEmail(supabase, email3) {
|
|
40599
|
+
const { data } = await supabase.from("users").select("id, email, full_name, job_title").eq("email", email3).limit(1).single();
|
|
40600
|
+
if (!data) return null;
|
|
40601
|
+
return { id: data.id, email: data.email, fullName: data.full_name, jobTitle: data.job_title || "member" };
|
|
40602
|
+
}
|
|
40603
|
+
|
|
40537
40604
|
// tools/features.ts
|
|
40538
40605
|
async function handleFeatures(params) {
|
|
40539
40606
|
const supabase = getAdminClient();
|
|
@@ -40541,7 +40608,16 @@ async function handleFeatures(params) {
|
|
|
40541
40608
|
switch (action) {
|
|
40542
40609
|
case "list": {
|
|
40543
40610
|
let query = supabase.from("features").select("*").order("week_start", { ascending: false });
|
|
40544
|
-
if (params.podId)
|
|
40611
|
+
if (params.podId) {
|
|
40612
|
+
query = query.eq("pod_id", params.podId);
|
|
40613
|
+
} else {
|
|
40614
|
+
const currentUser = await getCurrentUser();
|
|
40615
|
+
if (currentUser) {
|
|
40616
|
+
const { data: memberships } = await supabase.from("pod_members").select("pod_id").eq("user_id", currentUser.id);
|
|
40617
|
+
const myPodIds = (memberships || []).map((m) => m.pod_id);
|
|
40618
|
+
if (myPodIds.length > 0) query = query.in("pod_id", myPodIds);
|
|
40619
|
+
}
|
|
40620
|
+
}
|
|
40545
40621
|
if (params.projectName) query = query.eq("project_name", params.projectName);
|
|
40546
40622
|
if (params.weekStart) query = query.eq("week_start", params.weekStart);
|
|
40547
40623
|
if (params.status) query = query.eq("status", params.status);
|
|
@@ -42068,9 +42144,9 @@ var multipartFormRequestOptions = async (opts, fetch2, stripFilenames = true) =>
|
|
|
42068
42144
|
var supportsFormDataMap = /* @__PURE__ */ new WeakMap();
|
|
42069
42145
|
function supportsFormData(fetchObject) {
|
|
42070
42146
|
const fetch2 = typeof fetchObject === "function" ? fetchObject : fetchObject.fetch;
|
|
42071
|
-
const
|
|
42072
|
-
if (
|
|
42073
|
-
return
|
|
42147
|
+
const cached3 = supportsFormDataMap.get(fetch2);
|
|
42148
|
+
if (cached3)
|
|
42149
|
+
return cached3;
|
|
42074
42150
|
const promise2 = (async () => {
|
|
42075
42151
|
try {
|
|
42076
42152
|
const FetchResponse = "Response" in fetch2 ? fetch2.Response : (await fetch2("data:,")).constructor;
|
|
@@ -47393,7 +47469,8 @@ function formatWeekStart(date4) {
|
|
|
47393
47469
|
}
|
|
47394
47470
|
async function handleBriefing(params) {
|
|
47395
47471
|
const supabase = getAdminClient();
|
|
47396
|
-
const
|
|
47472
|
+
const currentUser = await getCurrentUser();
|
|
47473
|
+
const jobTitle = params.jobTitle || currentUser?.jobTitle || "engineer";
|
|
47397
47474
|
const podIds = params.podIds;
|
|
47398
47475
|
const now = /* @__PURE__ */ new Date();
|
|
47399
47476
|
const thisWeekStart = getWeekStart(now);
|
|
@@ -47680,6 +47757,8 @@ server.tool(
|
|
|
47680
47757
|
async function main() {
|
|
47681
47758
|
const transport = new StdioServerTransport();
|
|
47682
47759
|
await server.connect(transport);
|
|
47760
|
+
getCurrentUser().catch(() => {
|
|
47761
|
+
});
|
|
47683
47762
|
console.error("Pulse MCP server running");
|
|
47684
47763
|
}
|
|
47685
47764
|
main().catch((e) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vailent/pulse-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Pulse MCP server — manage pods, features, workstreams, bugs, and more from Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,13 +23,14 @@
|
|
|
23
23
|
"typescript": "^5"
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
|
-
"dist"
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md"
|
|
27
28
|
],
|
|
28
29
|
"keywords": ["mcp", "pulse", "project-management", "claude-code"],
|
|
29
30
|
"license": "UNLICENSED",
|
|
30
31
|
"repository": {
|
|
31
32
|
"type": "git",
|
|
32
|
-
"url": "https://github.com/
|
|
33
|
+
"url": "https://github.com/vailent-technology/pulse.git",
|
|
33
34
|
"directory": "src/mcp"
|
|
34
35
|
}
|
|
35
36
|
}
|