@starascendin/lifeos-mcp 0.7.52 → 0.7.54

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.
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.7.52";
2
- export declare const BUILD_TIME = "2026-03-09T17:02:07.718Z";
1
+ export declare const VERSION = "0.7.54";
2
+ export declare const BUILD_TIME = "2026-03-16T18:16:47.653Z";
@@ -1,3 +1,3 @@
1
1
  // AUTO-GENERATED — do not edit. Regenerated on every build.
2
- export const VERSION = "0.7.52";
3
- export const BUILD_TIME = "2026-03-09T17:02:07.718Z";
2
+ export const VERSION = "0.7.54";
3
+ export const BUILD_TIME = "2026-03-16T18:16:47.653Z";
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
@@ -5879,16 +5879,325 @@ async function callConvexJsonEndpoint(path, body) {
5879
5879
  }
5880
5880
  return await response.json();
5881
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
+ }
5882
6171
  // Create the MCP server
5883
6172
  const server = new Server({
5884
6173
  name: "lifeos-pm",
5885
6174
  version: VERSION,
5886
6175
  }, {
5887
6176
  capabilities: {
6177
+ resources: {},
5888
6178
  tools: {},
5889
6179
  prompts: {},
5890
6180
  },
5891
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
+ });
5892
6201
  // Handle list tools request
5893
6202
  server.setRequestHandler(ListToolsRequestSchema, async () => {
5894
6203
  return { tools: TOOLS };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starascendin/lifeos-mcp",
3
- "version": "0.7.52",
3
+ "version": "0.7.54",
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",