youmd 0.5.0 → 0.6.1

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.
Files changed (165) hide show
  1. package/dist/__tests__/api.test.d.ts +2 -0
  2. package/dist/__tests__/api.test.d.ts.map +1 -0
  3. package/dist/__tests__/api.test.js +84 -0
  4. package/dist/__tests__/api.test.js.map +1 -0
  5. package/dist/__tests__/compiler.test.d.ts +2 -0
  6. package/dist/__tests__/compiler.test.d.ts.map +1 -0
  7. package/dist/__tests__/compiler.test.js +127 -0
  8. package/dist/__tests__/compiler.test.js.map +1 -0
  9. package/dist/__tests__/config.test.d.ts +2 -0
  10. package/dist/__tests__/config.test.d.ts.map +1 -0
  11. package/dist/__tests__/config.test.js +79 -0
  12. package/dist/__tests__/config.test.js.map +1 -0
  13. package/dist/__tests__/decompile.test.d.ts +2 -0
  14. package/dist/__tests__/decompile.test.d.ts.map +1 -0
  15. package/dist/__tests__/decompile.test.js +102 -0
  16. package/dist/__tests__/decompile.test.js.map +1 -0
  17. package/dist/__tests__/hash.test.d.ts +2 -0
  18. package/dist/__tests__/hash.test.d.ts.map +1 -0
  19. package/dist/__tests__/hash.test.js +44 -0
  20. package/dist/__tests__/hash.test.js.map +1 -0
  21. package/dist/__tests__/integration.test.d.ts +2 -0
  22. package/dist/__tests__/integration.test.d.ts.map +1 -0
  23. package/dist/__tests__/integration.test.js +277 -0
  24. package/dist/__tests__/integration.test.js.map +1 -0
  25. package/dist/__tests__/skill-renderer.test.d.ts +2 -0
  26. package/dist/__tests__/skill-renderer.test.d.ts.map +1 -0
  27. package/dist/__tests__/skill-renderer.test.js +68 -0
  28. package/dist/__tests__/skill-renderer.test.js.map +1 -0
  29. package/dist/commands/add.d.ts.map +1 -1
  30. package/dist/commands/add.js +6 -3
  31. package/dist/commands/add.js.map +1 -1
  32. package/dist/commands/agents.d.ts +2 -0
  33. package/dist/commands/agents.d.ts.map +1 -0
  34. package/dist/commands/agents.js +93 -0
  35. package/dist/commands/agents.js.map +1 -0
  36. package/dist/commands/build.d.ts.map +1 -1
  37. package/dist/commands/build.js +73 -20
  38. package/dist/commands/build.js.map +1 -1
  39. package/dist/commands/chat.d.ts.map +1 -1
  40. package/dist/commands/chat.js +63 -4
  41. package/dist/commands/chat.js.map +1 -1
  42. package/dist/commands/diff.d.ts +1 -1
  43. package/dist/commands/diff.d.ts.map +1 -1
  44. package/dist/commands/diff.js +402 -16
  45. package/dist/commands/diff.js.map +1 -1
  46. package/dist/commands/export.js +1 -1
  47. package/dist/commands/export.js.map +1 -1
  48. package/dist/commands/init.d.ts +1 -0
  49. package/dist/commands/init.d.ts.map +1 -1
  50. package/dist/commands/init.js +131 -23
  51. package/dist/commands/init.js.map +1 -1
  52. package/dist/commands/link.d.ts +1 -0
  53. package/dist/commands/link.d.ts.map +1 -1
  54. package/dist/commands/link.js +76 -12
  55. package/dist/commands/link.js.map +1 -1
  56. package/dist/commands/login.d.ts.map +1 -1
  57. package/dist/commands/login.js +76 -59
  58. package/dist/commands/login.js.map +1 -1
  59. package/dist/commands/logs.d.ts +7 -0
  60. package/dist/commands/logs.d.ts.map +1 -0
  61. package/dist/commands/logs.js +115 -0
  62. package/dist/commands/logs.js.map +1 -0
  63. package/dist/commands/mcp.d.ts +6 -0
  64. package/dist/commands/mcp.d.ts.map +1 -0
  65. package/dist/commands/mcp.js +258 -0
  66. package/dist/commands/mcp.js.map +1 -0
  67. package/dist/commands/preview.d.ts.map +1 -1
  68. package/dist/commands/preview.js +191 -7
  69. package/dist/commands/preview.js.map +1 -1
  70. package/dist/commands/private.d.ts.map +1 -1
  71. package/dist/commands/private.js +248 -0
  72. package/dist/commands/private.js.map +1 -1
  73. package/dist/commands/prompts.d.ts +12 -0
  74. package/dist/commands/prompts.d.ts.map +1 -0
  75. package/dist/commands/prompts.js +245 -0
  76. package/dist/commands/prompts.js.map +1 -0
  77. package/dist/commands/publish.d.ts.map +1 -1
  78. package/dist/commands/publish.js +3 -2
  79. package/dist/commands/publish.js.map +1 -1
  80. package/dist/commands/pull.d.ts.map +1 -1
  81. package/dist/commands/pull.js +42 -133
  82. package/dist/commands/pull.js.map +1 -1
  83. package/dist/commands/push.d.ts.map +1 -1
  84. package/dist/commands/push.js +146 -38
  85. package/dist/commands/push.js.map +1 -1
  86. package/dist/commands/register.d.ts.map +1 -1
  87. package/dist/commands/register.js +39 -83
  88. package/dist/commands/register.js.map +1 -1
  89. package/dist/commands/skill.d.ts.map +1 -1
  90. package/dist/commands/skill.js +95 -19
  91. package/dist/commands/skill.js.map +1 -1
  92. package/dist/commands/status.d.ts.map +1 -1
  93. package/dist/commands/status.js +132 -96
  94. package/dist/commands/status.js.map +1 -1
  95. package/dist/commands/whoami.d.ts.map +1 -1
  96. package/dist/commands/whoami.js +53 -29
  97. package/dist/commands/whoami.js.map +1 -1
  98. package/dist/index.js +177 -5
  99. package/dist/index.js.map +1 -1
  100. package/dist/lib/api.d.ts +93 -7
  101. package/dist/lib/api.d.ts.map +1 -1
  102. package/dist/lib/api.js +143 -38
  103. package/dist/lib/api.js.map +1 -1
  104. package/dist/lib/compiler.d.ts +16 -33
  105. package/dist/lib/compiler.d.ts.map +1 -1
  106. package/dist/lib/compiler.js +499 -84
  107. package/dist/lib/compiler.js.map +1 -1
  108. package/dist/lib/config.d.ts +3 -0
  109. package/dist/lib/config.d.ts.map +1 -1
  110. package/dist/lib/config.js +10 -0
  111. package/dist/lib/config.js.map +1 -1
  112. package/dist/lib/decompile.d.ts +21 -0
  113. package/dist/lib/decompile.d.ts.map +1 -0
  114. package/dist/lib/decompile.js +304 -0
  115. package/dist/lib/decompile.js.map +1 -0
  116. package/dist/lib/onboarding.d.ts +4 -4
  117. package/dist/lib/onboarding.d.ts.map +1 -1
  118. package/dist/lib/onboarding.js +116 -25
  119. package/dist/lib/onboarding.js.map +1 -1
  120. package/dist/lib/skill-catalog.d.ts.map +1 -1
  121. package/dist/lib/skill-catalog.js +57 -8
  122. package/dist/lib/skill-catalog.js.map +1 -1
  123. package/dist/lib/skill-renderer.d.ts.map +1 -1
  124. package/dist/lib/skill-renderer.js +18 -0
  125. package/dist/lib/skill-renderer.js.map +1 -1
  126. package/dist/lib/skills.d.ts +8 -3
  127. package/dist/lib/skills.d.ts.map +1 -1
  128. package/dist/lib/skills.js +281 -82
  129. package/dist/lib/skills.js.map +1 -1
  130. package/dist/lib/vault.d.ts +40 -0
  131. package/dist/lib/vault.d.ts.map +1 -0
  132. package/dist/lib/vault.js +187 -0
  133. package/dist/lib/vault.js.map +1 -0
  134. package/dist/mcp/server.d.ts +21 -0
  135. package/dist/mcp/server.d.ts.map +1 -0
  136. package/dist/mcp/server.js +1283 -0
  137. package/dist/mcp/server.js.map +1 -0
  138. package/examples/houston/directives/agent.md +13 -0
  139. package/examples/houston/preferences/agent.md +14 -0
  140. package/examples/houston/profile/about.md +15 -0
  141. package/examples/houston/profile/links.md +10 -0
  142. package/examples/houston/profile/projects.md +37 -0
  143. package/examples/houston/profile/values.md +9 -0
  144. package/examples/houston/voice/voice.md +13 -0
  145. package/examples/jordan/directives/agent.md +13 -0
  146. package/examples/jordan/preferences/agent.md +16 -0
  147. package/examples/jordan/profile/about.md +15 -0
  148. package/examples/jordan/profile/links.md +10 -0
  149. package/examples/jordan/profile/projects.md +29 -0
  150. package/examples/jordan/profile/values.md +9 -0
  151. package/examples/jordan/voice/voice.md +12 -0
  152. package/examples/priya/directives/agent.md +13 -0
  153. package/examples/priya/preferences/agent.md +15 -0
  154. package/examples/priya/profile/about.md +15 -0
  155. package/examples/priya/profile/links.md +9 -0
  156. package/examples/priya/profile/projects.md +28 -0
  157. package/examples/priya/profile/values.md +9 -0
  158. package/examples/priya/voice/voice.md +12 -0
  159. package/package.json +11 -5
  160. package/skills/claude-md-generator.md +25 -0
  161. package/skills/meta-improve.md +27 -0
  162. package/skills/proactive-context-fill.md +52 -0
  163. package/skills/project-context-init.md +26 -0
  164. package/skills/voice-sync.md +48 -0
  165. package/skills/you-logs.md +71 -0
