@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.
- package/dist/index.d.ts +7 -0
- package/dist/index.js +92 -0
- package/dist/server.d.ts +14 -0
- package/dist/server.js +88 -0
- package/dist/tools/commit.d.ts +46 -0
- package/dist/tools/commit.js +40 -0
- package/dist/tools/competency.d.ts +18 -0
- package/dist/tools/competency.js +35 -0
- package/dist/tools/expand.d.ts +20 -0
- package/dist/tools/expand.js +24 -0
- package/dist/tools/get-context.d.ts +34 -0
- package/dist/tools/get-context.js +84 -0
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.js +39 -0
- package/dist/tools/legacy.d.ts +72 -0
- package/dist/tools/legacy.js +80 -0
- package/dist/tools/log-decision.d.ts +81 -0
- package/dist/tools/log-decision.js +121 -0
- package/dist/tools/search.d.ts +28 -0
- package/dist/tools/search.js +49 -0
- package/dist/tools/suggest.d.ts +30 -0
- package/dist/tools/suggest.js +32 -0
- package/dist/tools/supersede.d.ts +50 -0
- package/dist/tools/supersede.js +43 -0
- package/final_test.txt +68 -0
- package/mcp_test2.txt +60 -0
- package/mcp_test_output.txt +107 -0
- package/package.json +24 -0
- package/src/index.ts +107 -0
- package/src/server.ts +116 -0
- package/src/shims.d.ts +41 -0
- package/src/tools/commit.ts +40 -0
- package/src/tools/competency.ts +40 -0
- package/src/tools/expand.ts +24 -0
- package/src/tools/index.ts +23 -0
- package/src/tools/legacy.ts +88 -0
- package/src/tools/suggest.ts +32 -0
- package/src/tools/supersede.ts +43 -0
- package/tests/server.test.ts +43 -0
- package/tests/tools.test.ts +82 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
|
|
2
|
+
[1m[7m[36m RUN [39m[27m[22m [36mv2.1.9 [39m[90mC:/Users/ln_ni/OneDrive/Desktop/Desktop/ventures/learningnodes/git/marketplace-repos/Elen/packages/mcp-server[39m
|
|
3
|
+
|
|
4
|
+
[32mΓ£ô[39m tests/tools.test.ts[2m > [22mMCP tool schemas[2m > [22mtool descriptions match expected format
|
|
5
|
+
[32mΓ£ô[39m tests/tools.test.ts[2m > [22mMCP tool schemas[2m > [22melen_log_decision schema has required fields
|
|
6
|
+
[32mΓ£ô[39m tests/tools.test.ts[2m > [22mMCP tool schemas[2m > [22melen_search_precedents schema includes optional filters
|
|
7
|
+
[32mΓ£ô[39m tests/tools.test.ts[2m > [22mMCP tool schemas[2m > [22melen_get_competency schema supports optional agentId
|
|
8
|
+
[32mΓ£ô[39m tests/tools.test.ts[2m > [22mMCP tool handlers[2m > [22melen_log_decision creates a record via SDK
|
|
9
|
+
[32mΓ£ô[39m tests/tools.test.ts[2m > [22mMCP tool handlers[2m > [22melen_search_precedents returns matching records
|
|
10
|
+
[32mΓ£ô[39m tests/tools.test.ts[2m > [22mMCP tool handlers[2m > [22melen_get_competency returns profile
|
|
11
|
+
[32mΓ£ô[39m tests/tools.test.ts[2m > [22mMCP tool handlers[2m > [22mthrows on invalid tool inputs
|
|
12
|
+
[32mΓ£ô[39m tests/server.test.ts[2m > [22mMCP server CLI[2m > [22mparses --agent-id and --storage args
|
|
13
|
+
[31m×[39m tests/server.test.ts[2m > [22mMCP server CLI[2m > [22muses defaults when args are omitted
|
|
14
|
+
[31m → expected 'C:\Users\ln_ni\.elen\decisions.db' to contain '.elen/decisions.db'[39m
|
|
15
|
+
[31m×[39m tests/server.test.ts[2m > [22mrouteToolCall[2m > [22mroutes known tools and throws for unknown tool
|
|
16
|
+
[31m → expected { record_id: 'rec-1', …(1) } to deeply equal { record_id: 'rec-1' }[39m
|
|
17
|
+
|
|
18
|
+
node.exe : [31m⎯⎯⎯⎯⎯⎯⎯[1m[7m
|
|
19
|
+
Failed Tests 2
|
|
20
|
+
[27m[22m⎯⎯⎯⎯⎯⎯⎯[39m
|
|
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ΓÄ»ΓÄ»ΓÄ»Γ...»ΓÄ»ΓÄ»ΓÄ»[39m:String) [], R
|
|
29
|
+
emoteException
|
|
30
|
+
+ FullyQualifiedErrorId : NativeCommandError
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
[31m[1m[7m FAIL [27m[22m[39m
|
|
34
|
+
tests/server.test.ts[2m > [22mMCP server
|
|
35
|
+
CLI[2m > [22muses defaults when args are
|
|
36
|
+
omitted
|
|
37
|
+
[31m[1mAssertionError[22m: expected
|
|
38
|
+
'C:\Users\ln_ni\.elen\decisions.db' to contain
|
|
39
|
+
'.elen/decisions.db'[39m
|
|
40
|
+
|
|
41
|
+
Expected: [32m".elen[7m/[27mdecisions.db"[39m
|
|
42
|
+
Received: [31m"[7mC:\Users\ln_ni\[27m.elen[7m
|
|
43
|
+
\[27mdecisions.db"[39m
|
|
44
|
+
|
|
45
|
+
[36m [2mΓ¥»[22m
|
|
46
|
+
tests/server.test.ts:[2m17:34[22m[39m
|
|
47
|
+
[90m 15| [39m
|
|
48
|
+
[90m 16| [39m [34mexpect[39m(parsed[3
|
|
49
|
+
3m.[39magentId)[33m.[39m[34mtoBe[39m([32m'd
|
|
50
|
+
efault-agent'[39m)[33m;[39m
|
|
51
|
+
[90m 17| [39m [34mexpect[39m([34mdefa
|
|
52
|
+
ultStoragePath[39m())[33m.[39m[34mtoContain[
|
|
53
|
+
39m([32m'.elen/decisions.db'[39m)[33m;[39m
|
|
54
|
+
[90m | [39m
|
|
55
|
+
[31m^[39m
|
|
56
|
+
[90m 18| [39m [34mexpect[39m(parsed[3
|
|
57
|
+
3m.[39mstoragePath)[33m.[39m[34mtoBeUndefined
|
|
58
|
+
[39m()[33m;[39m
|
|
59
|
+
[90m 19| [39m })[33m;[39m
|
|
60
|
+
|
|
61
|
+
[31m[2mΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»Γ
|
|
62
|
+
Ä»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»[1/2]ΓÄ»[22m[39
|
|
63
|
+
m
|
|
64
|
+
|
|
65
|
+
[31m[1m[7m FAIL [27m[22m[39m
|
|
66
|
+
tests/server.test.ts[2m >
|
|
67
|
+
[22mrouteToolCall[2m > [22mroutes known tools
|
|
68
|
+
and throws for unknown tool
|
|
69
|
+
[31m[1mAssertionError[22m: expected {
|
|
70
|
+
record_id: 'rec-1', …(1) } to deeply equal {
|
|
71
|
+
record_id: 'rec-1' }[39m
|
|
72
|
+
|
|
73
|
+
[32m- Expected[39m
|
|
74
|
+
[31m+ Received[39m
|
|
75
|
+
|
|
76
|
+
[2m Object {[22m
|
|
77
|
+
[31m+ "next_suggested_action": "Decision
|
|
78
|
+
recorded. Consider using elen_search_precedents
|
|
79
|
+
to find related prior decisions before your next
|
|
80
|
+
choice.",[39m
|
|
81
|
+
[2m "record_id": "rec-1",[22m
|
|
82
|
+
[2m }[22m
|
|
83
|
+
|
|
84
|
+
[36m [2mΓ¥»[22m
|
|
85
|
+
tests/server.test.ts:[2m30:5[22m[39m
|
|
86
|
+
[90m 28| [39m } [35mas[39m
|
|
87
|
+
any[33m;[39m
|
|
88
|
+
[90m 29| [39m
|
|
89
|
+
[90m 30| [39m [35mawait[39m [34mexpec
|
|
90
|
+
t[39m([34mrouteToolCall[39m(elen[33m,[39m
|
|
91
|
+
[32m'agent-a'[39m[33m,[39m
|
|
92
|
+
[32m'elen_log_decision'[39m[33m,[39m {
|
|
93
|
+
[90m | [39m [31m^[39m
|
|
94
|
+
[90m 31| [39m question[33m:[39m
|
|
95
|
+
[32m'Q'[39m[33m,[39m
|
|
96
|
+
[90m 32| [39m domain[33m:[39m
|
|
97
|
+
[32m'infrastructure'[39m[33m,[39m
|
|
98
|
+
|
|
99
|
+
[31m[2mΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»Γ
|
|
100
|
+
Ä»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»ΓÄ»[2/2]ΓÄ»[22m[39
|
|
101
|
+
m
|
|
102
|
+
|
|
103
|
+
[2m Test Files [22m [1m[31m1 failed[39m[22m[2m | [22m[1m[32m1 passed[39m[22m[90m (2)[39m
|
|
104
|
+
[2m Tests [22m [1m[31m2 failed[39m[22m[2m | [22m[1m[32m9 passed[39m[22m[90m (11)[39m
|
|
105
|
+
[2m Start at [22m 12:20:16
|
|
106
|
+
[2m Duration [22m 6.93s[2m (transform 3.40s, setup 0ms, collect 7.36s, tests 99ms, environment 5ms, prepare 2.00s)[22m
|
|
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
|
+
}
|