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.
Files changed (88) hide show
  1. package/.env.example +23 -0
  2. package/.github/workflows/ci.yml +26 -0
  3. package/.github/workflows/release.yml +82 -0
  4. package/LICENSE +21 -0
  5. package/README.md +362 -0
  6. package/dist/index.d.ts +31 -0
  7. package/dist/index.js +122 -0
  8. package/dist/lib/blockchain.d.ts +50 -0
  9. package/dist/lib/blockchain.js +287 -0
  10. package/dist/lib/cards.d.ts +83 -0
  11. package/dist/lib/cards.js +276 -0
  12. package/dist/lib/cli-runner.d.ts +31 -0
  13. package/dist/lib/cli-runner.js +77 -0
  14. package/dist/lib/crypto.d.ts +39 -0
  15. package/dist/lib/crypto.js +228 -0
  16. package/dist/lib/cvv-crypto.d.ts +23 -0
  17. package/dist/lib/cvv-crypto.js +67 -0
  18. package/dist/lib/mcp-core.d.ts +46 -0
  19. package/dist/lib/mcp-core.js +86 -0
  20. package/dist/lib/pin-manager.d.ts +69 -0
  21. package/dist/lib/pin-manager.js +199 -0
  22. package/dist/lib/wallets.d.ts +91 -0
  23. package/dist/lib/wallets.js +227 -0
  24. package/dist/tools/add-card.d.ts +65 -0
  25. package/dist/tools/add-card.js +97 -0
  26. package/dist/tools/add-wallet.d.ts +65 -0
  27. package/dist/tools/add-wallet.js +104 -0
  28. package/dist/tools/card-status.d.ts +20 -0
  29. package/dist/tools/card-status.js +26 -0
  30. package/dist/tools/confirm-payment.d.ts +44 -0
  31. package/dist/tools/confirm-payment.js +88 -0
  32. package/dist/tools/get-total-balance.d.ts +41 -0
  33. package/dist/tools/get-total-balance.js +98 -0
  34. package/dist/tools/get-transactions.d.ts +39 -0
  35. package/dist/tools/get-transactions.js +40 -0
  36. package/dist/tools/get-wallet-balance.d.ts +43 -0
  37. package/dist/tools/get-wallet-balance.js +69 -0
  38. package/dist/tools/list-cards.d.ts +36 -0
  39. package/dist/tools/list-cards.js +39 -0
  40. package/dist/tools/list-wallet-transactions.d.ts +63 -0
  41. package/dist/tools/list-wallet-transactions.js +76 -0
  42. package/dist/tools/list-wallets.d.ts +41 -0
  43. package/dist/tools/list-wallets.js +50 -0
  44. package/dist/tools/lock-cards.d.ts +16 -0
  45. package/dist/tools/lock-cards.js +23 -0
  46. package/dist/tools/prepare-crypto-tx.d.ts +69 -0
  47. package/dist/tools/prepare-crypto-tx.js +93 -0
  48. package/dist/tools/prepare-payment.d.ts +57 -0
  49. package/dist/tools/prepare-payment.js +93 -0
  50. package/dist/tools/remove-card.d.ts +25 -0
  51. package/dist/tools/remove-card.js +39 -0
  52. package/dist/tools/remove-wallet.d.ts +27 -0
  53. package/dist/tools/remove-wallet.js +40 -0
  54. package/dist/tools/setup-pin.d.ts +26 -0
  55. package/dist/tools/setup-pin.js +33 -0
  56. package/dist/tools/sign-crypto-tx.d.ts +42 -0
  57. package/dist/tools/sign-crypto-tx.js +75 -0
  58. package/dist/tools/unlock-cards.d.ts +35 -0
  59. package/dist/tools/unlock-cards.js +41 -0
  60. package/package.json +50 -0
  61. package/src/index.ts +139 -0
  62. package/src/lib/blockchain.ts +375 -0
  63. package/src/lib/cards.ts +372 -0
  64. package/src/lib/cli-runner.ts +113 -0
  65. package/src/lib/crypto.ts +284 -0
  66. package/src/lib/cvv-crypto.ts +81 -0
  67. package/src/lib/mcp-core.ts +127 -0
  68. package/src/lib/pin-manager.ts +252 -0
  69. package/src/lib/wallets.ts +331 -0
  70. package/src/tools/add-card.ts +108 -0
  71. package/src/tools/add-wallet.ts +114 -0
  72. package/src/tools/card-status.ts +32 -0
  73. package/src/tools/confirm-payment.ts +103 -0
  74. package/src/tools/get-total-balance.ts +123 -0
  75. package/src/tools/get-transactions.ts +49 -0
  76. package/src/tools/get-wallet-balance.ts +75 -0
  77. package/src/tools/list-cards.ts +52 -0
  78. package/src/tools/list-wallet-transactions.ts +83 -0
  79. package/src/tools/list-wallets.ts +63 -0
  80. package/src/tools/lock-cards.ts +31 -0
  81. package/src/tools/prepare-crypto-tx.ts +108 -0
  82. package/src/tools/prepare-payment.ts +108 -0
  83. package/src/tools/remove-card.ts +46 -0
  84. package/src/tools/remove-wallet.ts +47 -0
  85. package/src/tools/setup-pin.ts +39 -0
  86. package/src/tools/sign-crypto-tx.ts +90 -0
  87. package/src/tools/unlock-cards.ts +48 -0
  88. package/tsconfig.json +19 -0
