@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.
@@ -1,4 +1,4 @@
1
1
 
2
- > @kalera/munin-runtime@1.2.0 build /home/runner/work/munin-for-agents/munin-for-agents/packages/runtime
2
+ > @kalera/munin-runtime@1.2.5 build /home/runner/work/munin-for-agents/munin-for-agents/packages/runtime
3
3
  > tsc -p tsconfig.json
4
4
 
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";
@@ -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>;
@@ -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 async function startMcpServer() {
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
- limit: { type: "number", description: "Max results (default: 10)" },
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
- const projectId = args.projectId || process.env.MUNIN_PROJECT;
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 arguments or MUNIN_PROJECT environment variable");
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, payload);
146
+ result = await client.store(projectId, enrichedPayload);
110
147
  break;
111
148
  case "munin_retrieve_memory":
112
- result = await client.retrieve(projectId, payload);
149
+ result = await client.retrieve(projectId, enrichedPayload);
113
150
  break;
114
151
  case "munin_search_memories":
115
- result = await client.search(projectId, payload);
152
+ result = await client.search(projectId, enrichedPayload);
116
153
  break;
117
154
  case "munin_list_memories":
118
- result = await client.list(projectId, payload);
155
+ result = await client.list(projectId, enrichedPayload);
119
156
  break;
120
157
  case "munin_recent_memories":
121
- result = await client.recent(projectId, payload);
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.0",
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.0"
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
- limit: { type: "number", description: "Max results (default: 10)" },
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
- const projectId = (args.projectId as string) || process.env.MUNIN_PROJECT;
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("projectId is required in arguments or MUNIN_PROJECT environment variable");
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, payload);
163
+ result = await client.store(projectId, enrichedPayload);
125
164
  break;
126
165
  case "munin_retrieve_memory":
127
- result = await client.retrieve(projectId, payload);
166
+ result = await client.retrieve(projectId, enrichedPayload);
128
167
  break;
129
168
  case "munin_search_memories":
130
- result = await client.search(projectId, payload);
169
+ result = await client.search(projectId, enrichedPayload);
131
170
  break;
132
171
  case "munin_list_memories":
133
- result = await client.list(projectId, payload);
172
+ result = await client.list(projectId, enrichedPayload);
134
173
  break;
135
174
  case "munin_recent_memories":
136
- result = await client.recent(projectId, payload);
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");