@proofofprotocol/inscribe-mcp 0.3.2 → 0.3.3

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 CHANGED
@@ -236,6 +236,72 @@ Account: https://hashscan.io/testnet/account/0.0.7455134
236
236
  Topic: https://hashscan.io/testnet/topic/0.0.7503789
237
237
  ```
238
238
 
239
+ ## Debug Mode (Learning Material)
240
+
241
+ MCPのシーケンスを詳細に追跡できるデバッグモードを搭載。学習教材として活用できます。
242
+
243
+ ### デバッグモードの有効化(CLI推奨)
244
+
245
+ ```bash
246
+ # デバッグモードをON(1時間後に自動OFF)
247
+ inscribe-mcp debug on
248
+
249
+ # 30分間だけON
250
+ inscribe-mcp debug on --time 30
251
+
252
+ # 状態確認
253
+ inscribe-mcp debug
254
+
255
+ # 手動でOFF
256
+ inscribe-mcp debug off
257
+ ```
258
+
259
+ デバッグ設定後、Claude Desktopを再起動してください。
260
+
261
+ ### 環境変数での有効化(上級者向け)
262
+
263
+ ```json
264
+ {
265
+ "mcpServers": {
266
+ "inscribe": {
267
+ "command": "npx",
268
+ "args": ["-y", "-p", "@proofofprotocol/inscribe-mcp", "inscribe-mcp-server"],
269
+ "env": {
270
+ "INSCRIBE_MCP_DEBUG": "1"
271
+ }
272
+ }
273
+ }
274
+ }
275
+ ```
276
+
277
+ ### デバッグ出力の確認
278
+
279
+ ```bash
280
+ # デバッグトレースを表示
281
+ inscribe-mcp show --debug
282
+ ```
283
+
284
+ ### MCPシーケンス図
285
+
286
+ ```
287
+ ┌─────────┐ ┌─────────┐ ┌─────────┐
288
+ │ Agent │ → │ MCP │ → │ Chain │
289
+ │(Claude) │ ← │ Server │ ← │ (Hedera)│
290
+ └─────────┘ └─────────┘ └─────────┘
291
+
292
+ [12:34:56] inscribe
293
+ → Agent → MCP {"content":"Hello World"}
294
+ → MCP → Chain processing...
295
+ ← Chain → MCP SUCCESS txId: 0.0.xxx-123...
296
+ ← MCP → Agent result: success
297
+ ```
298
+
299
+ デバッグモードでは以下の情報が記録されます:
300
+ - `agent_to_mcp`: AIエージェントからMCPサーバーへのリクエスト
301
+ - `mcp_to_chain`: MCPサーバーからHederaへの送信
302
+ - `chain_to_mcp`: Hederaからの応答
303
+ - `mcp_to_agent`: AIエージェントへの結果返却
304
+
239
305
  ## Configuration
240
306
 
241
307
  設定は `~/.inscribe-mcp/config.json` に保存されます:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proofofprotocol/inscribe-mcp",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
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",
@@ -0,0 +1,116 @@
1
+ /**
2
+ * debug command
3
+ *
4
+ * Toggle debug mode on/off with auto-expiry.
5
+ */
6
+
7
+ import { Command } from 'commander';
8
+ import { getDebugSettings, setDebugEnabled, configExists } from '../lib/config.js';
9
+ import { EXIT_CODES } from '../lib/exit-codes.js';
10
+
11
+ // ANSI colors
12
+ const colors = {
13
+ green: (s) => `\x1b[32m${s}\x1b[0m`,
14
+ yellow: (s) => `\x1b[33m${s}\x1b[0m`,
15
+ red: (s) => `\x1b[31m${s}\x1b[0m`,
16
+ cyan: (s) => `\x1b[36m${s}\x1b[0m`,
17
+ bold: (s) => `\x1b[1m${s}\x1b[0m`,
18
+ dim: (s) => `\x1b[2m${s}\x1b[0m`
19
+ };
20
+
21
+ /**
22
+ * Format remaining time
23
+ */
24
+ function formatRemainingTime(expiresAt) {
25
+ if (!expiresAt) return '';
26
+
27
+ const remaining = new Date(expiresAt).getTime() - Date.now();
28
+ if (remaining <= 0) return 'expired';
29
+
30
+ const minutes = Math.floor(remaining / 60000);
31
+ const hours = Math.floor(minutes / 60);
32
+
33
+ if (hours > 0) {
34
+ const remainingMins = minutes % 60;
35
+ return `${hours}h ${remainingMins}m remaining`;
36
+ }
37
+ return `${minutes}m remaining`;
38
+ }
39
+
40
+ export const debugCommand = new Command('debug')
41
+ .description('Toggle debug mode on/off')
42
+ .argument('[action]', 'on, off, or status (default: status)')
43
+ .option('-t, --time <minutes>', 'Auto-off after N minutes (default: 60)', '60')
44
+ .action(async (action, options) => {
45
+ // Check config
46
+ if (!configExists()) {
47
+ console.log('');
48
+ console.log(colors.red('Config not found.'));
49
+ console.log('');
50
+ console.log('Run ' + colors.cyan('inscribe-mcp init') + ' to configure.');
51
+ console.log('');
52
+ process.exit(EXIT_CODES.CONFIG_ERROR);
53
+ }
54
+
55
+ const durationMinutes = parseInt(options.time, 10) || 60;
56
+
57
+ // Default to status if no action provided
58
+ if (!action || action === 'status') {
59
+ const settings = getDebugSettings();
60
+ console.log('');
61
+ console.log(colors.bold('Debug Mode Status'));
62
+ console.log('─'.repeat(40));
63
+
64
+ if (settings.enabled) {
65
+ console.log(`Status: ${colors.green('ON')}`);
66
+ console.log(`Expires: ${formatRemainingTime(settings.expiresAt)}`);
67
+ console.log('');
68
+ console.log(colors.dim('Debug logs will be recorded by MCP server.'));
69
+ console.log(colors.dim('Use ') + colors.cyan('inscribe-mcp show --debug') + colors.dim(' to view.'));
70
+ } else {
71
+ console.log(`Status: ${colors.dim('OFF')}`);
72
+ console.log('');
73
+ console.log('To enable: ' + colors.cyan('inscribe-mcp debug on'));
74
+ console.log(' ' + colors.cyan('inscribe-mcp debug on --time 30') + colors.dim(' (30 min)'));
75
+ }
76
+ console.log('');
77
+ process.exit(EXIT_CODES.SUCCESS);
78
+ }
79
+
80
+ if (action === 'on') {
81
+ setDebugEnabled(true, durationMinutes);
82
+ console.log('');
83
+ console.log(colors.green('✓') + ' Debug mode ' + colors.green('ON'));
84
+ console.log('');
85
+ console.log(`Auto-off: in ${durationMinutes} minutes`);
86
+ console.log('');
87
+ console.log(colors.bold('Next steps:'));
88
+ console.log(' 1. ' + colors.cyan('Restart Claude Desktop') + ' to apply');
89
+ console.log(' 2. Use inscribe tools to generate debug traces');
90
+ console.log(' 3. ' + colors.cyan('inscribe-mcp show --debug') + ' to view traces');
91
+ console.log('');
92
+ process.exit(EXIT_CODES.SUCCESS);
93
+ }
94
+
95
+ if (action === 'off') {
96
+ setDebugEnabled(false);
97
+ console.log('');
98
+ console.log(colors.green('✓') + ' Debug mode ' + colors.dim('OFF'));
99
+ console.log('');
100
+ console.log('Restart Claude Desktop to apply.');
101
+ console.log('');
102
+ process.exit(EXIT_CODES.SUCCESS);
103
+ }
104
+
105
+ // Unknown action
106
+ console.log('');
107
+ console.log(colors.red('Unknown action: ' + action));
108
+ console.log('');
109
+ console.log('Usage:');
110
+ console.log(' ' + colors.cyan('inscribe-mcp debug') + ' Show status');
111
+ console.log(' ' + colors.cyan('inscribe-mcp debug on') + ' Enable (auto-off in 1h)');
112
+ console.log(' ' + colors.cyan('inscribe-mcp debug on -t 30') + ' Enable for 30 minutes');
113
+ console.log(' ' + colors.cyan('inscribe-mcp debug off') + ' Disable');
114
+ console.log('');
115
+ process.exit(EXIT_CODES.ERROR);
116
+ });
@@ -135,9 +135,80 @@ function computeStats(logsDir) {
135
135
  return stats;
136
136
  }
137
137
 
138
+ /**
139
+ * Read debug trace entries from logs
140
+ */
141
+ function getDebugTraces(logsDir, limit = 20) {
142
+ const traces = [];
143
+
144
+ if (!existsSync(logsDir)) {
145
+ return traces;
146
+ }
147
+
148
+ // Get log files sorted by date (newest first)
149
+ const files = readdirSync(logsDir)
150
+ .filter(f => f.endsWith('.jsonl'))
151
+ .sort()
152
+ .reverse();
153
+
154
+ for (const file of files) {
155
+ if (traces.length >= limit) break;
156
+
157
+ const filePath = join(logsDir, file);
158
+ const content = readFileSync(filePath, 'utf-8');
159
+ const lines = content.trim().split('\n').filter(Boolean).reverse();
160
+
161
+ for (const line of lines) {
162
+ if (traces.length >= limit) break;
163
+
164
+ try {
165
+ const entry = JSON.parse(line);
166
+ if (entry.level === 'debug') {
167
+ traces.push(entry);
168
+ }
169
+ } catch {
170
+ // Skip invalid lines
171
+ }
172
+ }
173
+ }
174
+
175
+ return traces.reverse(); // Chronological order
176
+ }
177
+
178
+ /**
179
+ * Format MCP sequence trace for display
180
+ */
181
+ function formatSequenceTrace(traces) {
182
+ const sequences = [];
183
+ let currentSequence = null;
184
+
185
+ for (const trace of traces) {
186
+ if (trace.phase === 'agent_to_mcp') {
187
+ // Start new sequence
188
+ if (currentSequence) {
189
+ sequences.push(currentSequence);
190
+ }
191
+ currentSequence = {
192
+ tool: trace.tool,
193
+ timestamp: trace.timestamp,
194
+ phases: [trace]
195
+ };
196
+ } else if (currentSequence) {
197
+ currentSequence.phases.push(trace);
198
+ }
199
+ }
200
+
201
+ if (currentSequence) {
202
+ sequences.push(currentSequence);
203
+ }
204
+
205
+ return sequences;
206
+ }
207
+
138
208
  export const showCommand = new Command('show')
139
209
  .description('Display inscribe-mcp status dashboard')
140
210
  .option('--json', 'Output as JSON')
211
+ .option('--debug', 'Show MCP sequence debug traces')
141
212
  .action(async (options) => {
142
213
  // Check config
143
214
  if (!configExists()) {
@@ -243,6 +314,82 @@ export const showCommand = new Command('show')
243
314
  console.log(`Topic: ${hashscanBase}/topic/${defaultTopicId}`);
244
315
  }
245
316
 
317
+ // Debug trace section
318
+ if (options.debug) {
319
+ console.log('');
320
+ console.log(colors.bold('MCP Sequence Debug Traces'));
321
+ console.log(line);
322
+
323
+ const traces = getDebugTraces(logsDir, 50);
324
+
325
+ if (traces.length === 0) {
326
+ console.log(colors.dim('No debug traces found.'));
327
+ console.log('');
328
+ console.log('To enable debug logging, set environment variable:');
329
+ console.log(colors.cyan(' export INSCRIBE_MCP_DEBUG=1'));
330
+ console.log('');
331
+ console.log('Then restart the MCP server (restart Claude Desktop).');
332
+ } else {
333
+ const sequences = formatSequenceTrace(traces);
334
+
335
+ if (sequences.length === 0) {
336
+ console.log(colors.dim('No complete sequences found.'));
337
+ } else {
338
+ // Phase symbols for visual diagram
339
+ const phaseSymbols = {
340
+ 'agent_to_mcp': '→',
341
+ 'mcp_to_chain': '→',
342
+ 'chain_to_mcp': '←',
343
+ 'mcp_to_agent': '←'
344
+ };
345
+
346
+ const phaseLabels = {
347
+ 'agent_to_mcp': 'Agent → MCP',
348
+ 'mcp_to_chain': 'MCP → Chain',
349
+ 'chain_to_mcp': 'Chain → MCP',
350
+ 'mcp_to_agent': 'MCP → Agent'
351
+ };
352
+
353
+ console.log('');
354
+ console.log(colors.dim(' ┌─────────┐ ┌─────────┐ ┌─────────┐'));
355
+ console.log(colors.dim(' │ Agent │ → │ MCP │ → │ Chain │'));
356
+ console.log(colors.dim(' │(Claude) │ ← │ Server │ ← │ (Hedera)│'));
357
+ console.log(colors.dim(' └─────────┘ └─────────┘ └─────────┘'));
358
+ console.log('');
359
+
360
+ for (const seq of sequences.slice(-5)) { // Last 5 sequences
361
+ const time = new Date(seq.timestamp).toLocaleTimeString();
362
+ console.log(colors.bold(`[${time}] ${colors.cyan(seq.tool)}`));
363
+
364
+ for (const phase of seq.phases) {
365
+ const symbol = phaseSymbols[phase.phase] || '?';
366
+ const label = phaseLabels[phase.phase] || phase.phase;
367
+ let detail = '';
368
+
369
+ if (phase.payload) {
370
+ const payloadStr = JSON.stringify(phase.payload);
371
+ detail = colors.dim(` ${payloadStr.slice(0, 40)}${payloadStr.length > 40 ? '...' : ''}`);
372
+ }
373
+ if (phase.status) {
374
+ detail = phase.status === 'SUCCESS'
375
+ ? colors.green(` ${phase.status}`)
376
+ : colors.red(` ${phase.status}`);
377
+ }
378
+ if (phase.txId) {
379
+ detail += colors.dim(` txId: ${phase.txId}`);
380
+ }
381
+ if (phase.result) {
382
+ detail += colors.dim(` result: ${phase.result}`);
383
+ }
384
+
385
+ console.log(` ${symbol} ${label}${detail}`);
386
+ }
387
+ console.log('');
388
+ }
389
+ }
390
+ }
391
+ }
392
+
246
393
  console.log('');
247
394
 
248
395
  process.exit(EXIT_CODES.SUCCESS);
package/src/cli/index.js CHANGED
@@ -18,6 +18,7 @@ import { balanceCommand } from './commands/balance.js';
18
18
  import { logCommand } from './commands/log.js';
19
19
  import { showCommand } from './commands/show.js';
20
20
  import { setupMcpCommand } from './commands/setup-mcp.js';
21
+ import { debugCommand } from './commands/debug.js';
21
22
 
22
23
  const __filename = fileURLToPath(import.meta.url);
23
24
  const __dirname = dirname(__filename);
@@ -41,6 +42,7 @@ program.addCommand(configCommand);
41
42
  program.addCommand(balanceCommand);
42
43
  program.addCommand(logCommand);
43
44
  program.addCommand(showCommand);
45
+ program.addCommand(debugCommand);
44
46
 
45
47
  // Parse and execute
46
48
  program.parse();
@@ -124,3 +124,64 @@ export function getDisplayConfig() {
124
124
  operatorPrivateKey: maskPrivateKey(config.operatorPrivateKey)
125
125
  };
126
126
  }
127
+
128
+ /**
129
+ * Update a specific field in config
130
+ * @param {string} key - Config key to update
131
+ * @param {any} value - New value
132
+ */
133
+ export function updateConfig(key, value) {
134
+ const config = readConfig() || {};
135
+ config[key] = value;
136
+ writeConfig(config);
137
+ }
138
+
139
+ /**
140
+ * Get debug settings
141
+ * @returns {Object} { enabled: boolean, expiresAt: string|null }
142
+ */
143
+ export function getDebugSettings() {
144
+ const config = readConfig();
145
+ if (!config || !config.debug) {
146
+ return { enabled: false, expiresAt: null };
147
+ }
148
+
149
+ const { enabled, expiresAt } = config.debug;
150
+
151
+ // Check if expired
152
+ if (enabled && expiresAt) {
153
+ const expiry = new Date(expiresAt);
154
+ if (Date.now() > expiry.getTime()) {
155
+ // Expired - auto disable
156
+ setDebugEnabled(false);
157
+ return { enabled: false, expiresAt: null };
158
+ }
159
+ }
160
+
161
+ return { enabled: !!enabled, expiresAt: expiresAt || null };
162
+ }
163
+
164
+ /**
165
+ * Set debug enabled/disabled
166
+ * @param {boolean} enabled - Enable debug mode
167
+ * @param {number} durationMinutes - Auto-off duration in minutes (default: 60)
168
+ */
169
+ export function setDebugEnabled(enabled, durationMinutes = 60) {
170
+ const config = readConfig() || {};
171
+
172
+ if (enabled) {
173
+ const expiresAt = new Date(Date.now() + durationMinutes * 60 * 1000);
174
+ config.debug = {
175
+ enabled: true,
176
+ expiresAt: expiresAt.toISOString(),
177
+ durationMinutes
178
+ };
179
+ } else {
180
+ config.debug = {
181
+ enabled: false,
182
+ expiresAt: null
183
+ };
184
+ }
185
+
186
+ writeConfig(config);
187
+ }
package/src/server.js CHANGED
@@ -3,6 +3,10 @@
3
3
  * inscribe-mcp Server (stdio transport)
4
4
  *
5
5
  * MCP Server for verifiable inscriptions on Hedera blockchain.
6
+ *
7
+ * Debug mode:
8
+ * - CLI: inscribe-mcp debug on
9
+ * - Environment: INSCRIBE_MCP_DEBUG=1
6
10
  */
7
11
 
8
12
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
@@ -12,6 +16,64 @@ import {
12
16
  ListToolsRequestSchema,
13
17
  } from '@modelcontextprotocol/sdk/types.js';
14
18
  import dotenv from 'dotenv';
19
+ import { existsSync, readFileSync } from 'fs';
20
+ import { homedir } from 'os';
21
+ import { join } from 'path';
22
+
23
+ /**
24
+ * Check if debug mode is enabled (config or env)
25
+ */
26
+ function isDebugEnabled() {
27
+ // Environment variable takes precedence
28
+ if (process.env.INSCRIBE_MCP_DEBUG === '1' || process.env.INSCRIBE_MCP_DEBUG === 'true') {
29
+ return true;
30
+ }
31
+
32
+ // Check config file
33
+ const configPath = join(homedir(), '.inscribe-mcp', 'config.json');
34
+ if (!existsSync(configPath)) {
35
+ return false;
36
+ }
37
+
38
+ try {
39
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'));
40
+ if (!config.debug || !config.debug.enabled) {
41
+ return false;
42
+ }
43
+
44
+ // Check expiry
45
+ if (config.debug.expiresAt) {
46
+ const expiry = new Date(config.debug.expiresAt);
47
+ if (Date.now() > expiry.getTime()) {
48
+ return false; // Expired
49
+ }
50
+ }
51
+
52
+ return true;
53
+ } catch {
54
+ return false;
55
+ }
56
+ }
57
+
58
+ // Debug mode check (evaluated at startup)
59
+ const DEBUG = isDebugEnabled();
60
+
61
+ /**
62
+ * Debug log to stderr (safe for MCP stdio transport)
63
+ */
64
+ function debugLog(phase, data = {}) {
65
+ if (!DEBUG) return;
66
+
67
+ const timestamp = new Date().toISOString();
68
+ const message = {
69
+ timestamp,
70
+ phase,
71
+ ...data
72
+ };
73
+
74
+ // Output to stderr to avoid interfering with MCP JSON-RPC over stdout
75
+ console.error(`[DEBUG] ${JSON.stringify(message)}`);
76
+ }
15
77
 
16
78
  // Layer 1 Tools (Primary)
17
79
  import { inscribeUrlTool } from './tools/layer1/inscribe_url.js';
@@ -58,6 +120,11 @@ const server = new Server(
58
120
 
59
121
  // List Tools Handler
60
122
  server.setRequestHandler(ListToolsRequestSchema, async () => {
123
+ debugLog('list_tools', {
124
+ toolCount: allTools.length,
125
+ tools: allTools.map(t => t.name)
126
+ });
127
+
61
128
  return {
62
129
  tools: allTools.map(t => ({
63
130
  name: t.name,
@@ -70,9 +137,22 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
70
137
  // Call Tool Handler
71
138
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
72
139
  const { name, arguments: args } = request.params;
140
+ const startTime = Date.now();
141
+
142
+ // Debug: Agent → MCP (request received)
143
+ debugLog('agent_to_mcp', {
144
+ tool: name,
145
+ args: args ? Object.keys(args) : []
146
+ });
147
+
73
148
  const tool = allTools.find(t => t.name === name);
74
149
 
75
150
  if (!tool) {
151
+ debugLog('mcp_error', {
152
+ tool: name,
153
+ error: 'Unknown tool'
154
+ });
155
+
76
156
  return {
77
157
  content: [{ type: 'text', text: `Unknown tool: ${name}` }],
78
158
  isError: true
@@ -80,11 +160,52 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
80
160
  }
81
161
 
82
162
  try {
163
+ // Debug: MCP → Chain (processing)
164
+ debugLog('mcp_to_chain', {
165
+ tool: name,
166
+ processing: true
167
+ });
168
+
83
169
  const result = await tool.handler(args || {});
170
+ const duration = Date.now() - startTime;
171
+
172
+ // Debug: Chain → MCP (response)
173
+ debugLog('chain_to_mcp', {
174
+ tool: name,
175
+ status: 'SUCCESS',
176
+ duration,
177
+ hasResult: !!result
178
+ });
179
+
180
+ // Debug: MCP → Agent (returning result)
181
+ debugLog('mcp_to_agent', {
182
+ tool: name,
183
+ status: 'SUCCESS',
184
+ duration
185
+ });
186
+
84
187
  return {
85
188
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
86
189
  };
87
190
  } catch (error) {
191
+ const duration = Date.now() - startTime;
192
+
193
+ // Debug: Chain → MCP (error)
194
+ debugLog('chain_to_mcp', {
195
+ tool: name,
196
+ status: 'FAILED',
197
+ duration,
198
+ error: error.message
199
+ });
200
+
201
+ // Debug: MCP → Agent (returning error)
202
+ debugLog('mcp_to_agent', {
203
+ tool: name,
204
+ status: 'ERROR',
205
+ duration,
206
+ error: error.message
207
+ });
208
+
88
209
  return {
89
210
  content: [{ type: 'text', text: `Error: ${error.message}` }],
90
211
  isError: true
@@ -96,7 +217,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
96
217
  async function main() {
97
218
  const transport = new StdioServerTransport();
98
219
  await server.connect(transport);
99
- console.error('inscribe-mcp server started (stdio)');
220
+
221
+ if (DEBUG) {
222
+ console.error('inscribe-mcp server started (stdio) [DEBUG MODE]');
223
+ console.error('Debug logs will be written to stderr');
224
+ debugLog('server_start', {
225
+ version: '0.3.3',
226
+ debug: true,
227
+ tools: allTools.map(t => t.name)
228
+ });
229
+ } else {
230
+ console.error('inscribe-mcp server started (stdio)');
231
+ }
100
232
  }
101
233
 
102
234
  main().catch(console.error);