@kalera/munin-runtime 1.2.0 → 1.2.5
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/.turbo/turbo-build.log +1 -1
- package/dist/env.d.ts +14 -0
- package/dist/env.js +34 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +44 -1
- package/dist/mcp-server.d.ts +36 -0
- package/dist/mcp-server.js +70 -16
- package/package.json +2 -2
- package/src/env.ts +48 -0
- package/src/index.ts +46 -1
- package/src/mcp-server.ts +76 -18
package/.turbo/turbo-build.log
CHANGED
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface EnvVar {
|
|
2
|
+
key: string;
|
|
3
|
+
value: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the encryption key for E2EE projects from MUNIN_ENCRYPTION_KEY env var.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveEncryptionKey(): string | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* Upsert key=value pairs into a .env file.
|
|
11
|
+
* - Existing keys are replaced.
|
|
12
|
+
* - New keys are appended.
|
|
13
|
+
*/
|
|
14
|
+
export declare function writeEnvFile(filename: string, vars: EnvVar[], cwd?: string): void;
|
package/dist/env.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the encryption key for E2EE projects from MUNIN_ENCRYPTION_KEY env var.
|
|
5
|
+
*/
|
|
6
|
+
export function resolveEncryptionKey() {
|
|
7
|
+
return process.env.MUNIN_ENCRYPTION_KEY;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Upsert key=value pairs into a .env file.
|
|
11
|
+
* - Existing keys are replaced.
|
|
12
|
+
* - New keys are appended.
|
|
13
|
+
*/
|
|
14
|
+
export function writeEnvFile(filename, vars, cwd = process.cwd()) {
|
|
15
|
+
const filePath = path.resolve(cwd, filename);
|
|
16
|
+
let lines = [];
|
|
17
|
+
if (fs.existsSync(filePath)) {
|
|
18
|
+
lines = fs.readFileSync(filePath, "utf8").split("\n");
|
|
19
|
+
}
|
|
20
|
+
for (const { key, value } of vars) {
|
|
21
|
+
const escapedValue = value.replace(/"/g, '\\"');
|
|
22
|
+
const newLine = `${key}="${escapedValue}"`;
|
|
23
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
24
|
+
const regex = new RegExp(`^${escapedKey}\\s*=.+$`, "m");
|
|
25
|
+
const idx = lines.findIndex((l) => regex.test(l));
|
|
26
|
+
if (idx !== -1) {
|
|
27
|
+
lines[idx] = newLine;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
lines.push(newLine);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
fs.writeFileSync(filePath, lines.join("\n"), "utf8");
|
|
34
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,14 @@ export interface CliEnv {
|
|
|
11
11
|
}
|
|
12
12
|
export declare function parseCliArgs(argv: string[], usage: string): ParsedCliArgs;
|
|
13
13
|
export declare function loadCliEnv(): CliEnv;
|
|
14
|
+
/**
|
|
15
|
+
* Resolve MUNIN_PROJECT from multiple sources, in priority order:
|
|
16
|
+
* 1. Explicit environment variable
|
|
17
|
+
* 2. .env.local in current working directory
|
|
18
|
+
* 3. .env in current working directory
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveProjectId(): string | undefined;
|
|
14
21
|
export declare function executeWithRetry<T>(task: () => Promise<T>, retries: number, backoffMs: number): Promise<T>;
|
|
15
22
|
export declare function safeError(error: unknown): Record<string, unknown>;
|
|
16
23
|
export * from "./mcp-server.js";
|
|
24
|
+
export * from "./env.js";
|
package/dist/index.js
CHANGED
|
@@ -19,13 +19,55 @@ export function parseCliArgs(argv, usage) {
|
|
|
19
19
|
}
|
|
20
20
|
export function loadCliEnv() {
|
|
21
21
|
return {
|
|
22
|
-
baseUrl: "https://munin.kalera.dev",
|
|
22
|
+
baseUrl: process.env.MUNIN_BASE_URL || "https://munin.kalera.dev",
|
|
23
23
|
apiKey: process.env.MUNIN_API_KEY,
|
|
24
24
|
timeoutMs: Number(process.env.MUNIN_TIMEOUT_MS ?? 15000),
|
|
25
25
|
retries: Number(process.env.MUNIN_RETRIES ?? 3),
|
|
26
26
|
backoffMs: Number(process.env.MUNIN_BACKOFF_MS ?? 300),
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Resolve MUNIN_PROJECT from multiple sources, in priority order:
|
|
31
|
+
* 1. Explicit environment variable
|
|
32
|
+
* 2. .env.local in current working directory
|
|
33
|
+
* 3. .env in current working directory
|
|
34
|
+
*/
|
|
35
|
+
export function resolveProjectId() {
|
|
36
|
+
// 1. Explicit env var (highest priority)
|
|
37
|
+
if (process.env.MUNIN_PROJECT) {
|
|
38
|
+
return process.env.MUNIN_PROJECT;
|
|
39
|
+
}
|
|
40
|
+
// 2. .env.local in CWD
|
|
41
|
+
const envLocal = resolveEnvFile(".env.local");
|
|
42
|
+
if (envLocal)
|
|
43
|
+
return envLocal;
|
|
44
|
+
// 3. .env in CWD
|
|
45
|
+
const envFile = resolveEnvFile(".env");
|
|
46
|
+
if (envFile)
|
|
47
|
+
return envFile;
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Read a .env file and extract MUNIN_PROJECT value.
|
|
52
|
+
* Returns undefined if file doesn't exist or value not found.
|
|
53
|
+
*/
|
|
54
|
+
function resolveEnvFile(filename) {
|
|
55
|
+
try {
|
|
56
|
+
const path = `${process.cwd()}/${filename}`;
|
|
57
|
+
const fs = require("fs");
|
|
58
|
+
if (!fs.existsSync(path))
|
|
59
|
+
return undefined;
|
|
60
|
+
const content = fs.readFileSync(path, "utf8");
|
|
61
|
+
const match = content.match(/^MUNIN_PROJECT\s*=\s*(.+)$/m);
|
|
62
|
+
if (match) {
|
|
63
|
+
return match[1].trim();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Ignore errors — file might be unreadable
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
29
71
|
export async function executeWithRetry(task, retries, backoffMs) {
|
|
30
72
|
let attempt = 0;
|
|
31
73
|
let lastError;
|
|
@@ -64,3 +106,4 @@ function sleep(ms) {
|
|
|
64
106
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
65
107
|
}
|
|
66
108
|
export * from "./mcp-server.js";
|
|
109
|
+
export * from "./env.js";
|
package/dist/mcp-server.d.ts
CHANGED
|
@@ -1 +1,37 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { loadCliEnv } from "./index.js";
|
|
3
|
+
export declare function createMcpServerInstance(env: ReturnType<typeof loadCliEnv>): Server<{
|
|
4
|
+
method: string;
|
|
5
|
+
params?: {
|
|
6
|
+
[x: string]: unknown;
|
|
7
|
+
_meta?: {
|
|
8
|
+
[x: string]: unknown;
|
|
9
|
+
progressToken?: string | number | undefined;
|
|
10
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
11
|
+
taskId: string;
|
|
12
|
+
} | undefined;
|
|
13
|
+
} | undefined;
|
|
14
|
+
} | undefined;
|
|
15
|
+
}, {
|
|
16
|
+
method: string;
|
|
17
|
+
params?: {
|
|
18
|
+
[x: string]: unknown;
|
|
19
|
+
_meta?: {
|
|
20
|
+
[x: string]: unknown;
|
|
21
|
+
progressToken?: string | number | undefined;
|
|
22
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
23
|
+
taskId: string;
|
|
24
|
+
} | undefined;
|
|
25
|
+
} | undefined;
|
|
26
|
+
} | undefined;
|
|
27
|
+
}, {
|
|
28
|
+
[x: string]: unknown;
|
|
29
|
+
_meta?: {
|
|
30
|
+
[x: string]: unknown;
|
|
31
|
+
progressToken?: string | number | undefined;
|
|
32
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
33
|
+
taskId: string;
|
|
34
|
+
} | undefined;
|
|
35
|
+
} | undefined;
|
|
36
|
+
}>;
|
|
1
37
|
export declare function startMcpServer(): Promise<void>;
|
package/dist/mcp-server.js
CHANGED
|
@@ -2,12 +2,11 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
3
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
4
|
import { MuninClient } from "@kalera/munin-sdk";
|
|
5
|
-
import { loadCliEnv, safeError } from "./index.js";
|
|
6
|
-
export
|
|
7
|
-
const env = loadCliEnv();
|
|
5
|
+
import { loadCliEnv, resolveProjectId, resolveEncryptionKey, safeError } from "./index.js";
|
|
6
|
+
export function createMcpServerInstance(env) {
|
|
8
7
|
const client = new MuninClient({
|
|
9
8
|
baseUrl: env.baseUrl,
|
|
10
|
-
apiKey: env.apiKey,
|
|
9
|
+
apiKey: env.apiKey || 'test-key',
|
|
11
10
|
timeoutMs: env.timeoutMs,
|
|
12
11
|
});
|
|
13
12
|
const server = new Server({
|
|
@@ -54,27 +53,29 @@ export async function startMcpServer() {
|
|
|
54
53
|
},
|
|
55
54
|
{
|
|
56
55
|
name: "munin_search_memories",
|
|
57
|
-
description: "Search for memories using semantic search or keywords. Returns formatted, token-efficient GraphRAG context. IMPORTANT: Call this as an MCP tool, NOT as a shell command.",
|
|
56
|
+
description: "Search for memories using semantic search or keywords. Returns formatted, token-efficient GraphRAG context. Supports pagination with topK/offset and optional total count. IMPORTANT: Call this as an MCP tool, NOT as a shell command.",
|
|
58
57
|
inputSchema: {
|
|
59
58
|
type: "object",
|
|
60
59
|
properties: {
|
|
61
60
|
projectId: { type: "string", description: "Optional. The Munin Context Core ID." },
|
|
62
61
|
query: { type: "string", description: "Search query" },
|
|
63
62
|
tags: { type: "array", items: { type: "string" } },
|
|
64
|
-
|
|
63
|
+
topK: { type: "number", description: "Max results to return (default: 10, max: 50)" },
|
|
64
|
+
offset: { type: "number", description: "Pagination offset for fetching more results (default: 0)" },
|
|
65
|
+
includeTotal: { type: "boolean", description: "If true, includes total count in response (default: false)" },
|
|
65
66
|
},
|
|
66
67
|
required: ["query"],
|
|
67
68
|
},
|
|
68
69
|
},
|
|
69
70
|
{
|
|
70
71
|
name: "munin_list_memories",
|
|
71
|
-
description: "List all memories with pagination. IMPORTANT: Call this as an MCP tool, NOT as a shell command.",
|
|
72
|
+
description: "List all memories with pagination support. IMPORTANT: Call this as an MCP tool, NOT as a shell command.",
|
|
72
73
|
inputSchema: {
|
|
73
74
|
type: "object",
|
|
74
75
|
properties: {
|
|
75
76
|
projectId: { type: "string", description: "Optional. The Munin Context Core ID." },
|
|
76
|
-
limit: { type: "number" },
|
|
77
|
-
offset: { type: "number" },
|
|
77
|
+
limit: { type: "number", description: "Max results to return (default: 10, max: 100)" },
|
|
78
|
+
offset: { type: "number", description: "Pagination offset (default: 0)" },
|
|
78
79
|
},
|
|
79
80
|
required: [],
|
|
80
81
|
},
|
|
@@ -91,35 +92,83 @@ export async function startMcpServer() {
|
|
|
91
92
|
required: [],
|
|
92
93
|
},
|
|
93
94
|
},
|
|
95
|
+
{
|
|
96
|
+
name: "munin_share_memory",
|
|
97
|
+
description: "Share one or more memories to other projects owned by the same user. The target project must share the same Hash Key to read encrypted content. Requires Pro or Elite tier. IMPORTANT: Call this as an MCP tool, NOT as a shell command.",
|
|
98
|
+
inputSchema: {
|
|
99
|
+
type: "object",
|
|
100
|
+
properties: {
|
|
101
|
+
projectId: { type: "string", description: "Optional. The source project ID." },
|
|
102
|
+
memoryIds: {
|
|
103
|
+
type: "array",
|
|
104
|
+
items: { type: "string" },
|
|
105
|
+
description: "Array of memory IDs to share",
|
|
106
|
+
},
|
|
107
|
+
targetProjectIds: {
|
|
108
|
+
type: "array",
|
|
109
|
+
items: { type: "string" },
|
|
110
|
+
description: "Array of target project IDs to share memories into",
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
required: ["memoryIds", "targetProjectIds"],
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: "munin_get_project_info",
|
|
118
|
+
description: "Get current project metadata including E2EE status, tier features, and limits. CRITICAL: Before storing or retrieving memories in an E2EE project, verify the encryption key is set correctly. Shows whether MUNIN_ENCRYPTION_KEY is configured. IMPORTANT: Call this as an MCP tool, NOT as a shell command.",
|
|
119
|
+
inputSchema: {
|
|
120
|
+
type: "object",
|
|
121
|
+
properties: {
|
|
122
|
+
projectId: { type: "string", description: "Optional. Defaults to active project." },
|
|
123
|
+
},
|
|
124
|
+
required: [],
|
|
125
|
+
},
|
|
126
|
+
},
|
|
94
127
|
],
|
|
95
128
|
};
|
|
96
129
|
});
|
|
97
130
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
98
131
|
try {
|
|
99
132
|
const args = request.params.arguments || {};
|
|
100
|
-
|
|
133
|
+
// Priority: explicit arg > env var > CWD .env file
|
|
134
|
+
const projectId = args.projectId || resolveProjectId();
|
|
101
135
|
if (!projectId) {
|
|
102
|
-
throw new Error("projectId is required in
|
|
136
|
+
throw new Error("projectId is required. Ensure MUNIN_PROJECT is set in .env or .env.local in your project directory, or passed as an argument.");
|
|
103
137
|
}
|
|
104
138
|
// Remove projectId from args before sending as payload
|
|
105
139
|
const { projectId: _ignored, ...payload } = args;
|
|
140
|
+
// Auto-inject encryptionKey from env if not explicitly provided
|
|
141
|
+
const encryptionKey = args.encryptionKey || resolveEncryptionKey();
|
|
142
|
+
const enrichedPayload = encryptionKey ? { ...payload, encryptionKey } : payload;
|
|
106
143
|
let result;
|
|
107
144
|
switch (request.params.name) {
|
|
108
145
|
case "munin_store_memory":
|
|
109
|
-
result = await client.store(projectId,
|
|
146
|
+
result = await client.store(projectId, enrichedPayload);
|
|
110
147
|
break;
|
|
111
148
|
case "munin_retrieve_memory":
|
|
112
|
-
result = await client.retrieve(projectId,
|
|
149
|
+
result = await client.retrieve(projectId, enrichedPayload);
|
|
113
150
|
break;
|
|
114
151
|
case "munin_search_memories":
|
|
115
|
-
result = await client.search(projectId,
|
|
152
|
+
result = await client.search(projectId, enrichedPayload);
|
|
116
153
|
break;
|
|
117
154
|
case "munin_list_memories":
|
|
118
|
-
result = await client.list(projectId,
|
|
155
|
+
result = await client.list(projectId, enrichedPayload);
|
|
119
156
|
break;
|
|
120
157
|
case "munin_recent_memories":
|
|
121
|
-
result = await client.recent(projectId,
|
|
158
|
+
result = await client.recent(projectId, enrichedPayload);
|
|
159
|
+
break;
|
|
160
|
+
case "munin_share_memory":
|
|
161
|
+
result = await client.invoke(projectId, "share", enrichedPayload);
|
|
162
|
+
break;
|
|
163
|
+
case "munin_get_project_info": {
|
|
164
|
+
const caps = await client.capabilities();
|
|
165
|
+
result = {
|
|
166
|
+
ok: true,
|
|
167
|
+
encryptionKeyConfigured: !!encryptionKey,
|
|
168
|
+
...caps,
|
|
169
|
+
};
|
|
122
170
|
break;
|
|
171
|
+
}
|
|
123
172
|
default:
|
|
124
173
|
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
125
174
|
}
|
|
@@ -145,6 +194,11 @@ export async function startMcpServer() {
|
|
|
145
194
|
};
|
|
146
195
|
}
|
|
147
196
|
});
|
|
197
|
+
return server;
|
|
198
|
+
}
|
|
199
|
+
export async function startMcpServer() {
|
|
200
|
+
const env = loadCliEnv();
|
|
201
|
+
const server = createMcpServerInstance(env);
|
|
148
202
|
const transport = new StdioServerTransport();
|
|
149
203
|
await server.connect(transport);
|
|
150
204
|
console.error("Munin MCP Server running on stdio");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kalera/munin-runtime",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
15
|
-
"@kalera/munin-sdk": "1.2.
|
|
15
|
+
"@kalera/munin-sdk": "1.2.5"
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
18
|
"build": "tsc -p tsconfig.json",
|
package/src/env.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
|
|
4
|
+
export interface EnvVar {
|
|
5
|
+
key: string;
|
|
6
|
+
value: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the encryption key for E2EE projects from MUNIN_ENCRYPTION_KEY env var.
|
|
11
|
+
*/
|
|
12
|
+
export function resolveEncryptionKey(): string | undefined {
|
|
13
|
+
return process.env.MUNIN_ENCRYPTION_KEY;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Upsert key=value pairs into a .env file.
|
|
18
|
+
* - Existing keys are replaced.
|
|
19
|
+
* - New keys are appended.
|
|
20
|
+
*/
|
|
21
|
+
export function writeEnvFile(
|
|
22
|
+
filename: string,
|
|
23
|
+
vars: EnvVar[],
|
|
24
|
+
cwd: string = process.cwd(),
|
|
25
|
+
): void {
|
|
26
|
+
const filePath = path.resolve(cwd, filename);
|
|
27
|
+
let lines: string[] = [];
|
|
28
|
+
|
|
29
|
+
if (fs.existsSync(filePath)) {
|
|
30
|
+
lines = fs.readFileSync(filePath, "utf8").split("\n");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (const { key, value } of vars) {
|
|
34
|
+
const escapedValue = value.replace(/"/g, '\\"');
|
|
35
|
+
const newLine = `${key}="${escapedValue}"`;
|
|
36
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
37
|
+
const regex = new RegExp(`^${escapedKey}\\s*=.+$`, "m");
|
|
38
|
+
|
|
39
|
+
const idx = lines.findIndex((l) => regex.test(l));
|
|
40
|
+
if (idx !== -1) {
|
|
41
|
+
lines[idx] = newLine;
|
|
42
|
+
} else {
|
|
43
|
+
lines.push(newLine);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fs.writeFileSync(filePath, lines.join("\n"), "utf8");
|
|
48
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -35,7 +35,7 @@ export function parseCliArgs(argv: string[], usage: string): ParsedCliArgs {
|
|
|
35
35
|
|
|
36
36
|
export function loadCliEnv(): CliEnv {
|
|
37
37
|
return {
|
|
38
|
-
baseUrl: "https://munin.kalera.dev",
|
|
38
|
+
baseUrl: process.env.MUNIN_BASE_URL || "https://munin.kalera.dev",
|
|
39
39
|
apiKey: process.env.MUNIN_API_KEY,
|
|
40
40
|
timeoutMs: Number(process.env.MUNIN_TIMEOUT_MS ?? 15000),
|
|
41
41
|
retries: Number(process.env.MUNIN_RETRIES ?? 3),
|
|
@@ -43,6 +43,50 @@ export function loadCliEnv(): CliEnv {
|
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Resolve MUNIN_PROJECT from multiple sources, in priority order:
|
|
48
|
+
* 1. Explicit environment variable
|
|
49
|
+
* 2. .env.local in current working directory
|
|
50
|
+
* 3. .env in current working directory
|
|
51
|
+
*/
|
|
52
|
+
export function resolveProjectId(): string | undefined {
|
|
53
|
+
// 1. Explicit env var (highest priority)
|
|
54
|
+
if (process.env.MUNIN_PROJECT) {
|
|
55
|
+
return process.env.MUNIN_PROJECT;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 2. .env.local in CWD
|
|
59
|
+
const envLocal = resolveEnvFile(".env.local");
|
|
60
|
+
if (envLocal) return envLocal;
|
|
61
|
+
|
|
62
|
+
// 3. .env in CWD
|
|
63
|
+
const envFile = resolveEnvFile(".env");
|
|
64
|
+
if (envFile) return envFile;
|
|
65
|
+
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Read a .env file and extract MUNIN_PROJECT value.
|
|
71
|
+
* Returns undefined if file doesn't exist or value not found.
|
|
72
|
+
*/
|
|
73
|
+
function resolveEnvFile(filename: string): string | undefined {
|
|
74
|
+
try {
|
|
75
|
+
const path = `${process.cwd()}/${filename}`;
|
|
76
|
+
const fs = require("fs") as typeof import("fs");
|
|
77
|
+
if (!fs.existsSync(path)) return undefined;
|
|
78
|
+
|
|
79
|
+
const content = fs.readFileSync(path, "utf8");
|
|
80
|
+
const match = content.match(/^MUNIN_PROJECT\s*=\s*(.+)$/m);
|
|
81
|
+
if (match) {
|
|
82
|
+
return match[1].trim();
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
// Ignore errors — file might be unreadable
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
46
90
|
export async function executeWithRetry<T>(
|
|
47
91
|
task: () => Promise<T>,
|
|
48
92
|
retries: number,
|
|
@@ -90,3 +134,4 @@ function sleep(ms: number): Promise<void> {
|
|
|
90
134
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
91
135
|
}
|
|
92
136
|
export * from "./mcp-server.js";
|
|
137
|
+
export * from "./env.js";
|
package/src/mcp-server.ts
CHANGED
|
@@ -5,14 +5,12 @@ import {
|
|
|
5
5
|
ListToolsRequestSchema,
|
|
6
6
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
7
7
|
import { MuninClient } from "@kalera/munin-sdk";
|
|
8
|
-
import { loadCliEnv, safeError } from "./index.js";
|
|
9
|
-
|
|
10
|
-
export async function startMcpServer() {
|
|
11
|
-
const env = loadCliEnv();
|
|
8
|
+
import { loadCliEnv, resolveProjectId, resolveEncryptionKey, safeError } from "./index.js";
|
|
12
9
|
|
|
10
|
+
export function createMcpServerInstance(env: ReturnType<typeof loadCliEnv>) {
|
|
13
11
|
const client = new MuninClient({
|
|
14
12
|
baseUrl: env.baseUrl,
|
|
15
|
-
apiKey: env.apiKey,
|
|
13
|
+
apiKey: env.apiKey || 'test-key',
|
|
16
14
|
timeoutMs: env.timeoutMs,
|
|
17
15
|
});
|
|
18
16
|
|
|
@@ -64,27 +62,29 @@ export async function startMcpServer() {
|
|
|
64
62
|
},
|
|
65
63
|
{
|
|
66
64
|
name: "munin_search_memories",
|
|
67
|
-
description: "Search for memories using semantic search or keywords. Returns formatted, token-efficient GraphRAG context. IMPORTANT: Call this as an MCP tool, NOT as a shell command.",
|
|
65
|
+
description: "Search for memories using semantic search or keywords. Returns formatted, token-efficient GraphRAG context. Supports pagination with topK/offset and optional total count. IMPORTANT: Call this as an MCP tool, NOT as a shell command.",
|
|
68
66
|
inputSchema: {
|
|
69
67
|
type: "object",
|
|
70
68
|
properties: {
|
|
71
69
|
projectId: { type: "string", description: "Optional. The Munin Context Core ID." },
|
|
72
70
|
query: { type: "string", description: "Search query" },
|
|
73
71
|
tags: { type: "array", items: { type: "string" } },
|
|
74
|
-
|
|
72
|
+
topK: { type: "number", description: "Max results to return (default: 10, max: 50)" },
|
|
73
|
+
offset: { type: "number", description: "Pagination offset for fetching more results (default: 0)" },
|
|
74
|
+
includeTotal: { type: "boolean", description: "If true, includes total count in response (default: false)" },
|
|
75
75
|
},
|
|
76
76
|
required: ["query"],
|
|
77
77
|
},
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
name: "munin_list_memories",
|
|
81
|
-
description: "List all memories with pagination. IMPORTANT: Call this as an MCP tool, NOT as a shell command.",
|
|
81
|
+
description: "List all memories with pagination support. IMPORTANT: Call this as an MCP tool, NOT as a shell command.",
|
|
82
82
|
inputSchema: {
|
|
83
83
|
type: "object",
|
|
84
84
|
properties: {
|
|
85
85
|
projectId: { type: "string", description: "Optional. The Munin Context Core ID." },
|
|
86
|
-
limit: { type: "number" },
|
|
87
|
-
offset: { type: "number" },
|
|
86
|
+
limit: { type: "number", description: "Max results to return (default: 10, max: 100)" },
|
|
87
|
+
offset: { type: "number", description: "Pagination offset (default: 0)" },
|
|
88
88
|
},
|
|
89
89
|
required: [],
|
|
90
90
|
},
|
|
@@ -101,6 +101,38 @@ export async function startMcpServer() {
|
|
|
101
101
|
required: [],
|
|
102
102
|
},
|
|
103
103
|
},
|
|
104
|
+
{
|
|
105
|
+
name: "munin_share_memory",
|
|
106
|
+
description: "Share one or more memories to other projects owned by the same user. The target project must share the same Hash Key to read encrypted content. Requires Pro or Elite tier. IMPORTANT: Call this as an MCP tool, NOT as a shell command.",
|
|
107
|
+
inputSchema: {
|
|
108
|
+
type: "object",
|
|
109
|
+
properties: {
|
|
110
|
+
projectId: { type: "string", description: "Optional. The source project ID." },
|
|
111
|
+
memoryIds: {
|
|
112
|
+
type: "array",
|
|
113
|
+
items: { type: "string" },
|
|
114
|
+
description: "Array of memory IDs to share",
|
|
115
|
+
},
|
|
116
|
+
targetProjectIds: {
|
|
117
|
+
type: "array",
|
|
118
|
+
items: { type: "string" },
|
|
119
|
+
description: "Array of target project IDs to share memories into",
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
required: ["memoryIds", "targetProjectIds"],
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: "munin_get_project_info",
|
|
127
|
+
description: "Get current project metadata including E2EE status, tier features, and limits. CRITICAL: Before storing or retrieving memories in an E2EE project, verify the encryption key is set correctly. Shows whether MUNIN_ENCRYPTION_KEY is configured. IMPORTANT: Call this as an MCP tool, NOT as a shell command.",
|
|
128
|
+
inputSchema: {
|
|
129
|
+
type: "object",
|
|
130
|
+
properties: {
|
|
131
|
+
projectId: { type: "string", description: "Optional. Defaults to active project." },
|
|
132
|
+
},
|
|
133
|
+
required: [],
|
|
134
|
+
},
|
|
135
|
+
},
|
|
104
136
|
],
|
|
105
137
|
};
|
|
106
138
|
});
|
|
@@ -108,33 +140,52 @@ export async function startMcpServer() {
|
|
|
108
140
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
109
141
|
try {
|
|
110
142
|
const args = request.params.arguments || {};
|
|
111
|
-
|
|
112
|
-
|
|
143
|
+
// Priority: explicit arg > env var > CWD .env file
|
|
144
|
+
const projectId = (args.projectId as string) || resolveProjectId();
|
|
145
|
+
|
|
113
146
|
if (!projectId) {
|
|
114
|
-
throw new Error(
|
|
147
|
+
throw new Error(
|
|
148
|
+
"projectId is required. Ensure MUNIN_PROJECT is set in .env or .env.local in your project directory, or passed as an argument."
|
|
149
|
+
);
|
|
115
150
|
}
|
|
116
151
|
|
|
117
152
|
// Remove projectId from args before sending as payload
|
|
118
153
|
const { projectId: _ignored, ...payload } = args;
|
|
119
154
|
|
|
155
|
+
// Auto-inject encryptionKey from env if not explicitly provided
|
|
156
|
+
const encryptionKey = (args.encryptionKey as string) || resolveEncryptionKey();
|
|
157
|
+
const enrichedPayload = encryptionKey ? { ...payload, encryptionKey } : payload;
|
|
158
|
+
|
|
120
159
|
let result;
|
|
121
160
|
|
|
122
161
|
switch (request.params.name) {
|
|
123
162
|
case "munin_store_memory":
|
|
124
|
-
result = await client.store(projectId,
|
|
163
|
+
result = await client.store(projectId, enrichedPayload);
|
|
125
164
|
break;
|
|
126
165
|
case "munin_retrieve_memory":
|
|
127
|
-
result = await client.retrieve(projectId,
|
|
166
|
+
result = await client.retrieve(projectId, enrichedPayload);
|
|
128
167
|
break;
|
|
129
168
|
case "munin_search_memories":
|
|
130
|
-
result = await client.search(projectId,
|
|
169
|
+
result = await client.search(projectId, enrichedPayload);
|
|
131
170
|
break;
|
|
132
171
|
case "munin_list_memories":
|
|
133
|
-
result = await client.list(projectId,
|
|
172
|
+
result = await client.list(projectId, enrichedPayload);
|
|
134
173
|
break;
|
|
135
174
|
case "munin_recent_memories":
|
|
136
|
-
result = await client.recent(projectId,
|
|
175
|
+
result = await client.recent(projectId, enrichedPayload);
|
|
137
176
|
break;
|
|
177
|
+
case "munin_share_memory":
|
|
178
|
+
result = await client.invoke(projectId, "share", enrichedPayload);
|
|
179
|
+
break;
|
|
180
|
+
case "munin_get_project_info": {
|
|
181
|
+
const caps = await client.capabilities();
|
|
182
|
+
result = {
|
|
183
|
+
ok: true,
|
|
184
|
+
encryptionKeyConfigured: !!encryptionKey,
|
|
185
|
+
...caps,
|
|
186
|
+
};
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
138
189
|
default:
|
|
139
190
|
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
140
191
|
}
|
|
@@ -161,6 +212,13 @@ export async function startMcpServer() {
|
|
|
161
212
|
}
|
|
162
213
|
});
|
|
163
214
|
|
|
215
|
+
return server;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function startMcpServer() {
|
|
219
|
+
const env = loadCliEnv();
|
|
220
|
+
const server = createMcpServerInstance(env);
|
|
221
|
+
|
|
164
222
|
const transport = new StdioServerTransport();
|
|
165
223
|
await server.connect(transport);
|
|
166
224
|
console.error("Munin MCP Server running on stdio");
|