@aaronsb/kg-cli 0.6.1
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/README.md +112 -0
- package/dist/api/client.d.ts +867 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +1362 -0
- package/dist/api/client.js.map +1 -0
- package/dist/cli/admin/backup.d.ts +9 -0
- package/dist/cli/admin/backup.d.ts.map +1 -0
- package/dist/cli/admin/backup.js +363 -0
- package/dist/cli/admin/backup.js.map +1 -0
- package/dist/cli/admin/index.d.ts +7 -0
- package/dist/cli/admin/index.d.ts.map +1 -0
- package/dist/cli/admin/index.js +52 -0
- package/dist/cli/admin/index.js.map +1 -0
- package/dist/cli/admin/scheduler.d.ts +7 -0
- package/dist/cli/admin/scheduler.d.ts.map +1 -0
- package/dist/cli/admin/scheduler.js +125 -0
- package/dist/cli/admin/scheduler.js.map +1 -0
- package/dist/cli/admin/status.d.ts +7 -0
- package/dist/cli/admin/status.d.ts.map +1 -0
- package/dist/cli/admin/status.js +134 -0
- package/dist/cli/admin/status.js.map +1 -0
- package/dist/cli/admin/utils.d.ts +34 -0
- package/dist/cli/admin/utils.d.ts.map +1 -0
- package/dist/cli/admin/utils.js +441 -0
- package/dist/cli/admin/utils.js.map +1 -0
- package/dist/cli/ai-config/embedding.d.ts +11 -0
- package/dist/cli/ai-config/embedding.d.ts.map +1 -0
- package/dist/cli/ai-config/embedding.js +598 -0
- package/dist/cli/ai-config/embedding.js.map +1 -0
- package/dist/cli/ai-config/extraction.d.ts +11 -0
- package/dist/cli/ai-config/extraction.d.ts.map +1 -0
- package/dist/cli/ai-config/extraction.js +206 -0
- package/dist/cli/ai-config/extraction.js.map +1 -0
- package/dist/cli/ai-config/index.d.ts +21 -0
- package/dist/cli/ai-config/index.d.ts.map +1 -0
- package/dist/cli/ai-config/index.js +27 -0
- package/dist/cli/ai-config/index.js.map +1 -0
- package/dist/cli/ai-config/keys.d.ts +11 -0
- package/dist/cli/ai-config/keys.d.ts.map +1 -0
- package/dist/cli/ai-config/keys.js +182 -0
- package/dist/cli/ai-config/keys.js.map +1 -0
- package/dist/cli/ai-config/utils.d.ts +13 -0
- package/dist/cli/ai-config/utils.d.ts.map +1 -0
- package/dist/cli/ai-config/utils.js +84 -0
- package/dist/cli/ai-config/utils.js.map +1 -0
- package/dist/cli/artifact.d.ts +8 -0
- package/dist/cli/artifact.d.ts.map +1 -0
- package/dist/cli/artifact.js +296 -0
- package/dist/cli/artifact.js.map +1 -0
- package/dist/cli/auth-admin.d.ts +11 -0
- package/dist/cli/auth-admin.d.ts.map +1 -0
- package/dist/cli/auth-admin.js +415 -0
- package/dist/cli/auth-admin.js.map +1 -0
- package/dist/cli/colors.d.ts +105 -0
- package/dist/cli/colors.d.ts.map +1 -0
- package/dist/cli/colors.js +164 -0
- package/dist/cli/colors.js.map +1 -0
- package/dist/cli/commands.d.ts +6 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +164 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/config.d.ts +6 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +694 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/curve-viz.d.ts +89 -0
- package/dist/cli/curve-viz.d.ts.map +1 -0
- package/dist/cli/curve-viz.js +228 -0
- package/dist/cli/curve-viz.js.map +1 -0
- package/dist/cli/database.d.ts +6 -0
- package/dist/cli/database.d.ts.map +1 -0
- package/dist/cli/database.js +324 -0
- package/dist/cli/database.js.map +1 -0
- package/dist/cli/document.d.ts +6 -0
- package/dist/cli/document.d.ts.map +1 -0
- package/dist/cli/document.js +458 -0
- package/dist/cli/document.js.map +1 -0
- package/dist/cli/group.d.ts +8 -0
- package/dist/cli/group.d.ts.map +1 -0
- package/dist/cli/group.js +174 -0
- package/dist/cli/group.js.map +1 -0
- package/dist/cli/health.d.ts +6 -0
- package/dist/cli/health.d.ts.map +1 -0
- package/dist/cli/health.js +34 -0
- package/dist/cli/health.js.map +1 -0
- package/dist/cli/help-formatter.d.ts +16 -0
- package/dist/cli/help-formatter.d.ts.map +1 -0
- package/dist/cli/help-formatter.js +248 -0
- package/dist/cli/help-formatter.js.map +1 -0
- package/dist/cli/help.d.ts +9 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/cli/help.js +227 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/ingest.d.ts +6 -0
- package/dist/cli/ingest.d.ts.map +1 -0
- package/dist/cli/ingest.js +722 -0
- package/dist/cli/ingest.js.map +1 -0
- package/dist/cli/jobs.d.ts +6 -0
- package/dist/cli/jobs.d.ts.map +1 -0
- package/dist/cli/jobs.js +663 -0
- package/dist/cli/jobs.js.map +1 -0
- package/dist/cli/login.d.ts +21 -0
- package/dist/cli/login.d.ts.map +1 -0
- package/dist/cli/login.js +221 -0
- package/dist/cli/login.js.map +1 -0
- package/dist/cli/logout.d.ts +16 -0
- package/dist/cli/logout.d.ts.map +1 -0
- package/dist/cli/logout.js +141 -0
- package/dist/cli/logout.js.map +1 -0
- package/dist/cli/mcp-config.d.ts +10 -0
- package/dist/cli/mcp-config.d.ts.map +1 -0
- package/dist/cli/mcp-config.js +358 -0
- package/dist/cli/mcp-config.js.map +1 -0
- package/dist/cli/oauth.d.ts +15 -0
- package/dist/cli/oauth.d.ts.map +1 -0
- package/dist/cli/oauth.js +296 -0
- package/dist/cli/oauth.js.map +1 -0
- package/dist/cli/ontology.d.ts +6 -0
- package/dist/cli/ontology.d.ts.map +1 -0
- package/dist/cli/ontology.js +231 -0
- package/dist/cli/ontology.js.map +1 -0
- package/dist/cli/polarity.d.ts +6 -0
- package/dist/cli/polarity.d.ts.map +1 -0
- package/dist/cli/polarity.js +295 -0
- package/dist/cli/polarity.js.map +1 -0
- package/dist/cli/projection.d.ts +8 -0
- package/dist/cli/projection.d.ts.map +1 -0
- package/dist/cli/projection.js +297 -0
- package/dist/cli/projection.js.map +1 -0
- package/dist/cli/query-def.d.ts +8 -0
- package/dist/cli/query-def.d.ts.map +1 -0
- package/dist/cli/query-def.js +163 -0
- package/dist/cli/query-def.js.map +1 -0
- package/dist/cli/rbac.d.ts +12 -0
- package/dist/cli/rbac.d.ts.map +1 -0
- package/dist/cli/rbac.js +615 -0
- package/dist/cli/rbac.js.map +1 -0
- package/dist/cli/search.d.ts +6 -0
- package/dist/cli/search.d.ts.map +1 -0
- package/dist/cli/search.js +829 -0
- package/dist/cli/search.js.map +1 -0
- package/dist/cli/source.d.ts +6 -0
- package/dist/cli/source.d.ts.map +1 -0
- package/dist/cli/source.js +202 -0
- package/dist/cli/source.js.map +1 -0
- package/dist/cli/verb-router.d.ts +25 -0
- package/dist/cli/verb-router.d.ts.map +1 -0
- package/dist/cli/verb-router.js +415 -0
- package/dist/cli/verb-router.js.map +1 -0
- package/dist/cli/vocabulary/config.d.ts +7 -0
- package/dist/cli/vocabulary/config.d.ts.map +1 -0
- package/dist/cli/vocabulary/config.js +201 -0
- package/dist/cli/vocabulary/config.js.map +1 -0
- package/dist/cli/vocabulary/consolidate.d.ts +8 -0
- package/dist/cli/vocabulary/consolidate.d.ts.map +1 -0
- package/dist/cli/vocabulary/consolidate.js +192 -0
- package/dist/cli/vocabulary/consolidate.js.map +1 -0
- package/dist/cli/vocabulary/embeddings.d.ts +9 -0
- package/dist/cli/vocabulary/embeddings.d.ts.map +1 -0
- package/dist/cli/vocabulary/embeddings.js +205 -0
- package/dist/cli/vocabulary/embeddings.js.map +1 -0
- package/dist/cli/vocabulary/epistemic.d.ts +7 -0
- package/dist/cli/vocabulary/epistemic.d.ts.map +1 -0
- package/dist/cli/vocabulary/epistemic.js +315 -0
- package/dist/cli/vocabulary/epistemic.js.map +1 -0
- package/dist/cli/vocabulary/index.d.ts +7 -0
- package/dist/cli/vocabulary/index.d.ts.map +1 -0
- package/dist/cli/vocabulary/index.js +45 -0
- package/dist/cli/vocabulary/index.js.map +1 -0
- package/dist/cli/vocabulary/profiles.d.ts +7 -0
- package/dist/cli/vocabulary/profiles.d.ts.map +1 -0
- package/dist/cli/vocabulary/profiles.js +171 -0
- package/dist/cli/vocabulary/profiles.js.map +1 -0
- package/dist/cli/vocabulary/similarity.d.ts +9 -0
- package/dist/cli/vocabulary/similarity.d.ts.map +1 -0
- package/dist/cli/vocabulary/similarity.js +199 -0
- package/dist/cli/vocabulary/similarity.js.map +1 -0
- package/dist/cli/vocabulary/status.d.ts +8 -0
- package/dist/cli/vocabulary/status.d.ts.map +1 -0
- package/dist/cli/vocabulary/status.js +280 -0
- package/dist/cli/vocabulary/status.js.map +1 -0
- package/dist/cli/vocabulary/sync.d.ts +7 -0
- package/dist/cli/vocabulary/sync.d.ts.map +1 -0
- package/dist/cli/vocabulary/sync.js +111 -0
- package/dist/cli/vocabulary/sync.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/auth/auth-client.d.ts +247 -0
- package/dist/lib/auth/auth-client.d.ts.map +1 -0
- package/dist/lib/auth/auth-client.js +305 -0
- package/dist/lib/auth/auth-client.js.map +1 -0
- package/dist/lib/auth/challenge.d.ts +39 -0
- package/dist/lib/auth/challenge.d.ts.map +1 -0
- package/dist/lib/auth/challenge.js +125 -0
- package/dist/lib/auth/challenge.js.map +1 -0
- package/dist/lib/auth/client-credentials-flow.d.ts +58 -0
- package/dist/lib/auth/client-credentials-flow.d.ts.map +1 -0
- package/dist/lib/auth/client-credentials-flow.js +118 -0
- package/dist/lib/auth/client-credentials-flow.js.map +1 -0
- package/dist/lib/auth/device-flow.d.ts +75 -0
- package/dist/lib/auth/device-flow.d.ts.map +1 -0
- package/dist/lib/auth/device-flow.js +177 -0
- package/dist/lib/auth/device-flow.js.map +1 -0
- package/dist/lib/auth/index.d.ts +14 -0
- package/dist/lib/auth/index.d.ts.map +1 -0
- package/dist/lib/auth/index.js +34 -0
- package/dist/lib/auth/index.js.map +1 -0
- package/dist/lib/auth/oauth-types.d.ts +69 -0
- package/dist/lib/auth/oauth-types.d.ts.map +1 -0
- package/dist/lib/auth/oauth-types.js +10 -0
- package/dist/lib/auth/oauth-types.js.map +1 -0
- package/dist/lib/auth/oauth-utils.d.ts +51 -0
- package/dist/lib/auth/oauth-utils.d.ts.map +1 -0
- package/dist/lib/auth/oauth-utils.js +110 -0
- package/dist/lib/auth/oauth-utils.js.map +1 -0
- package/dist/lib/auth/token-manager.d.ts +87 -0
- package/dist/lib/auth/token-manager.d.ts.map +1 -0
- package/dist/lib/auth/token-manager.js +139 -0
- package/dist/lib/auth/token-manager.js.map +1 -0
- package/dist/lib/auth/token-refresh.d.ts +63 -0
- package/dist/lib/auth/token-refresh.d.ts.map +1 -0
- package/dist/lib/auth/token-refresh.js +141 -0
- package/dist/lib/auth/token-refresh.js.map +1 -0
- package/dist/lib/config.d.ts +286 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +537 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/job-stream.d.ts +53 -0
- package/dist/lib/job-stream.d.ts.map +1 -0
- package/dist/lib/job-stream.js +153 -0
- package/dist/lib/job-stream.js.map +1 -0
- package/dist/lib/mcp-allowlist.d.ts +101 -0
- package/dist/lib/mcp-allowlist.d.ts.map +1 -0
- package/dist/lib/mcp-allowlist.js +340 -0
- package/dist/lib/mcp-allowlist.js.map +1 -0
- package/dist/lib/table-example.d.ts +7 -0
- package/dist/lib/table-example.d.ts.map +1 -0
- package/dist/lib/table-example.js +105 -0
- package/dist/lib/table-example.js.map +1 -0
- package/dist/lib/table.d.ts +95 -0
- package/dist/lib/table.d.ts.map +1 -0
- package/dist/lib/table.js +263 -0
- package/dist/lib/table.js.map +1 -0
- package/dist/lib/terminal-images.d.ts +66 -0
- package/dist/lib/terminal-images.d.ts.map +1 -0
- package/dist/lib/terminal-images.js +268 -0
- package/dist/lib/terminal-images.js.map +1 -0
- package/dist/mcp/formatters.d.ts +100 -0
- package/dist/mcp/formatters.d.ts.map +1 -0
- package/dist/mcp/formatters.js +1411 -0
- package/dist/mcp/formatters.js.map +1 -0
- package/dist/mcp-server.d.ts +9 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +1810 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/types/index.d.ts +742 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/dist/version.d.ts +10 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +13 -0
- package/dist/version.js.map +1 -0
- package/package.json +84 -0
|
@@ -0,0 +1,1810 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Knowledge Graph MCP Server
|
|
5
|
+
*
|
|
6
|
+
* Standalone MCP server exposing knowledge graph operations as MCP tools.
|
|
7
|
+
* Reuses the existing API client and types from the CLI.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
44
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
45
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
46
|
+
const client_js_1 = require("./api/client.js");
|
|
47
|
+
const auth_client_js_1 = require("./lib/auth/auth-client.js");
|
|
48
|
+
const mcp_allowlist_js_1 = require("./lib/mcp-allowlist.js");
|
|
49
|
+
const formatters_js_1 = require("./mcp/formatters.js");
|
|
50
|
+
const fs = __importStar(require("fs"));
|
|
51
|
+
const path = __importStar(require("path"));
|
|
52
|
+
/**
|
|
53
|
+
* Default parameters for graph queries (ADR-048 Query Safety)
|
|
54
|
+
*
|
|
55
|
+
* These defaults balance performance and result quality:
|
|
56
|
+
* - Higher thresholds (0.75+) prevent expensive full-graph scans
|
|
57
|
+
* - Lower max_hops (3) prevent exponential traversal explosion
|
|
58
|
+
* - Adjust based on graph size and performance characteristics
|
|
59
|
+
*/
|
|
60
|
+
const DEFAULT_SEARCH_SIMILARITY = 0.7; // Search tool minimum similarity
|
|
61
|
+
const DEFAULT_SEMANTIC_THRESHOLD = 0.75; // Connect queries semantic matching
|
|
62
|
+
const DEFAULT_MAX_HOPS = 3; // Maximum path traversal depth
|
|
63
|
+
const DEFAULT_MAX_DEPTH = 2; // Related concepts neighborhood depth
|
|
64
|
+
// Create server instance
|
|
65
|
+
const server = new index_js_1.Server({
|
|
66
|
+
name: 'knowledge-graph-server',
|
|
67
|
+
version: '0.1.0',
|
|
68
|
+
}, {
|
|
69
|
+
capabilities: {
|
|
70
|
+
tools: {},
|
|
71
|
+
prompts: {},
|
|
72
|
+
resources: {},
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
/**
|
|
76
|
+
* Knowledge Graph Server - Exploration Guide
|
|
77
|
+
*
|
|
78
|
+
* This system transforms documents into semantic concept graphs with multi-dimensional scoring:
|
|
79
|
+
* - Grounding strength (-1.0 to 1.0): Reliability/contradiction score
|
|
80
|
+
* - Diversity score (0-100%): Conceptual richness and connection breadth
|
|
81
|
+
* - Authenticated diversity (✅✓⚠❌): Directional quality combining grounding + diversity
|
|
82
|
+
*
|
|
83
|
+
* Explore by:
|
|
84
|
+
* 1. search - Find entry points (returns all scores + evidence samples)
|
|
85
|
+
* 2. concept - Work with concepts (details, related, connections)
|
|
86
|
+
* 3. ontology - Manage ontologies (list, info, files, delete)
|
|
87
|
+
* 4. job - Manage jobs (status, list, approve, cancel)
|
|
88
|
+
* 5. ingest - Ingest content (text, inspect-file, file, directory)
|
|
89
|
+
* 6. source - Retrieve source images for visual verification
|
|
90
|
+
*
|
|
91
|
+
* Resources provide fresh data on-demand without consuming tool budget:
|
|
92
|
+
* - database/stats, database/info, database/health
|
|
93
|
+
* - system/status, api/health
|
|
94
|
+
*
|
|
95
|
+
* Use high grounding + high diversity to find reliable, central concepts.
|
|
96
|
+
* Negative grounding often shows the most interesting problems/contradictions.
|
|
97
|
+
*/
|
|
98
|
+
// OAuth access token storage for authenticated session (ADR-054)
|
|
99
|
+
let oauthAccessToken = null;
|
|
100
|
+
let tokenRefreshTimer = null;
|
|
101
|
+
/**
|
|
102
|
+
* Get OAuth access token using client credentials grant (ADR-054)
|
|
103
|
+
* Returns the token and expiry time in milliseconds
|
|
104
|
+
*/
|
|
105
|
+
async function getOAuthAccessToken() {
|
|
106
|
+
const clientId = process.env.KG_OAUTH_CLIENT_ID;
|
|
107
|
+
const clientSecret = process.env.KG_OAUTH_CLIENT_SECRET;
|
|
108
|
+
const apiUrl = process.env.KG_API_URL || 'http://localhost:8000';
|
|
109
|
+
if (!clientId || !clientSecret) {
|
|
110
|
+
console.error('[MCP Auth] Missing OAuth credentials: KG_OAUTH_CLIENT_ID and KG_OAUTH_CLIENT_SECRET required');
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const authClient = new auth_client_js_1.AuthClient(apiUrl);
|
|
115
|
+
const tokenResponse = await authClient.getOAuthToken({
|
|
116
|
+
grant_type: 'client_credentials',
|
|
117
|
+
client_id: clientId,
|
|
118
|
+
client_secret: clientSecret,
|
|
119
|
+
scope: 'read:* write:*'
|
|
120
|
+
});
|
|
121
|
+
const expiresInMs = tokenResponse.expires_in * 1000;
|
|
122
|
+
const expiryTime = new Date(Date.now() + expiresInMs);
|
|
123
|
+
console.error(`[MCP Auth] Successfully authenticated with OAuth client`);
|
|
124
|
+
console.error(`[MCP Auth] Client ID: ${clientId}`);
|
|
125
|
+
console.error(`[MCP Auth] Token expires at ${expiryTime.toISOString()}`);
|
|
126
|
+
return {
|
|
127
|
+
token: tokenResponse.access_token,
|
|
128
|
+
expiresInMs
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
console.error(`[MCP Auth] Failed to get OAuth token: ${error.message}`);
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Schedule automatic token refresh before expiry
|
|
138
|
+
* Refreshes 5 minutes before the token expires
|
|
139
|
+
*/
|
|
140
|
+
function scheduleTokenRefresh(expiresInMs) {
|
|
141
|
+
// Clear existing timer if any
|
|
142
|
+
if (tokenRefreshTimer) {
|
|
143
|
+
clearTimeout(tokenRefreshTimer);
|
|
144
|
+
}
|
|
145
|
+
// Refresh 5 minutes (300000ms) before expiry, or halfway through if token life < 10 minutes
|
|
146
|
+
const refreshBeforeMs = Math.min(300000, expiresInMs / 2);
|
|
147
|
+
const refreshInMs = expiresInMs - refreshBeforeMs;
|
|
148
|
+
console.error(`[MCP Auth] Token refresh scheduled in ${Math.round(refreshInMs / 1000 / 60)} minutes`);
|
|
149
|
+
tokenRefreshTimer = setTimeout(async () => {
|
|
150
|
+
console.error('[MCP Auth] Refreshing OAuth access token...');
|
|
151
|
+
const result = await getOAuthAccessToken();
|
|
152
|
+
if (result) {
|
|
153
|
+
oauthAccessToken = result.token;
|
|
154
|
+
// Update the token in the client
|
|
155
|
+
if (client) {
|
|
156
|
+
client.setMcpJwtToken(result.token);
|
|
157
|
+
}
|
|
158
|
+
// Schedule next refresh
|
|
159
|
+
scheduleTokenRefresh(result.expiresInMs);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
console.error('[MCP Auth] Token refresh failed! Operations may fail with 401 errors.');
|
|
163
|
+
console.error('[MCP Auth] Please restart the MCP server or check OAuth credentials.');
|
|
164
|
+
}
|
|
165
|
+
}, refreshInMs);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Initialize OAuth authentication using client credentials (ADR-054)
|
|
169
|
+
*
|
|
170
|
+
* Requires KG_OAUTH_CLIENT_ID and KG_OAUTH_CLIENT_SECRET environment variables.
|
|
171
|
+
* This allows the MCP server to authenticate transparently without
|
|
172
|
+
* the AI being aware of authentication requirements.
|
|
173
|
+
*/
|
|
174
|
+
async function initializeAuth() {
|
|
175
|
+
const clientId = process.env.KG_OAUTH_CLIENT_ID;
|
|
176
|
+
const clientSecret = process.env.KG_OAUTH_CLIENT_SECRET;
|
|
177
|
+
if (clientId && clientSecret) {
|
|
178
|
+
const result = await getOAuthAccessToken();
|
|
179
|
+
if (result) {
|
|
180
|
+
oauthAccessToken = result.token;
|
|
181
|
+
scheduleTokenRefresh(result.expiresInMs);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
console.error('[MCP Auth] The MCP server will operate without authentication.');
|
|
185
|
+
console.error('[MCP Auth] Some operations may fail with 401 errors.');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
console.error('[MCP Auth] No OAuth credentials provided (KG_OAUTH_CLIENT_ID/KG_OAUTH_CLIENT_SECRET).');
|
|
190
|
+
console.error('[MCP Auth] The MCP server will operate without authentication.');
|
|
191
|
+
console.error('[MCP Auth] To authenticate:');
|
|
192
|
+
console.error('[MCP Auth] 1. Create a personal OAuth client: kg login');
|
|
193
|
+
console.error('[MCP Auth] 2. Copy client credentials from ~/.config/kg/config.json');
|
|
194
|
+
console.error('[MCP Auth] 3. Add to Claude Desktop config as KG_OAUTH_CLIENT_ID and KG_OAUTH_CLIENT_SECRET');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Create an authenticated API client
|
|
199
|
+
* If we have an OAuth access token, inject it into the client (ADR-054)
|
|
200
|
+
*/
|
|
201
|
+
function createAuthenticatedClient() {
|
|
202
|
+
const client = (0, client_js_1.createClientFromEnv)();
|
|
203
|
+
// If we have an OAuth access token, set it on the client
|
|
204
|
+
if (oauthAccessToken) {
|
|
205
|
+
client.setMcpJwtToken(oauthAccessToken);
|
|
206
|
+
}
|
|
207
|
+
return client;
|
|
208
|
+
}
|
|
209
|
+
// Client instance - will be initialized in main() before server starts
|
|
210
|
+
let client;
|
|
211
|
+
/**
|
|
212
|
+
* Tool Definitions
|
|
213
|
+
*
|
|
214
|
+
* Consolidated from 22 tools to 6 tools + 5 resources (ADR-013)
|
|
215
|
+
*/
|
|
216
|
+
// List available tools
|
|
217
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
218
|
+
return {
|
|
219
|
+
tools: [
|
|
220
|
+
{
|
|
221
|
+
name: 'search',
|
|
222
|
+
description: `Search for concepts, source passages, or documents using semantic similarity. Your ENTRY POINT to the graph.
|
|
223
|
+
|
|
224
|
+
CONCEPT SEARCH (type: "concepts", default) - Find concepts by semantic similarity:
|
|
225
|
+
- Grounding strength (-1.0 to 1.0): Reliability/contradiction score
|
|
226
|
+
- Diversity score: Conceptual richness (% of diverse connections)
|
|
227
|
+
- Authenticated diversity: Support vs contradiction indicator (✅✓⚠❌)
|
|
228
|
+
- Evidence samples: Quoted text from source documents
|
|
229
|
+
- Image indicators: Visual evidence when available
|
|
230
|
+
- Document sources: Where concepts originated
|
|
231
|
+
|
|
232
|
+
SOURCE SEARCH (type: "sources") - Find source text passages directly (ADR-068):
|
|
233
|
+
- Searches source document embeddings, not concept embeddings
|
|
234
|
+
- Returns matched text chunks with character offsets for highlighting
|
|
235
|
+
- Shows concepts extracted from those passages
|
|
236
|
+
- Useful for RAG workflows and finding original context
|
|
237
|
+
|
|
238
|
+
DOCUMENT SEARCH (type: "documents") - Find documents by semantic similarity (ADR-084):
|
|
239
|
+
- Searches at document level (aggregates source chunks)
|
|
240
|
+
- Returns documents ranked by best matching chunk similarity
|
|
241
|
+
- Shows concepts extracted from each document
|
|
242
|
+
- Use with document tool for content retrieval
|
|
243
|
+
|
|
244
|
+
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.
|
|
245
|
+
|
|
246
|
+
Use 2-3 word phrases (e.g., "linear thinking patterns").`,
|
|
247
|
+
inputSchema: {
|
|
248
|
+
type: 'object',
|
|
249
|
+
properties: {
|
|
250
|
+
query: {
|
|
251
|
+
type: 'string',
|
|
252
|
+
description: 'Search query text (2-3 word phrases work best, e.g., "linear thinking patterns")',
|
|
253
|
+
},
|
|
254
|
+
type: {
|
|
255
|
+
type: 'string',
|
|
256
|
+
enum: ['concepts', 'sources', 'documents'],
|
|
257
|
+
description: 'Search type: "concepts" (default), "sources" (passage search), or "documents" (document-level search)',
|
|
258
|
+
default: 'concepts',
|
|
259
|
+
},
|
|
260
|
+
limit: {
|
|
261
|
+
type: 'number',
|
|
262
|
+
description: 'Maximum number of results to return (default: 10, max: 100)',
|
|
263
|
+
default: 10,
|
|
264
|
+
},
|
|
265
|
+
min_similarity: {
|
|
266
|
+
type: 'number',
|
|
267
|
+
description: 'Minimum similarity score 0.0-1.0 (default: 0.7 for 70%, lower to 0.5-0.6 for broader matches)',
|
|
268
|
+
default: 0.7,
|
|
269
|
+
},
|
|
270
|
+
offset: {
|
|
271
|
+
type: 'number',
|
|
272
|
+
description: 'Number of results to skip for pagination (default: 0)',
|
|
273
|
+
default: 0,
|
|
274
|
+
},
|
|
275
|
+
ontology: {
|
|
276
|
+
type: 'string',
|
|
277
|
+
description: 'Filter by ontology/document name (sources only)',
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
required: ['query'],
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
name: 'concept',
|
|
285
|
+
description: `Work with concepts: get details (ALL evidence + relationships), find related concepts (neighborhood exploration), or discover connections (paths between concepts).
|
|
286
|
+
|
|
287
|
+
PERFORMANCE CRITICAL: For "connect" action, use threshold >= 0.75 to avoid database overload. Lower thresholds create exponentially larger searches that can hang for minutes. Start with threshold=0.8, max_hops=3, then adjust if needed.`,
|
|
288
|
+
inputSchema: {
|
|
289
|
+
type: 'object',
|
|
290
|
+
properties: {
|
|
291
|
+
action: {
|
|
292
|
+
type: 'string',
|
|
293
|
+
enum: ['details', 'related', 'connect'],
|
|
294
|
+
description: 'Operation: "details" (get ALL evidence), "related" (explore neighborhood), "connect" (find paths)',
|
|
295
|
+
},
|
|
296
|
+
// For details and related
|
|
297
|
+
concept_id: {
|
|
298
|
+
type: 'string',
|
|
299
|
+
description: 'Concept ID (required for details, related)',
|
|
300
|
+
},
|
|
301
|
+
include_grounding: {
|
|
302
|
+
type: 'boolean',
|
|
303
|
+
description: 'Include grounding_strength (default: true)',
|
|
304
|
+
default: true,
|
|
305
|
+
},
|
|
306
|
+
include_diversity: {
|
|
307
|
+
type: 'boolean',
|
|
308
|
+
description: 'Include diversity metrics for details action (default: false, adds ~100-500ms)',
|
|
309
|
+
default: false,
|
|
310
|
+
},
|
|
311
|
+
diversity_max_hops: {
|
|
312
|
+
type: 'number',
|
|
313
|
+
description: 'Max hops for diversity calculation (default: 2)',
|
|
314
|
+
default: 2,
|
|
315
|
+
},
|
|
316
|
+
truncate_evidence: {
|
|
317
|
+
type: 'boolean',
|
|
318
|
+
description: 'Truncate evidence full_text context to 200 chars (default: true for token efficiency). Set false for complete context.',
|
|
319
|
+
default: true,
|
|
320
|
+
},
|
|
321
|
+
// For related
|
|
322
|
+
max_depth: {
|
|
323
|
+
type: 'number',
|
|
324
|
+
description: 'Max traversal depth for related (1-5, default: 2)',
|
|
325
|
+
default: 2,
|
|
326
|
+
},
|
|
327
|
+
relationship_types: {
|
|
328
|
+
type: 'array',
|
|
329
|
+
items: { type: 'string' },
|
|
330
|
+
description: 'Filter relationships (e.g., ["SUPPORTS", "CONTRADICTS"])',
|
|
331
|
+
},
|
|
332
|
+
// ADR-065: Epistemic status filtering (for related and connect)
|
|
333
|
+
include_epistemic_status: {
|
|
334
|
+
type: 'array',
|
|
335
|
+
items: { type: 'string' },
|
|
336
|
+
description: 'Only include relationships with these epistemic statuses (e.g., ["AFFIRMATIVE", "CONTESTED"])',
|
|
337
|
+
},
|
|
338
|
+
exclude_epistemic_status: {
|
|
339
|
+
type: 'array',
|
|
340
|
+
items: { type: 'string' },
|
|
341
|
+
description: 'Exclude relationships with these epistemic statuses (e.g., ["HISTORICAL", "INSUFFICIENT_DATA"])',
|
|
342
|
+
},
|
|
343
|
+
// For connect
|
|
344
|
+
connection_mode: {
|
|
345
|
+
type: 'string',
|
|
346
|
+
enum: ['exact', 'semantic'],
|
|
347
|
+
description: 'Connection mode: "exact" (IDs) or "semantic" (phrases)',
|
|
348
|
+
default: 'semantic',
|
|
349
|
+
},
|
|
350
|
+
from_id: {
|
|
351
|
+
type: 'string',
|
|
352
|
+
description: 'Starting concept ID (for exact mode)',
|
|
353
|
+
},
|
|
354
|
+
to_id: {
|
|
355
|
+
type: 'string',
|
|
356
|
+
description: 'Target concept ID (for exact mode)',
|
|
357
|
+
},
|
|
358
|
+
from_query: {
|
|
359
|
+
type: 'string',
|
|
360
|
+
description: 'Starting phrase (for semantic mode, 2-3 words)',
|
|
361
|
+
},
|
|
362
|
+
to_query: {
|
|
363
|
+
type: 'string',
|
|
364
|
+
description: 'Target phrase (for semantic mode, 2-3 words)',
|
|
365
|
+
},
|
|
366
|
+
max_hops: {
|
|
367
|
+
type: 'number',
|
|
368
|
+
description: 'Max path length (default: 3). WARNING: Values >5 combined with threshold <0.75 can cause severe performance issues.',
|
|
369
|
+
default: 3,
|
|
370
|
+
},
|
|
371
|
+
threshold: {
|
|
372
|
+
type: 'number',
|
|
373
|
+
description: 'Similarity threshold for semantic mode (default: 0.75). PERFORMANCE GUIDE: 0.85+ = precise/fast, 0.75-0.84 = balanced, 0.60-0.74 = exploratory/SLOW, <0.60 = DANGEROUS (can hang database for minutes)',
|
|
374
|
+
default: 0.75,
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
required: ['action'],
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
name: 'ontology',
|
|
382
|
+
description: 'Manage ontologies (knowledge domains/collections): list all, get info, list files, or delete. Use action parameter to specify operation.',
|
|
383
|
+
inputSchema: {
|
|
384
|
+
type: 'object',
|
|
385
|
+
properties: {
|
|
386
|
+
action: {
|
|
387
|
+
type: 'string',
|
|
388
|
+
enum: ['list', 'info', 'files', 'delete'],
|
|
389
|
+
description: 'Operation: "list" (all ontologies), "info" (details), "files" (source files), "delete" (remove)',
|
|
390
|
+
},
|
|
391
|
+
ontology_name: {
|
|
392
|
+
type: 'string',
|
|
393
|
+
description: 'Ontology name (required for info, files, delete)',
|
|
394
|
+
},
|
|
395
|
+
force: {
|
|
396
|
+
type: 'boolean',
|
|
397
|
+
description: 'Confirm deletion (required for delete)',
|
|
398
|
+
default: false,
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
required: ['action'],
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
name: 'job',
|
|
406
|
+
description: 'Manage ingestion jobs: get status, list jobs, approve, or cancel. Use action parameter to specify operation.',
|
|
407
|
+
inputSchema: {
|
|
408
|
+
type: 'object',
|
|
409
|
+
properties: {
|
|
410
|
+
action: {
|
|
411
|
+
type: 'string',
|
|
412
|
+
enum: ['status', 'list', 'approve', 'cancel'],
|
|
413
|
+
description: 'Operation: "status" (get job status), "list" (list jobs), "approve" (approve job), "cancel" (cancel job)',
|
|
414
|
+
},
|
|
415
|
+
job_id: {
|
|
416
|
+
type: 'string',
|
|
417
|
+
description: 'Job ID (required for status, approve, cancel)',
|
|
418
|
+
},
|
|
419
|
+
status: {
|
|
420
|
+
type: 'string',
|
|
421
|
+
description: 'Filter by status for list (pending, awaiting_approval, running, completed, failed)',
|
|
422
|
+
},
|
|
423
|
+
limit: {
|
|
424
|
+
type: 'number',
|
|
425
|
+
description: 'Max jobs to return for list (default: 50)',
|
|
426
|
+
default: 50,
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
required: ['action'],
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
name: 'ingest',
|
|
434
|
+
description: 'Ingest content into the knowledge graph: submit text, inspect files, ingest files, or ingest directories. Use action parameter to specify operation.',
|
|
435
|
+
inputSchema: {
|
|
436
|
+
type: 'object',
|
|
437
|
+
properties: {
|
|
438
|
+
action: {
|
|
439
|
+
type: 'string',
|
|
440
|
+
enum: ['text', 'inspect-file', 'file', 'directory'],
|
|
441
|
+
description: 'Operation: "text" (raw text), "inspect-file" (validate), "file" (ingest files), "directory" (ingest directory)',
|
|
442
|
+
},
|
|
443
|
+
// For text action
|
|
444
|
+
text: {
|
|
445
|
+
type: 'string',
|
|
446
|
+
description: 'Text content to ingest (required for text action)',
|
|
447
|
+
},
|
|
448
|
+
// For all actions except inspect-file
|
|
449
|
+
ontology: {
|
|
450
|
+
type: 'string',
|
|
451
|
+
description: 'Ontology name (required for text/file/directory, optional for directory - defaults to dir name)',
|
|
452
|
+
},
|
|
453
|
+
// For text action
|
|
454
|
+
filename: {
|
|
455
|
+
type: 'string',
|
|
456
|
+
description: 'Optional filename for source tracking (text action)',
|
|
457
|
+
},
|
|
458
|
+
processing_mode: {
|
|
459
|
+
type: 'string',
|
|
460
|
+
enum: ['serial', 'parallel'],
|
|
461
|
+
description: 'Processing mode (text action, default: serial)',
|
|
462
|
+
default: 'serial',
|
|
463
|
+
},
|
|
464
|
+
target_words: {
|
|
465
|
+
type: 'number',
|
|
466
|
+
description: 'Words per chunk (text action, default: 1000)',
|
|
467
|
+
default: 1000,
|
|
468
|
+
},
|
|
469
|
+
overlap_words: {
|
|
470
|
+
type: 'number',
|
|
471
|
+
description: 'Overlap between chunks (text action, default: 200)',
|
|
472
|
+
default: 200,
|
|
473
|
+
},
|
|
474
|
+
// For file/directory/inspect-file actions
|
|
475
|
+
path: {
|
|
476
|
+
description: 'File/directory path (required for inspect-file/file/directory). For file action: single path string OR array for batch',
|
|
477
|
+
oneOf: [
|
|
478
|
+
{ type: 'string' },
|
|
479
|
+
{ type: 'array', items: { type: 'string' } }
|
|
480
|
+
],
|
|
481
|
+
},
|
|
482
|
+
// For file/directory actions
|
|
483
|
+
auto_approve: {
|
|
484
|
+
type: 'boolean',
|
|
485
|
+
description: 'Auto-approve processing (file/directory actions, default: true)',
|
|
486
|
+
default: true,
|
|
487
|
+
},
|
|
488
|
+
force: {
|
|
489
|
+
type: 'boolean',
|
|
490
|
+
description: 'Force re-ingestion (file/directory actions, default: false)',
|
|
491
|
+
default: false,
|
|
492
|
+
},
|
|
493
|
+
// For directory action
|
|
494
|
+
recursive: {
|
|
495
|
+
type: 'boolean',
|
|
496
|
+
description: 'Process subdirectories recursively (directory action, default: false)',
|
|
497
|
+
default: false,
|
|
498
|
+
},
|
|
499
|
+
limit: {
|
|
500
|
+
type: 'number',
|
|
501
|
+
description: 'Number of files to show per page (directory action, default: 10)',
|
|
502
|
+
default: 10,
|
|
503
|
+
},
|
|
504
|
+
offset: {
|
|
505
|
+
type: 'number',
|
|
506
|
+
description: 'Number of files to skip for pagination (directory action, default: 0)',
|
|
507
|
+
default: 0,
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
required: ['action'],
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
name: 'source',
|
|
515
|
+
description: `Retrieve original source content (text or image) for a source node (ADR-057).
|
|
516
|
+
|
|
517
|
+
For IMAGE sources: Returns the image for visual verification
|
|
518
|
+
For TEXT sources: Returns full_text content with metadata (document, paragraph, offsets)
|
|
519
|
+
|
|
520
|
+
Use when you need to:
|
|
521
|
+
- Verify extracted concepts against original source
|
|
522
|
+
- Get the full context of a text passage
|
|
523
|
+
- Retrieve images for visual analysis
|
|
524
|
+
- Check character offsets for highlighting`,
|
|
525
|
+
inputSchema: {
|
|
526
|
+
type: 'object',
|
|
527
|
+
properties: {
|
|
528
|
+
source_id: {
|
|
529
|
+
type: 'string',
|
|
530
|
+
description: 'Source ID from evidence or search results',
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
required: ['source_id'],
|
|
534
|
+
},
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
name: 'epistemic_status',
|
|
538
|
+
description: `Vocabulary epistemic status classification (ADR-065 Phase 2). Knowledge validation state for relationship types.
|
|
539
|
+
|
|
540
|
+
Three actions available:
|
|
541
|
+
- "list": List all vocabulary types with epistemic status classifications (AFFIRMATIVE/CONTESTED/CONTRADICTORY/HISTORICAL/INSUFFICIENT_DATA/UNCLASSIFIED)
|
|
542
|
+
- "show": Get detailed status for a specific relationship type
|
|
543
|
+
- "measure": Run measurement to calculate epistemic status for all types (admin operation)
|
|
544
|
+
|
|
545
|
+
EPISTEMIC STATUS CLASSIFICATIONS:
|
|
546
|
+
- AFFIRMATIVE: High avg grounding >0.8 (well-established knowledge)
|
|
547
|
+
- CONTESTED: Mixed grounding 0.2-0.8 (debated/mixed validation)
|
|
548
|
+
- CONTRADICTORY: Low grounding <-0.5 (contradicted knowledge)
|
|
549
|
+
- HISTORICAL: Temporal vocabulary (detected by name)
|
|
550
|
+
- INSUFFICIENT_DATA: <3 successful measurements
|
|
551
|
+
- UNCLASSIFIED: Doesn't fit known patterns
|
|
552
|
+
|
|
553
|
+
Use for filtering relationships by epistemic reliability, identifying contested knowledge areas, and curating high-confidence vs exploratory subgraphs.`,
|
|
554
|
+
inputSchema: {
|
|
555
|
+
type: 'object',
|
|
556
|
+
properties: {
|
|
557
|
+
action: {
|
|
558
|
+
type: 'string',
|
|
559
|
+
enum: ['list', 'show', 'measure'],
|
|
560
|
+
description: 'Operation: "list" (all types), "show" (specific type), "measure" (run measurement)',
|
|
561
|
+
},
|
|
562
|
+
// For list action
|
|
563
|
+
status_filter: {
|
|
564
|
+
type: 'string',
|
|
565
|
+
description: 'Filter by status for list action: AFFIRMATIVE, CONTESTED, CONTRADICTORY, HISTORICAL, INSUFFICIENT_DATA, UNCLASSIFIED',
|
|
566
|
+
},
|
|
567
|
+
// For show action
|
|
568
|
+
relationship_type: {
|
|
569
|
+
type: 'string',
|
|
570
|
+
description: 'Relationship type to show (required for show action, e.g., "IMPLIES", "SUPPORTS")',
|
|
571
|
+
},
|
|
572
|
+
// For measure action
|
|
573
|
+
sample_size: {
|
|
574
|
+
type: 'number',
|
|
575
|
+
description: 'Edges to sample per type for measure action (default: 100)',
|
|
576
|
+
default: 100,
|
|
577
|
+
},
|
|
578
|
+
store: {
|
|
579
|
+
type: 'boolean',
|
|
580
|
+
description: 'Store results to database for measure action (default: true)',
|
|
581
|
+
default: true,
|
|
582
|
+
},
|
|
583
|
+
verbose: {
|
|
584
|
+
type: 'boolean',
|
|
585
|
+
description: 'Include detailed statistics for measure action (default: false)',
|
|
586
|
+
default: false,
|
|
587
|
+
},
|
|
588
|
+
},
|
|
589
|
+
required: ['action'],
|
|
590
|
+
},
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
name: 'analyze_polarity_axis',
|
|
594
|
+
description: `Analyze bidirectional semantic dimension (polarity axis) between two concept poles (ADR-070).
|
|
595
|
+
|
|
596
|
+
Projects concepts onto an axis formed by opposing semantic poles (e.g., Modern ↔ Traditional, Centralized ↔ Distributed). Returns:
|
|
597
|
+
- Axis quality and magnitude (semantic distinctness)
|
|
598
|
+
- Concept positions along the axis (-1 to +1)
|
|
599
|
+
- Direction distribution (positive/neutral/negative)
|
|
600
|
+
- Grounding correlation patterns
|
|
601
|
+
- Statistical analysis of projections
|
|
602
|
+
|
|
603
|
+
PERFORMANCE: Direct query pattern, ~2-3 seconds execution time.
|
|
604
|
+
|
|
605
|
+
Use Cases:
|
|
606
|
+
- Explore conceptual spectrums and gradients
|
|
607
|
+
- Identify position-grounding correlation patterns
|
|
608
|
+
- Discover concepts balanced between opposing ideas
|
|
609
|
+
- Map semantic dimensions in the knowledge graph`,
|
|
610
|
+
inputSchema: {
|
|
611
|
+
type: 'object',
|
|
612
|
+
properties: {
|
|
613
|
+
positive_pole_id: {
|
|
614
|
+
type: 'string',
|
|
615
|
+
description: 'Concept ID for positive pole (e.g., ID for "Modern")',
|
|
616
|
+
},
|
|
617
|
+
negative_pole_id: {
|
|
618
|
+
type: 'string',
|
|
619
|
+
description: 'Concept ID for negative pole (e.g., ID for "Traditional")',
|
|
620
|
+
},
|
|
621
|
+
candidate_ids: {
|
|
622
|
+
type: 'array',
|
|
623
|
+
items: { type: 'string' },
|
|
624
|
+
description: 'Specific concept IDs to project onto axis (optional)',
|
|
625
|
+
},
|
|
626
|
+
auto_discover: {
|
|
627
|
+
type: 'boolean',
|
|
628
|
+
description: 'Auto-discover related concepts if candidate_ids not provided (default: true)',
|
|
629
|
+
default: true,
|
|
630
|
+
},
|
|
631
|
+
max_candidates: {
|
|
632
|
+
type: 'number',
|
|
633
|
+
description: 'Maximum candidates for auto-discovery (default: 20, max: 100)',
|
|
634
|
+
default: 20,
|
|
635
|
+
},
|
|
636
|
+
max_hops: {
|
|
637
|
+
type: 'number',
|
|
638
|
+
description: 'Maximum graph hops for auto-discovery (1-3, default: 1)',
|
|
639
|
+
default: 1,
|
|
640
|
+
},
|
|
641
|
+
},
|
|
642
|
+
required: ['positive_pole_id', 'negative_pole_id'],
|
|
643
|
+
},
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
name: 'artifact',
|
|
647
|
+
description: `Manage saved artifacts (ADR-083). Artifacts persist computed results like search results, projections, and polarity analyses for later recall.
|
|
648
|
+
|
|
649
|
+
Three actions available:
|
|
650
|
+
- "list": List artifacts with optional filtering by type, representation, or ontology
|
|
651
|
+
- "show": Get artifact metadata by ID (without payload)
|
|
652
|
+
- "payload": Get artifact with full payload (for reusing stored analysis)
|
|
653
|
+
|
|
654
|
+
Use artifacts to:
|
|
655
|
+
- Recall previously computed analyses without re-running expensive queries
|
|
656
|
+
- Share analysis results across sessions
|
|
657
|
+
- Track analysis history with parameters and timestamps
|
|
658
|
+
- Check freshness (is_fresh indicates if graph has changed since artifact creation)`,
|
|
659
|
+
inputSchema: {
|
|
660
|
+
type: 'object',
|
|
661
|
+
properties: {
|
|
662
|
+
action: {
|
|
663
|
+
type: 'string',
|
|
664
|
+
enum: ['list', 'show', 'payload'],
|
|
665
|
+
description: 'Operation: "list" (list artifacts), "show" (metadata only), "payload" (full result)',
|
|
666
|
+
},
|
|
667
|
+
// For show and payload
|
|
668
|
+
artifact_id: {
|
|
669
|
+
type: 'number',
|
|
670
|
+
description: 'Artifact ID (required for show, payload)',
|
|
671
|
+
},
|
|
672
|
+
// For list filtering
|
|
673
|
+
artifact_type: {
|
|
674
|
+
type: 'string',
|
|
675
|
+
description: 'Filter by type: search_result, projection, polarity_analysis, query_result, etc.',
|
|
676
|
+
},
|
|
677
|
+
representation: {
|
|
678
|
+
type: 'string',
|
|
679
|
+
description: 'Filter by source: cli, mcp_server, polarity_explorer, embedding_landscape, etc.',
|
|
680
|
+
},
|
|
681
|
+
ontology: {
|
|
682
|
+
type: 'string',
|
|
683
|
+
description: 'Filter by associated ontology name',
|
|
684
|
+
},
|
|
685
|
+
limit: {
|
|
686
|
+
type: 'number',
|
|
687
|
+
description: 'Max artifacts to return for list (default: 20)',
|
|
688
|
+
default: 20,
|
|
689
|
+
},
|
|
690
|
+
offset: {
|
|
691
|
+
type: 'number',
|
|
692
|
+
description: 'Number to skip for pagination (default: 0)',
|
|
693
|
+
default: 0,
|
|
694
|
+
},
|
|
695
|
+
},
|
|
696
|
+
required: ['action'],
|
|
697
|
+
},
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
name: 'document',
|
|
701
|
+
description: `Work with documents: list all, show content, or get concepts (ADR-084).
|
|
702
|
+
|
|
703
|
+
Three actions available:
|
|
704
|
+
- "list": List all documents with optional ontology filter
|
|
705
|
+
- "show": Retrieve document content from Garage storage
|
|
706
|
+
- "concepts": Get all concepts extracted from a document
|
|
707
|
+
|
|
708
|
+
Documents are aggregated from source chunks and stored in Garage (S3-compatible storage).
|
|
709
|
+
Use search tool with type="documents" to find documents semantically.`,
|
|
710
|
+
inputSchema: {
|
|
711
|
+
type: 'object',
|
|
712
|
+
properties: {
|
|
713
|
+
action: {
|
|
714
|
+
type: 'string',
|
|
715
|
+
enum: ['list', 'show', 'concepts'],
|
|
716
|
+
description: 'Operation: "list" (all documents), "show" (content), "concepts" (extracted concepts)',
|
|
717
|
+
},
|
|
718
|
+
// For show and concepts
|
|
719
|
+
document_id: {
|
|
720
|
+
type: 'string',
|
|
721
|
+
description: 'Document ID (required for show, concepts). Format: sha256:...',
|
|
722
|
+
},
|
|
723
|
+
// For concepts action
|
|
724
|
+
include_details: {
|
|
725
|
+
type: 'boolean',
|
|
726
|
+
description: 'Include full concept details (evidence, relationships, grounding) in one call. Default: false for lightweight list.',
|
|
727
|
+
default: false,
|
|
728
|
+
},
|
|
729
|
+
// For list
|
|
730
|
+
ontology: {
|
|
731
|
+
type: 'string',
|
|
732
|
+
description: 'Filter by ontology name (for list)',
|
|
733
|
+
},
|
|
734
|
+
limit: {
|
|
735
|
+
type: 'number',
|
|
736
|
+
description: 'Max documents to return for list (default: 50)',
|
|
737
|
+
default: 50,
|
|
738
|
+
},
|
|
739
|
+
offset: {
|
|
740
|
+
type: 'number',
|
|
741
|
+
description: 'Number to skip for pagination (default: 0)',
|
|
742
|
+
default: 0,
|
|
743
|
+
},
|
|
744
|
+
},
|
|
745
|
+
required: ['action'],
|
|
746
|
+
},
|
|
747
|
+
},
|
|
748
|
+
],
|
|
749
|
+
};
|
|
750
|
+
});
|
|
751
|
+
// List available resources
|
|
752
|
+
server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
|
|
753
|
+
return {
|
|
754
|
+
resources: [
|
|
755
|
+
{
|
|
756
|
+
uri: 'database/stats',
|
|
757
|
+
name: 'Database Statistics',
|
|
758
|
+
description: 'Concept counts, relationship counts, ontology statistics',
|
|
759
|
+
mimeType: 'application/json'
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
uri: 'database/info',
|
|
763
|
+
name: 'Database Information',
|
|
764
|
+
description: 'PostgreSQL version, Apache AGE extension details',
|
|
765
|
+
mimeType: 'application/json'
|
|
766
|
+
},
|
|
767
|
+
{
|
|
768
|
+
uri: 'database/health',
|
|
769
|
+
name: 'Database Health',
|
|
770
|
+
description: 'Database connection status, graph availability',
|
|
771
|
+
mimeType: 'application/json'
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
uri: 'system/status',
|
|
775
|
+
name: 'System Status',
|
|
776
|
+
description: 'Job scheduler status, resource usage',
|
|
777
|
+
mimeType: 'application/json'
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
uri: 'api/health',
|
|
781
|
+
name: 'API Health',
|
|
782
|
+
description: 'API server health and timestamp',
|
|
783
|
+
mimeType: 'application/json'
|
|
784
|
+
},
|
|
785
|
+
{
|
|
786
|
+
uri: 'mcp/allowed-paths',
|
|
787
|
+
name: 'MCP File Access Allowlist',
|
|
788
|
+
description: 'Path allowlist configuration for secure file/directory ingestion (ADR-062)',
|
|
789
|
+
mimeType: 'application/json'
|
|
790
|
+
}
|
|
791
|
+
]
|
|
792
|
+
};
|
|
793
|
+
});
|
|
794
|
+
// Read resource handler
|
|
795
|
+
server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) => {
|
|
796
|
+
const { uri } = request.params;
|
|
797
|
+
try {
|
|
798
|
+
switch (uri) {
|
|
799
|
+
case 'database/stats': {
|
|
800
|
+
const result = await client.getDatabaseStats();
|
|
801
|
+
return {
|
|
802
|
+
contents: [{
|
|
803
|
+
uri,
|
|
804
|
+
mimeType: 'text/plain',
|
|
805
|
+
text: (0, formatters_js_1.formatDatabaseStats)(result)
|
|
806
|
+
}]
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
case 'database/info': {
|
|
810
|
+
const result = await client.getDatabaseInfo();
|
|
811
|
+
return {
|
|
812
|
+
contents: [{
|
|
813
|
+
uri,
|
|
814
|
+
mimeType: 'text/plain',
|
|
815
|
+
text: (0, formatters_js_1.formatDatabaseInfo)(result)
|
|
816
|
+
}]
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
case 'database/health': {
|
|
820
|
+
const result = await client.getDatabaseHealth();
|
|
821
|
+
return {
|
|
822
|
+
contents: [{
|
|
823
|
+
uri,
|
|
824
|
+
mimeType: 'text/plain',
|
|
825
|
+
text: (0, formatters_js_1.formatDatabaseHealth)(result)
|
|
826
|
+
}]
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
case 'system/status': {
|
|
830
|
+
const result = await client.getSystemStatus();
|
|
831
|
+
return {
|
|
832
|
+
contents: [{
|
|
833
|
+
uri,
|
|
834
|
+
mimeType: 'text/plain',
|
|
835
|
+
text: (0, formatters_js_1.formatSystemStatus)(result)
|
|
836
|
+
}]
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
case 'api/health': {
|
|
840
|
+
const result = await client.health();
|
|
841
|
+
return {
|
|
842
|
+
contents: [{
|
|
843
|
+
uri,
|
|
844
|
+
mimeType: 'text/plain',
|
|
845
|
+
text: (0, formatters_js_1.formatApiHealth)(result)
|
|
846
|
+
}]
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
case 'mcp/allowed-paths': {
|
|
850
|
+
const manager = new mcp_allowlist_js_1.McpAllowlistManager();
|
|
851
|
+
const config = manager.getConfig();
|
|
852
|
+
if (!config) {
|
|
853
|
+
return {
|
|
854
|
+
contents: [{
|
|
855
|
+
uri,
|
|
856
|
+
mimeType: 'text/plain',
|
|
857
|
+
text: (0, formatters_js_1.formatMcpAllowedPaths)({
|
|
858
|
+
configured: false,
|
|
859
|
+
message: 'Allowlist not initialized. Run: kg mcp-config init-allowlist',
|
|
860
|
+
hint: 'Initialize with default safe patterns for .md, .txt, .pdf, .png, .jpg files'
|
|
861
|
+
})
|
|
862
|
+
}]
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
return {
|
|
866
|
+
contents: [{
|
|
867
|
+
uri,
|
|
868
|
+
mimeType: 'text/plain',
|
|
869
|
+
text: (0, formatters_js_1.formatMcpAllowedPaths)({
|
|
870
|
+
configured: true,
|
|
871
|
+
version: config.version,
|
|
872
|
+
allowed_directories: config.allowed_directories,
|
|
873
|
+
allowed_patterns: config.allowed_patterns,
|
|
874
|
+
blocked_patterns: config.blocked_patterns,
|
|
875
|
+
max_file_size_mb: config.max_file_size_mb,
|
|
876
|
+
max_files_per_directory: config.max_files_per_directory,
|
|
877
|
+
config_path: manager.getAllowlistPath()
|
|
878
|
+
})
|
|
879
|
+
}]
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
default:
|
|
883
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
catch (error) {
|
|
887
|
+
throw new Error(`Failed to read resource ${uri}: ${error.message}`);
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
/**
|
|
891
|
+
* Helper: Segment long paths for better readability
|
|
892
|
+
*
|
|
893
|
+
* Paths longer than 5 hops are chunked into segments of 5 hops each.
|
|
894
|
+
* This makes them much easier to read and understand.
|
|
895
|
+
*/
|
|
896
|
+
function segmentPath(path) {
|
|
897
|
+
if (!path.hops || path.hops <= 5) {
|
|
898
|
+
return {
|
|
899
|
+
totalHops: path.hops,
|
|
900
|
+
segmented: false,
|
|
901
|
+
nodes: path.nodes,
|
|
902
|
+
relationships: path.relationships,
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
// Chunk into segments of 5 hops
|
|
906
|
+
const segments = [];
|
|
907
|
+
const chunkSize = 5;
|
|
908
|
+
const nodes = path.nodes || [];
|
|
909
|
+
const relationships = path.relationships || [];
|
|
910
|
+
for (let i = 0; i < nodes.length; i += chunkSize) {
|
|
911
|
+
segments.push({
|
|
912
|
+
nodes: nodes.slice(i, Math.min(i + chunkSize + 1, nodes.length)),
|
|
913
|
+
relationships: relationships.slice(i, Math.min(i + chunkSize, relationships.length)),
|
|
914
|
+
segment: Math.floor(i / chunkSize) + 1,
|
|
915
|
+
totalSegments: Math.ceil(nodes.length / chunkSize),
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
return {
|
|
919
|
+
totalHops: path.hops,
|
|
920
|
+
segmented: true,
|
|
921
|
+
segments,
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
// Call tool handler with consolidated routing
|
|
925
|
+
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
926
|
+
const { name, arguments: args } = request.params;
|
|
927
|
+
const toolArgs = args || {};
|
|
928
|
+
try {
|
|
929
|
+
switch (name) {
|
|
930
|
+
case 'search': {
|
|
931
|
+
const query = toolArgs.query;
|
|
932
|
+
const searchType = toolArgs.type || 'concepts';
|
|
933
|
+
const limit = toolArgs.limit || 10;
|
|
934
|
+
const min_similarity = toolArgs.min_similarity || DEFAULT_SEARCH_SIMILARITY;
|
|
935
|
+
const offset = toolArgs.offset || 0;
|
|
936
|
+
const ontology = toolArgs.ontology;
|
|
937
|
+
if (searchType === 'sources') {
|
|
938
|
+
// ADR-068 Phase 5: Source text search
|
|
939
|
+
const result = await client.searchSources({
|
|
940
|
+
query,
|
|
941
|
+
limit,
|
|
942
|
+
min_similarity,
|
|
943
|
+
ontology,
|
|
944
|
+
include_concepts: true,
|
|
945
|
+
include_full_text: true,
|
|
946
|
+
});
|
|
947
|
+
const formattedText = (0, formatters_js_1.formatSourceSearchResults)(result);
|
|
948
|
+
return {
|
|
949
|
+
content: [{ type: 'text', text: formattedText }],
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
else if (searchType === 'documents') {
|
|
953
|
+
// ADR-084: Document search
|
|
954
|
+
const result = await client.searchDocuments({
|
|
955
|
+
query,
|
|
956
|
+
limit,
|
|
957
|
+
min_similarity,
|
|
958
|
+
ontology,
|
|
959
|
+
});
|
|
960
|
+
// Add query to result for formatting
|
|
961
|
+
const resultWithQuery = { ...result, query };
|
|
962
|
+
const formattedText = (0, formatters_js_1.formatDocumentSearchResults)(resultWithQuery);
|
|
963
|
+
return {
|
|
964
|
+
content: [{ type: 'text', text: formattedText }],
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
else {
|
|
968
|
+
// Default: Concept search
|
|
969
|
+
const result = await client.searchConcepts({
|
|
970
|
+
query,
|
|
971
|
+
limit,
|
|
972
|
+
min_similarity,
|
|
973
|
+
offset,
|
|
974
|
+
include_grounding: true,
|
|
975
|
+
include_evidence: true,
|
|
976
|
+
include_diversity: true,
|
|
977
|
+
diversity_max_hops: 2,
|
|
978
|
+
});
|
|
979
|
+
const formattedText = (0, formatters_js_1.formatSearchResults)(result);
|
|
980
|
+
return {
|
|
981
|
+
content: [{ type: 'text', text: formattedText }],
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
case 'concept': {
|
|
986
|
+
const action = toolArgs.action;
|
|
987
|
+
switch (action) {
|
|
988
|
+
case 'details': {
|
|
989
|
+
const includeGrounding = toolArgs.include_grounding !== false;
|
|
990
|
+
const includeDiversity = toolArgs.include_diversity === true;
|
|
991
|
+
const diversityMaxHops = toolArgs.diversity_max_hops || 2;
|
|
992
|
+
const truncateEvidence = toolArgs.truncate_evidence !== false; // Default true
|
|
993
|
+
const result = await client.getConceptDetails(toolArgs.concept_id, includeGrounding, includeDiversity, diversityMaxHops);
|
|
994
|
+
const formattedText = (0, formatters_js_1.formatConceptDetails)(result, truncateEvidence);
|
|
995
|
+
return {
|
|
996
|
+
content: [{ type: 'text', text: formattedText }],
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
case 'related': {
|
|
1000
|
+
const result = await client.findRelatedConcepts({
|
|
1001
|
+
concept_id: toolArgs.concept_id,
|
|
1002
|
+
max_depth: toolArgs.max_depth || DEFAULT_MAX_DEPTH,
|
|
1003
|
+
relationship_types: toolArgs.relationship_types,
|
|
1004
|
+
// ADR-065: Epistemic status filtering
|
|
1005
|
+
include_epistemic_status: toolArgs.include_epistemic_status,
|
|
1006
|
+
exclude_epistemic_status: toolArgs.exclude_epistemic_status,
|
|
1007
|
+
});
|
|
1008
|
+
const formattedText = (0, formatters_js_1.formatRelatedConcepts)(result);
|
|
1009
|
+
return {
|
|
1010
|
+
content: [{ type: 'text', text: formattedText }],
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
case 'connect': {
|
|
1014
|
+
const mode = toolArgs.connection_mode || 'semantic';
|
|
1015
|
+
if (mode === 'exact') {
|
|
1016
|
+
const result = await client.findConnection({
|
|
1017
|
+
from_id: toolArgs.from_id,
|
|
1018
|
+
to_id: toolArgs.to_id,
|
|
1019
|
+
max_hops: toolArgs.max_hops || DEFAULT_MAX_HOPS,
|
|
1020
|
+
// ADR-065: Epistemic status filtering
|
|
1021
|
+
include_epistemic_status: toolArgs.include_epistemic_status,
|
|
1022
|
+
exclude_epistemic_status: toolArgs.exclude_epistemic_status,
|
|
1023
|
+
});
|
|
1024
|
+
// Segment long paths for readability
|
|
1025
|
+
if (result.paths && result.paths.length > 0) {
|
|
1026
|
+
result.paths = result.paths.map(segmentPath);
|
|
1027
|
+
}
|
|
1028
|
+
return {
|
|
1029
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
else {
|
|
1033
|
+
const result = await client.findConnectionBySearch({
|
|
1034
|
+
from_query: toolArgs.from_query,
|
|
1035
|
+
to_query: toolArgs.to_query,
|
|
1036
|
+
max_hops: toolArgs.max_hops || DEFAULT_MAX_HOPS,
|
|
1037
|
+
threshold: toolArgs.threshold || DEFAULT_SEMANTIC_THRESHOLD,
|
|
1038
|
+
include_grounding: true,
|
|
1039
|
+
include_evidence: true,
|
|
1040
|
+
// ADR-065: Epistemic status filtering
|
|
1041
|
+
include_epistemic_status: toolArgs.include_epistemic_status,
|
|
1042
|
+
exclude_epistemic_status: toolArgs.exclude_epistemic_status,
|
|
1043
|
+
});
|
|
1044
|
+
const formattedText = (0, formatters_js_1.formatConnectionPaths)(result);
|
|
1045
|
+
return {
|
|
1046
|
+
content: [{ type: 'text', text: formattedText }],
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
default:
|
|
1051
|
+
throw new Error(`Unknown concept action: ${action}`);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
case 'ontology': {
|
|
1055
|
+
const action = toolArgs.action;
|
|
1056
|
+
switch (action) {
|
|
1057
|
+
case 'list': {
|
|
1058
|
+
const result = await client.listOntologies();
|
|
1059
|
+
return {
|
|
1060
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
case 'info': {
|
|
1064
|
+
const result = await client.getOntologyInfo(toolArgs.ontology_name);
|
|
1065
|
+
return {
|
|
1066
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
case 'files': {
|
|
1070
|
+
const result = await client.getOntologyFiles(toolArgs.ontology_name);
|
|
1071
|
+
return {
|
|
1072
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
case 'delete': {
|
|
1076
|
+
const result = await client.deleteOntology(toolArgs.ontology_name, toolArgs.force || false);
|
|
1077
|
+
return {
|
|
1078
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
default:
|
|
1082
|
+
throw new Error(`Unknown ontology action: ${action}`);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
case 'job': {
|
|
1086
|
+
const action = toolArgs.action;
|
|
1087
|
+
switch (action) {
|
|
1088
|
+
case 'status': {
|
|
1089
|
+
const result = await client.getJobStatus(toolArgs.job_id);
|
|
1090
|
+
const formattedText = (0, formatters_js_1.formatJobStatus)(result);
|
|
1091
|
+
return {
|
|
1092
|
+
content: [{ type: 'text', text: formattedText }],
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
case 'list': {
|
|
1096
|
+
const result = await client.listJobs(toolArgs.status, undefined, toolArgs.limit || 50);
|
|
1097
|
+
const formattedText = (0, formatters_js_1.formatJobList)(result);
|
|
1098
|
+
return {
|
|
1099
|
+
content: [{ type: 'text', text: formattedText }],
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
case 'approve': {
|
|
1103
|
+
const result = await client.approveJob(toolArgs.job_id);
|
|
1104
|
+
return {
|
|
1105
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
case 'cancel': {
|
|
1109
|
+
const result = await client.cancelJob(toolArgs.job_id);
|
|
1110
|
+
return {
|
|
1111
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
default:
|
|
1115
|
+
throw new Error(`Unknown job action: ${action}`);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
case 'ingest': {
|
|
1119
|
+
const action = toolArgs.action;
|
|
1120
|
+
switch (action) {
|
|
1121
|
+
case 'text': {
|
|
1122
|
+
const result = await client.ingestText(toolArgs.text, {
|
|
1123
|
+
ontology: toolArgs.ontology,
|
|
1124
|
+
filename: toolArgs.filename,
|
|
1125
|
+
auto_approve: toolArgs.auto_approve !== undefined ? toolArgs.auto_approve : true,
|
|
1126
|
+
force: toolArgs.force || false,
|
|
1127
|
+
processing_mode: toolArgs.processing_mode || 'serial',
|
|
1128
|
+
options: {
|
|
1129
|
+
target_words: toolArgs.target_words || 1000,
|
|
1130
|
+
overlap_words: toolArgs.overlap_words || 200,
|
|
1131
|
+
},
|
|
1132
|
+
source_type: 'mcp',
|
|
1133
|
+
});
|
|
1134
|
+
return {
|
|
1135
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
case 'inspect-file': {
|
|
1139
|
+
const filePath = toolArgs.path;
|
|
1140
|
+
if (!filePath) {
|
|
1141
|
+
throw new Error('path is required');
|
|
1142
|
+
}
|
|
1143
|
+
// Validate against allowlist
|
|
1144
|
+
const manager = new mcp_allowlist_js_1.McpAllowlistManager();
|
|
1145
|
+
const validation = manager.validatePath(filePath);
|
|
1146
|
+
// Expand tilde and resolve path
|
|
1147
|
+
const expandedPath = filePath.startsWith('~')
|
|
1148
|
+
? path.join(process.env.HOME || '', filePath.slice(1))
|
|
1149
|
+
: filePath;
|
|
1150
|
+
const absolutePath = path.resolve(expandedPath);
|
|
1151
|
+
const result = {
|
|
1152
|
+
path: absolutePath,
|
|
1153
|
+
validation: {
|
|
1154
|
+
allowed: validation.allowed,
|
|
1155
|
+
reason: validation.reason,
|
|
1156
|
+
hint: validation.hint,
|
|
1157
|
+
},
|
|
1158
|
+
};
|
|
1159
|
+
// Get file metadata if exists
|
|
1160
|
+
if (fs.existsSync(absolutePath)) {
|
|
1161
|
+
try {
|
|
1162
|
+
const stats = fs.statSync(absolutePath);
|
|
1163
|
+
const sizeMB = stats.size / (1024 * 1024);
|
|
1164
|
+
result.exists = true;
|
|
1165
|
+
result.metadata = {
|
|
1166
|
+
size_bytes: stats.size,
|
|
1167
|
+
size_mb: parseFloat(sizeMB.toFixed(2)),
|
|
1168
|
+
type: stats.isFile() ? 'file' : stats.isDirectory() ? 'directory' : 'other',
|
|
1169
|
+
modified: stats.mtime.toISOString(),
|
|
1170
|
+
permissions: {
|
|
1171
|
+
readable: fs.constants.R_OK && true,
|
|
1172
|
+
writable: fs.constants.W_OK && true,
|
|
1173
|
+
},
|
|
1174
|
+
};
|
|
1175
|
+
// Detect mime type from extension
|
|
1176
|
+
const ext = path.extname(absolutePath).toLowerCase();
|
|
1177
|
+
const mimeTypes = {
|
|
1178
|
+
'.md': 'text/markdown',
|
|
1179
|
+
'.txt': 'text/plain',
|
|
1180
|
+
'.pdf': 'application/pdf',
|
|
1181
|
+
'.png': 'image/png',
|
|
1182
|
+
'.jpg': 'image/jpeg',
|
|
1183
|
+
'.jpeg': 'image/jpeg',
|
|
1184
|
+
};
|
|
1185
|
+
result.metadata.mime_type = mimeTypes[ext] || 'application/octet-stream';
|
|
1186
|
+
result.metadata.is_image = ext in { '.png': 1, '.jpg': 1, '.jpeg': 1 };
|
|
1187
|
+
}
|
|
1188
|
+
catch (error) {
|
|
1189
|
+
result.exists = true;
|
|
1190
|
+
result.error = `Failed to read metadata: ${error.message}`;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
else {
|
|
1194
|
+
result.exists = false;
|
|
1195
|
+
}
|
|
1196
|
+
return {
|
|
1197
|
+
content: [
|
|
1198
|
+
{
|
|
1199
|
+
type: 'text',
|
|
1200
|
+
text: (0, formatters_js_1.formatInspectFileResult)(result),
|
|
1201
|
+
},
|
|
1202
|
+
],
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
case 'file': {
|
|
1206
|
+
const pathArg = toolArgs.path;
|
|
1207
|
+
const ontology = toolArgs.ontology;
|
|
1208
|
+
const auto_approve = toolArgs.auto_approve !== false;
|
|
1209
|
+
const force = toolArgs.force === true;
|
|
1210
|
+
if (!pathArg || !ontology) {
|
|
1211
|
+
throw new Error('path and ontology are required');
|
|
1212
|
+
}
|
|
1213
|
+
// Support both single path and array of paths
|
|
1214
|
+
const paths = Array.isArray(pathArg) ? pathArg : [pathArg];
|
|
1215
|
+
if (paths.length === 0) {
|
|
1216
|
+
throw new Error('At least one file path is required');
|
|
1217
|
+
}
|
|
1218
|
+
const manager = new mcp_allowlist_js_1.McpAllowlistManager();
|
|
1219
|
+
const results = [];
|
|
1220
|
+
const errors = [];
|
|
1221
|
+
// Process each file
|
|
1222
|
+
for (const filePath of paths) {
|
|
1223
|
+
try {
|
|
1224
|
+
// Validate against allowlist
|
|
1225
|
+
const validation = manager.validatePath(filePath);
|
|
1226
|
+
if (!validation.allowed) {
|
|
1227
|
+
errors.push({
|
|
1228
|
+
file: filePath,
|
|
1229
|
+
error: `File not allowed: ${validation.reason}. ${validation.hint || ''}`,
|
|
1230
|
+
});
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
1233
|
+
// Expand and resolve path
|
|
1234
|
+
const expandedPath = filePath.startsWith('~')
|
|
1235
|
+
? path.join(process.env.HOME || '', filePath.slice(1))
|
|
1236
|
+
: filePath;
|
|
1237
|
+
const absolutePath = path.resolve(expandedPath);
|
|
1238
|
+
if (!fs.existsSync(absolutePath)) {
|
|
1239
|
+
errors.push({
|
|
1240
|
+
file: absolutePath,
|
|
1241
|
+
error: 'File not found',
|
|
1242
|
+
});
|
|
1243
|
+
continue;
|
|
1244
|
+
}
|
|
1245
|
+
const stats = fs.statSync(absolutePath);
|
|
1246
|
+
if (!stats.isFile()) {
|
|
1247
|
+
errors.push({
|
|
1248
|
+
file: absolutePath,
|
|
1249
|
+
error: 'Path is not a file',
|
|
1250
|
+
});
|
|
1251
|
+
continue;
|
|
1252
|
+
}
|
|
1253
|
+
// Detect if image
|
|
1254
|
+
const ext = path.extname(absolutePath).toLowerCase();
|
|
1255
|
+
const isImage = ['.png', '.jpg', '.jpeg'].includes(ext);
|
|
1256
|
+
const filename = path.basename(absolutePath);
|
|
1257
|
+
// Use unified ingestFile endpoint (same as CLI)
|
|
1258
|
+
// Backend automatically detects file type and routes appropriately
|
|
1259
|
+
const jobResponse = await client.ingestFile(absolutePath, {
|
|
1260
|
+
ontology,
|
|
1261
|
+
filename,
|
|
1262
|
+
auto_approve,
|
|
1263
|
+
force,
|
|
1264
|
+
});
|
|
1265
|
+
results.push({
|
|
1266
|
+
status: 'job_id' in jobResponse ? 'submitted' : 'duplicate',
|
|
1267
|
+
job_id: 'job_id' in jobResponse ? jobResponse.job_id : undefined,
|
|
1268
|
+
duplicate_job_id: 'existing_job_id' in jobResponse ? jobResponse.existing_job_id : undefined,
|
|
1269
|
+
file: absolutePath,
|
|
1270
|
+
type: isImage ? 'image' : 'text',
|
|
1271
|
+
size_bytes: stats.size,
|
|
1272
|
+
ontology,
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
catch (error) {
|
|
1276
|
+
errors.push({
|
|
1277
|
+
file: filePath,
|
|
1278
|
+
error: error.message,
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
// Return batch result if multiple files, single result otherwise
|
|
1283
|
+
const result = paths.length > 1 ? {
|
|
1284
|
+
batch: true,
|
|
1285
|
+
ontology,
|
|
1286
|
+
total_files: paths.length,
|
|
1287
|
+
successful: results.length,
|
|
1288
|
+
failed: errors.length,
|
|
1289
|
+
results,
|
|
1290
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
1291
|
+
} : results[0] || errors[0];
|
|
1292
|
+
return {
|
|
1293
|
+
content: [
|
|
1294
|
+
{
|
|
1295
|
+
type: 'text',
|
|
1296
|
+
text: (0, formatters_js_1.formatIngestFileResult)(result),
|
|
1297
|
+
},
|
|
1298
|
+
],
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
case 'directory': {
|
|
1302
|
+
const dirPath = toolArgs.path;
|
|
1303
|
+
const ontology = toolArgs.ontology;
|
|
1304
|
+
const recursive = toolArgs.recursive === true;
|
|
1305
|
+
const auto_approve = toolArgs.auto_approve !== false;
|
|
1306
|
+
const force = toolArgs.force === true;
|
|
1307
|
+
const limit = toolArgs.limit || 10;
|
|
1308
|
+
const offset = toolArgs.offset || 0;
|
|
1309
|
+
if (!dirPath) {
|
|
1310
|
+
throw new Error('path is required');
|
|
1311
|
+
}
|
|
1312
|
+
// Expand and resolve path
|
|
1313
|
+
const expandedPath = dirPath.startsWith('~')
|
|
1314
|
+
? path.join(process.env.HOME || '', dirPath.slice(1))
|
|
1315
|
+
: dirPath;
|
|
1316
|
+
const absolutePath = path.resolve(expandedPath);
|
|
1317
|
+
if (!fs.existsSync(absolutePath)) {
|
|
1318
|
+
throw new Error(`Directory not found: ${absolutePath}`);
|
|
1319
|
+
}
|
|
1320
|
+
const stats = fs.statSync(absolutePath);
|
|
1321
|
+
if (!stats.isDirectory()) {
|
|
1322
|
+
throw new Error(`Path is not a directory: ${absolutePath}. Use action 'file' for files.`);
|
|
1323
|
+
}
|
|
1324
|
+
// Auto-name ontology from directory if not provided
|
|
1325
|
+
const finalOntology = ontology || path.basename(absolutePath);
|
|
1326
|
+
// Validate directory against allowlist
|
|
1327
|
+
const manager = new mcp_allowlist_js_1.McpAllowlistManager();
|
|
1328
|
+
const dirValidation = manager.validateDirectory(absolutePath);
|
|
1329
|
+
if (!dirValidation.allowed) {
|
|
1330
|
+
throw new Error(`Directory not allowed: ${dirValidation.reason}. ${dirValidation.hint || ''}`);
|
|
1331
|
+
}
|
|
1332
|
+
// Collect files
|
|
1333
|
+
const files = [];
|
|
1334
|
+
const skipped = [];
|
|
1335
|
+
function scanDirectory(dir) {
|
|
1336
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
1337
|
+
for (const entry of entries) {
|
|
1338
|
+
const fullPath = path.join(dir, entry.name);
|
|
1339
|
+
if (entry.isDirectory() && recursive) {
|
|
1340
|
+
scanDirectory(fullPath);
|
|
1341
|
+
}
|
|
1342
|
+
else if (entry.isFile()) {
|
|
1343
|
+
// Validate each file
|
|
1344
|
+
const fileValidation = manager.validatePath(fullPath);
|
|
1345
|
+
if (fileValidation.allowed) {
|
|
1346
|
+
files.push(fullPath);
|
|
1347
|
+
}
|
|
1348
|
+
else {
|
|
1349
|
+
skipped.push(fullPath);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
scanDirectory(absolutePath);
|
|
1355
|
+
// Apply pagination
|
|
1356
|
+
const paginatedFiles = files.slice(offset, offset + limit);
|
|
1357
|
+
// TODO: Implement batch ingestion
|
|
1358
|
+
// For now, return summary with pagination
|
|
1359
|
+
const result = {
|
|
1360
|
+
status: 'not_implemented',
|
|
1361
|
+
message: 'Batch directory ingestion not yet implemented',
|
|
1362
|
+
directory: absolutePath,
|
|
1363
|
+
ontology: finalOntology,
|
|
1364
|
+
files_found: files.length,
|
|
1365
|
+
files_skipped: skipped.length,
|
|
1366
|
+
next_phase: 'Phase 3 - Batch ingestion implementation',
|
|
1367
|
+
files: paginatedFiles,
|
|
1368
|
+
pagination: {
|
|
1369
|
+
offset,
|
|
1370
|
+
limit,
|
|
1371
|
+
total: files.length,
|
|
1372
|
+
has_more: offset + limit < files.length,
|
|
1373
|
+
},
|
|
1374
|
+
};
|
|
1375
|
+
return {
|
|
1376
|
+
content: [
|
|
1377
|
+
{
|
|
1378
|
+
type: 'text',
|
|
1379
|
+
text: (0, formatters_js_1.formatIngestDirectoryResult)(result),
|
|
1380
|
+
},
|
|
1381
|
+
],
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
default:
|
|
1385
|
+
throw new Error(`Unknown ingest action: ${action}`);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
case 'source': {
|
|
1389
|
+
const source_id = toolArgs.source_id;
|
|
1390
|
+
if (!source_id) {
|
|
1391
|
+
throw new Error('source_id is required');
|
|
1392
|
+
}
|
|
1393
|
+
try {
|
|
1394
|
+
// First get metadata to determine content type
|
|
1395
|
+
const metadata = await client.getSourceMetadata(source_id);
|
|
1396
|
+
if (metadata.content_type === 'image') {
|
|
1397
|
+
// Image source - return the image
|
|
1398
|
+
const base64Image = await client.getSourceImageBase64(source_id);
|
|
1399
|
+
return {
|
|
1400
|
+
content: [
|
|
1401
|
+
{
|
|
1402
|
+
type: 'image',
|
|
1403
|
+
data: base64Image,
|
|
1404
|
+
mimeType: 'image/jpeg',
|
|
1405
|
+
},
|
|
1406
|
+
{
|
|
1407
|
+
type: 'text',
|
|
1408
|
+
text: `Retrieved image for source: ${source_id}\n\nDocument: ${metadata.document}\nParagraph: ${metadata.paragraph}\n\nThis image was extracted from the knowledge graph. You can:\n1. Compare the image to extracted concepts to verify accuracy\n2. Create a new description if something was missed\n3. Use ingest action to add refined descriptions`,
|
|
1409
|
+
},
|
|
1410
|
+
],
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
else {
|
|
1414
|
+
// Text source - return metadata with full_text
|
|
1415
|
+
const lines = [
|
|
1416
|
+
`📄 Source: ${source_id}`,
|
|
1417
|
+
'',
|
|
1418
|
+
`Document: ${metadata.document}`,
|
|
1419
|
+
`Paragraph: ${metadata.paragraph}`,
|
|
1420
|
+
`Content Type: ${metadata.content_type || 'text'}`,
|
|
1421
|
+
'',
|
|
1422
|
+
];
|
|
1423
|
+
if (metadata.char_offset_start !== undefined && metadata.char_offset_end !== undefined) {
|
|
1424
|
+
lines.push(`Character Range: ${metadata.char_offset_start}-${metadata.char_offset_end}`);
|
|
1425
|
+
}
|
|
1426
|
+
if (metadata.file_path) {
|
|
1427
|
+
lines.push(`File Path: ${metadata.file_path}`);
|
|
1428
|
+
}
|
|
1429
|
+
lines.push('', '--- Full Text ---', '', metadata.full_text || '(no text content)');
|
|
1430
|
+
return {
|
|
1431
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
catch (error) {
|
|
1436
|
+
if (error.response?.status === 404) {
|
|
1437
|
+
throw new Error(`Source ${source_id} not found`);
|
|
1438
|
+
}
|
|
1439
|
+
throw error;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
case 'epistemic_status': {
|
|
1443
|
+
const action = toolArgs.action;
|
|
1444
|
+
switch (action) {
|
|
1445
|
+
case 'list': {
|
|
1446
|
+
const statusFilter = toolArgs.status_filter;
|
|
1447
|
+
const result = await client.listEpistemicStatus(statusFilter);
|
|
1448
|
+
const output = (0, formatters_js_1.formatEpistemicStatusList)(result);
|
|
1449
|
+
return {
|
|
1450
|
+
content: [{ type: 'text', text: output }],
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
case 'show': {
|
|
1454
|
+
const relationshipType = toolArgs.relationship_type;
|
|
1455
|
+
if (!relationshipType) {
|
|
1456
|
+
throw new Error('relationship_type is required for show action');
|
|
1457
|
+
}
|
|
1458
|
+
const result = await client.getEpistemicStatus(relationshipType);
|
|
1459
|
+
const output = (0, formatters_js_1.formatEpistemicStatusDetails)(result);
|
|
1460
|
+
return {
|
|
1461
|
+
content: [{ type: 'text', text: output }],
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
case 'measure': {
|
|
1465
|
+
const sampleSize = toolArgs.sample_size || 100;
|
|
1466
|
+
const store = toolArgs.store !== false;
|
|
1467
|
+
const verbose = toolArgs.verbose || false;
|
|
1468
|
+
const result = await client.measureEpistemicStatus({
|
|
1469
|
+
sample_size: sampleSize,
|
|
1470
|
+
store: store,
|
|
1471
|
+
verbose: verbose,
|
|
1472
|
+
});
|
|
1473
|
+
const output = (0, formatters_js_1.formatEpistemicStatusMeasurement)(result);
|
|
1474
|
+
return {
|
|
1475
|
+
content: [{ type: 'text', text: output }],
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
default:
|
|
1479
|
+
throw new Error(`Unknown epistemic_status action: ${action}`);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
case 'analyze_polarity_axis': {
|
|
1483
|
+
const result = await client.analyzePolarityAxis({
|
|
1484
|
+
positive_pole_id: toolArgs.positive_pole_id,
|
|
1485
|
+
negative_pole_id: toolArgs.negative_pole_id,
|
|
1486
|
+
candidate_ids: toolArgs.candidate_ids,
|
|
1487
|
+
auto_discover: toolArgs.auto_discover !== false,
|
|
1488
|
+
max_candidates: toolArgs.max_candidates || 20,
|
|
1489
|
+
max_hops: toolArgs.max_hops || 2,
|
|
1490
|
+
});
|
|
1491
|
+
const formattedText = (0, formatters_js_1.formatPolarityAxisResults)(result);
|
|
1492
|
+
return {
|
|
1493
|
+
content: [{ type: 'text', text: formattedText }],
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
case 'artifact': {
|
|
1497
|
+
const action = toolArgs.action;
|
|
1498
|
+
switch (action) {
|
|
1499
|
+
case 'list': {
|
|
1500
|
+
const result = await client.listArtifacts({
|
|
1501
|
+
artifact_type: toolArgs.artifact_type,
|
|
1502
|
+
representation: toolArgs.representation,
|
|
1503
|
+
ontology: toolArgs.ontology,
|
|
1504
|
+
limit: toolArgs.limit || 20,
|
|
1505
|
+
offset: toolArgs.offset || 0,
|
|
1506
|
+
});
|
|
1507
|
+
// Format artifact list for readability
|
|
1508
|
+
const lines = [
|
|
1509
|
+
`📦 Artifacts (${result.total} total, showing ${result.artifacts.length})`,
|
|
1510
|
+
'',
|
|
1511
|
+
];
|
|
1512
|
+
if (result.artifacts.length === 0) {
|
|
1513
|
+
lines.push('No artifacts found.');
|
|
1514
|
+
}
|
|
1515
|
+
else {
|
|
1516
|
+
for (const artifact of result.artifacts) {
|
|
1517
|
+
const freshness = artifact.is_fresh ? '✓ Fresh' : '⚠ Stale';
|
|
1518
|
+
lines.push(`[${artifact.id}] ${artifact.artifact_type} - ${artifact.name || '(unnamed)'}`);
|
|
1519
|
+
lines.push(` Representation: ${artifact.representation}`);
|
|
1520
|
+
lines.push(` ${freshness} (epoch: ${artifact.graph_epoch})`);
|
|
1521
|
+
if (artifact.ontology) {
|
|
1522
|
+
lines.push(` Ontology: ${artifact.ontology}`);
|
|
1523
|
+
}
|
|
1524
|
+
lines.push(` Created: ${artifact.created_at}`);
|
|
1525
|
+
lines.push('');
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
if (result.total > result.offset + result.artifacts.length) {
|
|
1529
|
+
lines.push(`Use offset=${result.offset + result.limit} to see more.`);
|
|
1530
|
+
}
|
|
1531
|
+
return {
|
|
1532
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
case 'show': {
|
|
1536
|
+
const artifactId = toolArgs.artifact_id;
|
|
1537
|
+
if (!artifactId) {
|
|
1538
|
+
throw new Error('artifact_id is required for show action');
|
|
1539
|
+
}
|
|
1540
|
+
const result = await client.getArtifact(artifactId);
|
|
1541
|
+
// Format artifact metadata
|
|
1542
|
+
const lines = [
|
|
1543
|
+
`📦 Artifact ${result.id}`,
|
|
1544
|
+
'',
|
|
1545
|
+
`Type: ${result.artifact_type}`,
|
|
1546
|
+
`Representation: ${result.representation}`,
|
|
1547
|
+
`Name: ${result.name || '(unnamed)'}`,
|
|
1548
|
+
`Owner ID: ${result.owner_id}`,
|
|
1549
|
+
'',
|
|
1550
|
+
`Freshness: ${result.is_fresh ? '✓ Fresh' : '⚠ Stale (graph changed)'}`,
|
|
1551
|
+
`Graph Epoch: ${result.graph_epoch}`,
|
|
1552
|
+
'',
|
|
1553
|
+
`Created: ${result.created_at}`,
|
|
1554
|
+
result.expires_at ? `Expires: ${result.expires_at}` : '',
|
|
1555
|
+
result.ontology ? `Ontology: ${result.ontology}` : '',
|
|
1556
|
+
'',
|
|
1557
|
+
'Parameters:',
|
|
1558
|
+
JSON.stringify(result.parameters, null, 2),
|
|
1559
|
+
].filter(Boolean);
|
|
1560
|
+
return {
|
|
1561
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
case 'payload': {
|
|
1565
|
+
const artifactId = toolArgs.artifact_id;
|
|
1566
|
+
if (!artifactId) {
|
|
1567
|
+
throw new Error('artifact_id is required for payload action');
|
|
1568
|
+
}
|
|
1569
|
+
const result = await client.getArtifactPayload(artifactId);
|
|
1570
|
+
// Return full artifact with payload
|
|
1571
|
+
const lines = [
|
|
1572
|
+
`📦 Artifact ${result.id} (${result.artifact_type})`,
|
|
1573
|
+
`Freshness: ${result.is_fresh ? '✓ Fresh' : '⚠ Stale'}`,
|
|
1574
|
+
'',
|
|
1575
|
+
'Payload:',
|
|
1576
|
+
JSON.stringify(result.payload, null, 2),
|
|
1577
|
+
];
|
|
1578
|
+
return {
|
|
1579
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
1582
|
+
default:
|
|
1583
|
+
throw new Error(`Unknown artifact action: ${action}`);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
case 'document': {
|
|
1587
|
+
const action = toolArgs.action;
|
|
1588
|
+
switch (action) {
|
|
1589
|
+
case 'list': {
|
|
1590
|
+
const result = await client.listDocuments({
|
|
1591
|
+
ontology: toolArgs.ontology,
|
|
1592
|
+
limit: toolArgs.limit || 50,
|
|
1593
|
+
offset: toolArgs.offset || 0,
|
|
1594
|
+
});
|
|
1595
|
+
const formattedText = (0, formatters_js_1.formatDocumentList)(result);
|
|
1596
|
+
return {
|
|
1597
|
+
content: [{ type: 'text', text: formattedText }],
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
case 'show': {
|
|
1601
|
+
const documentId = toolArgs.document_id;
|
|
1602
|
+
if (!documentId) {
|
|
1603
|
+
throw new Error('document_id is required for show action');
|
|
1604
|
+
}
|
|
1605
|
+
const result = await client.getDocumentContent(documentId);
|
|
1606
|
+
const formattedText = (0, formatters_js_1.formatDocumentContent)(result);
|
|
1607
|
+
return {
|
|
1608
|
+
content: [{ type: 'text', text: formattedText }],
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
case 'concepts': {
|
|
1612
|
+
const documentId = toolArgs.document_id;
|
|
1613
|
+
if (!documentId) {
|
|
1614
|
+
throw new Error('document_id is required for concepts action');
|
|
1615
|
+
}
|
|
1616
|
+
const includeDetails = toolArgs.include_details === true;
|
|
1617
|
+
const result = await client.getDocumentConcepts(documentId);
|
|
1618
|
+
if (includeDetails) {
|
|
1619
|
+
// Fetch full details for each unique concept
|
|
1620
|
+
const uniqueConceptIds = [...new Set(result.concepts.map((c) => c.concept_id))];
|
|
1621
|
+
const conceptDetails = [];
|
|
1622
|
+
for (const conceptId of uniqueConceptIds) {
|
|
1623
|
+
try {
|
|
1624
|
+
const details = await client.getConceptDetails(conceptId, true);
|
|
1625
|
+
conceptDetails.push(details);
|
|
1626
|
+
}
|
|
1627
|
+
catch (err) {
|
|
1628
|
+
conceptDetails.push({
|
|
1629
|
+
concept_id: conceptId,
|
|
1630
|
+
label: `(failed to load: ${err.message})`,
|
|
1631
|
+
error: true,
|
|
1632
|
+
});
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
const formattedText = (0, formatters_js_1.formatDocumentConceptsDetailed)(result, conceptDetails);
|
|
1636
|
+
return {
|
|
1637
|
+
content: [{ type: 'text', text: formattedText }],
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
else {
|
|
1641
|
+
const formattedText = (0, formatters_js_1.formatDocumentConcepts)(result);
|
|
1642
|
+
return {
|
|
1643
|
+
content: [{ type: 'text', text: formattedText }],
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
default:
|
|
1648
|
+
throw new Error(`Unknown document action: ${action}`);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
default:
|
|
1652
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
catch (error) {
|
|
1656
|
+
return {
|
|
1657
|
+
content: [
|
|
1658
|
+
{
|
|
1659
|
+
type: 'text',
|
|
1660
|
+
text: JSON.stringify({
|
|
1661
|
+
error: error.message,
|
|
1662
|
+
details: error.response?.data || error.toString(),
|
|
1663
|
+
}, null, 2),
|
|
1664
|
+
},
|
|
1665
|
+
],
|
|
1666
|
+
isError: true,
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
});
|
|
1670
|
+
/**
|
|
1671
|
+
* Prompt Handlers - Provide exploration guidance
|
|
1672
|
+
*/
|
|
1673
|
+
server.setRequestHandler(types_js_1.ListPromptsRequestSchema, async () => {
|
|
1674
|
+
return {
|
|
1675
|
+
prompts: [
|
|
1676
|
+
{
|
|
1677
|
+
name: 'explore-graph',
|
|
1678
|
+
description: 'Learn how to explore the knowledge graph effectively',
|
|
1679
|
+
},
|
|
1680
|
+
],
|
|
1681
|
+
};
|
|
1682
|
+
});
|
|
1683
|
+
server.setRequestHandler(types_js_1.GetPromptRequestSchema, async (request) => {
|
|
1684
|
+
const { name } = request.params;
|
|
1685
|
+
if (name === 'explore-graph') {
|
|
1686
|
+
return {
|
|
1687
|
+
messages: [
|
|
1688
|
+
{
|
|
1689
|
+
role: 'user',
|
|
1690
|
+
content: {
|
|
1691
|
+
type: 'text',
|
|
1692
|
+
text: `How should I explore this knowledge graph?`,
|
|
1693
|
+
},
|
|
1694
|
+
},
|
|
1695
|
+
{
|
|
1696
|
+
role: 'assistant',
|
|
1697
|
+
content: {
|
|
1698
|
+
type: 'text',
|
|
1699
|
+
text: `# Knowledge Graph Exploration Guide
|
|
1700
|
+
|
|
1701
|
+
This system transforms documents into semantic concept graphs with grounding strength (reliability scores) and evidence (quoted text).
|
|
1702
|
+
|
|
1703
|
+
## Exploration Workflow:
|
|
1704
|
+
|
|
1705
|
+
1. **search** - Your entry point. Find concepts by semantic similarity.
|
|
1706
|
+
- Returns RICH DATA:
|
|
1707
|
+
* Grounding strength: Reliability score (-1.0 to 1.0)
|
|
1708
|
+
* Diversity score: % showing conceptual richness
|
|
1709
|
+
* Authenticated diversity: Support/contradiction indicator (✅✓⚠❌)
|
|
1710
|
+
* Evidence samples: Quoted text from sources
|
|
1711
|
+
* Image indicators: Visual evidence available
|
|
1712
|
+
- Use 2-3 word phrases (e.g., "configuration management", "licensing issues")
|
|
1713
|
+
|
|
1714
|
+
2. **concept (action: connect)** - **PRIORITIZE THIS** - Discover HOW concepts connect.
|
|
1715
|
+
- This is often MORE VALUABLE than isolated concept details
|
|
1716
|
+
- Trace problem→solution chains, cause/effect relationships
|
|
1717
|
+
- See grounding + evidence at each step in the path
|
|
1718
|
+
- Reveals narrative flow through ideas
|
|
1719
|
+
- **PERFORMANCE CRITICAL**: Start with threshold=0.8, max_hops=3
|
|
1720
|
+
- Lower thresholds exponentially increase query time (0.6 = slow, <0.6 = very slow)
|
|
1721
|
+
|
|
1722
|
+
3. **concept (action: details)** - See the complete picture for any concept.
|
|
1723
|
+
- Returns: ALL quoted evidence + relationships
|
|
1724
|
+
- IMPORTANT: Contradicted concepts (negative grounding) are VALUABLE - they show problems/outdated approaches
|
|
1725
|
+
- Use this after finding interesting concepts via search or connect
|
|
1726
|
+
|
|
1727
|
+
4. **concept (action: related)** - Explore neighborhoods.
|
|
1728
|
+
- Find what's nearby in the concept graph
|
|
1729
|
+
- Discover clusters and themes
|
|
1730
|
+
- Use depth=1-2 for neighbors, 3-4 for broader exploration
|
|
1731
|
+
|
|
1732
|
+
## Resources (Fresh Data):
|
|
1733
|
+
|
|
1734
|
+
Use MCP resources for quick status checks without consuming tool budget:
|
|
1735
|
+
- **database/stats** - Concept counts, relationships, ontologies
|
|
1736
|
+
- **database/info** - PostgreSQL version, AGE extension
|
|
1737
|
+
- **database/health** - Connection status
|
|
1738
|
+
- **system/status** - Job scheduler, resource usage
|
|
1739
|
+
- **api/health** - API server status
|
|
1740
|
+
|
|
1741
|
+
## Understanding the Scores:
|
|
1742
|
+
|
|
1743
|
+
**Grounding Strength (-1.0 to 1.0)** - Reliability/Contradiction
|
|
1744
|
+
- **Positive (>0.7)**: Well-supported, reliable concept
|
|
1745
|
+
- **Moderate (0.3-0.7)**: Mixed evidence, use with caution
|
|
1746
|
+
- **Negative (<0)**: Contradicted or presented as a problem
|
|
1747
|
+
- **Contradicted (-1.0)**: Often the most interesting - shows pain points!
|
|
1748
|
+
|
|
1749
|
+
**Diversity Score (0-100%)** - Conceptual Richness
|
|
1750
|
+
- High diversity: Concept connects to many different related concepts
|
|
1751
|
+
- Shows breadth of connections in the knowledge graph
|
|
1752
|
+
- Higher scores indicate more interconnected, central concepts
|
|
1753
|
+
|
|
1754
|
+
**Authenticated Diversity (✅✓⚠❌)** - Directional Quality
|
|
1755
|
+
- ✅ Diverse support: Strong positive evidence across many connections
|
|
1756
|
+
- ✓ Some support: Moderate positive evidence
|
|
1757
|
+
- ⚠ Weak contradiction: Some conflicting evidence
|
|
1758
|
+
- ❌ Diverse contradiction: Strong negative evidence across connections
|
|
1759
|
+
- Combines grounding with diversity to show reliability + breadth
|
|
1760
|
+
|
|
1761
|
+
## How to Use the Scores:
|
|
1762
|
+
|
|
1763
|
+
**High Grounding + High Diversity** → Central, well-established concepts
|
|
1764
|
+
- These are reliable anchor points for exploration
|
|
1765
|
+
- Good starting points for connection queries
|
|
1766
|
+
|
|
1767
|
+
**High Grounding + Low Diversity** → Specialized, focused concepts
|
|
1768
|
+
- Domain-specific, niche ideas with strong support
|
|
1769
|
+
- May be isolated or newly added
|
|
1770
|
+
|
|
1771
|
+
**Low/Negative Grounding + High Diversity** → Controversial or evolving concepts
|
|
1772
|
+
- Contested ideas with multiple perspectives
|
|
1773
|
+
- Often the most interesting for understanding debates
|
|
1774
|
+
|
|
1775
|
+
**Authenticated Diversity** → Quick quality check
|
|
1776
|
+
- Use ✅ results confidently
|
|
1777
|
+
- Investigate ⚠ and ❌ results for contradictions and problems
|
|
1778
|
+
|
|
1779
|
+
## Pro Tips:
|
|
1780
|
+
- **Connection queries first**: After search, immediately explore HOW concepts connect - this reveals causality and narrative
|
|
1781
|
+
- Use diversity scores to find central vs. peripheral concepts
|
|
1782
|
+
- Start broad with search, then drill down with concept details
|
|
1783
|
+
- Contradicted concepts reveal what DIDN'T work - goldmines for learning
|
|
1784
|
+
- Connection paths tell stories - follow the evidence chain
|
|
1785
|
+
- Use resources for quick status checks instead of tools
|
|
1786
|
+
- **Performance**: Keep threshold >= 0.75 for connect queries to avoid slow/hung queries`,
|
|
1787
|
+
},
|
|
1788
|
+
},
|
|
1789
|
+
],
|
|
1790
|
+
};
|
|
1791
|
+
}
|
|
1792
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
1793
|
+
});
|
|
1794
|
+
/**
|
|
1795
|
+
* Start the MCP server
|
|
1796
|
+
*/
|
|
1797
|
+
async function main() {
|
|
1798
|
+
// Initialize authentication before starting server
|
|
1799
|
+
await initializeAuth();
|
|
1800
|
+
client = createAuthenticatedClient();
|
|
1801
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
1802
|
+
await server.connect(transport);
|
|
1803
|
+
// Log to stderr (stdout is used for MCP protocol)
|
|
1804
|
+
console.error('Knowledge Graph MCP Server running on stdio');
|
|
1805
|
+
}
|
|
1806
|
+
main().catch((error) => {
|
|
1807
|
+
console.error('Server error:', error);
|
|
1808
|
+
process.exit(1);
|
|
1809
|
+
});
|
|
1810
|
+
//# sourceMappingURL=mcp-server.js.map
|