@proofofprotocol/inscribe-mcp 0.1.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/LICENSE +21 -0
- package/README.md +255 -0
- package/package.json +58 -0
- package/src/cli/commands/balance.js +151 -0
- package/src/cli/commands/config.js +87 -0
- package/src/cli/commands/init.js +186 -0
- package/src/cli/commands/log.js +193 -0
- package/src/cli/commands/show.js +249 -0
- package/src/cli/index.js +44 -0
- package/src/cli/lib/config.js +126 -0
- package/src/cli/lib/exit-codes.js +19 -0
- package/src/lib/did.js +64 -0
- package/src/lib/hedera.js +221 -0
- package/src/lib/logger.js +200 -0
- package/src/server-sse.js +239 -0
- package/src/server.js +102 -0
- package/src/test.js +107 -0
- package/src/tools/layer1/history.js +174 -0
- package/src/tools/layer1/identity.js +120 -0
- package/src/tools/layer1/inscribe.js +132 -0
- package/src/tools/layer1/inscribe_url.js +193 -0
- package/src/tools/layer1/verify.js +177 -0
- package/src/tools/layer2/account.js +155 -0
- package/src/tools/layer2/hcs.js +163 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* inscribe-mcp Server (SSE/HTTP transport)
|
|
4
|
+
*
|
|
5
|
+
* MCP Server for verifiable inscriptions on Hedera blockchain.
|
|
6
|
+
* For remote access from Windows Claude Desktop via mcp-remote.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import express from 'express';
|
|
10
|
+
import cors from 'cors';
|
|
11
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
12
|
+
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
13
|
+
import {
|
|
14
|
+
CallToolRequestSchema,
|
|
15
|
+
ListToolsRequestSchema,
|
|
16
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
17
|
+
import dotenv from 'dotenv';
|
|
18
|
+
|
|
19
|
+
// Layer 1 Tools (Primary)
|
|
20
|
+
import { inscribeUrlTool } from './tools/layer1/inscribe_url.js';
|
|
21
|
+
import { inscribeTool } from './tools/layer1/inscribe.js';
|
|
22
|
+
import { verifyTool } from './tools/layer1/verify.js';
|
|
23
|
+
import { historyTool } from './tools/layer1/history.js';
|
|
24
|
+
import { identityTool } from './tools/layer1/identity.js';
|
|
25
|
+
|
|
26
|
+
// Layer 2 Tools (Advanced)
|
|
27
|
+
import { createTopicTool, submitMessageTool, getMessagesTool } from './tools/layer2/hcs.js';
|
|
28
|
+
import { getBalanceTool, sendHbarTool, getAccountInfoTool } from './tools/layer2/account.js';
|
|
29
|
+
|
|
30
|
+
dotenv.config();
|
|
31
|
+
|
|
32
|
+
const PORT = process.env.PORT || 3011;
|
|
33
|
+
const HOST = process.env.HOST || '0.0.0.0';
|
|
34
|
+
|
|
35
|
+
// All available tools
|
|
36
|
+
const allTools = [
|
|
37
|
+
// Layer 1 (Primary - Recommended)
|
|
38
|
+
inscribeUrlTool,
|
|
39
|
+
inscribeTool,
|
|
40
|
+
verifyTool,
|
|
41
|
+
historyTool,
|
|
42
|
+
identityTool,
|
|
43
|
+
// Layer 2 (Advanced - For developers)
|
|
44
|
+
createTopicTool,
|
|
45
|
+
submitMessageTool,
|
|
46
|
+
getMessagesTool,
|
|
47
|
+
getBalanceTool,
|
|
48
|
+
sendHbarTool,
|
|
49
|
+
getAccountInfoTool,
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
// Create MCP Server factory
|
|
53
|
+
function createMcpServer() {
|
|
54
|
+
const server = new Server(
|
|
55
|
+
{
|
|
56
|
+
name: 'inscribe-mcp',
|
|
57
|
+
version: '1.0.0',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
capabilities: {
|
|
61
|
+
tools: {},
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// List Tools Handler
|
|
67
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
68
|
+
return {
|
|
69
|
+
tools: allTools.map(t => ({
|
|
70
|
+
name: t.name,
|
|
71
|
+
description: t.description,
|
|
72
|
+
inputSchema: t.inputSchema
|
|
73
|
+
}))
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Call Tool Handler
|
|
78
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
79
|
+
const { name, arguments: args } = request.params;
|
|
80
|
+
const tool = allTools.find(t => t.name === name);
|
|
81
|
+
|
|
82
|
+
if (!tool) {
|
|
83
|
+
return {
|
|
84
|
+
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
85
|
+
isError: true
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const result = await tool.handler(args || {});
|
|
91
|
+
return {
|
|
92
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
|
|
93
|
+
};
|
|
94
|
+
} catch (error) {
|
|
95
|
+
return {
|
|
96
|
+
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
97
|
+
isError: true
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return server;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Express app
|
|
106
|
+
const app = express();
|
|
107
|
+
app.use(cors());
|
|
108
|
+
app.use(express.json());
|
|
109
|
+
|
|
110
|
+
// Track transports for each session
|
|
111
|
+
const transports = {};
|
|
112
|
+
|
|
113
|
+
// Health check
|
|
114
|
+
app.get('/health', (req, res) => {
|
|
115
|
+
res.json({
|
|
116
|
+
status: 'ok',
|
|
117
|
+
server: 'inscribe-mcp',
|
|
118
|
+
version: '1.0.0',
|
|
119
|
+
transport: 'sse',
|
|
120
|
+
connections: Object.keys(transports).length,
|
|
121
|
+
tools: allTools.length
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// SSE endpoint
|
|
126
|
+
app.get('/sse', async (req, res) => {
|
|
127
|
+
console.log(`[SSE] New connection from ${req.ip}`);
|
|
128
|
+
|
|
129
|
+
const transport = new SSEServerTransport('/messages', res);
|
|
130
|
+
transports[transport.sessionId] = transport;
|
|
131
|
+
|
|
132
|
+
res.on('close', () => {
|
|
133
|
+
console.log(`[SSE] Connection closed: ${transport.sessionId}`);
|
|
134
|
+
delete transports[transport.sessionId];
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const server = createMcpServer();
|
|
138
|
+
await server.connect(transport);
|
|
139
|
+
console.log(`[SSE] Server connected: ${transport.sessionId}`);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Messages endpoint
|
|
143
|
+
app.post('/messages', async (req, res) => {
|
|
144
|
+
const sessionId = req.query.sessionId;
|
|
145
|
+
console.log(`[Messages] Received for session: ${sessionId}`);
|
|
146
|
+
|
|
147
|
+
const transport = transports[sessionId];
|
|
148
|
+
|
|
149
|
+
if (!transport) {
|
|
150
|
+
console.log(`[Messages] Session not found: ${sessionId}`);
|
|
151
|
+
res.status(400).json({ error: 'Session not found' });
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
await transport.handlePostMessage(req, res, req.body);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error(`[Messages] Error: ${error.message}`);
|
|
159
|
+
if (!res.headersSent) {
|
|
160
|
+
res.status(500).json({ error: error.message });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Info page
|
|
166
|
+
app.get('/', (req, res) => {
|
|
167
|
+
res.send(`
|
|
168
|
+
<!DOCTYPE html>
|
|
169
|
+
<html>
|
|
170
|
+
<head>
|
|
171
|
+
<title>inscribe-mcp</title>
|
|
172
|
+
<style>
|
|
173
|
+
body { font-family: system-ui; max-width: 800px; margin: 2rem auto; padding: 0 1rem; }
|
|
174
|
+
h1 { color: #333; }
|
|
175
|
+
code { background: #f4f4f4; padding: 0.2rem 0.5rem; border-radius: 4px; }
|
|
176
|
+
pre { background: #f4f4f4; padding: 1rem; border-radius: 8px; overflow-x: auto; }
|
|
177
|
+
.tool { margin: 0.5rem 0; padding: 0.5rem; background: #f9f9f9; border-radius: 4px; }
|
|
178
|
+
.layer1 { border-left: 3px solid #4CAF50; }
|
|
179
|
+
.layer2 { border-left: 3px solid #FF9800; }
|
|
180
|
+
</style>
|
|
181
|
+
</head>
|
|
182
|
+
<body>
|
|
183
|
+
<h1>inscribe-mcp</h1>
|
|
184
|
+
<p>Verifiable inscription for AI agents</p>
|
|
185
|
+
|
|
186
|
+
<h2>Connection</h2>
|
|
187
|
+
<p>SSE Endpoint: <code>http://${req.headers.host}/sse</code></p>
|
|
188
|
+
|
|
189
|
+
<h3>Claude Desktop (Windows)</h3>
|
|
190
|
+
<pre>{
|
|
191
|
+
"mcpServers": {
|
|
192
|
+
"inscribe": {
|
|
193
|
+
"command": "npx",
|
|
194
|
+
"args": ["-y", "mcp-remote", "http://${req.headers.host}/sse", "--allow-http"],
|
|
195
|
+
"env": {}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}</pre>
|
|
199
|
+
|
|
200
|
+
<h2>Layer 1 Tools (Recommended)</h2>
|
|
201
|
+
${allTools.filter(t => !t.name.startsWith('hcs_') && !t.name.startsWith('account_')).map(t => `
|
|
202
|
+
<div class="tool layer1">
|
|
203
|
+
<strong>${t.name}</strong><br>
|
|
204
|
+
<small>${t.description}</small>
|
|
205
|
+
</div>
|
|
206
|
+
`).join('')}
|
|
207
|
+
|
|
208
|
+
<h2>Layer 2 Tools (Advanced)</h2>
|
|
209
|
+
${allTools.filter(t => t.name.startsWith('hcs_') || t.name.startsWith('account_')).map(t => `
|
|
210
|
+
<div class="tool layer2">
|
|
211
|
+
<strong>${t.name}</strong><br>
|
|
212
|
+
<small>${t.description}</small>
|
|
213
|
+
</div>
|
|
214
|
+
`).join('')}
|
|
215
|
+
|
|
216
|
+
<h2>Links</h2>
|
|
217
|
+
<ul>
|
|
218
|
+
<li><a href="/health">Health Check</a></li>
|
|
219
|
+
<li><a href="https://github.com/Shin-R2un/inscribe-mcp">GitHub</a></li>
|
|
220
|
+
</ul>
|
|
221
|
+
</body>
|
|
222
|
+
</html>
|
|
223
|
+
`);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Start server
|
|
227
|
+
app.listen(PORT, HOST, () => {
|
|
228
|
+
console.log(`
|
|
229
|
+
╔═══════════════════════════════════════════════════════════╗
|
|
230
|
+
║ inscribe-mcp Server (SSE Transport) ║
|
|
231
|
+
╠═══════════════════════════════════════════════════════════╣
|
|
232
|
+
║ Server: http://${HOST}:${PORT} ║
|
|
233
|
+
║ SSE Endpoint: http://${HOST}:${PORT}/sse ║
|
|
234
|
+
║ Health Check: http://${HOST}:${PORT}/health ║
|
|
235
|
+
╠═══════════════════════════════════════════════════════════╣
|
|
236
|
+
║ Tools: ${allTools.length} (Layer 1: 5, Layer 2: 6) ║
|
|
237
|
+
╚═══════════════════════════════════════════════════════════╝
|
|
238
|
+
`);
|
|
239
|
+
});
|
package/src/server.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* inscribe-mcp Server (stdio transport)
|
|
4
|
+
*
|
|
5
|
+
* MCP Server for verifiable inscriptions on Hedera blockchain.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
9
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
10
|
+
import {
|
|
11
|
+
CallToolRequestSchema,
|
|
12
|
+
ListToolsRequestSchema,
|
|
13
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
14
|
+
import dotenv from 'dotenv';
|
|
15
|
+
|
|
16
|
+
// Layer 1 Tools (Primary)
|
|
17
|
+
import { inscribeUrlTool } from './tools/layer1/inscribe_url.js';
|
|
18
|
+
import { inscribeTool } from './tools/layer1/inscribe.js';
|
|
19
|
+
import { verifyTool } from './tools/layer1/verify.js';
|
|
20
|
+
import { historyTool } from './tools/layer1/history.js';
|
|
21
|
+
import { identityTool } from './tools/layer1/identity.js';
|
|
22
|
+
|
|
23
|
+
// Layer 2 Tools (Advanced)
|
|
24
|
+
import { createTopicTool, submitMessageTool, getMessagesTool } from './tools/layer2/hcs.js';
|
|
25
|
+
import { getBalanceTool, sendHbarTool, getAccountInfoTool } from './tools/layer2/account.js';
|
|
26
|
+
|
|
27
|
+
dotenv.config();
|
|
28
|
+
|
|
29
|
+
// All available tools
|
|
30
|
+
const allTools = [
|
|
31
|
+
// Layer 1 (Primary - Recommended)
|
|
32
|
+
inscribeUrlTool, // Killer feature
|
|
33
|
+
inscribeTool,
|
|
34
|
+
verifyTool,
|
|
35
|
+
historyTool,
|
|
36
|
+
identityTool,
|
|
37
|
+
// Layer 2 (Advanced - For developers)
|
|
38
|
+
createTopicTool,
|
|
39
|
+
submitMessageTool,
|
|
40
|
+
getMessagesTool,
|
|
41
|
+
getBalanceTool,
|
|
42
|
+
sendHbarTool,
|
|
43
|
+
getAccountInfoTool,
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
// Create MCP Server
|
|
47
|
+
const server = new Server(
|
|
48
|
+
{
|
|
49
|
+
name: 'inscribe-mcp',
|
|
50
|
+
version: '1.0.0',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
capabilities: {
|
|
54
|
+
tools: {},
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// List Tools Handler
|
|
60
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
61
|
+
return {
|
|
62
|
+
tools: allTools.map(t => ({
|
|
63
|
+
name: t.name,
|
|
64
|
+
description: t.description,
|
|
65
|
+
inputSchema: t.inputSchema
|
|
66
|
+
}))
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Call Tool Handler
|
|
71
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
72
|
+
const { name, arguments: args } = request.params;
|
|
73
|
+
const tool = allTools.find(t => t.name === name);
|
|
74
|
+
|
|
75
|
+
if (!tool) {
|
|
76
|
+
return {
|
|
77
|
+
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
78
|
+
isError: true
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const result = await tool.handler(args || {});
|
|
84
|
+
return {
|
|
85
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
|
|
86
|
+
};
|
|
87
|
+
} catch (error) {
|
|
88
|
+
return {
|
|
89
|
+
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
90
|
+
isError: true
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Main
|
|
96
|
+
async function main() {
|
|
97
|
+
const transport = new StdioServerTransport();
|
|
98
|
+
await server.connect(transport);
|
|
99
|
+
console.error('inscribe-mcp server started (stdio)');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
main().catch(console.error);
|
package/src/test.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* inscribe-mcp Test Script
|
|
4
|
+
*
|
|
5
|
+
* Direct tool testing without MCP client
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import dotenv from 'dotenv';
|
|
9
|
+
dotenv.config();
|
|
10
|
+
|
|
11
|
+
// Layer 1 Tools
|
|
12
|
+
import { inscribeUrlTool } from './tools/layer1/inscribe_url.js';
|
|
13
|
+
import { inscribeTool } from './tools/layer1/inscribe.js';
|
|
14
|
+
import { verifyTool } from './tools/layer1/verify.js';
|
|
15
|
+
import { historyTool } from './tools/layer1/history.js';
|
|
16
|
+
|
|
17
|
+
// Layer 2 Tools
|
|
18
|
+
import { getBalanceTool } from './tools/layer2/account.js';
|
|
19
|
+
|
|
20
|
+
async function testMcpServer() {
|
|
21
|
+
console.log('=== inscribe-mcp Test ===\n');
|
|
22
|
+
|
|
23
|
+
const allTools = [
|
|
24
|
+
inscribeUrlTool, inscribeTool, verifyTool, historyTool, getBalanceTool
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
console.log('✅ Tools loaded\n');
|
|
29
|
+
|
|
30
|
+
// 1. List Tools
|
|
31
|
+
console.log('1. List Tools:');
|
|
32
|
+
console.log('---');
|
|
33
|
+
allTools.forEach(t => console.log(` • ${t.name}: ${t.description.substring(0, 50)}...`));
|
|
34
|
+
console.log(`\n Total: ${allTools.length} tools (testing subset)\n`);
|
|
35
|
+
|
|
36
|
+
// 2. Test account_get_balance
|
|
37
|
+
console.log('2. Test account_get_balance:');
|
|
38
|
+
console.log('---');
|
|
39
|
+
const balanceData = await getBalanceTool.handler({});
|
|
40
|
+
console.log(` Account: ${balanceData.account_id}`);
|
|
41
|
+
console.log(` Balance: ${balanceData.balance_hbar} HBAR\n`);
|
|
42
|
+
|
|
43
|
+
// 3. Test inscribe
|
|
44
|
+
console.log('3. Test inscribe:');
|
|
45
|
+
console.log('---');
|
|
46
|
+
const inscribeData = await inscribeTool.handler({
|
|
47
|
+
content: 'inscribe-mcp テスト: ' + new Date().toISOString(),
|
|
48
|
+
type: 'test'
|
|
49
|
+
});
|
|
50
|
+
console.log(` Success: ${inscribeData.success}`);
|
|
51
|
+
console.log(` Inscription ID: ${inscribeData.inscription_id}`);
|
|
52
|
+
console.log(` Hash: ${inscribeData.content_hash?.substring(0, 16)}...`);
|
|
53
|
+
console.log(` HashScan: ${inscribeData.verify_url}\n`);
|
|
54
|
+
|
|
55
|
+
// 4. Test verify
|
|
56
|
+
if (inscribeData.success && inscribeData.inscription_id) {
|
|
57
|
+
// Wait for mirror node to index
|
|
58
|
+
console.log('4. Test verify (waiting 3s for indexing):');
|
|
59
|
+
console.log('---');
|
|
60
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
61
|
+
|
|
62
|
+
const verifyData = await verifyTool.handler({
|
|
63
|
+
inscription_id: inscribeData.inscription_id
|
|
64
|
+
});
|
|
65
|
+
console.log(` Valid: ${verifyData.valid}`);
|
|
66
|
+
console.log(` Type: ${verifyData.type}`);
|
|
67
|
+
console.log(` Timestamp: ${verifyData.consensus_timestamp}\n`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 5. Test history
|
|
71
|
+
console.log('5. Test history:');
|
|
72
|
+
console.log('---');
|
|
73
|
+
const historyData = await historyTool.handler({ limit: 5 });
|
|
74
|
+
console.log(` Success: ${historyData.success}`);
|
|
75
|
+
console.log(` Count: ${historyData.count}`);
|
|
76
|
+
if (historyData.inscriptions?.length > 0) {
|
|
77
|
+
console.log(` Latest: ${historyData.inscriptions[0].type} - ${historyData.inscriptions[0].content_preview?.substring(0, 40)}...`);
|
|
78
|
+
}
|
|
79
|
+
console.log('');
|
|
80
|
+
|
|
81
|
+
// 6. Test inscribe_url (YouTube)
|
|
82
|
+
console.log('6. Test inscribe_url (YouTube):');
|
|
83
|
+
console.log('---');
|
|
84
|
+
const urlData = await inscribeUrlTool.handler({
|
|
85
|
+
url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
|
|
86
|
+
note: 'テスト用'
|
|
87
|
+
});
|
|
88
|
+
console.log(` Success: ${urlData.success}`);
|
|
89
|
+
if (urlData.success) {
|
|
90
|
+
console.log(` Platform: ${urlData.capture?.platform}`);
|
|
91
|
+
console.log(` Source: ${urlData.capture?.source}`);
|
|
92
|
+
console.log(` Author: ${urlData.capture?.author}`);
|
|
93
|
+
console.log(` Content: ${urlData.capture?.content_preview?.substring(0, 50)}...`);
|
|
94
|
+
} else {
|
|
95
|
+
console.log(` Error: ${urlData.error}`);
|
|
96
|
+
}
|
|
97
|
+
console.log('');
|
|
98
|
+
|
|
99
|
+
console.log('=== All Tests Completed! ===');
|
|
100
|
+
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error('Test error:', error.message);
|
|
103
|
+
console.error(error.stack);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
testMcpServer().catch(console.error);
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* history - Inscription History Tool
|
|
3
|
+
*
|
|
4
|
+
* Retrieve history of inscriptions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getMirrorNodeUrl, getNetwork, getOperatorId } from '../../lib/hedera.js';
|
|
8
|
+
import { logToolExecution, createTimer } from '../../lib/logger.js';
|
|
9
|
+
|
|
10
|
+
export const historyTool = {
|
|
11
|
+
name: 'history',
|
|
12
|
+
description: '刻んだ履歴を取得する',
|
|
13
|
+
inputSchema: {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
limit: {
|
|
17
|
+
type: 'number',
|
|
18
|
+
description: '取得件数(デフォルト: 10、最大: 100)',
|
|
19
|
+
default: 10
|
|
20
|
+
},
|
|
21
|
+
type: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: '種類でフィルター(article, log, decision, url_capture など)'
|
|
24
|
+
},
|
|
25
|
+
author: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: '著者でフィルター'
|
|
28
|
+
},
|
|
29
|
+
topic_id: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
description: 'トピックID(省略時はデフォルトトピック)'
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
handler: async ({ limit = 10, type, author, topic_id }) => {
|
|
37
|
+
const timer = createTimer();
|
|
38
|
+
const network = getNetwork();
|
|
39
|
+
const operator = getOperatorId().toString();
|
|
40
|
+
const topicId = topic_id || process.env.DEFAULT_TOPIC_ID;
|
|
41
|
+
|
|
42
|
+
if (!topicId) {
|
|
43
|
+
logToolExecution({
|
|
44
|
+
command: 'history',
|
|
45
|
+
network,
|
|
46
|
+
operator,
|
|
47
|
+
status: 'error',
|
|
48
|
+
latency: timer(),
|
|
49
|
+
error: 'Topic ID not specified'
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
error: 'トピックIDが指定されていません',
|
|
54
|
+
inscriptions: [],
|
|
55
|
+
count: 0
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Limit to max 100
|
|
60
|
+
const fetchLimit = Math.min(limit, 100);
|
|
61
|
+
|
|
62
|
+
// Fetch from Mirror Node
|
|
63
|
+
const mirrorUrl = getMirrorNodeUrl();
|
|
64
|
+
const apiUrl = `${mirrorUrl}/api/v1/topics/${topicId}/messages?limit=${fetchLimit}&order=desc`;
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const response = await fetch(apiUrl);
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
logToolExecution({
|
|
70
|
+
command: 'history',
|
|
71
|
+
network,
|
|
72
|
+
topicId,
|
|
73
|
+
operator,
|
|
74
|
+
status: 'error',
|
|
75
|
+
latency: timer(),
|
|
76
|
+
error: 'Mirror Node fetch failed'
|
|
77
|
+
});
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
error: 'Mirror Node からの取得に失敗しました',
|
|
81
|
+
inscriptions: [],
|
|
82
|
+
count: 0
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const data = await response.json();
|
|
87
|
+
|
|
88
|
+
if (!data.messages) {
|
|
89
|
+
logToolExecution({
|
|
90
|
+
command: 'history',
|
|
91
|
+
network,
|
|
92
|
+
topicId,
|
|
93
|
+
operator,
|
|
94
|
+
status: 'success',
|
|
95
|
+
latency: timer(),
|
|
96
|
+
metadata: { count: 0 }
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
success: true,
|
|
100
|
+
inscriptions: [],
|
|
101
|
+
count: 0,
|
|
102
|
+
topic_id: topicId
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Parse and filter inscriptions
|
|
107
|
+
let inscriptions = data.messages.map(msg => {
|
|
108
|
+
try {
|
|
109
|
+
const content = JSON.parse(
|
|
110
|
+
Buffer.from(msg.message, 'base64').toString('utf-8')
|
|
111
|
+
);
|
|
112
|
+
return {
|
|
113
|
+
inscription_id: `${topicId}-${msg.consensus_timestamp}`,
|
|
114
|
+
consensus_timestamp: msg.consensus_timestamp,
|
|
115
|
+
...content
|
|
116
|
+
};
|
|
117
|
+
} catch {
|
|
118
|
+
// Skip non-JSON messages
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}).filter(Boolean);
|
|
122
|
+
|
|
123
|
+
// Apply filters
|
|
124
|
+
if (type) {
|
|
125
|
+
inscriptions = inscriptions.filter(i => i.type === type);
|
|
126
|
+
}
|
|
127
|
+
if (author) {
|
|
128
|
+
inscriptions = inscriptions.filter(i => i.author === author);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
logToolExecution({
|
|
132
|
+
command: 'history',
|
|
133
|
+
network,
|
|
134
|
+
topicId,
|
|
135
|
+
operator,
|
|
136
|
+
status: 'success',
|
|
137
|
+
latency: timer(),
|
|
138
|
+
metadata: { count: inscriptions.length }
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
success: true,
|
|
143
|
+
inscriptions: inscriptions.map(i => ({
|
|
144
|
+
inscription_id: i.inscription_id,
|
|
145
|
+
type: i.type,
|
|
146
|
+
content_preview: i.content_preview || i.content?.substring(0, 100),
|
|
147
|
+
author: i.author,
|
|
148
|
+
platform: i.platform,
|
|
149
|
+
inscribed_at: i.inscribed_at || i.captured_at,
|
|
150
|
+
consensus_timestamp: i.consensus_timestamp
|
|
151
|
+
})),
|
|
152
|
+
count: inscriptions.length,
|
|
153
|
+
topic_id: topicId,
|
|
154
|
+
verify_url: `https://hashscan.io/${network}/topic/${topicId}`
|
|
155
|
+
};
|
|
156
|
+
} catch (error) {
|
|
157
|
+
logToolExecution({
|
|
158
|
+
command: 'history',
|
|
159
|
+
network,
|
|
160
|
+
topicId,
|
|
161
|
+
operator,
|
|
162
|
+
status: 'error',
|
|
163
|
+
latency: timer(),
|
|
164
|
+
error: error.message
|
|
165
|
+
});
|
|
166
|
+
return {
|
|
167
|
+
success: false,
|
|
168
|
+
error: `履歴取得エラー: ${error.message}`,
|
|
169
|
+
inscriptions: [],
|
|
170
|
+
count: 0
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|