@mayamemo/mcp 0.1.0 → 0.2.0

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.d.ts CHANGED
@@ -12,6 +12,9 @@ export interface McpDeps {
12
12
  status: number;
13
13
  body: unknown;
14
14
  }>;
15
+ /** Hosted transports pin a default project (most recently active in the workspace)
16
+ * so remote agents can omit project_id entirely. */
17
+ fallbackProjectId?: string;
15
18
  }
16
19
  export interface McpToolResult {
17
20
  text: string;
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,28 @@ 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), then to the transport's fallback (hosted
67
+ * MCP pins the workspace's most recently active project). */
68
+ function resolveProjectId(args, deps) {
69
+ const raw = args.project_id;
70
+ if (raw !== undefined && raw !== null) {
71
+ if (typeof raw !== 'string')
72
+ throw new ToolInputError('Argument project_id must be a string');
73
+ const trimmed = raw.trim();
74
+ if (trimmed.length > 0 && !isPlaceholderProjectId(trimmed))
75
+ return trimmed;
76
+ }
77
+ // Hosted transports pin their default first: on a shared server the process
78
+ // cwd/env is meaningless (it belongs to the server, not the caller).
79
+ if (deps.fallbackProjectId)
80
+ return deps.fallbackProjectId;
81
+ const mapped = resolveDefaultProjectId();
82
+ if (mapped)
83
+ return mapped;
84
+ 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, pass project_id explicitly, or use the hosted MCP at mcp.mayamemo.com where your most recently active project is used by default.');
85
+ }
63
86
  function apiErrorText(status, body) {
64
87
  const error = body.error;
65
88
  const code = error?.code ?? 'API_ERROR';
@@ -76,7 +99,7 @@ export const TOOLS = [
76
99
  inputSchema: {
77
100
  type: 'object',
78
101
  properties: {
79
- project_id: { type: 'string', description: 'MayaMemo project id (from the project mapping)' },
102
+ project_id: { type: 'string', description: 'Optional — defaults to the project linked to this folder (.mayamemo/project.json or MAYAMEMO_PROJECT_ID)' },
80
103
  statement: { type: 'string', description: 'The durable claim, present tense' },
81
104
  type: { type: 'string', description: 'Memory type' },
82
105
  confidence: { type: 'number', description: '0..1 how strongly the evidence supports it' },
@@ -101,11 +124,11 @@ export const TOOLS = [
101
124
  description: 'Optional code entities this memory is about (linked into the project graph)',
102
125
  },
103
126
  },
104
- required: ['project_id', 'statement', 'type'],
127
+ required: ['statement', 'type'],
105
128
  },
106
129
  async handler(args, deps) {
107
130
  const payload = {
108
- project_id: requireString(args, 'project_id'),
131
+ project_id: resolveProjectId(args, deps),
109
132
  statement: requireString(args, 'statement'),
110
133
  type: requireString(args, 'type'),
111
134
  };
@@ -141,7 +164,7 @@ export const TOOLS = [
141
164
  inputSchema: {
142
165
  type: 'object',
143
166
  properties: {
144
- project_id: { type: 'string' },
167
+ project_id: { type: 'string', description: 'Optional — defaults to the project linked to this folder' },
145
168
  query: { type: 'string', description: 'Natural-language search query (may embed a time phrase: "last week", "past 3 days", "since 2026-09-01")' },
146
169
  status: { type: 'string', description: 'Optional memory status filter (default active)' },
147
170
  limit: { type: 'number', description: 'Max results (default 8, max 25)' },
@@ -153,11 +176,11 @@ export const TOOLS = [
153
176
  since: { type: 'string', description: 'Optional ISO datetime lower bound (memories created or changed after it)' },
154
177
  until: { type: 'string', description: 'Optional ISO datetime upper bound' },
155
178
  },
156
- required: ['project_id', 'query'],
179
+ required: ['query'],
157
180
  },
158
181
  async handler(args, deps) {
159
182
  const payload = {
160
- project_id: requireString(args, 'project_id'),
183
+ project_id: resolveProjectId(args, deps),
161
184
  query: requireString(args, 'query'),
162
185
  };
163
186
  const status = optionalString(args, 'status');
@@ -193,16 +216,16 @@ export const TOOLS = [
193
216
  inputSchema: {
194
217
  type: 'object',
195
218
  properties: {
196
- project_id: { type: 'string' },
219
+ project_id: { type: 'string', description: 'Optional — defaults to the project linked to this folder' },
197
220
  query: { type: 'string', description: 'Task title / intent to compile context for' },
198
221
  token_budget: { type: 'number', description: 'Hard token budget (default 4000, max 50000)' },
199
222
  branch: { type: 'string', description: 'Current git branch (branch-matched context ranks higher)' },
200
223
  },
201
- required: ['project_id', 'query'],
224
+ required: ['query'],
202
225
  },
203
226
  async handler(args, deps) {
204
227
  const payload = {
205
- project_id: requireString(args, 'project_id'),
228
+ project_id: resolveProjectId(args, deps),
206
229
  title: requireString(args, 'query'),
207
230
  token_budget: Math.min(Math.max(optionalNumber(args, 'token_budget') ?? 4000, 64), 50000),
208
231
  max_items: MAX_CONTEXT_ITEMS,
@@ -258,11 +281,11 @@ export const TOOLS = [
258
281
  description: 'Project state snapshot: counts, last session, last checkpoint, and recent ledger activity.',
259
282
  inputSchema: {
260
283
  type: 'object',
261
- properties: { project_id: { type: 'string' } },
262
- required: ['project_id'],
284
+ properties: { project_id: { type: 'string', description: 'Optional — defaults to the project linked to this folder' } },
285
+ required: [],
263
286
  },
264
287
  async handler(args, deps) {
265
- const projectId = requireString(args, 'project_id');
288
+ const projectId = resolveProjectId(args, deps);
266
289
  const res = await deps.callApi('GET', `/v1/projects/${encodeURIComponent(projectId)}/state`);
267
290
  if (res.status >= 400)
268
291
  return apiErrorText(res.status, res.body);
@@ -286,7 +309,7 @@ export const TOOLS = [
286
309
  inputSchema: {
287
310
  type: 'object',
288
311
  properties: {
289
- project_id: { type: 'string' },
312
+ project_id: { type: 'string', description: 'Optional — defaults to the project linked to this folder' },
290
313
  title: { type: 'string', description: 'Short checkpoint title' },
291
314
  summary: { type: 'string', description: 'What was done and where things stand' },
292
315
  next_steps: { type: 'array', items: { type: 'string' }, description: 'What the next agent should do' },
@@ -296,11 +319,11 @@ export const TOOLS = [
296
319
  context_version_id: { type: 'string', description: 'Link an existing context version instead of compiling fresh' },
297
320
  token_budget: { type: 'number', description: 'Budget for the fresh compile (default 6000)' },
298
321
  },
299
- required: ['project_id', 'title', 'summary'],
322
+ required: ['title', 'summary'],
300
323
  },
301
324
  async handler(args, deps) {
302
325
  const payload = {
303
- project_id: requireString(args, 'project_id'),
326
+ project_id: resolveProjectId(args, deps),
304
327
  title: requireString(args, 'title'),
305
328
  summary: requireString(args, 'summary'),
306
329
  };
@@ -337,7 +360,7 @@ export const TOOLS = [
337
360
  inputSchema: {
338
361
  type: 'object',
339
362
  properties: {
340
- project_id: { type: 'string', description: 'MayaMemo project id' },
363
+ project_id: { type: 'string', description: 'Optional — defaults to the project linked to this folder' },
341
364
  entities: {
342
365
  type: 'array',
343
366
  items: {
@@ -362,10 +385,10 @@ export const TOOLS = [
362
385
  description: 'Relations between entities (max 100)',
363
386
  },
364
387
  },
365
- required: ['project_id'],
388
+ required: [],
366
389
  },
367
390
  async handler(args, deps) {
368
- const payload = { project_id: requireString(args, 'project_id') };
391
+ const payload = { project_id: resolveProjectId(args, deps) };
369
392
  const entities = entityRefs(args, 'entities', 50);
370
393
  if (entities)
371
394
  payload.entities = entities;
@@ -393,7 +416,7 @@ export const TOOLS = [
393
416
  inputSchema: {
394
417
  type: 'object',
395
418
  properties: {
396
- project_id: { type: 'string', description: 'MayaMemo project id (required when listing)' },
419
+ project_id: { type: 'string', description: 'Optional — defaults to the project linked to this folder' },
397
420
  q: { type: 'string', description: 'Name search (substring)' },
398
421
  kind: { type: 'string', description: 'Filter by entity kind (file, module, symbol…)' },
399
422
  entity_id: { type: 'string', description: 'Entity id — switches to neighbor mode' },
@@ -416,7 +439,7 @@ export const TOOLS = [
416
439
  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
440
  };
418
441
  }
419
- const projectId = requireString(args, 'project_id');
442
+ const projectId = resolveProjectId(args, deps);
420
443
  const params = new URLSearchParams();
421
444
  const q = optionalString(args, 'q');
422
445
  if (q)
@@ -441,17 +464,17 @@ export const TOOLS = [
441
464
  inputSchema: {
442
465
  type: 'object',
443
466
  properties: {
444
- project_id: { type: 'string', description: 'MayaMemo project id' },
467
+ project_id: { type: 'string', description: 'Optional — defaults to the project linked to this folder' },
445
468
  filename: { type: 'string', description: 'File name (drives text/code detection)' },
446
469
  content_base64: { type: 'string', description: 'Raw file bytes, base64-encoded (max 10 MB decoded)' },
447
470
  mime: { type: 'string', description: 'Optional MIME type (defaults to application/octet-stream)' },
448
471
  memory_id: { type: 'string', description: 'Optional memory id to link the attachment to' },
449
472
  },
450
- required: ['project_id', 'filename', 'content_base64'],
473
+ required: ['filename', 'content_base64'],
451
474
  },
452
475
  async handler(args, deps) {
453
476
  const payload = {
454
- project_id: requireString(args, 'project_id'),
477
+ project_id: resolveProjectId(args, deps),
455
478
  filename: requireString(args, 'filename'),
456
479
  content_base64: requireString(args, 'content_base64'),
457
480
  };
@@ -536,15 +559,15 @@ export const TOOLS = [
536
559
  inputSchema: {
537
560
  type: 'object',
538
561
  properties: {
539
- project_id: { type: 'string', description: 'MayaMemo project id (from the project mapping)' },
562
+ project_id: { type: 'string', description: 'Optional — defaults to the project linked to this folder (.mayamemo/project.json or MAYAMEMO_PROJECT_ID)' },
540
563
  agent: { type: 'string', description: 'Your agent name (e.g. opencode, claude-code, codex)' },
541
564
  branch: { type: 'string', description: 'Optional git branch this session works on' },
542
565
  summary: { type: 'string', description: 'Optional one-line intent for the session' },
543
566
  },
544
- required: ['project_id'],
567
+ required: [],
545
568
  },
546
569
  async handler(args, deps) {
547
- const payload = { project_id: requireString(args, 'project_id') };
570
+ const payload = { project_id: resolveProjectId(args, deps) };
548
571
  const agent = optionalString(args, 'agent');
549
572
  if (agent)
550
573
  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.2.0",
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",