globodai-mcp-payment-manager 1.0.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/.env.example +23 -0
- package/.github/workflows/ci.yml +26 -0
- package/.github/workflows/release.yml +82 -0
- package/LICENSE +21 -0
- package/README.md +362 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +122 -0
- package/dist/lib/blockchain.d.ts +50 -0
- package/dist/lib/blockchain.js +287 -0
- package/dist/lib/cards.d.ts +83 -0
- package/dist/lib/cards.js +276 -0
- package/dist/lib/cli-runner.d.ts +31 -0
- package/dist/lib/cli-runner.js +77 -0
- package/dist/lib/crypto.d.ts +39 -0
- package/dist/lib/crypto.js +228 -0
- package/dist/lib/cvv-crypto.d.ts +23 -0
- package/dist/lib/cvv-crypto.js +67 -0
- package/dist/lib/mcp-core.d.ts +46 -0
- package/dist/lib/mcp-core.js +86 -0
- package/dist/lib/pin-manager.d.ts +69 -0
- package/dist/lib/pin-manager.js +199 -0
- package/dist/lib/wallets.d.ts +91 -0
- package/dist/lib/wallets.js +227 -0
- package/dist/tools/add-card.d.ts +65 -0
- package/dist/tools/add-card.js +97 -0
- package/dist/tools/add-wallet.d.ts +65 -0
- package/dist/tools/add-wallet.js +104 -0
- package/dist/tools/card-status.d.ts +20 -0
- package/dist/tools/card-status.js +26 -0
- package/dist/tools/confirm-payment.d.ts +44 -0
- package/dist/tools/confirm-payment.js +88 -0
- package/dist/tools/get-total-balance.d.ts +41 -0
- package/dist/tools/get-total-balance.js +98 -0
- package/dist/tools/get-transactions.d.ts +39 -0
- package/dist/tools/get-transactions.js +40 -0
- package/dist/tools/get-wallet-balance.d.ts +43 -0
- package/dist/tools/get-wallet-balance.js +69 -0
- package/dist/tools/list-cards.d.ts +36 -0
- package/dist/tools/list-cards.js +39 -0
- package/dist/tools/list-wallet-transactions.d.ts +63 -0
- package/dist/tools/list-wallet-transactions.js +76 -0
- package/dist/tools/list-wallets.d.ts +41 -0
- package/dist/tools/list-wallets.js +50 -0
- package/dist/tools/lock-cards.d.ts +16 -0
- package/dist/tools/lock-cards.js +23 -0
- package/dist/tools/prepare-crypto-tx.d.ts +69 -0
- package/dist/tools/prepare-crypto-tx.js +93 -0
- package/dist/tools/prepare-payment.d.ts +57 -0
- package/dist/tools/prepare-payment.js +93 -0
- package/dist/tools/remove-card.d.ts +25 -0
- package/dist/tools/remove-card.js +39 -0
- package/dist/tools/remove-wallet.d.ts +27 -0
- package/dist/tools/remove-wallet.js +40 -0
- package/dist/tools/setup-pin.d.ts +26 -0
- package/dist/tools/setup-pin.js +33 -0
- package/dist/tools/sign-crypto-tx.d.ts +42 -0
- package/dist/tools/sign-crypto-tx.js +75 -0
- package/dist/tools/unlock-cards.d.ts +35 -0
- package/dist/tools/unlock-cards.js +41 -0
- package/package.json +50 -0
- package/src/index.ts +139 -0
- package/src/lib/blockchain.ts +375 -0
- package/src/lib/cards.ts +372 -0
- package/src/lib/cli-runner.ts +113 -0
- package/src/lib/crypto.ts +284 -0
- package/src/lib/cvv-crypto.ts +81 -0
- package/src/lib/mcp-core.ts +127 -0
- package/src/lib/pin-manager.ts +252 -0
- package/src/lib/wallets.ts +331 -0
- package/src/tools/add-card.ts +108 -0
- package/src/tools/add-wallet.ts +114 -0
- package/src/tools/card-status.ts +32 -0
- package/src/tools/confirm-payment.ts +103 -0
- package/src/tools/get-total-balance.ts +123 -0
- package/src/tools/get-transactions.ts +49 -0
- package/src/tools/get-wallet-balance.ts +75 -0
- package/src/tools/list-cards.ts +52 -0
- package/src/tools/list-wallet-transactions.ts +83 -0
- package/src/tools/list-wallets.ts +63 -0
- package/src/tools/lock-cards.ts +31 -0
- package/src/tools/prepare-crypto-tx.ts +108 -0
- package/src/tools/prepare-payment.ts +108 -0
- package/src/tools/remove-card.ts +46 -0
- package/src/tools/remove-wallet.ts +47 -0
- package/src/tools/setup-pin.ts +39 -0
- package/src/tools/sign-crypto-tx.ts +90 -0
- package/src/tools/unlock-cards.ts +48 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encryption for sensitive data with KMS support
|
|
3
|
+
*
|
|
4
|
+
* Supports multiple backends:
|
|
5
|
+
* 1. AWS KMS (recommended for production)
|
|
6
|
+
* 2. Local AES-256-GCM (for development)
|
|
7
|
+
*
|
|
8
|
+
* Configuration via environment:
|
|
9
|
+
* - MCP_CRYPTO_BACKEND: "aws-kms" | "local" (default: "local")
|
|
10
|
+
* - AWS_KMS_KEY_ID: ARN or alias of the KMS key
|
|
11
|
+
* - AWS_REGION: AWS region for KMS
|
|
12
|
+
* - MCP_MASTER_KEY: Local master key (if using local backend)
|
|
13
|
+
*
|
|
14
|
+
* Security model with KMS:
|
|
15
|
+
* - Even with root access to VPS, attacker needs AWS credentials
|
|
16
|
+
* - AWS credentials should use IAM role with minimal permissions
|
|
17
|
+
* - KMS provides audit logs of all decrypt operations
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
|
|
21
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from "fs";
|
|
22
|
+
import { homedir } from "os";
|
|
23
|
+
import { join } from "path";
|
|
24
|
+
import { KMSClient, EncryptCommand, DecryptCommand, GenerateDataKeyCommand } from "@aws-sdk/client-kms";
|
|
25
|
+
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// Configuration
|
|
28
|
+
// ============================================================================
|
|
29
|
+
|
|
30
|
+
const ALGORITHM = "aes-256-gcm";
|
|
31
|
+
const KEY_LENGTH = 32;
|
|
32
|
+
const IV_LENGTH = 16;
|
|
33
|
+
const AUTH_TAG_LENGTH = 16;
|
|
34
|
+
|
|
35
|
+
const CONFIG_DIR = join(homedir(), ".mcp-ecosystem");
|
|
36
|
+
const KEY_FILE = join(CONFIG_DIR, ".master-key");
|
|
37
|
+
const DEK_CACHE_FILE = join(CONFIG_DIR, ".dek-cache");
|
|
38
|
+
|
|
39
|
+
// Prefixes to identify encryption version/backend
|
|
40
|
+
const PREFIX_LOCAL = "enc:local:";
|
|
41
|
+
const PREFIX_KMS = "enc:kms:";
|
|
42
|
+
|
|
43
|
+
type CryptoBackend = "local" | "aws-kms";
|
|
44
|
+
|
|
45
|
+
function getBackend(): CryptoBackend {
|
|
46
|
+
const backend = process.env.MCP_CRYPTO_BACKEND?.toLowerCase();
|
|
47
|
+
if (backend === "aws-kms" || backend === "kms") return "aws-kms";
|
|
48
|
+
return "local";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ============================================================================
|
|
52
|
+
// AWS KMS Backend
|
|
53
|
+
// ============================================================================
|
|
54
|
+
|
|
55
|
+
let kmsClient: KMSClient | null = null;
|
|
56
|
+
let cachedDataKey: Buffer | null = null;
|
|
57
|
+
let cachedEncryptedDataKey: Buffer | null = null;
|
|
58
|
+
|
|
59
|
+
function getKMSClient(): KMSClient {
|
|
60
|
+
if (!kmsClient) {
|
|
61
|
+
kmsClient = new KMSClient({
|
|
62
|
+
region: process.env.AWS_REGION || "us-east-1",
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return kmsClient;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getKMSKeyId(): string {
|
|
69
|
+
const keyId = process.env.AWS_KMS_KEY_ID;
|
|
70
|
+
if (!keyId) {
|
|
71
|
+
throw new Error("AWS_KMS_KEY_ID environment variable required for KMS backend");
|
|
72
|
+
}
|
|
73
|
+
return keyId;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Generate or retrieve Data Encryption Key (DEK) from KMS
|
|
78
|
+
* Uses envelope encryption pattern
|
|
79
|
+
*/
|
|
80
|
+
async function getDataKey(): Promise<{ plaintext: Buffer; encrypted: Buffer }> {
|
|
81
|
+
// Check memory cache first
|
|
82
|
+
if (cachedDataKey && cachedEncryptedDataKey) {
|
|
83
|
+
return { plaintext: cachedDataKey, encrypted: cachedEncryptedDataKey };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check disk cache (encrypted DEK)
|
|
87
|
+
if (existsSync(DEK_CACHE_FILE)) {
|
|
88
|
+
try {
|
|
89
|
+
const encryptedDek = readFileSync(DEK_CACHE_FILE);
|
|
90
|
+
const client = getKMSClient();
|
|
91
|
+
|
|
92
|
+
const response = await client.send(new DecryptCommand({
|
|
93
|
+
CiphertextBlob: encryptedDek,
|
|
94
|
+
KeyId: getKMSKeyId(),
|
|
95
|
+
}));
|
|
96
|
+
|
|
97
|
+
if (response.Plaintext) {
|
|
98
|
+
cachedDataKey = Buffer.from(response.Plaintext);
|
|
99
|
+
cachedEncryptedDataKey = encryptedDek;
|
|
100
|
+
return { plaintext: cachedDataKey, encrypted: cachedEncryptedDataKey };
|
|
101
|
+
}
|
|
102
|
+
} catch (err) {
|
|
103
|
+
console.error("[crypto] Failed to decrypt cached DEK, generating new one");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Generate new DEK
|
|
108
|
+
const client = getKMSClient();
|
|
109
|
+
const response = await client.send(new GenerateDataKeyCommand({
|
|
110
|
+
KeyId: getKMSKeyId(),
|
|
111
|
+
KeySpec: "AES_256",
|
|
112
|
+
}));
|
|
113
|
+
|
|
114
|
+
if (!response.Plaintext || !response.CiphertextBlob) {
|
|
115
|
+
throw new Error("KMS GenerateDataKey failed");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
cachedDataKey = Buffer.from(response.Plaintext);
|
|
119
|
+
cachedEncryptedDataKey = Buffer.from(response.CiphertextBlob);
|
|
120
|
+
|
|
121
|
+
// Cache encrypted DEK to disk (safe - it's encrypted by KMS)
|
|
122
|
+
ensureConfigDir();
|
|
123
|
+
writeFileSync(DEK_CACHE_FILE, cachedEncryptedDataKey, { mode: 0o600 });
|
|
124
|
+
|
|
125
|
+
console.error("[crypto] Generated new Data Encryption Key via KMS");
|
|
126
|
+
return { plaintext: cachedDataKey, encrypted: cachedEncryptedDataKey };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function encryptWithKMS(plaintext: string): Promise<string> {
|
|
130
|
+
const { plaintext: dek } = await getDataKey();
|
|
131
|
+
|
|
132
|
+
const iv = randomBytes(IV_LENGTH);
|
|
133
|
+
const cipher = createCipheriv(ALGORITHM, dek, iv);
|
|
134
|
+
|
|
135
|
+
let encrypted = cipher.update(plaintext, "utf8", "base64");
|
|
136
|
+
encrypted += cipher.final("base64");
|
|
137
|
+
const authTag = cipher.getAuthTag();
|
|
138
|
+
|
|
139
|
+
// Format: enc:kms:<iv>:<authTag>:<ciphertext>
|
|
140
|
+
return `${PREFIX_KMS}${iv.toString("base64")}:${authTag.toString("base64")}:${encrypted}`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function decryptWithKMS(encrypted: string): Promise<string> {
|
|
144
|
+
const { plaintext: dek } = await getDataKey();
|
|
145
|
+
|
|
146
|
+
const parts = encrypted.slice(PREFIX_KMS.length).split(":");
|
|
147
|
+
if (parts.length !== 3) {
|
|
148
|
+
throw new Error("Invalid KMS encrypted format");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const iv = Buffer.from(parts[0]!, "base64");
|
|
152
|
+
const authTag = Buffer.from(parts[1]!, "base64");
|
|
153
|
+
const ciphertext = parts[2]!;
|
|
154
|
+
|
|
155
|
+
const decipher = createDecipheriv(ALGORITHM, dek, iv);
|
|
156
|
+
decipher.setAuthTag(authTag);
|
|
157
|
+
|
|
158
|
+
let decrypted: string = decipher.update(ciphertext, "base64", "utf8");
|
|
159
|
+
decrypted += decipher.final("utf8");
|
|
160
|
+
|
|
161
|
+
return decrypted;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ============================================================================
|
|
165
|
+
// Local Backend (for development)
|
|
166
|
+
// ============================================================================
|
|
167
|
+
|
|
168
|
+
function ensureConfigDir(): void {
|
|
169
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
170
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function getLocalMasterKey(): Buffer {
|
|
175
|
+
if (process.env.MCP_MASTER_KEY) {
|
|
176
|
+
return scryptSync(process.env.MCP_MASTER_KEY, "mcp-ecosystem-salt-v1", KEY_LENGTH);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (existsSync(KEY_FILE)) {
|
|
180
|
+
const keyData = readFileSync(KEY_FILE, "utf-8").trim();
|
|
181
|
+
return scryptSync(keyData, "mcp-ecosystem-salt-v1", KEY_LENGTH);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
console.error("[crypto] Generating new local master key...");
|
|
185
|
+
const newKey = randomBytes(32).toString("base64");
|
|
186
|
+
|
|
187
|
+
ensureConfigDir();
|
|
188
|
+
writeFileSync(KEY_FILE, newKey, { mode: 0o600 });
|
|
189
|
+
chmodSync(KEY_FILE, 0o600);
|
|
190
|
+
|
|
191
|
+
console.error(`[crypto] Master key saved to ${KEY_FILE}`);
|
|
192
|
+
return scryptSync(newKey, "mcp-ecosystem-salt-v1", KEY_LENGTH);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function encryptLocal(plaintext: string): string {
|
|
196
|
+
const key = getLocalMasterKey();
|
|
197
|
+
const iv = randomBytes(IV_LENGTH);
|
|
198
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
199
|
+
|
|
200
|
+
let encrypted = cipher.update(plaintext, "utf8", "base64");
|
|
201
|
+
encrypted += cipher.final("base64");
|
|
202
|
+
const authTag = cipher.getAuthTag();
|
|
203
|
+
|
|
204
|
+
return `${PREFIX_LOCAL}${iv.toString("base64")}:${authTag.toString("base64")}:${encrypted}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function decryptLocal(encrypted: string): string {
|
|
208
|
+
const key = getLocalMasterKey();
|
|
209
|
+
const parts = encrypted.slice(PREFIX_LOCAL.length).split(":");
|
|
210
|
+
|
|
211
|
+
if (parts.length !== 3) {
|
|
212
|
+
throw new Error("Invalid local encrypted format");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const iv = Buffer.from(parts[0]!, "base64");
|
|
216
|
+
const authTag = Buffer.from(parts[1]!, "base64");
|
|
217
|
+
const ciphertext = parts[2]!;
|
|
218
|
+
|
|
219
|
+
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
|
220
|
+
decipher.setAuthTag(authTag);
|
|
221
|
+
|
|
222
|
+
let decrypted: string = decipher.update(ciphertext, "base64", "utf8");
|
|
223
|
+
decrypted += decipher.final("utf8");
|
|
224
|
+
|
|
225
|
+
return decrypted;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ============================================================================
|
|
229
|
+
// Public API
|
|
230
|
+
// ============================================================================
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Check if a value is encrypted
|
|
234
|
+
*/
|
|
235
|
+
export function isEncrypted(value: string): boolean {
|
|
236
|
+
return value?.startsWith(PREFIX_LOCAL) || value?.startsWith(PREFIX_KMS) || false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Encrypt sensitive data using configured backend
|
|
241
|
+
*/
|
|
242
|
+
export async function encrypt(plaintext: string): Promise<string> {
|
|
243
|
+
if (!plaintext || isEncrypted(plaintext)) {
|
|
244
|
+
return plaintext;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const backend = getBackend();
|
|
248
|
+
|
|
249
|
+
if (backend === "aws-kms") {
|
|
250
|
+
return encryptWithKMS(plaintext);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return encryptLocal(plaintext);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Decrypt sensitive data (auto-detects backend from prefix)
|
|
258
|
+
*/
|
|
259
|
+
export async function decrypt(encrypted: string): Promise<string> {
|
|
260
|
+
if (!encrypted || !isEncrypted(encrypted)) {
|
|
261
|
+
return encrypted;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (encrypted.startsWith(PREFIX_KMS)) {
|
|
265
|
+
return decryptWithKMS(encrypted);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (encrypted.startsWith(PREFIX_LOCAL)) {
|
|
269
|
+
return decryptLocal(encrypted);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
throw new Error("Unknown encryption format");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Get current crypto backend info
|
|
277
|
+
*/
|
|
278
|
+
export function getCryptoInfo(): { backend: CryptoBackend; kmsKeyId?: string } {
|
|
279
|
+
const backend = getBackend();
|
|
280
|
+
return {
|
|
281
|
+
backend,
|
|
282
|
+
kmsKeyId: backend === "aws-kms" ? process.env.AWS_KMS_KEY_ID : undefined,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CVV-specific encryption using PIN-derived key
|
|
3
|
+
*
|
|
4
|
+
* CVV is encrypted with a key derived from the master PIN.
|
|
5
|
+
* This means:
|
|
6
|
+
* - CVV is stored encrypted
|
|
7
|
+
* - Without the PIN, CVV cannot be decrypted
|
|
8
|
+
* - Even with system access, attacker needs the PIN
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
12
|
+
import { getCvvEncryptionKey } from "./pin-manager";
|
|
13
|
+
|
|
14
|
+
const ALGORITHM = "aes-256-gcm";
|
|
15
|
+
const IV_LENGTH = 16;
|
|
16
|
+
|
|
17
|
+
const CVV_PREFIX = "cvv:";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Check if value is CVV-encrypted
|
|
21
|
+
*/
|
|
22
|
+
export function isCvvEncrypted(value: string): boolean {
|
|
23
|
+
return value?.startsWith(CVV_PREFIX) || false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Encrypt CVV with PIN-derived key
|
|
28
|
+
* Requires cards to be unlocked
|
|
29
|
+
*/
|
|
30
|
+
export function encryptCvv(cvv: string): string | null {
|
|
31
|
+
const key = getCvvEncryptionKey();
|
|
32
|
+
if (!key) {
|
|
33
|
+
return null; // Not unlocked
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const iv = randomBytes(IV_LENGTH);
|
|
37
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
38
|
+
|
|
39
|
+
let encrypted = cipher.update(cvv, "utf8", "base64");
|
|
40
|
+
encrypted += cipher.final("base64");
|
|
41
|
+
const authTag = cipher.getAuthTag();
|
|
42
|
+
|
|
43
|
+
// Format: cvv:<iv>:<authTag>:<ciphertext>
|
|
44
|
+
return `${CVV_PREFIX}${iv.toString("base64")}:${authTag.toString("base64")}:${encrypted}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Decrypt CVV with PIN-derived key
|
|
49
|
+
* Requires cards to be unlocked
|
|
50
|
+
*/
|
|
51
|
+
export function decryptCvv(encrypted: string): string | null {
|
|
52
|
+
if (!isCvvEncrypted(encrypted)) {
|
|
53
|
+
return encrypted; // Not encrypted, return as-is
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const key = getCvvEncryptionKey();
|
|
57
|
+
if (!key) {
|
|
58
|
+
return null; // Not unlocked
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const parts = encrypted.slice(CVV_PREFIX.length).split(":");
|
|
62
|
+
if (parts.length !== 3) {
|
|
63
|
+
return null; // Invalid format
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const iv = Buffer.from(parts[0]!, "base64");
|
|
68
|
+
const authTag = Buffer.from(parts[1]!, "base64");
|
|
69
|
+
const ciphertext = parts[2]!;
|
|
70
|
+
|
|
71
|
+
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
|
72
|
+
decipher.setAuthTag(authTag);
|
|
73
|
+
|
|
74
|
+
let decrypted = decipher.update(ciphertext, "base64", "utf8");
|
|
75
|
+
decrypted += decipher.final("utf8");
|
|
76
|
+
|
|
77
|
+
return decrypted;
|
|
78
|
+
} catch {
|
|
79
|
+
return null; // Decryption failed (wrong PIN or corrupted)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Core - Shared utilities for all MCP servers
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
6
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
+
import {
|
|
8
|
+
CallToolRequestSchema,
|
|
9
|
+
ListToolsRequestSchema,
|
|
10
|
+
type Tool,
|
|
11
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
12
|
+
|
|
13
|
+
export interface MCPServerConfig {
|
|
14
|
+
name: string;
|
|
15
|
+
version: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ToolDefinition {
|
|
20
|
+
name: string;
|
|
21
|
+
description: string;
|
|
22
|
+
inputSchema: Record<string, unknown>;
|
|
23
|
+
handler: (args: Record<string, unknown>) => Promise<ToolResult>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ToolResult {
|
|
27
|
+
content: Array<{ type: "text"; text: string }>;
|
|
28
|
+
isError?: boolean;
|
|
29
|
+
[key: string]: unknown;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create and configure an MCP server with tools
|
|
34
|
+
*/
|
|
35
|
+
export function createMCPServer(
|
|
36
|
+
config: MCPServerConfig,
|
|
37
|
+
tools: ToolDefinition[]
|
|
38
|
+
): Server {
|
|
39
|
+
const server = new Server(
|
|
40
|
+
{
|
|
41
|
+
name: config.name,
|
|
42
|
+
version: config.version,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
capabilities: {
|
|
46
|
+
tools: {},
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Register tool list handler
|
|
52
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
53
|
+
tools: tools.map((t) => ({
|
|
54
|
+
name: t.name,
|
|
55
|
+
description: t.description,
|
|
56
|
+
inputSchema: t.inputSchema,
|
|
57
|
+
})),
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
// Register tool call handler
|
|
61
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
62
|
+
const toolName = request.params.name;
|
|
63
|
+
const tool = tools.find((t) => t.name === toolName);
|
|
64
|
+
|
|
65
|
+
if (!tool) {
|
|
66
|
+
return {
|
|
67
|
+
content: [{ type: "text" as const, text: `Unknown tool: ${toolName}` }],
|
|
68
|
+
isError: true,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const args = request.params.arguments ?? {};
|
|
74
|
+
return await tool.handler(args as Record<string, unknown>);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
77
|
+
return {
|
|
78
|
+
content: [{ type: "text" as const, text: `Error: ${message}` }],
|
|
79
|
+
isError: true,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return server;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Start MCP server with stdio transport
|
|
89
|
+
*/
|
|
90
|
+
export async function startMCPServer(server: Server): Promise<void> {
|
|
91
|
+
const transport = new StdioServerTransport();
|
|
92
|
+
await server.connect(transport);
|
|
93
|
+
console.error(`[MCP] Server started`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Helper to create a successful tool result
|
|
98
|
+
*/
|
|
99
|
+
export function success(text: string): ToolResult {
|
|
100
|
+
return {
|
|
101
|
+
content: [{ type: "text", text }],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Helper to create a JSON tool result
|
|
107
|
+
*/
|
|
108
|
+
export function json(data: unknown): ToolResult {
|
|
109
|
+
return {
|
|
110
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Helper to create an error tool result
|
|
116
|
+
*/
|
|
117
|
+
export function error(message: string): ToolResult {
|
|
118
|
+
return {
|
|
119
|
+
content: [{ type: "text", text: message }],
|
|
120
|
+
isError: true,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export { Server, StdioServerTransport };
|
|
125
|
+
|
|
126
|
+
// CLI execution utilities
|
|
127
|
+
export { execCommand, execGit, execHeroku, parseJsonOutput, type ExecResult, type ExecOptions } from "./cli-runner";
|