poi-plugin-mcp 0.2.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.
@@ -0,0 +1,184 @@
1
+ function loadReact() {
2
+ try {
3
+ return require('react')
4
+ } catch (_) {
5
+ if (typeof window !== 'undefined' && window.React) return window.React
6
+ return null
7
+ }
8
+ }
9
+
10
+ function createSettingsClass(controller) {
11
+ return function PoiMcpSettings() {
12
+ const React = loadReact()
13
+
14
+ if (React && React.useState) {
15
+ return renderStatefulSettings(React, controller)
16
+ }
17
+
18
+ return renderStaticSettings(createFallbackElement, controller)
19
+ }
20
+ }
21
+
22
+ function renderStatefulSettings(React, controller) {
23
+ const e = React.createElement
24
+ const [status, setStatus] = React.useState(() => controller.getStatus())
25
+ const [portText, setPortText] = React.useState(() => String(status.port))
26
+ const [busy, setBusy] = React.useState(false)
27
+ const [message, setMessage] = React.useState('')
28
+
29
+ async function run(action, successMessage) {
30
+ setBusy(true)
31
+ setMessage('')
32
+ try {
33
+ await action()
34
+ const nextStatus = controller.getStatus()
35
+ setStatus(nextStatus)
36
+ setPortText(String(nextStatus.port))
37
+ setMessage(successMessage)
38
+ } catch (error) {
39
+ setMessage(error && error.message ? error.message : String(error))
40
+ } finally {
41
+ setBusy(false)
42
+ }
43
+ }
44
+
45
+ const port = Number(portText)
46
+ const validPort = Number.isInteger(port) && port >= 1 && port <= 65535
47
+
48
+ return renderSettings(e, {
49
+ busy,
50
+ message,
51
+ port,
52
+ portText,
53
+ status,
54
+ validPort,
55
+ onPortChange: (event) => setPortText(event.target.value),
56
+ onApply: () => run(
57
+ () => controller.applySettings({ port, enabled: status.enabled }),
58
+ 'Port saved',
59
+ ),
60
+ onToggle: () => run(
61
+ () => (status.running ? controller.stopBridge() : controller.startBridge()),
62
+ status.running ? 'Stopped' : 'Started',
63
+ ),
64
+ })
65
+ }
66
+
67
+ function renderStaticSettings(e, controller) {
68
+ const status = controller.getStatus()
69
+ const port = status.port
70
+
71
+ return renderSettings(e, {
72
+ busy: false,
73
+ message: '',
74
+ port,
75
+ portText: String(port),
76
+ status,
77
+ validPort: true,
78
+ onPortChange: null,
79
+ onApply: async () => {
80
+ const input = typeof document !== 'undefined' ? document.getElementById('poi-mcp-port') : null
81
+ const nextPort = input ? Number(input.value) : port
82
+ await controller.applySettings({ port: nextPort, enabled: status.enabled })
83
+ },
84
+ onToggle: () => (status.running ? controller.stopBridge() : controller.startBridge()),
85
+ })
86
+ }
87
+
88
+ function renderSettings(e, props) {
89
+ const statusText = props.status.running
90
+ ? `Running on 127.0.0.1:${props.status.actualPort}`
91
+ : 'Stopped'
92
+
93
+ return e('div', { style: styles.root },
94
+ e('div', { style: styles.row },
95
+ e('label', { style: styles.label, htmlFor: 'poi-mcp-port' }, 'Port'),
96
+ e('input', {
97
+ id: 'poi-mcp-port',
98
+ type: 'number',
99
+ min: 1,
100
+ max: 65535,
101
+ value: props.onPortChange ? props.portText : undefined,
102
+ defaultValue: props.onPortChange ? undefined : props.portText,
103
+ disabled: props.busy,
104
+ style: styles.input,
105
+ onChange: props.onPortChange,
106
+ }),
107
+ e('button', {
108
+ type: 'button',
109
+ disabled: props.busy || !props.validPort,
110
+ style: styles.button,
111
+ onClick: props.onApply,
112
+ }, 'Apply'),
113
+ ),
114
+ e('div', { style: styles.row },
115
+ e('span', { style: styles.label }, 'Service'),
116
+ e('button', {
117
+ type: 'button',
118
+ disabled: props.busy,
119
+ style: styles.button,
120
+ onClick: props.onToggle,
121
+ }, props.status.running ? 'Stop' : 'Start'),
122
+ e('span', { style: styles.status }, statusText),
123
+ ),
124
+ e('div', { style: styles.note },
125
+ 'Pi extension defaults to 127.0.0.1:17777; keep this port unless you also update Pi.',
126
+ ),
127
+ props.message ? e('div', { style: styles.message }, props.message) : null,
128
+ )
129
+ }
130
+
131
+ function createFallbackElement(type, props, ...children) {
132
+ const nextProps = { ...(props || {}) }
133
+ if (children.length === 1) nextProps.children = children[0]
134
+ if (children.length > 1) nextProps.children = children
135
+
136
+ return {
137
+ $$typeof: Symbol.for('react.element'),
138
+ type,
139
+ key: nextProps.key == null ? null : String(nextProps.key),
140
+ ref: nextProps.ref == null ? null : nextProps.ref,
141
+ props: nextProps,
142
+ _owner: null,
143
+ }
144
+ }
145
+
146
+ const styles = {
147
+ root: {
148
+ display: 'flex',
149
+ flexDirection: 'column',
150
+ gap: 12,
151
+ maxWidth: 560,
152
+ },
153
+ row: {
154
+ alignItems: 'center',
155
+ display: 'flex',
156
+ gap: 8,
157
+ },
158
+ label: {
159
+ flex: '0 0 80px',
160
+ fontWeight: 600,
161
+ },
162
+ input: {
163
+ width: 120,
164
+ },
165
+ button: {
166
+ minWidth: 72,
167
+ },
168
+ status: {
169
+ color: '#59636e',
170
+ },
171
+ note: {
172
+ color: '#59636e',
173
+ fontSize: 12,
174
+ lineHeight: 1.4,
175
+ },
176
+ message: {
177
+ color: '#1f6feb',
178
+ fontSize: 12,
179
+ },
180
+ }
181
+
182
+ module.exports = {
183
+ createSettingsClass,
184
+ }
@@ -0,0 +1,51 @@
1
+ const fs = require('fs')
2
+ const os = require('os')
3
+ const path = require('path')
4
+
5
+ const DEFAULT_SETTINGS = Object.freeze({
6
+ port: 17777,
7
+ enabled: true,
8
+ })
9
+
10
+ const DEFAULT_SETTINGS_FILE = path.join(os.homedir(), '.poi-mcp', 'settings.json')
11
+
12
+ function normalizeSettings(input = {}) {
13
+ const port = normalizePort(input.port)
14
+ return {
15
+ port: port == null ? DEFAULT_SETTINGS.port : port,
16
+ enabled: typeof input.enabled === 'boolean' ? input.enabled : DEFAULT_SETTINGS.enabled,
17
+ }
18
+ }
19
+
20
+ function normalizePort(value) {
21
+ if (value == null || value === '') return null
22
+
23
+ const port = Number(value)
24
+ if (!Number.isInteger(port) || port < 1 || port > 65535) return null
25
+ return port
26
+ }
27
+
28
+ function loadSettings(settingsPath = DEFAULT_SETTINGS_FILE) {
29
+ try {
30
+ const raw = fs.readFileSync(settingsPath, 'utf8')
31
+ return normalizeSettings(JSON.parse(raw))
32
+ } catch (_) {
33
+ return { ...DEFAULT_SETTINGS }
34
+ }
35
+ }
36
+
37
+ function saveSettings(settings, settingsPath = DEFAULT_SETTINGS_FILE) {
38
+ const normalized = normalizeSettings(settings)
39
+ fs.mkdirSync(path.dirname(settingsPath), { recursive: true })
40
+ fs.writeFileSync(settingsPath, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8')
41
+ return normalized
42
+ }
43
+
44
+ module.exports = {
45
+ DEFAULT_SETTINGS,
46
+ DEFAULT_SETTINGS_FILE,
47
+ loadSettings,
48
+ normalizePort,
49
+ normalizeSettings,
50
+ saveSettings,
51
+ }
package/mcp-server.js ADDED
@@ -0,0 +1,397 @@
1
+ #!/usr/bin/env node
2
+ // poi-mcp — MCP Server for KanColle game data
3
+ //
4
+ // 两种使用方式:
5
+ // 方式 A: 配合 POI DevTools 脚本 (推荐, 最稳定)
6
+ // 方式 B: 配合 POI 的 --remote-debugging-port
7
+ //
8
+ // ── 快速开始 ──
9
+ // 1. 启动 POI,进入游戏母港
10
+ // 2. POI 菜单 → 开发工具 → 切换开发工具 (F12)
11
+ // 3. 在 Console 中粘贴下面这段脚本:
12
+ //
13
+ // fetch('https://raw.githubusercontent.com/your/poi-plugin-mcp/main/inject.js')
14
+ // .then(r => r.text())
15
+ // .then(eval)
16
+ //
17
+ // 4. 脚本会自动启动 HTTP 服务并写入端口号到 ~/.poi-mcp/port
18
+ // 5. 然后运行: node mcp-server.js
19
+ //
20
+ // ── 备用方案: 粘贴下面脚本到 Console ──
21
+ // (function(){
22
+ // var port = 17777;
23
+ // var http = new XMLHttpRequest();
24
+ // http.open('GET', 'http://127.0.0.1:' + port + '/health', true);
25
+ // http.onload = function() {
26
+ // if (http.status === 200) console.log('[poi-mcp] Server already running on port', port);
27
+ // };
28
+ // http.send();
29
+ // var s = document.createElement('script');
30
+ // s.src = 'data:text/javascript,' + encodeURIComponent([
31
+ // 'var p='+port+';',
32
+ // 'var gs=function(){return window.getStore()};',
33
+ // 'var s=require("http").createServer(function(q,r){',
34
+ // ' r.setHeader("Access-Control-Allow-Origin","*");',
35
+ // ' r.setHeader("Content-Type","application/json");',
36
+ // ' var u=q.url;',
37
+ // ' if(u==="/health"){r.end('+JSON.stringify(JSON.stringify({status:"ok"}))+')}',
38
+ // ' else if(u==="/fleets"){r.end(JSON.stringify(gs().info.fleets))}',
39
+ // ' else if(u==="/ships"){r.end(JSON.stringify(gs().info.ships))}',
40
+ // ' else if(u==="/equipment"){r.end(JSON.stringify(gs().info.equips))}',
41
+ // ' else if(u==="/resources"){r.end(JSON.stringify(gs().info.resources))}',
42
+ // ' else if(u==="/quests"){r.end(JSON.stringify({activeQuests:gs().info.quests.activeQuests,records:gs().info.quests.records}))}',
43
+ // ' else if(u==="/airbase"){r.end(JSON.stringify(gs().info.airbase))}',
44
+ // ' else if(u==="/basic"){r.end(JSON.stringify(gs().info.basic))}',
45
+ // ' else if(u==="/all"){r.end(JSON.stringify(gs().info))}',
46
+ // ' else{r.writeHead(404);r.end("Not found")}',
47
+ // '});',
48
+ // 's.listen(p,"127.0.0.1",function(){',
49
+ // ' require("fs").writeFileSync("'+require('path').join(os.homedir(),'.poi-mcp','port').replace(/\\/g,'/')+'",String(p),"utf8");',
50
+ // ' console.log("[poi-mcp] API running on http://127.0.0.1:"+p);',
51
+ // '});'
52
+ // ].join(''));
53
+ // document.head.appendChild(s);
54
+ // })();
55
+
56
+ const os = require('os')
57
+ const path = require('path')
58
+ const fs = require('fs')
59
+ const http = require('http')
60
+
61
+ const PORT_FILE = path.join(os.homedir(), '.poi-mcp', 'port')
62
+
63
+ // ─── POI HTTP API Client ─────────────────────────────────────────────────────
64
+
65
+ function getPoiPort() {
66
+ try {
67
+ return parseInt(fs.readFileSync(PORT_FILE, 'utf8').trim(), 10)
68
+ } catch (_) {
69
+ return null
70
+ }
71
+ }
72
+
73
+ function fetchFromPoi(endpoint) {
74
+ return new Promise((resolve, reject) => {
75
+ const port = getPoiPort()
76
+ if (!port) {
77
+ return reject(new Error(
78
+ 'POI data API not found.\n\n' +
79
+ 'Please:\n' +
80
+ ' 1. Open POI → F12 (DevTools) → Console tab\n' +
81
+ ' 2. Paste this script and press Enter:\n\n' +
82
+ '─── PASTE THIS INTO POI CONSOLE ───\n' +
83
+ getInjectScript() +
84
+ '\n─── END ───\n\n' +
85
+ ' 3. Then run this MCP server again.'
86
+ ))
87
+ }
88
+ http.get(`http://127.0.0.1:${port}${endpoint}`, (res) => {
89
+ let data = ''
90
+ res.on('data', chunk => data += chunk)
91
+ res.on('end', () => {
92
+ try { resolve(JSON.parse(data)) } catch (e) { reject(e) }
93
+ })
94
+ }).on('error', reject).setTimeout(10000, function() {
95
+ this.destroy()
96
+ reject(new Error('Request timed out'))
97
+ })
98
+ })
99
+ }
100
+
101
+ function getInjectScript() {
102
+ return [
103
+ '(function(){',
104
+ 'var p=17777;',
105
+ 'var s=require("http").createServer(function(q,r){',
106
+ ' r.setHeader("Access-Control-Allow-Origin","*");',
107
+ ' r.setHeader("Content-Type","application/json");',
108
+ ' try{',
109
+ ' var st=window.getStore();',
110
+ ' if(!st||!st.info)throw new Error("Store not ready");',
111
+ ' var u=q.url;',
112
+ ' if(u==="/health"){r.end(JSON.stringify({status:"ok"}))}',
113
+ ' else if(u==="/fleets"){r.end(JSON.stringify(st.info.fleets||[]))}',
114
+ ' else if(u==="/ships"){r.end(JSON.stringify(st.info.ships||{}))}',
115
+ ' else if(u==="/equipment"){r.end(JSON.stringify(st.info.equips||{}))}',
116
+ ' else if(u==="/resources"){r.end(JSON.stringify(st.info.resources||[]))}',
117
+ ' else if(u==="/quests"){r.end(JSON.stringify({activeQuests:st.info.quests?.activeQuests||{},records:st.info.quests?.records||{}}))}',
118
+ ' else if(u==="/airbase"){r.end(JSON.stringify(st.info.airbase||[]))}',
119
+ ' else if(u==="/basic"){r.end(JSON.stringify(st.info.basic||{}))}',
120
+ ' else if(u==="/all"){r.end(JSON.stringify({',
121
+ ' basic:st.info.basic,',
122
+ ' fleets:st.info.fleets,',
123
+ ' ships:st.info.ships,',
124
+ ' equipment:st.info.equips,',
125
+ ' resources:st.info.resources,',
126
+ ' quests:{activeQuests:st.info.quests?.activeQuests,records:st.info.quests?.records},',
127
+ ' airbase:st.info.airbase',
128
+ ' }))}',
129
+ ' else{r.writeHead(404);r.end("Not found")}',
130
+ ' }catch(e){r.writeHead(500);r.end(e.message)}',
131
+ '});',
132
+ 's.listen(p,"127.0.0.1",function(){',
133
+ ' var d=require("path").join(require("os").homedir(),".poi-mcp");',
134
+ ' try{require("fs").mkdirSync(d,{recursive:true})}catch(e){}',
135
+ ' require("fs").writeFileSync(require("path").join(d,"port"),String(p),"utf8");',
136
+ ' console.log("[poi-mcp] API: http://127.0.0.1:"+p+" (/fleets /ships /equipment /resources /quests /airbase /basic /all)");',
137
+ '});',
138
+ '})()'
139
+ ].join('\n')
140
+ }
141
+
142
+ // ─── MCP Protocol ────────────────────────────────────────────────────────────
143
+
144
+ // Minimum viable MCP stdio server — no external dependencies
145
+
146
+ const JSONRPC_VERSION = '2.0'
147
+ let reqId = 0
148
+
149
+ function send(id, result, error) {
150
+ const msg = { jsonrpc: JSONRPC_VERSION }
151
+ if (id != null) msg.id = id
152
+ if (error) msg.error = { code: error.code || -32603, message: error.message }
153
+ else msg.result = result
154
+ process.stdout.write(JSON.stringify(msg) + '\n')
155
+ }
156
+
157
+ function sendLog(text) {
158
+ console.error('[poi-mcp] ' + text)
159
+ }
160
+
161
+ // ─── Main ────────────────────────────────────────────────────────────────────
162
+
163
+ async function main() {
164
+ sendLog('POI MCP Server v0.1.0')
165
+ sendLog('Checking POI data API...')
166
+
167
+ const port = getPoiPort()
168
+ if (!port) {
169
+ sendLog('NOT CONNECTED — POI DevTools script not running')
170
+ sendLog('')
171
+ sendLog('=== 请在 POI 中执行以下步骤 ===')
172
+ sendLog('1. 启动 POI,进入游戏母港')
173
+ sendLog('2. 按 F12 打开 DevTools → Console 标签')
174
+ sendLog('3. 粘贴下面一整段脚本,按回车:')
175
+ sendLog('')
176
+ console.error(getInjectScript())
177
+ sendLog('')
178
+ sendLog('4. 关闭 DevTools,重新运行本命令')
179
+ process.exit(1)
180
+ }
181
+
182
+ // Verify the API is responding
183
+ try {
184
+ const health = await fetchFromPoi('/health')
185
+ sendLog(`Connected to POI API on port ${port}: ${health.status}`)
186
+ } catch (err) {
187
+ sendLog(`ERROR: POI API on port ${port} is not responding: ${err.message}`)
188
+ sendLog('Make sure POI is running and the script was pasted into DevTools Console.')
189
+ process.exit(1)
190
+ }
191
+
192
+ // ── MCP Request Handler ──────────────────────────────────────────────
193
+
194
+ const toolHandlers = {
195
+ get_fleet_status: async (args) => {
196
+ const fleets = await fetchFromPoi('/fleets')
197
+ if (!Array.isArray(fleets)) return { error: 'No fleet data' }
198
+ const fleet = fleets[args.fleetId - 1]
199
+ if (!fleet) return { error: `Fleet #${args.fleetId} not found` }
200
+
201
+ // Enrich with ship names and equipment
202
+ const ships = await fetchFromPoi('/ships')
203
+ const equips = await fetchFromPoi('/equipment')
204
+ return {
205
+ id: fleet.api_id,
206
+ name: fleet.api_name,
207
+ mission: fleet.api_mission,
208
+ ships: (fleet.api_ship || []).filter(id => id > 0).map(sid => {
209
+ const s = ships[sid]
210
+ if (!s) return { id: sid }
211
+ return {
212
+ id: s.api_id,
213
+ shipId: s.api_ship_id,
214
+ level: s.api_lv,
215
+ hp: `${s.api_nowhp}/${s.api_maxhp}`,
216
+ morale: s.api_cond,
217
+ locked: s.api_locked,
218
+ slotItems: (s.api_slot || []).filter(eid => eid > 0).map(eid => {
219
+ const e = equips[eid]
220
+ return e ? { equipId: e.api_slotitem_id, level: e.api_level || 0, prof: e.api_alv || 0 } : null
221
+ }).filter(Boolean)
222
+ }
223
+ })
224
+ }
225
+ },
226
+
227
+ search_ships: async (args) => {
228
+ const ships = await fetchFromPoi('/ships')
229
+ const results = Object.values(ships).filter(s => {
230
+ if (!s) return false
231
+ if (args.minLevel != null && s.api_lv < args.minLevel) return false
232
+ if (args.maxLevel != null && s.api_lv > args.maxLevel) return false
233
+ if (args.minMorale != null && s.api_cond < args.minMorale) return false
234
+ return true
235
+ })
236
+ return { total: results.length, ships: results }
237
+ },
238
+
239
+ search_equipment: async (args) => {
240
+ const equips = await fetchFromPoi('/equipment')
241
+ const results = Object.values(equips).filter(e => {
242
+ if (!e) return false
243
+ if (args.minLevel != null && (e.api_level || 0) < args.minLevel) return false
244
+ return true
245
+ })
246
+ return { total: results.length, equipment: results }
247
+ },
248
+
249
+ get_resources: async () => {
250
+ return await fetchFromPoi('/resources')
251
+ },
252
+
253
+ get_all: async () => {
254
+ return await fetchFromPoi('/all')
255
+ }
256
+ }
257
+
258
+ const resourceUris = [
259
+ 'poi://fleets', 'poi://ships', 'poi://equipment',
260
+ 'poi://resources', 'poi://quests', 'poi://airbase', 'poi://basic', 'poi://all'
261
+ ]
262
+
263
+ // ── JSON-RPC over stdio ──────────────────────────────────────────────
264
+
265
+ let buffer = ''
266
+ process.stdin.setEncoding('utf8')
267
+ process.stdin.on('data', async (chunk) => {
268
+ buffer += chunk
269
+ const lines = buffer.split('\n')
270
+ buffer = lines.pop() || ''
271
+ for (const line of lines) {
272
+ if (!line.trim()) continue
273
+ try {
274
+ const req = JSON.parse(line)
275
+ const { id, method, params } = req
276
+
277
+ switch (method) {
278
+ case 'initialize':
279
+ send(id, {
280
+ protocolVersion: '2024-11-05',
281
+ capabilities: {
282
+ resources: { subscribe: false },
283
+ tools: {}
284
+ },
285
+ serverInfo: { name: 'poi-mcp', version: '0.1.0' }
286
+ })
287
+ break
288
+
289
+ case 'notifications/initialized':
290
+ case 'notifications/cancelled':
291
+ break
292
+
293
+ case 'ping':
294
+ send(id, {})
295
+ break
296
+
297
+ case 'resources/list':
298
+ send(id, {
299
+ resources: resourceUris.map(uri => ({
300
+ uri, name: uri.replace('poi://', ''), mimeType: 'application/json'
301
+ }))
302
+ })
303
+ break
304
+
305
+ case 'resources/read': {
306
+ const uri = params?.uri
307
+ const endpoint = '/' + uri.replace('poi://', '')
308
+ const data = await fetchFromPoi(endpoint)
309
+ send(id, {
310
+ contents: [{
311
+ uri,
312
+ mimeType: 'application/json',
313
+ text: JSON.stringify(data, null, 2)
314
+ }]
315
+ })
316
+ break
317
+ }
318
+
319
+ case 'tools/list':
320
+ send(id, {
321
+ tools: [
322
+ {
323
+ name: 'get_fleet_status',
324
+ description: '获取舰队详细编成',
325
+ inputSchema: {
326
+ type: 'object',
327
+ properties: { fleetId: { type: 'number', description: '舰队编号 1-4' } },
328
+ required: ['fleetId']
329
+ }
330
+ },
331
+ {
332
+ name: 'search_ships',
333
+ description: '搜索舰娘',
334
+ inputSchema: {
335
+ type: 'object',
336
+ properties: {
337
+ minLevel: { type: 'number' },
338
+ maxLevel: { type: 'number' },
339
+ minMorale: { type: 'number', description: '最低士气(闪)' }
340
+ }
341
+ }
342
+ },
343
+ {
344
+ name: 'search_equipment',
345
+ description: '搜索装备',
346
+ inputSchema: {
347
+ type: 'object',
348
+ properties: {
349
+ minLevel: { type: 'number', description: '最低改修★' }
350
+ }
351
+ }
352
+ },
353
+ {
354
+ name: 'get_resources',
355
+ description: '获取资源概况',
356
+ inputSchema: { type: 'object', properties: {} }
357
+ },
358
+ {
359
+ name: 'get_all',
360
+ description: '获取所有数据(舰队/舰娘/装备/资源/任务/陆航)',
361
+ inputSchema: { type: 'object', properties: {} }
362
+ }
363
+ ]
364
+ })
365
+ break
366
+
367
+ case 'tools/call': {
368
+ const toolName = params?.name
369
+ const toolArgs = params?.arguments || {}
370
+ const handler = toolHandlers[toolName]
371
+ if (handler) {
372
+ const result = await handler(toolArgs)
373
+ send(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] })
374
+ } else {
375
+ send(id, null, { code: -32602, message: `Unknown tool: ${toolName}` })
376
+ }
377
+ break
378
+ }
379
+
380
+ default:
381
+ send(id, null, { code: -32601, message: `Unknown method: ${method}` })
382
+ }
383
+ } catch (err) {
384
+ // Malformed JSON — ignore
385
+ }
386
+ }
387
+ })
388
+
389
+ process.stdin.on('end', () => process.exit(0))
390
+ process.on('SIGINT', () => process.exit(0))
391
+ process.on('SIGTERM', () => process.exit(0))
392
+ }
393
+
394
+ main().catch(err => {
395
+ console.error('[poi-mcp] Fatal:', err.message)
396
+ process.exit(1)
397
+ })
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "poi-plugin-mcp",
3
+ "version": "0.2.0",
4
+ "description": "Poi data bridge for local KanColle inventory tools.",
5
+ "main": "index.js",
6
+ "keywords": [
7
+ "poi-plugin",
8
+ "kancolle",
9
+ "mcp"
10
+ ],
11
+ "files": [
12
+ "index.js",
13
+ "lib",
14
+ "mcp-server.js",
15
+ "README.md"
16
+ ],
17
+ "bin": {
18
+ "poi-mcp": "mcp-server.js"
19
+ },
20
+ "scripts": {
21
+ "start": "node mcp-server.js",
22
+ "test": "node --test test/*.test.js"
23
+ },
24
+ "poiPlugin": {
25
+ "title": "MCP 数据桥",
26
+ "id": "mcp_data_bridge",
27
+ "description": "Expose Poi fleet, equipment, master, event tag, and planner data on local HTTP for CLI/MCP agents.",
28
+ "icon": "fa/database",
29
+ "priority": 53
30
+ },
31
+ "license": "MIT"
32
+ }