@starascendin/lifeos-mcp 0.7.51 → 0.7.53
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 +17 -0
- package/dist/build-info.d.ts +2 -2
- package/dist/build-info.js +2 -2
- package/dist/index.js +614 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -121,9 +121,26 @@ Add to your `.mcp.json` (project root or `~/.claude/mcp.json`):
|
|
|
121
121
|
- **get_clients** - List all clients
|
|
122
122
|
- **get_client** - Get client details
|
|
123
123
|
- **get_projects_for_client** - Get client's projects
|
|
124
|
+
- **get_client_notes** - List client/project/phase notes
|
|
125
|
+
- **get_client_note** - Get one client note with linked context
|
|
126
|
+
- **create_client_note** - Save requirement or account notes
|
|
127
|
+
- **update_client_note** - Update client notes
|
|
124
128
|
- **create_client** - Create a new client
|
|
125
129
|
- **update_client** - Update client details
|
|
126
130
|
|
|
131
|
+
### Meetings
|
|
132
|
+
- **get_fathom_meetings** - List synced Fathom meetings
|
|
133
|
+
- **get_fathom_meeting** - Get Fathom meeting details
|
|
134
|
+
- **get_fathom_transcript** - Get full Fathom transcript
|
|
135
|
+
- **search_fathom_meetings** - Search Fathom meetings
|
|
136
|
+
- **get_granola_meetings** - List synced Granola meetings
|
|
137
|
+
- **get_granola_meeting** - Get Granola meeting details
|
|
138
|
+
- **get_granola_transcript** - Get full Granola transcript
|
|
139
|
+
|
|
140
|
+
### CRM / Customer Success
|
|
141
|
+
- **get_business_contacts** - List business-marked Beeper threads with linked contact/client info
|
|
142
|
+
- **get_client_success_workspace** - Load one client's chats, meetings, notes, projects, and open work in one call
|
|
143
|
+
|
|
127
144
|
## Shared Council Architecture
|
|
128
145
|
|
|
129
146
|
`run_council` calls the Convex `POST /council-skill` endpoint. That is the same shared council core used by:
|
package/dist/build-info.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.7.
|
|
2
|
-
export declare const BUILD_TIME = "2026-03-
|
|
1
|
+
export declare const VERSION = "0.7.53";
|
|
2
|
+
export declare const BUILD_TIME = "2026-03-09T18:11:42.668Z";
|
package/dist/build-info.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// AUTO-GENERATED — do not edit. Regenerated on every build.
|
|
2
|
-
export const VERSION = "0.7.
|
|
3
|
-
export const BUILD_TIME = "2026-03-
|
|
2
|
+
export const VERSION = "0.7.53";
|
|
3
|
+
export const BUILD_TIME = "2026-03-09T18:11:42.668Z";
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import { Command } from "commander";
|
|
|
19
19
|
import { VERSION, BUILD_TIME } from "./build-info.js";
|
|
20
20
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
21
21
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
22
|
-
import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
22
|
+
import { CallToolRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
23
23
|
// Parse CLI arguments
|
|
24
24
|
const program = new Command();
|
|
25
25
|
program
|
|
@@ -1388,6 +1388,143 @@ const TOOLS = [
|
|
|
1388
1388
|
required: ["clientId"],
|
|
1389
1389
|
},
|
|
1390
1390
|
},
|
|
1391
|
+
{
|
|
1392
|
+
name: "get_client_notes",
|
|
1393
|
+
description: "Get client/project/phase notes for customer requirements and account context.",
|
|
1394
|
+
inputSchema: {
|
|
1395
|
+
type: "object",
|
|
1396
|
+
properties: {
|
|
1397
|
+
userId: {
|
|
1398
|
+
type: "string",
|
|
1399
|
+
description: "Override the default user ID (optional)",
|
|
1400
|
+
},
|
|
1401
|
+
clientId: {
|
|
1402
|
+
type: "string",
|
|
1403
|
+
description: "Filter by client ID (optional)",
|
|
1404
|
+
},
|
|
1405
|
+
projectId: {
|
|
1406
|
+
type: "string",
|
|
1407
|
+
description: "Filter by project ID (optional)",
|
|
1408
|
+
},
|
|
1409
|
+
phaseId: {
|
|
1410
|
+
type: "string",
|
|
1411
|
+
description: "Filter by phase ID (optional)",
|
|
1412
|
+
},
|
|
1413
|
+
limit: {
|
|
1414
|
+
type: "number",
|
|
1415
|
+
description: "Max results (default 20, max 100)",
|
|
1416
|
+
},
|
|
1417
|
+
},
|
|
1418
|
+
},
|
|
1419
|
+
},
|
|
1420
|
+
{
|
|
1421
|
+
name: "get_client_note",
|
|
1422
|
+
description: "Get a single client/project/phase note with linked context.",
|
|
1423
|
+
inputSchema: {
|
|
1424
|
+
type: "object",
|
|
1425
|
+
properties: {
|
|
1426
|
+
userId: {
|
|
1427
|
+
type: "string",
|
|
1428
|
+
description: "Override the default user ID (optional)",
|
|
1429
|
+
},
|
|
1430
|
+
noteId: {
|
|
1431
|
+
type: "string",
|
|
1432
|
+
description: "The note ID (required)",
|
|
1433
|
+
},
|
|
1434
|
+
},
|
|
1435
|
+
required: ["noteId"],
|
|
1436
|
+
},
|
|
1437
|
+
},
|
|
1438
|
+
{
|
|
1439
|
+
name: "create_client_note",
|
|
1440
|
+
description: "Create a note for a client, project, or phase to track customer requirements and follow-ups.",
|
|
1441
|
+
inputSchema: {
|
|
1442
|
+
type: "object",
|
|
1443
|
+
properties: {
|
|
1444
|
+
userId: {
|
|
1445
|
+
type: "string",
|
|
1446
|
+
description: "Override the default user ID (optional)",
|
|
1447
|
+
},
|
|
1448
|
+
title: {
|
|
1449
|
+
type: "string",
|
|
1450
|
+
description: "Short note title (required)",
|
|
1451
|
+
},
|
|
1452
|
+
content: {
|
|
1453
|
+
type: "string",
|
|
1454
|
+
description: "Note content (required)",
|
|
1455
|
+
},
|
|
1456
|
+
clientId: {
|
|
1457
|
+
type: "string",
|
|
1458
|
+
description: "Link to a client (optional)",
|
|
1459
|
+
},
|
|
1460
|
+
projectId: {
|
|
1461
|
+
type: "string",
|
|
1462
|
+
description: "Link to a project (optional)",
|
|
1463
|
+
},
|
|
1464
|
+
phaseId: {
|
|
1465
|
+
type: "string",
|
|
1466
|
+
description: "Link to a phase (optional)",
|
|
1467
|
+
},
|
|
1468
|
+
},
|
|
1469
|
+
required: ["title", "content"],
|
|
1470
|
+
},
|
|
1471
|
+
},
|
|
1472
|
+
{
|
|
1473
|
+
name: "update_client_note",
|
|
1474
|
+
description: "Update an existing client/project/phase note.",
|
|
1475
|
+
inputSchema: {
|
|
1476
|
+
type: "object",
|
|
1477
|
+
properties: {
|
|
1478
|
+
userId: {
|
|
1479
|
+
type: "string",
|
|
1480
|
+
description: "Override the default user ID (optional)",
|
|
1481
|
+
},
|
|
1482
|
+
noteId: {
|
|
1483
|
+
type: "string",
|
|
1484
|
+
description: "The note ID (required)",
|
|
1485
|
+
},
|
|
1486
|
+
title: {
|
|
1487
|
+
type: "string",
|
|
1488
|
+
description: "Updated title (optional)",
|
|
1489
|
+
},
|
|
1490
|
+
content: {
|
|
1491
|
+
type: "string",
|
|
1492
|
+
description: "Updated content (optional)",
|
|
1493
|
+
},
|
|
1494
|
+
clientId: {
|
|
1495
|
+
type: "string",
|
|
1496
|
+
description: "Updated client ID, or empty to unlink (optional)",
|
|
1497
|
+
},
|
|
1498
|
+
projectId: {
|
|
1499
|
+
type: "string",
|
|
1500
|
+
description: "Updated project ID, or empty to unlink (optional)",
|
|
1501
|
+
},
|
|
1502
|
+
phaseId: {
|
|
1503
|
+
type: "string",
|
|
1504
|
+
description: "Updated phase ID, or empty to unlink (optional)",
|
|
1505
|
+
},
|
|
1506
|
+
},
|
|
1507
|
+
required: ["noteId"],
|
|
1508
|
+
},
|
|
1509
|
+
},
|
|
1510
|
+
{
|
|
1511
|
+
name: "delete_client_note",
|
|
1512
|
+
description: "Delete a client/project/phase note.",
|
|
1513
|
+
inputSchema: {
|
|
1514
|
+
type: "object",
|
|
1515
|
+
properties: {
|
|
1516
|
+
userId: {
|
|
1517
|
+
type: "string",
|
|
1518
|
+
description: "Override the default user ID (optional)",
|
|
1519
|
+
},
|
|
1520
|
+
noteId: {
|
|
1521
|
+
type: "string",
|
|
1522
|
+
description: "The note ID (required)",
|
|
1523
|
+
},
|
|
1524
|
+
},
|
|
1525
|
+
required: ["noteId"],
|
|
1526
|
+
},
|
|
1527
|
+
},
|
|
1391
1528
|
{
|
|
1392
1529
|
name: "create_client",
|
|
1393
1530
|
description: "Create a new client for consulting/freelance work.",
|
|
@@ -1722,6 +1859,82 @@ const TOOLS = [
|
|
|
1722
1859
|
required: ["clientId"],
|
|
1723
1860
|
},
|
|
1724
1861
|
},
|
|
1862
|
+
// Fathom Meeting Tools
|
|
1863
|
+
{
|
|
1864
|
+
name: "get_fathom_meetings",
|
|
1865
|
+
description: "List all synced Fathom meeting notes and recordings.",
|
|
1866
|
+
inputSchema: {
|
|
1867
|
+
type: "object",
|
|
1868
|
+
properties: {
|
|
1869
|
+
userId: {
|
|
1870
|
+
type: "string",
|
|
1871
|
+
description: "Override the default user ID (optional)",
|
|
1872
|
+
},
|
|
1873
|
+
limit: {
|
|
1874
|
+
type: "number",
|
|
1875
|
+
description: "Max results (default 50)",
|
|
1876
|
+
},
|
|
1877
|
+
},
|
|
1878
|
+
},
|
|
1879
|
+
},
|
|
1880
|
+
{
|
|
1881
|
+
name: "get_fathom_meeting",
|
|
1882
|
+
description: "Get a single Fathom meeting by its Convex meeting ID, including AI summary and action items.",
|
|
1883
|
+
inputSchema: {
|
|
1884
|
+
type: "object",
|
|
1885
|
+
properties: {
|
|
1886
|
+
userId: {
|
|
1887
|
+
type: "string",
|
|
1888
|
+
description: "Override the default user ID (optional)",
|
|
1889
|
+
},
|
|
1890
|
+
meetingId: {
|
|
1891
|
+
type: "string",
|
|
1892
|
+
description: "The Fathom meeting ID (required)",
|
|
1893
|
+
},
|
|
1894
|
+
},
|
|
1895
|
+
required: ["meetingId"],
|
|
1896
|
+
},
|
|
1897
|
+
},
|
|
1898
|
+
{
|
|
1899
|
+
name: "get_fathom_transcript",
|
|
1900
|
+
description: "Get the full transcript for a Fathom meeting.",
|
|
1901
|
+
inputSchema: {
|
|
1902
|
+
type: "object",
|
|
1903
|
+
properties: {
|
|
1904
|
+
userId: {
|
|
1905
|
+
type: "string",
|
|
1906
|
+
description: "Override the default user ID (optional)",
|
|
1907
|
+
},
|
|
1908
|
+
meetingId: {
|
|
1909
|
+
type: "string",
|
|
1910
|
+
description: "The Fathom meeting ID (required)",
|
|
1911
|
+
},
|
|
1912
|
+
},
|
|
1913
|
+
required: ["meetingId"],
|
|
1914
|
+
},
|
|
1915
|
+
},
|
|
1916
|
+
{
|
|
1917
|
+
name: "search_fathom_meetings",
|
|
1918
|
+
description: "Search Fathom meetings by title or summary content.",
|
|
1919
|
+
inputSchema: {
|
|
1920
|
+
type: "object",
|
|
1921
|
+
properties: {
|
|
1922
|
+
userId: {
|
|
1923
|
+
type: "string",
|
|
1924
|
+
description: "Override the default user ID (optional)",
|
|
1925
|
+
},
|
|
1926
|
+
query: {
|
|
1927
|
+
type: "string",
|
|
1928
|
+
description: "Search terms to find in Fathom meetings (required)",
|
|
1929
|
+
},
|
|
1930
|
+
limit: {
|
|
1931
|
+
type: "number",
|
|
1932
|
+
description: "Max results (default 20)",
|
|
1933
|
+
},
|
|
1934
|
+
},
|
|
1935
|
+
required: ["query"],
|
|
1936
|
+
},
|
|
1937
|
+
},
|
|
1725
1938
|
// Granola Meeting Tools
|
|
1726
1939
|
{
|
|
1727
1940
|
name: "get_granola_meetings",
|
|
@@ -1937,6 +2150,40 @@ const TOOLS = [
|
|
|
1937
2150
|
},
|
|
1938
2151
|
},
|
|
1939
2152
|
},
|
|
2153
|
+
{
|
|
2154
|
+
name: "get_client_success_workspace",
|
|
2155
|
+
description: "Load a customer success workspace for one client: projects, open work, linked chats, meetings, and notes in one call.",
|
|
2156
|
+
inputSchema: {
|
|
2157
|
+
type: "object",
|
|
2158
|
+
properties: {
|
|
2159
|
+
userId: {
|
|
2160
|
+
type: "string",
|
|
2161
|
+
description: "Override the default user ID (optional)",
|
|
2162
|
+
},
|
|
2163
|
+
clientIdOrName: {
|
|
2164
|
+
type: "string",
|
|
2165
|
+
description: "Client ID or client name (required)",
|
|
2166
|
+
},
|
|
2167
|
+
messageLimit: {
|
|
2168
|
+
type: "number",
|
|
2169
|
+
description: "Recent messages to include per linked thread (default 5, max 20)",
|
|
2170
|
+
},
|
|
2171
|
+
meetingLimit: {
|
|
2172
|
+
type: "number",
|
|
2173
|
+
description: "Meetings to include across Granola and Fathom (default 10, max 30)",
|
|
2174
|
+
},
|
|
2175
|
+
noteLimit: {
|
|
2176
|
+
type: "number",
|
|
2177
|
+
description: "Notes to include (default 10, max 50)",
|
|
2178
|
+
},
|
|
2179
|
+
openTaskLimit: {
|
|
2180
|
+
type: "number",
|
|
2181
|
+
description: "Open tasks to include (default 25, max 100)",
|
|
2182
|
+
},
|
|
2183
|
+
},
|
|
2184
|
+
required: ["clientIdOrName"],
|
|
2185
|
+
},
|
|
2186
|
+
},
|
|
1940
2187
|
{
|
|
1941
2188
|
name: "get_merge_suggestions",
|
|
1942
2189
|
description: "Get pending contact merge suggestions. Returns pairs of contacts that may be duplicates based on matching email, phone, or name similarity.",
|
|
@@ -4434,6 +4681,22 @@ const PROMPTS = [
|
|
|
4434
4681
|
},
|
|
4435
4682
|
],
|
|
4436
4683
|
},
|
|
4684
|
+
{
|
|
4685
|
+
name: "customer-success-triage",
|
|
4686
|
+
description: "Triage a client request using business chats, Fathom/Granola meetings, notes, and open work.",
|
|
4687
|
+
arguments: [
|
|
4688
|
+
{
|
|
4689
|
+
name: "client",
|
|
4690
|
+
description: "Client name or ID (required)",
|
|
4691
|
+
required: true,
|
|
4692
|
+
},
|
|
4693
|
+
{
|
|
4694
|
+
name: "focus",
|
|
4695
|
+
description: "Optional focus area, like a feature request, escalation, or meeting topic",
|
|
4696
|
+
required: false,
|
|
4697
|
+
},
|
|
4698
|
+
],
|
|
4699
|
+
},
|
|
4437
4700
|
{
|
|
4438
4701
|
name: "project-status",
|
|
4439
4702
|
description: "Project status report: phases, task breakdown, blockers, urgent items.",
|
|
@@ -4805,6 +5068,47 @@ Present as a client brief:
|
|
|
4805
5068
|
},
|
|
4806
5069
|
},
|
|
4807
5070
|
],
|
|
5071
|
+
"customer-success-triage": (args) => {
|
|
5072
|
+
const focusClause = args.focus
|
|
5073
|
+
? `\n\nFocus area: ${args.focus}`
|
|
5074
|
+
: "";
|
|
5075
|
+
return [
|
|
5076
|
+
{
|
|
5077
|
+
role: "user",
|
|
5078
|
+
content: {
|
|
5079
|
+
type: "text",
|
|
5080
|
+
text: `Triage customer success work for "${args.client}". Use the LifeOS MCP tools:
|
|
5081
|
+
|
|
5082
|
+
1. Call get_client_success_workspace with clientIdOrName "${args.client}"
|
|
5083
|
+
2. Review recentThreads, recentMeetings, notes, openTasks, and projects from that workspace
|
|
5084
|
+
3. If any thread needs deeper inspection, call get_beeper_thread_messages for the relevant thread
|
|
5085
|
+
4. If any meeting needs deeper inspection:
|
|
5086
|
+
- For Fathom, call get_fathom_meeting and optionally get_fathom_transcript
|
|
5087
|
+
- For Granola, call get_granola_meeting and optionally get_granola_transcript
|
|
5088
|
+
5. If there is already requirement history, call get_client_notes for the client
|
|
5089
|
+
|
|
5090
|
+
Classify what you find into:
|
|
5091
|
+
- **New Requirements**: Net-new asks or requested changes
|
|
5092
|
+
- **Follow-Ups**: Things waiting on you or the team
|
|
5093
|
+
- **Delivery Risks**: Blockers, overdue work, ambiguous asks, churn risk
|
|
5094
|
+
- **Existing Tracking**: Open tasks or notes already covering the request
|
|
5095
|
+
|
|
5096
|
+
For each item, recommend the next tracking action:
|
|
5097
|
+
- Use create_client_note to save requirement summaries, meeting recaps, or decisions
|
|
5098
|
+
- Use create_issue for implementation work or follow-up tasks
|
|
5099
|
+
- Use update_issue if an existing task should be re-scoped or reprioritized
|
|
5100
|
+
|
|
5101
|
+
Present the result as:
|
|
5102
|
+
- **Situation Summary**: What the client currently needs
|
|
5103
|
+
- **Evidence**: Supporting messages/meetings/notes
|
|
5104
|
+
- **Tracking Plan**: What should be captured as notes vs. tasks
|
|
5105
|
+
- **Next Actions**: Concrete owner/action/deadline suggestions
|
|
5106
|
+
|
|
5107
|
+
Ask for confirmation before any write operations unless I explicitly told you to make changes.${focusClause}`,
|
|
5108
|
+
},
|
|
5109
|
+
},
|
|
5110
|
+
];
|
|
5111
|
+
},
|
|
4808
5112
|
"project-status": (args) => [
|
|
4809
5113
|
{
|
|
4810
5114
|
role: "user",
|
|
@@ -5575,16 +5879,325 @@ async function callConvexJsonEndpoint(path, body) {
|
|
|
5575
5879
|
}
|
|
5576
5880
|
return await response.json();
|
|
5577
5881
|
}
|
|
5882
|
+
const STATIC_RESOURCES = [
|
|
5883
|
+
{
|
|
5884
|
+
uri: "lifeos://customer-success/guide",
|
|
5885
|
+
name: "customer_success_guide",
|
|
5886
|
+
title: "Customer Success Guide",
|
|
5887
|
+
mimeType: "text/markdown",
|
|
5888
|
+
description: "How to use LifeOS customer-success resources, notes, and issue tracking together.",
|
|
5889
|
+
},
|
|
5890
|
+
];
|
|
5891
|
+
const RESOURCE_TEMPLATES = [
|
|
5892
|
+
{
|
|
5893
|
+
uriTemplate: "lifeos://client/{clientIdOrName}/workspace",
|
|
5894
|
+
name: "client_workspace",
|
|
5895
|
+
title: "Client Workspace",
|
|
5896
|
+
mimeType: "text/markdown",
|
|
5897
|
+
description: "Composite customer-success workspace for one client, including projects, open tasks, recent chats, meetings, linked people, and notes.",
|
|
5898
|
+
},
|
|
5899
|
+
{
|
|
5900
|
+
uriTemplate: "lifeos://client/{clientIdOrName}/notes",
|
|
5901
|
+
name: "client_notes",
|
|
5902
|
+
title: "Client Notes",
|
|
5903
|
+
mimeType: "text/markdown",
|
|
5904
|
+
description: "Tracked customer notes for one client, including requirements, decisions, and follow-ups.",
|
|
5905
|
+
},
|
|
5906
|
+
];
|
|
5907
|
+
function asJsonObject(value) {
|
|
5908
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
5909
|
+
? value
|
|
5910
|
+
: {};
|
|
5911
|
+
}
|
|
5912
|
+
function asJsonArray(value) {
|
|
5913
|
+
return Array.isArray(value)
|
|
5914
|
+
? value.filter((entry) => {
|
|
5915
|
+
return !!entry && typeof entry === "object" && !Array.isArray(entry);
|
|
5916
|
+
})
|
|
5917
|
+
: [];
|
|
5918
|
+
}
|
|
5919
|
+
function getString(value) {
|
|
5920
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
5921
|
+
}
|
|
5922
|
+
function getClientId(client) {
|
|
5923
|
+
return getString(client._id) || getString(client.id);
|
|
5924
|
+
}
|
|
5925
|
+
function getClientName(client) {
|
|
5926
|
+
return getString(client.name) || getString(client.title);
|
|
5927
|
+
}
|
|
5928
|
+
function slugifyName(value) {
|
|
5929
|
+
return value
|
|
5930
|
+
.toLowerCase()
|
|
5931
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
5932
|
+
.replace(/^-+|-+$/g, "")
|
|
5933
|
+
.slice(0, 80);
|
|
5934
|
+
}
|
|
5935
|
+
function getItemLabel(item) {
|
|
5936
|
+
return (getString(item.title) ||
|
|
5937
|
+
getString(item.name) ||
|
|
5938
|
+
getString(item.identifier) ||
|
|
5939
|
+
getString(item.host) ||
|
|
5940
|
+
getString(item.status) ||
|
|
5941
|
+
"Untitled");
|
|
5942
|
+
}
|
|
5943
|
+
function getItemMeta(item, keys) {
|
|
5944
|
+
const parts = keys
|
|
5945
|
+
.map((key) => getString(item[key]))
|
|
5946
|
+
.filter((value) => !!value);
|
|
5947
|
+
return parts.length > 0 ? parts.join(" | ") : undefined;
|
|
5948
|
+
}
|
|
5949
|
+
function formatListSection(heading, items, options) {
|
|
5950
|
+
const limit = options?.limit ?? 8;
|
|
5951
|
+
if (items.length === 0) {
|
|
5952
|
+
return `## ${heading}\n${options?.emptyText ?? "None"}\n`;
|
|
5953
|
+
}
|
|
5954
|
+
const rendered = items.slice(0, limit).map((item) => {
|
|
5955
|
+
const label = getItemLabel(item);
|
|
5956
|
+
const meta = options?.metaKeys ? getItemMeta(item, options.metaKeys) : undefined;
|
|
5957
|
+
return meta ? `- ${label} (${meta})` : `- ${label}`;
|
|
5958
|
+
});
|
|
5959
|
+
if (items.length > limit) {
|
|
5960
|
+
rendered.push(`- ... ${items.length - limit} more`);
|
|
5961
|
+
}
|
|
5962
|
+
return `## ${heading}\n${rendered.join("\n")}\n`;
|
|
5963
|
+
}
|
|
5964
|
+
function formatCustomerSuccessGuide() {
|
|
5965
|
+
return [
|
|
5966
|
+
"# Customer Success Resource Guide",
|
|
5967
|
+
"",
|
|
5968
|
+
"Start with the client workspace resource. It pulls the same composite context as `get_client_success_workspace` and is the fastest way to load account state.",
|
|
5969
|
+
"",
|
|
5970
|
+
"Workflow:",
|
|
5971
|
+
"- Open `lifeos://client/{clientIdOrName}/workspace` first.",
|
|
5972
|
+
"- Open `lifeos://client/{clientIdOrName}/notes` to review tracked requirements and decisions.",
|
|
5973
|
+
"- Use raw tools only when the resource snapshot is not enough: `get_beeper_thread_messages`, `get_fathom_meeting`, `get_fathom_transcript`, `get_granola_meeting`, `get_granola_transcript`.",
|
|
5974
|
+
"- Save account memory with `create_client_note` or `update_client_note`.",
|
|
5975
|
+
"- Track execution work with `create_issue` or `update_issue`.",
|
|
5976
|
+
"",
|
|
5977
|
+
"Rules:",
|
|
5978
|
+
"- Notes are for customer asks, decisions, constraints, and follow-ups.",
|
|
5979
|
+
"- Issues are for internal execution work.",
|
|
5980
|
+
"- Prefer updating existing artifacts over creating duplicates.",
|
|
5981
|
+
].join("\n");
|
|
5982
|
+
}
|
|
5983
|
+
async function getClientsList() {
|
|
5984
|
+
const response = asJsonObject(await callConvexTool("get_clients", {}));
|
|
5985
|
+
const clients = asJsonArray(response.clients);
|
|
5986
|
+
return clients
|
|
5987
|
+
.map((client) => {
|
|
5988
|
+
const id = getClientId(client);
|
|
5989
|
+
const name = getClientName(client);
|
|
5990
|
+
if (!id || !name) {
|
|
5991
|
+
return null;
|
|
5992
|
+
}
|
|
5993
|
+
return { id, name, raw: client };
|
|
5994
|
+
})
|
|
5995
|
+
.filter((client) => client !== null);
|
|
5996
|
+
}
|
|
5997
|
+
async function resolveClient(clientIdOrName) {
|
|
5998
|
+
const clients = await getClientsList();
|
|
5999
|
+
const lookup = clientIdOrName.trim().toLowerCase();
|
|
6000
|
+
const exactMatch = clients.find((client) => {
|
|
6001
|
+
return (client.id.toLowerCase() === lookup ||
|
|
6002
|
+
client.name.toLowerCase() === lookup ||
|
|
6003
|
+
slugifyName(client.name) === lookup);
|
|
6004
|
+
});
|
|
6005
|
+
if (exactMatch) {
|
|
6006
|
+
return exactMatch;
|
|
6007
|
+
}
|
|
6008
|
+
const partialMatch = clients.find((client) => client.name.toLowerCase().includes(lookup));
|
|
6009
|
+
if (partialMatch) {
|
|
6010
|
+
return partialMatch;
|
|
6011
|
+
}
|
|
6012
|
+
throw new Error(`Unknown client resource target: ${clientIdOrName}`);
|
|
6013
|
+
}
|
|
6014
|
+
function buildClientResourceUri(clientIdOrName, kind) {
|
|
6015
|
+
return `lifeos://client/${encodeURIComponent(clientIdOrName)}/${kind}`;
|
|
6016
|
+
}
|
|
6017
|
+
async function listCustomerSuccessResources() {
|
|
6018
|
+
const clients = await getClientsList();
|
|
6019
|
+
const clientResources = clients.flatMap((client) => [
|
|
6020
|
+
{
|
|
6021
|
+
uri: buildClientResourceUri(client.id, "workspace"),
|
|
6022
|
+
name: `client_workspace_${slugifyName(client.name) || client.id}`,
|
|
6023
|
+
title: `${client.name} Workspace`,
|
|
6024
|
+
mimeType: "text/markdown",
|
|
6025
|
+
description: "Composite customer-success workspace with account context, activity, and open work.",
|
|
6026
|
+
},
|
|
6027
|
+
{
|
|
6028
|
+
uri: buildClientResourceUri(client.id, "notes"),
|
|
6029
|
+
name: `client_notes_${slugifyName(client.name) || client.id}`,
|
|
6030
|
+
title: `${client.name} Notes`,
|
|
6031
|
+
mimeType: "text/markdown",
|
|
6032
|
+
description: "Tracked customer notes for requirements, decisions, and follow-ups.",
|
|
6033
|
+
},
|
|
6034
|
+
]);
|
|
6035
|
+
return [...STATIC_RESOURCES, ...clientResources];
|
|
6036
|
+
}
|
|
6037
|
+
function parseClientResourceUri(uri) {
|
|
6038
|
+
const parsed = new URL(uri);
|
|
6039
|
+
if (parsed.protocol !== "lifeos:") {
|
|
6040
|
+
return null;
|
|
6041
|
+
}
|
|
6042
|
+
if (parsed.hostname !== "client") {
|
|
6043
|
+
return null;
|
|
6044
|
+
}
|
|
6045
|
+
const parts = parsed.pathname
|
|
6046
|
+
.split("/")
|
|
6047
|
+
.filter(Boolean)
|
|
6048
|
+
.map((part) => decodeURIComponent(part));
|
|
6049
|
+
if (parts.length !== 2) {
|
|
6050
|
+
return null;
|
|
6051
|
+
}
|
|
6052
|
+
const [clientIdOrName, kind] = parts;
|
|
6053
|
+
if (kind !== "workspace" && kind !== "notes") {
|
|
6054
|
+
return null;
|
|
6055
|
+
}
|
|
6056
|
+
return { clientIdOrName, kind };
|
|
6057
|
+
}
|
|
6058
|
+
function formatWorkspaceResource(client, workspace) {
|
|
6059
|
+
const projects = asJsonArray(workspace.projects);
|
|
6060
|
+
const openTasks = asJsonArray(workspace.openTasks);
|
|
6061
|
+
const recentThreads = asJsonArray(workspace.recentThreads);
|
|
6062
|
+
const recentMeetings = asJsonArray(workspace.recentMeetings);
|
|
6063
|
+
const linkedPeople = asJsonArray(workspace.linkedPeople);
|
|
6064
|
+
const notes = asJsonArray(workspace.notes);
|
|
6065
|
+
return [
|
|
6066
|
+
`# ${client.name} Customer Success Workspace`,
|
|
6067
|
+
"",
|
|
6068
|
+
`Client ID: \`${client.id}\``,
|
|
6069
|
+
"",
|
|
6070
|
+
"## Summary",
|
|
6071
|
+
`- Projects: ${projects.length}`,
|
|
6072
|
+
`- Open tasks: ${openTasks.length}`,
|
|
6073
|
+
`- Recent threads: ${recentThreads.length}`,
|
|
6074
|
+
`- Recent meetings: ${recentMeetings.length}`,
|
|
6075
|
+
`- Linked people: ${linkedPeople.length}`,
|
|
6076
|
+
`- Notes: ${notes.length}`,
|
|
6077
|
+
"",
|
|
6078
|
+
formatListSection("Projects", projects, {
|
|
6079
|
+
emptyText: "No linked projects.",
|
|
6080
|
+
metaKeys: ["status", "health", "priority"],
|
|
6081
|
+
}).trimEnd(),
|
|
6082
|
+
"",
|
|
6083
|
+
formatListSection("Open Tasks", openTasks, {
|
|
6084
|
+
emptyText: "No open tasks.",
|
|
6085
|
+
metaKeys: ["identifier", "status", "priority", "dueDate"],
|
|
6086
|
+
}).trimEnd(),
|
|
6087
|
+
"",
|
|
6088
|
+
formatListSection("Recent Threads", recentThreads, {
|
|
6089
|
+
emptyText: "No linked chat threads.",
|
|
6090
|
+
metaKeys: ["platform", "lastMessageAt", "threadId"],
|
|
6091
|
+
}).trimEnd(),
|
|
6092
|
+
"",
|
|
6093
|
+
formatListSection("Recent Meetings", recentMeetings, {
|
|
6094
|
+
emptyText: "No linked meetings.",
|
|
6095
|
+
metaKeys: ["source", "startTime", "meetingDate"],
|
|
6096
|
+
}).trimEnd(),
|
|
6097
|
+
"",
|
|
6098
|
+
formatListSection("Linked People", linkedPeople, {
|
|
6099
|
+
emptyText: "No linked people.",
|
|
6100
|
+
metaKeys: ["relationshipType", "email", "phone"],
|
|
6101
|
+
}).trimEnd(),
|
|
6102
|
+
"",
|
|
6103
|
+
formatListSection("Tracked Notes", notes, {
|
|
6104
|
+
emptyText: "No client notes yet.",
|
|
6105
|
+
metaKeys: ["updatedAt", "createdAt"],
|
|
6106
|
+
}).trimEnd(),
|
|
6107
|
+
].join("\n");
|
|
6108
|
+
}
|
|
6109
|
+
function formatNotesResource(client, notesResponse) {
|
|
6110
|
+
const notes = asJsonArray(notesResponse.notes);
|
|
6111
|
+
if (notes.length === 0) {
|
|
6112
|
+
return [
|
|
6113
|
+
`# ${client.name} Client Notes`,
|
|
6114
|
+
"",
|
|
6115
|
+
"No client notes are currently tracked for this account.",
|
|
6116
|
+
"",
|
|
6117
|
+
"Use `create_client_note` to capture requirements, decisions, and follow-ups.",
|
|
6118
|
+
].join("\n");
|
|
6119
|
+
}
|
|
6120
|
+
const renderedNotes = notes.slice(0, 20).map((note, index) => {
|
|
6121
|
+
const title = getString(note.title) || `Untitled note ${index + 1}`;
|
|
6122
|
+
const content = getString(note.content) || "";
|
|
6123
|
+
const updatedAt = getString(note.updatedAt) || getString(note.createdAt);
|
|
6124
|
+
const meta = updatedAt ? `Updated: ${updatedAt}` : undefined;
|
|
6125
|
+
return [
|
|
6126
|
+
`## ${title}`,
|
|
6127
|
+
meta ?? "",
|
|
6128
|
+
content,
|
|
6129
|
+
]
|
|
6130
|
+
.filter(Boolean)
|
|
6131
|
+
.join("\n");
|
|
6132
|
+
});
|
|
6133
|
+
return [
|
|
6134
|
+
`# ${client.name} Client Notes`,
|
|
6135
|
+
"",
|
|
6136
|
+
`Total notes: ${notes.length}`,
|
|
6137
|
+
"",
|
|
6138
|
+
...renderedNotes,
|
|
6139
|
+
].join("\n\n");
|
|
6140
|
+
}
|
|
6141
|
+
async function readCustomerSuccessResource(uri) {
|
|
6142
|
+
if (uri === "lifeos://customer-success/guide") {
|
|
6143
|
+
return {
|
|
6144
|
+
text: formatCustomerSuccessGuide(),
|
|
6145
|
+
mimeType: "text/markdown",
|
|
6146
|
+
};
|
|
6147
|
+
}
|
|
6148
|
+
const parsed = parseClientResourceUri(uri);
|
|
6149
|
+
if (!parsed) {
|
|
6150
|
+
throw new Error(`Unknown resource URI: ${uri}`);
|
|
6151
|
+
}
|
|
6152
|
+
const client = await resolveClient(parsed.clientIdOrName);
|
|
6153
|
+
if (parsed.kind === "workspace") {
|
|
6154
|
+
const workspace = asJsonObject(await callConvexTool("get_client_success_workspace", {
|
|
6155
|
+
clientIdOrName: client.id,
|
|
6156
|
+
}));
|
|
6157
|
+
return {
|
|
6158
|
+
text: formatWorkspaceResource(client, workspace),
|
|
6159
|
+
mimeType: "text/markdown",
|
|
6160
|
+
};
|
|
6161
|
+
}
|
|
6162
|
+
const notesResponse = asJsonObject(await callConvexTool("get_client_notes", {
|
|
6163
|
+
clientId: client.id,
|
|
6164
|
+
limit: 50,
|
|
6165
|
+
}));
|
|
6166
|
+
return {
|
|
6167
|
+
text: formatNotesResource(client, notesResponse),
|
|
6168
|
+
mimeType: "text/markdown",
|
|
6169
|
+
};
|
|
6170
|
+
}
|
|
5578
6171
|
// Create the MCP server
|
|
5579
6172
|
const server = new Server({
|
|
5580
6173
|
name: "lifeos-pm",
|
|
5581
6174
|
version: VERSION,
|
|
5582
6175
|
}, {
|
|
5583
6176
|
capabilities: {
|
|
6177
|
+
resources: {},
|
|
5584
6178
|
tools: {},
|
|
5585
6179
|
prompts: {},
|
|
5586
6180
|
},
|
|
5587
6181
|
});
|
|
6182
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
6183
|
+
return { resources: await listCustomerSuccessResources() };
|
|
6184
|
+
});
|
|
6185
|
+
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
6186
|
+
return { resourceTemplates: RESOURCE_TEMPLATES };
|
|
6187
|
+
});
|
|
6188
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
6189
|
+
const { uri } = request.params;
|
|
6190
|
+
const resource = await readCustomerSuccessResource(uri);
|
|
6191
|
+
return {
|
|
6192
|
+
contents: [
|
|
6193
|
+
{
|
|
6194
|
+
uri,
|
|
6195
|
+
mimeType: resource.mimeType,
|
|
6196
|
+
text: resource.text,
|
|
6197
|
+
},
|
|
6198
|
+
],
|
|
6199
|
+
};
|
|
6200
|
+
});
|
|
5588
6201
|
// Handle list tools request
|
|
5589
6202
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
5590
6203
|
return { tools: TOOLS };
|
package/package.json
CHANGED