@memoraone/mcp 0.1.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/cli.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-env node */
3
+ /* global console, process */
4
+ // CLI entrypoint for npx @memoraone/mcp
5
+ import './index.js';
@@ -0,0 +1,44 @@
1
+ //packages/mcp/src/client/memoraClient.ts
2
+ import { fetch } from 'undici';
3
+ export class MemoraOneHttpError extends Error {
4
+ constructor(status, statusText, body) {
5
+ super(`MemoraOne request failed: ${status} ${statusText}`);
6
+ this.name = 'MemoraOneHttpError';
7
+ this.status = status;
8
+ this.body = body;
9
+ }
10
+ }
11
+ export class MemoraClient {
12
+ constructor(cfg) {
13
+ this.baseUrl = cfg.apiUrl;
14
+ this.projectId = cfg.projectId;
15
+ this.apiKey = cfg.apiKey;
16
+ }
17
+ async post(path, body) {
18
+ const url = `${this.baseUrl}${path.startsWith('/') ? path : `/${path}`}`;
19
+ process.stderr.write(`[memoraone-mcp] http url=${url} projectId=${this.projectId} apiKeyPrefix=${String(this.apiKey).slice(0, 8)} apiKeyLen=${String(this.apiKey).length}\n`);
20
+ const res = await fetch(url, {
21
+ method: 'POST',
22
+ headers: {
23
+ 'content-type': 'application/json',
24
+ 'x-api-key': this.apiKey,
25
+ 'x-memora-project-id': this.projectId,
26
+ },
27
+ body: JSON.stringify(body ?? {}),
28
+ });
29
+ const text = await res.text();
30
+ const parsedBody = (() => {
31
+ try {
32
+ return text ? JSON.parse(text) : null;
33
+ }
34
+ catch {
35
+ return text;
36
+ }
37
+ })();
38
+ if (!res.ok) {
39
+ throw new MemoraOneHttpError(res.status, res.statusText, parsedBody);
40
+ }
41
+ return parsedBody;
42
+ }
43
+ }
44
+ export default MemoraClient;
package/dist/config.js ADDED
@@ -0,0 +1,69 @@
1
+ //packages/mcp/src/config.ts
2
+ import * as process from 'node:process';
3
+ import * as fs from 'node:fs';
4
+ import * as path from 'node:path';
5
+ import { z } from 'zod/v4';
6
+ const dotenvPath = path.resolve(process.cwd(), '.env');
7
+ if (fs.existsSync(dotenvPath)) {
8
+ try {
9
+ const { config: loadDotEnv } = await import('dotenv');
10
+ loadDotEnv({ path: dotenvPath });
11
+ }
12
+ catch (err) {
13
+ process.stderr.write('[memoraone-mcp] Failed to load .env: ' + String(err) + '\n');
14
+ }
15
+ }
16
+ const EnvSchema = z.object({
17
+ MEMORAONE_API_URL: z.string().url(),
18
+ MEMORAONE_PROJECT_ID: z.string().min(1),
19
+ MEMORAONE_API_KEY: z.string().min(1),
20
+ MEMORAONE_AGENT_NAME: z.string().min(1).optional(),
21
+ MEMORAONE_AGENT_TYPE: z.string().min(1).optional(),
22
+ MEMORAONE_SOURCE: z.string().min(1).optional(),
23
+ MEMORAONE_WORKLOG: z.string().min(1).optional(),
24
+ MEMORAONE_HEARTBEAT: z.string().min(1).optional(),
25
+ MEMORAONE_HEARTBEAT_INTERVAL_MS: z.string().min(1).optional(),
26
+ });
27
+ const requiredEnvVars = [
28
+ 'MEMORAONE_API_URL',
29
+ 'MEMORAONE_PROJECT_ID',
30
+ 'MEMORAONE_API_KEY',
31
+ ];
32
+ const missingEnvVars = requiredEnvVars.filter((key) => {
33
+ const value = process.env[key];
34
+ return value === undefined || value.trim() === '';
35
+ });
36
+ if (missingEnvVars.length > 0) {
37
+ process.stderr.write(`[memoraone-mcp] Missing required environment variables: ${missingEnvVars.join(', ')}\n`);
38
+ process.exit(1);
39
+ }
40
+ const parsed = EnvSchema.safeParse(process.env);
41
+ if (!parsed.success) {
42
+ const formatted = parsed.error.format();
43
+ process.stderr.write('[memoraone-mcp] Invalid environment variables ' + JSON.stringify(formatted) + '\n');
44
+ throw new Error('Config validation failed');
45
+ }
46
+ const parseBooleanFlag = (value, defaultValue) => {
47
+ if (value === undefined) {
48
+ return defaultValue;
49
+ }
50
+ const normalized = value.trim().toLowerCase();
51
+ if (['1', 'true', 'yes', 'on'].includes(normalized)) {
52
+ return true;
53
+ }
54
+ if (['0', 'false', 'no', 'off'].includes(normalized)) {
55
+ return false;
56
+ }
57
+ return defaultValue;
58
+ };
59
+ export const config = {
60
+ apiUrl: parsed.data.MEMORAONE_API_URL.replace(/\/+$/, ''),
61
+ projectId: parsed.data.MEMORAONE_PROJECT_ID,
62
+ apiKey: parsed.data.MEMORAONE_API_KEY,
63
+ agentName: parsed.data.MEMORAONE_AGENT_NAME ?? 'cursor',
64
+ agentType: parsed.data.MEMORAONE_AGENT_TYPE ?? 'agent',
65
+ source: parsed.data.MEMORAONE_SOURCE ?? 'cursor',
66
+ worklogEnabled: parseBooleanFlag(parsed.data.MEMORAONE_WORKLOG, true),
67
+ heartbeatEnabled: parseBooleanFlag(parsed.data.MEMORAONE_HEARTBEAT, true),
68
+ heartbeatIntervalMs: Number.parseInt(parsed.data.MEMORAONE_HEARTBEAT_INTERVAL_MS ?? '30000', 10),
69
+ };
package/dist/index.js ADDED
@@ -0,0 +1,230 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-env node */
3
+ /* global console, process */
4
+ // packages/mcp/src/index.ts
5
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
7
+ import { config } from './config.js';
8
+ import MemoraClient from './client/memoraClient.js';
9
+ import { postEventShape } from './tools/postEvent.js';
10
+ import { askWithMemoryShape } from './tools/askWithMemory.js';
11
+ import { logIntentShape } from './tools/logIntent.js';
12
+ import { logChangeSummaryShape } from './tools/logChangeSummary.js';
13
+ import { logToolResultShape } from './tools/logToolResult.js';
14
+ import { logCommandShape } from './tools/logCommand.js';
15
+ import { handlePostEvent } from './tools/handlers/postEvent.js';
16
+ import { handleAskWithMemory } from './tools/handlers/askWithMemory.js';
17
+ import { handleLogIntent } from './tools/handlers/logIntent.js';
18
+ import { handleLogChangeSummary } from './tools/handlers/logChangeSummary.js';
19
+ import { handleLogToolResult } from './tools/handlers/logToolResult.js';
20
+ import { handleLogCommand } from './tools/handlers/logCommand.js';
21
+ async function sendHeartbeat(client) {
22
+ try {
23
+ await client.post('/admin/api-keys/heartbeat', {});
24
+ }
25
+ catch (err) {
26
+ console.error('[memoraone-mcp] heartbeat error (silent)', err);
27
+ }
28
+ }
29
+ function redactSensitiveFields(obj) {
30
+ if (obj === null || obj === undefined) {
31
+ return obj;
32
+ }
33
+ if (typeof obj !== 'object') {
34
+ return obj;
35
+ }
36
+ if (Array.isArray(obj)) {
37
+ return obj.map(redactSensitiveFields);
38
+ }
39
+ const redacted = {};
40
+ const sensitiveKeys = /^(headers?|api[_-]?key|token|cookie|authorization|auth|password|secret|credential|bearer)$/i;
41
+ for (const [key, value] of Object.entries(obj)) {
42
+ if (sensitiveKeys.test(key)) {
43
+ redacted[key] = '[REDACTED]';
44
+ }
45
+ else if (typeof value === 'object') {
46
+ redacted[key] = redactSensitiveFields(value);
47
+ }
48
+ else {
49
+ redacted[key] = value;
50
+ }
51
+ }
52
+ return redacted;
53
+ }
54
+ function sanitizeArgsSummary(args) {
55
+ try {
56
+ const redacted = redactSensitiveFields(args);
57
+ const summary = JSON.stringify(redacted);
58
+ return summary.length > 200 ? summary.slice(0, 200) + '...' : summary;
59
+ }
60
+ catch {
61
+ return '[unable to serialize args]';
62
+ }
63
+ }
64
+ async function postWorklogEvent(client, message) {
65
+ try {
66
+ const body = {
67
+ kind: 'note',
68
+ concept: 'concept:worklog',
69
+ actor: { type: config.agentType, name: config.agentName },
70
+ message,
71
+ metadata: {
72
+ source: config.source,
73
+ purpose: 'worklog',
74
+ },
75
+ };
76
+ await client.post('/timeline/events', body);
77
+ }
78
+ catch (err) {
79
+ console.error('[memoraone-mcp] worklog error (silent)', err);
80
+ }
81
+ }
82
+ function registerToolWithWorklog(server, client, toolName, description, schema, handler) {
83
+ if (!config.worklogEnabled) {
84
+ server.tool(toolName, description, schema, handler);
85
+ return;
86
+ }
87
+ server.tool(toolName, description, schema, async (args) => {
88
+ const argsSummary = sanitizeArgsSummary(args);
89
+ const start = Date.now();
90
+ await postWorklogEvent(client, `tool_start: ${toolName} ${argsSummary}`);
91
+ try {
92
+ const result = await handler(args);
93
+ const durationMs = Date.now() - start;
94
+ await postWorklogEvent(client, `tool_end: ${toolName} ok (${durationMs}ms)`);
95
+ return result;
96
+ }
97
+ catch (err) {
98
+ const durationMs = Date.now() - start;
99
+ const errorSummary = err?.message || String(err);
100
+ const shortError = errorSummary.length > 100 ? errorSummary.slice(0, 100) + '...' : errorSummary;
101
+ await postWorklogEvent(client, `tool_end: ${toolName} error (${durationMs}ms): ${shortError}`);
102
+ throw err;
103
+ }
104
+ });
105
+ }
106
+ async function main() {
107
+ const client = new MemoraClient(config);
108
+ // Log agent identity (no secrets)
109
+ console.error(`[memoraone-mcp] Agent identity: name="${config.agentName}", type="${config.agentType}", source="${config.source}"`);
110
+ const server = new McpServer({
111
+ name: 'memoraone-vscode-mcp',
112
+ version: '1.0.0',
113
+ });
114
+ // Track tool names for diagnostic logging
115
+ const registeredToolNames = [];
116
+ // ---- memora_post_event ----
117
+ registeredToolNames.push('memora_post_event');
118
+ registerToolWithWorklog(server, client, 'memora_post_event', 'Forward an event to MemoraOne timeline', postEventShape, async (args) => {
119
+ const result = await handlePostEvent(client, args);
120
+ return {
121
+ content: [
122
+ {
123
+ type: 'text',
124
+ text: JSON.stringify(result),
125
+ },
126
+ ],
127
+ };
128
+ });
129
+ // ---- memora_ask_with_memory ----
130
+ registeredToolNames.push('memora_ask_with_memory');
131
+ registerToolWithWorklog(server, client, 'memora_ask_with_memory', 'Ask MemoraOne with project memory context', askWithMemoryShape, async (args) => {
132
+ const result = await handleAskWithMemory(client, args);
133
+ return {
134
+ content: [
135
+ {
136
+ type: 'text',
137
+ text: JSON.stringify(result),
138
+ },
139
+ ],
140
+ };
141
+ });
142
+ // ---- memora_log_intent ----
143
+ registeredToolNames.push('memora_log_intent');
144
+ registerToolWithWorklog(server, client, 'memora_log_intent', 'Log a natural-language TASK or DECISION intent to the MemoraOne timeline', logIntentShape, async (args) => {
145
+ const result = await handleLogIntent(client, args);
146
+ return {
147
+ content: [
148
+ {
149
+ type: 'text',
150
+ text: JSON.stringify(result),
151
+ },
152
+ ],
153
+ };
154
+ });
155
+ // ---- memora_log_change_summary ----
156
+ registeredToolNames.push('memora_log_change_summary');
157
+ registerToolWithWorklog(server, client, 'memora_log_change_summary', 'Log a concise code change summary to the MemoraOne timeline', logChangeSummaryShape, async (args) => {
158
+ const result = await handleLogChangeSummary(client, args);
159
+ return {
160
+ content: [
161
+ {
162
+ type: 'text',
163
+ text: JSON.stringify(result),
164
+ },
165
+ ],
166
+ };
167
+ });
168
+ // ---- memora_log_tool_result ----
169
+ registeredToolNames.push('memora_log_tool_result');
170
+ registerToolWithWorklog(server, client, 'memora_log_tool_result', 'Log a tool execution result to the MemoraOne timeline', logToolResultShape, async (args) => {
171
+ const result = await handleLogToolResult(client, args);
172
+ return {
173
+ content: [
174
+ {
175
+ type: 'text',
176
+ text: JSON.stringify(result),
177
+ },
178
+ ],
179
+ };
180
+ });
181
+ // ---- memora_log_command ----
182
+ registeredToolNames.push('memora_log_command');
183
+ registerToolWithWorklog(server, client, 'memora_log_command', 'Log a command execution to the MemoraOne timeline', logCommandShape, async (args) => {
184
+ const result = await handleLogCommand(client, args);
185
+ return {
186
+ content: [
187
+ {
188
+ type: 'text',
189
+ text: JSON.stringify(result),
190
+ },
191
+ ],
192
+ };
193
+ });
194
+ console.error('[memoraone-vscode-mcp] Starting MCP server with', registeredToolNames.length, 'tools');
195
+ const transport = new StdioServerTransport();
196
+ await server.connect(transport);
197
+ let heartbeatInterval = null;
198
+ if (config.heartbeatEnabled) {
199
+ if (globalThis.__memoraHeartbeatInterval) {
200
+ clearInterval(globalThis.__memoraHeartbeatInterval);
201
+ }
202
+ await sendHeartbeat(client);
203
+ const intervalMs = Number.isFinite(config.heartbeatIntervalMs)
204
+ ? Math.max(1000, config.heartbeatIntervalMs)
205
+ : 30000;
206
+ heartbeatInterval = setInterval(() => {
207
+ sendHeartbeat(client).catch(() => {
208
+ // Errors already logged in sendHeartbeat
209
+ });
210
+ }, intervalMs);
211
+ globalThis.__memoraHeartbeatInterval = heartbeatInterval;
212
+ }
213
+ const shutdown = (signal) => {
214
+ if (heartbeatInterval) {
215
+ clearInterval(heartbeatInterval);
216
+ }
217
+ if (globalThis.__memoraHeartbeatInterval) {
218
+ clearInterval(globalThis.__memoraHeartbeatInterval);
219
+ }
220
+ console.error(`[memoraone-vscode-mcp] Received ${signal}, shutting down`);
221
+ process.exit(0);
222
+ };
223
+ process.on('SIGINT', () => shutdown('SIGINT'));
224
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
225
+ console.error('[memoraone-vscode-mcp] MCP server ready');
226
+ }
227
+ main().catch((err) => {
228
+ console.error('[memoraone-vscode-mcp] fatal error', err);
229
+ process.exit(1);
230
+ });
@@ -0,0 +1,18 @@
1
+ // packages/mcp/src/runContext.ts
2
+ import { randomUUID } from 'crypto';
3
+ let currentRunId = null;
4
+ export function getCurrentRunId() {
5
+ return currentRunId;
6
+ }
7
+ export function setCurrentRunId(id) {
8
+ currentRunId = id;
9
+ }
10
+ export function resolveRunId(passed) {
11
+ if (passed) {
12
+ return passed;
13
+ }
14
+ return currentRunId;
15
+ }
16
+ export function generateRunId() {
17
+ return randomUUID();
18
+ }
@@ -0,0 +1,12 @@
1
+ //memoraone/adapters/vscode-mcp/src/tools/askWithMemory.ts
2
+ import { z } from 'zod/v4';
3
+ export const askWithMemoryShape = {
4
+ question: z.string().min(1),
5
+ code_context: z
6
+ .object({
7
+ file_path: z.string().optional(),
8
+ selected_text: z.string().optional(),
9
+ language: z.string().optional(),
10
+ })
11
+ .optional(),
12
+ };
@@ -0,0 +1,38 @@
1
+ //memoraone/adapters/vscode-mcp/src/tools/handlers/askWithMemory.ts
2
+ import { z } from 'zod/v4';
3
+ const askWithMemoryInputSchema = z.object({
4
+ question: z.string().min(1),
5
+ code_context: z
6
+ .object({
7
+ file_path: z.string().optional(),
8
+ selected_text: z.string().optional(),
9
+ language: z.string().optional(),
10
+ })
11
+ .optional(),
12
+ });
13
+ function isAskWithMemoryResponse(value) {
14
+ return (typeof value === 'object' &&
15
+ value !== null &&
16
+ typeof value.answer === 'string' &&
17
+ value.answer !== '');
18
+ }
19
+ export async function handleAskWithMemory(client, args) {
20
+ const parsed = askWithMemoryInputSchema.parse(args ?? {});
21
+ const payload = {
22
+ question: parsed.question,
23
+ };
24
+ if (parsed.code_context) {
25
+ payload.code_context = parsed.code_context;
26
+ }
27
+ const res = await client.post('/agent/ask-with-memory', payload);
28
+ if (!isAskWithMemoryResponse(res)) {
29
+ const err = new Error('Unexpected response from MemoraOne');
30
+ err.status = 502;
31
+ err.body = res;
32
+ throw err;
33
+ }
34
+ return {
35
+ answer: res.answer,
36
+ used: res.used ?? {},
37
+ };
38
+ }
@@ -0,0 +1,44 @@
1
+ //packages/mcp/src/tools/handlers/logChangeSummary.ts
2
+ import { z } from 'zod/v4';
3
+ import { resolveRunId } from '../../runContext.js';
4
+ import { config } from '../../config.js';
5
+ const logChangeSummaryInputSchema = z.object({
6
+ summary: z.string().min(1),
7
+ scope: z.string().min(1).optional(),
8
+ files: z.array(z.string().min(1)).optional(),
9
+ stats: z
10
+ .object({
11
+ files: z.number().int().nonnegative().optional(),
12
+ add: z.number().int().nonnegative().optional(),
13
+ del: z.number().int().nonnegative().optional(),
14
+ })
15
+ .optional(),
16
+ commit: z.string().min(1).optional(),
17
+ run_id: z.string().min(1).optional(),
18
+ });
19
+ export async function handleLogChangeSummary(client, args) {
20
+ const parsed = logChangeSummaryInputSchema.parse(args ?? {});
21
+ const { summary, scope, files, stats, commit } = parsed;
22
+ const message = summary.startsWith('CHANGE:')
23
+ ? summary
24
+ : `CHANGE: ${scope ?? 'code'} — ${summary}`;
25
+ const run_id = resolveRunId(parsed.run_id);
26
+ const body = {
27
+ kind: 'note',
28
+ concept: 'concept:change_summary',
29
+ actor: { type: config.agentType, name: config.agentName },
30
+ message,
31
+ metadata: {
32
+ source: config.source,
33
+ purpose: 'change_summary',
34
+ tool: 'memora_log_change_summary',
35
+ ...(scope ? { scope } : {}),
36
+ ...(files ? { files } : {}),
37
+ ...(stats ? { stats } : {}),
38
+ ...(commit ? { commit } : {}),
39
+ ...(run_id ? { run_id } : {}),
40
+ },
41
+ };
42
+ await client.post('/timeline/events', body);
43
+ return { ok: true };
44
+ }
@@ -0,0 +1,40 @@
1
+ //packages/mcp/src/tools/handlers/logCommand.ts
2
+ import { z } from 'zod/v4';
3
+ import { resolveRunId } from '../../runContext.js';
4
+ import { config } from '../../config.js';
5
+ const logCommandInputSchema = z.object({
6
+ cmd: z.string().min(1),
7
+ summary: z.string().min(1),
8
+ cwd: z.string().min(1).optional(),
9
+ exit_code: z.number().int().optional(),
10
+ duration_ms: z.number().int().nonnegative().optional(),
11
+ run_id: z.string().min(1).optional(),
12
+ stats: z.record(z.string(), z.any()).optional(),
13
+ });
14
+ export async function handleLogCommand(client, args) {
15
+ const parsed = logCommandInputSchema.parse(args ?? {});
16
+ const { cmd, summary, cwd, exit_code, duration_ms, stats } = parsed;
17
+ const message = summary.startsWith('COMMAND:')
18
+ ? summary
19
+ : `COMMAND: ${cmd} — ${summary}`;
20
+ const run_id = resolveRunId(parsed.run_id);
21
+ const body = {
22
+ kind: 'note',
23
+ concept: 'concept:command',
24
+ actor: { type: config.agentType, name: config.agentName },
25
+ message,
26
+ metadata: {
27
+ source: config.source,
28
+ purpose: 'command',
29
+ tool: 'memora_log_command',
30
+ cmd,
31
+ ...(cwd ? { cwd } : {}),
32
+ ...(exit_code !== undefined ? { exit_code } : {}),
33
+ ...(duration_ms !== undefined ? { duration_ms } : {}),
34
+ ...(run_id ? { run_id } : {}),
35
+ ...(stats ? { stats } : {}),
36
+ },
37
+ };
38
+ await client.post('/timeline/events', body);
39
+ return { ok: true };
40
+ }
@@ -0,0 +1,42 @@
1
+ //packages/mcp/src/tools/handlers/logIntent.ts
2
+ import { z } from 'zod/v4';
3
+ import { generateRunId, setCurrentRunId } from '../../runContext.js';
4
+ import { config } from '../../config.js';
5
+ const logIntentInputSchema = z.object({
6
+ intent: z.enum(['task', 'decision']),
7
+ message: z.string().min(1),
8
+ context: z.record(z.string(), z.any()).optional(),
9
+ intent_source: z.string().optional().default('cursor_chat'),
10
+ run_id: z.string().min(1).optional(),
11
+ });
12
+ export async function handleLogIntent(client, args) {
13
+ const parsed = logIntentInputSchema.parse(args ?? {});
14
+ const intent = parsed.intent;
15
+ const message = parsed.message.trim();
16
+ if (!message) {
17
+ throw new Error('message cannot be empty after trimming');
18
+ }
19
+ const intent_source = parsed.intent_source ?? 'cursor_chat';
20
+ const context = parsed.context;
21
+ const purpose = intent; // 'task' | 'decision'
22
+ const concept = purpose === 'task' ? 'concept:task' : 'concept:decision';
23
+ // Determine run_id: use provided or generate new one
24
+ const run_id = parsed.run_id ?? generateRunId();
25
+ // Set current run_id when purpose is task or decision
26
+ setCurrentRunId(run_id);
27
+ const body = {
28
+ kind: 'note',
29
+ actor: { type: config.agentType, name: config.agentName },
30
+ concept, // MUST be TOP-LEVEL so it populates timeline_events.concept
31
+ message,
32
+ metadata: {
33
+ source: config.source,
34
+ purpose, // 'task' | 'decision'
35
+ intent_source: intent_source ?? 'cursor_chat',
36
+ run_id,
37
+ ...(context ? { context } : {}),
38
+ },
39
+ };
40
+ await client.post('/timeline/events', body);
41
+ return { ok: true, run_id };
42
+ }
@@ -0,0 +1,44 @@
1
+ //packages/mcp/src/tools/handlers/logToolResult.ts
2
+ import { z } from 'zod/v4';
3
+ import { resolveRunId } from '../../runContext.js';
4
+ import { config } from '../../config.js';
5
+ const logToolResultInputSchema = z.object({
6
+ tool: z.string().min(1),
7
+ status: z.enum(['ok', 'error', 'partial']),
8
+ summary: z.string().min(1),
9
+ run_id: z.string().min(1).optional(),
10
+ duration_ms: z.number().int().nonnegative().optional(),
11
+ error_code: z.string().min(1).optional(),
12
+ error_message: z.string().min(1).optional(),
13
+ error_kind: z.enum(['infra', 'logic', 'auth', 'rate_limit', 'validation', 'unknown']).optional(),
14
+ stats: z.record(z.string(), z.any()).optional(),
15
+ });
16
+ export async function handleLogToolResult(client, args) {
17
+ const parsed = logToolResultInputSchema.parse(args ?? {});
18
+ const { tool, status, summary, duration_ms, error_code, error_message, error_kind, stats } = parsed;
19
+ const message = summary.startsWith('RESULT:')
20
+ ? summary
21
+ : `RESULT: ${tool} — ${status} — ${summary}`;
22
+ const run_id = resolveRunId(parsed.run_id);
23
+ const body = {
24
+ kind: 'note',
25
+ concept: 'concept:tool_result',
26
+ actor: { type: config.agentType, name: config.agentName },
27
+ message,
28
+ metadata: {
29
+ source: config.source,
30
+ purpose: 'tool_result',
31
+ tool: 'memora_log_tool_result',
32
+ tool_name: tool,
33
+ status,
34
+ ...(run_id ? { run_id } : {}),
35
+ ...(duration_ms ? { duration_ms } : {}),
36
+ ...(error_code ? { error_code } : {}),
37
+ ...(error_message ? { error_message } : {}),
38
+ ...(error_kind ? { error_kind } : {}),
39
+ ...(stats ? { stats } : {}),
40
+ },
41
+ };
42
+ await client.post('/timeline/events', body);
43
+ return { ok: true };
44
+ }
@@ -0,0 +1,23 @@
1
+ //packages/mcp/src/tools/handlers/postEvent.ts
2
+ import { z } from 'zod/v4';
3
+ import { config } from '../../config.js';
4
+ const postEventInputSchema = z.object({
5
+ kind: z.string().min(1),
6
+ actor: z.object({ identifier: z.string().min(1) }),
7
+ content: z.record(z.string(), z.any()),
8
+ metadata: z.record(z.string(), z.any()).optional(),
9
+ });
10
+ export async function handlePostEvent(client, args) {
11
+ const parsed = postEventInputSchema.parse(args ?? {});
12
+ const body = {
13
+ kind: parsed.kind,
14
+ actor: { type: config.agentType, identifier: parsed.actor.identifier },
15
+ content: parsed.content,
16
+ metadata: {
17
+ source: config.source,
18
+ ...parsed.metadata,
19
+ },
20
+ };
21
+ await client.post('/timeline/events', body);
22
+ return { ok: true, forwarded: true };
23
+ }
@@ -0,0 +1,16 @@
1
+ //memoraone/adapters/vscode-mcp/src/tools/logChangeSummary.ts
2
+ import { z } from 'zod/v4';
3
+ export const logChangeSummaryShape = {
4
+ summary: z.string().min(1),
5
+ scope: z.string().min(1).optional(),
6
+ files: z.array(z.string().min(1)).optional(),
7
+ stats: z
8
+ .object({
9
+ files: z.number().int().nonnegative().optional(),
10
+ add: z.number().int().nonnegative().optional(),
11
+ del: z.number().int().nonnegative().optional(),
12
+ })
13
+ .optional(),
14
+ commit: z.string().min(1).optional(),
15
+ run_id: z.string().min(1).optional(),
16
+ };
@@ -0,0 +1,11 @@
1
+ //memoraone/adapters/vscode-mcp/src/tools/logCommand.ts
2
+ import { z } from 'zod/v4';
3
+ export const logCommandShape = {
4
+ cmd: z.string().min(1),
5
+ summary: z.string().min(1),
6
+ cwd: z.string().min(1).optional(),
7
+ exit_code: z.number().int().optional(),
8
+ duration_ms: z.number().int().nonnegative().optional(),
9
+ run_id: z.string().min(1).optional(),
10
+ stats: z.record(z.string(), z.any()).optional(),
11
+ };
@@ -0,0 +1,9 @@
1
+ //memoraone/adapters/vscode-mcp/src/tools/logIntent.ts
2
+ import { z } from 'zod/v4';
3
+ export const logIntentShape = {
4
+ intent: z.enum(['task', 'decision']),
5
+ message: z.string().min(1),
6
+ context: z.record(z.string(), z.any()).optional(),
7
+ intent_source: z.string().optional().default('cursor_chat'),
8
+ run_id: z.string().min(1).optional(),
9
+ };
@@ -0,0 +1,13 @@
1
+ //memoraone/adapters/vscode-mcp/src/tools/logToolResult.ts
2
+ import { z } from 'zod/v4';
3
+ export const logToolResultShape = {
4
+ tool: z.string().min(1),
5
+ status: z.enum(['ok', 'error', 'partial']),
6
+ summary: z.string().min(1),
7
+ run_id: z.string().min(1).optional(),
8
+ duration_ms: z.number().int().nonnegative().optional(),
9
+ error_code: z.string().min(1).optional(),
10
+ error_message: z.string().min(1).optional(),
11
+ error_kind: z.enum(['infra', 'logic', 'auth', 'rate_limit', 'validation', 'unknown']).optional(),
12
+ stats: z.record(z.string(), z.any()).optional(),
13
+ };
@@ -0,0 +1,8 @@
1
+ //memoraone/adapters/vscode-mcp/src/tools/postEvent.ts
2
+ import { z } from 'zod/v4';
3
+ export const postEventShape = {
4
+ kind: z.string().min(1),
5
+ actor: z.object({ identifier: z.string().min(1) }),
6
+ content: z.record(z.string(), z.any()),
7
+ metadata: z.record(z.string(), z.any()).optional(),
8
+ };
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@memoraone/mcp",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+
6
+ "bin": {
7
+ "memoraone-mcp": "./dist/cli.js"
8
+ },
9
+
10
+ "files": [
11
+ "dist",
12
+ "package.json",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+
20
+ "scripts": {
21
+ "build": "tsc -p tsconfig.json",
22
+ "dev": "tsx src/index.ts",
23
+ "lint": "eslint ."
24
+ },
25
+
26
+ "dependencies": {
27
+ "@modelcontextprotocol/sdk": "^1.25.1",
28
+ "dotenv": "^16.4.5",
29
+ "undici": "^7.16.0",
30
+ "zod": "^4.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "typescript": "^5.9.2"
34
+ }
35
+ }