@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 +3 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/project.d.ts +12 -0
- package/dist/project.js +43 -0
- package/dist/tools.js +44 -26
- package/package.json +1 -1
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
|
-
|
|
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
package/dist/index.js
CHANGED
|
@@ -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;
|
package/dist/project.js
ADDED
|
@@ -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: '
|
|
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: ['
|
|
122
|
+
required: ['statement', 'type'],
|
|
105
123
|
},
|
|
106
124
|
async handler(args, deps) {
|
|
107
125
|
const payload = {
|
|
108
|
-
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: ['
|
|
174
|
+
required: ['query'],
|
|
157
175
|
},
|
|
158
176
|
async handler(args, deps) {
|
|
159
177
|
const payload = {
|
|
160
|
-
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: ['
|
|
219
|
+
required: ['query'],
|
|
202
220
|
},
|
|
203
221
|
async handler(args, deps) {
|
|
204
222
|
const payload = {
|
|
205
|
-
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: [
|
|
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 =
|
|
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: ['
|
|
317
|
+
required: ['title', 'summary'],
|
|
300
318
|
},
|
|
301
319
|
async handler(args, deps) {
|
|
302
320
|
const payload = {
|
|
303
|
-
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: '
|
|
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: [
|
|
383
|
+
required: [],
|
|
366
384
|
},
|
|
367
385
|
async handler(args, deps) {
|
|
368
|
-
const payload = { 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: '
|
|
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 =
|
|
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: '
|
|
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: ['
|
|
468
|
+
required: ['filename', 'content_base64'],
|
|
451
469
|
},
|
|
452
470
|
async handler(args, deps) {
|
|
453
471
|
const payload = {
|
|
454
|
-
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: '
|
|
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: [
|
|
562
|
+
required: [],
|
|
545
563
|
},
|
|
546
564
|
async handler(args, deps) {
|
|
547
|
-
const payload = { 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;
|