@rikaidev/yomi 0.2.6

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.
Files changed (119) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +81 -0
  3. package/PRIVACY.md +39 -0
  4. package/README.md +659 -0
  5. package/dist/auth/credential-store.js +395 -0
  6. package/dist/auth/keychain.js +256 -0
  7. package/dist/cli/login.js +142 -0
  8. package/dist/line/auth/protocol/e2ee-bootstrap.js +87 -0
  9. package/dist/line/auth/protocol/index.js +3 -0
  10. package/dist/line/auth/protocol/login-metadata.js +159 -0
  11. package/dist/line/auth/protocol/pwless-login-flow.js +83 -0
  12. package/dist/line/auth/pwless/index.js +163 -0
  13. package/dist/line/auth/pwless/steps.js +213 -0
  14. package/dist/line/client/auth-service/client.js +240 -0
  15. package/dist/line/client/e2ee-service/client.js +69 -0
  16. package/dist/line/client/e2ee-service/requests.js +92 -0
  17. package/dist/line/client/e2ee-service/shared-key-log.js +24 -0
  18. package/dist/line/client/e2ee-service/shared-key-payload.js +35 -0
  19. package/dist/line/client/index.js +213 -0
  20. package/dist/line/client/obs-media-client.js +140 -0
  21. package/dist/line/client/parsers.js +69 -0
  22. package/dist/line/client/shop-service/client.js +63 -0
  23. package/dist/line/client/sticker-meta.js +46 -0
  24. package/dist/line/client/sync-service/client.js +317 -0
  25. package/dist/line/client/talk-service/chat-query.js +122 -0
  26. package/dist/line/client/talk-service/command-client.js +242 -0
  27. package/dist/line/client/talk-service/contact-query.js +77 -0
  28. package/dist/line/client/talk-service/message-box-query.js +137 -0
  29. package/dist/line/client/talk-service/message-id-query.js +241 -0
  30. package/dist/line/client/talk-service/query-client.js +219 -0
  31. package/dist/line/client/talk-service/requests.js +383 -0
  32. package/dist/line/client/transport.js +138 -0
  33. package/dist/line/core/auth-session-logout.js +55 -0
  34. package/dist/line/core/auth-session-runtime.js +231 -0
  35. package/dist/line/core/auth-session-service.js +85 -0
  36. package/dist/line/core/chat-runtime-service.js +343 -0
  37. package/dist/line/core/client-runtime.js +17 -0
  38. package/dist/line/core/config.js +67 -0
  39. package/dist/line/core/constants.js +56 -0
  40. package/dist/line/core/contact-query-service.js +20 -0
  41. package/dist/line/core/directory-runtime-service.js +83 -0
  42. package/dist/line/core/e2ee/bootstrap/keychain.js +41 -0
  43. package/dist/line/core/e2ee/crypto/crypto-primitives.js +98 -0
  44. package/dist/line/core/e2ee/index.js +6 -0
  45. package/dist/line/core/e2ee/keys/decrypt.js +110 -0
  46. package/dist/line/core/e2ee/keys/encrypt.js +117 -0
  47. package/dist/line/core/e2ee/keys/group-key.js +260 -0
  48. package/dist/line/core/e2ee/keys/key-manager.js +231 -0
  49. package/dist/line/core/e2ee/keys/key-payload.js +130 -0
  50. package/dist/line/core/e2ee/keys/key-types.js +1 -0
  51. package/dist/line/core/e2ee/keys/message-crypto.js +146 -0
  52. package/dist/line/core/e2ee/keys/message-decrypt.js +390 -0
  53. package/dist/line/core/e2ee/keys/peer-key.js +191 -0
  54. package/dist/line/core/e2ee/recovery/bootstrap-keys.js +161 -0
  55. package/dist/line/core/e2ee/types.js +4 -0
  56. package/dist/line/core/group-query-service.js +20 -0
  57. package/dist/line/core/media-decrypt.js +78 -0
  58. package/dist/line/core/media-encrypt.js +97 -0
  59. package/dist/line/core/mention.js +89 -0
  60. package/dist/line/core/message-command-service.js +454 -0
  61. package/dist/line/core/message-query-service.js +161 -0
  62. package/dist/line/core/service.js +134 -0
  63. package/dist/line/core/session-state/index.js +243 -0
  64. package/dist/line/core/session-state/persist.js +192 -0
  65. package/dist/line/core/store.js +8 -0
  66. package/dist/line/core/thrift/binary/binary-reader.js +151 -0
  67. package/dist/line/core/thrift/binary/binary-writer.js +157 -0
  68. package/dist/line/core/thrift/binary/type-codec-dsl.js +42 -0
  69. package/dist/line/core/thrift/compact/compact-reader.js +180 -0
  70. package/dist/line/core/thrift/compact/compact-writer.js +161 -0
  71. package/dist/line/core/thrift/compact/type-codec-dsl.js +66 -0
  72. package/dist/line/core/thrift/fields/builders.js +124 -0
  73. package/dist/line/core/thrift/index.js +12 -0
  74. package/dist/line/core/thrift/message/decoder.js +55 -0
  75. package/dist/line/core/thrift/message/encoder.js +25 -0
  76. package/dist/line/core/thrift/primitives/varint.js +64 -0
  77. package/dist/line/core/thrift/struct/reader.js +172 -0
  78. package/dist/line/core/thrift/struct/writer.js +189 -0
  79. package/dist/line/core/thrift/types.js +23 -0
  80. package/dist/line/core/video-thumbnail.js +104 -0
  81. package/dist/mcp/handlers/contacts.js +166 -0
  82. package/dist/mcp/handlers/conversations.js +193 -0
  83. package/dist/mcp/handlers/groups.js +123 -0
  84. package/dist/mcp/handlers/index.js +15 -0
  85. package/dist/mcp/handlers/login.js +473 -0
  86. package/dist/mcp/handlers/media.js +69 -0
  87. package/dist/mcp/handlers/message-actions.js +62 -0
  88. package/dist/mcp/handlers/scope.js +120 -0
  89. package/dist/mcp/handlers/search.js +196 -0
  90. package/dist/mcp/handlers/send.js +523 -0
  91. package/dist/mcp/handlers/shared.js +84 -0
  92. package/dist/mcp/handlers/stickers.js +87 -0
  93. package/dist/mcp/media.js +228 -0
  94. package/dist/mcp/names.js +133 -0
  95. package/dist/mcp/phi-guard.js +105 -0
  96. package/dist/mcp/policy.js +34 -0
  97. package/dist/mcp/server.js +301 -0
  98. package/dist/mcp/tools.js +761 -0
  99. package/dist/mcp/ui/capability.js +26 -0
  100. package/dist/mcp/ui/login-app-protocol.js +118 -0
  101. package/dist/mcp/ui/login-app-styles.js +142 -0
  102. package/dist/mcp/ui/login-app-view.js +361 -0
  103. package/dist/mcp/ui/login-app.js +72 -0
  104. package/dist/mcp/ui/login-structured-content.js +1 -0
  105. package/dist/mcp/ui/resource.js +35 -0
  106. package/dist/mcp/ui/tools-with-ui.js +40 -0
  107. package/dist/search/bigram.js +86 -0
  108. package/dist/search/capture.js +140 -0
  109. package/dist/search/collector.js +176 -0
  110. package/dist/search/default-embedder.js +101 -0
  111. package/dist/search/embedder.js +13 -0
  112. package/dist/search/scope.js +142 -0
  113. package/dist/search/sqlite.js +141 -0
  114. package/dist/search/store.js +469 -0
  115. package/dist/util/data-dir.js +55 -0
  116. package/dist/util/log.js +86 -0
  117. package/dist/version.js +7 -0
  118. package/package.json +63 -0
  119. package/run.mjs +71 -0