@@ -0,0 +1,1283 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * You.md MCP Server — Identity context protocol for the agent internet.
5
+ *
6
+ * An MCP where the context is you. Every agent that connects gets structured,
7
+ * portable identity context: who you are, how you work, what you sound like.
8
+ *
9
+ * Resources: identity, profile sections, preferences, voice, directives,
10
+ * memories, projects, skills
11
+ * Tools: add_memory, update_section, search_memories, use_skill,
12
+ * compile_bundle, push_bundle, get_project_context,
13
+ * add_source, create_context_link, list_projects, get_remote_status
14
+ *
15
+ * Transport: stdio (for Claude Code, Cursor, any MCP client)
16
+ *
17
+ * Usage:
18
+ * youmd mcp # start MCP server
19
+ * youmd mcp --json # output config JSON for claude_desktop_config
20
+ */
21
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
22
+ if (k2 === undefined) k2 = k;
23
+ var desc = Object.getOwnPropertyDescriptor(m, k);
24
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
25
+ desc = { enumerable: true, get: function() { return m[k]; } };
26
+ }
27
+ Object.defineProperty(o, k2, desc);
28
+ }) : (function(o, m, k, k2) {
29
+ if (k2 === undefined) k2 = k;
30
+ o[k2] = m[k];
31
+ }));
32
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
33
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
34
+ }) : function(o, v) {
35
+ o["default"] = v;
36
+ });
37
+ var __importStar = (this && this.__importStar) || (function () {
38
+ var ownKeys = function(o) {
39
+ ownKeys = Object.getOwnPropertyNames || function (o) {
40
+ var ar = [];
41
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
42
+ return ar;
43
+ };
44
+ return ownKeys(o);
45
+ };
46
+ return function (mod) {
47
+ if (mod && mod.__esModule) return mod;
48
+ var result = {};
49
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
50
+ __setModuleDefault(result, mod);
51
+ return result;
52
+ };
53
+ })();
54
+ Object.defineProperty(exports, "__esModule", { value: true });
55
+ exports.startMcpServer = startMcpServer;
56
+ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
57
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
58
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
59
+ const fs = __importStar(require("fs"));
60
+ const path = __importStar(require("path"));
61
+ const config_1 = require("../lib/config");
62
+ const project_1 = require("../lib/project");
63
+ function getCurrentProject() {
64
+ const root = (0, project_1.findProjectsRoot)();
65
+ if (!root)
66
+ return null;
67
+ const name = (0, project_1.detectCurrentProject)(root);
68
+ if (!name)
69
+ return null;
70
+ return { name, dir: (0, project_1.getProjectDir)(root, name) };
71
+ }
72
+ // ---------------------------------------------------------------------------
73
+ // Helpers
74
+ // ---------------------------------------------------------------------------
75
+ function readFileOr(filePath, fallback) {
76
+ try {
77
+ return fs.readFileSync(filePath, "utf-8");
78
+ }
79
+ catch {
80
+ return fallback;
81
+ }
82
+ }
83
+ function readJsonOr(filePath, fallback) {
84
+ try {
85
+ return JSON.parse(fs.readFileSync(filePath, "utf-8"));
86
+ }
87
+ catch {
88
+ return fallback;
89
+ }
90
+ }
91
+ function getBundleDir() {
92
+ return (0, config_1.getLocalBundleDir)();
93
+ }
94
+ function getYouJson() {
95
+ const bundleDir = getBundleDir();
96
+ const youJsonPath = path.join(bundleDir, "you.json");
97
+ return readJsonOr(youJsonPath, {});
98
+ }
99
+ function getYouMd() {
100
+ const bundleDir = getBundleDir();
101
+ return readFileOr(path.join(bundleDir, "you.md"), "");
102
+ }
103
+ function getSectionFiles() {
104
+ const bundleDir = getBundleDir();
105
+ const sections = [];
106
+ const dirs = ["profile", "preferences", "voice", "directives"];
107
+ for (const dir of dirs) {
108
+ const dirPath = path.join(bundleDir, dir);
109
+ if (!fs.existsSync(dirPath))
110
+ continue;
111
+ try {
112
+ const files = fs.readdirSync(dirPath).filter((f) => f.endsWith(".md"));
113
+ for (const file of files) {
114
+ const slug = file.replace(/\.md$/, "");
115
+ const content = readFileOr(path.join(dirPath, file), "");
116
+ sections.push({ slug, dir, content });
117
+ }
118
+ }
119
+ catch {
120
+ // skip
121
+ }
122
+ }
123
+ return sections;
124
+ }
125
+ function getInstalledSkills() {
126
+ const skillsDir = path.join((0, config_1.getGlobalConfigDir)(), "skills");
127
+ if (!fs.existsSync(skillsDir))
128
+ return [];
129
+ const skills = [];
130
+ try {
131
+ const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
132
+ for (const entry of entries) {
133
+ if (!entry.isDirectory())
134
+ continue;
135
+ const skillDir = path.join(skillsDir, entry.name);
136
+ const raw = readFileOr(path.join(skillDir, "SKILL.md"), "");
137
+ const rendered = readFileOr(path.join(skillDir, "RENDERED.md"), raw);
138
+ if (raw || rendered) {
139
+ skills.push({ name: entry.name, rendered, raw });
140
+ }
141
+ }
142
+ }
143
+ catch {
144
+ // skip
145
+ }
146
+ return skills;
147
+ }
148
+ async function fetchMemories(category, limit) {
149
+ if (!(0, config_1.isAuthenticated)())
150
+ return [];
151
+ const config = (0, config_1.readGlobalConfig)();
152
+ const siteUrl = (0, config_1.getConvexSiteUrl)();
153
+ const params = new URLSearchParams();
154
+ if (category)
155
+ params.set("category", category);
156
+ if (limit)
157
+ params.set("limit", String(limit));
158
+ try {
159
+ const res = await fetch(`${siteUrl}/api/v1/me/memories?${params}`, {
160
+ headers: { Authorization: `Bearer ${config.token}` },
161
+ signal: AbortSignal.timeout(10000),
162
+ });
163
+ if (!res.ok)
164
+ return [];
165
+ const data = await res.json();
166
+ return (data.memories || data) || [];
167
+ }
168
+ catch {
169
+ return [];
170
+ }
171
+ }
172
+ async function apiRequest(path, opts) {
173
+ const config = (0, config_1.readGlobalConfig)();
174
+ const siteUrl = (0, config_1.getConvexSiteUrl)();
175
+ const res = await fetch(`${siteUrl}${path}`, {
176
+ method: opts?.method || "GET",
177
+ headers: {
178
+ Authorization: `Bearer ${config.token}`,
179
+ "Content-Type": "application/json",
180
+ },
181
+ body: opts?.body ? JSON.stringify(opts.body) : undefined,
182
+ signal: AbortSignal.timeout(15000),
183
+ });
184
+ return res.json();
185
+ }
186
+ /**
187
+ * Fire-and-forget activity logger. Logs every MCP tool call so the user can
188
+ * see which agents are using their you.md identity via `youmd logs` or the
189
+ * web shell. Non-fatal — failures are swallowed so logging can't break tool
190
+ * calls.
191
+ */
192
+ async function logMcpActivity(action, resource, details) {
193
+ if (!(0, config_1.isAuthenticated)())
194
+ return;
195
+ try {
196
+ const config = (0, config_1.readGlobalConfig)();
197
+ await fetch(`${(0, config_1.getConvexSiteUrl)()}/api/v1/me/activity/log`, {
198
+ method: "POST",
199
+ headers: {
200
+ Authorization: `Bearer ${config.token}`,
201
+ "Content-Type": "application/json",
202
+ "User-Agent": "youmd-mcp/0.6.1",
203
+ },
204
+ body: JSON.stringify({
205
+ agentName: process.env.YOUMD_AGENT_NAME || "Claude Code",
206
+ agentSource: "mcp",
207
+ action,
208
+ resource,
209
+ status: "success",
210
+ details,
211
+ }),
212
+ signal: AbortSignal.timeout(5000),
213
+ });
214
+ }
215
+ catch {
216
+ // non-fatal — logging shouldn't break tool calls
217
+ }
218
+ }
219
+ // Valid memory categories enforced by add_memory
220
+ const MEMORY_CATEGORIES = [
221
+ "fact",
222
+ "preference",
223
+ "decision",
224
+ "project",
225
+ "goal",
226
+ "insight",
227
+ "context",
228
+ "relationship",
229
+ ];
230
+ /**
231
+ * Build a compact (~500 char) identity summary. This is what the whoami tool
232
+ * and the `compact` format of get_identity return. Designed to be the very
233
+ * first call an agent makes so it can orient on the user in <1 tool round.
234
+ */
235
+ function buildWhoamiSummary() {
236
+ const youJson = getYouJson();
237
+ const identity = youJson.identity || {};
238
+ const preferences = youJson.preferences || {};
239
+ const agentPrefs = preferences.agent || {};
240
+ const directives = youJson.agent_directives || {};
241
+ const projects = Array.isArray(youJson.projects) ? youJson.projects : [];
242
+ const name = identity.name || youJson.username || "(unknown)";
243
+ const role = identity.tagline || identity.bio?.short || "";
244
+ const stack = directives.default_stack || "";
245
+ const tone = agentPrefs.tone || "";
246
+ const avoidList = Array.isArray(agentPrefs.avoid) ? agentPrefs.avoid : [];
247
+ const avoid = avoidList.join(", ");
248
+ const topProjects = projects
249
+ .slice(0, 3)
250
+ .map((p) => (typeof p === "string" ? p : p?.name || ""))
251
+ .filter(Boolean)
252
+ .join(", ");
253
+ const goal = directives.current_goal || "";
254
+ const lines = [];
255
+ lines.push(`Name: ${name}`);
256
+ if (role)
257
+ lines.push(`Role: ${role}`);
258
+ if (stack)
259
+ lines.push(`Stack: ${stack}`);
260
+ if (tone)
261
+ lines.push(`Tone: ${tone}`);
262
+ if (avoid)
263
+ lines.push(`Avoid: ${avoid}`);
264
+ if (topProjects)
265
+ lines.push(`Top projects: ${topProjects}`);
266
+ if (goal)
267
+ lines.push(`Goal: ${goal}`);
268
+ let summary = lines.join("\n");
269
+ // Hard cap at ~500 chars to keep it cheap for agents
270
+ if (summary.length > 500) {
271
+ summary = summary.slice(0, 497) + "...";
272
+ }
273
+ return summary;
274
+ }
275
+ /**
276
+ * Build a human-readable markdown version of the identity bundle.
277
+ * Used by get_identity format=markdown.
278
+ */
279
+ function buildIdentityMarkdown() {
280
+ const md = getYouMd();
281
+ if (md)
282
+ return md;
283
+ // Fallback: synthesize from youJson if you.md is missing
284
+ const youJson = getYouJson();
285
+ const identity = youJson.identity || {};
286
+ const parts = [];
287
+ parts.push(`# ${identity.name || youJson.username || "Identity"}`);
288
+ if (identity.tagline)
289
+ parts.push(identity.tagline);
290
+ if (identity.bio?.long || identity.bio?.medium || identity.bio?.short) {
291
+ parts.push(`\n## About\n\n${identity.bio.long || identity.bio.medium || identity.bio.short}`);
292
+ }
293
+ return parts.join("\n") + "\n";
294
+ }
295
+ /**
296
+ * Find the nearest project-context/ directory by walking up from cwd.
297
+ * Also treats a `.youmd-project` marker file as a valid project root.
298
+ * Returns the directory that contains project-context/ or the marker.
299
+ */
300
+ function findLocalProjectContextRoot(startDir = process.cwd()) {
301
+ let dir = startDir;
302
+ // Walk up at most 8 levels to avoid runaway searches
303
+ for (let i = 0; i < 8; i++) {
304
+ const contextDir = path.join(dir, "project-context");
305
+ const marker = path.join(dir, ".youmd-project");
306
+ if (fs.existsSync(contextDir) && fs.statSync(contextDir).isDirectory()) {
307
+ return dir;
308
+ }
309
+ if (fs.existsSync(marker)) {
310
+ return dir;
311
+ }
312
+ const parent = path.dirname(dir);
313
+ if (parent === dir)
314
+ break;
315
+ dir = parent;
316
+ }
317
+ return null;
318
+ }
319
+ /**
320
+ * Returns true if the current cwd has local project context available
321
+ * (either via project-context/ directory or a .youmd-project marker).
322
+ */
323
+ function hasLocalProjectContext() {
324
+ return findLocalProjectContextRoot() !== null;
325
+ }
326
+ /**
327
+ * Read a single project-context markdown file. Tries project-context/<name>.md
328
+ * in the detected project root. Returns empty string if not found.
329
+ */
330
+ function readLocalProjectContextFile(name) {
331
+ const root = findLocalProjectContextRoot();
332
+ if (!root)
333
+ return "";
334
+ const candidates = [
335
+ path.join(root, "project-context", `${name}.md`),
336
+ path.join(root, "project-context", `${name.toUpperCase()}.md`),
337
+ path.join(root, `${name}.md`),
338
+ path.join(root, `${name.toUpperCase()}.md`),
339
+ ];
340
+ for (const candidate of candidates) {
341
+ if (fs.existsSync(candidate)) {
342
+ return readFileOr(candidate, "");
343
+ }
344
+ }
345
+ return "";
346
+ }
347
+ // ---------------------------------------------------------------------------
348
+ // MCP Server
349
+ // ---------------------------------------------------------------------------
350
+ async function startMcpServer() {
351
+ const server = new index_js_1.Server({
352
+ name: "youmd",
353
+ version: "0.5.0",
354
+ }, {
355
+ capabilities: {
356
+ resources: {},
357
+ tools: {},
358
+ },
359
+ });
360
+ // ── LIST RESOURCES ─────────────────────────────────────────────────
361
+ server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
362
+ const resources = [];
363
+ // Core identity
364
+ resources.push({
365
+ uri: "youmd://identity",
366
+ name: "identity",
367
+ description: "Complete you.md identity bundle (you.json) — who you are, how you work, what you sound like",
368
+ mimeType: "application/json",
369
+ });
370
+ resources.push({
371
+ uri: "youmd://identity/markdown",
372
+ name: "identity-markdown",
373
+ description: "Human-readable you.md identity (you.md format)",
374
+ mimeType: "text/markdown",
375
+ });
376
+ // Profile sections
377
+ const sections = getSectionFiles();
378
+ for (const section of sections) {
379
+ resources.push({
380
+ uri: `youmd://${section.dir}/${section.slug}`,
381
+ name: `${section.dir}/${section.slug}`,
382
+ description: `Identity section: ${section.dir}/${section.slug}`,
383
+ mimeType: "text/markdown",
384
+ });
385
+ }
386
+ // Memories (if authenticated)
387
+ if ((0, config_1.isAuthenticated)()) {
388
+ resources.push({
389
+ uri: "youmd://memories",
390
+ name: "memories",
391
+ description: "Active memories — facts, preferences, decisions, and context the agent has learned about you",
392
+ mimeType: "application/json",
393
+ });
394
+ // All active memories as JSON (alias to the base memories resource, but
395
+ // exposed explicitly so agents can discover a stable "all" URI).
396
+ resources.push({
397
+ uri: "youmd://memories/all",
398
+ name: "memories/all",
399
+ description: "All active memories as JSON (every category combined)",
400
+ mimeType: "application/json",
401
+ });
402
+ // Per-category memory resources. Agents can load only the slice they need
403
+ // instead of pulling the full memory firehose.
404
+ const memoryCategories = [
405
+ { slug: "preference", desc: "Preferences learned about the user (tone, format, defaults)" },
406
+ { slug: "decision", desc: "Decisions the user has made (architecture, product, stack)" },
407
+ { slug: "fact", desc: "Facts about the user (bio, stack, constraints)" },
408
+ { slug: "project", desc: "Project-related memories" },
409
+ { slug: "goal", desc: "Current goals and objectives" },
410
+ ];
411
+ for (const { slug, desc } of memoryCategories) {
412
+ resources.push({
413
+ uri: `youmd://memories/${slug}`,
414
+ name: `memories/${slug}`,
415
+ description: desc,
416
+ mimeType: "application/json",
417
+ });
418
+ }
419
+ }
420
+ // Local project context auto-loaded from cwd (project-context/*.md).
421
+ // Only surfaced when a project-context/ dir or .youmd-project marker exists.
422
+ if (hasLocalProjectContext()) {
423
+ const localFiles = [
424
+ { slug: "prd", desc: "Current project PRD (from local project-context/prd.md)" },
425
+ { slug: "todo", desc: "Current project TODO list (from local project-context/todo.md)" },
426
+ { slug: "features", desc: "Current project feature list (from local project-context/features.md)" },
427
+ { slug: "decisions", desc: "Current project decisions log (from local project-context/decisions.md)" },
428
+ { slug: "changelog", desc: "Current project changelog (from local project-context/changelog.md)" },
429
+ ];
430
+ for (const { slug, desc } of localFiles) {
431
+ resources.push({
432
+ uri: `youmd://project/current/${slug}`,
433
+ name: `project/current/${slug}`,
434
+ description: desc,
435
+ mimeType: "text/markdown",
436
+ });
437
+ }
438
+ }
439
+ // Projects
440
+ try {
441
+ const root = (0, project_1.findProjectsRoot)();
442
+ if (root) {
443
+ const projects = (0, project_1.listProjects)(root);
444
+ for (const name of projects) {
445
+ resources.push({
446
+ uri: `youmd://projects/${name}`,
447
+ name: `project/${name}`,
448
+ description: `Project context for ${name} — PRD, TODO, features, decisions, memories`,
449
+ mimeType: "application/json",
450
+ });
451
+ }
452
+ }
453
+ }
454
+ catch {
455
+ // no projects
456
+ }
457
+ // Current project (auto-detected from cwd)
458
+ try {
459
+ const current = getCurrentProject();
460
+ if (current) {
461
+ resources.push({
462
+ uri: "youmd://projects/current",
463
+ name: "project/current",
464
+ description: `Current project context (auto-detected: ${current.name})`,
465
+ mimeType: "application/json",
466
+ });
467
+ }
468
+ }
469
+ catch {
470
+ // no current project
471
+ }
472
+ // Skills
473
+ const skills = getInstalledSkills();
474
+ for (const skill of skills) {
475
+ resources.push({
476
+ uri: `youmd://skills/${skill.name}`,
477
+ name: `skill/${skill.name}`,
478
+ description: `Rendered skill: ${skill.name} — identity-aware agent instructions`,
479
+ mimeType: "text/markdown",
480
+ });
481
+ }
482
+ return { resources };
483
+ });
484
+ // ── READ RESOURCE ──────────────────────────────────────────────────
485
+ server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) => {
486
+ const uri = request.params.uri;
487
+ // youmd://identity
488
+ if (uri === "youmd://identity") {
489
+ const youJson = getYouJson();
490
+ return {
491
+ contents: [{
492
+ uri,
493
+ mimeType: "application/json",
494
+ text: JSON.stringify(youJson, null, 2),
495
+ }],
496
+ };
497
+ }
498
+ // youmd://identity/markdown
499
+ if (uri === "youmd://identity/markdown") {
500
+ return {
501
+ contents: [{
502
+ uri,
503
+ mimeType: "text/markdown",
504
+ text: getYouMd(),
505
+ }],
506
+ };
507
+ }
508
+ // youmd://memories
509
+ if (uri === "youmd://memories" || uri === "youmd://memories/all") {
510
+ const memories = await fetchMemories(undefined, 50);
511
+ return {
512
+ contents: [{
513
+ uri,
514
+ mimeType: "application/json",
515
+ text: JSON.stringify(memories, null, 2),
516
+ }],
517
+ };
518
+ }
519
+ // youmd://memories/{category}
520
+ const memCatMatch = uri.match(/^youmd:\/\/memories\/(.+)$/);
521
+ if (memCatMatch) {
522
+ const memories = await fetchMemories(memCatMatch[1], 50);
523
+ return {
524
+ contents: [{
525
+ uri,
526
+ mimeType: "application/json",
527
+ text: JSON.stringify(memories, null, 2),
528
+ }],
529
+ };
530
+ }
531
+ // youmd://project/current/{file} — local project-context files from cwd
532
+ const localProjMatch = uri.match(/^youmd:\/\/project\/current\/(prd|todo|features|decisions|changelog)$/);
533
+ if (localProjMatch) {
534
+ const content = readLocalProjectContextFile(localProjMatch[1]);
535
+ if (!content) {
536
+ throw new Error(`no local project-context/${localProjMatch[1]}.md found in current directory or parents`);
537
+ }
538
+ return {
539
+ contents: [{
540
+ uri,
541
+ mimeType: "text/markdown",
542
+ text: content,
543
+ }],
544
+ };
545
+ }
546
+ // youmd://projects/current
547
+ if (uri === "youmd://projects/current") {
548
+ const current = getCurrentProject();
549
+ if (!current)
550
+ throw new Error("no project detected in current directory");
551
+ const ctx = (0, project_1.readProjectContext)(current.dir);
552
+ return {
553
+ contents: [{
554
+ uri,
555
+ mimeType: "application/json",
556
+ text: JSON.stringify(ctx, null, 2),
557
+ }],
558
+ };
559
+ }
560
+ // youmd://projects/{name}
561
+ const projMatch = uri.match(/^youmd:\/\/projects\/(.+)$/);
562
+ if (projMatch) {
563
+ const root = (0, project_1.findProjectsRoot)();
564
+ if (!root)
565
+ throw new Error("no projects directory found");
566
+ const projDir = (0, project_1.getProjectDir)(root, projMatch[1]);
567
+ const ctx = (0, project_1.readProjectContext)(projDir);
568
+ return {
569
+ contents: [{
570
+ uri,
571
+ mimeType: "application/json",
572
+ text: JSON.stringify(ctx, null, 2),
573
+ }],
574
+ };
575
+ }
576
+ // youmd://skills/{name}
577
+ const skillMatch = uri.match(/^youmd:\/\/skills\/(.+)$/);
578
+ if (skillMatch) {
579
+ const skills = getInstalledSkills();
580
+ const skill = skills.find((s) => s.name === skillMatch[1]);
581
+ if (!skill)
582
+ throw new Error(`skill not found: ${skillMatch[1]}`);
583
+ return {
584
+ contents: [{
585
+ uri,
586
+ mimeType: "text/markdown",
587
+ text: skill.rendered || skill.raw,
588
+ }],
589
+ };
590
+ }
591
+ // youmd://{dir}/{slug} — profile/preferences/voice/directives sections
592
+ const sectionMatch = uri.match(/^youmd:\/\/(profile|preferences|voice|directives)\/(.+)$/);
593
+ if (sectionMatch) {
594
+ const bundleDir = getBundleDir();
595
+ const filePath = path.join(bundleDir, sectionMatch[1], `${sectionMatch[2]}.md`);
596
+ const content = readFileOr(filePath, "");
597
+ if (!content)
598
+ throw new Error(`section not found: ${sectionMatch[1]}/${sectionMatch[2]}`);
599
+ return {
600
+ contents: [{
601
+ uri,
602
+ mimeType: "text/markdown",
603
+ text: content,
604
+ }],
605
+ };
606
+ }
607
+ throw new Error(`unknown resource: ${uri}`);
608
+ });
609
+ // ── LIST TOOLS ─────────────────────────────────────────────────────
610
+ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
611
+ return {
612
+ tools: [
613
+ {
614
+ name: "whoami",
615
+ description: "Return a compact ~500-char identity summary: name, role, stack, tone, things to avoid, top projects, and current goal. This is the FIRST tool you should call when starting a new conversation — it gives you just enough context to orient on the user before deciding whether to pull the full identity bundle. Returns plain text, not JSON.",
616
+ inputSchema: {
617
+ type: "object",
618
+ properties: {},
619
+ },
620
+ },
621
+ {
622
+ name: "get_identity",
623
+ description: "Get the user's complete you.md identity bundle. Returns compact (default), full JSON, or human-readable markdown. For the fastest orient, call whoami first; call get_identity when you need full detail (preferences, voice, directives, projects).",
624
+ inputSchema: {
625
+ type: "object",
626
+ properties: {
627
+ format: {
628
+ type: "string",
629
+ enum: ["compact", "full", "json", "markdown"],
630
+ description: "Output format: compact (500-char summary, default — same as whoami), full/json (complete identity bundle as JSON), markdown (human-readable markdown).",
631
+ },
632
+ },
633
+ },
634
+ },
635
+ {
636
+ name: "get_section",
637
+ description: "Read a specific identity section by path. Use when you need just ONE section rather than the full bundle. Returns markdown content for the requested section. Available paths: profile/about, profile/projects, profile/now, profile/values, profile/links, preferences/agent, preferences/writing, voice/voice, directives/agent.",
638
+ inputSchema: {
639
+ type: "object",
640
+ properties: {
641
+ section: {
642
+ type: "string",
643
+ description: "Section path: profile/about, profile/projects, profile/now, profile/values, profile/links, preferences/agent, preferences/writing, voice/voice, directives/agent",
644
+ },
645
+ },
646
+ required: ["section"],
647
+ },
648
+ },
649
+ {
650
+ name: "update_section",
651
+ description: "Update a section of the user's identity. Writes markdown content to the local .youmd/ directory. Use after the user explicitly asks to change their profile, preferences, voice, or directives. Always confirm changes with the user before calling. Does NOT auto-push — call push_bundle afterward if the user wants to publish.",
652
+ inputSchema: {
653
+ type: "object",
654
+ properties: {
655
+ section: {
656
+ type: "string",
657
+ description: "Section to update: profile/about, profile/projects, profile/now, profile/values, preferences/agent, preferences/writing, voice/voice, directives/agent",
658
+ },
659
+ content: {
660
+ type: "string",
661
+ description: "New markdown content for the section",
662
+ },
663
+ },
664
+ required: ["section", "content"],
665
+ },
666
+ },
667
+ {
668
+ name: "add_memory",
669
+ description: "Save a memory about the user — facts, preferences, decisions, or context learned during this conversation. Memories persist across sessions and inform ALL future agent interactions. Use proactively when you learn something important about the user (a preference, a decision, a project detail). Requires authentication.",
670
+ inputSchema: {
671
+ type: "object",
672
+ properties: {
673
+ category: {
674
+ type: "string",
675
+ enum: [...MEMORY_CATEGORIES],
676
+ description: "Memory category. Must be one of: fact, preference, decision, project, goal, insight, context, relationship.",
677
+ },
678
+ content: {
679
+ type: "string",
680
+ description: "The memory content — be specific and actionable",
681
+ },
682
+ tags: {
683
+ type: "array",
684
+ items: { type: "string" },
685
+ description: "Optional tags for searchability",
686
+ },
687
+ },
688
+ required: ["category", "content"],
689
+ },
690
+ },
691
+ {
692
+ name: "search_memories",
693
+ description: "Search the user's memories by category or list all active memories. Returns an array of memory objects with category, content, tags, and timestamps. Use to check what you already know before asking the user a question they may have answered before.",
694
+ inputSchema: {
695
+ type: "object",
696
+ properties: {
697
+ category: {
698
+ type: "string",
699
+ description: "Filter by category (optional): fact, preference, decision, project, goal, insight, context",
700
+ },
701
+ limit: {
702
+ type: "number",
703
+ description: "Max results (default 30)",
704
+ },
705
+ },
706
+ },
707
+ },
708
+ {
709
+ name: "get_project_context",
710
+ description: "Get the full project context for the current or named project — PRD, TODO, features, decisions, changelog, and project memories. Returns a JSON object with all project-context/ files. Use when starting work on a project to understand what has been built, what is planned, and what decisions have been made.",
711
+ inputSchema: {
712
+ type: "object",
713
+ properties: {
714
+ project: {
715
+ type: "string",
716
+ description: "Project name. Omit to auto-detect from current directory.",
717
+ },
718
+ },
719
+ },
720
+ },
721
+ {
722
+ name: "add_project_memory",
723
+ description: "Save a memory scoped to a specific project. Unlike add_memory (which is global), project memories are stored locally in the project-context/ directory and only surface when working on that project. Use for architecture decisions, bug context, and feature-specific learnings.",
724
+ inputSchema: {
725
+ type: "object",
726
+ properties: {
727
+ project: {
728
+ type: "string",
729
+ description: "Project name. Omit to use current project.",
730
+ },
731
+ category: {
732
+ type: "string",
733
+ description: "Memory category: decision, context, bug, feature, architecture",
734
+ },
735
+ content: {
736
+ type: "string",
737
+ description: "The memory content",
738
+ },
739
+ },
740
+ required: ["category", "content"],
741
+ },
742
+ },
743
+ {
744
+ name: "use_skill",
745
+ description: "Render an identity-aware skill — returns the skill content with the user's identity interpolated into {{var}} placeholders. Returns rendered markdown with instructions the agent should follow. Use when the user asks to generate a CLAUDE.md, sync voice, scaffold a project, or run a self-improvement review.",
746
+ inputSchema: {
747
+ type: "object",
748
+ properties: {
749
+ name: {
750
+ type: "string",
751
+ description: "Skill name: voice-sync, claude-md-generator, project-context-init, meta-improve, proactive-context-fill, you-logs",
752
+ },
753
+ },
754
+ required: ["name"],
755
+ },
756
+ },
757
+ {
758
+ name: "compile_bundle",
759
+ description: "Recompile the local identity bundle from profile/, preferences/, voice/, directives/ files into you.json + you.md. Call after update_section to regenerate the compiled bundle. Returns compilation stats including version, section count, and files read.",
760
+ inputSchema: {
761
+ type: "object",
762
+ properties: {},
763
+ },
764
+ },
765
+ {
766
+ name: "push_bundle",
767
+ description: "Push the local identity bundle to you.md servers and publish the profile. Requires authentication. Call after compile_bundle when the user wants their changes live. Returns the published version number.",
768
+ inputSchema: {
769
+ type: "object",
770
+ properties: {
771
+ publish: {
772
+ type: "boolean",
773
+ description: "Whether to publish after push (default true)",
774
+ },
775
+ },
776
+ },
777
+ },
778
+ {
779
+ name: "compile_and_push",
780
+ description: "Combo tool that compiles the local .youmd bundle, writes it to disk, uploads it, and publishes it in one call. Replaces having to call compile_bundle + push_bundle + publish separately. Requires authentication. Returns the new version number and bundle content hash.",
781
+ inputSchema: {
782
+ type: "object",
783
+ properties: {},
784
+ },
785
+ },
786
+ {
787
+ name: "list_skills",
788
+ description: "List all installed identity-aware skills with their names. Use to discover what skills are available before calling use_skill. Returns a simple list of installed skill names.",
789
+ inputSchema: {
790
+ type: "object",
791
+ properties: {},
792
+ },
793
+ },
794
+ {
795
+ name: "add_source",
796
+ description: "Register an identity data source (LinkedIn, GitHub, X, website, blog, YouTube). Links an external profile to the user's identity so it can be scraped and indexed. Requires authentication. Use when the user wants to connect a new social profile or website to their identity.",
797
+ inputSchema: {
798
+ type: "object",
799
+ properties: {
800
+ sourceType: {
801
+ type: "string",
802
+ enum: ["website", "linkedin", "x", "github", "blog", "youtube"],
803
+ description: "Type of source to register",
804
+ },
805
+ sourceUrl: {
806
+ type: "string",
807
+ description: "Full URL to the source (e.g., https://github.com/username)",
808
+ },
809
+ },
810
+ required: ["sourceType", "sourceUrl"],
811
+ },
812
+ },
813
+ {
814
+ name: "create_context_link",
815
+ description: "Generate a shareable context link for agents. The link gives any agent temporary or permanent read access to the user's identity context. Use when the user wants to share their identity with a third-party agent or service. Returns a URL that can be passed to any agent.",
816
+ inputSchema: {
817
+ type: "object",
818
+ properties: {
819
+ scope: {
820
+ type: "string",
821
+ enum: ["public", "full"],
822
+ description: "Access scope: public (profile only) or full (includes memories, preferences, directives). Default: public",
823
+ },
824
+ ttl: {
825
+ type: "string",
826
+ enum: ["1h", "24h", "7d", "30d", "never"],
827
+ description: "Time-to-live for the link. Default: 24h",
828
+ },
829
+ },
830
+ },
831
+ },
832
+ {
833
+ name: "list_projects",
834
+ description: "List all detected projects with their metadata. Returns project names found in the projects root directory. Use to discover available projects before calling get_project_context with a specific project name.",
835
+ inputSchema: {
836
+ type: "object",
837
+ properties: {},
838
+ },
839
+ },
840
+ {
841
+ name: "get_remote_status",
842
+ description: "Check sync status between local identity bundle and the remote you.md server. Returns whether the user is authenticated, whether the local bundle exists, and the current version info. Use to diagnose sync issues or confirm a push was successful.",
843
+ inputSchema: {
844
+ type: "object",
845
+ properties: {},
846
+ },
847
+ },
848
+ {
849
+ name: "get_activity_log",
850
+ description: "Get the user's recent agent activity log. Use this to see which agents have connected to their you.md identity and what they did. Returns an array of activity events with agent name, action, resource, timestamp. Useful for: showing the user proof their identity context is being used by other agents, debugging integration issues, auditing access.",
851
+ inputSchema: {
852
+ type: "object",
853
+ properties: {
854
+ limit: { type: "number", description: "Max events (default 30)" },
855
+ agentName: { type: "string", description: "Filter by agent name (e.g. 'Claude Code')" },
856
+ action: { type: "string", description: "Filter by action (read|write|push|memory_add)" },
857
+ },
858
+ },
859
+ },
860
+ ],
861
+ };
862
+ });
863
+ // ── CALL TOOL ──────────────────────────────────────────────────────
864
+ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
865
+ const { name, arguments: args } = request.params;
866
+ switch (name) {
867
+ case "whoami": {
868
+ void logMcpActivity("read", "identity:compact");
869
+ return {
870
+ content: [{
871
+ type: "text",
872
+ text: buildWhoamiSummary(),
873
+ }],
874
+ };
875
+ }
876
+ case "get_identity": {
877
+ const format = args?.format || "compact";
878
+ void logMcpActivity("read", "identity:" + format);
879
+ if (format === "markdown") {
880
+ return { content: [{ type: "text", text: buildIdentityMarkdown() }] };
881
+ }
882
+ if (format === "full" || format === "json") {
883
+ return {
884
+ content: [{
885
+ type: "text",
886
+ text: JSON.stringify(getYouJson(), null, 2),
887
+ }],
888
+ };
889
+ }
890
+ // compact (default) — matches whoami output
891
+ return {
892
+ content: [{
893
+ type: "text",
894
+ text: buildWhoamiSummary(),
895
+ }],
896
+ };
897
+ }
898
+ case "get_section": {
899
+ const section = args.section;
900
+ const parts = section.split("/");
901
+ if (parts.length !== 2) {
902
+ return { content: [{ type: "text", text: "invalid section path — use format: dir/slug (e.g., profile/about)" }], isError: true };
903
+ }
904
+ const filePath = path.join(getBundleDir(), parts[0], `${parts[1]}.md`);
905
+ const content = readFileOr(filePath, "");
906
+ if (!content) {
907
+ return { content: [{ type: "text", text: `section not found: ${section}` }], isError: true };
908
+ }
909
+ void logMcpActivity("read_section", section);
910
+ return { content: [{ type: "text", text: content }] };
911
+ }
912
+ case "update_section": {
913
+ const section = args.section;
914
+ const content = args.content;
915
+ const parts = section.split("/");
916
+ if (parts.length !== 2) {
917
+ return { content: [{ type: "text", text: "invalid section path" }], isError: true };
918
+ }
919
+ const bundleDir = getBundleDir();
920
+ const dirPath = path.join(bundleDir, parts[0]);
921
+ if (!fs.existsSync(dirPath)) {
922
+ fs.mkdirSync(dirPath, { recursive: true });
923
+ }
924
+ const filePath = path.join(dirPath, `${parts[1]}.md`);
925
+ fs.writeFileSync(filePath, content);
926
+ void logMcpActivity("write", section);
927
+ return { content: [{ type: "text", text: `updated ${section}` }] };
928
+ }
929
+ case "add_memory": {
930
+ const { category, content: memContent, tags } = args;
931
+ if (!(0, config_1.isAuthenticated)()) {
932
+ return { content: [{ type: "text", text: "not authenticated — run youmd login first" }], isError: true };
933
+ }
934
+ // Validate category against known enum. Reject unknown categories so
935
+ // we don't pollute the memory store with one-off tags masquerading as
936
+ // categories.
937
+ if (!category || !MEMORY_CATEGORIES.includes(category)) {
938
+ return {
939
+ content: [{
940
+ type: "text",
941
+ text: `invalid category: ${category || "(missing)"}. valid categories: ${MEMORY_CATEGORIES.join(", ")}`,
942
+ }],
943
+ isError: true,
944
+ };
945
+ }
946
+ try {
947
+ const result = await apiRequest("/api/v1/me/memories", {
948
+ method: "POST",
949
+ body: {
950
+ memories: [{
951
+ category,
952
+ content: memContent,
953
+ source: "mcp",
954
+ sourceAgent: "youmd-mcp",
955
+ tags,
956
+ }],
957
+ },
958
+ });
959
+ void logMcpActivity("memory_add", category);
960
+ return { content: [{ type: "text", text: `memory saved: [${category}] ${memContent.slice(0, 80)}${memContent.length > 80 ? "..." : ""}` }] };
961
+ }
962
+ catch (err) {
963
+ return { content: [{ type: "text", text: `failed to save memory: ${err instanceof Error ? err.message : "unknown error"}` }], isError: true };
964
+ }
965
+ }
966
+ case "search_memories": {
967
+ const { category, limit } = (args || {});
968
+ const memories = await fetchMemories(category, limit || 30);
969
+ void logMcpActivity("read", "memories");
970
+ return {
971
+ content: [{
972
+ type: "text",
973
+ text: JSON.stringify(memories, null, 2),
974
+ }],
975
+ };
976
+ }
977
+ case "get_project_context": {
978
+ const projectName = args?.project;
979
+ try {
980
+ if (projectName) {
981
+ const root = (0, project_1.findProjectsRoot)();
982
+ if (!root)
983
+ throw new Error("no projects directory found");
984
+ const projDir = (0, project_1.getProjectDir)(root, projectName);
985
+ const ctx = (0, project_1.readProjectContext)(projDir);
986
+ void logMcpActivity("read", "project/" + projectName);
987
+ return { content: [{ type: "text", text: JSON.stringify(ctx, null, 2) }] };
988
+ }
989
+ const current = getCurrentProject();
990
+ if (!current) {
991
+ return { content: [{ type: "text", text: "no project detected in current directory" }], isError: true };
992
+ }
993
+ const ctx = (0, project_1.readProjectContext)(current.dir);
994
+ void logMcpActivity("read", "project/current");
995
+ return { content: [{ type: "text", text: JSON.stringify(ctx, null, 2) }] };
996
+ }
997
+ catch (err) {
998
+ return { content: [{ type: "text", text: `project error: ${err instanceof Error ? err.message : "unknown"}` }], isError: true };
999
+ }
1000
+ }
1001
+ case "add_project_memory": {
1002
+ const { project: projName, category, content: memContent } = args;
1003
+ try {
1004
+ let projDir;
1005
+ if (projName) {
1006
+ const root = (0, project_1.findProjectsRoot)();
1007
+ if (!root)
1008
+ throw new Error("no projects directory found");
1009
+ projDir = (0, project_1.getProjectDir)(root, projName);
1010
+ }
1011
+ else {
1012
+ const current = getCurrentProject();
1013
+ if (!current)
1014
+ throw new Error("no project detected");
1015
+ projDir = current.dir;
1016
+ }
1017
+ (0, project_1.addProjectMemory)(projDir, { category, content: memContent });
1018
+ void logMcpActivity("memory_add", "project/" + (projName || "current"));
1019
+ return { content: [{ type: "text", text: `project memory saved: [${category}] ${memContent.slice(0, 80)}` }] };
1020
+ }
1021
+ catch (err) {
1022
+ return { content: [{ type: "text", text: `error: ${err instanceof Error ? err.message : "unknown"}` }], isError: true };
1023
+ }
1024
+ }
1025
+ case "use_skill": {
1026
+ const skillName = args.name;
1027
+ const skills = getInstalledSkills();
1028
+ const skill = skills.find((s) => s.name === skillName);
1029
+ if (!skill) {
1030
+ const available = skills.map((s) => s.name).join(", ") || "none installed";
1031
+ return { content: [{ type: "text", text: `skill not found: ${skillName}. available: ${available}` }], isError: true };
1032
+ }
1033
+ void logMcpActivity("skill_use", "skill/" + skillName);
1034
+ return { content: [{ type: "text", text: skill.rendered || skill.raw }] };
1035
+ }
1036
+ case "compile_bundle": {
1037
+ try {
1038
+ const { compileBundle, writeBundle } = await Promise.resolve().then(() => __importStar(require("../lib/compiler")));
1039
+ const bundleDir = getBundleDir();
1040
+ const result = compileBundle(bundleDir);
1041
+ writeBundle(bundleDir, result);
1042
+ void logMcpActivity("compile", "bundle");
1043
+ return {
1044
+ content: [{
1045
+ type: "text",
1046
+ text: `compiled: v${result.stats.version} — ${result.stats.filledSections}/${result.stats.totalSections} sections filled, ${result.filesRead.length} files read`,
1047
+ }],
1048
+ };
1049
+ }
1050
+ catch (err) {
1051
+ return { content: [{ type: "text", text: `compile error: ${err instanceof Error ? err.message : "unknown"}` }], isError: true };
1052
+ }
1053
+ }
1054
+ case "push_bundle": {
1055
+ if (!(0, config_1.isAuthenticated)()) {
1056
+ return { content: [{ type: "text", text: "not authenticated — run youmd login first" }], isError: true };
1057
+ }
1058
+ try {
1059
+ const { compileBundle } = await Promise.resolve().then(() => __importStar(require("../lib/compiler")));
1060
+ const { uploadBundle, publishLatest } = await Promise.resolve().then(() => __importStar(require("../lib/api")));
1061
+ const bundleDir = getBundleDir();
1062
+ const result = compileBundle(bundleDir);
1063
+ const uploadRes = await uploadBundle({
1064
+ manifest: result.manifest,
1065
+ youJson: result.youJson,
1066
+ youMd: result.markdown,
1067
+ });
1068
+ if (!uploadRes.ok) {
1069
+ return { content: [{ type: "text", text: `push failed: ${JSON.stringify(uploadRes.data)}` }], isError: true };
1070
+ }
1071
+ const shouldPublish = args?.publish !== false;
1072
+ if (shouldPublish) {
1073
+ await publishLatest();
1074
+ }
1075
+ void logMcpActivity("push", "bundle", { version: result.stats.version });
1076
+ return {
1077
+ content: [{
1078
+ type: "text",
1079
+ text: `pushed v${result.stats.version}${shouldPublish ? " and published" : ""}`,
1080
+ }],
1081
+ };
1082
+ }
1083
+ catch (err) {
1084
+ return { content: [{ type: "text", text: `push error: ${err instanceof Error ? err.message : "unknown"}` }], isError: true };
1085
+ }
1086
+ }
1087
+ case "compile_and_push": {
1088
+ if (!(0, config_1.isAuthenticated)()) {
1089
+ return { content: [{ type: "text", text: "not authenticated — run youmd login first" }], isError: true };
1090
+ }
1091
+ try {
1092
+ const { compileBundle, writeBundle } = await Promise.resolve().then(() => __importStar(require("../lib/compiler")));
1093
+ const { uploadBundle, publishLatest } = await Promise.resolve().then(() => __importStar(require("../lib/api")));
1094
+ const bundleDir = getBundleDir();
1095
+ // 1. Compile from the local .youmd directory
1096
+ const result = compileBundle(bundleDir);
1097
+ // 2. Write the compiled bundle to disk (you.json, you.md, manifest.json)
1098
+ writeBundle(bundleDir, result);
1099
+ // 3. Upload the bundle via the API
1100
+ const uploadRes = await uploadBundle({
1101
+ manifest: result.manifest,
1102
+ youJson: result.youJson,
1103
+ youMd: result.markdown,
1104
+ });
1105
+ if (!uploadRes.ok) {
1106
+ return {
1107
+ content: [{ type: "text", text: `upload failed: ${JSON.stringify(uploadRes.data)}` }],
1108
+ isError: true,
1109
+ };
1110
+ }
1111
+ // 4. Publish the latest bundle
1112
+ const publishRes = await publishLatest();
1113
+ if (!publishRes.ok) {
1114
+ return {
1115
+ content: [{ type: "text", text: `publish failed: ${JSON.stringify(publishRes.data)}` }],
1116
+ isError: true,
1117
+ };
1118
+ }
1119
+ // 5. Return version + content hash. Pull hash from upload response
1120
+ // when available, otherwise fall back to whatever the manifest has.
1121
+ const uploadData = (uploadRes.data || {});
1122
+ const contentHash = uploadData.contentHash ||
1123
+ uploadData.hash ||
1124
+ result.manifest.contentHash ||
1125
+ "unknown";
1126
+ void logMcpActivity("push", "bundle", { version: result.stats.version, hash: contentHash });
1127
+ return {
1128
+ content: [{
1129
+ type: "text",
1130
+ text: `compiled + pushed + published v${result.stats.version} (hash: ${contentHash})`,
1131
+ }],
1132
+ };
1133
+ }
1134
+ catch (err) {
1135
+ return {
1136
+ content: [{ type: "text", text: `compile_and_push error: ${err instanceof Error ? err.message : "unknown"}` }],
1137
+ isError: true,
1138
+ };
1139
+ }
1140
+ }
1141
+ case "list_skills": {
1142
+ const skills = getInstalledSkills();
1143
+ if (skills.length === 0) {
1144
+ return { content: [{ type: "text", text: "no skills installed. run: youmd skill install voice-sync" }] };
1145
+ }
1146
+ const list = skills.map((s) => `- ${s.name}`).join("\n");
1147
+ void logMcpActivity("read", "skills");
1148
+ return { content: [{ type: "text", text: `installed skills:\n${list}` }] };
1149
+ }
1150
+ case "add_source": {
1151
+ if (!(0, config_1.isAuthenticated)()) {
1152
+ return { content: [{ type: "text", text: "not authenticated — run youmd login first" }], isError: true };
1153
+ }
1154
+ const { sourceType, sourceUrl } = args;
1155
+ try {
1156
+ const result = await apiRequest("/api/v1/me/sources", {
1157
+ method: "POST",
1158
+ body: { sourceType, sourceUrl },
1159
+ });
1160
+ void logMcpActivity("write", "source/" + sourceType);
1161
+ return { content: [{ type: "text", text: `source registered: [${sourceType}] ${sourceUrl}` }] };
1162
+ }
1163
+ catch (err) {
1164
+ return { content: [{ type: "text", text: `failed to add source: ${err instanceof Error ? err.message : "unknown error"}` }], isError: true };
1165
+ }
1166
+ }
1167
+ case "create_context_link": {
1168
+ if (!(0, config_1.isAuthenticated)()) {
1169
+ return { content: [{ type: "text", text: "not authenticated — run youmd login first" }], isError: true };
1170
+ }
1171
+ const { scope, ttl } = (args || {});
1172
+ try {
1173
+ const result = await apiRequest("/api/v1/me/context-links", {
1174
+ method: "POST",
1175
+ body: { scope: scope || "public", ttl: ttl || "24h" },
1176
+ });
1177
+ const link = result.url || result.link || JSON.stringify(result);
1178
+ void logMcpActivity("write", "context-link", { scope: scope || "public" });
1179
+ return { content: [{ type: "text", text: `context link created: ${link}\nscope: ${scope || "public"}, ttl: ${ttl || "24h"}` }] };
1180
+ }
1181
+ catch (err) {
1182
+ return { content: [{ type: "text", text: `failed to create context link: ${err instanceof Error ? err.message : "unknown error"}` }], isError: true };
1183
+ }
1184
+ }
1185
+ case "list_projects": {
1186
+ try {
1187
+ const root = (0, project_1.findProjectsRoot)();
1188
+ if (!root) {
1189
+ return { content: [{ type: "text", text: "no projects directory found" }], isError: true };
1190
+ }
1191
+ const projects = (0, project_1.listProjects)(root);
1192
+ if (projects.length === 0) {
1193
+ return { content: [{ type: "text", text: "no projects detected. create one with: youmd project init <name>" }] };
1194
+ }
1195
+ const current = getCurrentProject();
1196
+ const list = projects.map((p) => {
1197
+ const marker = current && current.name === p ? " (current)" : "";
1198
+ return `- ${p}${marker}`;
1199
+ }).join("\n");
1200
+ void logMcpActivity("read", "projects");
1201
+ return { content: [{ type: "text", text: `projects:\n${list}` }] };
1202
+ }
1203
+ catch (err) {
1204
+ return { content: [{ type: "text", text: `error listing projects: ${err instanceof Error ? err.message : "unknown"}` }], isError: true };
1205
+ }
1206
+ }
1207
+ case "get_remote_status": {
1208
+ const authenticated = (0, config_1.isAuthenticated)();
1209
+ const bundleExists = (0, config_1.localBundleExists)();
1210
+ const youJson = getYouJson();
1211
+ const version = youJson?.version || "unknown";
1212
+ const status = {
1213
+ authenticated,
1214
+ localBundleExists: bundleExists,
1215
+ localVersion: version,
1216
+ };
1217
+ if (authenticated) {
1218
+ try {
1219
+ const remote = await apiRequest("/api/v1/me/status");
1220
+ status.remote = remote;
1221
+ }
1222
+ catch {
1223
+ status.remote = "unreachable";
1224
+ }
1225
+ }
1226
+ void logMcpActivity("read", "status");
1227
+ return {
1228
+ content: [{
1229
+ type: "text",
1230
+ text: JSON.stringify(status, null, 2),
1231
+ }],
1232
+ };
1233
+ }
1234
+ case "get_activity_log": {
1235
+ if (!(0, config_1.isAuthenticated)()) {
1236
+ return { content: [{ type: "text", text: "not authenticated — run youmd login first" }], isError: true };
1237
+ }
1238
+ const config = (0, config_1.readGlobalConfig)();
1239
+ const params = new URLSearchParams();
1240
+ const activityArgs = (args || {});
1241
+ if (activityArgs.limit)
1242
+ params.set("limit", String(activityArgs.limit));
1243
+ if (activityArgs.agentName)
1244
+ params.set("agent", String(activityArgs.agentName));
1245
+ if (activityArgs.action)
1246
+ params.set("action", String(activityArgs.action));
1247
+ const res = await fetch(`${(0, config_1.getConvexSiteUrl)()}/api/v1/me/activity?${params}`, {
1248
+ headers: { Authorization: `Bearer ${config.token}` },
1249
+ signal: AbortSignal.timeout(10000),
1250
+ });
1251
+ if (!res.ok) {
1252
+ return { content: [{ type: "text", text: `failed to fetch activity log: ${res.status}` }], isError: true };
1253
+ }
1254
+ const data = await res.json();
1255
+ const events = data.activity || [];
1256
+ if (events.length === 0) {
1257
+ return { content: [{ type: "text", text: "No activity yet. Agents will appear here when they connect to your you.md identity." }] };
1258
+ }
1259
+ const formatted = events.slice(0, 30).reverse().map((e) => {
1260
+ const time = new Date(e.createdAt).toTimeString().slice(0, 5);
1261
+ const versions = e.bundleVersionBefore && e.bundleVersionAfter
1262
+ ? ` v${e.bundleVersionBefore}→v${e.bundleVersionAfter}`
1263
+ : '';
1264
+ return `${time} ${e.agentName.padEnd(16)} ${e.action.padEnd(12)} ${e.resource || ''}${versions}`;
1265
+ }).join('\n');
1266
+ return {
1267
+ content: [{
1268
+ type: "text",
1269
+ text: `── recent agent activity (${events.length} events) ──\n\n${formatted}`,
1270
+ }],
1271
+ };
1272
+ }
1273
+ default:
1274
+ throw new Error(`unknown tool: ${name}`);
1275
+ }
1276
+ });
1277
+ // ── START ──────────────────────────────────────────────────────────
1278
+ const transport = new stdio_js_1.StdioServerTransport();
1279
+ await server.connect(transport);
1280
+ // All logging goes to stderr — stdout is reserved for MCP protocol
1281
+ console.error("youmd mcp server running");
1282
+ }
1283
+ //# sourceMappingURL=server.js.map