@proofofprotocol/inscribe-mcp 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proofofprotocol/inscribe-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Verifiable inscription for AI agents - inscribe anything to blockchain with Hedera HCS",
5
5
  "type": "module",
6
6
  "main": "src/server.js",
@@ -6,8 +6,11 @@
6
6
 
7
7
  import { Command } from 'commander';
8
8
  import { createInterface } from 'readline';
9
+ import { PrivateKey } from '@hashgraph/sdk';
9
10
  import { writeConfig, configExists, getConfigPath } from '../lib/config.js';
10
11
  import { EXIT_CODES } from '../lib/exit-codes.js';
12
+ import { exec } from 'child_process';
13
+ import { platform } from 'os';
11
14
 
12
15
  // Simple prompt helper (no external dependency)
13
16
  function prompt(question) {
@@ -34,6 +37,57 @@ const colors = {
34
37
  dim: (s) => `\x1b[2m${s}\x1b[0m`
35
38
  };
36
39
 
40
+ // Open URL in browser
41
+ function openBrowser(url) {
42
+ const os = platform();
43
+ let cmd;
44
+ if (os === 'darwin') {
45
+ cmd = `open "${url}"`;
46
+ } else if (os === 'win32') {
47
+ cmd = `start "" "${url}"`;
48
+ } else {
49
+ cmd = `xdg-open "${url}"`;
50
+ }
51
+ exec(cmd, () => {});
52
+ }
53
+
54
+ // Generate ECDSA key pair and EVM address
55
+ function generateKeyPair() {
56
+ const privateKey = PrivateKey.generateECDSA();
57
+ const publicKey = privateKey.publicKey;
58
+
59
+ // Get EVM address from public key
60
+ const evmAddress = publicKey.toEvmAddress();
61
+
62
+ return {
63
+ privateKey: privateKey.toStringDer(),
64
+ privateKeyRaw: privateKey.toStringRaw(),
65
+ publicKey: publicKey.toStringDer(),
66
+ evmAddress: `0x${evmAddress}`
67
+ };
68
+ }
69
+
70
+ // Fetch account ID from EVM address via Mirror Node
71
+ async function fetchAccountIdFromEvmAddress(evmAddress, network) {
72
+ const mirrorUrl = network === 'mainnet'
73
+ ? 'https://mainnet.mirrornode.hedera.com'
74
+ : 'https://testnet.mirrornode.hedera.com';
75
+
76
+ // Remove 0x prefix if present
77
+ const address = evmAddress.startsWith('0x') ? evmAddress.slice(2) : evmAddress;
78
+
79
+ try {
80
+ const response = await fetch(`${mirrorUrl}/api/v1/accounts/${address}`);
81
+ if (response.ok) {
82
+ const data = await response.json();
83
+ return data.account;
84
+ }
85
+ } catch {
86
+ // Ignore errors
87
+ }
88
+ return null;
89
+ }
90
+
37
91
  export const initCommand = new Command('init')
38
92
  .description('Initialize inscribe-mcp configuration')
39
93
  .option('-f, --force', 'Overwrite existing configuration')
@@ -90,53 +144,162 @@ export const initCommand = new Command('init')
90
144
  console.log(colors.bold('2. Hedera Account'));
91
145
  console.log('');
92
146
 
147
+ let accountId;
148
+ let privateKey;
149
+
93
150
  if (network === 'testnet') {
94
- console.log(colors.cyan('Testnet Faucet:'));
95
- console.log(' https://portal.hedera.com/faucet');
151
+ // Testnet: offer new or existing account
152
+ console.log(' 1) ' + colors.cyan('Create new account') + colors.dim(' (generate key + faucet)'));
153
+ console.log(' 2) ' + colors.dim('Use existing account'));
96
154
  console.log('');
97
- console.log('Create an account and get free HBAR for testing.');
155
+
156
+ let accountChoice;
157
+ while (!accountChoice) {
158
+ const choice = await prompt('Select (1 or 2): ');
159
+ if (choice === '1') accountChoice = 'new';
160
+ else if (choice === '2') accountChoice = 'existing';
161
+ else console.log(colors.red('Invalid selection. Enter 1 or 2.'));
162
+ }
98
163
  console.log('');
164
+
165
+ if (accountChoice === 'new') {
166
+ // Generate new key pair
167
+ console.log(colors.bold('Generating ECDSA key pair...'));
168
+ const keyPair = generateKeyPair();
169
+ console.log('');
170
+ console.log(colors.green('✓') + ' Key pair generated');
171
+ console.log('');
172
+ console.log(colors.bold('Your EVM Address:'));
173
+ console.log(colors.cyan(` ${keyPair.evmAddress}`));
174
+ console.log('');
175
+
176
+ privateKey = keyPair.privateKey;
177
+
178
+ // Open faucet
179
+ console.log(colors.bold('3. Get Free HBAR'));
180
+ console.log('');
181
+ console.log('Opening faucet in browser...');
182
+ console.log(colors.dim(' https://portal.hedera.com/faucet'));
183
+ console.log('');
184
+ openBrowser('https://portal.hedera.com/faucet');
185
+
186
+ console.log('Steps:');
187
+ console.log(' 1. Paste your EVM address above in the faucet');
188
+ console.log(' 2. Click "RECEIVE 100 TESTNET HBAR"');
189
+ console.log(' 3. Wait a few seconds for the account to be created');
190
+ console.log('');
191
+
192
+ await prompt('Press Enter when you have received HBAR...');
193
+ console.log('');
194
+
195
+ // Try to fetch account ID automatically
196
+ console.log('Looking up your Account ID...');
197
+ let retries = 3;
198
+ while (retries > 0 && !accountId) {
199
+ accountId = await fetchAccountIdFromEvmAddress(keyPair.evmAddress, network);
200
+ if (!accountId) {
201
+ retries--;
202
+ if (retries > 0) {
203
+ console.log(colors.dim(' Waiting for account creation...'));
204
+ await new Promise(r => setTimeout(r, 3000));
205
+ }
206
+ }
207
+ }
208
+
209
+ if (accountId) {
210
+ console.log(colors.green('✓') + ` Account ID: ${accountId}`);
211
+ } else {
212
+ console.log(colors.yellow('Could not find Account ID automatically.'));
213
+ console.log('');
214
+ console.log('Check HashScan:');
215
+ console.log(colors.cyan(` https://hashscan.io/testnet/account/${keyPair.evmAddress.slice(2)}`));
216
+ console.log('');
217
+ while (!accountId) {
218
+ const input = await prompt('Enter your Account ID (e.g., 0.0.123456): ');
219
+ if (/^0\.0\.\d+$/.test(input)) {
220
+ accountId = input;
221
+ } else {
222
+ console.log(colors.red('Invalid format. Use 0.0.XXXXXX'));
223
+ }
224
+ }
225
+ }
226
+ console.log('');
227
+
228
+ } else {
229
+ // Existing account
230
+ console.log(colors.cyan('Enter your existing Hedera account details.'));
231
+ console.log('');
232
+
233
+ while (!accountId) {
234
+ const input = await prompt('Account ID (e.g., 0.0.123456): ');
235
+ if (/^0\.0\.\d+$/.test(input)) {
236
+ accountId = input;
237
+ } else {
238
+ console.log(colors.red('Invalid format. Use 0.0.XXXXXX'));
239
+ }
240
+ }
241
+
242
+ console.log(colors.green('✓') + ` Account ID: ${accountId}`);
243
+ console.log('');
244
+
245
+ // Private key input
246
+ console.log(colors.bold('3. Private Key'));
247
+ console.log('');
248
+ console.log(colors.dim('Your private key is stored locally and never transmitted.'));
249
+ console.log('');
250
+
251
+ while (!privateKey) {
252
+ const input = await prompt('Private Key (hex or DER format): ');
253
+ if (input.length >= 32) {
254
+ privateKey = input;
255
+ } else {
256
+ console.log(colors.red('Private key seems too short.'));
257
+ }
258
+ }
259
+
260
+ console.log(colors.green('✓') + ' Private key received');
261
+ console.log('');
262
+ }
263
+
99
264
  } else {
265
+ // Mainnet: always use existing account
100
266
  console.log(colors.cyan('Create Mainnet Account:'));
101
267
  console.log(' HashPack: https://www.hashpack.app/');
102
268
  console.log(' Blade: https://www.bladewallet.io/');
103
269
  console.log('');
104
270
  console.log('Transfer a small amount of HBAR (1-5 HBAR recommended).');
105
271
  console.log('');
106
- }
107
272
 
108
- // Account ID input
109
- let accountId;
110
- while (!accountId) {
111
- const input = await prompt('Account ID (e.g., 0.0.123456): ');
112
- if (/^0\.0\.\d+$/.test(input)) {
113
- accountId = input;
114
- } else {
115
- console.log(colors.red('Invalid format. Use 0.0.XXXXXX'));
273
+ while (!accountId) {
274
+ const input = await prompt('Account ID (e.g., 0.0.123456): ');
275
+ if (/^0\.0\.\d+$/.test(input)) {
276
+ accountId = input;
277
+ } else {
278
+ console.log(colors.red('Invalid format. Use 0.0.XXXXXX'));
279
+ }
116
280
  }
117
- }
118
281
 
119
- console.log(colors.green('✓') + ` Account ID: ${accountId}`);
120
- console.log('');
282
+ console.log(colors.green('✓') + ` Account ID: ${accountId}`);
283
+ console.log('');
121
284
 
122
- // Private key input
123
- console.log(colors.bold('3. Private Key'));
124
- console.log('');
125
- console.log(colors.dim('Your private key is stored locally and never transmitted.'));
126
- console.log('');
285
+ // Private key input
286
+ console.log(colors.bold('3. Private Key'));
287
+ console.log('');
288
+ console.log(colors.dim('Your private key is stored locally and never transmitted.'));
289
+ console.log('');
127
290
 
128
- let privateKey;
129
- while (!privateKey) {
130
- const input = await prompt('Private Key (hex or DER format): ');
131
- if (input.length >= 32) {
132
- privateKey = input;
133
- } else {
134
- console.log(colors.red('Private key seems too short.'));
291
+ while (!privateKey) {
292
+ const input = await prompt('Private Key (hex or DER format): ');
293
+ if (input.length >= 32) {
294
+ privateKey = input;
295
+ } else {
296
+ console.log(colors.red('Private key seems too short.'));
297
+ }
135
298
  }
136
- }
137
299
 
138
- console.log(colors.green('✓') + ' Private key received');
139
- console.log('');
300
+ console.log(colors.green('✓') + ' Private key received');
301
+ console.log('');
302
+ }
140
303
 
141
304
  // Topic ID (optional)
142
305
  console.log(colors.bold('4. Topic ID (optional)'));
@@ -0,0 +1,205 @@
1
+ /**
2
+ * setup-mcp command
3
+ *
4
+ * Configure MCP client to use inscribe-mcp server.
5
+ */
6
+
7
+ import { Command } from 'commander';
8
+ import { createInterface } from 'readline';
9
+ import { existsSync, readFileSync, writeFileSync, copyFileSync } from 'fs';
10
+ import { homedir, platform } from 'os';
11
+ import { join } from 'path';
12
+ import { EXIT_CODES } from '../lib/exit-codes.js';
13
+ import { configExists } from '../lib/config.js';
14
+
15
+ // Simple prompt helper
16
+ function prompt(question) {
17
+ const rl = createInterface({
18
+ input: process.stdin,
19
+ output: process.stdout
20
+ });
21
+
22
+ return new Promise((resolve) => {
23
+ rl.question(question, (answer) => {
24
+ rl.close();
25
+ resolve(answer.trim());
26
+ });
27
+ });
28
+ }
29
+
30
+ // ANSI colors
31
+ const colors = {
32
+ green: (s) => `\x1b[32m${s}\x1b[0m`,
33
+ yellow: (s) => `\x1b[33m${s}\x1b[0m`,
34
+ red: (s) => `\x1b[31m${s}\x1b[0m`,
35
+ cyan: (s) => `\x1b[36m${s}\x1b[0m`,
36
+ bold: (s) => `\x1b[1m${s}\x1b[0m`,
37
+ dim: (s) => `\x1b[2m${s}\x1b[0m`
38
+ };
39
+
40
+ // Get Claude Desktop config path based on OS
41
+ function getClaudeDesktopConfigPath() {
42
+ const os = platform();
43
+ if (os === 'darwin') {
44
+ return join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
45
+ } else if (os === 'win32') {
46
+ return join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json');
47
+ } else {
48
+ // Linux
49
+ return join(homedir(), '.config', 'Claude', 'claude_desktop_config.json');
50
+ }
51
+ }
52
+
53
+ // MCP server config for inscribe
54
+ const INSCRIBE_MCP_CONFIG = {
55
+ command: 'npx',
56
+ args: ['@proofofprotocol/inscribe-mcp-server']
57
+ };
58
+
59
+ // Generate JSON snippet for display
60
+ function generateJsonSnippet() {
61
+ return JSON.stringify({
62
+ mcpServers: {
63
+ inscribe: INSCRIBE_MCP_CONFIG
64
+ }
65
+ }, null, 2);
66
+ }
67
+
68
+ export const setupMcpCommand = new Command('setup-mcp')
69
+ .description('Configure MCP client to use inscribe-mcp server')
70
+ .option('--claude-desktop', 'Configure Claude Desktop automatically')
71
+ .option('--show-json', 'Just show the JSON configuration')
72
+ .action(async (options) => {
73
+ console.log('');
74
+ console.log(colors.bold('inscribe-mcp MCP Setup'));
75
+ console.log('─'.repeat(40));
76
+ console.log('');
77
+
78
+ // Check if inscribe-mcp is configured
79
+ if (!configExists()) {
80
+ console.log(colors.yellow('⚠ inscribe-mcp is not configured yet.'));
81
+ console.log('');
82
+ console.log('Run ' + colors.cyan('inscribe-mcp init') + ' first to set up your Hedera account.');
83
+ console.log('');
84
+ process.exit(EXIT_CODES.ERROR);
85
+ }
86
+
87
+ // If --show-json, just display and exit
88
+ if (options.showJson) {
89
+ console.log('Add this to your MCP client configuration:');
90
+ console.log('');
91
+ console.log(colors.cyan(generateJsonSnippet()));
92
+ console.log('');
93
+ process.exit(EXIT_CODES.SUCCESS);
94
+ }
95
+
96
+ // If --claude-desktop, skip selection
97
+ let clientChoice = options.claudeDesktop ? 'claude' : null;
98
+
99
+ if (!clientChoice) {
100
+ console.log(colors.bold('Select MCP Client'));
101
+ console.log('');
102
+ console.log(' 1) ' + colors.cyan('Claude Desktop') + colors.dim(' (auto-configure)'));
103
+ console.log(' 2) ' + colors.dim('Other MCP client') + colors.dim(' (show JSON)'));
104
+ console.log('');
105
+
106
+ while (!clientChoice) {
107
+ const choice = await prompt('Select (1 or 2): ');
108
+ if (choice === '1') clientChoice = 'claude';
109
+ else if (choice === '2') clientChoice = 'other';
110
+ else console.log(colors.red('Invalid selection. Enter 1 or 2.'));
111
+ }
112
+ console.log('');
113
+ }
114
+
115
+ if (clientChoice === 'other') {
116
+ // Just show JSON
117
+ console.log(colors.bold('MCP Configuration'));
118
+ console.log('');
119
+ console.log('Add this to your MCP client configuration:');
120
+ console.log('');
121
+ console.log(colors.cyan(generateJsonSnippet()));
122
+ console.log('');
123
+ process.exit(EXIT_CODES.SUCCESS);
124
+ }
125
+
126
+ // Claude Desktop configuration
127
+ console.log(colors.bold('Claude Desktop Configuration'));
128
+ console.log('');
129
+
130
+ const configPath = getClaudeDesktopConfigPath();
131
+ console.log('Config file: ' + colors.dim(configPath));
132
+ console.log('');
133
+
134
+ // Check if config file exists
135
+ let existingConfig = { mcpServers: {} };
136
+ let configFileExists = false;
137
+
138
+ if (existsSync(configPath)) {
139
+ configFileExists = true;
140
+ try {
141
+ const content = readFileSync(configPath, 'utf-8');
142
+ existingConfig = JSON.parse(content);
143
+ if (!existingConfig.mcpServers) {
144
+ existingConfig.mcpServers = {};
145
+ }
146
+ } catch (error) {
147
+ console.log(colors.yellow('⚠ Could not parse existing config, will create new one.'));
148
+ existingConfig = { mcpServers: {} };
149
+ }
150
+ }
151
+
152
+ // Check if inscribe is already configured
153
+ if (existingConfig.mcpServers.inscribe) {
154
+ console.log(colors.yellow('inscribe is already configured in Claude Desktop.'));
155
+ console.log('');
156
+ const confirm = await prompt('Overwrite? (yes/no): ');
157
+ if (confirm.toLowerCase() !== 'yes') {
158
+ console.log('Aborted.');
159
+ process.exit(EXIT_CODES.SUCCESS);
160
+ }
161
+ console.log('');
162
+ }
163
+
164
+ // Backup existing config if it exists
165
+ if (configFileExists) {
166
+ const backupPath = configPath + '.backup.' + Date.now();
167
+ try {
168
+ copyFileSync(configPath, backupPath);
169
+ console.log(colors.green('✓') + ' Backup created: ' + colors.dim(backupPath));
170
+ } catch (error) {
171
+ console.log(colors.yellow('⚠ Could not create backup: ' + error.message));
172
+ }
173
+ }
174
+
175
+ // Add inscribe config
176
+ existingConfig.mcpServers.inscribe = INSCRIBE_MCP_CONFIG;
177
+
178
+ // Write new config
179
+ try {
180
+ writeFileSync(configPath, JSON.stringify(existingConfig, null, 2), 'utf-8');
181
+ console.log(colors.green('✓') + ' Configuration updated');
182
+ } catch (error) {
183
+ console.log(colors.red('✗ Failed to write config: ' + error.message));
184
+ console.log('');
185
+ console.log('You may need to create the config file manually:');
186
+ console.log(colors.cyan(configPath));
187
+ console.log('');
188
+ console.log('With content:');
189
+ console.log(colors.cyan(generateJsonSnippet()));
190
+ console.log('');
191
+ process.exit(EXIT_CODES.ERROR);
192
+ }
193
+
194
+ console.log('');
195
+ console.log('─'.repeat(40));
196
+ console.log(colors.green(colors.bold('✓ Setup Complete!')));
197
+ console.log('');
198
+ console.log('Next steps:');
199
+ console.log(' 1. ' + colors.cyan('Restart Claude Desktop'));
200
+ console.log(' 2. Look for "inscribe" in the MCP tools menu');
201
+ console.log(' 3. Try: "このテキストを刻んで: Hello World"');
202
+ console.log('');
203
+
204
+ process.exit(EXIT_CODES.SUCCESS);
205
+ });
package/src/cli/index.js CHANGED
@@ -17,6 +17,7 @@ import { configCommand } from './commands/config.js';
17
17
  import { balanceCommand } from './commands/balance.js';
18
18
  import { logCommand } from './commands/log.js';
19
19
  import { showCommand } from './commands/show.js';
20
+ import { setupMcpCommand } from './commands/setup-mcp.js';
20
21
 
21
22
  const __filename = fileURLToPath(import.meta.url);
22
23
  const __dirname = dirname(__filename);
@@ -35,6 +36,7 @@ program
35
36
 
36
37
  // Register commands
37
38
  program.addCommand(initCommand);
39
+ program.addCommand(setupMcpCommand);
38
40
  program.addCommand(configCommand);
39
41
  program.addCommand(balanceCommand);
40
42
  program.addCommand(logCommand);