@mayamemo/mcp 0.1.0 → 0.1.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.
package/dist/bin.js CHANGED
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import { createHttpCallApi, createStdioRunner } from './stdio.js';
3
+ import { resolveDefaultProjectId } from './project.js';
3
4
  const apiUrl = process.env.MAYAMEMO_API_URL ?? 'http://localhost:4000';
4
5
  const token = process.env.MAYAMEMO_TOKEN;
5
6
  if (!token) {
6
7
  process.stderr.write('mayamemo-mcp: MAYAMEMO_TOKEN is required (workspace API key, mm_sk_...).\n');
7
8
  process.exit(1);
8
9
  }
9
- process.stderr.write(`mayamemo-mcp: serving on stdio, API at ${apiUrl}\n`);
10
+ const linked = resolveDefaultProjectId();
11
+ process.stderr.write(`mayamemo-mcp: serving on stdio, API at ${apiUrl}${linked ? `, linked project ${linked}` : ' — no project linked to this folder'}\n`);
10
12
  createStdioRunner({
11
13
  input: process.stdin,
12
14
  output: process.stdout,
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './server.js';
2
2
  export * from './tools.js';
3
3
  export * from './stdio.js';
4
+ export * from './project.js';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './server.js';
2
2
  export * from './tools.js';
3
3
  export * from './stdio.js';
4
+ export * from './project.js';
@@ -0,0 +1,12 @@
1
+ export interface ProjectMapping {
2
+ projectId: string;
3
+ name?: string;
4
+ workspaceId?: string;
5
+ apiUrl?: string;
6
+ }
7
+ /** True for placeholder ids agents invent when they don't know the project. */
8
+ export declare function isPlaceholderProjectId(value: string): boolean;
9
+ /** Walk up from `startDir` looking for .mayamemo/project.json. */
10
+ export declare function findMappedProjectId(startDir?: string): string | null;
11
+ /** env override first, then the linked mapping; null when nothing is linked. */
12
+ export declare function resolveDefaultProjectId(startDir?: string): string | null;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Default project resolution for the MCP server. When a tool call omits
3
+ * project_id, resolve the project linked to the working directory:
4
+ * MAYAMEMO_PROJECT_ID env first, then the nearest .mayamemo/project.json
5
+ * (written by `mayamemo init` / `mayamemo install`), walking up.
6
+ */
7
+ import { existsSync, readFileSync } from 'node:fs';
8
+ import { dirname, join, resolve } from 'node:path';
9
+ const ZERO_UUID = /^0{8}-0{4}-0{4}-0{4}-0{12}$/;
10
+ /** True for placeholder ids agents invent when they don't know the project. */
11
+ export function isPlaceholderProjectId(value) {
12
+ return ZERO_UUID.test(value.trim());
13
+ }
14
+ /** Walk up from `startDir` looking for .mayamemo/project.json. */
15
+ export function findMappedProjectId(startDir = process.cwd()) {
16
+ let dir = resolve(startDir);
17
+ for (;;) {
18
+ const candidate = join(dir, '.mayamemo', 'project.json');
19
+ if (existsSync(candidate)) {
20
+ try {
21
+ const parsed = JSON.parse(readFileSync(candidate, 'utf8'));
22
+ if (typeof parsed.projectId === 'string' && parsed.projectId.trim().length > 0) {
23
+ return parsed.projectId.trim();
24
+ }
25
+ }
26
+ catch {
27
+ // Corrupt mapping — keep walking up.
28
+ }
29
+ }
30
+ const parent = dirname(dir);
31
+ if (parent === dir)
32
+ return null;
33
+ dir = parent;
34
+ }
35
+ }
36
+ /** env override first, then the linked mapping; null when nothing is linked. */
37
+ export function resolveDefaultProjectId(startDir) {
38
+ const env = process.env.MAYAMEMO_PROJECT_ID;
39
+ if (typeof env === 'string' && env.trim().length > 0 && !isPlaceholderProjectId(env)) {
40
+ return env.trim();
41
+ }
42
+ return findMappedProjectId(startDir);
43
+ }
package/dist/tools.js CHANGED
@@ -4,6 +4,7 @@
4
4
  * V1 tools per docs/MCP.md: context_get, context_search, memory_search,
5
5
  * memory_save, project_state, checkpoint_create (unlocked at M06).
6
6
  */
7
+ import { isPlaceholderProjectId, resolveDefaultProjectId } from './project.js';
7
8
  /** Thrown by handlers for invalid tool arguments → JSON-RPC -32602. */
8
9
  export class ToolInputError extends Error {
9
10
  }
@@ -60,6 +61,23 @@ function entityRefs(args, key, cap) {
60
61
  }
61
62
  return refs;
62
63
  }
64
+ /** project_id is optional: an explicit value wins; otherwise fall back to the
65
+ * project linked to the working directory (MAYAMEMO_PROJECT_ID env or the
66
+ * nearest .mayamemo/project.json). */
67
+ function resolveProjectId(args) {
68
+ const raw = args.project_id;
69
+ if (raw !== undefined && raw !== null) {
70
+ if (typeof raw !== 'string')
71
+ throw new ToolInputError('Argument project_id must be a string');
72
+ const trimmed = raw.trim();
73
+ if (trimmed.length > 0 && !isPlaceholderProjectId(trimmed))
74
+ return trimmed;
75
+ }
76
+ const mapped = resolveDefaultProjectId();
77
+ if (mapped)
78
+ return mapped;
79
+ throw new ToolInputError('No MayaMemo project is linked to this folder. Run `npx -y @mayamemo/cli init` (or `mayamemo install opencode`) in the repo to link one, or pass project_id explicitly.');
80
+ }
63
81
  function apiErrorText(status, body) {
64
82
  const error = body.error;
65
83
  const code = error?.code ?? 'API_ERROR';
@@ -76,7 +94,7 @@ export const TOOLS = [
76
94
  inputSchema: {
77
95
  type: 'object',
78
96
  properties: {
79
- project_id: { type: 'string', description: 'MayaMemo project id (from the project mapping)' },
97
+ project_id: { type: 'string', description: 'Optional — defaults to the project linked to this folder (.mayamemo/project.json or MAYAMEMO_PROJECT_ID)' },
80
98
  statement: { type: 'string', description: 'The durable claim, present tense' },
81
99
  type: { type: 'string', description: 'Memory type' },
82
100
  confidence: { type: 'number', description: '0..1 how strongly the evidence supports it' },
@@ -101,11 +119,11 @@ export const TOOLS = [
101
119
  description: 'Optional code entities this memory is about (linked into the project graph)',
102
120
  },
103
121
  },
104
- required: ['project_id', 'statement', 'type'],
122
+ required: ['statement', 'type'],
105
123
  },
106
124
  async handler(args, deps) {
107
125
  const payload = {
108
- project_id: requireString(args, 'project_id'),
126
+ project_id: resolveProjectId(args),
109
127
  statement: requireString(args, 'statement'),
110
128
  type: requireString(args, 'type'),
111
129
  };
@@ -141,7 +159,7 @@ export const TOOLS = [
141
159
  inputSchema: {
142
160
  type: 'object',
143
161
  properties: {
144
- project_id: { type: 'string' },
162
+ project_id: { type: 'string', description: 'Optional — defaults to the project linked to this folder' },
145
163
  query: { type: 'string', description: 'Natural-language search query (may embed a time phrase: "last week", "past 3 days", "since 2026-09-01")' },
146
164
  status: { type: 'string', description: 'Optional memory status filter (default active)' },
147
165
  limit: { type: 'number', description: 'Max results (default 8, max 25)' },
@@ -153,11 +171,11 @@ export const TOOLS = [
153
171
  since: { type: 'string', description: 'Optional ISO datetime lower bound (memories created or changed after it)' },
154
172
  until: { type: 'string', description: 'Optional ISO datetime upper bound' },
155
173
  },
156
- required: ['project_id', 'query'],
174
+ required: ['query'],
157
175
  },
158
176
  async handler(args, deps) {
159
177
  const payload = {
160
- project_id: requireString(args, 'project_id'),
178
+ project_id: resolveProjectId(args),
161
179
  query: requireString(args, 'query'),
162
180
  };
163
181
  const status = optionalString(args, 'status');
@@ -193,16 +211,16 @@ export const TOOLS = [
193
211
  inputSchema: {
194
212
  type: 'object',
195
213
  properties: {
196
- project_id: { type: 'string' },
214
+ project_id: { type: 'string', description: 'Optional — defaults to the project linked to this folder' },
197
215
  query: { type: 'string', description: 'Task title / intent to compile context for' },
198
216
  token_budget: { type: 'number', description: 'Hard token budget (default 4000, max 50000)' },
199
217
  branch: { type: 'string', description: 'Current git branch (branch-matched context ranks higher)' },
200
218
  },
201
- required: ['project_id', 'query'],
219
+ required: ['query'],
202
220
  },
203
221
  async handler(args, deps) {
204
222
  const payload = {
205
- project_id: requireString(args, 'project_id'),
223
+ project_id: resolveProjectId(args),
206
224
  title: requireString(args, 'query'),
207
225
  token_budget: Math.min(Math.max(optionalNumber(args, 'token_budget') ?? 4000, 64), 50000),
208
226
  max_items: MAX_CONTEXT_ITEMS,
@@ -258,11 +276,11 @@ export const TOOLS = [
258
276
  description: 'Project state snapshot: counts, last session, last checkpoint, and recent ledger activity.',
259
277
  inputSchema: {
260
278
  type: 'object',
261
- properties: { project_id: { type: 'string' } },
262
- required: ['project_id'],
279
+ properties: { project_id: { type: 'string', description: 'Optional — defaults to the project linked to this folder' } },
280
+ required: [],
263
281
  },
264
282
  async handler(args, deps) {
265
- const projectId = requireString(args, 'project_id');
283
+ const projectId = resolveProjectId(args);
266
284
  const res = await deps.callApi('GET', `/v1/projects/${encodeURIComponent(projectId)}/state`);
267
285
  if (res.status >= 400)
268
286
  return apiErrorText(res.status, res.body);
@@ -286,7 +304,7 @@ export const TOOLS = [
286
304
  inputSchema: {
287
305
  type: 'object',
288
306
  properties: {
289
- project_id: { type: 'string' },
307
+ project_id: { type: 'string', description: 'Optional — defaults to the project linked to this folder' },
290
308
  title: { type: 'string', description: 'Short checkpoint title' },
291
309
  summary: { type: 'string', description: 'What was done and where things stand' },
292
310
  next_steps: { type: 'array', items: { type: 'string' }, description: 'What the next agent should do' },
@@ -296,11 +314,11 @@ export const TOOLS = [
296
314
  context_version_id: { type: 'string', description: 'Link an existing context version instead of compiling fresh' },
297
315
  token_budget: { type: 'number', description: 'Budget for the fresh compile (default 6000)' },
298
316
  },
299
- required: ['project_id', 'title', 'summary'],
317
+ required: ['title', 'summary'],
300
318
  },
301
319
  async handler(args, deps) {
302
320
  const payload = {
303
- project_id: requireString(args, 'project_id'),
321
+ project_id: resolveProjectId(args),
304
322
  title: requireString(args, 'title'),
305
323
  summary: requireString(args, 'summary'),
306
324
  };
@@ -337,7 +355,7 @@ export const TOOLS = [
337
355
  inputSchema: {
338
356
  type: 'object',
339
357
  properties: {
340
- project_id: { type: 'string', description: 'MayaMemo project id' },
358
+ project_id: { type: 'string', description: 'Optional — defaults to the project linked to this folder' },
341
359
  entities: {
342
360
  type: 'array',
343
361
  items: {
@@ -362,10 +380,10 @@ export const TOOLS = [
362
380
  description: 'Relations between entities (max 100)',
363
381
  },
364
382
  },
365
- required: ['project_id'],
383
+ required: [],
366
384
  },
367
385
  async handler(args, deps) {
368
- const payload = { project_id: requireString(args, 'project_id') };
386
+ const payload = { project_id: resolveProjectId(args) };
369
387
  const entities = entityRefs(args, 'entities', 50);
370
388
  if (entities)
371
389
  payload.entities = entities;
@@ -393,7 +411,7 @@ export const TOOLS = [
393
411
  inputSchema: {
394
412
  type: 'object',
395
413
  properties: {
396
- project_id: { type: 'string', description: 'MayaMemo project id (required when listing)' },
414
+ project_id: { type: 'string', description: 'Optional — defaults to the project linked to this folder' },
397
415
  q: { type: 'string', description: 'Name search (substring)' },
398
416
  kind: { type: 'string', description: 'Filter by entity kind (file, module, symbol…)' },
399
417
  entity_id: { type: 'string', description: 'Entity id — switches to neighbor mode' },
@@ -416,7 +434,7 @@ export const TOOLS = [
416
434
  text: `Neighbors of ${graph.root.kind} ${graph.root.name} (${graph.nodes.length} nodes, ${graph.edges.length} edges${graph.truncated ? ', truncated' : ''}):\n${lines.join('\n')}`,
417
435
  };
418
436
  }
419
- const projectId = requireString(args, 'project_id');
437
+ const projectId = resolveProjectId(args);
420
438
  const params = new URLSearchParams();
421
439
  const q = optionalString(args, 'q');
422
440
  if (q)
@@ -441,17 +459,17 @@ export const TOOLS = [
441
459
  inputSchema: {
442
460
  type: 'object',
443
461
  properties: {
444
- project_id: { type: 'string', description: 'MayaMemo project id' },
462
+ project_id: { type: 'string', description: 'Optional — defaults to the project linked to this folder' },
445
463
  filename: { type: 'string', description: 'File name (drives text/code detection)' },
446
464
  content_base64: { type: 'string', description: 'Raw file bytes, base64-encoded (max 10 MB decoded)' },
447
465
  mime: { type: 'string', description: 'Optional MIME type (defaults to application/octet-stream)' },
448
466
  memory_id: { type: 'string', description: 'Optional memory id to link the attachment to' },
449
467
  },
450
- required: ['project_id', 'filename', 'content_base64'],
468
+ required: ['filename', 'content_base64'],
451
469
  },
452
470
  async handler(args, deps) {
453
471
  const payload = {
454
- project_id: requireString(args, 'project_id'),
472
+ project_id: resolveProjectId(args),
455
473
  filename: requireString(args, 'filename'),
456
474
  content_base64: requireString(args, 'content_base64'),
457
475
  };
@@ -536,15 +554,15 @@ export const TOOLS = [
536
554
  inputSchema: {
537
555
  type: 'object',
538
556
  properties: {
539
- project_id: { type: 'string', description: 'MayaMemo project id (from the project mapping)' },
557
+ project_id: { type: 'string', description: 'Optional — defaults to the project linked to this folder (.mayamemo/project.json or MAYAMEMO_PROJECT_ID)' },
540
558
  agent: { type: 'string', description: 'Your agent name (e.g. opencode, claude-code, codex)' },
541
559
  branch: { type: 'string', description: 'Optional git branch this session works on' },
542
560
  summary: { type: 'string', description: 'Optional one-line intent for the session' },
543
561
  },
544
- required: ['project_id'],
562
+ required: [],
545
563
  },
546
564
  async handler(args, deps) {
547
- const payload = { project_id: requireString(args, 'project_id') };
565
+ const payload = { project_id: resolveProjectId(args) };
548
566
  const agent = optionalString(args, 'agent');
549
567
  if (agent)
550
568
  payload.agent = agent;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mayamemo/mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "MayaMemo MCP server — durable project memory, checkpoints and handoffs for coding agents over stdio.",
5
5
  "license": "MIT",
6
6
  "type": "module",