@bitsbound/mcp-server 1.0.5 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/orchestration/backautocrat/Bitsbound_Kings_McpServer_Backend_Orchestration_Backautocrat.d.ts +21 -5
- package/dist/orchestration/backautocrat/Bitsbound_Kings_McpServer_Backend_Orchestration_Backautocrat.d.ts.map +1 -1
- package/dist/orchestration/backautocrat/Bitsbound_Kings_McpServer_Backend_Orchestration_Backautocrat.js +118 -237
- package/dist/orchestration/backautocrat/Bitsbound_Kings_McpServer_Backend_Orchestration_Backautocrat.js.map +1 -1
- package/dist/server/Bitsbound_Kings_McpServer_Backend_Server.js +31 -0
- package/dist/server/Bitsbound_Kings_McpServer_Backend_Server.js.map +1 -1
- package/dist/types/Bitsbound_Kings_McpServer_Backend_Types.d.ts +318 -46
- package/dist/types/Bitsbound_Kings_McpServer_Backend_Types.d.ts.map +1 -1
- package/dist/types/Bitsbound_Kings_McpServer_Backend_Types.js +399 -8
- package/dist/types/Bitsbound_Kings_McpServer_Backend_Types.js.map +1 -1
- package/logger/Bitsbound_Kings_McpServer_Backend_Logger.ts +130 -0
- package/orchestration/backautocrat/Bitsbound_Kings_McpServer_Backend_Orchestration_Backautocrat.ts +715 -0
- package/package.json +12 -17
- package/server/Bitsbound_Kings_McpServer_Backend_Server.ts +1441 -0
- package/server.json +20 -0
- package/tsconfig.json +21 -0
- package/types/Bitsbound_Kings_McpServer_Backend_Types.node.test.mjs +14 -0
- package/types/Bitsbound_Kings_McpServer_Backend_Types.ts +1667 -0
package/orchestration/backautocrat/Bitsbound_Kings_McpServer_Backend_Orchestration_Backautocrat.ts
ADDED
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
////////////////////////////////////////
|
|
2
|
+
// # GOLDEN RULE!!!! HONOR ABOVE ALL!!!!
|
|
3
|
+
////////////////////////////////////////
|
|
4
|
+
// NO NEW FILES!!!!!!!!!
|
|
5
|
+
////////////////////////////////////////
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
|
|
9
|
+
*/
|
|
10
|
+
// ## REQUIREMENTS 'R'
|
|
11
|
+
/*
|
|
12
|
+
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§
|
|
18
|
+
*/
|
|
19
|
+
// ### DEFINITIONS, VALIDATIONS, AND TRANSFORMATIONS 'R_DVT'
|
|
20
|
+
/*
|
|
21
|
+
§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
/*
|
|
26
|
+
######################################################################################################################################################################################################
|
|
27
|
+
*/
|
|
28
|
+
// #### GLOBAL 'R_DVT_G' - Imports & Configuration
|
|
29
|
+
/*
|
|
30
|
+
######################################################################################################################################################################################################
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
34
|
+
import { basename } from 'path';
|
|
35
|
+
|
|
36
|
+
import type {
|
|
37
|
+
McpServerConfig,
|
|
38
|
+
AnalyzeContractInput,
|
|
39
|
+
AnalyzeContractOutput,
|
|
40
|
+
GetAnalysisStatusInput,
|
|
41
|
+
AnalysisStatusOutput,
|
|
42
|
+
AskSacInput,
|
|
43
|
+
SacResponseOutput,
|
|
44
|
+
GenerateRedlineInput,
|
|
45
|
+
RedlineOutput,
|
|
46
|
+
GenerateNegotiationEmailInput,
|
|
47
|
+
NegotiationEmailOutput,
|
|
48
|
+
ExtractClauseInput,
|
|
49
|
+
ClauseExtractionOutput,
|
|
50
|
+
ComparePlaybookInput,
|
|
51
|
+
PlaybookComparisonOutput,
|
|
52
|
+
BitsBoundApiResponse,
|
|
53
|
+
StartAnalysisResponse,
|
|
54
|
+
AnalysisProgressResponse,
|
|
55
|
+
// Instant Swarm - Parallel Section Redlining
|
|
56
|
+
InstantSwarmInput,
|
|
57
|
+
InstantSwarmOutput,
|
|
58
|
+
// Quick Tools
|
|
59
|
+
QuickScanInput,
|
|
60
|
+
QuickScanOutput,
|
|
61
|
+
AskClauseInput,
|
|
62
|
+
AskClauseOutput,
|
|
63
|
+
CheckDealbreakersInput,
|
|
64
|
+
CheckDealbreakersOutput,
|
|
65
|
+
// File Download
|
|
66
|
+
DownloadFileInput
|
|
67
|
+
} from '../../types/Bitsbound_Kings_McpServer_Backend_Types.js';
|
|
68
|
+
import { DEFAULT_API_URL } from '../../types/Bitsbound_Kings_McpServer_Backend_Types.js';
|
|
69
|
+
import { apiLogger } from '../../logger/Bitsbound_Kings_McpServer_Backend_Logger.js';
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
/*
|
|
73
|
+
❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅
|
|
74
|
+
*/
|
|
75
|
+
// ##### DEFINITIONS 'R_DVT_G_D' - Backautocrat Class
|
|
76
|
+
/*
|
|
77
|
+
❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅❅
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
81
|
+
// Backautocrat 'R_DVT_G_D_Backautocrat' - Orchestrates API calls to BitsBound SaaS backend
|
|
82
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
83
|
+
|
|
84
|
+
export class BitsBoundBackautocrat {
|
|
85
|
+
private readonly apiUrl: string;
|
|
86
|
+
private readonly apiKey: string;
|
|
87
|
+
|
|
88
|
+
constructor(config: McpServerConfig) {
|
|
89
|
+
this.apiUrl = config.BITSBOUND_API_URL || DEFAULT_API_URL;
|
|
90
|
+
this.apiKey = config.BITSBOUND_API_KEY;
|
|
91
|
+
|
|
92
|
+
if (!this.apiKey) {
|
|
93
|
+
throw new Error('BITSBOUND_API_KEY is required');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
98
|
+
// Private: HTTP Request Helper 'R_DVT_G_T_Request'
|
|
99
|
+
// ────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
private async request<T>(
|
|
102
|
+
method: 'GET' | 'POST',
|
|
103
|
+
endpoint: string,
|
|
104
|
+
body?: Record<string, unknown>
|
|
105
|
+
): Promise<BitsBoundApiResponse<T>> {
|
|
106
|
+
const url = `${this.apiUrl}${endpoint}`;
|
|
107
|
+
|
|
108
|
+
apiLogger.debug('Making API request', { method, endpoint });
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const response = await fetch(url, {
|
|
112
|
+
method,
|
|
113
|
+
headers: {
|
|
114
|
+
'Content-Type': 'application/json',
|
|
115
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
116
|
+
'X-MCP-Client': 'bitsbound-mcp-server/1.0.0'
|
|
117
|
+
},
|
|
118
|
+
body: body ? JSON.stringify(body) : undefined
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
const errorText = await response.text();
|
|
123
|
+
apiLogger.error('API request failed', {
|
|
124
|
+
status: response.status,
|
|
125
|
+
error: errorText
|
|
126
|
+
});
|
|
127
|
+
return {
|
|
128
|
+
success: false,
|
|
129
|
+
error: `API error ${response.status}: ${errorText}`
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const data = await response.json() as T;
|
|
134
|
+
apiLogger.debug('API request successful', { endpoint });
|
|
135
|
+
|
|
136
|
+
return { success: true, data };
|
|
137
|
+
} catch (error) {
|
|
138
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
139
|
+
apiLogger.error('API request exception', { error: errorMessage });
|
|
140
|
+
return {
|
|
141
|
+
success: false,
|
|
142
|
+
error: `Request failed: ${errorMessage}`
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
/*
|
|
149
|
+
######################################################################################################################################################################################################
|
|
150
|
+
*/
|
|
151
|
+
// #### LOCAL 'R_DVT_L' - Tool Handler Methods
|
|
152
|
+
/*
|
|
153
|
+
######################################################################################################################################################################################################
|
|
154
|
+
*/
|
|
155
|
+
|
|
156
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
157
|
+
// Tool: process_contract 'R_DVT_L_T_ProcessContract'
|
|
158
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
159
|
+
|
|
160
|
+
async analyzeContract(input: AnalyzeContractInput): Promise<AnalyzeContractOutput> {
|
|
161
|
+
let docxBase64: string;
|
|
162
|
+
let fileName: string;
|
|
163
|
+
|
|
164
|
+
// Handle filePath (RECOMMENDED) or docxBase64
|
|
165
|
+
if (input.filePath) {
|
|
166
|
+
// Read file from disk and base64 encode it
|
|
167
|
+
apiLogger.info('Reading contract from file path', { filePath: input.filePath });
|
|
168
|
+
try {
|
|
169
|
+
const fileBuffer = await readFile(input.filePath);
|
|
170
|
+
docxBase64 = fileBuffer.toString('base64');
|
|
171
|
+
fileName = input.fileName || basename(input.filePath);
|
|
172
|
+
apiLogger.info('File read successfully', { fileName, sizeBytes: fileBuffer.length });
|
|
173
|
+
} catch (error) {
|
|
174
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
175
|
+
apiLogger.error('Failed to read file', { filePath: input.filePath, error: errorMessage });
|
|
176
|
+
throw new Error(`Failed to read file at ${input.filePath}: ${errorMessage}`);
|
|
177
|
+
}
|
|
178
|
+
} else if (input.docxBase64) {
|
|
179
|
+
// Use provided base64
|
|
180
|
+
docxBase64 = input.docxBase64;
|
|
181
|
+
fileName = input.fileName || 'contract.docx';
|
|
182
|
+
} else {
|
|
183
|
+
throw new Error('Either filePath or docxBase64 is required. Use filePath for best results (e.g., "/Users/you/Documents/contract.docx")');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
apiLogger.info('Starting contract analysis', { fileName });
|
|
187
|
+
|
|
188
|
+
const response = await this.request<StartAnalysisResponse>(
|
|
189
|
+
'POST',
|
|
190
|
+
'/api/v1/mcp/analyze',
|
|
191
|
+
{
|
|
192
|
+
contract_content: docxBase64,
|
|
193
|
+
filename: fileName,
|
|
194
|
+
analysis_type: input.analysisDepth || 'standard',
|
|
195
|
+
perspective: input.perspective || 'customer'
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
if (!response.success || !response.data) {
|
|
200
|
+
throw new Error(response.error || 'Failed to start analysis');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
analysisId: response.data.analysisId,
|
|
205
|
+
status: 'queued',
|
|
206
|
+
estimatedTimeMinutes: response.data.estimatedMinutes,
|
|
207
|
+
initialRiskSummary: 'Analysis queued. Use get_analysis_status to check progress.'
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
212
|
+
// Tool: get_analysis_status 'R_DVT_L_T_GetStatus'
|
|
213
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
214
|
+
|
|
215
|
+
async getAnalysisStatus(input: GetAnalysisStatusInput): Promise<AnalysisStatusOutput> {
|
|
216
|
+
apiLogger.info('Checking analysis status', { analysisId: input.analysisId });
|
|
217
|
+
|
|
218
|
+
const response = await this.request<AnalysisProgressResponse>(
|
|
219
|
+
'GET',
|
|
220
|
+
`/api/v1/mcp/analysis/${input.analysisId}/status`
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
if (!response.success || !response.data) {
|
|
224
|
+
throw new Error(response.error || 'Failed to get analysis status');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const data = response.data;
|
|
228
|
+
return {
|
|
229
|
+
analysisId: data.analysisId,
|
|
230
|
+
status: data.status as AnalysisStatusOutput['status'],
|
|
231
|
+
progressPercent: data.progress,
|
|
232
|
+
currentPhase: data.phase,
|
|
233
|
+
phasesCompleted: data.phasesCompleted,
|
|
234
|
+
favorabilityScore: data.results?.favorabilityScore,
|
|
235
|
+
topRisks: data.results?.topRisks.map((r: { severity: string; section: string; risk: string }) => `[${r.severity.toUpperCase()}] ${r.section}: ${r.risk}`)
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
240
|
+
// Tool: ask_sac 'R_DVT_L_T_AskSac'
|
|
241
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
242
|
+
|
|
243
|
+
async askSac(input: AskSacInput): Promise<SacResponseOutput> {
|
|
244
|
+
apiLogger.info('Asking SAC', { analysisId: input.analysisId });
|
|
245
|
+
|
|
246
|
+
const response = await this.request<{
|
|
247
|
+
response: string;
|
|
248
|
+
citations?: Array<{ section: string; text: string; page?: number }>;
|
|
249
|
+
suggestions?: string[];
|
|
250
|
+
}>(
|
|
251
|
+
'POST',
|
|
252
|
+
'/api/v1/mcp/sac/chat',
|
|
253
|
+
{
|
|
254
|
+
analysisId: input.analysisId,
|
|
255
|
+
question: input.question,
|
|
256
|
+
includeClauseCitations: input.includeClauseCitations ?? true
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
if (!response.success || !response.data) {
|
|
261
|
+
throw new Error(response.error || 'Failed to get SAC response');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
response: response.data.response,
|
|
266
|
+
clauseCitations: response.data.citations?.map((c: { section: string; text: string; page?: number }) => ({
|
|
267
|
+
sectionName: c.section,
|
|
268
|
+
clauseText: c.text,
|
|
269
|
+
pageNumber: c.page
|
|
270
|
+
})),
|
|
271
|
+
followUpSuggestions: response.data.suggestions
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
276
|
+
// Tool: generate_redline 'R_DVT_L_T_GenerateRedline'
|
|
277
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
278
|
+
|
|
279
|
+
async generateRedline(input: GenerateRedlineInput): Promise<RedlineOutput> {
|
|
280
|
+
apiLogger.info('Generating redline', { analysisId: input.analysisId });
|
|
281
|
+
|
|
282
|
+
const response = await this.request<{
|
|
283
|
+
docxBase64: string;
|
|
284
|
+
downloadUrl: string;
|
|
285
|
+
changesCount: number;
|
|
286
|
+
summary: string;
|
|
287
|
+
}>(
|
|
288
|
+
'POST',
|
|
289
|
+
'/api/v1/mcp/redline/generate',
|
|
290
|
+
{
|
|
291
|
+
analysisId: input.analysisId,
|
|
292
|
+
aggressiveness: input.aggressiveness ?? 3,
|
|
293
|
+
includeComments: input.includeComments ?? true
|
|
294
|
+
}
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
if (!response.success || !response.data) {
|
|
298
|
+
throw new Error(response.error || 'Failed to generate redline');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
redlinedDocxBase64: response.data.docxBase64,
|
|
303
|
+
downloadUrl: response.data.downloadUrl,
|
|
304
|
+
changesCount: response.data.changesCount,
|
|
305
|
+
changesSummary: response.data.summary
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
310
|
+
// Tool: generate_negotiation_email 'R_DVT_L_T_GenerateEmail'
|
|
311
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
312
|
+
|
|
313
|
+
async generateNegotiationEmail(input: GenerateNegotiationEmailInput): Promise<NegotiationEmailOutput> {
|
|
314
|
+
apiLogger.info('Generating negotiation email', { analysisId: input.analysisId });
|
|
315
|
+
|
|
316
|
+
const response = await this.request<{
|
|
317
|
+
subject: string;
|
|
318
|
+
body: string;
|
|
319
|
+
keyPoints: string[];
|
|
320
|
+
}>(
|
|
321
|
+
'POST',
|
|
322
|
+
'/api/v1/mcp/email/generate',
|
|
323
|
+
{
|
|
324
|
+
analysisId: input.analysisId,
|
|
325
|
+
tone: input.tone ?? 'collaborative',
|
|
326
|
+
recipientRole: input.recipientRole
|
|
327
|
+
}
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
if (!response.success || !response.data) {
|
|
331
|
+
throw new Error(response.error || 'Failed to generate email');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return response.data;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
338
|
+
// Tool: extract_clause 'R_DVT_L_T_ExtractClause'
|
|
339
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
340
|
+
|
|
341
|
+
async extractClause(input: ExtractClauseInput): Promise<ClauseExtractionOutput> {
|
|
342
|
+
apiLogger.info('Extracting clause', {
|
|
343
|
+
analysisId: input.analysisId,
|
|
344
|
+
clauseType: input.clauseType
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
const response = await this.request<{
|
|
348
|
+
clauseType: string;
|
|
349
|
+
clauseText: string;
|
|
350
|
+
riskLevel: 'low' | 'medium' | 'high' | 'critical';
|
|
351
|
+
riskAnalysis: string;
|
|
352
|
+
suggestedImprovements: string[];
|
|
353
|
+
marketComparison?: string;
|
|
354
|
+
}>(
|
|
355
|
+
'GET',
|
|
356
|
+
`/api/v1/mcp/analysis/${input.analysisId}/clause/${input.clauseType}`
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
if (!response.success || !response.data) {
|
|
360
|
+
throw new Error(response.error || 'Failed to extract clause');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return response.data;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
367
|
+
// Tool: compare_playbook 'R_DVT_L_T_ComparePlaybook'
|
|
368
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
369
|
+
|
|
370
|
+
async comparePlaybook(input: ComparePlaybookInput): Promise<PlaybookComparisonOutput> {
|
|
371
|
+
apiLogger.info('Comparing against playbook', {
|
|
372
|
+
analysisId: input.analysisId,
|
|
373
|
+
playbookId: input.playbookId
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const response = await this.request<{
|
|
377
|
+
deviations: Array<{
|
|
378
|
+
section: string;
|
|
379
|
+
playbookRequirement: string;
|
|
380
|
+
actualClause: string;
|
|
381
|
+
severity: 'minor' | 'moderate' | 'major';
|
|
382
|
+
recommendation: string;
|
|
383
|
+
}>;
|
|
384
|
+
complianceScore: number;
|
|
385
|
+
requiredApprovals: string[];
|
|
386
|
+
}>(
|
|
387
|
+
'POST',
|
|
388
|
+
'/api/v1/mcp/playbook/compare',
|
|
389
|
+
{
|
|
390
|
+
analysisId: input.analysisId,
|
|
391
|
+
playbookId: input.playbookId
|
|
392
|
+
}
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
if (!response.success || !response.data) {
|
|
396
|
+
throw new Error(response.error || 'Failed to compare playbook');
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return response.data;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
403
|
+
// Resource: Get Analysis Results 'R_DVT_L_T_GetAnalysis'
|
|
404
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
405
|
+
|
|
406
|
+
async getAnalysisResults(analysisId: string): Promise<Record<string, unknown>> {
|
|
407
|
+
apiLogger.info('Fetching full analysis results', { analysisId });
|
|
408
|
+
|
|
409
|
+
const response = await this.request<Record<string, unknown>>(
|
|
410
|
+
'GET',
|
|
411
|
+
`/api/v1/mcp/analysis/${analysisId}/full`
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
if (!response.success || !response.data) {
|
|
415
|
+
throw new Error(response.error || 'Failed to fetch analysis');
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return response.data;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
422
|
+
// Resource: Get Playbook 'R_DVT_L_T_GetPlaybook'
|
|
423
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
424
|
+
|
|
425
|
+
async getPlaybook(playbookId: string): Promise<Record<string, unknown>> {
|
|
426
|
+
apiLogger.info('Fetching playbook', { playbookId });
|
|
427
|
+
|
|
428
|
+
const response = await this.request<Record<string, unknown>>(
|
|
429
|
+
'GET',
|
|
430
|
+
`/api/v1/mcp/playbook/${playbookId}`
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
if (!response.success || !response.data) {
|
|
434
|
+
throw new Error(response.error || 'Failed to fetch playbook');
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return response.data;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
/*
|
|
442
|
+
######################################################################################################################################################################################################
|
|
443
|
+
*/
|
|
444
|
+
// #### INSTANT SWARM 'R_DVT_L_InstantSwarm' - Parallel Section Redlining
|
|
445
|
+
/*
|
|
446
|
+
######################################################################################################################################################################################################
|
|
447
|
+
*/
|
|
448
|
+
|
|
449
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
450
|
+
// Tool: instant_swarm 'R_DVT_L_T_InstantSwarm' - Parallel redlining across ALL sections simultaneously
|
|
451
|
+
// Spawns N agents for N sections (dynamic - not fixed) for maximum parallelization
|
|
452
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
453
|
+
|
|
454
|
+
async instantSwarm(input: InstantSwarmInput): Promise<InstantSwarmOutput> {
|
|
455
|
+
apiLogger.info('Starting Instant Swarm - parallel section redlining', {
|
|
456
|
+
analysisId: input.analysisId,
|
|
457
|
+
aggressivenessLevel: input.aggressivenessLevel,
|
|
458
|
+
partyPosition: input.partyPosition,
|
|
459
|
+
targetSections: input.targetSections || 'all'
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// T: Call the existing /api/v1/sac/autopilot/swarm endpoint on the SaaS backend
|
|
463
|
+
// This endpoint spawns N parallel SAC sessions for N sections, each writing to isolated R2 branches
|
|
464
|
+
const response = await this.request<{
|
|
465
|
+
success: boolean;
|
|
466
|
+
totalSections: number;
|
|
467
|
+
completedSections: number;
|
|
468
|
+
failedSections: number;
|
|
469
|
+
mergedDocumentPath?: string;
|
|
470
|
+
mergedDocumentBase64?: string;
|
|
471
|
+
sectionResults: Array<{
|
|
472
|
+
sectionId: string;
|
|
473
|
+
sectionName: string;
|
|
474
|
+
success: boolean;
|
|
475
|
+
redlinesApplied: number;
|
|
476
|
+
commentsApplied: number;
|
|
477
|
+
keyChanges?: string[];
|
|
478
|
+
error?: string;
|
|
479
|
+
executionTimeMs: number;
|
|
480
|
+
}>;
|
|
481
|
+
totalRedlinesApplied: number;
|
|
482
|
+
totalCommentsApplied: number;
|
|
483
|
+
executionTimeMs: number;
|
|
484
|
+
tokenUsage?: {
|
|
485
|
+
inputTokens: number;
|
|
486
|
+
outputTokens: number;
|
|
487
|
+
};
|
|
488
|
+
}>(
|
|
489
|
+
'POST',
|
|
490
|
+
'/api/v1/mcp/instant/swarm', // MCP-specific endpoint that wraps /sac/autopilot/swarm
|
|
491
|
+
{
|
|
492
|
+
analysisId: input.analysisId,
|
|
493
|
+
aggressivenessLevel: input.aggressivenessLevel,
|
|
494
|
+
partyPosition: input.partyPosition,
|
|
495
|
+
ourPartyName: input.ourPartyName,
|
|
496
|
+
counterpartyName: input.counterpartyName,
|
|
497
|
+
targetSections: input.targetSections
|
|
498
|
+
}
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
if (!response.success || !response.data) {
|
|
502
|
+
throw new Error(response.error || 'Failed to execute Instant Swarm');
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const data = response.data;
|
|
506
|
+
|
|
507
|
+
return {
|
|
508
|
+
success: data.success,
|
|
509
|
+
totalSections: data.totalSections,
|
|
510
|
+
completedSections: data.completedSections,
|
|
511
|
+
failedSections: data.failedSections,
|
|
512
|
+
redlinedDocumentBase64: data.mergedDocumentBase64,
|
|
513
|
+
redlinedDocumentUrl: data.mergedDocumentPath,
|
|
514
|
+
sectionResults: data.sectionResults.map(sr => ({
|
|
515
|
+
sectionId: sr.sectionId,
|
|
516
|
+
sectionName: sr.sectionName,
|
|
517
|
+
success: sr.success,
|
|
518
|
+
redlinesApplied: sr.redlinesApplied,
|
|
519
|
+
commentsApplied: sr.commentsApplied,
|
|
520
|
+
keyChanges: sr.keyChanges,
|
|
521
|
+
error: sr.error,
|
|
522
|
+
executionTimeMs: sr.executionTimeMs
|
|
523
|
+
})),
|
|
524
|
+
totalRedlinesApplied: data.totalRedlinesApplied,
|
|
525
|
+
totalCommentsApplied: data.totalCommentsApplied,
|
|
526
|
+
executionTimeMs: data.executionTimeMs,
|
|
527
|
+
tokenUsage: data.tokenUsage
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
/*
|
|
533
|
+
######################################################################################################################################################################################################
|
|
534
|
+
*/
|
|
535
|
+
// #### QUICK TOOLS 'R_DVT_L_QuickTools' - Immediate Analysis
|
|
536
|
+
/*
|
|
537
|
+
######################################################################################################################################################################################################
|
|
538
|
+
*/
|
|
539
|
+
|
|
540
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
541
|
+
// Tool: quick_scan 'R_DVT_L_T_QuickScan' - Instant contract analysis (5-10 seconds)
|
|
542
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
543
|
+
|
|
544
|
+
async quickScan(input: QuickScanInput): Promise<QuickScanOutput> {
|
|
545
|
+
apiLogger.info('Starting quick scan', {
|
|
546
|
+
fileName: input.fileName,
|
|
547
|
+
perspective: input.perspective || 'customer'
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
const response = await this.request<QuickScanOutput>(
|
|
551
|
+
'POST',
|
|
552
|
+
'/api/v1/mcp/instant/quick-scan',
|
|
553
|
+
{
|
|
554
|
+
contractText: input.contractText,
|
|
555
|
+
fileName: input.fileName,
|
|
556
|
+
perspective: input.perspective || 'customer'
|
|
557
|
+
}
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
if (!response.success || !response.data) {
|
|
561
|
+
throw new Error(response.error || 'Failed to perform quick scan');
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return response.data;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
568
|
+
// Tool: ask_clause 'R_DVT_L_T_AskClause' - Instant clause Q&A (2-5 seconds)
|
|
569
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
570
|
+
|
|
571
|
+
async askClause(input: AskClauseInput): Promise<AskClauseOutput> {
|
|
572
|
+
apiLogger.info('Asking about clause', {
|
|
573
|
+
question: input.question.substring(0, 50) + '...',
|
|
574
|
+
clauseType: input.clauseType || 'any'
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
const response = await this.request<AskClauseOutput>(
|
|
578
|
+
'POST',
|
|
579
|
+
'/api/v1/mcp/instant/ask-clause',
|
|
580
|
+
{
|
|
581
|
+
contractText: input.contractText,
|
|
582
|
+
question: input.question,
|
|
583
|
+
clauseType: input.clauseType || 'any'
|
|
584
|
+
}
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
if (!response.success || !response.data) {
|
|
588
|
+
throw new Error(response.error || 'Failed to get clause answer');
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
return response.data;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
595
|
+
// Tool: check_dealbreakers 'R_DVT_L_T_CheckDealbreakers' - Instant playbook compliance (3-5 seconds)
|
|
596
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
597
|
+
|
|
598
|
+
async checkDealbreakers(input: CheckDealbreakersInput): Promise<CheckDealbreakersOutput> {
|
|
599
|
+
apiLogger.info('Checking dealbreakers', {
|
|
600
|
+
playbookId: input.playbookId || 'default'
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
const response = await this.request<CheckDealbreakersOutput>(
|
|
604
|
+
'POST',
|
|
605
|
+
'/api/v1/mcp/instant/check-dealbreakers',
|
|
606
|
+
{
|
|
607
|
+
contractText: input.contractText,
|
|
608
|
+
playbookId: input.playbookId
|
|
609
|
+
}
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
if (!response.success || !response.data) {
|
|
613
|
+
throw new Error(response.error || 'Failed to check dealbreakers');
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
return response.data;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
620
|
+
// Download File 'R_DVT_L_T_DownloadFile' - Download file from R2 via MCP endpoint
|
|
621
|
+
// ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* T_DOWNLOAD_FILE: Download a file from BitsBound R2 storage
|
|
625
|
+
*
|
|
626
|
+
* TIR{ADVT}O Context:
|
|
627
|
+
* - T: User requests to download a file (e.g., redlined DOCX)
|
|
628
|
+
* - I: downloadUrl from get_analysis_status deliverables
|
|
629
|
+
* - R{ADVT}:
|
|
630
|
+
* - D: MCP download endpoint URL pattern
|
|
631
|
+
* - V: URL must be a valid BitsBound download URL
|
|
632
|
+
* - T: Fetch file via HTTP, optionally save to disk
|
|
633
|
+
* - O: { success, base64Content, savedToPath?, fileName, fileSizeBytes }
|
|
634
|
+
*/
|
|
635
|
+
async downloadFile(input: DownloadFileInput): Promise<{
|
|
636
|
+
success: boolean;
|
|
637
|
+
base64Content?: string;
|
|
638
|
+
savedToPath?: string;
|
|
639
|
+
fileName?: string;
|
|
640
|
+
fileSizeBytes?: number;
|
|
641
|
+
error?: string;
|
|
642
|
+
}> {
|
|
643
|
+
const { downloadUrl, saveToPath } = input;
|
|
644
|
+
|
|
645
|
+
apiLogger.info('Downloading file', { downloadUrl, saveToPath });
|
|
646
|
+
|
|
647
|
+
try {
|
|
648
|
+
// The download URL is a full URL - just fetch it with auth header
|
|
649
|
+
const response = await fetch(downloadUrl, {
|
|
650
|
+
method: 'GET',
|
|
651
|
+
headers: {
|
|
652
|
+
'X-API-Key': this.apiKey
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
if (!response.ok) {
|
|
657
|
+
const errorText = await response.text();
|
|
658
|
+
throw new Error(`Download failed: ${response.status} ${response.statusText} - ${errorText}`);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Get the file as ArrayBuffer
|
|
662
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
663
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
664
|
+
|
|
665
|
+
// Extract filename from Content-Disposition header or URL
|
|
666
|
+
const contentDisposition = response.headers.get('Content-Disposition');
|
|
667
|
+
let fileName = 'document.docx';
|
|
668
|
+
if (contentDisposition) {
|
|
669
|
+
const match = contentDisposition.match(/filename="?([^"]+)"?/);
|
|
670
|
+
if (match) {
|
|
671
|
+
fileName = match[1];
|
|
672
|
+
}
|
|
673
|
+
} else {
|
|
674
|
+
// Try to extract from URL
|
|
675
|
+
const urlParts = downloadUrl.split('/');
|
|
676
|
+
const lastPart = urlParts[urlParts.length - 1];
|
|
677
|
+
if (lastPart && !lastPart.includes('?')) {
|
|
678
|
+
fileName = decodeURIComponent(lastPart);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// If saveToPath is provided, write to disk
|
|
683
|
+
if (saveToPath) {
|
|
684
|
+
await writeFile(saveToPath, buffer);
|
|
685
|
+
apiLogger.info('File saved to disk', { path: saveToPath, size: buffer.length });
|
|
686
|
+
|
|
687
|
+
return {
|
|
688
|
+
success: true,
|
|
689
|
+
savedToPath: saveToPath,
|
|
690
|
+
fileName,
|
|
691
|
+
fileSizeBytes: buffer.length
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// Otherwise, return as base64
|
|
696
|
+
const base64Content = buffer.toString('base64');
|
|
697
|
+
apiLogger.info('File downloaded as base64', { fileName, size: buffer.length });
|
|
698
|
+
|
|
699
|
+
return {
|
|
700
|
+
success: true,
|
|
701
|
+
base64Content,
|
|
702
|
+
fileName,
|
|
703
|
+
fileSizeBytes: buffer.length
|
|
704
|
+
};
|
|
705
|
+
} catch (error) {
|
|
706
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
707
|
+
apiLogger.error('Download failed', { error: errorMessage });
|
|
708
|
+
|
|
709
|
+
return {
|
|
710
|
+
success: false,
|
|
711
|
+
error: errorMessage
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|