@learningnodes/elen-mcp 0.1.2

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.
@@ -0,0 +1,107 @@
1
+ 
2
+  RUN  v2.1.9 C:/Users/ln_ni/OneDrive/Desktop/Desktop/ventures/learningnodes/git/marketplace-repos/Elen/packages/mcp-server
3
+
4
+ Γ£ô tests/tools.test.ts > MCP tool schemas > tool descriptions match expected format
5
+ Γ£ô tests/tools.test.ts > MCP tool schemas > elen_log_decision schema has required fields
6
+ Γ£ô tests/tools.test.ts > MCP tool schemas > elen_search_precedents schema includes optional filters
7
+ Γ£ô tests/tools.test.ts > MCP tool schemas > elen_get_competency schema supports optional agentId
8
+ Γ£ô tests/tools.test.ts > MCP tool handlers > elen_log_decision creates a record via SDK
9
+ Γ£ô tests/tools.test.ts > MCP tool handlers > elen_search_precedents returns matching records
10
+ Γ£ô tests/tools.test.ts > MCP tool handlers > elen_get_competency returns profile
11
+ Γ£ô tests/tools.test.ts > MCP tool handlers > throws on invalid tool inputs
12
+ Γ£ô tests/server.test.ts > MCP server CLI > parses --agent-id and --storage args
13
+ × tests/server.test.ts > MCP server CLI > uses defaults when args are omitted
14
+  → expected 'C:\Users\ln_ni\.elen\decisions.db' to contain '.elen/decisions.db'
15
+ × tests/server.test.ts > routeToolCall > routes known tools and throws for unknown tool
16
+  → expected { record_id: 'rec-1', …(1) } to deeply equal { record_id: 'rec-1' }
17
+
18
+ node.exe : ⎯⎯⎯⎯⎯⎯⎯
19
+ Failed Tests 2
20
+ ⎯⎯⎯⎯⎯⎯⎯
21
+ At C:\Users\ln_ni\AppData\Roaming\npm\npx.ps1:24
22
+ char:5
23
+ + & "node$exe"
24
+ "$basedir/node_modules/npm/bin/npx-cli.js" $args
25
+ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
26
+ ~~~~~~~~~~~~~~~~~~~
27
+ + CategoryInfo : NotSpecified: ([3
28
+ 1mΓÄ»ΓÄ»ΓÄ»Γ...»ΓÄ»ΓÄ»ΓÄ»:String) [], R
29
+ emoteException
30
+ + FullyQualifiedErrorId : NativeCommandError
31
+
32
+
33
+  FAIL 
34
+ tests/server.test.ts > MCP server
35
+ CLI > uses defaults when args are
36
+ omitted
37
+ AssertionError: expected
38
+ 'C:\Users\ln_ni\.elen\decisions.db' to contain
39
+ '.elen/decisions.db'
40
+
41
+ Expected: ".elen/decisions.db"
42
+ Received: "C:\Users\ln_ni\.elen
43
+ \decisions.db"
44
+
45
+  Γ¥»
46
+ tests/server.test.ts:17:34
47
+  15| 
48
+  16|  expect(parsed[3
49
+ 3m.agentId).toBe('d
50
+ efault-agent');
51
+  17|  expect(defa
52
+ ultStoragePath()).toContain[
53
+ 39m('.elen/decisions.db');
54
+  | 
55
+ ^
56
+  18|  expect(parsed[3
57
+ 3m.storagePath).toBeUndefined
58
+ ();
59
+  19|  });
60
+
61
+ ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»Γ
62
+ Ä»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»[1/2]ΓÄ»[39
63
+ m
64
+
65
+  FAIL 
66
+ tests/server.test.ts >
67
+ routeToolCall > routes known tools
68
+ and throws for unknown tool
69
+ AssertionError: expected {
70
+ record_id: 'rec-1', …(1) } to deeply equal {
71
+ record_id: 'rec-1' }
72
+
73
+ - Expected
74
+ + Received
75
+
76
+  Object {
77
+ + "next_suggested_action": "Decision
78
+ recorded. Consider using elen_search_precedents
79
+ to find related prior decisions before your next
80
+ choice.",
81
+  "record_id": "rec-1",
82
+  }
83
+
84
+  Γ¥»
85
+ tests/server.test.ts:30:5
86
+  28|  } as
87
+ any;
88
+  29| 
89
+  30|  await expec
90
+ t(routeToolCall(elen,
91
+ 'agent-a',
92
+ 'elen_log_decision', {
93
+  |  ^
94
+  31|  question:
95
+ 'Q',
96
+  32|  domain:
97
+ 'infrastructure',
98
+
99
+ ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»Γ
100
+ Ä»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»[2/2]ΓÄ»[39
101
+ m
102
+
103
+  Test Files  1 failed | 1 passed (2)
104
+  Tests  2 failed | 9 passed (11)
105
+  Start at  12:20:16
106
+  Duration  6.93s (transform 3.40s, setup 0ms, collect 7.36s, tests 99ms, environment 5ms, prepare 2.00s)
107
+
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@learningnodes/elen-mcp",
3
+ "version": "0.1.2",
4
+ "license": "AGPL-3.0",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "elen-mcp": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc -p tsconfig.json",
12
+ "test": "vitest run",
13
+ "lint": "tsc -p tsconfig.json --noEmit"
14
+ },
15
+ "dependencies": {
16
+ "@learningnodes/elen": "file:../sdk-ts",
17
+ "@modelcontextprotocol/sdk": "^1.17.4"
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "^22.13.10",
21
+ "typescript": "^5.6.3",
22
+ "vitest": "^2.1.8"
23
+ }
24
+ }
package/src/index.ts ADDED
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ import { execSync } from 'node:child_process';
3
+ import { existsSync, readFileSync } from 'node:fs';
4
+ import { basename, resolve } from 'node:path';
5
+ import { createMcpServer, defaultStoragePath } from './server';
6
+
7
+ // MCP SDK overrides the ambient `process` type, stripping cwd().
8
+ // We use execSync as a workaround.
9
+ const currentDir = (): string => execSync('cd', { encoding: 'utf-8' }).trim();
10
+
11
+ export interface CliOptions {
12
+ agentId: string;
13
+ projectId: string;
14
+ storagePath?: string;
15
+ }
16
+
17
+ /**
18
+ * Auto-detect project identity from the environment.
19
+ * Priority: git remote name > package.json name > cwd basename > 'default'
20
+ */
21
+ function detectProject(): string {
22
+ // 1. Try git remote URL → extract repo name
23
+ try {
24
+ const remote = execSync('git remote get-url origin', {
25
+ encoding: 'utf-8',
26
+ stdio: ['pipe', 'pipe', 'pipe']
27
+ }).trim();
28
+ // https://github.com/org/repo-name.git → repo-name
29
+ // git@github.com:org/repo-name.git → repo-name
30
+ const match = remote.match(/[/:]([^/]+?)(?:\.git)?$/);
31
+ if (match?.[1]) return match[1];
32
+ } catch {
33
+ // Not a git repo or git not available
34
+ }
35
+
36
+ // 2. Try package.json name
37
+ const pkgPath = resolve(currentDir(), 'package.json');
38
+ if (existsSync(pkgPath)) {
39
+ try {
40
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
41
+ if (pkg.name) {
42
+ // Strip scope: @org/name → name
43
+ return pkg.name.replace(/^@[^/]+\//, '');
44
+ }
45
+ } catch {
46
+ // Malformed package.json
47
+ }
48
+ }
49
+
50
+ // 3. Working directory basename
51
+ return basename(currentDir()) || 'default';
52
+ }
53
+
54
+ export function parseCliArgs(argv: string[]): CliOptions {
55
+ let agentId = 'default-agent';
56
+ let projectId: string | undefined;
57
+ let storagePath: string | undefined;
58
+
59
+ for (let i = 0; i < argv.length; i += 1) {
60
+ const arg = argv[i];
61
+
62
+ if (arg === '--agent-id') {
63
+ agentId = argv[i + 1] ?? agentId;
64
+ i += 1;
65
+ continue;
66
+ }
67
+
68
+ if (arg === '--project') {
69
+ projectId = argv[i + 1] ?? projectId;
70
+ i += 1;
71
+ continue;
72
+ }
73
+
74
+ if (arg === '--storage') {
75
+ storagePath = argv[i + 1] ?? storagePath;
76
+ i += 1;
77
+ }
78
+ }
79
+
80
+ return {
81
+ agentId,
82
+ projectId: projectId ?? detectProject(),
83
+ storagePath
84
+ };
85
+ }
86
+
87
+ async function main() {
88
+ const options = parseCliArgs(process.argv.slice(2));
89
+
90
+ process.stderr.write(`✦ Elen MCP starting — agent: ${options.agentId}, project: ${options.projectId}\n`);
91
+
92
+ const server = createMcpServer({
93
+ agentId: options.agentId,
94
+ projectId: options.projectId,
95
+ storagePath: options.storagePath ?? defaultStoragePath()
96
+ });
97
+
98
+ await server.start();
99
+ }
100
+
101
+ if (require.main === module) {
102
+ main().catch((error: unknown) => {
103
+ const message = error instanceof Error ? error.message : String(error);
104
+ process.stderr.write(`Failed to start @learningnodes/elen-mcp: ${message}\n`);
105
+ process.exit(1);
106
+ });
107
+ }
package/src/server.ts ADDED
@@ -0,0 +1,116 @@
1
+ import { mkdirSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { dirname, join } from 'node:path';
4
+ import { Elen } from '@learningnodes/elen';
5
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
6
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
7
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
8
+ import {
9
+ elenCommitTool,
10
+ elenSuggestTool,
11
+ elenExpandTool,
12
+ elenSupersedeTool,
13
+ elenGetCompetencyTool,
14
+ elenLogDecisionTool,
15
+ elenSearchPrecedentsTool,
16
+ handleCommit,
17
+ handleSuggest,
18
+ handleExpand,
19
+ handleSupersede,
20
+ handleGetCompetency,
21
+ handleLogDecision,
22
+ handleSearchPrecedents
23
+ } from './tools';
24
+
25
+ export interface McpServerOptions {
26
+ agentId: string;
27
+ projectId?: string;
28
+ storagePath?: string;
29
+ }
30
+
31
+ export function defaultStoragePath(): string {
32
+ return join(homedir(), '.elen', 'decisions.db');
33
+ }
34
+
35
+ export function createElenClient(options: McpServerOptions): Elen {
36
+ const sqlitePath = options.storagePath ?? defaultStoragePath();
37
+ mkdirSync(dirname(sqlitePath), { recursive: true });
38
+
39
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
40
+ return new Elen({
41
+ agentId: options.agentId,
42
+ projectId: options.projectId,
43
+ storage: 'sqlite' as const,
44
+ sqlitePath
45
+ } as any);
46
+ }
47
+
48
+ export async function routeToolCall(elen: Elen, agentId: string, name: string, args: unknown): Promise<unknown> {
49
+ switch (name) {
50
+ case elenCommitTool.name:
51
+ return handleCommit(elen, args);
52
+ case elenSuggestTool.name:
53
+ return handleSuggest(elen, args);
54
+ case elenExpandTool.name:
55
+ return handleExpand(elen, args);
56
+ case elenSupersedeTool.name:
57
+ return handleSupersede(elen, args);
58
+ case elenGetCompetencyTool.name:
59
+ return handleGetCompetency(elen, args, agentId);
60
+ case elenLogDecisionTool.name:
61
+ return handleLogDecision(elen, args);
62
+ case elenSearchPrecedentsTool.name:
63
+ return handleSearchPrecedents(elen, args);
64
+ default:
65
+ throw new Error(`Unknown tool: ${name}`);
66
+ }
67
+ }
68
+
69
+ export function createMcpServer(options: McpServerOptions) {
70
+ const elen = createElenClient(options);
71
+
72
+ const server = new Server(
73
+ {
74
+ name: '@learningnodes/elen-mcp',
75
+ version: '0.1.0'
76
+ },
77
+ {
78
+ capabilities: {
79
+ tools: {}
80
+ }
81
+ }
82
+ );
83
+
84
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
85
+ tools: [
86
+ elenCommitTool,
87
+ elenSuggestTool,
88
+ elenExpandTool,
89
+ elenSupersedeTool,
90
+ elenGetCompetencyTool,
91
+ elenLogDecisionTool,
92
+ elenSearchPrecedentsTool
93
+ ]
94
+ }));
95
+
96
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
97
+ const result = await routeToolCall(elen, options.agentId, request.params.name, request.params.arguments);
98
+
99
+ return {
100
+ content: [
101
+ {
102
+ type: 'text',
103
+ text: JSON.stringify(result, null, 2)
104
+ }
105
+ ]
106
+ };
107
+ });
108
+
109
+ return {
110
+ server,
111
+ async start() {
112
+ const transport = new StdioServerTransport();
113
+ await server.connect(transport);
114
+ }
115
+ };
116
+ }
package/src/shims.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ declare module '@modelcontextprotocol/sdk/server/index.js' {
2
+ export class Server {
3
+ constructor(info: { name: string; version: string }, options: { capabilities: { tools: Record<string, never> } });
4
+ setRequestHandler(schema: unknown, handler: (request: any) => Promise<any>): void;
5
+ connect(transport: unknown): Promise<void>;
6
+ }
7
+ }
8
+
9
+ declare module '@modelcontextprotocol/sdk/server/stdio.js' {
10
+ export class StdioServerTransport { }
11
+ }
12
+
13
+ declare module '@modelcontextprotocol/sdk/types.js' {
14
+ export const ListToolsRequestSchema: unknown;
15
+ export const CallToolRequestSchema: unknown;
16
+ }
17
+
18
+ declare module 'node:fs' {
19
+ export function mkdirSync(path: string, options?: { recursive?: boolean }): void;
20
+ }
21
+
22
+ declare module 'node:os' {
23
+ export function homedir(): string;
24
+ }
25
+
26
+ declare module 'node:path' {
27
+ export function dirname(path: string): string;
28
+ export function join(...parts: string[]): string;
29
+ }
30
+
31
+ declare const require: {
32
+ main?: unknown;
33
+ };
34
+
35
+ declare const module: unknown;
36
+
37
+ declare const process: {
38
+ argv: string[];
39
+ stderr: { write: (message: string) => void };
40
+ exit: (code?: number) => never;
41
+ };
@@ -0,0 +1,40 @@
1
+ import { z } from 'zod';
2
+ import type { Elen } from '@learningnodes/elen';
3
+
4
+ export const elenCommitTool = {
5
+ name: 'elen_commit',
6
+ description: 'Commit a new minimal epistemic decision to the graph. Pass constraints as plain text array.',
7
+ inputSchema: {
8
+ type: 'object',
9
+ properties: {
10
+ question: { type: 'string', description: 'The question or problem statement' },
11
+ domain: { type: 'string', description: 'Domain of decision (e.g. infrastructure, product)' },
12
+ decisionText: { type: 'string', description: 'The proposed answer/decision made' },
13
+ constraints: {
14
+ type: 'array',
15
+ items: { type: 'string' },
16
+ description: 'Plain-text constraint rules (e.g. "budget < 500 tokens")'
17
+ },
18
+ refs: {
19
+ type: 'array',
20
+ items: { type: 'string' },
21
+ description: 'Explicit pointers to other decision IDs or artifacts'
22
+ }
23
+ },
24
+ required: ['question', 'domain', 'decisionText', 'constraints']
25
+ }
26
+ };
27
+
28
+ export const commitInputSchema = z.object({
29
+ question: z.string().min(1),
30
+ domain: z.string().min(1),
31
+ decisionText: z.string().min(1),
32
+ constraints: z.array(z.string().min(1)),
33
+ refs: z.array(z.string()).optional()
34
+ });
35
+
36
+ export async function handleCommit(elen: Elen, args: unknown): Promise<unknown> {
37
+ const parsed = commitInputSchema.parse(args);
38
+ const result = await elen.commitDecision(parsed);
39
+ return result;
40
+ }
@@ -0,0 +1,40 @@
1
+ import type { Elen } from '@learningnodes/elen';
2
+
3
+ export const elenGetCompetencyTool = {
4
+ name: 'elen_get_competency',
5
+ description:
6
+ 'View the competency profile for this agent — domain expertise based on validated decision history.',
7
+ inputSchema: {
8
+ type: 'object',
9
+ properties: {
10
+ agentId: { type: 'string', description: 'Agent ID (defaults to current agent)' }
11
+ }
12
+ }
13
+ } as const;
14
+
15
+ export function validateCompetencyInput(input: unknown): { agentId?: string } {
16
+ if (input === undefined) {
17
+ return {};
18
+ }
19
+
20
+ if (!input || typeof input !== 'object') {
21
+ throw new Error('Invalid input: expected object');
22
+ }
23
+
24
+ const candidate = input as Record<string, unknown>;
25
+ if (candidate.agentId !== undefined && typeof candidate.agentId !== 'string') {
26
+ throw new Error('Invalid input: agentId must be a string');
27
+ }
28
+
29
+ return { agentId: candidate.agentId as string | undefined };
30
+ }
31
+
32
+ export async function handleGetCompetency(elen: Elen, args: unknown, defaultAgentId: string): Promise<unknown> {
33
+ const parsed = validateCompetencyInput(args);
34
+
35
+ if (parsed.agentId && parsed.agentId !== defaultAgentId) {
36
+ throw new Error('Cross-agent profile lookup is not supported by this MCP server');
37
+ }
38
+
39
+ return elen.getCompetencyProfile();
40
+ }
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod';
2
+ import type { Elen } from '@learningnodes/elen';
3
+
4
+ export const elenExpandTool = {
5
+ name: 'elen_expand',
6
+ description: 'Expand a minimal pointer decision ID into its full constraints and text when ambiguity requires it.',
7
+ inputSchema: {
8
+ type: 'object',
9
+ properties: {
10
+ decisionId: { type: 'string', description: 'The decision_id (e.g. dec:INFA-aBcDeF) to expand' }
11
+ },
12
+ required: ['decisionId']
13
+ }
14
+ };
15
+
16
+ export const expandInputSchema = z.object({
17
+ decisionId: z.string().min(1)
18
+ });
19
+
20
+ export async function handleExpand(elen: Elen, args: unknown): Promise<unknown> {
21
+ const parsed = expandInputSchema.parse(args);
22
+ const result = await elen.expand(parsed.decisionId);
23
+ return result;
24
+ }
@@ -0,0 +1,23 @@
1
+ export { elenCommitTool, handleCommit, commitInputSchema } from './commit';
2
+ export { elenSuggestTool, handleSuggest, suggestInputSchema } from './suggest';
3
+ export { elenExpandTool, handleExpand, expandInputSchema } from './expand';
4
+ export { elenSupersedeTool, handleSupersede, supersedeInputSchema } from './supersede';
5
+ export { elenGetCompetencyTool, handleGetCompetency, validateCompetencyInput } from './competency';
6
+ export {
7
+ elenLogDecisionTool,
8
+ elenSearchPrecedentsTool,
9
+ handleLogDecision,
10
+ handleSearchPrecedents,
11
+ validateLogDecisionInput,
12
+ validateSearchInput
13
+ } from './legacy';
14
+
15
+ export const ELEN_TOOLS = [
16
+ 'elen_commit',
17
+ 'elen_suggest',
18
+ 'elen_expand',
19
+ 'elen_supersede',
20
+ 'elen_get_competency',
21
+ 'elen_log_decision',
22
+ 'elen_search_precedents'
23
+ ] as const;
@@ -0,0 +1,88 @@
1
+ import type { Elen } from '@learningnodes/elen';
2
+
3
+ export const elenLogDecisionTool = {
4
+ name: 'elen_log_decision',
5
+ description: 'Create a validated Decision Record from constraints/evidence/answer.',
6
+ inputSchema: {
7
+ type: 'object',
8
+ properties: {
9
+ question: { type: 'string' },
10
+ domain: { type: 'string' },
11
+ constraints: { type: 'array', items: { type: 'string' } },
12
+ evidence: { type: 'array', items: { type: 'string' } },
13
+ answer: { type: 'string' },
14
+ parentPrompt: { type: 'string' }
15
+ },
16
+ required: ['question', 'constraints', 'evidence', 'answer']
17
+ }
18
+ } as const;
19
+
20
+ export const elenSearchPrecedentsTool = {
21
+ name: 'elen_search_precedents',
22
+ description: 'Search validated decisions for precedents.',
23
+ inputSchema: {
24
+ type: 'object',
25
+ properties: {
26
+ query: { type: 'string' },
27
+ domain: { type: 'string' },
28
+ minConfidence: { type: 'number' },
29
+ limit: { type: 'number' }
30
+ }
31
+ }
32
+ } as const;
33
+
34
+ export function validateLogDecisionInput(input: unknown) {
35
+ if (!input || typeof input !== 'object') throw new Error('missing required field: question');
36
+ const candidate = input as Record<string, unknown>;
37
+ for (const field of ['question', 'constraints', 'evidence', 'answer']) {
38
+ if (!(field in candidate)) throw new Error(`missing required field: ${field}`);
39
+ }
40
+ return candidate as {
41
+ question: string;
42
+ domain?: string;
43
+ constraints: string[];
44
+ evidence: string[];
45
+ answer: string;
46
+ parentPrompt?: string;
47
+ };
48
+ }
49
+
50
+ export function validateSearchInput(input: unknown): {
51
+ query?: string;
52
+ domain?: string;
53
+ minConfidence?: number;
54
+ limit?: number;
55
+ } {
56
+ if (!input) return {};
57
+ if (typeof input !== 'object') throw new Error('invalid search input');
58
+ const c = input as Record<string, unknown>;
59
+ if (c.limit !== undefined && typeof c.limit !== 'number') throw new Error('limit must be a number');
60
+ if (c.minConfidence !== undefined && typeof c.minConfidence !== 'number') {
61
+ throw new Error('minConfidence must be a number');
62
+ }
63
+ return c as { query?: string; domain?: string; minConfidence?: number; limit?: number };
64
+ }
65
+
66
+ export async function handleLogDecision(elen: Elen, args: unknown): Promise<unknown> {
67
+ const parsed = validateLogDecisionInput(args);
68
+ const result = await elen.logDecision({
69
+ question: parsed.question,
70
+ domain: parsed.domain ?? 'general',
71
+ constraints: parsed.constraints,
72
+ evidence: parsed.evidence,
73
+ answer: parsed.answer,
74
+ parentPrompt: parsed.parentPrompt
75
+ });
76
+
77
+ return { ...result, next_suggested_action: 'Use elen_search_precedents to discover related decisions.' };
78
+ }
79
+
80
+ export async function handleSearchPrecedents(elen: Elen, args: unknown): Promise<unknown> {
81
+ const parsed = validateSearchInput(args);
82
+ return elen.searchRecords({
83
+ query: parsed.query,
84
+ domain: parsed.domain,
85
+ minConfidence: parsed.minConfidence,
86
+ limit: parsed.limit
87
+ });
88
+ }
@@ -0,0 +1,32 @@
1
+ import { z } from 'zod';
2
+ import type { Elen } from '@learningnodes/elen';
3
+
4
+ export const elenSuggestTool = {
5
+ name: 'elen_suggest',
6
+ description: 'Before making a decision or taking action, retrieve pointer-first minimal candidates of prior decisions.',
7
+ inputSchema: {
8
+ type: 'object',
9
+ properties: {
10
+ query: { type: 'string', description: 'Search term or question' },
11
+ domain: { type: 'string', description: 'Filter by domain' },
12
+ limit: { type: 'number', description: 'Max suggestions to return (Top-K)' }
13
+ },
14
+ required: ['query']
15
+ }
16
+ };
17
+
18
+ export const suggestInputSchema = z.object({
19
+ query: z.string().min(1),
20
+ domain: z.string().optional(),
21
+ limit: z.number().int().min(1).max(50).optional().default(5)
22
+ });
23
+
24
+ export async function handleSuggest(elen: Elen, args: unknown): Promise<unknown> {
25
+ const parsed = suggestInputSchema.parse(args);
26
+ const results = await elen.suggest({
27
+ query: parsed.query,
28
+ domain: parsed.domain,
29
+ limit: parsed.limit
30
+ });
31
+ return results;
32
+ }