@aaronsb/kg-cli 0.8.0 → 0.9.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/api/client.d.ts +31 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +65 -0
- package/dist/api/client.js.map +1 -1
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +2 -0
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/program.d.ts +9 -0
- package/dist/cli/program.d.ts.map +1 -0
- package/dist/cli/program.js +315 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/cli/search.d.ts.map +1 -1
- package/dist/cli/search.js +17 -10
- package/dist/cli/search.js.map +1 -1
- package/dist/mcp-server.js +778 -99
- package/dist/mcp-server.js.map +1 -1
- package/dist/types/index.d.ts +148 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/version.d.ts +3 -3
- package/dist/version.js +3 -3
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
package/dist/mcp-server.js
CHANGED
|
@@ -57,14 +57,12 @@ const package_json_1 = __importDefault(require("../package.json"));
|
|
|
57
57
|
/**
|
|
58
58
|
* Default parameters for graph queries (ADR-048 Query Safety)
|
|
59
59
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* - Lower max_hops (3) prevent exponential traversal explosion
|
|
63
|
-
* - Adjust based on graph size and performance characteristics
|
|
60
|
+
* Aligned with CLI defaults so MCP agents get the same results as CLI users.
|
|
61
|
+
* The API server enforces its own safety limits on the backend.
|
|
64
62
|
*/
|
|
65
63
|
const DEFAULT_SEARCH_SIMILARITY = 0.7; // Search tool minimum similarity
|
|
66
|
-
const DEFAULT_SEMANTIC_THRESHOLD = 0.
|
|
67
|
-
const DEFAULT_MAX_HOPS =
|
|
64
|
+
const DEFAULT_SEMANTIC_THRESHOLD = 0.5; // Connect queries semantic matching (matches CLI)
|
|
65
|
+
const DEFAULT_MAX_HOPS = 5; // Maximum path traversal depth (matches CLI)
|
|
68
66
|
const DEFAULT_MAX_DEPTH = 2; // Related concepts neighborhood depth
|
|
69
67
|
// Create server instance
|
|
70
68
|
const server = new index_js_1.Server({
|
|
@@ -85,17 +83,26 @@ const server = new index_js_1.Server({
|
|
|
85
83
|
* - Diversity score (0-100%): Conceptual richness and connection breadth
|
|
86
84
|
* - Authenticated diversity (✅✓⚠❌): Directional quality combining grounding + diversity
|
|
87
85
|
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
86
|
+
* Three tiers of exploration (prefer higher tiers for efficiency):
|
|
87
|
+
*
|
|
88
|
+
* TIER 3 — Programs & Chains (most efficient):
|
|
89
|
+
* program (action: "execute") - Compose multi-step queries into a single server-side program
|
|
90
|
+
* program (action: "chain") - Thread results through multiple stored programs
|
|
91
|
+
* program (action: "list") - Discover reusable stored programs
|
|
92
|
+
* Read the program/syntax resource for the full language reference.
|
|
93
|
+
*
|
|
94
|
+
* TIER 2 — Individual tools (quick lookups):
|
|
95
|
+
* search - Find concepts, sources, or documents by semantic similarity
|
|
96
|
+
* concept - Get details, find related concepts, discover connection paths
|
|
97
|
+
* source - Retrieve original source text or images
|
|
98
|
+
*
|
|
99
|
+
* TIER 1 — Management:
|
|
100
|
+
* ontology, job, ingest, graph, document, epistemic_status, analyze_polarity_axis
|
|
95
101
|
*
|
|
96
102
|
* Resources provide fresh data on-demand without consuming tool budget:
|
|
97
103
|
* - database/stats, database/info, database/health
|
|
98
104
|
* - system/status, api/health
|
|
105
|
+
* - program/syntax — full GraphProgram language reference
|
|
99
106
|
*
|
|
100
107
|
* Use high grounding + high diversity to find reliable, central concepts.
|
|
101
108
|
* Negative grounding often shows the most interesting problems/contradictions.
|
|
@@ -248,6 +255,8 @@ DOCUMENT SEARCH (type: "documents") - Find documents by semantic similarity (ADR
|
|
|
248
255
|
|
|
249
256
|
RECOMMENDED WORKFLOW: After search, use concept (action: "connect") to find HOW concepts relate - this reveals narrative flows and cause/effect chains that individual searches cannot show. Connection paths are often more valuable than isolated concepts.
|
|
250
257
|
|
|
258
|
+
For multi-step exploration, compose searches into a GraphProgram (program tool) instead of making individual calls. One program can seed from search, expand relationships, and filter — all server-side in a single round-trip. Use program (action: "list") to find reusable stored programs, or read the program/syntax resource for composition examples.
|
|
259
|
+
|
|
251
260
|
Use 2-3 word phrases (e.g., "linear thinking patterns").`,
|
|
252
261
|
inputSchema: {
|
|
253
262
|
type: 'object',
|
|
@@ -289,7 +298,9 @@ Use 2-3 word phrases (e.g., "linear thinking patterns").`,
|
|
|
289
298
|
name: 'concept',
|
|
290
299
|
description: `Work with concepts: get details (ALL evidence + relationships), find related concepts (neighborhood exploration), or discover connections (paths between concepts).
|
|
291
300
|
|
|
292
|
-
|
|
301
|
+
For "connect" action, defaults (threshold=0.5, max_hops=5) match the CLI and work well for most queries. Use higher thresholds (0.75+) only if you need to narrow results for precision.
|
|
302
|
+
|
|
303
|
+
For multi-step workflows (search → connect → expand → filter), compose these into a GraphProgram instead of making individual calls. See the program tool and program/syntax resource.`,
|
|
293
304
|
inputSchema: {
|
|
294
305
|
type: 'object',
|
|
295
306
|
properties: {
|
|
@@ -370,13 +381,13 @@ PERFORMANCE CRITICAL: For "connect" action, use threshold >= 0.75 to avoid datab
|
|
|
370
381
|
},
|
|
371
382
|
max_hops: {
|
|
372
383
|
type: 'number',
|
|
373
|
-
description: 'Max path length (default:
|
|
374
|
-
default:
|
|
384
|
+
description: 'Max path length (default: 5). Higher values find longer paths but take more time.',
|
|
385
|
+
default: 5,
|
|
375
386
|
},
|
|
376
387
|
threshold: {
|
|
377
388
|
type: 'number',
|
|
378
|
-
description: 'Similarity threshold for semantic mode (default: 0.
|
|
379
|
-
default: 0.
|
|
389
|
+
description: 'Similarity threshold for semantic mode (default: 0.5). Lower values find broader matches. The API enforces backend safety limits.',
|
|
390
|
+
default: 0.5,
|
|
380
391
|
},
|
|
381
392
|
},
|
|
382
393
|
required: ['action'],
|
|
@@ -1026,6 +1037,105 @@ Queue executes sequentially, stops on first error (unless continue_on_error=true
|
|
|
1026
1037
|
required: ['action'],
|
|
1027
1038
|
},
|
|
1028
1039
|
},
|
|
1040
|
+
// ADR-500: GraphProgram notarization and execution
|
|
1041
|
+
{
|
|
1042
|
+
name: 'program',
|
|
1043
|
+
description: `Compose and execute GraphProgram queries against the knowledge graph (ADR-500).
|
|
1044
|
+
|
|
1045
|
+
Programs are JSON ASTs that compose Cypher queries and API calls using set-algebra operators.
|
|
1046
|
+
Each statement applies an operator to merge/filter results into a mutable Working Graph (W).
|
|
1047
|
+
|
|
1048
|
+
**Actions:**
|
|
1049
|
+
- "validate": Dry-run validation. Returns errors/warnings without storing.
|
|
1050
|
+
- "create": Notarize + store. Returns program ID for later execution.
|
|
1051
|
+
- "get": Retrieve a stored program by ID.
|
|
1052
|
+
- "list": Search stored programs by name/description. Returns lightweight metadata.
|
|
1053
|
+
- "execute": Run a program server-side. Pass inline AST (program) or stored ID (program_id).
|
|
1054
|
+
- "chain": Run multiple programs sequentially. W threads through each — program N's output becomes program N+1's input. Pass a deck array of {program_id} or {program} entries (max 10).
|
|
1055
|
+
|
|
1056
|
+
**Program Structure:**
|
|
1057
|
+
{ version: 1, metadata?: { name, description, author }, statements: [{ op, operation, label? }] }
|
|
1058
|
+
|
|
1059
|
+
**Operators** (applied to Working Graph W using result R):
|
|
1060
|
+
+ Union: merge R into W (dedup by concept_id / link compound key)
|
|
1061
|
+
- Difference: remove R's nodes from W, cascade dangling links
|
|
1062
|
+
& Intersect: keep only W nodes that appear in R
|
|
1063
|
+
? Optional: union if R non-empty, silent no-op if empty
|
|
1064
|
+
! Assert: union if R non-empty, abort program if empty
|
|
1065
|
+
|
|
1066
|
+
**CypherOp** — run read-only openCypher against the source graph:
|
|
1067
|
+
{ type: "cypher", query: "MATCH (c:Concept)-[r]->(t:Concept) RETURN c, r, t", limit?: 20 }
|
|
1068
|
+
Queries must be read-only (no CREATE/SET/DELETE/MERGE). RETURN nodes and relationships.
|
|
1069
|
+
|
|
1070
|
+
**ApiOp** — call internal service functions (no HTTP):
|
|
1071
|
+
{ type: "api", endpoint: "/search/concepts", params: { query: "...", limit: 10 } }
|
|
1072
|
+
|
|
1073
|
+
Allowed endpoints:
|
|
1074
|
+
/search/concepts — params: query (required), min_similarity?, limit?
|
|
1075
|
+
/search/sources — params: query (required), min_similarity?, limit?
|
|
1076
|
+
/concepts/details — params: concept_id (required)
|
|
1077
|
+
/concepts/related — params: concept_id (required), max_depth?, relationship_types?
|
|
1078
|
+
/concepts/batch — params: concept_ids (required, list)
|
|
1079
|
+
/vocabulary/status — params: relationship_type?, status_filter?
|
|
1080
|
+
|
|
1081
|
+
**Example** — find concepts about "machine learning", add their relationships:
|
|
1082
|
+
{ version: 1, statements: [
|
|
1083
|
+
{ op: "+", operation: { type: "api", endpoint: "/search/concepts",
|
|
1084
|
+
params: { query: "machine learning", limit: 5 } }, label: "seed" },
|
|
1085
|
+
{ op: "+", operation: { type: "cypher",
|
|
1086
|
+
query: "MATCH (c:Concept)-[r]->(t:Concept) WHERE c.concept_id IN $W_IDS RETURN c, r, t" },
|
|
1087
|
+
label: "expand relationships" }
|
|
1088
|
+
]}
|
|
1089
|
+
|
|
1090
|
+
Read the program/syntax resource for the complete language reference with more examples.`,
|
|
1091
|
+
inputSchema: {
|
|
1092
|
+
type: 'object',
|
|
1093
|
+
properties: {
|
|
1094
|
+
action: {
|
|
1095
|
+
type: 'string',
|
|
1096
|
+
enum: ['validate', 'create', 'get', 'list', 'execute', 'chain'],
|
|
1097
|
+
description: 'Operation: "validate" (dry run), "create" (notarize + store), "get" (retrieve by ID), "list" (search stored programs), "execute" (run server-side), "chain" (run multiple programs sequentially)',
|
|
1098
|
+
},
|
|
1099
|
+
program: {
|
|
1100
|
+
type: 'object',
|
|
1101
|
+
description: 'GraphProgram AST (required for validate, create, execute). Must have version:1 and statements array.',
|
|
1102
|
+
},
|
|
1103
|
+
name: {
|
|
1104
|
+
type: 'string',
|
|
1105
|
+
description: 'Program name (optional for create)',
|
|
1106
|
+
},
|
|
1107
|
+
program_id: {
|
|
1108
|
+
type: 'number',
|
|
1109
|
+
description: 'Program ID (required for get, optional for execute as alternative to inline program)',
|
|
1110
|
+
},
|
|
1111
|
+
params: {
|
|
1112
|
+
type: 'object',
|
|
1113
|
+
description: 'Runtime parameter values for execute (optional)',
|
|
1114
|
+
},
|
|
1115
|
+
search: {
|
|
1116
|
+
type: 'string',
|
|
1117
|
+
description: 'Search text for list action (matches name and description)',
|
|
1118
|
+
},
|
|
1119
|
+
limit: {
|
|
1120
|
+
type: 'number',
|
|
1121
|
+
description: 'Max results for list action (default: 20)',
|
|
1122
|
+
},
|
|
1123
|
+
deck: {
|
|
1124
|
+
type: 'array',
|
|
1125
|
+
items: {
|
|
1126
|
+
type: 'object',
|
|
1127
|
+
properties: {
|
|
1128
|
+
program_id: { type: 'number', description: 'Stored program ID' },
|
|
1129
|
+
program: { type: 'object', description: 'Inline program AST' },
|
|
1130
|
+
params: { type: 'object', description: 'Runtime params for this program' },
|
|
1131
|
+
},
|
|
1132
|
+
},
|
|
1133
|
+
description: 'Array of program entries for chain action (max 10). Each entry needs program_id or program.',
|
|
1134
|
+
},
|
|
1135
|
+
},
|
|
1136
|
+
required: ['action'],
|
|
1137
|
+
},
|
|
1138
|
+
},
|
|
1029
1139
|
],
|
|
1030
1140
|
};
|
|
1031
1141
|
});
|
|
@@ -1068,6 +1178,12 @@ server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
|
|
|
1068
1178
|
name: 'MCP File Access Allowlist',
|
|
1069
1179
|
description: 'Path allowlist configuration for secure file/directory ingestion (ADR-062)',
|
|
1070
1180
|
mimeType: 'application/json'
|
|
1181
|
+
},
|
|
1182
|
+
{
|
|
1183
|
+
uri: 'program/syntax',
|
|
1184
|
+
name: 'GraphProgram Language Reference',
|
|
1185
|
+
description: 'Complete syntax reference for composing GraphProgram ASTs (ADR-500). Includes operators, operation types, allowed endpoints, conditions, and examples.',
|
|
1186
|
+
mimeType: 'text/plain'
|
|
1071
1187
|
}
|
|
1072
1188
|
]
|
|
1073
1189
|
};
|
|
@@ -1160,6 +1276,15 @@ server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) =
|
|
|
1160
1276
|
}]
|
|
1161
1277
|
};
|
|
1162
1278
|
}
|
|
1279
|
+
case 'program/syntax': {
|
|
1280
|
+
return {
|
|
1281
|
+
contents: [{
|
|
1282
|
+
uri,
|
|
1283
|
+
mimeType: 'text/plain',
|
|
1284
|
+
text: getGraphProgramSyntaxReference()
|
|
1285
|
+
}]
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1163
1288
|
default:
|
|
1164
1289
|
throw new Error(`Unknown resource: ${uri}`);
|
|
1165
1290
|
}
|
|
@@ -1298,6 +1423,8 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
1298
1423
|
from_id: toolArgs.from_id,
|
|
1299
1424
|
to_id: toolArgs.to_id,
|
|
1300
1425
|
max_hops: toolArgs.max_hops || DEFAULT_MAX_HOPS,
|
|
1426
|
+
include_grounding: true,
|
|
1427
|
+
include_evidence: true,
|
|
1301
1428
|
// ADR-065: Epistemic status filtering
|
|
1302
1429
|
include_epistemic_status: toolArgs.include_epistemic_status,
|
|
1303
1430
|
exclude_epistemic_status: toolArgs.exclude_epistemic_status,
|
|
@@ -2240,6 +2367,273 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
2240
2367
|
throw new Error(`Unknown graph action: ${action}. Use: create, edit, delete, list, or queue`);
|
|
2241
2368
|
}
|
|
2242
2369
|
}
|
|
2370
|
+
// ADR-500: GraphProgram notarization
|
|
2371
|
+
case 'program': {
|
|
2372
|
+
const action = toolArgs.action;
|
|
2373
|
+
const client = (0, client_js_1.createClientFromEnv)();
|
|
2374
|
+
switch (action) {
|
|
2375
|
+
case 'validate': {
|
|
2376
|
+
if (!toolArgs.program) {
|
|
2377
|
+
throw new Error('program is required for validate action');
|
|
2378
|
+
}
|
|
2379
|
+
const result = await client.validateProgram(toolArgs.program);
|
|
2380
|
+
const lines = [];
|
|
2381
|
+
lines.push(result.valid ? '✓ Program is valid' : '✗ Program is invalid');
|
|
2382
|
+
if (result.errors.length > 0) {
|
|
2383
|
+
lines.push('\nErrors:');
|
|
2384
|
+
for (const err of result.errors) {
|
|
2385
|
+
const loc = err.statement !== null && err.statement !== undefined
|
|
2386
|
+
? `stmt ${err.statement}`
|
|
2387
|
+
: 'program';
|
|
2388
|
+
lines.push(` [${err.rule_id}] ${loc}: ${err.message}`);
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
if (result.warnings.length > 0) {
|
|
2392
|
+
lines.push('\nWarnings:');
|
|
2393
|
+
for (const warn of result.warnings) {
|
|
2394
|
+
const loc = warn.statement !== null && warn.statement !== undefined
|
|
2395
|
+
? `stmt ${warn.statement}`
|
|
2396
|
+
: 'program';
|
|
2397
|
+
lines.push(` [${warn.rule_id}] ${loc}: ${warn.message}`);
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
return {
|
|
2401
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
2402
|
+
};
|
|
2403
|
+
}
|
|
2404
|
+
case 'create': {
|
|
2405
|
+
if (!toolArgs.program) {
|
|
2406
|
+
throw new Error('program is required for create action');
|
|
2407
|
+
}
|
|
2408
|
+
try {
|
|
2409
|
+
const result = await client.createProgram(toolArgs.program, toolArgs.name);
|
|
2410
|
+
const lines = [];
|
|
2411
|
+
lines.push(`Notarized program "${result.name}" (ID ${result.id})`);
|
|
2412
|
+
lines.push(` Statements: ${result.program.statements.length}`);
|
|
2413
|
+
lines.push(` Created: ${result.created_at}`);
|
|
2414
|
+
return {
|
|
2415
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
2416
|
+
};
|
|
2417
|
+
}
|
|
2418
|
+
catch (err) {
|
|
2419
|
+
if (err.response?.status === 400 && err.response?.data?.detail?.validation) {
|
|
2420
|
+
const validation = err.response.data.detail.validation;
|
|
2421
|
+
const lines = ['✗ Program validation failed'];
|
|
2422
|
+
if (validation.errors) {
|
|
2423
|
+
for (const e of validation.errors) {
|
|
2424
|
+
const loc = e.statement !== null && e.statement !== undefined
|
|
2425
|
+
? `stmt ${e.statement}`
|
|
2426
|
+
: 'program';
|
|
2427
|
+
lines.push(` [${e.rule_id}] ${loc}: ${e.message}`);
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
return {
|
|
2431
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
2432
|
+
isError: true,
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
throw err;
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
case 'get': {
|
|
2439
|
+
if (!toolArgs.program_id) {
|
|
2440
|
+
throw new Error('program_id is required for get action');
|
|
2441
|
+
}
|
|
2442
|
+
const result = await client.getProgram(toolArgs.program_id);
|
|
2443
|
+
const lines = [];
|
|
2444
|
+
lines.push(`Program ${result.id}: ${result.name}`);
|
|
2445
|
+
lines.push(` Owner: ${result.owner_id ?? '(system)'}`);
|
|
2446
|
+
lines.push(` Version: ${result.program.version}`);
|
|
2447
|
+
lines.push(` Statements: ${result.program.statements.length}`);
|
|
2448
|
+
lines.push(` Created: ${result.created_at}`);
|
|
2449
|
+
lines.push(` Updated: ${result.updated_at}`);
|
|
2450
|
+
if (result.program.metadata?.description) {
|
|
2451
|
+
lines.push(` Description: ${result.program.metadata.description}`);
|
|
2452
|
+
}
|
|
2453
|
+
lines.push('\nStatements:');
|
|
2454
|
+
for (let i = 0; i < result.program.statements.length; i++) {
|
|
2455
|
+
const stmt = result.program.statements[i];
|
|
2456
|
+
const op = stmt.operation;
|
|
2457
|
+
const label = stmt.label ? ` (${stmt.label})` : '';
|
|
2458
|
+
if (op.type === 'cypher') {
|
|
2459
|
+
lines.push(` [${i}] ${stmt.op} cypher: ${op.query}${label}`);
|
|
2460
|
+
}
|
|
2461
|
+
else if (op.type === 'api') {
|
|
2462
|
+
lines.push(` [${i}] ${stmt.op} api: ${op.endpoint}${label}`);
|
|
2463
|
+
}
|
|
2464
|
+
else if (op.type === 'conditional') {
|
|
2465
|
+
lines.push(` [${i}] ${stmt.op} conditional${label}`);
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
lines.push('\nProgram AST:');
|
|
2469
|
+
lines.push(JSON.stringify(result.program, null, 2));
|
|
2470
|
+
return {
|
|
2471
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
2474
|
+
case 'execute': {
|
|
2475
|
+
if (!toolArgs.program && !toolArgs.program_id) {
|
|
2476
|
+
throw new Error('program or program_id is required for execute action');
|
|
2477
|
+
}
|
|
2478
|
+
try {
|
|
2479
|
+
const result = await client.executeProgram({
|
|
2480
|
+
program: toolArgs.program,
|
|
2481
|
+
programId: toolArgs.program_id,
|
|
2482
|
+
params: toolArgs.params,
|
|
2483
|
+
});
|
|
2484
|
+
const lines = [];
|
|
2485
|
+
const totalMs = result.log.reduce((sum, e) => sum + e.duration_ms, 0);
|
|
2486
|
+
if (result.aborted) {
|
|
2487
|
+
lines.push(`✗ Aborted at statement ${result.aborted.statement}: ${result.aborted.reason}`);
|
|
2488
|
+
}
|
|
2489
|
+
else {
|
|
2490
|
+
lines.push(`✓ Execution completed (${totalMs.toFixed(0)}ms)`);
|
|
2491
|
+
}
|
|
2492
|
+
lines.push(` Nodes: ${result.result.nodes.length}`);
|
|
2493
|
+
lines.push(` Links: ${result.result.links.length}`);
|
|
2494
|
+
if (result.log.length > 0) {
|
|
2495
|
+
lines.push('\nExecution Log:');
|
|
2496
|
+
const opNames = { '+': 'union', '-': 'diff', '&': 'intersect', '?': 'optional', '!': 'assert' };
|
|
2497
|
+
for (const entry of result.log) {
|
|
2498
|
+
const opLabel = opNames[entry.op] || entry.op;
|
|
2499
|
+
const branch = entry.branch_taken ? ` → ${entry.branch_taken}` : '';
|
|
2500
|
+
const affected = entry.operation_type === 'conditional'
|
|
2501
|
+
? ''
|
|
2502
|
+
: ` (${entry.nodes_affected}n, ${entry.links_affected}l)`;
|
|
2503
|
+
lines.push(` [${entry.statement}] ${opLabel} ${entry.operation_type}${branch}${affected} ${entry.duration_ms.toFixed(0)}ms`);
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
if (result.result.nodes.length > 0) {
|
|
2507
|
+
lines.push('\nNodes:');
|
|
2508
|
+
for (const node of result.result.nodes.slice(0, 50)) {
|
|
2509
|
+
const ont = node.ontology ? ` [${node.ontology}]` : '';
|
|
2510
|
+
lines.push(` • ${node.label} (${node.concept_id})${ont}`);
|
|
2511
|
+
}
|
|
2512
|
+
if (result.result.nodes.length > 50) {
|
|
2513
|
+
lines.push(` ... and ${result.result.nodes.length - 50} more`);
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
if (result.result.links.length > 0) {
|
|
2517
|
+
lines.push('\nLinks:');
|
|
2518
|
+
for (const link of result.result.links.slice(0, 30)) {
|
|
2519
|
+
lines.push(` ${link.from_id} → ${link.relationship_type} → ${link.to_id}`);
|
|
2520
|
+
}
|
|
2521
|
+
if (result.result.links.length > 30) {
|
|
2522
|
+
lines.push(` ... and ${result.result.links.length - 30} more`);
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
return {
|
|
2526
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
2527
|
+
isError: !!result.aborted,
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2530
|
+
catch (err) {
|
|
2531
|
+
if (err.response?.status === 400 && err.response?.data?.detail?.validation) {
|
|
2532
|
+
const validation = err.response.data.detail.validation;
|
|
2533
|
+
const lines = ['✗ Program validation failed'];
|
|
2534
|
+
if (validation.errors) {
|
|
2535
|
+
for (const e of validation.errors) {
|
|
2536
|
+
const loc = e.statement !== null && e.statement !== undefined
|
|
2537
|
+
? `stmt ${e.statement}`
|
|
2538
|
+
: 'program';
|
|
2539
|
+
lines.push(` [${e.rule_id}] ${loc}: ${e.message}`);
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
return {
|
|
2543
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
2544
|
+
isError: true,
|
|
2545
|
+
};
|
|
2546
|
+
}
|
|
2547
|
+
throw err;
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
case 'list': {
|
|
2551
|
+
const programs = await client.listPrograms({
|
|
2552
|
+
search: toolArgs.search,
|
|
2553
|
+
limit: toolArgs.limit,
|
|
2554
|
+
});
|
|
2555
|
+
if (programs.length === 0) {
|
|
2556
|
+
return {
|
|
2557
|
+
content: [{ type: 'text', text: 'No stored programs found.' }],
|
|
2558
|
+
};
|
|
2559
|
+
}
|
|
2560
|
+
const lines = [`Found ${programs.length} program(s):\n`];
|
|
2561
|
+
for (const p of programs) {
|
|
2562
|
+
const desc = p.description ? ` ${p.description}` : '';
|
|
2563
|
+
lines.push(` [${p.id}] ${p.name} (${p.statement_count} statements)${desc}`);
|
|
2564
|
+
}
|
|
2565
|
+
return {
|
|
2566
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
2567
|
+
};
|
|
2568
|
+
}
|
|
2569
|
+
case 'chain': {
|
|
2570
|
+
if (!toolArgs.deck || !Array.isArray(toolArgs.deck)) {
|
|
2571
|
+
throw new Error('deck array is required for chain action');
|
|
2572
|
+
}
|
|
2573
|
+
if (toolArgs.deck.length === 0) {
|
|
2574
|
+
throw new Error('deck must contain at least one entry');
|
|
2575
|
+
}
|
|
2576
|
+
if (toolArgs.deck.length > 10) {
|
|
2577
|
+
throw new Error(`Deck too large: ${toolArgs.deck.length} entries (max 10)`);
|
|
2578
|
+
}
|
|
2579
|
+
try {
|
|
2580
|
+
const result = await client.chainPrograms(toolArgs.deck);
|
|
2581
|
+
const lines = [];
|
|
2582
|
+
if (result.aborted) {
|
|
2583
|
+
lines.push(`✗ Chain aborted at statement ${result.aborted.statement}: ${result.aborted.reason}`);
|
|
2584
|
+
}
|
|
2585
|
+
else {
|
|
2586
|
+
const totalMs = result.programs.reduce((sum, p) => sum + p.log.reduce((s, e) => s + e.duration_ms, 0), 0);
|
|
2587
|
+
lines.push(`✓ Chain completed: ${result.programs.length} programs (${totalMs.toFixed(0)}ms)`);
|
|
2588
|
+
}
|
|
2589
|
+
lines.push(` Nodes: ${result.result.nodes.length}`);
|
|
2590
|
+
lines.push(` Links: ${result.result.links.length}`);
|
|
2591
|
+
// Per-program summary
|
|
2592
|
+
lines.push('\nProgram Results:');
|
|
2593
|
+
for (let i = 0; i < result.programs.length; i++) {
|
|
2594
|
+
const pr = result.programs[i];
|
|
2595
|
+
const ms = pr.log.reduce((s, e) => s + e.duration_ms, 0);
|
|
2596
|
+
const status = pr.aborted ? '✗' : '✓';
|
|
2597
|
+
lines.push(` ${status} [${i}] ${pr.log.length} steps, ${pr.result.nodes.length}n/${pr.result.links.length}l (${ms.toFixed(0)}ms)`);
|
|
2598
|
+
}
|
|
2599
|
+
if (result.result.nodes.length > 0) {
|
|
2600
|
+
lines.push('\nNodes:');
|
|
2601
|
+
for (const node of result.result.nodes.slice(0, 50)) {
|
|
2602
|
+
const ont = node.ontology ? ` [${node.ontology}]` : '';
|
|
2603
|
+
lines.push(` • ${node.label} (${node.concept_id})${ont}`);
|
|
2604
|
+
}
|
|
2605
|
+
if (result.result.nodes.length > 50) {
|
|
2606
|
+
lines.push(` ... and ${result.result.nodes.length - 50} more`);
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
if (result.result.links.length > 0) {
|
|
2610
|
+
lines.push('\nLinks:');
|
|
2611
|
+
for (const link of result.result.links.slice(0, 30)) {
|
|
2612
|
+
lines.push(` ${link.from_id} → ${link.relationship_type} → ${link.to_id}`);
|
|
2613
|
+
}
|
|
2614
|
+
if (result.result.links.length > 30) {
|
|
2615
|
+
lines.push(` ... and ${result.result.links.length - 30} more`);
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
return {
|
|
2619
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
2620
|
+
isError: !!result.aborted,
|
|
2621
|
+
};
|
|
2622
|
+
}
|
|
2623
|
+
catch (err) {
|
|
2624
|
+
if (err.response?.status === 400) {
|
|
2625
|
+
return {
|
|
2626
|
+
content: [{ type: 'text', text: `✗ Chain error: ${err.response?.data?.detail || err.message}` }],
|
|
2627
|
+
isError: true,
|
|
2628
|
+
};
|
|
2629
|
+
}
|
|
2630
|
+
throw err;
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
default:
|
|
2634
|
+
throw new Error(`Unknown program action: ${action}. Use: validate, create, get, list, execute, or chain`);
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2243
2637
|
default:
|
|
2244
2638
|
throw new Error(`Unknown tool: ${name}`);
|
|
2245
2639
|
}
|
|
@@ -2292,90 +2686,73 @@ server.setRequestHandler(types_js_1.GetPromptRequestSchema, async (request) => {
|
|
|
2292
2686
|
|
|
2293
2687
|
This system transforms documents into semantic concept graphs with grounding strength (reliability scores) and evidence (quoted text).
|
|
2294
2688
|
|
|
2295
|
-
##
|
|
2689
|
+
## Choose Your Approach
|
|
2690
|
+
|
|
2691
|
+
### Quick lookup (1-2 results needed)
|
|
2692
|
+
Use individual tools directly:
|
|
2693
|
+
- **search** — find concepts by semantic similarity (2-3 word phrases)
|
|
2694
|
+
- **concept** (action: "connect") — trace paths between two concepts
|
|
2695
|
+
- **concept** (action: "details") — get all evidence for one concept
|
|
2696
|
+
|
|
2697
|
+
### Multi-step exploration (recommended for most tasks)
|
|
2698
|
+
Compose a **GraphProgram** using the **program** tool. One program replaces
|
|
2699
|
+
multiple individual tool calls and runs entirely server-side:
|
|
2700
|
+
\`\`\`json
|
|
2701
|
+
{ "version": 1, "statements": [
|
|
2702
|
+
{ "op": "+", "operation": { "type": "api", "endpoint": "/search/concepts",
|
|
2703
|
+
"params": { "query": "your topic", "limit": 10 } }, "label": "seed" },
|
|
2704
|
+
{ "op": "+", "operation": { "type": "cypher",
|
|
2705
|
+
"query": "MATCH (c:Concept)-[r]->(t:Concept) WHERE c.concept_id IN $W_IDS RETURN c, r, t" },
|
|
2706
|
+
"label": "expand relationships" }
|
|
2707
|
+
]}
|
|
2708
|
+
\`\`\`
|
|
2709
|
+
Use **program** (action: "execute") to run inline, or (action: "create") to store for reuse.
|
|
2710
|
+
|
|
2711
|
+
### Chained exploration (complex investigations)
|
|
2712
|
+
Stack multiple programs with **program** (action: "chain"). Each program's
|
|
2713
|
+
output becomes the next program's input. Use (action: "list") to find stored
|
|
2714
|
+
programs you can compose.
|
|
2715
|
+
|
|
2716
|
+
Read the **program/syntax** resource for the full language reference with examples.
|
|
2296
2717
|
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
* Authenticated diversity: Support/contradiction indicator (✅✓⚠❌)
|
|
2302
|
-
* Evidence samples: Quoted text from sources
|
|
2303
|
-
* Image indicators: Visual evidence available
|
|
2718
|
+
## Individual Tool Reference
|
|
2719
|
+
|
|
2720
|
+
1. **search** — Find concepts, source passages, or documents
|
|
2721
|
+
- Returns: grounding strength, diversity score, authenticated diversity (✅✓⚠❌), evidence samples
|
|
2304
2722
|
- Use 2-3 word phrases (e.g., "configuration management", "licensing issues")
|
|
2305
2723
|
|
|
2306
|
-
2. **concept (action: connect)
|
|
2307
|
-
-
|
|
2308
|
-
-
|
|
2309
|
-
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
**
|
|
2336
|
-
- **
|
|
2337
|
-
- **
|
|
2338
|
-
- **Negative (<0)**: Contradicted or presented as a problem
|
|
2339
|
-
- **Contradicted (-1.0)**: Often the most interesting - shows pain points!
|
|
2340
|
-
|
|
2341
|
-
**Diversity Score (0-100%)** - Conceptual Richness
|
|
2342
|
-
- High diversity: Concept connects to many different related concepts
|
|
2343
|
-
- Shows breadth of connections in the knowledge graph
|
|
2344
|
-
- Higher scores indicate more interconnected, central concepts
|
|
2345
|
-
|
|
2346
|
-
**Authenticated Diversity (✅✓⚠❌)** - Directional Quality
|
|
2347
|
-
- ✅ Diverse support: Strong positive evidence across many connections
|
|
2348
|
-
- ✓ Some support: Moderate positive evidence
|
|
2349
|
-
- ⚠ Weak contradiction: Some conflicting evidence
|
|
2350
|
-
- ❌ Diverse contradiction: Strong negative evidence across connections
|
|
2351
|
-
- Combines grounding with diversity to show reliability + breadth
|
|
2352
|
-
|
|
2353
|
-
## How to Use the Scores:
|
|
2354
|
-
|
|
2355
|
-
**High Grounding + High Diversity** → Central, well-established concepts
|
|
2356
|
-
- These are reliable anchor points for exploration
|
|
2357
|
-
- Good starting points for connection queries
|
|
2358
|
-
|
|
2359
|
-
**High Grounding + Low Diversity** → Specialized, focused concepts
|
|
2360
|
-
- Domain-specific, niche ideas with strong support
|
|
2361
|
-
- May be isolated or newly added
|
|
2362
|
-
|
|
2363
|
-
**Low/Negative Grounding + High Diversity** → Controversial or evolving concepts
|
|
2364
|
-
- Contested ideas with multiple perspectives
|
|
2365
|
-
- Often the most interesting for understanding debates
|
|
2366
|
-
|
|
2367
|
-
**Authenticated Diversity** → Quick quality check
|
|
2368
|
-
- Use ✅ results confidently
|
|
2369
|
-
- Investigate ⚠ and ❌ results for contradictions and problems
|
|
2370
|
-
|
|
2371
|
-
## Pro Tips:
|
|
2372
|
-
- **Connection queries first**: After search, immediately explore HOW concepts connect - this reveals causality and narrative
|
|
2373
|
-
- Use diversity scores to find central vs. peripheral concepts
|
|
2374
|
-
- Start broad with search, then drill down with concept details
|
|
2375
|
-
- Contradicted concepts reveal what DIDN'T work - goldmines for learning
|
|
2376
|
-
- Connection paths tell stories - follow the evidence chain
|
|
2377
|
-
- Use resources for quick status checks instead of tools
|
|
2378
|
-
- **Performance**: Keep threshold >= 0.75 for connect queries to avoid slow/hung queries`,
|
|
2724
|
+
2. **concept** (action: "connect") — Discover HOW concepts relate
|
|
2725
|
+
- Traces paths: problem→solution, cause→effect
|
|
2726
|
+
- Defaults (threshold=0.5, max_hops=5) match CLI for broad discovery
|
|
2727
|
+
- Often MORE VALUABLE than isolated concept details
|
|
2728
|
+
|
|
2729
|
+
3. **concept** (action: "details") — Complete picture for one concept
|
|
2730
|
+
- ALL quoted evidence + relationships
|
|
2731
|
+
- Contradicted concepts (negative grounding) show problems/outdated approaches
|
|
2732
|
+
|
|
2733
|
+
4. **concept** (action: "related") — Explore neighborhoods
|
|
2734
|
+
- depth=1-2 for neighbors, 3-4 for broader exploration
|
|
2735
|
+
|
|
2736
|
+
## Understanding the Scores
|
|
2737
|
+
|
|
2738
|
+
**Grounding Strength (-1.0 to 1.0)** — Reliability/Contradiction
|
|
2739
|
+
- Positive (>0.7): Well-supported, reliable
|
|
2740
|
+
- Moderate (0.3-0.7): Mixed evidence
|
|
2741
|
+
- Negative (<0): Contradicted — often the most interesting!
|
|
2742
|
+
|
|
2743
|
+
**Diversity Score (0-100%)** — Conceptual Richness
|
|
2744
|
+
- High: Connects to many different related concepts (central)
|
|
2745
|
+
- Low: Specialized or newly added
|
|
2746
|
+
|
|
2747
|
+
**Authenticated Diversity (✅✓⚠❌)** — Combines grounding + diversity
|
|
2748
|
+
- ✅ Use confidently · ✓ Moderate support · ⚠ Investigate · ❌ Contradicted
|
|
2749
|
+
|
|
2750
|
+
## Tips
|
|
2751
|
+
- **Programs over individual calls**: A 3-statement program is faster and cheaper than 3 tool calls
|
|
2752
|
+
- **Contradictions are valuable**: Negative grounding reveals pain points and what didn't work
|
|
2753
|
+
- **Connection paths tell stories**: Follow the evidence chain between concepts
|
|
2754
|
+
- **Use resources for status**: database/stats, system/status — no tool budget cost
|
|
2755
|
+
- **Defaults match CLI**: threshold=0.5, max_hops=5 — broad discovery out of the box`,
|
|
2379
2756
|
},
|
|
2380
2757
|
},
|
|
2381
2758
|
],
|
|
@@ -2383,6 +2760,308 @@ Use MCP resources for quick status checks without consuming tool budget:
|
|
|
2383
2760
|
}
|
|
2384
2761
|
throw new Error(`Unknown prompt: ${name}`);
|
|
2385
2762
|
});
|
|
2763
|
+
/**
|
|
2764
|
+
* GraphProgram language reference for agent consumption (ADR-500).
|
|
2765
|
+
* Returned by the program/syntax MCP resource.
|
|
2766
|
+
*/
|
|
2767
|
+
function getGraphProgramSyntaxReference() {
|
|
2768
|
+
return `# GraphProgram Language Reference (ADR-500)
|
|
2769
|
+
|
|
2770
|
+
GraphProgram is a JSON-based query composition language for the knowledge graph.
|
|
2771
|
+
Programs compose openCypher queries and REST API calls using set-algebra operators
|
|
2772
|
+
to build a mutable Working Graph (W) from the persistent Source Graph (H).
|
|
2773
|
+
|
|
2774
|
+
## Program Structure
|
|
2775
|
+
|
|
2776
|
+
\`\`\`json
|
|
2777
|
+
{
|
|
2778
|
+
"version": 1,
|
|
2779
|
+
"metadata": { "name": "...", "description": "...", "author": "agent" },
|
|
2780
|
+
"statements": [
|
|
2781
|
+
{ "op": "+", "operation": { ... }, "label": "description of this step" }
|
|
2782
|
+
]
|
|
2783
|
+
}
|
|
2784
|
+
\`\`\`
|
|
2785
|
+
|
|
2786
|
+
- version: Must be 1 (integer)
|
|
2787
|
+
- metadata: Optional. author can be "human", "agent", or "system"
|
|
2788
|
+
- statements: Non-empty array. Executed sequentially. Min 1, max 100.
|
|
2789
|
+
|
|
2790
|
+
## Operators
|
|
2791
|
+
|
|
2792
|
+
Each statement has an "op" that controls how the operation's result (R) modifies W:
|
|
2793
|
+
|
|
2794
|
+
+ UNION — Merge R into W. Dedup by concept_id (nodes) and (from_id, relationship_type, to_id) (links). W wins on collision.
|
|
2795
|
+
- DIFFERENCE — Remove R's nodes from W by concept_id. Links referencing removed nodes are cascade-removed.
|
|
2796
|
+
& INTERSECT — Keep only W nodes whose concept_id appears in R. Remove all others + dangling links.
|
|
2797
|
+
? OPTIONAL — If R is non-empty: apply union. If R is empty: silent no-op. Use for exploratory steps.
|
|
2798
|
+
! ASSERT — If R is non-empty: apply union. If R is empty: ABORT the entire program. Use for guard steps.
|
|
2799
|
+
|
|
2800
|
+
Invariant: After every operator, links whose from_id or to_id has no matching node are removed (dangling link invariant).
|
|
2801
|
+
|
|
2802
|
+
## Operation Types
|
|
2803
|
+
|
|
2804
|
+
### CypherOp — Query the source graph with openCypher
|
|
2805
|
+
|
|
2806
|
+
\`\`\`json
|
|
2807
|
+
{
|
|
2808
|
+
"type": "cypher",
|
|
2809
|
+
"query": "MATCH (c:Concept)-[r]->(t:Concept) WHERE c.ontology = 'physics' RETURN c, r, t",
|
|
2810
|
+
"limit": 20
|
|
2811
|
+
}
|
|
2812
|
+
\`\`\`
|
|
2813
|
+
|
|
2814
|
+
- query: Read-only openCypher. Must RETURN nodes (c) and/or relationships (r). No CREATE/SET/DELETE/MERGE.
|
|
2815
|
+
- limit: Optional positive integer. Appended as LIMIT if query doesn't already have one.
|
|
2816
|
+
|
|
2817
|
+
Common Cypher patterns:
|
|
2818
|
+
MATCH (c:Concept) RETURN c LIMIT 10 — fetch concepts
|
|
2819
|
+
MATCH (c:Concept)-[r]->(t:Concept) RETURN c, r, t LIMIT 20 — concepts + relationships
|
|
2820
|
+
MATCH (c:Concept) WHERE c.ontology = 'name' RETURN c — filter by ontology
|
|
2821
|
+
MATCH (c:Concept) WHERE c.concept_id IN ['id1','id2'] RETURN c — batch by ID
|
|
2822
|
+
MATCH p=(a:Concept)-[*1..3]->(b:Concept) RETURN p — paths (max depth 6)
|
|
2823
|
+
|
|
2824
|
+
### ApiOp — Call internal service functions
|
|
2825
|
+
|
|
2826
|
+
\`\`\`json
|
|
2827
|
+
{
|
|
2828
|
+
"type": "api",
|
|
2829
|
+
"endpoint": "/search/concepts",
|
|
2830
|
+
"params": { "query": "neural networks", "limit": 10, "min_similarity": 0.7 }
|
|
2831
|
+
}
|
|
2832
|
+
\`\`\`
|
|
2833
|
+
|
|
2834
|
+
Allowed endpoints and their parameters:
|
|
2835
|
+
|
|
2836
|
+
/search/concepts
|
|
2837
|
+
Required: query (string) — semantic search text
|
|
2838
|
+
Optional: min_similarity (number, default 0.7), limit (integer, default 10)
|
|
2839
|
+
Returns: concept nodes matching the search
|
|
2840
|
+
|
|
2841
|
+
/search/sources
|
|
2842
|
+
Required: query (string) — semantic search text
|
|
2843
|
+
Optional: min_similarity (number, default 0.7), limit (integer, default 10)
|
|
2844
|
+
Returns: concept nodes extracted from matching source passages
|
|
2845
|
+
|
|
2846
|
+
/concepts/details
|
|
2847
|
+
Required: concept_id (string)
|
|
2848
|
+
Returns: the concept node + its outgoing relationships and target nodes
|
|
2849
|
+
|
|
2850
|
+
/concepts/related
|
|
2851
|
+
Required: concept_id (string)
|
|
2852
|
+
Optional: max_depth (integer, default 2), relationship_types (string array)
|
|
2853
|
+
Returns: neighborhood concept nodes
|
|
2854
|
+
|
|
2855
|
+
/concepts/batch
|
|
2856
|
+
Required: concept_ids (string array)
|
|
2857
|
+
Returns: concept nodes for all matching IDs
|
|
2858
|
+
|
|
2859
|
+
/vocabulary/status
|
|
2860
|
+
Optional: relationship_type (string), status_filter (string)
|
|
2861
|
+
Returns: vocabulary type nodes with epistemic status metadata
|
|
2862
|
+
|
|
2863
|
+
### ConditionalOp — Branch based on Working Graph state
|
|
2864
|
+
|
|
2865
|
+
\`\`\`json
|
|
2866
|
+
{
|
|
2867
|
+
"type": "conditional",
|
|
2868
|
+
"condition": { "test": "has_results" },
|
|
2869
|
+
"then": [ { "op": "+", "operation": { ... } } ],
|
|
2870
|
+
"else": [ { "op": "+", "operation": { ... } } ]
|
|
2871
|
+
}
|
|
2872
|
+
\`\`\`
|
|
2873
|
+
|
|
2874
|
+
Condition tests (evaluated against current W):
|
|
2875
|
+
has_results — W has at least one node
|
|
2876
|
+
empty — W has zero nodes
|
|
2877
|
+
count_gte — W node count >= value (needs "value": number)
|
|
2878
|
+
count_lte — W node count <= value (needs "value": number)
|
|
2879
|
+
has_ontology — any W node has matching ontology (needs "ontology": string)
|
|
2880
|
+
has_relationship — any W link has matching type (needs "type": string)
|
|
2881
|
+
|
|
2882
|
+
Max nesting depth: 3.
|
|
2883
|
+
|
|
2884
|
+
## Working Graph Shape
|
|
2885
|
+
|
|
2886
|
+
Nodes are keyed by concept_id (string). Links are keyed by (from_id, relationship_type, to_id).
|
|
2887
|
+
|
|
2888
|
+
Node: { concept_id, label, ontology?, description?, properties? }
|
|
2889
|
+
Link: { from_id, to_id, relationship_type, category?, confidence? }
|
|
2890
|
+
|
|
2891
|
+
## Examples
|
|
2892
|
+
|
|
2893
|
+
### 1. Simple concept search + relationship expansion
|
|
2894
|
+
\`\`\`json
|
|
2895
|
+
{
|
|
2896
|
+
"version": 1,
|
|
2897
|
+
"metadata": { "name": "explore topic", "author": "agent" },
|
|
2898
|
+
"statements": [
|
|
2899
|
+
{
|
|
2900
|
+
"op": "+",
|
|
2901
|
+
"operation": { "type": "api", "endpoint": "/search/concepts",
|
|
2902
|
+
"params": { "query": "machine learning", "limit": 5 } },
|
|
2903
|
+
"label": "seed with search results"
|
|
2904
|
+
},
|
|
2905
|
+
{
|
|
2906
|
+
"op": "+",
|
|
2907
|
+
"operation": { "type": "cypher",
|
|
2908
|
+
"query": "MATCH (c:Concept)-[r]->(t:Concept) RETURN c, r, t LIMIT 30" },
|
|
2909
|
+
"label": "add relationships between any concepts"
|
|
2910
|
+
}
|
|
2911
|
+
]
|
|
2912
|
+
}
|
|
2913
|
+
\`\`\`
|
|
2914
|
+
|
|
2915
|
+
### 2. Ontology-filtered exploration
|
|
2916
|
+
\`\`\`json
|
|
2917
|
+
{
|
|
2918
|
+
"version": 1,
|
|
2919
|
+
"metadata": { "name": "ontology deep dive", "author": "agent" },
|
|
2920
|
+
"statements": [
|
|
2921
|
+
{
|
|
2922
|
+
"op": "+",
|
|
2923
|
+
"operation": { "type": "cypher",
|
|
2924
|
+
"query": "MATCH (c:Concept) WHERE c.ontology = 'distributed-systems' RETURN c LIMIT 20" },
|
|
2925
|
+
"label": "get ontology concepts"
|
|
2926
|
+
},
|
|
2927
|
+
{
|
|
2928
|
+
"op": "+",
|
|
2929
|
+
"operation": { "type": "cypher",
|
|
2930
|
+
"query": "MATCH (c:Concept)-[r]->(t:Concept) WHERE c.ontology = 'distributed-systems' AND t.ontology = 'distributed-systems' RETURN c, r, t" },
|
|
2931
|
+
"label": "intra-ontology relationships"
|
|
2932
|
+
}
|
|
2933
|
+
]
|
|
2934
|
+
}
|
|
2935
|
+
\`\`\`
|
|
2936
|
+
|
|
2937
|
+
### 3. Assert + conditional pattern
|
|
2938
|
+
\`\`\`json
|
|
2939
|
+
{
|
|
2940
|
+
"version": 1,
|
|
2941
|
+
"metadata": { "name": "guarded expansion", "author": "agent" },
|
|
2942
|
+
"statements": [
|
|
2943
|
+
{
|
|
2944
|
+
"op": "!",
|
|
2945
|
+
"operation": { "type": "api", "endpoint": "/search/concepts",
|
|
2946
|
+
"params": { "query": "quantum computing", "limit": 3, "min_similarity": 0.8 } },
|
|
2947
|
+
"label": "must find quantum concepts (abort if not)"
|
|
2948
|
+
},
|
|
2949
|
+
{
|
|
2950
|
+
"op": "?",
|
|
2951
|
+
"operation": { "type": "api", "endpoint": "/search/concepts",
|
|
2952
|
+
"params": { "query": "quantum entanglement", "limit": 5 } },
|
|
2953
|
+
"label": "optionally add entanglement concepts"
|
|
2954
|
+
},
|
|
2955
|
+
{
|
|
2956
|
+
"op": "+",
|
|
2957
|
+
"operation": { "type": "cypher",
|
|
2958
|
+
"query": "MATCH (c:Concept)-[r:SUPPORTS|IMPLIES]->(t:Concept) RETURN c, r, t LIMIT 50" },
|
|
2959
|
+
"label": "add supporting/implied relationships"
|
|
2960
|
+
}
|
|
2961
|
+
]
|
|
2962
|
+
}
|
|
2963
|
+
\`\`\`
|
|
2964
|
+
|
|
2965
|
+
### 4. Cross-ontology comparison (intersect)
|
|
2966
|
+
\`\`\`json
|
|
2967
|
+
{
|
|
2968
|
+
"version": 1,
|
|
2969
|
+
"metadata": { "name": "cross-ontology overlap", "author": "agent" },
|
|
2970
|
+
"statements": [
|
|
2971
|
+
{
|
|
2972
|
+
"op": "+",
|
|
2973
|
+
"operation": { "type": "cypher",
|
|
2974
|
+
"query": "MATCH (c:Concept) WHERE c.ontology = 'machine-learning' RETURN c" },
|
|
2975
|
+
"label": "load ML concepts"
|
|
2976
|
+
},
|
|
2977
|
+
{
|
|
2978
|
+
"op": "&",
|
|
2979
|
+
"operation": { "type": "cypher",
|
|
2980
|
+
"query": "MATCH (c:Concept)-[r]->(t:Concept) WHERE t.ontology = 'statistics' RETURN c" },
|
|
2981
|
+
"label": "keep only ML concepts that relate to statistics"
|
|
2982
|
+
}
|
|
2983
|
+
]
|
|
2984
|
+
}
|
|
2985
|
+
\`\`\`
|
|
2986
|
+
|
|
2987
|
+
### 5. Enrichment pipeline (concept details)
|
|
2988
|
+
\`\`\`json
|
|
2989
|
+
{
|
|
2990
|
+
"version": 1,
|
|
2991
|
+
"metadata": { "name": "enrich concepts", "author": "agent" },
|
|
2992
|
+
"statements": [
|
|
2993
|
+
{
|
|
2994
|
+
"op": "+",
|
|
2995
|
+
"operation": { "type": "api", "endpoint": "/search/concepts",
|
|
2996
|
+
"params": { "query": "neural architecture", "limit": 3 } },
|
|
2997
|
+
"label": "find seed concepts"
|
|
2998
|
+
},
|
|
2999
|
+
{
|
|
3000
|
+
"op": "+",
|
|
3001
|
+
"operation": { "type": "api", "endpoint": "/concepts/details",
|
|
3002
|
+
"params": { "concept_id": "CONCEPT_ID_FROM_SEARCH" } },
|
|
3003
|
+
"label": "enrich first concept with relationships"
|
|
3004
|
+
}
|
|
3005
|
+
]
|
|
3006
|
+
}
|
|
3007
|
+
\`\`\`
|
|
3008
|
+
Note: For enrichment, you typically execute a search first, inspect the results,
|
|
3009
|
+
then compose a second program using specific concept_ids from the first result.
|
|
3010
|
+
|
|
3011
|
+
## Execution
|
|
3012
|
+
|
|
3013
|
+
Use the program tool with action "execute":
|
|
3014
|
+
- Inline: { action: "execute", program: { version: 1, statements: [...] } }
|
|
3015
|
+
- Stored: { action: "execute", program_id: 42 }
|
|
3016
|
+
|
|
3017
|
+
The response includes:
|
|
3018
|
+
- result: { nodes: [...], links: [...] } — the final Working Graph
|
|
3019
|
+
- log: step-by-step execution record with timing
|
|
3020
|
+
- aborted: present only if an assert (!) failed
|
|
3021
|
+
|
|
3022
|
+
## Tips for Agent Program Composition
|
|
3023
|
+
|
|
3024
|
+
1. Start with + (union) to seed W with initial data.
|
|
3025
|
+
2. Use ? (optional) for speculative searches that might return nothing.
|
|
3026
|
+
3. Use ! (assert) for critical steps — if the data doesn't exist, abort early.
|
|
3027
|
+
4. Use - (difference) to remove unwanted nodes and their links.
|
|
3028
|
+
5. Use & (intersect) to filter W down to a specific subset.
|
|
3029
|
+
6. Cypher queries that RETURN c, r, t give both nodes and relationships.
|
|
3030
|
+
7. API searches return only nodes. Use Cypher to add relationships between them.
|
|
3031
|
+
8. Validate before execute: use action "validate" to catch errors without side effects.
|
|
3032
|
+
9. Programs are bounded: max 100 operations, max conditional nesting depth 3.
|
|
3033
|
+
10. All queries are read-only. Programs cannot modify the source graph.
|
|
3034
|
+
11. Use "list" to discover stored programs, then "chain" to compose them.
|
|
3035
|
+
12. Chain programs that build on each other: seed → expand → filter → enrich.
|
|
3036
|
+
|
|
3037
|
+
## Program Chaining (Deck Mode)
|
|
3038
|
+
|
|
3039
|
+
Chain multiple programs into a single execution. W threads through each program
|
|
3040
|
+
sequentially — program N's final W becomes program N+1's initial W.
|
|
3041
|
+
|
|
3042
|
+
Use the program tool with action "chain" and a "deck" array:
|
|
3043
|
+
{ action: "chain", deck: [
|
|
3044
|
+
{ program_id: 1 },
|
|
3045
|
+
{ program_id: 5 },
|
|
3046
|
+
{ program: { version: 1, statements: [...] } }
|
|
3047
|
+
]}
|
|
3048
|
+
|
|
3049
|
+
Each deck entry can be a stored program (program_id) or an inline AST (program).
|
|
3050
|
+
Max 10 programs per chain. If any program aborts (! assert fails), the chain stops.
|
|
3051
|
+
|
|
3052
|
+
The response includes:
|
|
3053
|
+
- result: final W after all programs
|
|
3054
|
+
- programs: per-program results (each with own log)
|
|
3055
|
+
- aborted: present only if any program aborted
|
|
3056
|
+
|
|
3057
|
+
## Discovering Programs
|
|
3058
|
+
|
|
3059
|
+
Use action "list" to find stored programs:
|
|
3060
|
+
{ action: "list" } — list all
|
|
3061
|
+
{ action: "list", search: "ontology" } — search by name/description
|
|
3062
|
+
|
|
3063
|
+
Returns: id, name, description, statement_count. Use the IDs in chain decks.`;
|
|
3064
|
+
}
|
|
2386
3065
|
/**
|
|
2387
3066
|
* Start the MCP server
|
|
2388
3067
|
*/
|