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