@pcircle/footprint 1.1.1 → 1.2.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 +165 -12
- package/SKILL.md +72 -15
- package/dist/src/analyzers/content-analyzer.d.ts +20 -0
- package/dist/src/analyzers/content-analyzer.d.ts.map +1 -0
- package/dist/src/analyzers/content-analyzer.js +169 -0
- package/dist/src/analyzers/content-analyzer.js.map +1 -0
- package/dist/src/index.d.ts +38 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +243 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lib/crypto/decrypt.d.ts.map +1 -0
- package/dist/{lib → src/lib}/crypto/decrypt.js +4 -0
- package/dist/src/lib/crypto/decrypt.js.map +1 -0
- package/dist/src/lib/crypto/encrypt.d.ts.map +1 -0
- package/dist/src/lib/crypto/encrypt.js.map +1 -0
- package/dist/{lib → src/lib}/crypto/index.d.ts +1 -1
- package/dist/src/lib/crypto/index.d.ts.map +1 -0
- package/dist/{lib → src/lib}/crypto/index.js +1 -1
- package/dist/src/lib/crypto/index.js.map +1 -0
- package/dist/{lib → src/lib}/crypto/key-derivation.d.ts +17 -3
- package/dist/src/lib/crypto/key-derivation.d.ts.map +1 -0
- package/dist/{lib → src/lib}/crypto/key-derivation.js +44 -3
- package/dist/src/lib/crypto/key-derivation.js.map +1 -0
- package/dist/src/lib/crypto/types.d.ts.map +1 -0
- package/dist/src/lib/crypto/types.js.map +1 -0
- package/dist/src/lib/errors/base-error.d.ts +15 -0
- package/dist/src/lib/errors/base-error.d.ts.map +1 -0
- package/dist/src/lib/errors/base-error.js +34 -0
- package/dist/src/lib/errors/base-error.js.map +1 -0
- package/dist/src/lib/errors/crypto-error.d.ts +29 -0
- package/dist/src/lib/errors/crypto-error.d.ts.map +1 -0
- package/dist/src/lib/errors/crypto-error.js +43 -0
- package/dist/src/lib/errors/crypto-error.js.map +1 -0
- package/dist/src/lib/errors/index.d.ts +26 -0
- package/dist/src/lib/errors/index.d.ts.map +1 -0
- package/dist/src/lib/errors/index.js +26 -0
- package/dist/src/lib/errors/index.js.map +1 -0
- package/dist/src/lib/errors/storage-error.d.ts +25 -0
- package/dist/src/lib/errors/storage-error.d.ts.map +1 -0
- package/dist/src/lib/errors/storage-error.js +38 -0
- package/dist/src/lib/errors/storage-error.js.map +1 -0
- package/dist/src/lib/errors/validation-error.d.ts +21 -0
- package/dist/src/lib/errors/validation-error.d.ts.map +1 -0
- package/dist/src/lib/errors/validation-error.js +29 -0
- package/dist/src/lib/errors/validation-error.js.map +1 -0
- package/dist/{lib → src/lib}/storage/database.d.ts +14 -2
- package/dist/src/lib/storage/database.d.ts.map +1 -0
- package/dist/{lib → src/lib}/storage/database.js +65 -29
- package/dist/src/lib/storage/database.js.map +1 -0
- package/dist/src/lib/storage/export.d.ts.map +1 -0
- package/dist/{lib → src/lib}/storage/export.js +16 -1
- package/dist/src/lib/storage/export.js.map +1 -0
- package/dist/{lib → src/lib}/storage/git.d.ts +1 -1
- package/dist/src/lib/storage/git.d.ts.map +1 -0
- package/dist/{lib → src/lib}/storage/git.js +5 -2
- package/dist/src/lib/storage/git.js.map +1 -0
- package/dist/{lib → src/lib}/storage/index.d.ts +1 -0
- package/dist/src/lib/storage/index.d.ts.map +1 -0
- package/dist/{lib → src/lib}/storage/index.js +1 -0
- package/dist/src/lib/storage/index.js.map +1 -0
- package/dist/src/lib/storage/salt-storage.d.ts +25 -0
- package/dist/src/lib/storage/salt-storage.d.ts.map +1 -0
- package/dist/src/lib/storage/salt-storage.js +66 -0
- package/dist/src/lib/storage/salt-storage.js.map +1 -0
- package/dist/src/lib/storage/schema.d.ts.map +1 -0
- package/dist/{lib → src/lib}/storage/schema.js +22 -1
- package/dist/src/lib/storage/schema.js.map +1 -0
- package/dist/src/lib/storage/types.d.ts.map +1 -0
- package/dist/src/lib/storage/types.js.map +1 -0
- package/dist/src/lib/tool-response.d.ts +84 -0
- package/dist/src/lib/tool-response.d.ts.map +1 -0
- package/dist/src/lib/tool-response.js +91 -0
- package/dist/src/lib/tool-response.js.map +1 -0
- package/dist/src/lib/tool-wrapper.d.ts +45 -0
- package/dist/src/lib/tool-wrapper.d.ts.map +1 -0
- package/dist/src/lib/tool-wrapper.js +73 -0
- package/dist/src/lib/tool-wrapper.js.map +1 -0
- package/dist/{test-helpers.d.ts → src/test-helpers.d.ts} +4 -4
- package/dist/src/test-helpers.d.ts.map +1 -0
- package/dist/{test-helpers.js → src/test-helpers.js} +2 -2
- package/dist/src/test-helpers.js.map +1 -0
- package/dist/src/tools/capture-footprint.d.ts +29 -0
- package/dist/src/tools/capture-footprint.d.ts.map +1 -0
- package/dist/src/tools/capture-footprint.js +94 -0
- package/dist/src/tools/capture-footprint.js.map +1 -0
- package/dist/src/tools/delete-footprints.d.ts +22 -0
- package/dist/src/tools/delete-footprints.d.ts.map +1 -0
- package/dist/src/tools/delete-footprints.js +33 -0
- package/dist/src/tools/delete-footprints.js.map +1 -0
- package/dist/src/tools/export-footprints.d.ts +33 -0
- package/dist/src/tools/export-footprints.d.ts.map +1 -0
- package/dist/src/tools/export-footprints.js +50 -0
- package/dist/src/tools/export-footprints.js.map +1 -0
- package/dist/src/tools/get-footprint.d.ts +51 -0
- package/dist/src/tools/get-footprint.d.ts.map +1 -0
- package/dist/src/tools/get-footprint.js +66 -0
- package/dist/src/tools/get-footprint.js.map +1 -0
- package/dist/src/tools/get-tag-stats.d.ts +30 -0
- package/dist/src/tools/get-tag-stats.d.ts.map +1 -0
- package/dist/src/tools/get-tag-stats.js +33 -0
- package/dist/src/tools/get-tag-stats.js.map +1 -0
- package/dist/src/tools/index.d.ts +16 -0
- package/dist/src/tools/index.d.ts.map +1 -0
- package/dist/src/tools/index.js +16 -0
- package/dist/src/tools/index.js.map +1 -0
- package/dist/src/tools/list-footprints.d.ts +57 -0
- package/dist/src/tools/list-footprints.d.ts.map +1 -0
- package/dist/src/tools/list-footprints.js +57 -0
- package/dist/src/tools/list-footprints.js.map +1 -0
- package/dist/src/tools/remove-tag.d.ts +22 -0
- package/dist/src/tools/remove-tag.d.ts.map +1 -0
- package/dist/src/tools/remove-tag.js +30 -0
- package/dist/src/tools/remove-tag.js.map +1 -0
- package/dist/src/tools/rename-tag.d.ts +24 -0
- package/dist/src/tools/rename-tag.d.ts.map +1 -0
- package/dist/src/tools/rename-tag.js +34 -0
- package/dist/src/tools/rename-tag.js.map +1 -0
- package/dist/src/tools/search-footprints.d.ts +60 -0
- package/dist/src/tools/search-footprints.d.ts.map +1 -0
- package/dist/src/tools/search-footprints.js +78 -0
- package/dist/src/tools/search-footprints.js.map +1 -0
- package/dist/src/tools/suggest-capture.d.ts +21 -0
- package/dist/src/tools/suggest-capture.d.ts.map +1 -0
- package/dist/src/tools/suggest-capture.js +37 -0
- package/dist/src/tools/suggest-capture.js.map +1 -0
- package/dist/src/tools/verify-footprint.d.ts +104 -0
- package/dist/src/tools/verify-footprint.d.ts.map +1 -0
- package/dist/src/tools/verify-footprint.js +102 -0
- package/dist/src/tools/verify-footprint.js.map +1 -0
- package/dist/{types.d.ts → src/types.d.ts} +1 -1
- package/dist/src/types.d.ts.map +1 -0
- package/dist/{lib/storage → src}/types.js.map +1 -1
- package/dist/src/ui/register.d.ts.map +1 -0
- package/dist/src/ui/register.js +94 -0
- package/dist/src/ui/register.js.map +1 -0
- package/dist/tests/error-handling.test.d.ts +2 -0
- package/dist/tests/error-handling.test.d.ts.map +1 -0
- package/dist/tests/error-handling.test.js +114 -0
- package/dist/tests/error-handling.test.js.map +1 -0
- package/dist/tests/fixtures.d.ts +87 -0
- package/dist/tests/fixtures.d.ts.map +1 -0
- package/dist/tests/fixtures.js +130 -0
- package/dist/tests/fixtures.js.map +1 -0
- package/dist/tests/integration.test.d.ts +2 -0
- package/dist/tests/integration.test.d.ts.map +1 -0
- package/dist/tests/integration.test.js +115 -0
- package/dist/tests/integration.test.js.map +1 -0
- package/dist/tests/resources.test.d.ts +2 -0
- package/dist/tests/resources.test.d.ts.map +1 -0
- package/dist/tests/resources.test.js +73 -0
- package/dist/tests/resources.test.js.map +1 -0
- package/dist/tests/setup.d.ts +8 -0
- package/dist/tests/setup.d.ts.map +1 -0
- package/dist/tests/setup.js +8 -0
- package/dist/tests/setup.js.map +1 -0
- package/dist/tests/tools.test.d.ts +2 -0
- package/dist/tests/tools.test.d.ts.map +1 -0
- package/dist/tests/tools.test.js +224 -0
- package/dist/tests/tools.test.js.map +1 -0
- package/dist/ui/dashboard.html +1 -1
- package/dist/ui/detail.html +1 -1
- package/dist/ui/export.html +1 -1
- package/dist/ui-tmp/ui/export.html +1 -1
- package/package.json +8 -8
- package/dist/index.d.ts +0 -19
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -694
- package/dist/index.js.map +0 -1
- package/dist/lib/crypto/decrypt.d.ts.map +0 -1
- package/dist/lib/crypto/decrypt.js.map +0 -1
- package/dist/lib/crypto/encrypt.d.ts.map +0 -1
- package/dist/lib/crypto/encrypt.js.map +0 -1
- package/dist/lib/crypto/index.d.ts.map +0 -1
- package/dist/lib/crypto/index.js.map +0 -1
- package/dist/lib/crypto/key-derivation.d.ts.map +0 -1
- package/dist/lib/crypto/key-derivation.js.map +0 -1
- package/dist/lib/crypto/types.d.ts.map +0 -1
- package/dist/lib/crypto/types.js.map +0 -1
- package/dist/lib/storage/database.d.ts.map +0 -1
- package/dist/lib/storage/database.js.map +0 -1
- package/dist/lib/storage/export.d.ts.map +0 -1
- package/dist/lib/storage/export.js.map +0 -1
- package/dist/lib/storage/git.d.ts.map +0 -1
- package/dist/lib/storage/git.js.map +0 -1
- package/dist/lib/storage/index.d.ts.map +0 -1
- package/dist/lib/storage/index.js.map +0 -1
- package/dist/lib/storage/schema.d.ts.map +0 -1
- package/dist/lib/storage/schema.js.map +0 -1
- package/dist/lib/storage/types.d.ts.map +0 -1
- package/dist/test-helpers.d.ts.map +0 -1
- package/dist/test-helpers.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/ui/register.d.ts.map +0 -1
- package/dist/ui/register.js +0 -154
- package/dist/ui/register.js.map +0 -1
- /package/dist/{lib → src/lib}/crypto/decrypt.d.ts +0 -0
- /package/dist/{lib → src/lib}/crypto/encrypt.d.ts +0 -0
- /package/dist/{lib → src/lib}/crypto/encrypt.js +0 -0
- /package/dist/{lib → src/lib}/crypto/types.d.ts +0 -0
- /package/dist/{lib → src/lib}/crypto/types.js +0 -0
- /package/dist/{lib → src/lib}/storage/export.d.ts +0 -0
- /package/dist/{lib → src/lib}/storage/schema.d.ts +0 -0
- /package/dist/{lib → src/lib}/storage/types.d.ts +0 -0
- /package/dist/{lib → src/lib}/storage/types.js +0 -0
- /package/dist/{types.js → src/types.js} +0 -0
- /package/dist/{ui → src/ui}/register.d.ts +0 -0
package/dist/index.js
DELETED
|
@@ -1,694 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
-
import { EvidenceDatabase, getCurrentCommit } from './lib/storage/index.js';
|
|
5
|
-
import { deriveKey, encrypt, decrypt } from './lib/crypto/index.js';
|
|
6
|
-
import { registerUIResources } from './ui/register.js';
|
|
7
|
-
import * as z from 'zod';
|
|
8
|
-
import * as crypto from 'crypto';
|
|
9
|
-
function getErrorMessage(error) {
|
|
10
|
-
return error instanceof Error ? error.message : 'Unknown error';
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* EvidenceMCP Server - Captures LLM conversations as encrypted footprint
|
|
14
|
-
* with Git timestamps and export capabilities.
|
|
15
|
-
*/
|
|
16
|
-
export class EvidenceMCPServer {
|
|
17
|
-
server;
|
|
18
|
-
config;
|
|
19
|
-
db;
|
|
20
|
-
derivedKey = null;
|
|
21
|
-
constructor(config) {
|
|
22
|
-
this.config = config;
|
|
23
|
-
try {
|
|
24
|
-
this.db = new EvidenceDatabase(config.dbPath);
|
|
25
|
-
}
|
|
26
|
-
catch (error) {
|
|
27
|
-
throw new Error(`Failed to initialize database: ${getErrorMessage(error)}`);
|
|
28
|
-
}
|
|
29
|
-
this.server = new McpServer({
|
|
30
|
-
name: config.name || 'traceguard-mcp',
|
|
31
|
-
version: config.version || '0.1.0'
|
|
32
|
-
});
|
|
33
|
-
// Register UI resources for MCP Apps
|
|
34
|
-
registerUIResources(this.server);
|
|
35
|
-
this.registerTools();
|
|
36
|
-
this.registerResources();
|
|
37
|
-
}
|
|
38
|
-
async getDerivedKey() {
|
|
39
|
-
if (!this.derivedKey) {
|
|
40
|
-
const result = await deriveKey(this.config.password);
|
|
41
|
-
this.derivedKey = result.key;
|
|
42
|
-
}
|
|
43
|
-
return this.derivedKey;
|
|
44
|
-
}
|
|
45
|
-
registerTools() {
|
|
46
|
-
this.server.registerTool('capture-footprint', {
|
|
47
|
-
title: 'Capture Footprint',
|
|
48
|
-
description: 'Capture and encrypt LLM conversation as footprint',
|
|
49
|
-
inputSchema: {
|
|
50
|
-
conversationId: z.string().describe('Conversation ID'),
|
|
51
|
-
llmProvider: z.string().describe('LLM provider name (e.g., Claude Sonnet 4.5)'),
|
|
52
|
-
content: z.string().describe('Conversation content (messages, prompts, responses)'),
|
|
53
|
-
messageCount: z.number().int().positive().describe('Number of messages'),
|
|
54
|
-
tags: z.string().optional().describe('Optional tags (comma-separated)')
|
|
55
|
-
},
|
|
56
|
-
outputSchema: {
|
|
57
|
-
id: z.string(),
|
|
58
|
-
timestamp: z.string(),
|
|
59
|
-
gitCommitHash: z.string().nullable(),
|
|
60
|
-
success: z.boolean()
|
|
61
|
-
}
|
|
62
|
-
}, async (params) => {
|
|
63
|
-
try {
|
|
64
|
-
if (!params.content || params.content.trim().length === 0) {
|
|
65
|
-
throw new Error('Content cannot be empty');
|
|
66
|
-
}
|
|
67
|
-
if (params.messageCount <= 0) {
|
|
68
|
-
throw new Error('Message count must be positive');
|
|
69
|
-
}
|
|
70
|
-
const key = await this.getDerivedKey();
|
|
71
|
-
const encrypted = await encrypt(params.content, key);
|
|
72
|
-
const gitInfo = await getCurrentCommit();
|
|
73
|
-
const contentHash = crypto.createHash('sha256').update(params.content).digest('hex');
|
|
74
|
-
const id = this.db.create({
|
|
75
|
-
timestamp: new Date().toISOString(),
|
|
76
|
-
conversationId: params.conversationId,
|
|
77
|
-
llmProvider: params.llmProvider,
|
|
78
|
-
encryptedContent: encrypted.ciphertext,
|
|
79
|
-
nonce: encrypted.nonce,
|
|
80
|
-
contentHash,
|
|
81
|
-
messageCount: params.messageCount,
|
|
82
|
-
gitCommitHash: gitInfo?.commitHash || null,
|
|
83
|
-
gitTimestamp: gitInfo?.timestamp || null,
|
|
84
|
-
tags: params.tags || null
|
|
85
|
-
});
|
|
86
|
-
return {
|
|
87
|
-
content: [{ type: 'text', text: `✅ Evidence captured successfully\n- ID: ${id}\n- Timestamp: ${new Date().toISOString()}\n- Git Commit: ${gitInfo?.commitHash || 'N/A'}\n- Message Count: ${params.messageCount}` }],
|
|
88
|
-
structuredContent: {
|
|
89
|
-
id,
|
|
90
|
-
timestamp: new Date().toISOString(),
|
|
91
|
-
gitCommitHash: gitInfo?.commitHash || null,
|
|
92
|
-
success: true
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
catch (error) {
|
|
97
|
-
throw new Error(`[Tool: capture-footprint] ${getErrorMessage(error)}. Suggested action: Check content is not empty and messageCount is positive.`);
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
this.server.registerTool('list-footprints', {
|
|
101
|
-
title: 'List Footprints',
|
|
102
|
-
description: 'List all captured footprint with pagination',
|
|
103
|
-
inputSchema: {
|
|
104
|
-
limit: z.number().int().positive().optional().describe('Maximum results'),
|
|
105
|
-
offset: z.number().int().min(0).optional().describe('Pagination offset')
|
|
106
|
-
},
|
|
107
|
-
outputSchema: {
|
|
108
|
-
evidences: z.array(z.object({
|
|
109
|
-
id: z.string(),
|
|
110
|
-
timestamp: z.string(),
|
|
111
|
-
conversationId: z.string(),
|
|
112
|
-
llmProvider: z.string(),
|
|
113
|
-
messageCount: z.number(),
|
|
114
|
-
tags: z.string().nullable()
|
|
115
|
-
})),
|
|
116
|
-
total: z.number()
|
|
117
|
-
},
|
|
118
|
-
_meta: {
|
|
119
|
-
ui: {
|
|
120
|
-
resourceUri: "ui://footprint/dashboard.html"
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}, async (params) => {
|
|
124
|
-
try {
|
|
125
|
-
if (params.limit !== undefined && params.limit <= 0) {
|
|
126
|
-
throw new Error('Limit must be positive');
|
|
127
|
-
}
|
|
128
|
-
if (params.offset !== undefined && params.offset < 0) {
|
|
129
|
-
throw new Error('Offset cannot be negative');
|
|
130
|
-
}
|
|
131
|
-
const evidences = this.db.list({ limit: params.limit, offset: params.offset });
|
|
132
|
-
const mappedEvidences = evidences.map(e => ({
|
|
133
|
-
id: e.id,
|
|
134
|
-
timestamp: e.timestamp,
|
|
135
|
-
conversationId: e.conversationId,
|
|
136
|
-
llmProvider: e.llmProvider,
|
|
137
|
-
messageCount: e.messageCount,
|
|
138
|
-
tags: e.tags
|
|
139
|
-
}));
|
|
140
|
-
return {
|
|
141
|
-
content: [{ type: 'text', text: `✅ Footprint list retrieved successfully\n- Count: ${evidences.length} footprint(s)\n- Limit: ${params.limit || 'No limit'}\n- Offset: ${params.offset || 0}` }],
|
|
142
|
-
structuredContent: { evidences: mappedEvidences, total: evidences.length }
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
catch (error) {
|
|
146
|
-
throw new Error(`[Tool: list-footprints] ${getErrorMessage(error)}. Suggested action: Ensure limit is positive and offset is non-negative.`);
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
this.server.registerTool('export-footprints', {
|
|
150
|
-
title: 'Export Footprints',
|
|
151
|
-
description: 'Export footprints to encrypted ZIP archive',
|
|
152
|
-
inputSchema: {
|
|
153
|
-
evidenceIds: z.array(z.string()).optional().describe('Specific IDs (empty = all)'),
|
|
154
|
-
includeGitInfo: z.boolean().optional().describe('Include Git timestamps')
|
|
155
|
-
},
|
|
156
|
-
outputSchema: {
|
|
157
|
-
filename: z.string(),
|
|
158
|
-
checksum: z.string(),
|
|
159
|
-
evidenceCount: z.number(),
|
|
160
|
-
success: z.boolean()
|
|
161
|
-
},
|
|
162
|
-
_meta: {
|
|
163
|
-
ui: {
|
|
164
|
-
resourceUri: "ui://footprint/export.html"
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}, async (params) => {
|
|
168
|
-
try {
|
|
169
|
-
const { exportEvidences } = await import('./lib/storage/index.js');
|
|
170
|
-
const fs = await import('fs');
|
|
171
|
-
const result = await exportEvidences(this.db, {
|
|
172
|
-
evidenceIds: params.evidenceIds,
|
|
173
|
-
includeGitInfo: params.includeGitInfo ?? false
|
|
174
|
-
});
|
|
175
|
-
fs.writeFileSync(result.filename, result.zipData);
|
|
176
|
-
return {
|
|
177
|
-
content: [{ type: 'text', text: `✅ Export completed successfully\n- Footprint Count: ${result.evidenceCount}\n- Filename: ${result.filename}\n- Checksum: ${result.checksum}\n- Git Info: ${params.includeGitInfo ? 'Included' : 'Excluded'}` }],
|
|
178
|
-
structuredContent: {
|
|
179
|
-
filename: result.filename,
|
|
180
|
-
checksum: result.checksum,
|
|
181
|
-
evidenceCount: result.evidenceCount,
|
|
182
|
-
success: true
|
|
183
|
-
}
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
catch (error) {
|
|
187
|
-
throw new Error(`[Tool: export-footprints] ${getErrorMessage(error)}. Suggested action: Check evidenceIds exist and filesystem has write permissions.`);
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
this.server.registerTool('get-footprint', {
|
|
191
|
-
title: 'Get Footprint',
|
|
192
|
-
description: 'Retrieve and decrypt specific footprint by ID',
|
|
193
|
-
inputSchema: {
|
|
194
|
-
id: z.string().describe('Footprint ID')
|
|
195
|
-
},
|
|
196
|
-
outputSchema: {
|
|
197
|
-
id: z.string(),
|
|
198
|
-
timestamp: z.string(),
|
|
199
|
-
conversationId: z.string(),
|
|
200
|
-
llmProvider: z.string(),
|
|
201
|
-
content: z.string(),
|
|
202
|
-
messageCount: z.number(),
|
|
203
|
-
gitInfo: z.object({
|
|
204
|
-
commitHash: z.string(),
|
|
205
|
-
timestamp: z.string()
|
|
206
|
-
}).nullable(),
|
|
207
|
-
tags: z.string().nullable()
|
|
208
|
-
},
|
|
209
|
-
_meta: {
|
|
210
|
-
ui: {
|
|
211
|
-
resourceUri: "ui://footprint/detail.html"
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}, async (params) => {
|
|
215
|
-
try {
|
|
216
|
-
const evidence = this.db.findById(params.id);
|
|
217
|
-
if (!evidence) {
|
|
218
|
-
throw new Error(`Evidence not found: ${params.id}`);
|
|
219
|
-
}
|
|
220
|
-
const key = await this.getDerivedKey();
|
|
221
|
-
const decrypted = decrypt(evidence.encryptedContent, evidence.nonce, key);
|
|
222
|
-
const gitInfo = evidence.gitCommitHash
|
|
223
|
-
? { commitHash: evidence.gitCommitHash, timestamp: evidence.gitTimestamp }
|
|
224
|
-
: null;
|
|
225
|
-
return {
|
|
226
|
-
content: [{ type: 'text', text: `✅ Evidence retrieved successfully\n- ID: ${evidence.id}\n- Timestamp: ${evidence.timestamp}\n- Provider: ${evidence.llmProvider}\n- Message Count: ${evidence.messageCount}\n- Content Preview: ${decrypted.substring(0, 100)}...` }],
|
|
227
|
-
structuredContent: {
|
|
228
|
-
id: evidence.id,
|
|
229
|
-
timestamp: evidence.timestamp,
|
|
230
|
-
conversationId: evidence.conversationId,
|
|
231
|
-
llmProvider: evidence.llmProvider,
|
|
232
|
-
content: decrypted,
|
|
233
|
-
messageCount: evidence.messageCount,
|
|
234
|
-
gitInfo,
|
|
235
|
-
tags: evidence.tags
|
|
236
|
-
}
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
catch (error) {
|
|
240
|
-
throw new Error(`[Tool: get-footprint] ${getErrorMessage(error)}. Suggested action: Verify the evidence ID exists and password is correct.`);
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
|
-
this.server.registerTool('search-footprints', {
|
|
244
|
-
title: 'Search Footprints',
|
|
245
|
-
description: 'Search and filter footprints by query, tags, or date range',
|
|
246
|
-
inputSchema: {
|
|
247
|
-
query: z.string().optional().describe('Search text (matches conversationId, tags)'),
|
|
248
|
-
tags: z.array(z.string()).optional().describe('Filter by tags'),
|
|
249
|
-
dateFrom: z.string().optional().describe('Start date (ISO format)'),
|
|
250
|
-
dateTo: z.string().optional().describe('End date (ISO format)'),
|
|
251
|
-
limit: z.number().int().positive().optional().describe('Maximum results'),
|
|
252
|
-
offset: z.number().int().min(0).optional().describe('Pagination offset')
|
|
253
|
-
},
|
|
254
|
-
outputSchema: {
|
|
255
|
-
evidences: z.array(z.object({
|
|
256
|
-
id: z.string(),
|
|
257
|
-
timestamp: z.string(),
|
|
258
|
-
conversationId: z.string(),
|
|
259
|
-
llmProvider: z.string(),
|
|
260
|
-
messageCount: z.number(),
|
|
261
|
-
tags: z.string().nullable()
|
|
262
|
-
})),
|
|
263
|
-
total: z.number()
|
|
264
|
-
}
|
|
265
|
-
}, async (params) => {
|
|
266
|
-
try {
|
|
267
|
-
// Validate parameters
|
|
268
|
-
if (params.limit !== undefined && params.limit <= 0) {
|
|
269
|
-
throw new Error('Limit must be positive');
|
|
270
|
-
}
|
|
271
|
-
if (params.offset !== undefined && params.offset < 0) {
|
|
272
|
-
throw new Error('Offset cannot be negative');
|
|
273
|
-
}
|
|
274
|
-
// Validate date format if provided
|
|
275
|
-
if (params.dateFrom) {
|
|
276
|
-
const date = new Date(params.dateFrom);
|
|
277
|
-
if (isNaN(date.getTime())) {
|
|
278
|
-
throw new Error('dateFrom must be a valid ISO date string');
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
if (params.dateTo) {
|
|
282
|
-
const date = new Date(params.dateTo);
|
|
283
|
-
if (isNaN(date.getTime())) {
|
|
284
|
-
throw new Error('dateTo must be a valid ISO date string');
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
const evidences = this.db.search({
|
|
288
|
-
query: params.query,
|
|
289
|
-
tags: params.tags,
|
|
290
|
-
dateFrom: params.dateFrom,
|
|
291
|
-
dateTo: params.dateTo,
|
|
292
|
-
limit: params.limit,
|
|
293
|
-
offset: params.offset
|
|
294
|
-
});
|
|
295
|
-
const mappedEvidences = evidences.map(e => ({
|
|
296
|
-
id: e.id,
|
|
297
|
-
timestamp: e.timestamp,
|
|
298
|
-
conversationId: e.conversationId,
|
|
299
|
-
llmProvider: e.llmProvider,
|
|
300
|
-
messageCount: e.messageCount,
|
|
301
|
-
tags: e.tags
|
|
302
|
-
}));
|
|
303
|
-
return {
|
|
304
|
-
content: [{ type: 'text', text: `✅ Search completed successfully\n- Results: ${evidences.length} footprint(s) found\n- Query: ${params.query || 'None'}\n- Tags: ${params.tags?.join(', ') || 'None'}\n- Date Range: ${params.dateFrom || 'Start'} to ${params.dateTo || 'End'}` }],
|
|
305
|
-
structuredContent: { evidences: mappedEvidences, total: evidences.length }
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
catch (error) {
|
|
309
|
-
throw new Error(`[Tool: search-footprints] ${getErrorMessage(error)}. Suggested action: Check date format (ISO 8601), limit > 0, and offset >= 0.`);
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
|
-
this.server.registerTool('verify-footprint', {
|
|
313
|
-
title: 'Verify Footprint',
|
|
314
|
-
description: 'Verify the integrity and authenticity of captured footprint',
|
|
315
|
-
inputSchema: {
|
|
316
|
-
id: z.string().describe('Footprint ID to verify')
|
|
317
|
-
},
|
|
318
|
-
outputSchema: {
|
|
319
|
-
id: z.string(),
|
|
320
|
-
verified: z.boolean(),
|
|
321
|
-
checks: z.object({
|
|
322
|
-
contentIntegrity: z.object({
|
|
323
|
-
passed: z.boolean(),
|
|
324
|
-
hash: z.string()
|
|
325
|
-
}),
|
|
326
|
-
gitTimestamp: z.object({
|
|
327
|
-
passed: z.boolean(),
|
|
328
|
-
commitHash: z.string().nullable(),
|
|
329
|
-
timestamp: z.string().nullable()
|
|
330
|
-
}),
|
|
331
|
-
encryptionStatus: z.object({
|
|
332
|
-
passed: z.boolean(),
|
|
333
|
-
algorithm: z.string()
|
|
334
|
-
})
|
|
335
|
-
}),
|
|
336
|
-
legalReadiness: z.boolean(),
|
|
337
|
-
verifiedAt: z.string()
|
|
338
|
-
}
|
|
339
|
-
}, async (params) => {
|
|
340
|
-
try {
|
|
341
|
-
// Find the footprint record
|
|
342
|
-
const evidence = this.db.findById(params.id);
|
|
343
|
-
if (!evidence) {
|
|
344
|
-
throw new Error(`Evidence with ID ${params.id} not found`);
|
|
345
|
-
}
|
|
346
|
-
const key = await this.getDerivedKey();
|
|
347
|
-
// Perform verifications
|
|
348
|
-
const checks = {
|
|
349
|
-
contentIntegrity: { passed: false, hash: '' },
|
|
350
|
-
gitTimestamp: { passed: false, commitHash: null, timestamp: null },
|
|
351
|
-
encryptionStatus: { passed: false, algorithm: 'XChaCha20-Poly1305' }
|
|
352
|
-
};
|
|
353
|
-
// 1. Content Integrity: Decrypt content, compute SHA-256 hash, verify it matches stored contentHash
|
|
354
|
-
try {
|
|
355
|
-
const decryptedContent = decrypt(evidence.encryptedContent, evidence.nonce, key);
|
|
356
|
-
const computedHash = crypto.createHash('sha256').update(decryptedContent).digest('hex');
|
|
357
|
-
checks.contentIntegrity.passed = computedHash === evidence.contentHash;
|
|
358
|
-
checks.contentIntegrity.hash = computedHash;
|
|
359
|
-
}
|
|
360
|
-
catch (error) {
|
|
361
|
-
checks.contentIntegrity.passed = false;
|
|
362
|
-
checks.contentIntegrity.hash = '';
|
|
363
|
-
}
|
|
364
|
-
// 2. Git Timestamp: Check if gitCommitHash exists and gitTimestamp is valid
|
|
365
|
-
checks.gitTimestamp.commitHash = evidence.gitCommitHash;
|
|
366
|
-
checks.gitTimestamp.timestamp = evidence.gitTimestamp;
|
|
367
|
-
checks.gitTimestamp.passed = !!(evidence.gitCommitHash && evidence.gitTimestamp);
|
|
368
|
-
// 3. Encryption Status: Verify decryption works (XChaCha20-Poly1305)
|
|
369
|
-
try {
|
|
370
|
-
decrypt(evidence.encryptedContent, evidence.nonce, key);
|
|
371
|
-
checks.encryptionStatus.passed = true;
|
|
372
|
-
}
|
|
373
|
-
catch (error) {
|
|
374
|
-
checks.encryptionStatus.passed = false;
|
|
375
|
-
}
|
|
376
|
-
const verified = checks.contentIntegrity.passed && checks.gitTimestamp.passed && checks.encryptionStatus.passed;
|
|
377
|
-
const legalReadiness = verified;
|
|
378
|
-
const statusSymbols = {
|
|
379
|
-
content: checks.contentIntegrity.passed ? '✓' : '✗',
|
|
380
|
-
git: checks.gitTimestamp.passed ? '✓' : '✗',
|
|
381
|
-
encryption: checks.encryptionStatus.passed ? '✓' : '✗'
|
|
382
|
-
};
|
|
383
|
-
const statusText = verified
|
|
384
|
-
? `✅ Evidence ${params.id} verified successfully\n- Content: ${statusSymbols.content} Integrity preserved\n- Git: ${statusSymbols.git} Timestamp verified\n- Encryption: ${statusSymbols.encryption} XChaCha20-Poly1305`
|
|
385
|
-
: `❌ Evidence ${params.id} verification failed\n- Content: ${statusSymbols.content} Integrity check\n- Git: ${statusSymbols.git} Timestamp check\n- Encryption: ${statusSymbols.encryption} Decryption check`;
|
|
386
|
-
return {
|
|
387
|
-
content: [{ type: 'text', text: statusText }],
|
|
388
|
-
structuredContent: {
|
|
389
|
-
id: params.id,
|
|
390
|
-
verified,
|
|
391
|
-
checks,
|
|
392
|
-
legalReadiness,
|
|
393
|
-
verifiedAt: new Date().toISOString()
|
|
394
|
-
}
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
catch (error) {
|
|
398
|
-
throw new Error(`[Tool: verify-footprint] ${getErrorMessage(error)}. Suggested action: Verify the evidence ID exists and encryption password is correct.`);
|
|
399
|
-
}
|
|
400
|
-
});
|
|
401
|
-
this.server.registerTool('suggest-capture', {
|
|
402
|
-
title: 'Suggest Capture',
|
|
403
|
-
description: 'Analyze conversation content and suggest whether to capture it as evidence',
|
|
404
|
-
inputSchema: {
|
|
405
|
-
summary: z.string().describe('Conversation summary or key content to analyze')
|
|
406
|
-
},
|
|
407
|
-
outputSchema: {
|
|
408
|
-
shouldCapture: z.boolean(),
|
|
409
|
-
reason: z.string(),
|
|
410
|
-
suggestedTags: z.array(z.string()),
|
|
411
|
-
suggestedConversationId: z.string(),
|
|
412
|
-
confidence: z.number()
|
|
413
|
-
}
|
|
414
|
-
}, async (params) => {
|
|
415
|
-
try {
|
|
416
|
-
if (!params.summary || params.summary.trim().length === 0) {
|
|
417
|
-
throw new Error('Summary cannot be empty');
|
|
418
|
-
}
|
|
419
|
-
const summary = params.summary.toLowerCase();
|
|
420
|
-
// Define keyword categories for detection
|
|
421
|
-
const keywordCategories = {
|
|
422
|
-
ip: ['patent', 'intellectual property', 'invention', 'algorithm', 'proprietary', 'innovation', 'design'],
|
|
423
|
-
legal: ['contract', 'agreement', 'legal', 'copyright', 'license', 'terms', 'clause', 'liability'],
|
|
424
|
-
business: ['decision', 'milestone', 'deliverable', 'approval', 'strategy', 'roadmap', 'budget', 'timeline'],
|
|
425
|
-
research: ['hypothesis', 'findings', 'proof', 'research', 'study', 'experiment', 'analysis', 'data'],
|
|
426
|
-
compliance: ['audit', 'compliance', 'evidence', 'documentation', 'regulation', 'policy', 'requirement']
|
|
427
|
-
};
|
|
428
|
-
// Helper: match whole words only (avoid false positives like "logarithm" matching "algorithm")
|
|
429
|
-
const matchesWord = (text, word) => {
|
|
430
|
-
const regex = new RegExp(`\\b${word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'i');
|
|
431
|
-
return regex.test(text);
|
|
432
|
-
};
|
|
433
|
-
// Detect matching categories and keywords
|
|
434
|
-
const matches = [];
|
|
435
|
-
let totalKeywords = 0;
|
|
436
|
-
for (const [category, keywords] of Object.entries(keywordCategories)) {
|
|
437
|
-
const foundKeywords = keywords.filter(keyword => matchesWord(summary, keyword));
|
|
438
|
-
if (foundKeywords.length > 0) {
|
|
439
|
-
const weight = foundKeywords.length / keywords.length;
|
|
440
|
-
matches.push({ category, keywords: foundKeywords, weight });
|
|
441
|
-
totalKeywords += foundKeywords.length;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
// Determine if should capture
|
|
445
|
-
const shouldCapture = matches.length > 0;
|
|
446
|
-
// Calculate confidence: represents likelihood content is worth capturing
|
|
447
|
-
// High confidence (>0.7) = definitely important
|
|
448
|
-
// Low confidence (<0.5) = probably not important
|
|
449
|
-
let confidence;
|
|
450
|
-
if (shouldCapture) {
|
|
451
|
-
// More keywords and categories = higher importance confidence
|
|
452
|
-
const keywordDensity = totalKeywords / Math.max(summary.split(/\s+/).length, 1);
|
|
453
|
-
const categoryBonus = matches.length * 0.15;
|
|
454
|
-
const keywordBonus = Math.min(totalKeywords * 0.1, 0.4);
|
|
455
|
-
confidence = Math.min(0.95, 0.3 + categoryBonus + keywordBonus + keywordDensity);
|
|
456
|
-
}
|
|
457
|
-
else {
|
|
458
|
-
// No keywords found = low importance confidence
|
|
459
|
-
// Longer text without keywords = slightly higher (might have missed something)
|
|
460
|
-
const wordCount = summary.split(/\s+/).length;
|
|
461
|
-
const lengthFactor = Math.min(wordCount / 200, 0.3); // Max 0.3 for very long text
|
|
462
|
-
confidence = 0.1 + lengthFactor; // Range: 0.1 to 0.4
|
|
463
|
-
}
|
|
464
|
-
// Generate reason
|
|
465
|
-
let reason;
|
|
466
|
-
if (shouldCapture) {
|
|
467
|
-
const categories = matches.map(m => m.category === 'ip' ? 'IP' : m.category);
|
|
468
|
-
if (categories.length === 1) {
|
|
469
|
-
reason = `Contains ${categories[0]} keywords: ${matches[0].keywords.join(', ')}`;
|
|
470
|
-
}
|
|
471
|
-
else {
|
|
472
|
-
reason = `Contains multiple important categories: ${categories.join(', ')}`;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
else {
|
|
476
|
-
reason = 'Appears to be casual conversation with no critical business, legal, or IP content';
|
|
477
|
-
}
|
|
478
|
-
// Generate suggested tags
|
|
479
|
-
const suggestedTags = shouldCapture
|
|
480
|
-
? [...new Set(matches.flatMap(m => [m.category, ...m.keywords.slice(0, 2)]))]
|
|
481
|
-
: [];
|
|
482
|
-
// Generate conversation ID
|
|
483
|
-
const currentDate = new Date().toISOString().slice(0, 10);
|
|
484
|
-
let conversationId;
|
|
485
|
-
if (shouldCapture && matches.length > 0) {
|
|
486
|
-
// Extract key terms for ID generation
|
|
487
|
-
const primaryKeywords = matches[0].keywords.slice(0, 2);
|
|
488
|
-
const cleanKeywords = primaryKeywords
|
|
489
|
-
.map(k => k.replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''))
|
|
490
|
-
.filter(k => k.length > 0);
|
|
491
|
-
if (cleanKeywords.length > 0) {
|
|
492
|
-
conversationId = `${cleanKeywords.join('-')}-${currentDate}`;
|
|
493
|
-
}
|
|
494
|
-
else {
|
|
495
|
-
conversationId = `${matches[0].category}-discussion-${currentDate}`;
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
else {
|
|
499
|
-
conversationId = `conversation-${currentDate}`;
|
|
500
|
-
}
|
|
501
|
-
const resultText = shouldCapture
|
|
502
|
-
? `💡 Capture suggested (${Math.round(confidence * 100)}% confidence)\n🔍 Reason: ${reason}\n🏷️ Tags: ${suggestedTags.join(', ')}\n📝 ID: ${conversationId}`
|
|
503
|
-
: `🤷 Capture not recommended (${Math.round(confidence * 100)}% confidence)\n💬 ${reason}`;
|
|
504
|
-
return {
|
|
505
|
-
content: [{ type: 'text', text: resultText }],
|
|
506
|
-
structuredContent: {
|
|
507
|
-
shouldCapture,
|
|
508
|
-
reason,
|
|
509
|
-
suggestedTags,
|
|
510
|
-
suggestedConversationId: conversationId,
|
|
511
|
-
confidence: Math.round(confidence * 100) / 100
|
|
512
|
-
}
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
|
-
catch (error) {
|
|
516
|
-
throw new Error(`[Tool: suggest-capture] ${getErrorMessage(error)}`);
|
|
517
|
-
}
|
|
518
|
-
});
|
|
519
|
-
// Delete evidences tool
|
|
520
|
-
this.server.registerTool('delete-footprints', {
|
|
521
|
-
title: 'Delete Footprints',
|
|
522
|
-
description: 'Permanently delete one or more footprint records',
|
|
523
|
-
inputSchema: {
|
|
524
|
-
evidenceIds: z.array(z.string()).min(1).describe('Array of footprint IDs to delete')
|
|
525
|
-
},
|
|
526
|
-
outputSchema: {
|
|
527
|
-
deletedCount: z.number(),
|
|
528
|
-
success: z.boolean()
|
|
529
|
-
}
|
|
530
|
-
}, async (params) => {
|
|
531
|
-
try {
|
|
532
|
-
if (!params.evidenceIds || params.evidenceIds.length === 0) {
|
|
533
|
-
throw new Error('At least one evidence ID is required');
|
|
534
|
-
}
|
|
535
|
-
const deletedCount = this.db.deleteMany(params.evidenceIds);
|
|
536
|
-
const success = deletedCount > 0;
|
|
537
|
-
const resultText = success
|
|
538
|
-
? `🗑️ Successfully deleted ${deletedCount} footprint record${deletedCount > 1 ? 's' : ''}`
|
|
539
|
-
: `⚠️ No footprint records found with the provided IDs`;
|
|
540
|
-
return {
|
|
541
|
-
content: [{ type: 'text', text: resultText }],
|
|
542
|
-
structuredContent: { deletedCount, success }
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
|
-
catch (error) {
|
|
546
|
-
throw new Error(`[Tool: delete-footprints] ${getErrorMessage(error)}. Suggested action: Verify the footprint IDs exist.`);
|
|
547
|
-
}
|
|
548
|
-
});
|
|
549
|
-
// Rename tag tool
|
|
550
|
-
this.server.registerTool('rename-tag', {
|
|
551
|
-
title: 'Rename Tag',
|
|
552
|
-
description: 'Rename a tag across all footprint records',
|
|
553
|
-
inputSchema: {
|
|
554
|
-
oldTag: z.string().min(1).describe('Current tag name to rename'),
|
|
555
|
-
newTag: z.string().min(1).describe('New tag name')
|
|
556
|
-
},
|
|
557
|
-
outputSchema: {
|
|
558
|
-
updatedCount: z.number(),
|
|
559
|
-
success: z.boolean()
|
|
560
|
-
}
|
|
561
|
-
}, async (params) => {
|
|
562
|
-
try {
|
|
563
|
-
if (!params.oldTag || !params.newTag) {
|
|
564
|
-
throw new Error('Both old and new tag names are required');
|
|
565
|
-
}
|
|
566
|
-
if (params.oldTag.trim() === params.newTag.trim()) {
|
|
567
|
-
throw new Error('New tag must be different from old tag');
|
|
568
|
-
}
|
|
569
|
-
const updatedCount = this.db.renameTag(params.oldTag.trim(), params.newTag.trim());
|
|
570
|
-
const success = updatedCount > 0;
|
|
571
|
-
const resultText = success
|
|
572
|
-
? `🏷️ Renamed tag "${params.oldTag}" to "${params.newTag}" in ${updatedCount} footprint record${updatedCount > 1 ? 's' : ''}`
|
|
573
|
-
: `⚠️ No footprint records found with tag "${params.oldTag}"`;
|
|
574
|
-
return {
|
|
575
|
-
content: [{ type: 'text', text: resultText }],
|
|
576
|
-
structuredContent: { updatedCount, success }
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
catch (error) {
|
|
580
|
-
throw new Error(`[Tool: rename-tag] ${getErrorMessage(error)}`);
|
|
581
|
-
}
|
|
582
|
-
});
|
|
583
|
-
// Remove tag tool
|
|
584
|
-
this.server.registerTool('remove-tag', {
|
|
585
|
-
title: 'Remove Tag',
|
|
586
|
-
description: 'Remove a tag from all footprint records',
|
|
587
|
-
inputSchema: {
|
|
588
|
-
tag: z.string().min(1).describe('Tag name to remove')
|
|
589
|
-
},
|
|
590
|
-
outputSchema: {
|
|
591
|
-
updatedCount: z.number(),
|
|
592
|
-
success: z.boolean()
|
|
593
|
-
}
|
|
594
|
-
}, async (params) => {
|
|
595
|
-
try {
|
|
596
|
-
if (!params.tag) {
|
|
597
|
-
throw new Error('Tag name is required');
|
|
598
|
-
}
|
|
599
|
-
const updatedCount = this.db.removeTag(params.tag.trim());
|
|
600
|
-
const success = updatedCount > 0;
|
|
601
|
-
const resultText = success
|
|
602
|
-
? `🗑️ Removed tag "${params.tag}" from ${updatedCount} footprint record${updatedCount > 1 ? 's' : ''}`
|
|
603
|
-
: `⚠️ No footprint records found with tag "${params.tag}"`;
|
|
604
|
-
return {
|
|
605
|
-
content: [{ type: 'text', text: resultText }],
|
|
606
|
-
structuredContent: { updatedCount, success }
|
|
607
|
-
};
|
|
608
|
-
}
|
|
609
|
-
catch (error) {
|
|
610
|
-
throw new Error(`[Tool: remove-tag] ${getErrorMessage(error)}`);
|
|
611
|
-
}
|
|
612
|
-
});
|
|
613
|
-
// Get tag statistics tool
|
|
614
|
-
this.server.registerTool('get-tag-stats', {
|
|
615
|
-
title: 'Get Tag Statistics',
|
|
616
|
-
description: 'Get all unique tags with their usage counts',
|
|
617
|
-
inputSchema: {},
|
|
618
|
-
outputSchema: {
|
|
619
|
-
tags: z.array(z.object({
|
|
620
|
-
tag: z.string(),
|
|
621
|
-
count: z.number()
|
|
622
|
-
})),
|
|
623
|
-
totalTags: z.number()
|
|
624
|
-
}
|
|
625
|
-
}, async () => {
|
|
626
|
-
try {
|
|
627
|
-
const tagCounts = this.db.getTagCounts();
|
|
628
|
-
const tags = Array.from(tagCounts.entries())
|
|
629
|
-
.map(([tag, count]) => ({ tag, count }))
|
|
630
|
-
.sort((a, b) => b.count - a.count);
|
|
631
|
-
const resultText = tags.length > 0
|
|
632
|
-
? `📊 Tag Statistics:\n${tags.map(t => ` • ${t.tag}: ${t.count}`).join('\n')}`
|
|
633
|
-
: `📊 No tags found in any footprint records`;
|
|
634
|
-
return {
|
|
635
|
-
content: [{ type: 'text', text: resultText }],
|
|
636
|
-
structuredContent: { tags, totalTags: tags.length }
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
catch (error) {
|
|
640
|
-
throw new Error(`[Tool: get-tag-stats] ${getErrorMessage(error)}`);
|
|
641
|
-
}
|
|
642
|
-
});
|
|
643
|
-
}
|
|
644
|
-
registerResources() {
|
|
645
|
-
this.server.registerResource('evidence', new ResourceTemplate('evidence://{id}', { list: undefined }), {
|
|
646
|
-
title: 'Evidence Content',
|
|
647
|
-
description: 'Access encrypted footprint record by ID',
|
|
648
|
-
mimeType: 'text/plain'
|
|
649
|
-
}, async (uri, { id }) => {
|
|
650
|
-
try {
|
|
651
|
-
const evidence = this.db.findById(id);
|
|
652
|
-
if (!evidence) {
|
|
653
|
-
throw new Error(`Evidence with ID ${id} not found`);
|
|
654
|
-
}
|
|
655
|
-
const key = await this.getDerivedKey();
|
|
656
|
-
const decrypted = decrypt(evidence.encryptedContent, evidence.nonce, key);
|
|
657
|
-
return {
|
|
658
|
-
contents: [{ uri: uri.href, mimeType: 'text/plain', text: decrypted }]
|
|
659
|
-
};
|
|
660
|
-
}
|
|
661
|
-
catch (error) {
|
|
662
|
-
throw new Error(`Failed to access evidence resource: ${getErrorMessage(error)}`);
|
|
663
|
-
}
|
|
664
|
-
});
|
|
665
|
-
}
|
|
666
|
-
async start() {
|
|
667
|
-
const transport = new StdioServerTransport();
|
|
668
|
-
await this.server.connect(transport);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
async function main() {
|
|
672
|
-
const config = {
|
|
673
|
-
dbPath: process.env.EVIDENCEMCP_DB_PATH || './evidence.db',
|
|
674
|
-
password: process.env.EVIDENCEMCP_PASSWORD || ''
|
|
675
|
-
};
|
|
676
|
-
if (!config.password) {
|
|
677
|
-
console.error('Error: EVIDENCEMCP_PASSWORD environment variable required');
|
|
678
|
-
process.exit(1);
|
|
679
|
-
}
|
|
680
|
-
const server = new EvidenceMCPServer(config);
|
|
681
|
-
await server.start();
|
|
682
|
-
}
|
|
683
|
-
// Handle both direct execution and symlink execution
|
|
684
|
-
const isMainModule = import.meta.url === `file://${process.argv[1]}` ||
|
|
685
|
-
process.argv[1]?.endsWith('/footprint') ||
|
|
686
|
-
process.argv[1]?.endsWith('/dist/index.js');
|
|
687
|
-
if (isMainModule) {
|
|
688
|
-
main().catch((error) => {
|
|
689
|
-
console.error('Server error:', error);
|
|
690
|
-
process.exit(1);
|
|
691
|
-
});
|
|
692
|
-
}
|
|
693
|
-
export { EvidenceMCPTestHelpers } from './test-helpers.js';
|
|
694
|
-
//# sourceMappingURL=index.js.map
|