contradiction-mcp 0.1.0

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.
Files changed (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +536 -0
  3. package/bin/cli.js +182 -0
  4. package/dist/analysis/claimMatcher.d.ts +48 -0
  5. package/dist/analysis/claimMatcher.js +131 -0
  6. package/dist/analysis/claimMatcher.js.map +1 -0
  7. package/dist/analysis/claimRelationship.d.ts +15 -0
  8. package/dist/analysis/claimRelationship.js +12 -0
  9. package/dist/analysis/claimRelationship.js.map +1 -0
  10. package/dist/analysis/confidenceScorer.d.ts +40 -0
  11. package/dist/analysis/confidenceScorer.js +59 -0
  12. package/dist/analysis/confidenceScorer.js.map +1 -0
  13. package/dist/analysis/contextAnalyzer.d.ts +13 -0
  14. package/dist/analysis/contextAnalyzer.js +187 -0
  15. package/dist/analysis/contextAnalyzer.js.map +1 -0
  16. package/dist/analysis/contradictionClassifier.d.ts +16 -0
  17. package/dist/analysis/contradictionClassifier.js +141 -0
  18. package/dist/analysis/contradictionClassifier.js.map +1 -0
  19. package/dist/analysis/contradictionEngine.d.ts +29 -0
  20. package/dist/analysis/contradictionEngine.js +284 -0
  21. package/dist/analysis/contradictionEngine.js.map +1 -0
  22. package/dist/analysis/explanationBuilder.d.ts +36 -0
  23. package/dist/analysis/explanationBuilder.js +77 -0
  24. package/dist/analysis/explanationBuilder.js.map +1 -0
  25. package/dist/analysis/valueComparator.d.ts +38 -0
  26. package/dist/analysis/valueComparator.js +418 -0
  27. package/dist/analysis/valueComparator.js.map +1 -0
  28. package/dist/config/env.d.ts +46 -0
  29. package/dist/config/env.js +34 -0
  30. package/dist/config/env.js.map +1 -0
  31. package/dist/connectors/base/connectorUtils.d.ts +26 -0
  32. package/dist/connectors/base/connectorUtils.js +63 -0
  33. package/dist/connectors/base/connectorUtils.js.map +1 -0
  34. package/dist/connectors/base/security.d.ts +11 -0
  35. package/dist/connectors/base/security.js +9 -0
  36. package/dist/connectors/base/security.js.map +1 -0
  37. package/dist/connectors/connectorRegistry.d.ts +43 -0
  38. package/dist/connectors/connectorRegistry.js +91 -0
  39. package/dist/connectors/connectorRegistry.js.map +1 -0
  40. package/dist/connectors/document/documentConnector.d.ts +45 -0
  41. package/dist/connectors/document/documentConnector.js +410 -0
  42. package/dist/connectors/document/documentConnector.js.map +1 -0
  43. package/dist/connectors/github/githubClient.d.ts +52 -0
  44. package/dist/connectors/github/githubClient.js +207 -0
  45. package/dist/connectors/github/githubClient.js.map +1 -0
  46. package/dist/connectors/github/githubConnector.d.ts +26 -0
  47. package/dist/connectors/github/githubConnector.js +169 -0
  48. package/dist/connectors/github/githubConnector.js.map +1 -0
  49. package/dist/connectors/github/githubExtractor.d.ts +26 -0
  50. package/dist/connectors/github/githubExtractor.js +469 -0
  51. package/dist/connectors/github/githubExtractor.js.map +1 -0
  52. package/dist/connectors/github/githubNormalizer.d.ts +11 -0
  53. package/dist/connectors/github/githubNormalizer.js +24 -0
  54. package/dist/connectors/github/githubNormalizer.js.map +1 -0
  55. package/dist/connectors/github/githubTypes.d.ts +55 -0
  56. package/dist/connectors/github/githubTypes.js +2 -0
  57. package/dist/connectors/github/githubTypes.js.map +1 -0
  58. package/dist/connectors/syncService.d.ts +60 -0
  59. package/dist/connectors/syncService.js +176 -0
  60. package/dist/connectors/syncService.js.map +1 -0
  61. package/dist/connectors/types/connector.d.ts +34 -0
  62. package/dist/connectors/types/connector.js +2 -0
  63. package/dist/connectors/types/connector.js.map +1 -0
  64. package/dist/connectors/types/fetchResult.d.ts +39 -0
  65. package/dist/connectors/types/fetchResult.js +2 -0
  66. package/dist/connectors/types/fetchResult.js.map +1 -0
  67. package/dist/connectors/website/ssrfGuard.d.ts +11 -0
  68. package/dist/connectors/website/ssrfGuard.js +122 -0
  69. package/dist/connectors/website/ssrfGuard.js.map +1 -0
  70. package/dist/connectors/website/websiteConnector.d.ts +30 -0
  71. package/dist/connectors/website/websiteConnector.js +231 -0
  72. package/dist/connectors/website/websiteConnector.js.map +1 -0
  73. package/dist/discovery/candidateGenerator.d.ts +12 -0
  74. package/dist/discovery/candidateGenerator.js +110 -0
  75. package/dist/discovery/candidateGenerator.js.map +1 -0
  76. package/dist/discovery/contradictionScanner.d.ts +30 -0
  77. package/dist/discovery/contradictionScanner.js +47 -0
  78. package/dist/discovery/contradictionScanner.js.map +1 -0
  79. package/dist/discovery/discoveryService.d.ts +37 -0
  80. package/dist/discovery/discoveryService.js +131 -0
  81. package/dist/discovery/discoveryService.js.map +1 -0
  82. package/dist/discovery/rankingService.d.ts +34 -0
  83. package/dist/discovery/rankingService.js +52 -0
  84. package/dist/discovery/rankingService.js.map +1 -0
  85. package/dist/discovery/similarityIndex.d.ts +20 -0
  86. package/dist/discovery/similarityIndex.js +89 -0
  87. package/dist/discovery/similarityIndex.js.map +1 -0
  88. package/dist/domain/entities/claim.d.ts +99 -0
  89. package/dist/domain/entities/claim.js +92 -0
  90. package/dist/domain/entities/claim.js.map +1 -0
  91. package/dist/domain/entities/contradiction.d.ts +110 -0
  92. package/dist/domain/entities/contradiction.js +74 -0
  93. package/dist/domain/entities/contradiction.js.map +1 -0
  94. package/dist/domain/entities/source.d.ts +43 -0
  95. package/dist/domain/entities/source.js +35 -0
  96. package/dist/domain/entities/source.js.map +1 -0
  97. package/dist/domain/types/common.d.ts +46 -0
  98. package/dist/domain/types/common.js +74 -0
  99. package/dist/domain/types/common.js.map +1 -0
  100. package/dist/httpServer.d.ts +25 -0
  101. package/dist/httpServer.js +164 -0
  102. package/dist/httpServer.js.map +1 -0
  103. package/dist/index.d.ts +1 -0
  104. package/dist/index.js +94 -0
  105. package/dist/index.js.map +1 -0
  106. package/dist/intelligence/authorityScorer.d.ts +20 -0
  107. package/dist/intelligence/authorityScorer.js +103 -0
  108. package/dist/intelligence/authorityScorer.js.map +1 -0
  109. package/dist/intelligence/entityResolver.d.ts +33 -0
  110. package/dist/intelligence/entityResolver.js +152 -0
  111. package/dist/intelligence/entityResolver.js.map +1 -0
  112. package/dist/intelligence/evidenceEvaluator.d.ts +15 -0
  113. package/dist/intelligence/evidenceEvaluator.js +63 -0
  114. package/dist/intelligence/evidenceEvaluator.js.map +1 -0
  115. package/dist/intelligence/freshnessScorer.d.ts +25 -0
  116. package/dist/intelligence/freshnessScorer.js +90 -0
  117. package/dist/intelligence/freshnessScorer.js.map +1 -0
  118. package/dist/intelligence/provenanceAnalyzer.d.ts +19 -0
  119. package/dist/intelligence/provenanceAnalyzer.js +33 -0
  120. package/dist/intelligence/provenanceAnalyzer.js.map +1 -0
  121. package/dist/intelligence/resolutionAdvisor.d.ts +40 -0
  122. package/dist/intelligence/resolutionAdvisor.js +118 -0
  123. package/dist/intelligence/resolutionAdvisor.js.map +1 -0
  124. package/dist/intelligence/semanticMatcher.d.ts +21 -0
  125. package/dist/intelligence/semanticMatcher.js +71 -0
  126. package/dist/intelligence/semanticMatcher.js.map +1 -0
  127. package/dist/scripts/comprehensiveVerification.d.ts +5 -0
  128. package/dist/scripts/comprehensiveVerification.js +961 -0
  129. package/dist/scripts/comprehensiveVerification.js.map +1 -0
  130. package/dist/scripts/finalLiveDemo.d.ts +5 -0
  131. package/dist/scripts/finalLiveDemo.js +226 -0
  132. package/dist/scripts/finalLiveDemo.js.map +1 -0
  133. package/dist/scripts/liveMcpSmoke.d.ts +1 -0
  134. package/dist/scripts/liveMcpSmoke.js +89 -0
  135. package/dist/scripts/liveMcpSmoke.js.map +1 -0
  136. package/dist/scripts/seedDemoData.d.ts +6 -0
  137. package/dist/scripts/seedDemoData.js +179 -0
  138. package/dist/scripts/seedDemoData.js.map +1 -0
  139. package/dist/server.d.ts +28 -0
  140. package/dist/server.js +1264 -0
  141. package/dist/server.js.map +1 -0
  142. package/dist/services/analysisService.d.ts +40 -0
  143. package/dist/services/analysisService.js +65 -0
  144. package/dist/services/analysisService.js.map +1 -0
  145. package/dist/services/healthService.d.ts +42 -0
  146. package/dist/services/healthService.js +50 -0
  147. package/dist/services/healthService.js.map +1 -0
  148. package/dist/services/metricsService.d.ts +35 -0
  149. package/dist/services/metricsService.js +57 -0
  150. package/dist/services/metricsService.js.map +1 -0
  151. package/dist/services/rateLimiter.d.ts +23 -0
  152. package/dist/services/rateLimiter.js +45 -0
  153. package/dist/services/rateLimiter.js.map +1 -0
  154. package/dist/services/reviewService.d.ts +32 -0
  155. package/dist/services/reviewService.js +94 -0
  156. package/dist/services/reviewService.js.map +1 -0
  157. package/dist/services/schedulerService.d.ts +30 -0
  158. package/dist/services/schedulerService.js +89 -0
  159. package/dist/services/schedulerService.js.map +1 -0
  160. package/dist/storage/backupService.d.ts +14 -0
  161. package/dist/storage/backupService.js +90 -0
  162. package/dist/storage/backupService.js.map +1 -0
  163. package/dist/storage/database.d.ts +119 -0
  164. package/dist/storage/database.js +935 -0
  165. package/dist/storage/database.js.map +1 -0
  166. package/dist/storage/migrations/001_initial.sql +63 -0
  167. package/dist/storage/migrations/002_discovery_uniqueness.sql +8 -0
  168. package/dist/storage/migrations/003_connector_identity.sql +7 -0
  169. package/dist/storage/migrations/004_claim_context.sql +15 -0
  170. package/dist/storage/migrations/005_claim_history.sql +26 -0
  171. package/dist/storage/migrations/006_contradiction_resolution.sql +27 -0
  172. package/dist/utils/logger.d.ts +20 -0
  173. package/dist/utils/logger.js +48 -0
  174. package/dist/utils/logger.js.map +1 -0
  175. package/package.json +73 -0
package/dist/server.js ADDED
@@ -0,0 +1,1264 @@
1
+ import { McpServer, ResourceTemplate } from '@modelcontextprotocol/server';
2
+ import { z } from 'zod';
3
+ import { ReviewService } from './services/reviewService.js';
4
+ import { ResolutionAdvisor } from './intelligence/resolutionAdvisor.js';
5
+ import { globalMetrics } from './services/metricsService.js';
6
+ import { logger } from './utils/logger.js';
7
+ import { NotFoundError, AuthorizationError, toSafeError } from './domain/types/common.js';
8
+ import { ContradictionSeverities, ContradictionStatuses } from './domain/entities/contradiction.js';
9
+ function checkScope(scope, options) {
10
+ if (!options.clientScopes)
11
+ return;
12
+ if (!options.clientScopes.includes('admin') && !options.clientScopes.includes(scope)) {
13
+ throw new AuthorizationError(`Insufficient permissions: operation requires '${scope}' or 'admin' scope`, { requiredScope: scope, activeScopes: options.clientScopes });
14
+ }
15
+ }
16
+ export function createMcpServer(options) {
17
+ const name = options.name || 'contradiction-mcp';
18
+ const version = options.version || '0.1.0';
19
+ const reviewService = options.reviewService ?? (options.dbManager ? new ReviewService(options.dbManager) : undefined);
20
+ const resolutionAdvisor = options.resolutionAdvisor ?? new ResolutionAdvisor();
21
+ const metricsService = options.metricsService ?? globalMetrics;
22
+ const server = new McpServer({
23
+ name,
24
+ version,
25
+ }, {
26
+ capabilities: {
27
+ tools: {},
28
+ resources: {},
29
+ prompts: {},
30
+ logging: {},
31
+ },
32
+ });
33
+ const registerTool = (toolName, description, schema,
34
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
+ handler) => {
36
+ if (!schema || Object.keys(schema).length === 0) {
37
+ server.registerTool(toolName, { description }, handler);
38
+ }
39
+ else {
40
+ server.registerTool(toolName, { description, inputSchema: schema }, handler);
41
+ }
42
+ };
43
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
+ server.tool = registerTool;
45
+ // 1. Tool: health_check
46
+ registerTool('health_check', 'Checks the operational status of Contradiction MCP, including database connectivity and server metrics.', {}, async () => {
47
+ try {
48
+ logger.debug('Executing health_check tool');
49
+ const health = await options.healthService.getHealth();
50
+ return {
51
+ isError: health.status === 'unhealthy',
52
+ content: [
53
+ {
54
+ type: 'text',
55
+ text: JSON.stringify(health, null, 2),
56
+ },
57
+ ],
58
+ };
59
+ }
60
+ catch (error) {
61
+ logger.error('Error during health_check execution', { error: String(error) });
62
+ const safe = toSafeError(error);
63
+ return {
64
+ isError: true,
65
+ content: [
66
+ {
67
+ type: 'text',
68
+ text: JSON.stringify({
69
+ status: 'unhealthy',
70
+ error: safe.message,
71
+ code: safe.code,
72
+ timestamp: new Date().toISOString(),
73
+ }, null, 2),
74
+ },
75
+ ],
76
+ };
77
+ }
78
+ });
79
+ // 2. Tool: analyze_claim_pair
80
+ registerTool('analyze_claim_pair', 'Analyzes two claims to determine whether they contradict each other, returning contradiction classification, severity, confidence score, and explanation.', {
81
+ claimAId: z.string().min(1).describe('The unique ID of the first claim'),
82
+ claimBId: z.string().min(1).describe('The unique ID of the second claim'),
83
+ }, async (args) => {
84
+ try {
85
+ logger.debug('Executing analyze_claim_pair tool', {
86
+ claimAId: args.claimAId,
87
+ claimBId: args.claimBId,
88
+ });
89
+ if (!options.analysisService) {
90
+ throw new Error('AnalysisService is not configured on this server instance');
91
+ }
92
+ const result = options.analysisService.analyzeClaimPair(args.claimAId, args.claimBId);
93
+ return {
94
+ isError: false,
95
+ content: [
96
+ {
97
+ type: 'text',
98
+ text: JSON.stringify(result, null, 2),
99
+ },
100
+ ],
101
+ };
102
+ }
103
+ catch (error) {
104
+ logger.error('Error during analyze_claim_pair execution', { error: String(error) });
105
+ const safe = toSafeError(error);
106
+ return {
107
+ isError: true,
108
+ content: [
109
+ {
110
+ type: 'text',
111
+ text: JSON.stringify({
112
+ error: safe.message,
113
+ code: safe.code,
114
+ claimAId: args.claimAId,
115
+ claimBId: args.claimBId,
116
+ }, null, 2),
117
+ },
118
+ ],
119
+ };
120
+ }
121
+ });
122
+ // 3. Tool: scan_for_contradictions
123
+ registerTool('scan_for_contradictions', 'Scans all claims in the database using deterministic candidate grouping, detects contradictions, persists them without duplicates, and returns a summary report.', {
124
+ limit: z
125
+ .number()
126
+ .int()
127
+ .positive()
128
+ .optional()
129
+ .describe('Maximum number of contradiction results to return (default: 50)'),
130
+ minConfidence: z
131
+ .number()
132
+ .min(0)
133
+ .max(1)
134
+ .optional()
135
+ .describe('Minimum confidence threshold between 0.0 and 1.0 (default: 0.5)'),
136
+ includeDismissed: z
137
+ .boolean()
138
+ .optional()
139
+ .describe('Whether to include previously dismissed contradictions (default: false)'),
140
+ }, async (args) => {
141
+ try {
142
+ logger.debug('Executing scan_for_contradictions tool', args);
143
+ if (!options.discoveryService) {
144
+ throw new Error('DiscoveryService is not configured on this server instance');
145
+ }
146
+ const summary = options.discoveryService.scanAllClaims(args);
147
+ return {
148
+ isError: false,
149
+ content: [
150
+ {
151
+ type: 'text',
152
+ text: JSON.stringify(summary, null, 2),
153
+ },
154
+ ],
155
+ };
156
+ }
157
+ catch (error) {
158
+ logger.error('Error during scan_for_contradictions execution', { error: String(error) });
159
+ const safe = toSafeError(error);
160
+ return {
161
+ isError: true,
162
+ content: [
163
+ {
164
+ type: 'text',
165
+ text: JSON.stringify({
166
+ error: safe.message,
167
+ code: safe.code,
168
+ }, null, 2),
169
+ },
170
+ ],
171
+ };
172
+ }
173
+ });
174
+ // 4. Tool: scan_claim_for_contradictions
175
+ registerTool('scan_claim_for_contradictions', 'Performs an incremental scan for a single claim against relevant candidate claims in the database and persists new contradictions.', {
176
+ claimId: z.string().min(1).describe('The unique ID of the claim to scan'),
177
+ minConfidence: z
178
+ .number()
179
+ .min(0)
180
+ .max(1)
181
+ .optional()
182
+ .describe('Minimum confidence threshold between 0.0 and 1.0 (default: 0.5)'),
183
+ }, async (args) => {
184
+ try {
185
+ logger.debug('Executing scan_claim_for_contradictions tool', args);
186
+ if (!options.discoveryService) {
187
+ throw new Error('DiscoveryService is not configured on this server instance');
188
+ }
189
+ const summary = options.discoveryService.scanClaim(args.claimId, {
190
+ minConfidence: args.minConfidence,
191
+ });
192
+ return {
193
+ isError: false,
194
+ content: [
195
+ {
196
+ type: 'text',
197
+ text: JSON.stringify(summary, null, 2),
198
+ },
199
+ ],
200
+ };
201
+ }
202
+ catch (error) {
203
+ logger.error('Error during scan_claim_for_contradictions execution', {
204
+ error: String(error),
205
+ });
206
+ const safe = toSafeError(error);
207
+ return {
208
+ isError: true,
209
+ content: [
210
+ {
211
+ type: 'text',
212
+ text: JSON.stringify({
213
+ error: safe.message,
214
+ code: safe.code,
215
+ claimId: args.claimId,
216
+ }, null, 2),
217
+ },
218
+ ],
219
+ };
220
+ }
221
+ });
222
+ // 5. Tool: list_contradictions
223
+ registerTool('list_contradictions', 'Queries stored contradiction records from the database with optional filtering by status, severity, type, and confidence.', {
224
+ status: z
225
+ .enum(ContradictionStatuses)
226
+ .optional()
227
+ .describe('Filter by contradiction status (OPEN, REVIEWED, RESOLVED, DISMISSED)'),
228
+ severity: z
229
+ .enum(ContradictionSeverities)
230
+ .optional()
231
+ .describe('Filter by severity level (LOW, MEDIUM, HIGH, CRITICAL)'),
232
+ type: z.string().optional().describe('Filter by contradiction type string'),
233
+ minConfidence: z
234
+ .number()
235
+ .min(0)
236
+ .max(1)
237
+ .optional()
238
+ .describe('Filter by minimum confidence threshold'),
239
+ limit: z
240
+ .number()
241
+ .int()
242
+ .positive()
243
+ .optional()
244
+ .describe('Maximum number of records to return (default: 50)'),
245
+ offset: z.number().int().nonnegative().optional().describe('Pagination offset (default: 0)'),
246
+ }, async (args) => {
247
+ try {
248
+ logger.debug('Executing list_contradictions tool', args);
249
+ if (!options.dbManager) {
250
+ throw new Error('DatabaseManager is not configured on this server instance');
251
+ }
252
+ const items = options.dbManager.listContradictions({
253
+ status: args.status,
254
+ severity: args.severity,
255
+ type: args.type,
256
+ minConfidence: args.minConfidence,
257
+ limit: args.limit ?? 50,
258
+ offset: args.offset,
259
+ });
260
+ // Enrich with basic claim and source references
261
+ const enriched = items.map((item) => {
262
+ const claimA = options.dbManager?.getClaimById(item.claimAId);
263
+ const claimB = options.dbManager?.getClaimById(item.claimBId);
264
+ const sourceA = claimA ? options.dbManager?.getSourceById(claimA.sourceId) : null;
265
+ const sourceB = claimB ? options.dbManager?.getSourceById(claimB.sourceId) : null;
266
+ return {
267
+ id: item.id,
268
+ contradictionType: item.contradictionType,
269
+ severity: item.severity,
270
+ confidence: item.confidence,
271
+ status: item.status,
272
+ explanation: item.explanation,
273
+ detectedAt: item.detectedAt,
274
+ resolvedAt: item.resolvedAt,
275
+ claims: {
276
+ claimA: claimA
277
+ ? {
278
+ id: claimA.id,
279
+ subject: claimA.subject,
280
+ predicate: claimA.predicate,
281
+ value: claimA.value,
282
+ }
283
+ : { id: item.claimAId },
284
+ claimB: claimB
285
+ ? {
286
+ id: claimB.id,
287
+ subject: claimB.subject,
288
+ predicate: claimB.predicate,
289
+ value: claimB.value,
290
+ }
291
+ : { id: item.claimBId },
292
+ },
293
+ sources: {
294
+ sourceA: sourceA ? { id: sourceA.id, name: sourceA.name, type: sourceA.type } : null,
295
+ sourceB: sourceB ? { id: sourceB.id, name: sourceB.name, type: sourceB.type } : null,
296
+ },
297
+ };
298
+ });
299
+ return {
300
+ isError: false,
301
+ content: [
302
+ {
303
+ type: 'text',
304
+ text: JSON.stringify({
305
+ count: enriched.length,
306
+ contradictions: enriched,
307
+ }, null, 2),
308
+ },
309
+ ],
310
+ };
311
+ }
312
+ catch (error) {
313
+ logger.error('Error during list_contradictions execution', { error: String(error) });
314
+ const safe = toSafeError(error);
315
+ return {
316
+ isError: true,
317
+ content: [
318
+ {
319
+ type: 'text',
320
+ text: JSON.stringify({
321
+ error: safe.message,
322
+ code: safe.code,
323
+ }, null, 2),
324
+ },
325
+ ],
326
+ };
327
+ }
328
+ });
329
+ // 6. Tool: get_contradiction
330
+ registerTool('get_contradiction', 'Retrieves a single contradiction by ID, including its complete claim and source records for in-depth agent investigation.', {
331
+ contradictionId: z.string().min(1).describe('The unique ID of the contradiction record'),
332
+ }, async (args) => {
333
+ try {
334
+ logger.debug('Executing get_contradiction tool', args);
335
+ if (!options.dbManager) {
336
+ throw new Error('DatabaseManager is not configured on this server instance');
337
+ }
338
+ const details = options.dbManager.getContradictionWithDetails(args.contradictionId);
339
+ if (!details) {
340
+ throw new NotFoundError('Contradiction', args.contradictionId);
341
+ }
342
+ return {
343
+ isError: false,
344
+ content: [
345
+ {
346
+ type: 'text',
347
+ text: JSON.stringify(details, null, 2),
348
+ },
349
+ ],
350
+ };
351
+ }
352
+ catch (error) {
353
+ logger.error('Error during get_contradiction execution', { error: String(error) });
354
+ const safe = toSafeError(error);
355
+ return {
356
+ isError: true,
357
+ content: [
358
+ {
359
+ type: 'text',
360
+ text: JSON.stringify({
361
+ error: safe.message,
362
+ code: safe.code,
363
+ contradictionId: args.contradictionId,
364
+ }, null, 2),
365
+ },
366
+ ],
367
+ };
368
+ }
369
+ });
370
+ // 7. Tool: list_connectors
371
+ registerTool('list_connectors', 'Lists all available external source connectors with their capabilities, authentication requirements, and statuses.', {}, async () => {
372
+ try {
373
+ logger.debug('Executing list_connectors tool');
374
+ if (!options.connectorRegistry) {
375
+ throw new Error('ConnectorRegistry is not configured on this server instance');
376
+ }
377
+ const connectors = options.connectorRegistry.list();
378
+ return {
379
+ isError: false,
380
+ content: [
381
+ {
382
+ type: 'text',
383
+ text: JSON.stringify({
384
+ count: connectors.length,
385
+ connectors,
386
+ }, null, 2),
387
+ },
388
+ ],
389
+ };
390
+ }
391
+ catch (error) {
392
+ logger.error('Error during list_connectors execution', { error: String(error) });
393
+ const safe = toSafeError(error);
394
+ return {
395
+ isError: true,
396
+ content: [
397
+ {
398
+ type: 'text',
399
+ text: JSON.stringify({
400
+ error: safe.message,
401
+ code: safe.code,
402
+ }, null, 2),
403
+ },
404
+ ],
405
+ };
406
+ }
407
+ });
408
+ // 8. Tool: test_github_connection
409
+ registerTool('test_github_connection', 'Tests connectivity to GitHub and validates accessibility of a specific repository without exposing credentials.', {
410
+ owner: z
411
+ .string()
412
+ .min(1)
413
+ .describe('The GitHub organization or username owning the repository'),
414
+ repo: z.string().min(1).describe('The repository name'),
415
+ }, async (args) => {
416
+ try {
417
+ logger.debug('Executing test_github_connection tool', args);
418
+ if (!options.connectorRegistry) {
419
+ throw new Error('ConnectorRegistry is not configured on this server instance');
420
+ }
421
+ const connector = options.connectorRegistry.get('github');
422
+ if (!connector) {
423
+ throw new NotFoundError('Connector', 'github');
424
+ }
425
+ const result = await connector.testConnection({
426
+ owner: args.owner,
427
+ repo: args.repo,
428
+ });
429
+ return {
430
+ isError: !result.accessible,
431
+ content: [
432
+ {
433
+ type: 'text',
434
+ text: JSON.stringify(result, null, 2),
435
+ },
436
+ ],
437
+ };
438
+ }
439
+ catch (error) {
440
+ logger.error('Error during test_github_connection execution', { error: String(error) });
441
+ const safe = toSafeError(error);
442
+ return {
443
+ isError: true,
444
+ content: [
445
+ {
446
+ type: 'text',
447
+ text: JSON.stringify({
448
+ error: safe.message,
449
+ code: safe.code,
450
+ owner: args.owner,
451
+ repo: args.repo,
452
+ }, null, 2),
453
+ },
454
+ ],
455
+ };
456
+ }
457
+ });
458
+ // 9. Tool: sync_github_repository
459
+ registerTool('sync_github_repository', 'Ingests a real GitHub repository, extracts factual claims (runtimes, dependencies, ports), idempotently persists them in SQLite, and runs automatic contradiction discovery on touched claims.', {
460
+ owner: z
461
+ .string()
462
+ .min(1)
463
+ .describe('The GitHub organization or username owning the repository'),
464
+ repo: z.string().min(1).describe('The repository name'),
465
+ branch: z
466
+ .string()
467
+ .optional()
468
+ .describe('Optional git branch or tag name (defaults to repository default branch)'),
469
+ runDiscovery: z
470
+ .boolean()
471
+ .optional()
472
+ .describe('Whether to automatically trigger incremental contradiction discovery after sync (default: true)'),
473
+ }, async (args) => {
474
+ try {
475
+ checkScope('sync', options);
476
+ logger.debug('Executing sync_github_repository tool', args);
477
+ if (!options.syncService) {
478
+ throw new Error('SyncService is not configured on this server instance');
479
+ }
480
+ const summary = await options.syncService.syncSource('github', {
481
+ owner: args.owner,
482
+ repo: args.repo,
483
+ branch: args.branch,
484
+ }, {
485
+ runDiscoveryAfterSync: args.runDiscovery ?? true,
486
+ });
487
+ return {
488
+ isError: summary.status === 'failed',
489
+ content: [
490
+ {
491
+ type: 'text',
492
+ text: JSON.stringify(summary, null, 2),
493
+ },
494
+ ],
495
+ };
496
+ }
497
+ catch (error) {
498
+ logger.error('Error during sync_github_repository execution', { error: String(error) });
499
+ const safe = toSafeError(error);
500
+ return {
501
+ isError: true,
502
+ content: [
503
+ {
504
+ type: 'text',
505
+ text: JSON.stringify({
506
+ error: safe.message,
507
+ code: safe.code,
508
+ owner: args.owner,
509
+ repo: args.repo,
510
+ }, null, 2),
511
+ },
512
+ ],
513
+ };
514
+ }
515
+ });
516
+ // 10. Tool: explain_claim_relationship
517
+ registerTool('explain_claim_relationship', 'Explains why two claims do or do not contradict each other based on context dimensions including environment, scope, source role, temporal state, SemVer range compatibility, and set membership.', {
518
+ claimAId: z.string().uuid().describe('ID of the first claim'),
519
+ claimBId: z.string().uuid().describe('ID of the second claim'),
520
+ }, async (args) => {
521
+ try {
522
+ logger.debug('Executing explain_claim_relationship tool', args);
523
+ if (!options.analysisService) {
524
+ throw new Error('AnalysisService is not configured on this server instance');
525
+ }
526
+ const result = options.analysisService.explainClaimRelationship(args.claimAId, args.claimBId);
527
+ return {
528
+ content: [
529
+ {
530
+ type: 'text',
531
+ text: JSON.stringify(result, null, 2),
532
+ },
533
+ ],
534
+ };
535
+ }
536
+ catch (error) {
537
+ logger.error('Error during explain_claim_relationship execution', { error: String(error) });
538
+ const safe = toSafeError(error);
539
+ return {
540
+ isError: true,
541
+ content: [
542
+ {
543
+ type: 'text',
544
+ text: JSON.stringify({
545
+ error: safe.message,
546
+ code: safe.code,
547
+ claimAId: args.claimAId,
548
+ claimBId: args.claimBId,
549
+ }, null, 2),
550
+ },
551
+ ],
552
+ };
553
+ }
554
+ });
555
+ // 11. Tool: review_contradiction
556
+ registerTool('review_contradiction', 'Marks a contradiction record as REVIEWED, recording the reviewer identity and optional notes in the persistent audit trail.', {
557
+ contradictionId: z
558
+ .string()
559
+ .min(1)
560
+ .describe('The unique ID of the contradiction record to review'),
561
+ reviewedBy: z.string().min(1).describe('Name or identifier of the reviewer / agent'),
562
+ notes: z.string().optional().describe('Review findings or triage notes'),
563
+ }, async (args) => {
564
+ try {
565
+ checkScope('review', options);
566
+ metricsService.recordToolCall('review_contradiction');
567
+ if (!reviewService) {
568
+ throw new Error('ReviewService is not configured on this server instance');
569
+ }
570
+ const updated = reviewService.reviewContradiction(args.contradictionId, {
571
+ reviewedBy: args.reviewedBy,
572
+ notes: args.notes,
573
+ });
574
+ return {
575
+ isError: false,
576
+ content: [
577
+ {
578
+ type: 'text',
579
+ text: JSON.stringify(updated, null, 2),
580
+ },
581
+ ],
582
+ };
583
+ }
584
+ catch (error) {
585
+ logger.error('Error during review_contradiction execution', { error: String(error) });
586
+ const safe = toSafeError(error);
587
+ return {
588
+ isError: true,
589
+ content: [
590
+ {
591
+ type: 'text',
592
+ text: JSON.stringify({
593
+ error: safe.message,
594
+ code: safe.code,
595
+ contradictionId: args.contradictionId,
596
+ }, null, 2),
597
+ },
598
+ ],
599
+ };
600
+ }
601
+ });
602
+ // 12. Tool: resolve_contradiction
603
+ registerTool('resolve_contradiction', 'Resolves a contradiction record, recording the authoritative chosen claim (optional), resolution reason, and audit trail.', {
604
+ contradictionId: z
605
+ .string()
606
+ .min(1)
607
+ .describe('The unique ID of the contradiction record to resolve'),
608
+ resolvedBy: z.string().min(1).describe('Name or identifier of the resolver'),
609
+ reason: z
610
+ .string()
611
+ .min(1)
612
+ .describe('Detailed explanation of why and how this contradiction was resolved'),
613
+ chosenClaimId: z
614
+ .string()
615
+ .optional()
616
+ .describe('ID of the claim accepted as authoritative (optional)'),
617
+ notes: z.string().optional().describe('Additional resolution notes'),
618
+ }, async (args) => {
619
+ try {
620
+ checkScope('resolve', options);
621
+ metricsService.recordToolCall('resolve_contradiction');
622
+ if (!reviewService) {
623
+ throw new Error('ReviewService is not configured on this server instance');
624
+ }
625
+ const updated = reviewService.resolveContradiction(args.contradictionId, {
626
+ resolvedBy: args.resolvedBy,
627
+ reason: args.reason,
628
+ chosenClaimId: args.chosenClaimId,
629
+ notes: args.notes,
630
+ });
631
+ return {
632
+ isError: false,
633
+ content: [
634
+ {
635
+ type: 'text',
636
+ text: JSON.stringify(updated, null, 2),
637
+ },
638
+ ],
639
+ };
640
+ }
641
+ catch (error) {
642
+ logger.error('Error during resolve_contradiction execution', { error: String(error) });
643
+ const safe = toSafeError(error);
644
+ return {
645
+ isError: true,
646
+ content: [
647
+ {
648
+ type: 'text',
649
+ text: JSON.stringify({
650
+ error: safe.message,
651
+ code: safe.code,
652
+ contradictionId: args.contradictionId,
653
+ }, null, 2),
654
+ },
655
+ ],
656
+ };
657
+ }
658
+ });
659
+ // 13. Tool: dismiss_contradiction
660
+ registerTool('dismiss_contradiction', 'Dismisses a contradiction record as acceptable or non-actionable, preserving an audit record of the decision.', {
661
+ contradictionId: z
662
+ .string()
663
+ .min(1)
664
+ .describe('The unique ID of the contradiction record to dismiss'),
665
+ dismissedBy: z
666
+ .string()
667
+ .min(1)
668
+ .describe('Name or identifier of the actor dismissing this contradiction'),
669
+ reason: z.string().min(1).describe('Reason why this contradiction is dismissed'),
670
+ notes: z.string().optional().describe('Additional notes'),
671
+ }, async (args) => {
672
+ try {
673
+ checkScope('review', options);
674
+ metricsService.recordToolCall('dismiss_contradiction');
675
+ if (!reviewService) {
676
+ throw new Error('ReviewService is not configured on this server instance');
677
+ }
678
+ const updated = reviewService.dismissContradiction(args.contradictionId, {
679
+ dismissedBy: args.dismissedBy,
680
+ reason: args.reason,
681
+ notes: args.notes,
682
+ });
683
+ return {
684
+ isError: false,
685
+ content: [
686
+ {
687
+ type: 'text',
688
+ text: JSON.stringify(updated, null, 2),
689
+ },
690
+ ],
691
+ };
692
+ }
693
+ catch (error) {
694
+ logger.error('Error during dismiss_contradiction execution', { error: String(error) });
695
+ const safe = toSafeError(error);
696
+ return {
697
+ isError: true,
698
+ content: [
699
+ {
700
+ type: 'text',
701
+ text: JSON.stringify({
702
+ error: safe.message,
703
+ code: safe.code,
704
+ contradictionId: args.contradictionId,
705
+ }, null, 2),
706
+ },
707
+ ],
708
+ };
709
+ }
710
+ });
711
+ // 14. Tool: reopen_contradiction
712
+ registerTool('reopen_contradiction', 'Reopens a previously resolved or dismissed contradiction back to OPEN status with audit trail logging.', {
713
+ contradictionId: z
714
+ .string()
715
+ .min(1)
716
+ .describe('The unique ID of the contradiction record to reopen'),
717
+ reopenedBy: z
718
+ .string()
719
+ .min(1)
720
+ .describe('Name or identifier of the actor reopening this contradiction'),
721
+ reason: z.string().optional().describe('Reason for reopening'),
722
+ notes: z.string().optional().describe('Additional notes'),
723
+ }, async (args) => {
724
+ try {
725
+ checkScope('review', options);
726
+ metricsService.recordToolCall('reopen_contradiction');
727
+ if (!reviewService) {
728
+ throw new Error('ReviewService is not configured on this server instance');
729
+ }
730
+ const updated = reviewService.reopenContradiction(args.contradictionId, {
731
+ reopenedBy: args.reopenedBy,
732
+ reason: args.reason,
733
+ notes: args.notes,
734
+ });
735
+ return {
736
+ isError: false,
737
+ content: [
738
+ {
739
+ type: 'text',
740
+ text: JSON.stringify(updated, null, 2),
741
+ },
742
+ ],
743
+ };
744
+ }
745
+ catch (error) {
746
+ logger.error('Error during reopen_contradiction execution', { error: String(error) });
747
+ const safe = toSafeError(error);
748
+ return {
749
+ isError: true,
750
+ content: [
751
+ {
752
+ type: 'text',
753
+ text: JSON.stringify({
754
+ error: safe.message,
755
+ code: safe.code,
756
+ contradictionId: args.contradictionId,
757
+ }, null, 2),
758
+ },
759
+ ],
760
+ };
761
+ }
762
+ });
763
+ // 15. Tool: get_contradiction_history
764
+ registerTool('get_contradiction_history', 'Retrieves the chronological audit history of review and resolution actions performed on a contradiction record.', {
765
+ contradictionId: z.string().min(1).describe('The unique ID of the contradiction record'),
766
+ }, async (args) => {
767
+ try {
768
+ metricsService.recordToolCall('get_contradiction_history');
769
+ if (!reviewService) {
770
+ throw new Error('ReviewService is not configured on this server instance');
771
+ }
772
+ const history = reviewService.getContradictionHistory(args.contradictionId);
773
+ return {
774
+ isError: false,
775
+ content: [
776
+ {
777
+ type: 'text',
778
+ text: JSON.stringify({
779
+ contradictionId: args.contradictionId,
780
+ count: history.length,
781
+ history,
782
+ }, null, 2),
783
+ },
784
+ ],
785
+ };
786
+ }
787
+ catch (error) {
788
+ logger.error('Error during get_contradiction_history execution', { error: String(error) });
789
+ const safe = toSafeError(error);
790
+ return {
791
+ isError: true,
792
+ content: [
793
+ {
794
+ type: 'text',
795
+ text: JSON.stringify({
796
+ error: safe.message,
797
+ code: safe.code,
798
+ contradictionId: args.contradictionId,
799
+ }, null, 2),
800
+ },
801
+ ],
802
+ };
803
+ }
804
+ });
805
+ // 16. Tool: get_claim_history
806
+ registerTool('get_claim_history', 'Retrieves the chronological value transition history for a specific factual claim, tracking updates and superseded values.', {
807
+ claimId: z.string().min(1).describe('The unique ID of the claim'),
808
+ }, async (args) => {
809
+ try {
810
+ metricsService.recordToolCall('get_claim_history');
811
+ if (!options.dbManager) {
812
+ throw new Error('DatabaseManager is not configured on this server instance');
813
+ }
814
+ const claim = options.dbManager.getClaimById(args.claimId);
815
+ if (!claim) {
816
+ throw new NotFoundError('Claim', args.claimId);
817
+ }
818
+ const history = options.dbManager.getClaimHistory(args.claimId);
819
+ return {
820
+ isError: false,
821
+ content: [
822
+ {
823
+ type: 'text',
824
+ text: JSON.stringify({
825
+ claimId: args.claimId,
826
+ currentValue: claim.value,
827
+ normalizedValue: claim.normalizedValue,
828
+ observedAt: claim.observedAt,
829
+ firstSeenAt: claim.firstSeenAt,
830
+ lastSeenAt: claim.lastSeenAt,
831
+ supersededBy: claim.supersededBy,
832
+ historyCount: history.length,
833
+ history,
834
+ }, null, 2),
835
+ },
836
+ ],
837
+ };
838
+ }
839
+ catch (error) {
840
+ logger.error('Error during get_claim_history execution', { error: String(error) });
841
+ const safe = toSafeError(error);
842
+ return {
843
+ isError: true,
844
+ content: [
845
+ {
846
+ type: 'text',
847
+ text: JSON.stringify({
848
+ error: safe.message,
849
+ code: safe.code,
850
+ claimId: args.claimId,
851
+ }, null, 2),
852
+ },
853
+ ],
854
+ };
855
+ }
856
+ });
857
+ // 17. Tool: advise_resolution
858
+ registerTool('advise_resolution', 'Generates deterministic authority and freshness scoring comparison to advise an agent on which claim likely represents current truth.', {
859
+ contradictionId: z.string().min(1).describe('The unique ID of the contradiction record'),
860
+ }, async (args) => {
861
+ try {
862
+ metricsService.recordToolCall('advise_resolution');
863
+ if (!options.dbManager) {
864
+ throw new Error('DatabaseManager is not configured on this server instance');
865
+ }
866
+ const details = options.dbManager.getContradictionWithDetails(args.contradictionId);
867
+ if (!details) {
868
+ throw new NotFoundError('Contradiction', args.contradictionId);
869
+ }
870
+ const advice = resolutionAdvisor.adviseResolution(details.contradiction, details.claimA, details.claimB, details.sourceA, details.sourceB);
871
+ return {
872
+ isError: false,
873
+ content: [
874
+ {
875
+ type: 'text',
876
+ text: JSON.stringify(advice, null, 2),
877
+ },
878
+ ],
879
+ };
880
+ }
881
+ catch (error) {
882
+ logger.error('Error during advise_resolution execution', { error: String(error) });
883
+ const safe = toSafeError(error);
884
+ return {
885
+ isError: true,
886
+ content: [
887
+ {
888
+ type: 'text',
889
+ text: JSON.stringify({
890
+ error: safe.message,
891
+ code: safe.code,
892
+ contradictionId: args.contradictionId,
893
+ }, null, 2),
894
+ },
895
+ ],
896
+ };
897
+ }
898
+ });
899
+ // 18. Tool: sync_source
900
+ registerTool('sync_source', 'Unified ingestion tool: synchronizes any registered source connector (github, document, website), idempotently extracts claims, and runs automatic contradiction discovery.', {
901
+ connector: z
902
+ .string()
903
+ .min(1)
904
+ .describe('The connector identifier (e.g. github, document, website)'),
905
+ input: z.record(z.string(), z.unknown()).describe('Connector-specific input parameters'),
906
+ runDiscovery: z
907
+ .boolean()
908
+ .optional()
909
+ .describe('Whether to trigger automatic contradiction discovery (default: true)'),
910
+ }, async (args) => {
911
+ try {
912
+ checkScope('sync', options);
913
+ metricsService.recordToolCall('sync_source');
914
+ if (!options.syncService) {
915
+ throw new Error('SyncService is not configured on this server instance');
916
+ }
917
+ const summary = await options.syncService.syncSource(args.connector, args.input, {
918
+ runDiscoveryAfterSync: args.runDiscovery ?? true,
919
+ });
920
+ metricsService.recordSync(summary.status === 'completed', summary.claimsCreated, summary.contradictionsFound ?? 0);
921
+ return {
922
+ isError: summary.status === 'failed',
923
+ content: [
924
+ {
925
+ type: 'text',
926
+ text: JSON.stringify(summary, null, 2),
927
+ },
928
+ ],
929
+ };
930
+ }
931
+ catch (error) {
932
+ logger.error('Error during sync_source execution', { error: String(error) });
933
+ const safe = toSafeError(error);
934
+ return {
935
+ isError: true,
936
+ content: [
937
+ {
938
+ type: 'text',
939
+ text: JSON.stringify({
940
+ error: safe.message,
941
+ code: safe.code,
942
+ connector: args.connector,
943
+ }, null, 2),
944
+ },
945
+ ],
946
+ };
947
+ }
948
+ });
949
+ // 19. Tool: sync_sources
950
+ registerTool('sync_sources', 'Batch ingestion tool: synchronizes multiple external sources with bounded concurrency, failure isolation, and unified contradiction discovery.', {
951
+ sources: z
952
+ .array(z.object({
953
+ connector: z.string().min(1).describe('Connector type: github, document, website'),
954
+ input: z.record(z.string(), z.unknown()).describe('Input parameters for connector'),
955
+ }))
956
+ .min(1)
957
+ .describe('List of source synchronization requests'),
958
+ concurrency: z
959
+ .number()
960
+ .int()
961
+ .positive()
962
+ .max(10)
963
+ .optional()
964
+ .describe('Max concurrent workers (default: 3)'),
965
+ runDiscovery: z
966
+ .boolean()
967
+ .optional()
968
+ .describe('Whether to run discovery after synchronization (default: true)'),
969
+ }, async (args) => {
970
+ try {
971
+ checkScope('sync', options);
972
+ metricsService.recordToolCall('sync_sources');
973
+ if (!options.syncService) {
974
+ throw new Error('SyncService is not configured on this server instance');
975
+ }
976
+ const summary = await options.syncService.syncSources(args.sources, {
977
+ concurrency: args.concurrency ?? 3,
978
+ runDiscoveryAfterSync: args.runDiscovery ?? true,
979
+ });
980
+ return {
981
+ isError: summary.sourcesFailed === summary.sourcesRequested && summary.sourcesRequested > 0,
982
+ content: [
983
+ {
984
+ type: 'text',
985
+ text: JSON.stringify(summary, null, 2),
986
+ },
987
+ ],
988
+ };
989
+ }
990
+ catch (error) {
991
+ logger.error('Error during sync_sources execution', { error: String(error) });
992
+ const safe = toSafeError(error);
993
+ return {
994
+ isError: true,
995
+ content: [
996
+ {
997
+ type: 'text',
998
+ text: JSON.stringify({
999
+ error: safe.message,
1000
+ code: safe.code,
1001
+ }, null, 2),
1002
+ },
1003
+ ],
1004
+ };
1005
+ }
1006
+ });
1007
+ // 20. Tool: sync_document
1008
+ registerTool('sync_document', 'Synchronizes a local document file (Markdown, text, JSON, YAML, CSV), extracts claims with exact line provenance, and discovers contradictions.', {
1009
+ filePath: z.string().min(1).describe('Absolute or relative file path on disk'),
1010
+ sourceName: z.string().optional().describe('Optional friendly name for this source'),
1011
+ subject: z
1012
+ .string()
1013
+ .optional()
1014
+ .describe('Subject entity name for extracted claims (default: filename)'),
1015
+ scope: z.string().optional().describe('Scope of the document (default: file)'),
1016
+ environment: z
1017
+ .string()
1018
+ .optional()
1019
+ .describe('Environment context (e.g. production, development)'),
1020
+ sourceRole: z
1021
+ .string()
1022
+ .optional()
1023
+ .describe('Source role (e.g. deployment, configuration, documentation)'),
1024
+ runDiscovery: z
1025
+ .boolean()
1026
+ .optional()
1027
+ .describe('Whether to trigger automatic contradiction discovery (default: true)'),
1028
+ }, async (args) => {
1029
+ try {
1030
+ checkScope('sync', options);
1031
+ metricsService.recordToolCall('sync_document');
1032
+ if (!options.syncService) {
1033
+ throw new Error('SyncService is not configured on this server instance');
1034
+ }
1035
+ const summary = await options.syncService.syncSource('document', {
1036
+ filePath: args.filePath,
1037
+ sourceName: args.sourceName,
1038
+ subject: args.subject,
1039
+ scope: args.scope,
1040
+ environment: args.environment,
1041
+ sourceRole: args.sourceRole,
1042
+ }, {
1043
+ runDiscoveryAfterSync: args.runDiscovery ?? true,
1044
+ });
1045
+ return {
1046
+ isError: summary.status === 'failed',
1047
+ content: [
1048
+ {
1049
+ type: 'text',
1050
+ text: JSON.stringify(summary, null, 2),
1051
+ },
1052
+ ],
1053
+ };
1054
+ }
1055
+ catch (error) {
1056
+ logger.error('Error during sync_document execution', { error: String(error) });
1057
+ const safe = toSafeError(error);
1058
+ return {
1059
+ isError: true,
1060
+ content: [
1061
+ {
1062
+ type: 'text',
1063
+ text: JSON.stringify({
1064
+ error: safe.message,
1065
+ code: safe.code,
1066
+ filePath: args.filePath,
1067
+ }, null, 2),
1068
+ },
1069
+ ],
1070
+ };
1071
+ }
1072
+ });
1073
+ // 21. Tool: sync_website
1074
+ registerTool('sync_website', 'Fetches an explicit public web page with strict SSRF defenses, extracts factual claims, and discovers contradictions.', {
1075
+ url: z.string().url().describe('Public web page URL (http or https only)'),
1076
+ sourceName: z.string().optional().describe('Friendly name for the website'),
1077
+ runDiscovery: z
1078
+ .boolean()
1079
+ .optional()
1080
+ .describe('Whether to trigger automatic contradiction discovery (default: true)'),
1081
+ }, async (args) => {
1082
+ try {
1083
+ checkScope('sync', options);
1084
+ metricsService.recordToolCall('sync_website');
1085
+ if (!options.syncService) {
1086
+ throw new Error('SyncService is not configured on this server instance');
1087
+ }
1088
+ const summary = await options.syncService.syncSource('website', {
1089
+ url: args.url,
1090
+ sourceName: args.sourceName,
1091
+ }, {
1092
+ runDiscoveryAfterSync: args.runDiscovery ?? true,
1093
+ });
1094
+ return {
1095
+ isError: summary.status === 'failed',
1096
+ content: [
1097
+ {
1098
+ type: 'text',
1099
+ text: JSON.stringify(summary, null, 2),
1100
+ },
1101
+ ],
1102
+ };
1103
+ }
1104
+ catch (error) {
1105
+ logger.error('Error during sync_website execution', { error: String(error) });
1106
+ const safe = toSafeError(error);
1107
+ return {
1108
+ isError: true,
1109
+ content: [
1110
+ {
1111
+ type: 'text',
1112
+ text: JSON.stringify({
1113
+ error: safe.message,
1114
+ code: safe.code,
1115
+ url: args.url,
1116
+ }, null, 2),
1117
+ },
1118
+ ],
1119
+ };
1120
+ }
1121
+ });
1122
+ // --- MCP RESOURCES ---
1123
+ server.registerResource('health-metrics', 'health://metrics', {
1124
+ title: 'Health Metrics',
1125
+ description: 'System health and operational metrics',
1126
+ mimeType: 'application/json',
1127
+ }, async (uri) => {
1128
+ const snapshot = metricsService.getSnapshot();
1129
+ return {
1130
+ contents: [
1131
+ {
1132
+ uri: uri.href,
1133
+ mimeType: 'application/json',
1134
+ text: JSON.stringify(snapshot, null, 2),
1135
+ },
1136
+ ],
1137
+ };
1138
+ });
1139
+ server.registerResource('contradiction-details', new ResourceTemplate('contradiction://{id}', { list: undefined }), {
1140
+ title: 'Contradiction Details',
1141
+ description: 'Details and claims for a specific contradiction',
1142
+ mimeType: 'application/json',
1143
+ }, async (uri, { id }) => {
1144
+ if (!options.dbManager) {
1145
+ throw new Error('DatabaseManager is not configured');
1146
+ }
1147
+ const record = options.dbManager.getContradictionWithDetails(id);
1148
+ if (!record) {
1149
+ throw new NotFoundError('Contradiction', id);
1150
+ }
1151
+ return {
1152
+ contents: [
1153
+ {
1154
+ uri: uri.href,
1155
+ mimeType: 'application/json',
1156
+ text: JSON.stringify(record, null, 2),
1157
+ },
1158
+ ],
1159
+ };
1160
+ });
1161
+ server.registerResource('claim-details', new ResourceTemplate('claim://{id}', { list: undefined }), {
1162
+ title: 'Claim Details',
1163
+ description: 'Factual claim details and source provenance',
1164
+ mimeType: 'application/json',
1165
+ }, async (uri, { id }) => {
1166
+ if (!options.dbManager) {
1167
+ throw new Error('DatabaseManager is not configured');
1168
+ }
1169
+ const claim = options.dbManager.getClaimById(id);
1170
+ if (!claim) {
1171
+ throw new NotFoundError('Claim', id);
1172
+ }
1173
+ const source = options.dbManager.getSourceById(claim.sourceId);
1174
+ return {
1175
+ contents: [
1176
+ {
1177
+ uri: uri.href,
1178
+ mimeType: 'application/json',
1179
+ text: JSON.stringify({ claim, source }, null, 2),
1180
+ },
1181
+ ],
1182
+ };
1183
+ });
1184
+ server.registerResource('source-details', new ResourceTemplate('source://{id}', { list: undefined }), {
1185
+ title: 'Source Details',
1186
+ description: 'Ingested source record details',
1187
+ mimeType: 'application/json',
1188
+ }, async (uri, { id }) => {
1189
+ if (!options.dbManager) {
1190
+ throw new Error('DatabaseManager is not configured');
1191
+ }
1192
+ const source = options.dbManager.getSourceById(id);
1193
+ if (!source) {
1194
+ throw new NotFoundError('Source', id);
1195
+ }
1196
+ return {
1197
+ contents: [
1198
+ {
1199
+ uri: uri.href,
1200
+ mimeType: 'application/json',
1201
+ text: JSON.stringify(source, null, 2),
1202
+ },
1203
+ ],
1204
+ };
1205
+ });
1206
+ // --- MCP PROMPTS ---
1207
+ server.registerPrompt('investigate_contradiction', {
1208
+ description: 'Provides a structured investigation plan for an AI agent to resolve a specific contradiction.',
1209
+ argsSchema: z
1210
+ .object({
1211
+ contradictionId: z
1212
+ .string()
1213
+ .optional()
1214
+ .describe('ID of the contradiction to investigate (optional)'),
1215
+ })
1216
+ .optional()
1217
+ .default({}),
1218
+ }, async (args) => {
1219
+ const id = args?.contradictionId;
1220
+ const targetText = id ? `contradiction record '${id}'` : 'open contradiction records';
1221
+ const step1 = id
1222
+ ? `1. Call 'get_contradiction' with contradictionId='${id}'.`
1223
+ : `1. Call 'list_contradictions' with status='OPEN' to identify target contradiction IDs, then inspect them via 'get_contradiction'.`;
1224
+ return {
1225
+ messages: [
1226
+ {
1227
+ role: 'user',
1228
+ content: {
1229
+ type: 'text',
1230
+ text: `Please investigate ${targetText} using Contradiction MCP tools:\n${step1}\n2. Inspect the conflicting claims and their originating sources.\n3. Call 'explain_claim_relationship' to analyze contextual dimensions (environment, scope, roles, ranges).\n4. Call 'advise_resolution' to assess authority and freshness scoring.\n5. Recommend or execute 'resolve_contradiction' or 'dismiss_contradiction' with justified reasoning.`,
1231
+ },
1232
+ },
1233
+ ],
1234
+ };
1235
+ });
1236
+ server.registerPrompt('review_source_consistency', {
1237
+ description: 'Guides an agent through scanning and reviewing consistency for claims associated with an ingested source.',
1238
+ argsSchema: z
1239
+ .object({
1240
+ sourceName: z
1241
+ .string()
1242
+ .optional()
1243
+ .describe('Name or identifier of the source repository or document (optional)'),
1244
+ })
1245
+ .optional()
1246
+ .default({}),
1247
+ }, async (args) => {
1248
+ const source = args?.sourceName;
1249
+ const sourceTarget = source ? `for source '${source}'` : 'across all registered sources';
1250
+ return {
1251
+ messages: [
1252
+ {
1253
+ role: 'user',
1254
+ content: {
1255
+ type: 'text',
1256
+ text: `Review consistency ${sourceTarget}:\n1. Run 'list_connectors' to review registered data sources.\n2. Run 'scan_for_contradictions' to discover any newly introduced discrepancies.\n3. Query 'list_contradictions' filtered by status='OPEN'.\n4. For each high or critical contradiction, review the conflicting evidence snippets and recommend updates.`,
1257
+ },
1258
+ },
1259
+ ],
1260
+ };
1261
+ });
1262
+ return server;
1263
+ }
1264
+ //# sourceMappingURL=server.js.map