@@ -0,0 +1,31 @@
1
+ /**
2
+ * CLI Runner - Execute shell commands with secure credential injection
3
+ */
4
+ export interface ExecResult {
5
+ stdout: string;
6
+ stderr: string;
7
+ exitCode: number;
8
+ success: boolean;
9
+ }
10
+ export interface ExecOptions {
11
+ cwd?: string;
12
+ env?: Record<string, string>;
13
+ timeout?: number;
14
+ stdin?: string;
15
+ }
16
+ /**
17
+ * Execute a CLI command and return the result
18
+ */
19
+ export declare function execCommand(command: string, args: string[], options?: ExecOptions): Promise<ExecResult>;
20
+ /**
21
+ * Execute a git command
22
+ */
23
+ export declare function execGit(args: string[], options?: ExecOptions): Promise<ExecResult>;
24
+ /**
25
+ * Execute a heroku command with API key
26
+ */
27
+ export declare function execHeroku(args: string[], apiKey: string, options?: ExecOptions): Promise<ExecResult>;
28
+ /**
29
+ * Parse JSON output safely
30
+ */
31
+ export declare function parseJsonOutput<T>(output: string): T | null;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * CLI Runner - Execute shell commands with secure credential injection
3
+ */
4
+ import { spawn } from "child_process";
5
+ /**
6
+ * Execute a CLI command and return the result
7
+ */
8
+ export async function execCommand(command, args, options = {}) {
9
+ return new Promise((resolve) => {
10
+ const env = {
11
+ ...process.env,
12
+ ...options.env,
13
+ };
14
+ const proc = spawn(command, args, {
15
+ cwd: options.cwd,
16
+ env,
17
+ timeout: options.timeout ?? 60000,
18
+ });
19
+ let stdout = "";
20
+ let stderr = "";
21
+ proc.stdout.on("data", (data) => {
22
+ stdout += data.toString();
23
+ });
24
+ proc.stderr.on("data", (data) => {
25
+ stderr += data.toString();
26
+ });
27
+ if (options.stdin) {
28
+ proc.stdin.write(options.stdin);
29
+ proc.stdin.end();
30
+ }
31
+ proc.on("close", (code) => {
32
+ resolve({
33
+ stdout: stdout.trim(),
34
+ stderr: stderr.trim(),
35
+ exitCode: code ?? 0,
36
+ success: code === 0,
37
+ });
38
+ });
39
+ proc.on("error", (err) => {
40
+ resolve({
41
+ stdout: "",
42
+ stderr: err.message,
43
+ exitCode: 1,
44
+ success: false,
45
+ });
46
+ });
47
+ });
48
+ }
49
+ /**
50
+ * Execute a git command
51
+ */
52
+ export async function execGit(args, options = {}) {
53
+ return execCommand("git", args, options);
54
+ }
55
+ /**
56
+ * Execute a heroku command with API key
57
+ */
58
+ export async function execHeroku(args, apiKey, options = {}) {
59
+ return execCommand("heroku", args, {
60
+ ...options,
61
+ env: {
62
+ ...options.env,
63
+ HEROKU_API_KEY: apiKey,
64
+ },
65
+ });
66
+ }
67
+ /**
68
+ * Parse JSON output safely
69
+ */
70
+ export function parseJsonOutput(output) {
71
+ try {
72
+ return JSON.parse(output);
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ }
@@ -0,0 +1,39 @@
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
+ type CryptoBackend = "local" | "aws-kms";
20
+ /**
21
+ * Check if a value is encrypted
22
+ */
23
+ export declare function isEncrypted(value: string): boolean;
24
+ /**
25
+ * Encrypt sensitive data using configured backend
26
+ */
27
+ export declare function encrypt(plaintext: string): Promise<string>;
28
+ /**
29
+ * Decrypt sensitive data (auto-detects backend from prefix)
30
+ */
31
+ export declare function decrypt(encrypted: string): Promise<string>;
32
+ /**
33
+ * Get current crypto backend info
34
+ */
35
+ export declare function getCryptoInfo(): {
36
+ backend: CryptoBackend;
37
+ kmsKeyId?: string;
38
+ };
39
+ export {};
@@ -0,0 +1,228 @@
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
+ import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
20
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from "fs";
21
+ import { homedir } from "os";
22
+ import { join } from "path";
23
+ import { KMSClient, DecryptCommand, GenerateDataKeyCommand } from "@aws-sdk/client-kms";
24
+ // ============================================================================
25
+ // Configuration
26
+ // ============================================================================
27
+ const ALGORITHM = "aes-256-gcm";
28
+ const KEY_LENGTH = 32;
29
+ const IV_LENGTH = 16;
30
+ const AUTH_TAG_LENGTH = 16;
31
+ const CONFIG_DIR = join(homedir(), ".mcp-ecosystem");
32
+ const KEY_FILE = join(CONFIG_DIR, ".master-key");
33
+ const DEK_CACHE_FILE = join(CONFIG_DIR, ".dek-cache");
34
+ // Prefixes to identify encryption version/backend
35
+ const PREFIX_LOCAL = "enc:local:";
36
+ const PREFIX_KMS = "enc:kms:";
37
+ function getBackend() {
38
+ const backend = process.env.MCP_CRYPTO_BACKEND?.toLowerCase();
39
+ if (backend === "aws-kms" || backend === "kms")
40
+ return "aws-kms";
41
+ return "local";
42
+ }
43
+ // ============================================================================
44
+ // AWS KMS Backend
45
+ // ============================================================================
46
+ let kmsClient = null;
47
+ let cachedDataKey = null;
48
+ let cachedEncryptedDataKey = null;
49
+ function getKMSClient() {
50
+ if (!kmsClient) {
51
+ kmsClient = new KMSClient({
52
+ region: process.env.AWS_REGION || "us-east-1",
53
+ });
54
+ }
55
+ return kmsClient;
56
+ }
57
+ function getKMSKeyId() {
58
+ const keyId = process.env.AWS_KMS_KEY_ID;
59
+ if (!keyId) {
60
+ throw new Error("AWS_KMS_KEY_ID environment variable required for KMS backend");
61
+ }
62
+ return keyId;
63
+ }
64
+ /**
65
+ * Generate or retrieve Data Encryption Key (DEK) from KMS
66
+ * Uses envelope encryption pattern
67
+ */
68
+ async function getDataKey() {
69
+ // Check memory cache first
70
+ if (cachedDataKey && cachedEncryptedDataKey) {
71
+ return { plaintext: cachedDataKey, encrypted: cachedEncryptedDataKey };
72
+ }
73
+ // Check disk cache (encrypted DEK)
74
+ if (existsSync(DEK_CACHE_FILE)) {
75
+ try {
76
+ const encryptedDek = readFileSync(DEK_CACHE_FILE);
77
+ const client = getKMSClient();
78
+ const response = await client.send(new DecryptCommand({
79
+ CiphertextBlob: encryptedDek,
80
+ KeyId: getKMSKeyId(),
81
+ }));
82
+ if (response.Plaintext) {
83
+ cachedDataKey = Buffer.from(response.Plaintext);
84
+ cachedEncryptedDataKey = encryptedDek;
85
+ return { plaintext: cachedDataKey, encrypted: cachedEncryptedDataKey };
86
+ }
87
+ }
88
+ catch (err) {
89
+ console.error("[crypto] Failed to decrypt cached DEK, generating new one");
90
+ }
91
+ }
92
+ // Generate new DEK
93
+ const client = getKMSClient();
94
+ const response = await client.send(new GenerateDataKeyCommand({
95
+ KeyId: getKMSKeyId(),
96
+ KeySpec: "AES_256",
97
+ }));
98
+ if (!response.Plaintext || !response.CiphertextBlob) {
99
+ throw new Error("KMS GenerateDataKey failed");
100
+ }
101
+ cachedDataKey = Buffer.from(response.Plaintext);
102
+ cachedEncryptedDataKey = Buffer.from(response.CiphertextBlob);
103
+ // Cache encrypted DEK to disk (safe - it's encrypted by KMS)
104
+ ensureConfigDir();
105
+ writeFileSync(DEK_CACHE_FILE, cachedEncryptedDataKey, { mode: 0o600 });
106
+ console.error("[crypto] Generated new Data Encryption Key via KMS");
107
+ return { plaintext: cachedDataKey, encrypted: cachedEncryptedDataKey };
108
+ }
109
+ async function encryptWithKMS(plaintext) {
110
+ const { plaintext: dek } = await getDataKey();
111
+ const iv = randomBytes(IV_LENGTH);
112
+ const cipher = createCipheriv(ALGORITHM, dek, iv);
113
+ let encrypted = cipher.update(plaintext, "utf8", "base64");
114
+ encrypted += cipher.final("base64");
115
+ const authTag = cipher.getAuthTag();
116
+ // Format: enc:kms:<iv>:<authTag>:<ciphertext>
117
+ return `${PREFIX_KMS}${iv.toString("base64")}:${authTag.toString("base64")}:${encrypted}`;
118
+ }
119
+ async function decryptWithKMS(encrypted) {
120
+ const { plaintext: dek } = await getDataKey();
121
+ const parts = encrypted.slice(PREFIX_KMS.length).split(":");
122
+ if (parts.length !== 3) {
123
+ throw new Error("Invalid KMS encrypted format");
124
+ }
125
+ const iv = Buffer.from(parts[0], "base64");
126
+ const authTag = Buffer.from(parts[1], "base64");
127
+ const ciphertext = parts[2];
128
+ const decipher = createDecipheriv(ALGORITHM, dek, iv);
129
+ decipher.setAuthTag(authTag);
130
+ let decrypted = decipher.update(ciphertext, "base64", "utf8");
131
+ decrypted += decipher.final("utf8");
132
+ return decrypted;
133
+ }
134
+ // ============================================================================
135
+ // Local Backend (for development)
136
+ // ============================================================================
137
+ function ensureConfigDir() {
138
+ if (!existsSync(CONFIG_DIR)) {
139
+ mkdirSync(CONFIG_DIR, { recursive: true });
140
+ }
141
+ }
142
+ function getLocalMasterKey() {
143
+ if (process.env.MCP_MASTER_KEY) {
144
+ return scryptSync(process.env.MCP_MASTER_KEY, "mcp-ecosystem-salt-v1", KEY_LENGTH);
145
+ }
146
+ if (existsSync(KEY_FILE)) {
147
+ const keyData = readFileSync(KEY_FILE, "utf-8").trim();
148
+ return scryptSync(keyData, "mcp-ecosystem-salt-v1", KEY_LENGTH);
149
+ }
150
+ console.error("[crypto] Generating new local master key...");
151
+ const newKey = randomBytes(32).toString("base64");
152
+ ensureConfigDir();
153
+ writeFileSync(KEY_FILE, newKey, { mode: 0o600 });
154
+ chmodSync(KEY_FILE, 0o600);
155
+ console.error(`[crypto] Master key saved to ${KEY_FILE}`);
156
+ return scryptSync(newKey, "mcp-ecosystem-salt-v1", KEY_LENGTH);
157
+ }
158
+ function encryptLocal(plaintext) {
159
+ const key = getLocalMasterKey();
160
+ const iv = randomBytes(IV_LENGTH);
161
+ const cipher = createCipheriv(ALGORITHM, key, iv);
162
+ let encrypted = cipher.update(plaintext, "utf8", "base64");
163
+ encrypted += cipher.final("base64");
164
+ const authTag = cipher.getAuthTag();
165
+ return `${PREFIX_LOCAL}${iv.toString("base64")}:${authTag.toString("base64")}:${encrypted}`;
166
+ }
167
+ function decryptLocal(encrypted) {
168
+ const key = getLocalMasterKey();
169
+ const parts = encrypted.slice(PREFIX_LOCAL.length).split(":");
170
+ if (parts.length !== 3) {
171
+ throw new Error("Invalid local encrypted format");
172
+ }
173
+ const iv = Buffer.from(parts[0], "base64");
174
+ const authTag = Buffer.from(parts[1], "base64");
175
+ const ciphertext = parts[2];
176
+ const decipher = createDecipheriv(ALGORITHM, key, iv);
177
+ decipher.setAuthTag(authTag);
178
+ let decrypted = decipher.update(ciphertext, "base64", "utf8");
179
+ decrypted += decipher.final("utf8");
180
+ return decrypted;
181
+ }
182
+ // ============================================================================
183
+ // Public API
184
+ // ============================================================================
185
+ /**
186
+ * Check if a value is encrypted
187
+ */
188
+ export function isEncrypted(value) {
189
+ return value?.startsWith(PREFIX_LOCAL) || value?.startsWith(PREFIX_KMS) || false;
190
+ }
191
+ /**
192
+ * Encrypt sensitive data using configured backend
193
+ */
194
+ export async function encrypt(plaintext) {
195
+ if (!plaintext || isEncrypted(plaintext)) {
196
+ return plaintext;
197
+ }
198
+ const backend = getBackend();
199
+ if (backend === "aws-kms") {
200
+ return encryptWithKMS(plaintext);
201
+ }
202
+ return encryptLocal(plaintext);
203
+ }
204
+ /**
205
+ * Decrypt sensitive data (auto-detects backend from prefix)
206
+ */
207
+ export async function decrypt(encrypted) {
208
+ if (!encrypted || !isEncrypted(encrypted)) {
209
+ return encrypted;
210
+ }
211
+ if (encrypted.startsWith(PREFIX_KMS)) {
212
+ return decryptWithKMS(encrypted);
213
+ }
214
+ if (encrypted.startsWith(PREFIX_LOCAL)) {
215
+ return decryptLocal(encrypted);
216
+ }
217
+ throw new Error("Unknown encryption format");
218
+ }
219
+ /**
220
+ * Get current crypto backend info
221
+ */
222
+ export function getCryptoInfo() {
223
+ const backend = getBackend();
224
+ return {
225
+ backend,
226
+ kmsKeyId: backend === "aws-kms" ? process.env.AWS_KMS_KEY_ID : undefined,
227
+ };
228
+ }
@@ -0,0 +1,23 @@
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
+ * Check if value is CVV-encrypted
12
+ */
13
+ export declare function isCvvEncrypted(value: string): boolean;
14
+ /**
15
+ * Encrypt CVV with PIN-derived key
16
+ * Requires cards to be unlocked
17
+ */
18
+ export declare function encryptCvv(cvv: string): string | null;
19
+ /**
20
+ * Decrypt CVV with PIN-derived key
21
+ * Requires cards to be unlocked
22
+ */
23
+ export declare function decryptCvv(encrypted: string): string | null;
@@ -0,0 +1,67 @@
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
+ import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
11
+ import { getCvvEncryptionKey } from "./pin-manager";
12
+ const ALGORITHM = "aes-256-gcm";
13
+ const IV_LENGTH = 16;
14
+ const CVV_PREFIX = "cvv:";
15
+ /**
16
+ * Check if value is CVV-encrypted
17
+ */
18
+ export function isCvvEncrypted(value) {
19
+ return value?.startsWith(CVV_PREFIX) || false;
20
+ }
21
+ /**
22
+ * Encrypt CVV with PIN-derived key
23
+ * Requires cards to be unlocked
24
+ */
25
+ export function encryptCvv(cvv) {
26
+ const key = getCvvEncryptionKey();
27
+ if (!key) {
28
+ return null; // Not unlocked
29
+ }
30
+ const iv = randomBytes(IV_LENGTH);
31
+ const cipher = createCipheriv(ALGORITHM, key, iv);
32
+ let encrypted = cipher.update(cvv, "utf8", "base64");
33
+ encrypted += cipher.final("base64");
34
+ const authTag = cipher.getAuthTag();
35
+ // Format: cvv:<iv>:<authTag>:<ciphertext>
36
+ return `${CVV_PREFIX}${iv.toString("base64")}:${authTag.toString("base64")}:${encrypted}`;
37
+ }
38
+ /**
39
+ * Decrypt CVV with PIN-derived key
40
+ * Requires cards to be unlocked
41
+ */
42
+ export function decryptCvv(encrypted) {
43
+ if (!isCvvEncrypted(encrypted)) {
44
+ return encrypted; // Not encrypted, return as-is
45
+ }
46
+ const key = getCvvEncryptionKey();
47
+ if (!key) {
48
+ return null; // Not unlocked
49
+ }
50
+ const parts = encrypted.slice(CVV_PREFIX.length).split(":");
51
+ if (parts.length !== 3) {
52
+ return null; // Invalid format
53
+ }
54
+ try {
55
+ const iv = Buffer.from(parts[0], "base64");
56
+ const authTag = Buffer.from(parts[1], "base64");
57
+ const ciphertext = parts[2];
58
+ const decipher = createDecipheriv(ALGORITHM, key, iv);
59
+ decipher.setAuthTag(authTag);
60
+ let decrypted = decipher.update(ciphertext, "base64", "utf8");
61
+ decrypted += decipher.final("utf8");
62
+ return decrypted;
63
+ }
64
+ catch {
65
+ return null; // Decryption failed (wrong PIN or corrupted)
66
+ }
67
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * MCP Core - Shared utilities for all MCP servers
3
+ */
4
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ export interface MCPServerConfig {
7
+ name: string;
8
+ version: string;
9
+ description?: string;
10
+ }
11
+ export interface ToolDefinition {
12
+ name: string;
13
+ description: string;
14
+ inputSchema: Record<string, unknown>;
15
+ handler: (args: Record<string, unknown>) => Promise<ToolResult>;
16
+ }
17
+ export interface ToolResult {
18
+ content: Array<{
19
+ type: "text";
20
+ text: string;
21
+ }>;
22
+ isError?: boolean;
23
+ [key: string]: unknown;
24
+ }
25
+ /**
26
+ * Create and configure an MCP server with tools
27
+ */
28
+ export declare function createMCPServer(config: MCPServerConfig, tools: ToolDefinition[]): Server;
29
+ /**
30
+ * Start MCP server with stdio transport
31
+ */
32
+ export declare function startMCPServer(server: Server): Promise<void>;
33
+ /**
34
+ * Helper to create a successful tool result
35
+ */
36
+ export declare function success(text: string): ToolResult;
37
+ /**
38
+ * Helper to create a JSON tool result
39
+ */
40
+ export declare function json(data: unknown): ToolResult;
41
+ /**
42
+ * Helper to create an error tool result
43
+ */
44
+ export declare function error(message: string): ToolResult;
45
+ export { Server, StdioServerTransport };
46
+ export { execCommand, execGit, execHeroku, parseJsonOutput, type ExecResult, type ExecOptions } from "./cli-runner";
@@ -0,0 +1,86 @@
1
+ /**
2
+ * MCP Core - Shared utilities for all MCP servers
3
+ */
4
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
7
+ /**
8
+ * Create and configure an MCP server with tools
9
+ */
10
+ export function createMCPServer(config, tools) {
11
+ const server = new Server({
12
+ name: config.name,
13
+ version: config.version,
14
+ }, {
15
+ capabilities: {
16
+ tools: {},
17
+ },
18
+ });
19
+ // Register tool list handler
20
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
21
+ tools: tools.map((t) => ({
22
+ name: t.name,
23
+ description: t.description,
24
+ inputSchema: t.inputSchema,
25
+ })),
26
+ }));
27
+ // Register tool call handler
28
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29
+ const toolName = request.params.name;
30
+ const tool = tools.find((t) => t.name === toolName);
31
+ if (!tool) {
32
+ return {
33
+ content: [{ type: "text", text: `Unknown tool: ${toolName}` }],
34
+ isError: true,
35
+ };
36
+ }
37
+ try {
38
+ const args = request.params.arguments ?? {};
39
+ return await tool.handler(args);
40
+ }
41
+ catch (error) {
42
+ const message = error instanceof Error ? error.message : String(error);
43
+ return {
44
+ content: [{ type: "text", text: `Error: ${message}` }],
45
+ isError: true,
46
+ };
47
+ }
48
+ });
49
+ return server;
50
+ }
51
+ /**
52
+ * Start MCP server with stdio transport
53
+ */
54
+ export async function startMCPServer(server) {
55
+ const transport = new StdioServerTransport();
56
+ await server.connect(transport);
57
+ console.error(`[MCP] Server started`);
58
+ }
59
+ /**
60
+ * Helper to create a successful tool result
61
+ */
62
+ export function success(text) {
63
+ return {
64
+ content: [{ type: "text", text }],
65
+ };
66
+ }
67
+ /**
68
+ * Helper to create a JSON tool result
69
+ */
70
+ export function json(data) {
71
+ return {
72
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
73
+ };
74
+ }
75
+ /**
76
+ * Helper to create an error tool result
77
+ */
78
+ export function error(message) {
79
+ return {
80
+ content: [{ type: "text", text: message }],
81
+ isError: true,
82
+ };
83
+ }
84
+ export { Server, StdioServerTransport };
85
+ // CLI execution utilities
86
+ export { execCommand, execGit, execHeroku, parseJsonOutput } from "./cli-runner";
@@ -0,0 +1,69 @@
1
+ /**
2
+ * PIN Manager - Master PIN for card access
3
+ *
4
+ * Security model:
5
+ * - CVV is encrypted with a key derived from the master PIN
6
+ * - PIN is stored in memory only (never persisted)
7
+ * - PIN auto-expires after inactivity timeout
8
+ * - Without PIN, cards cannot be used for payments
9
+ *
10
+ * Flow:
11
+ * 1. User sets PIN once (stored as hash for verification)
12
+ * 2. User unlocks with PIN → PIN held in memory
13
+ * 3. Payments work while unlocked
14
+ * 4. After timeout or explicit lock → PIN cleared from memory
15
+ */
16
+ /**
17
+ * Derive encryption key from PIN (for CVV encryption)
18
+ */
19
+ export declare function deriveKeyFromPin(pin: string, salt: string): Buffer;
20
+ /**
21
+ * Check if PIN is configured
22
+ */
23
+ export declare function isPinConfigured(): boolean;
24
+ /**
25
+ * Set up master PIN (first time)
26
+ */
27
+ export declare function setupPin(pin: string): {
28
+ success: boolean;
29
+ error?: string;
30
+ };
31
+ /**
32
+ * Change master PIN (requires current PIN)
33
+ */
34
+ export declare function changePin(currentPinInput: string, newPin: string): {
35
+ success: boolean;
36
+ error?: string;
37
+ };
38
+ /**
39
+ * Unlock cards with PIN
40
+ */
41
+ export declare function unlock(pin: string): {
42
+ success: boolean;
43
+ error?: string;
44
+ expiresIn?: number;
45
+ };
46
+ /**
47
+ * Lock cards (clear PIN from memory)
48
+ */
49
+ export declare function lock(): void;
50
+ /**
51
+ * Check if cards are currently unlocked
52
+ */
53
+ export declare function isUnlocked(): boolean;
54
+ /**
55
+ * Refresh activity (extend session)
56
+ */
57
+ export declare function refreshActivity(): void;
58
+ /**
59
+ * Get CVV encryption key (only works when unlocked)
60
+ */
61
+ export declare function getCvvEncryptionKey(): Buffer | null;
62
+ /**
63
+ * Get status
64
+ */
65
+ export declare function getStatus(): {
66
+ configured: boolean;
67
+ unlocked: boolean;
68
+ remainingMinutes?: number;
69
+ };