@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 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:
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.7.51";
2
- export declare const BUILD_TIME = "2026-03-08T04:47:42.547Z";
1
+ export declare const VERSION = "0.7.53";
2
+ export declare const BUILD_TIME = "2026-03-09T18:11:42.668Z";
@@ -1,3 +1,3 @@
1
1
  // AUTO-GENERATED — do not edit. Regenerated on every build.
2
- export const VERSION = "0.7.51";
3
- export const BUILD_TIME = "2026-03-08T04:47:42.547Z";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starascendin/lifeos-mcp",
3
- "version": "0.7.51",
3
+ "version": "0.7.53",
4
4
  "description": "MCP server for LifeOS Project Management - manage projects, tasks, notes, and contacts via AI assistants",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",