@proofofprotocol/inscribe-mcp 0.3.2 → 0.3.4

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.4",
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,117 @@
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. Use inscribe tools to generate debug traces');
89
+ console.log(' 2. ' + colors.cyan('inscribe-mcp show --debug') + ' to view traces');
90
+ console.log('');
91
+ console.log(colors.dim('No restart required - takes effect immediately.'));
92
+ console.log('');
93
+ process.exit(EXIT_CODES.SUCCESS);
94
+ }
95
+
96
+ if (action === 'off') {
97
+ setDebugEnabled(false);
98
+ console.log('');
99
+ console.log(colors.green('✓') + ' Debug mode ' + colors.dim('OFF'));
100
+ console.log('');
101
+ console.log(colors.dim('Takes effect immediately.'));
102
+ console.log('');
103
+ process.exit(EXIT_CODES.SUCCESS);
104
+ }
105
+
106
+ // Unknown action
107
+ console.log('');
108
+ console.log(colors.red('Unknown action: ' + action));
109
+ console.log('');
110
+ console.log('Usage:');
111
+ console.log(' ' + colors.cyan('inscribe-mcp debug') + ' Show status');
112
+ console.log(' ' + colors.cyan('inscribe-mcp debug on') + ' Enable (auto-off in 1h)');
113
+ console.log(' ' + colors.cyan('inscribe-mcp debug on -t 30') + ' Enable for 30 minutes');
114
+ console.log(' ' + colors.cyan('inscribe-mcp debug off') + ' Disable');
115
+ console.log('');
116
+ process.exit(EXIT_CODES.ERROR);
117
+ });
@@ -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,62 @@ 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
+ /**
59
+ * Debug log to stderr (safe for MCP stdio transport)
60
+ * Checks config on each call for hot-reload support
61
+ */
62
+ function debugLog(phase, data = {}) {
63
+ if (!isDebugEnabled()) return;
64
+
65
+ const timestamp = new Date().toISOString();
66
+ const message = {
67
+ timestamp,
68
+ phase,
69
+ ...data
70
+ };
71
+
72
+ // Output to stderr to avoid interfering with MCP JSON-RPC over stdout
73
+ console.error(`[DEBUG] ${JSON.stringify(message)}`);
74
+ }
15
75
 
16
76
  // Layer 1 Tools (Primary)
17
77
  import { inscribeUrlTool } from './tools/layer1/inscribe_url.js';
@@ -58,6 +118,11 @@ const server = new Server(
58
118
 
59
119
  // List Tools Handler
60
120
  server.setRequestHandler(ListToolsRequestSchema, async () => {
121
+ debugLog('list_tools', {
122
+ toolCount: allTools.length,
123
+ tools: allTools.map(t => t.name)
124
+ });
125
+
61
126
  return {
62
127
  tools: allTools.map(t => ({
63
128
  name: t.name,
@@ -70,9 +135,22 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
70
135
  // Call Tool Handler
71
136
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
72
137
  const { name, arguments: args } = request.params;
138
+ const startTime = Date.now();
139
+
140
+ // Debug: Agent → MCP (request received)
141
+ debugLog('agent_to_mcp', {
142
+ tool: name,
143
+ args: args ? Object.keys(args) : []
144
+ });
145
+
73
146
  const tool = allTools.find(t => t.name === name);
74
147
 
75
148
  if (!tool) {
149
+ debugLog('mcp_error', {
150
+ tool: name,
151
+ error: 'Unknown tool'
152
+ });
153
+
76
154
  return {
77
155
  content: [{ type: 'text', text: `Unknown tool: ${name}` }],
78
156
  isError: true
@@ -80,11 +158,52 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
80
158
  }
81
159
 
82
160
  try {
161
+ // Debug: MCP → Chain (processing)
162
+ debugLog('mcp_to_chain', {
163
+ tool: name,
164
+ processing: true
165
+ });
166
+
83
167
  const result = await tool.handler(args || {});
168
+ const duration = Date.now() - startTime;
169
+
170
+ // Debug: Chain → MCP (response)
171
+ debugLog('chain_to_mcp', {
172
+ tool: name,
173
+ status: 'SUCCESS',
174
+ duration,
175
+ hasResult: !!result
176
+ });
177
+
178
+ // Debug: MCP → Agent (returning result)
179
+ debugLog('mcp_to_agent', {
180
+ tool: name,
181
+ status: 'SUCCESS',
182
+ duration
183
+ });
184
+
84
185
  return {
85
186
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
86
187
  };
87
188
  } catch (error) {
189
+ const duration = Date.now() - startTime;
190
+
191
+ // Debug: Chain → MCP (error)
192
+ debugLog('chain_to_mcp', {
193
+ tool: name,
194
+ status: 'FAILED',
195
+ duration,
196
+ error: error.message
197
+ });
198
+
199
+ // Debug: MCP → Agent (returning error)
200
+ debugLog('mcp_to_agent', {
201
+ tool: name,
202
+ status: 'ERROR',
203
+ duration,
204
+ error: error.message
205
+ });
206
+
88
207
  return {
89
208
  content: [{ type: 'text', text: `Error: ${error.message}` }],
90
209
  isError: true
@@ -96,7 +215,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
96
215
  async function main() {
97
216
  const transport = new StdioServerTransport();
98
217
  await server.connect(transport);
218
+
219
+ // Log startup (debug mode is now hot-reloadable)
99
220
  console.error('inscribe-mcp server started (stdio)');
221
+
222
+ // Initial debug log if enabled
223
+ if (isDebugEnabled()) {
224
+ console.error('Debug mode is currently ON (hot-reloadable)');
225
+ debugLog('server_start', {
226
+ version: '0.3.4',
227
+ tools: allTools.map(t => t.name)
228
+ });
229
+ }
100
230
  }
101
231
 
102
232
  main().catch(console.error);