@@ -0,0 +1,761 @@
1
+ /**
2
+ * Yomi MCP tool schemas — the full TOOLS list served by ListToolsRequest
3
+ * and dispatched by CallToolRequest in server.ts.
4
+ *
5
+ * Split out from server.ts purely to keep server.ts (the wiring/dispatch
6
+ * file) under the project's 200-scc-line cap; behavior owned here is
7
+ * unchanged from what previously lived inline in server.ts.
8
+ *
9
+ * Descriptions are deliberately terse: the tool list renders at prompt
10
+ * position 0 on every request, so trimming boilerplate here directly lowers
11
+ * the fixed per-request token cost every connected client pays.
12
+ */
13
+ export const TOOLS = [
14
+ {
15
+ description: 'Log in to LINE via the passwordless secondary-device flow. Requires 允許自其他裝置登入 enabled on the primary phone. On MCP clients with elicitation, this call prompts for phone/region and PIN and completes login by itself. On clients without it (e.g. Claude Desktop), phone/region come from the arguments or a persisted login, and this returns as soon as LINE issues the PIN (or reports none needed) — then call login_complete IMMEDIATELY (do not wait for the human). LINE gives ~3 minutes from PIN display to confirm on the phone; login_complete blocks past that, so calling it late only wastes that window.',
16
+ inputSchema: {
17
+ type: 'object',
18
+ properties: {
19
+ phone: {
20
+ type: 'string',
21
+ description: 'Phone number in E.164 form, e.g. +8869XXXXXXXX. Omit to be prompted (elicitation) or to reuse a persisted number.',
22
+ },
23
+ region: {
24
+ type: 'string',
25
+ description: 'Region code, e.g. TW, JP, TH, ID, US. Omit to be prompted (elicitation) or to reuse a persisted region.',
26
+ },
27
+ },
28
+ },
29
+ name: 'login',
30
+ },
31
+ {
32
+ description: "Finish a passwordless login that `login` started on a client without elicitation (not needed on elicitation-capable clients). No arguments. Call immediately after `login` returns — do not wait for the human. Blocks while they enter the PIN (skipped if a stored certificate is valid) and approve the device, then returns the profile. LINE's real deadline is ~3 minutes from PIN display. Errors if no login is pending.",
33
+ inputSchema: {
34
+ type: 'object',
35
+ properties: {},
36
+ },
37
+ name: 'login_complete',
38
+ },
39
+ {
40
+ description: "List LINE conversations (chats, groups, rooms) with unread counts, a preview of the last message, and a human-readable name (group title, or the other party's display name for a 1:1).",
41
+ inputSchema: {
42
+ type: 'object',
43
+ properties: {
44
+ limit: {
45
+ type: 'number',
46
+ description: 'Maximum number of conversations to return (default 20).',
47
+ },
48
+ },
49
+ },
50
+ name: 'list_conversations',
51
+ },
52
+ {
53
+ description: 'Fetch messages from one LINE conversation. Text is E2EE-decrypted when keys are available; each message has fromName (resolved sender), a mediaType flag (image/video/audio/file) plus messageId for get_message_media, and `mentions` (LINE\'s raw contentMetadata.MENTION, or null — a literal "@name" in `text` is not itself a mention). Without `before`, returns the most recent `count`. With `before` (id/deliveredTime of the oldest message already seen), returns one older page — repeat to page further back.',
54
+ inputSchema: {
55
+ type: 'object',
56
+ properties: {
57
+ chatId: {
58
+ type: 'string',
59
+ description: 'LINE chat/group/room MID, as returned by list_conversations.',
60
+ },
61
+ count: {
62
+ type: 'number',
63
+ description: 'Maximum number of messages to return (default 50).',
64
+ },
65
+ before: {
66
+ type: 'object',
67
+ description: 'Cursor to fetch messages older than this point. Use the id and/or deliveredTime of the oldest message already seen.',
68
+ properties: {
69
+ messageId: {
70
+ type: 'string',
71
+ description: 'LINE message id of the cursor message.',
72
+ },
73
+ deliveredTime: {
74
+ type: 'number',
75
+ description: 'deliveredTime of the cursor message.',
76
+ },
77
+ },
78
+ },
79
+ },
80
+ required: ['chatId'],
81
+ },
82
+ name: 'get_chat_messages',
83
+ },
84
+ {
85
+ description: 'Download and decrypt one LINE image message. Legacy alias of get_message_media restricted to images; prefer get_message_media for video/audio/file.',
86
+ inputSchema: {
87
+ type: 'object',
88
+ properties: {
89
+ chatId: {
90
+ type: 'string',
91
+ description: 'LINE chat/group/room MID the message belongs to. Required to locate E2EE key material.',
92
+ },
93
+ messageId: {
94
+ type: 'string',
95
+ description: 'LINE message id, as returned by get_chat_messages.',
96
+ },
97
+ preview: {
98
+ type: 'boolean',
99
+ description: 'Fetch the smaller preview object instead of the full-resolution original.',
100
+ },
101
+ },
102
+ required: ['chatId', 'messageId'],
103
+ },
104
+ name: 'get_message_image',
105
+ },
106
+ {
107
+ description: 'Download and decrypt one LINE media message of any downloadable type (image, video, audio, file). Returns image/audio MCP content, or an embedded resource blob (with filename when known) for video/file. Non-media messages (text, sticker refs, unsupported types) return an honest error naming the content type — never fabricated bytes.',
108
+ inputSchema: {
109
+ type: 'object',
110
+ properties: {
111
+ chatId: {
112
+ type: 'string',
113
+ description: 'LINE chat/group/room MID the message belongs to. Required to locate E2EE key material.',
114
+ },
115
+ messageId: {
116
+ type: 'string',
117
+ description: 'LINE message id, as returned by get_chat_messages.',
118
+ },
119
+ preview: {
120
+ type: 'boolean',
121
+ description: 'Fetch the smaller preview object instead of the full-resolution original (images/video only).',
122
+ },
123
+ },
124
+ required: ['chatId', 'messageId'],
125
+ },
126
+ name: 'get_message_media',
127
+ },
128
+ {
129
+ description: 'One-shot unread digest: every LINE conversation with unread messages, each with its most recent messages (default 10), E2EE-decrypted, with resolved sender names. Saves calling list_conversations then get_chat_messages per chat. Read-only: never marks anything read, never touches the search index; denylist-excluded conversations are omitted. Returns an empty list when nothing is unread — never fabricated.',
130
+ inputSchema: {
131
+ type: 'object',
132
+ properties: {
133
+ perChat: {
134
+ type: 'number',
135
+ description: 'Maximum recent messages to include per unread conversation (default 10).',
136
+ },
137
+ limit: {
138
+ type: 'number',
139
+ description: 'Maximum number of conversations to scan for unread (default 20).',
140
+ },
141
+ },
142
+ },
143
+ name: 'get_unread_digest',
144
+ },
145
+ {
146
+ description: 'Sends a text message to a LINE conversation immediately (not a draft). Always E2EE (pairwise for 1:1, group key for group/room); fails honestly rather than sending plaintext if the key cannot be resolved. One send per call. To @mention someone, put the visible "@name " into `text` AND pass a matching `mentions` entry — without `mentions`, "@name" is plain text and notifies no one. Resolve MIDs via get_group_members or find_contact first.',
147
+ inputSchema: {
148
+ type: 'object',
149
+ properties: {
150
+ chatId: {
151
+ type: 'string',
152
+ description: 'LINE chat/group/room MID to send to, as returned by list_conversations.',
153
+ },
154
+ text: {
155
+ type: 'string',
156
+ description: 'Plain-text message body, including the literal "@name" text for any mentions — mentions only mark up text that is already there.',
157
+ },
158
+ mentions: {
159
+ type: 'array',
160
+ description: 'Optional @mentions. Each entry marks a span of `text` as a mention of one user, which LINE highlights and notifies. Omit to send `text` as plain, non-notifying text.',
161
+ items: {
162
+ type: 'object',
163
+ properties: {
164
+ mid: {
165
+ type: 'string',
166
+ description: 'MID of the user being mentioned, as returned by get_group_members or find_contact.',
167
+ },
168
+ start: {
169
+ type: 'number',
170
+ description: 'Start offset (inclusive) of the "@name" run in `text`, in UTF-16 code units (plain JS string index).',
171
+ },
172
+ end: {
173
+ type: 'number',
174
+ description: 'End offset (exclusive) of the "@name" run in `text` — half-open range [start, end), UTF-16 code units.',
175
+ },
176
+ },
177
+ required: ['mid', 'start', 'end'],
178
+ },
179
+ },
180
+ replyToMessageId: {
181
+ type: 'string',
182
+ description: 'Optional message id (from get_chat_messages) this replies to — LINE renders a quoted reply. Omit for a normal message.',
183
+ },
184
+ },
185
+ required: ['chatId', 'text'],
186
+ },
187
+ name: 'send_message',
188
+ },
189
+ {
190
+ description: 'Sends an E2EE image to a LINE conversation immediately (encrypt → upload to OBS → send). Works for 1:1/group/room; fails honestly if the key cannot be resolved or the upload is rejected. One send per call. Provide exactly one of imagePath or imageBase64.',
191
+ inputSchema: {
192
+ type: 'object',
193
+ properties: {
194
+ chatId: {
195
+ type: 'string',
196
+ description: 'LINE chat/group/room MID to send to, as returned by list_conversations.',
197
+ },
198
+ imagePath: {
199
+ type: 'string',
200
+ description: 'Local filesystem path to the image file. Mutually exclusive with imageBase64.',
201
+ },
202
+ imageBase64: {
203
+ type: 'string',
204
+ description: 'Base64-encoded image bytes. Mutually exclusive with imagePath.',
205
+ },
206
+ },
207
+ required: ['chatId'],
208
+ },
209
+ name: 'send_image',
210
+ },
211
+ {
212
+ description: 'Sends an E2EE file attachment (any type — .docx, .pdf, .zip, …) to a LINE conversation immediately (same pipeline as send_image; the original filename is sealed end-to-end). Works for 1:1/group/room; fails honestly if the key cannot be resolved or the upload is rejected. One send per call. Provide exactly one of filePath or fileBase64; fileName is required with fileBase64 (and overrides the basename when given with filePath).',
213
+ inputSchema: {
214
+ type: 'object',
215
+ properties: {
216
+ chatId: {
217
+ type: 'string',
218
+ description: 'LINE chat/group/room MID to send to, as returned by list_conversations.',
219
+ },
220
+ filePath: {
221
+ type: 'string',
222
+ description: 'Local filesystem path to the file. Mutually exclusive with fileBase64.',
223
+ },
224
+ fileBase64: {
225
+ type: 'string',
226
+ description: 'Base64-encoded file bytes. Mutually exclusive with filePath; requires fileName.',
227
+ },
228
+ fileName: {
229
+ type: 'string',
230
+ description: 'Original filename shown to the recipient (sealed E2EE). Required with fileBase64; optional with filePath (defaults to its basename).',
231
+ },
232
+ },
233
+ required: ['chatId'],
234
+ },
235
+ name: 'send_file',
236
+ },
237
+ {
238
+ description: 'Sends an E2EE audio message to a LINE conversation immediately (same pipeline as send_file). Provide exactly one of filePath or audioBase64; optional durationMs sets the recipient player length. Works for 1:1/group/room. One send per call.',
239
+ inputSchema: {
240
+ type: 'object',
241
+ properties: {
242
+ chatId: {
243
+ type: 'string',
244
+ description: 'LINE chat/group/room MID to send to, as returned by list_conversations.',
245
+ },
246
+ filePath: {
247
+ type: 'string',
248
+ description: 'Local filesystem path to the audio file. Mutually exclusive with audioBase64.',
249
+ },
250
+ audioBase64: {
251
+ type: 'string',
252
+ description: 'Base64-encoded audio bytes. Mutually exclusive with filePath.',
253
+ },
254
+ fileName: {
255
+ type: 'string',
256
+ description: 'Optional original filename (used for the upload name; defaults to the basename of filePath or audio.m4a).',
257
+ },
258
+ durationMs: {
259
+ type: 'number',
260
+ description: 'Optional audio duration in milliseconds, for the recipient player progress bar.',
261
+ },
262
+ },
263
+ required: ['chatId'],
264
+ },
265
+ name: 'send_audio',
266
+ },
267
+ {
268
+ description: 'Sends an E2EE video to a LINE conversation immediately (same pipeline as send_file; uses LINE chunked video encryption so it plays and integrity-verifies on official clients). Provide exactly one of filePath or videoBase64; optional durationMs sets the scrubber length. Works for 1:1/group/room. One send per call.',
269
+ inputSchema: {
270
+ type: 'object',
271
+ properties: {
272
+ chatId: {
273
+ type: 'string',
274
+ description: 'LINE chat/group/room MID to send to, as returned by list_conversations.',
275
+ },
276
+ filePath: {
277
+ type: 'string',
278
+ description: 'Local filesystem path to the video file. Mutually exclusive with videoBase64.',
279
+ },
280
+ videoBase64: {
281
+ type: 'string',
282
+ description: 'Base64-encoded video bytes. Mutually exclusive with filePath.',
283
+ },
284
+ fileName: {
285
+ type: 'string',
286
+ description: 'Optional original filename (used for the upload name; defaults to the basename of filePath or video.mp4).',
287
+ },
288
+ durationMs: {
289
+ type: 'number',
290
+ description: 'Optional video duration in milliseconds, for the recipient player scrubber.',
291
+ },
292
+ },
293
+ required: ['chatId'],
294
+ },
295
+ name: 'send_video',
296
+ },
297
+ {
298
+ description: 'Sends a location (map pin) to a LINE conversation immediately — latitude/longitude plus optional title (place name) and address. Works for 1:1/group/room. One send per call.',
299
+ inputSchema: {
300
+ type: 'object',
301
+ properties: {
302
+ chatId: {
303
+ type: 'string',
304
+ description: 'LINE chat/group/room MID to send to, as returned by list_conversations.',
305
+ },
306
+ latitude: {
307
+ type: 'number',
308
+ description: 'Latitude in decimal degrees.',
309
+ },
310
+ longitude: {
311
+ type: 'number',
312
+ description: 'Longitude in decimal degrees.',
313
+ },
314
+ title: {
315
+ type: 'string',
316
+ description: 'Optional place name shown on the pin.',
317
+ },
318
+ address: {
319
+ type: 'string',
320
+ description: 'Optional address shown under the pin.',
321
+ },
322
+ },
323
+ required: ['chatId', 'latitude', 'longitude'],
324
+ },
325
+ name: 'send_location',
326
+ },
327
+ {
328
+ description: 'Shares a LINE contact card to a conversation immediately — the recipient sees a tappable card for `contactMid` (e.g. from find_contact or get_group_members). `displayName` is optional (resolved from the mid when omitted). Works for 1:1/group/room. One send per call.',
329
+ inputSchema: {
330
+ type: 'object',
331
+ properties: {
332
+ chatId: {
333
+ type: 'string',
334
+ description: 'LINE chat/group/room MID to send to, as returned by list_conversations.',
335
+ },
336
+ contactMid: {
337
+ type: 'string',
338
+ description: 'MID of the person whose contact card to share, as returned by find_contact or get_group_members.',
339
+ },
340
+ displayName: {
341
+ type: 'string',
342
+ description: 'Optional display name for the card. Resolved from contactMid when omitted.',
343
+ },
344
+ },
345
+ required: ['chatId', 'contactMid'],
346
+ },
347
+ name: 'send_contact',
348
+ },
349
+ {
350
+ description: 'Sends a LINE sticker to a conversation immediately, named by stickerId (STKID) + packageId (STKPKGID). Only OWNED stickers can be sent — get ids from search_stickers/list_stickers. Works for 1:1/group/room. One send per call.',
351
+ inputSchema: {
352
+ type: 'object',
353
+ properties: {
354
+ chatId: {
355
+ type: 'string',
356
+ description: 'LINE chat/group/room MID to send to, as returned by list_conversations.',
357
+ },
358
+ stickerId: {
359
+ type: 'string',
360
+ description: 'LINE sticker id (STKID).',
361
+ },
362
+ packageId: {
363
+ type: 'string',
364
+ description: 'LINE sticker package id (STKPKGID).',
365
+ },
366
+ version: {
367
+ type: 'string',
368
+ description: 'Sticker version (STKVER). Defaults to "1".',
369
+ },
370
+ },
371
+ required: ['chatId', 'stickerId', 'packageId'],
372
+ },
373
+ name: 'send_sticker',
374
+ },
375
+ {
376
+ description: "List the sticker packages this LINE account OWNS — the only stickers it can send. Returns each package's packageId (STKPKGID), title, and version (STKVER). Get individual sticker ids via search_stickers, then send_sticker. Read-only.",
377
+ inputSchema: {
378
+ type: 'object',
379
+ properties: {
380
+ language: {
381
+ type: 'string',
382
+ description: "Locale for package titles, e.g. 'en' or 'zh-Hant'. Defaults to 'en'.",
383
+ },
384
+ },
385
+ },
386
+ name: 'list_stickers',
387
+ },
388
+ {
389
+ description: "Search the account's OWNED sticker packages by title and expand each match into its individual sticker ids (STKID), ready for send_sticker. Case-insensitive substring match on the package title. Read-only.",
390
+ inputSchema: {
391
+ type: 'object',
392
+ properties: {
393
+ query: {
394
+ type: 'string',
395
+ description: 'Substring to match against owned package titles (case-insensitive).',
396
+ },
397
+ language: {
398
+ type: 'string',
399
+ description: "Locale for package titles to match against, e.g. 'en' or 'zh-Hant'. Defaults to 'en'.",
400
+ },
401
+ limit: {
402
+ type: 'number',
403
+ description: 'Max matching packages to expand with sticker ids (default 8).',
404
+ },
405
+ },
406
+ required: ['query'],
407
+ },
408
+ name: 'search_stickers',
409
+ },
410
+ {
411
+ description: 'Show sticker preview images (MCP image content from the public sticker CDN) so you and the user can SEE them before sending. Give a packageId (from list_stickers/search_stickers) to preview its first stickers, or add a stickerId to preview just one. Each image is labeled with its stickerId + packageId for send_sticker. Read-only.',
412
+ inputSchema: {
413
+ type: 'object',
414
+ properties: {
415
+ packageId: {
416
+ type: 'string',
417
+ description: 'Sticker package id (STKPKGID) to preview.',
418
+ },
419
+ stickerId: {
420
+ type: 'string',
421
+ description: 'Optional specific sticker id (STKID) to preview just that sticker.',
422
+ },
423
+ limit: {
424
+ type: 'number',
425
+ description: 'Max stickers to preview when no stickerId is given (default 8).',
426
+ },
427
+ },
428
+ required: ['packageId'],
429
+ },
430
+ name: 'preview_sticker',
431
+ },
432
+ {
433
+ description: 'Send a LINE read receipt (mark a conversation read up to `messageId`, or the latest when omitted) — a real action the other party can see. Use ONLY when the user explicitly wants to mark a chat read; reading (get_chat_messages, get_unread_digest) and background capture never mark read. Fails honestly if there is nothing to mark.',
434
+ inputSchema: {
435
+ type: 'object',
436
+ properties: {
437
+ chatId: {
438
+ type: 'string',
439
+ description: 'LINE chat/group/room MID to mark read, as returned by list_conversations.',
440
+ },
441
+ messageId: {
442
+ type: 'string',
443
+ description: 'Optional message id to mark read up to. Omit to mark read up to the latest message.',
444
+ },
445
+ },
446
+ required: ['chatId'],
447
+ },
448
+ name: 'mark_read',
449
+ },
450
+ {
451
+ description: "Find LINE friends whose display name contains `name` (case-insensitive substring). Returns each match's mid for send_message. Raw friend-list lookup — no ranking, no fuzzy scoring.",
452
+ inputSchema: {
453
+ type: 'object',
454
+ properties: {
455
+ name: {
456
+ type: 'string',
457
+ description: "Substring to match against friends' display names, case-insensitive.",
458
+ },
459
+ },
460
+ required: ['name'],
461
+ },
462
+ name: 'find_contact',
463
+ },
464
+ {
465
+ description: "List the authenticated user's full LINE friend list as-is (mid + displayName). No ranking, no interaction-frequency ordering.",
466
+ inputSchema: {
467
+ type: 'object',
468
+ properties: {},
469
+ },
470
+ name: 'list_contacts',
471
+ },
472
+ {
473
+ description: 'List the members of one LINE group (mid + displayName each). Resolves persistent groups (`c...`); ad-hoc rooms (`r...`) without a group record return an honest error, never a fabricated empty list.',
474
+ inputSchema: {
475
+ type: 'object',
476
+ properties: {
477
+ chatId: {
478
+ type: 'string',
479
+ description: 'LINE group chat MID, as returned by list_conversations.',
480
+ },
481
+ },
482
+ required: ['chatId'],
483
+ },
484
+ name: 'get_group_members',
485
+ },
486
+ {
487
+ description: 'Renames a LINE group/chat immediately; the new name is visible to every member. Works on groups/rooms (chatId starting with `c`/`r`). One rename per call.',
488
+ inputSchema: {
489
+ type: 'object',
490
+ properties: {
491
+ chatId: {
492
+ type: 'string',
493
+ description: 'LINE group/room MID to rename, as returned by list_conversations.',
494
+ },
495
+ name: { type: 'string', description: 'New group name.' },
496
+ },
497
+ required: ['chatId', 'name'],
498
+ },
499
+ name: 'rename_group',
500
+ },
501
+ {
502
+ description: 'Invites members into a LINE group immediately. Invitees must accept before joining. Provide `mids` (from find_contact or get_group_members). One call per invite batch.',
503
+ inputSchema: {
504
+ type: 'object',
505
+ properties: {
506
+ chatId: {
507
+ type: 'string',
508
+ description: 'LINE group/room MID to invite into, as returned by list_conversations.',
509
+ },
510
+ mids: {
511
+ type: 'array',
512
+ items: { type: 'string' },
513
+ description: 'MIDs of the people to invite.',
514
+ },
515
+ },
516
+ required: ['chatId', 'mids'],
517
+ },
518
+ name: 'invite_member',
519
+ },
520
+ {
521
+ description: 'Removes (kicks) members from a LINE group immediately — they lose access at once (re-adding needs a fresh invite); visible to every member. Provide `mids` (from get_group_members). One call per removal batch.',
522
+ inputSchema: {
523
+ type: 'object',
524
+ properties: {
525
+ chatId: {
526
+ type: 'string',
527
+ description: 'LINE group/room MID to remove members from, as returned by list_conversations.',
528
+ },
529
+ mids: {
530
+ type: 'array',
531
+ items: { type: 'string' },
532
+ description: 'MIDs of the members to remove.',
533
+ },
534
+ },
535
+ required: ['chatId', 'mids'],
536
+ },
537
+ name: 'kick_member',
538
+ },
539
+ {
540
+ description: 'Makes THIS LINE account leave a group immediately — it loses access to the group and its history. One leave per call.',
541
+ inputSchema: {
542
+ type: 'object',
543
+ properties: {
544
+ chatId: {
545
+ type: 'string',
546
+ description: 'LINE group/room MID to leave, as returned by list_conversations.',
547
+ },
548
+ },
549
+ required: ['chatId'],
550
+ },
551
+ name: 'leave_group',
552
+ },
553
+ {
554
+ description: 'Creates a new LINE group/room immediately with the given members (no message is sent). chatType 0 = group (invitees must accept before joining), 1 = room (members added directly); default 1. Provide `name` and `mids` (initial members, e.g. from find_contact). One create per call.',
555
+ inputSchema: {
556
+ type: 'object',
557
+ properties: {
558
+ name: { type: 'string', description: 'Name for the new group.' },
559
+ mids: {
560
+ type: 'array',
561
+ items: { type: 'string' },
562
+ description: 'MIDs of the initial members to add.',
563
+ },
564
+ chatType: {
565
+ type: 'number',
566
+ description: 'LINE chat type: 0 = group (invite-based), 1 = room (direct add). Default 1.',
567
+ },
568
+ },
569
+ required: ['name', 'mids'],
570
+ },
571
+ name: 'create_group',
572
+ },
573
+ {
574
+ description: 'Adds a person to your LINE friends by their MID (e.g. from get_group_members or find_contact). One add per call.',
575
+ inputSchema: {
576
+ type: 'object',
577
+ properties: {
578
+ mid: {
579
+ type: 'string',
580
+ description: 'MID of the person to add as a friend, e.g. from get_group_members.',
581
+ },
582
+ },
583
+ required: ['mid'],
584
+ },
585
+ name: 'add_friend',
586
+ },
587
+ {
588
+ description: 'Blocks a contact for your LINE account — they can no longer message you. Reversible with unblock_contact. One block per call.',
589
+ inputSchema: {
590
+ type: 'object',
591
+ properties: {
592
+ mid: {
593
+ type: 'string',
594
+ description: 'MID of the contact to block.',
595
+ },
596
+ },
597
+ required: ['mid'],
598
+ },
599
+ name: 'block_contact',
600
+ },
601
+ {
602
+ description: 'Unblocks a previously blocked contact for your LINE account. Undoes block_contact. One unblock per call.',
603
+ inputSchema: {
604
+ type: 'object',
605
+ properties: {
606
+ mid: {
607
+ type: 'string',
608
+ description: 'MID of the contact to unblock.',
609
+ },
610
+ },
611
+ required: ['mid'],
612
+ },
613
+ name: 'unblock_contact',
614
+ },
615
+ {
616
+ description: 'Accepts a group/chat invitation for your LINE account — you join the chat. Use for a group you were invited to (its chatId appears with invited status). One accept per call.',
617
+ inputSchema: {
618
+ type: 'object',
619
+ properties: {
620
+ chatId: {
621
+ type: 'string',
622
+ description: 'LINE group/room MID whose invitation to accept, as returned by list_conversations.',
623
+ },
624
+ },
625
+ required: ['chatId'],
626
+ },
627
+ name: 'accept_invitation',
628
+ },
629
+ {
630
+ description: 'Adds a reaction to a LINE message, visible to the conversation. reactionType: 2 = 👍 LIKE, 3 = ❤️ LOVE, 4 = 😆 LAUGH, 5 = 😮 SURPRISE, 6 = 😢 SAD, 7 = 😡 ANGRY (default 2). One reaction per call.',
631
+ inputSchema: {
632
+ type: 'object',
633
+ properties: {
634
+ messageId: {
635
+ type: 'string',
636
+ description: 'LINE message id to react to, as returned by get_chat_messages.',
637
+ },
638
+ reactionType: {
639
+ type: 'number',
640
+ description: 'Predefined reaction: 2=👍LIKE, 3=❤️LOVE, 4=😆LAUGH, 5=😮SURPRISE, 6=😢SAD, 7=😡ANGRY. Default 2.',
641
+ },
642
+ },
643
+ required: ['messageId'],
644
+ },
645
+ name: 'react_message',
646
+ },
647
+ {
648
+ description: "Removes this account's reaction from a LINE message. Undoes a react_message. One cancellation per call.",
649
+ inputSchema: {
650
+ type: 'object',
651
+ properties: {
652
+ messageId: {
653
+ type: 'string',
654
+ description: 'LINE message id whose reaction to remove, as returned by get_chat_messages.',
655
+ },
656
+ },
657
+ required: ['messageId'],
658
+ },
659
+ name: 'cancel_reaction',
660
+ },
661
+ {
662
+ description: 'Retracts (unsends) one of YOUR OWN LINE messages for everyone — deletes it from the conversation for all participants and CANNOT be undone. LINE allows unsending only your own messages. SAFETY GATE: you must pass confirm: true, or the call refuses so it can never fire by accident. One unsend per call.',
663
+ inputSchema: {
664
+ type: 'object',
665
+ properties: {
666
+ messageId: {
667
+ type: 'string',
668
+ description: 'LINE message id to retract (must be your own), as returned by get_chat_messages.',
669
+ },
670
+ confirm: {
671
+ type: 'boolean',
672
+ description: 'Must be true to proceed. Retraction is irreversible; the call refuses unless this is explicitly true.',
673
+ },
674
+ },
675
+ required: ['messageId', 'confirm'],
676
+ },
677
+ name: 'unsend_message',
678
+ },
679
+ {
680
+ description: "Bulk-fetch recent messages from LINE conversations into Yomi's local cross-conversation search index (LINE has no native cross-chat search). Fetches up to `perChat` per chat (default 100) for `chatIds`, or all conversations when omitted, and best-effort embeds them for semantic search — re-running also repairs any messages still missing a vector. A background capture loop keeps the index current on its own, so call this only to force a reconcile or backfill specific chats. Undecryptable messages are skipped, not fabricated.",
681
+ inputSchema: {
682
+ type: 'object',
683
+ properties: {
684
+ chatIds: {
685
+ type: 'array',
686
+ items: { type: 'string' },
687
+ description: 'LINE chat/group/room MIDs to collect from, as returned by list_conversations. Omit to collect from all conversations.',
688
+ },
689
+ perChat: {
690
+ type: 'number',
691
+ description: 'Maximum recent messages to fetch per chat (default 100).',
692
+ },
693
+ },
694
+ },
695
+ name: 'collect_messages',
696
+ },
697
+ {
698
+ description: 'Search across your LINE messages. Hybrid ranking: FTS5 keyword search (covers every indexed message, so exact matches are never dropped) fused with semantic similarity when embeddings are available — the `mode` field reports which contributed (hybrid | semantic | keyword). If the index is empty and a session is live, it auto-collects all conversations first; a populated index searches locally with no network. Empty index with no session returns an honest notice, never a fabricated match list.',
699
+ inputSchema: {
700
+ type: 'object',
701
+ properties: {
702
+ query: {
703
+ type: 'string',
704
+ description: 'Search query. Plain keywords and natural-language descriptions both work — keyword matching catches exact terms, semantic matching catches paraphrases.',
705
+ },
706
+ limit: {
707
+ type: 'number',
708
+ description: 'Maximum number of results to return (default 20).',
709
+ },
710
+ },
711
+ required: ['query'],
712
+ },
713
+ name: 'search_messages',
714
+ },
715
+ {
716
+ description: "Add conversations to Yomi's privacy denylist. Excluded chats are (1) skipped by future collect_messages/search_messages auto-collect — never fetched or indexed — and (2) PURGED now: their already-indexed messages and embeddings are deleted from the local index in the same call. A real privacy action, not just a future filter. Local-index operation; works without a live LINE session.",
717
+ inputSchema: {
718
+ type: 'object',
719
+ properties: {
720
+ chatIds: {
721
+ type: 'array',
722
+ items: { type: 'string' },
723
+ description: 'LINE chat/group/room MIDs to exclude, as returned by list_conversations.',
724
+ },
725
+ },
726
+ required: ['chatIds'],
727
+ },
728
+ name: 'exclude_chats',
729
+ },
730
+ {
731
+ description: "Remove conversations from Yomi's denylist, re-allowing future capture. Does NOT restore data purged when the chat was excluded — capture resumes from empty history going forward. Local-index operation; works without a live LINE session.",
732
+ inputSchema: {
733
+ type: 'object',
734
+ properties: {
735
+ chatIds: {
736
+ type: 'array',
737
+ items: { type: 'string' },
738
+ description: 'LINE chat/group/room MIDs to re-include.',
739
+ },
740
+ },
741
+ required: ['chatIds'],
742
+ },
743
+ name: 'include_chats',
744
+ },
745
+ {
746
+ description: "List the conversations currently on Yomi's privacy denylist. Returns `[{ chatId, name }]`; `name` is best-effort resolved when a live LINE session exists, otherwise null — never a fabricated placeholder. Local-index operation; works without a live LINE session.",
747
+ inputSchema: {
748
+ type: 'object',
749
+ properties: {},
750
+ },
751
+ name: 'list_excluded_chats',
752
+ },
753
+ {
754
+ description: "Return Yomi's data-capture privacy policy (the disclosure to show the user) plus the current list of excluded conversations. Call this to show the user, in concrete terms, what Yomi captures by default and how to exclude conversations.",
755
+ inputSchema: {
756
+ type: 'object',
757
+ properties: {},
758
+ },
759
+ name: 'get_scope_policy',
760
+ },
761
+ ];