@line-harness/mcp-server 0.2.2 → 0.4.1
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/dist/index.js +568 -515
- package/package.json +9 -24
- package/src/client.ts +26 -0
- package/src/index.ts +23 -0
- package/src/resources/index.ts +80 -0
- package/src/tools/account-summary.ts +178 -0
- package/src/tools/broadcast.ts +195 -0
- package/src/tools/create-form.ts +80 -0
- package/src/tools/create-rich-menu.ts +90 -0
- package/src/tools/create-scenario.ts +152 -0
- package/src/tools/create-tracked-link.ts +57 -0
- package/src/tools/enroll-scenario.ts +48 -0
- package/src/tools/get-form-submissions.ts +45 -0
- package/src/tools/get-friend-detail.ts +41 -0
- package/src/tools/get-link-clicks.ts +41 -0
- package/src/tools/index.ts +32 -0
- package/src/tools/list-crm-objects.ts +80 -0
- package/src/tools/list-friends.ts +64 -0
- package/src/tools/manage-tags.ts +90 -0
- package/src/tools/send-message.ts +60 -0
- package/tsconfig.json +20 -0
- package/tsup.config.ts +12 -0
- package/README.md +0 -63
package/dist/index.js
CHANGED
|
@@ -7,412 +7,8 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
7
7
|
// src/tools/send-message.ts
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
|
|
10
|
-
// ../sdk/dist/index.mjs
|
|
11
|
-
var LineHarnessError = class extends Error {
|
|
12
|
-
constructor(message, status, endpoint) {
|
|
13
|
-
super(message);
|
|
14
|
-
this.status = status;
|
|
15
|
-
this.endpoint = endpoint;
|
|
16
|
-
this.name = "LineHarnessError";
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
var HttpClient = class {
|
|
20
|
-
baseUrl;
|
|
21
|
-
apiKey;
|
|
22
|
-
timeout;
|
|
23
|
-
constructor(config) {
|
|
24
|
-
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
25
|
-
this.apiKey = config.apiKey;
|
|
26
|
-
this.timeout = config.timeout;
|
|
27
|
-
}
|
|
28
|
-
async get(path) {
|
|
29
|
-
return this.request("GET", path);
|
|
30
|
-
}
|
|
31
|
-
async post(path, body) {
|
|
32
|
-
return this.request("POST", path, body);
|
|
33
|
-
}
|
|
34
|
-
async put(path, body) {
|
|
35
|
-
return this.request("PUT", path, body);
|
|
36
|
-
}
|
|
37
|
-
async delete(path) {
|
|
38
|
-
return this.request("DELETE", path);
|
|
39
|
-
}
|
|
40
|
-
async request(method, path, body) {
|
|
41
|
-
const url = `${this.baseUrl}${path}`;
|
|
42
|
-
const headers = {
|
|
43
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
44
|
-
"Content-Type": "application/json"
|
|
45
|
-
};
|
|
46
|
-
const options = {
|
|
47
|
-
method,
|
|
48
|
-
headers,
|
|
49
|
-
signal: AbortSignal.timeout(this.timeout)
|
|
50
|
-
};
|
|
51
|
-
if (body !== void 0) {
|
|
52
|
-
options.body = JSON.stringify(body);
|
|
53
|
-
}
|
|
54
|
-
const res = await fetch(url, options);
|
|
55
|
-
if (!res.ok) {
|
|
56
|
-
let errorMessage = `HTTP ${res.status}`;
|
|
57
|
-
try {
|
|
58
|
-
const errorBody = await res.json();
|
|
59
|
-
if (errorBody.error) errorMessage = errorBody.error;
|
|
60
|
-
} catch {
|
|
61
|
-
}
|
|
62
|
-
throw new LineHarnessError(errorMessage, res.status, `${method} ${path}`);
|
|
63
|
-
}
|
|
64
|
-
return res.json();
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
var FriendsResource = class {
|
|
68
|
-
constructor(http, defaultAccountId) {
|
|
69
|
-
this.http = http;
|
|
70
|
-
this.defaultAccountId = defaultAccountId;
|
|
71
|
-
}
|
|
72
|
-
async list(params) {
|
|
73
|
-
const query = new URLSearchParams();
|
|
74
|
-
if (params?.limit !== void 0) query.set("limit", String(params.limit));
|
|
75
|
-
if (params?.offset !== void 0) query.set("offset", String(params.offset));
|
|
76
|
-
if (params?.tagId) query.set("tagId", params.tagId);
|
|
77
|
-
const accountId = params?.accountId ?? this.defaultAccountId;
|
|
78
|
-
if (accountId) query.set("lineAccountId", accountId);
|
|
79
|
-
const qs = query.toString();
|
|
80
|
-
const path = qs ? `/api/friends?${qs}` : "/api/friends";
|
|
81
|
-
const res = await this.http.get(path);
|
|
82
|
-
return res.data;
|
|
83
|
-
}
|
|
84
|
-
async get(id) {
|
|
85
|
-
const res = await this.http.get(`/api/friends/${id}`);
|
|
86
|
-
return res.data;
|
|
87
|
-
}
|
|
88
|
-
async count(params) {
|
|
89
|
-
const accountId = params?.accountId ?? this.defaultAccountId;
|
|
90
|
-
const path = accountId ? `/api/friends/count?lineAccountId=${encodeURIComponent(accountId)}` : "/api/friends/count";
|
|
91
|
-
const res = await this.http.get(path);
|
|
92
|
-
return res.data.count;
|
|
93
|
-
}
|
|
94
|
-
async addTag(friendId, tagId) {
|
|
95
|
-
await this.http.post(`/api/friends/${friendId}/tags`, { tagId });
|
|
96
|
-
}
|
|
97
|
-
async removeTag(friendId, tagId) {
|
|
98
|
-
await this.http.delete(`/api/friends/${friendId}/tags/${tagId}`);
|
|
99
|
-
}
|
|
100
|
-
async sendMessage(friendId, content, messageType = "text") {
|
|
101
|
-
const res = await this.http.post(`/api/friends/${friendId}/messages`, {
|
|
102
|
-
messageType,
|
|
103
|
-
content
|
|
104
|
-
});
|
|
105
|
-
return res.data;
|
|
106
|
-
}
|
|
107
|
-
async setMetadata(friendId, fields) {
|
|
108
|
-
const res = await this.http.put(`/api/friends/${friendId}/metadata`, fields);
|
|
109
|
-
return res.data;
|
|
110
|
-
}
|
|
111
|
-
async setRichMenu(friendId, richMenuId) {
|
|
112
|
-
await this.http.post(`/api/friends/${friendId}/rich-menu`, { richMenuId });
|
|
113
|
-
}
|
|
114
|
-
async removeRichMenu(friendId) {
|
|
115
|
-
await this.http.delete(`/api/friends/${friendId}/rich-menu`);
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
var TagsResource = class {
|
|
119
|
-
constructor(http) {
|
|
120
|
-
this.http = http;
|
|
121
|
-
}
|
|
122
|
-
async list() {
|
|
123
|
-
const res = await this.http.get("/api/tags");
|
|
124
|
-
return res.data;
|
|
125
|
-
}
|
|
126
|
-
async create(input) {
|
|
127
|
-
const res = await this.http.post("/api/tags", input);
|
|
128
|
-
return res.data;
|
|
129
|
-
}
|
|
130
|
-
async delete(id) {
|
|
131
|
-
await this.http.delete(`/api/tags/${id}`);
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
var ScenariosResource = class {
|
|
135
|
-
constructor(http, defaultAccountId) {
|
|
136
|
-
this.http = http;
|
|
137
|
-
this.defaultAccountId = defaultAccountId;
|
|
138
|
-
}
|
|
139
|
-
async list(params) {
|
|
140
|
-
const accountId = params?.accountId ?? this.defaultAccountId;
|
|
141
|
-
const query = accountId ? `?lineAccountId=${accountId}` : "";
|
|
142
|
-
const res = await this.http.get(`/api/scenarios${query}`);
|
|
143
|
-
return res.data;
|
|
144
|
-
}
|
|
145
|
-
async get(id) {
|
|
146
|
-
const res = await this.http.get(`/api/scenarios/${id}`);
|
|
147
|
-
return res.data;
|
|
148
|
-
}
|
|
149
|
-
async create(input) {
|
|
150
|
-
const body = { ...input };
|
|
151
|
-
if (!body.lineAccountId && this.defaultAccountId) {
|
|
152
|
-
body.lineAccountId = this.defaultAccountId;
|
|
153
|
-
}
|
|
154
|
-
const res = await this.http.post("/api/scenarios", body);
|
|
155
|
-
return res.data;
|
|
156
|
-
}
|
|
157
|
-
async update(id, input) {
|
|
158
|
-
const res = await this.http.put(`/api/scenarios/${id}`, input);
|
|
159
|
-
return res.data;
|
|
160
|
-
}
|
|
161
|
-
async delete(id) {
|
|
162
|
-
await this.http.delete(`/api/scenarios/${id}`);
|
|
163
|
-
}
|
|
164
|
-
async addStep(scenarioId, input) {
|
|
165
|
-
const res = await this.http.post(`/api/scenarios/${scenarioId}/steps`, input);
|
|
166
|
-
return res.data;
|
|
167
|
-
}
|
|
168
|
-
async updateStep(scenarioId, stepId, input) {
|
|
169
|
-
const res = await this.http.put(`/api/scenarios/${scenarioId}/steps/${stepId}`, input);
|
|
170
|
-
return res.data;
|
|
171
|
-
}
|
|
172
|
-
async deleteStep(scenarioId, stepId) {
|
|
173
|
-
await this.http.delete(`/api/scenarios/${scenarioId}/steps/${stepId}`);
|
|
174
|
-
}
|
|
175
|
-
async enroll(scenarioId, friendId) {
|
|
176
|
-
const res = await this.http.post(
|
|
177
|
-
`/api/scenarios/${scenarioId}/enroll/${friendId}`
|
|
178
|
-
);
|
|
179
|
-
return res.data;
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
var BroadcastsResource = class {
|
|
183
|
-
constructor(http, defaultAccountId) {
|
|
184
|
-
this.http = http;
|
|
185
|
-
this.defaultAccountId = defaultAccountId;
|
|
186
|
-
}
|
|
187
|
-
async list(params) {
|
|
188
|
-
const accountId = params?.accountId ?? this.defaultAccountId;
|
|
189
|
-
const query = accountId ? `?lineAccountId=${accountId}` : "";
|
|
190
|
-
const res = await this.http.get(`/api/broadcasts${query}`);
|
|
191
|
-
return res.data;
|
|
192
|
-
}
|
|
193
|
-
async get(id) {
|
|
194
|
-
const res = await this.http.get(`/api/broadcasts/${id}`);
|
|
195
|
-
return res.data;
|
|
196
|
-
}
|
|
197
|
-
async create(input) {
|
|
198
|
-
const body = { ...input };
|
|
199
|
-
if (!body.lineAccountId && this.defaultAccountId) {
|
|
200
|
-
body.lineAccountId = this.defaultAccountId;
|
|
201
|
-
}
|
|
202
|
-
const res = await this.http.post("/api/broadcasts", body);
|
|
203
|
-
return res.data;
|
|
204
|
-
}
|
|
205
|
-
async update(id, input) {
|
|
206
|
-
const res = await this.http.put(`/api/broadcasts/${id}`, input);
|
|
207
|
-
return res.data;
|
|
208
|
-
}
|
|
209
|
-
async delete(id) {
|
|
210
|
-
await this.http.delete(`/api/broadcasts/${id}`);
|
|
211
|
-
}
|
|
212
|
-
async send(id) {
|
|
213
|
-
const res = await this.http.post(`/api/broadcasts/${id}/send`);
|
|
214
|
-
return res.data;
|
|
215
|
-
}
|
|
216
|
-
async sendToSegment(id, conditions) {
|
|
217
|
-
const res = await this.http.post(
|
|
218
|
-
`/api/broadcasts/${id}/send-segment`,
|
|
219
|
-
{ conditions }
|
|
220
|
-
);
|
|
221
|
-
return res.data;
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
var RichMenusResource = class {
|
|
225
|
-
constructor(http) {
|
|
226
|
-
this.http = http;
|
|
227
|
-
}
|
|
228
|
-
async list() {
|
|
229
|
-
const res = await this.http.get("/api/rich-menus");
|
|
230
|
-
return res.data;
|
|
231
|
-
}
|
|
232
|
-
async create(menu) {
|
|
233
|
-
const res = await this.http.post("/api/rich-menus", menu);
|
|
234
|
-
return res.data;
|
|
235
|
-
}
|
|
236
|
-
async delete(richMenuId) {
|
|
237
|
-
await this.http.delete(`/api/rich-menus/${encodeURIComponent(richMenuId)}`);
|
|
238
|
-
}
|
|
239
|
-
async setDefault(richMenuId) {
|
|
240
|
-
await this.http.post(`/api/rich-menus/${encodeURIComponent(richMenuId)}/default`);
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
var TrackedLinksResource = class {
|
|
244
|
-
constructor(http) {
|
|
245
|
-
this.http = http;
|
|
246
|
-
}
|
|
247
|
-
async list() {
|
|
248
|
-
const res = await this.http.get("/api/tracked-links");
|
|
249
|
-
return res.data;
|
|
250
|
-
}
|
|
251
|
-
async create(input) {
|
|
252
|
-
const res = await this.http.post("/api/tracked-links", input);
|
|
253
|
-
return res.data;
|
|
254
|
-
}
|
|
255
|
-
async get(id) {
|
|
256
|
-
const res = await this.http.get(`/api/tracked-links/${id}`);
|
|
257
|
-
return res.data;
|
|
258
|
-
}
|
|
259
|
-
async delete(id) {
|
|
260
|
-
await this.http.delete(`/api/tracked-links/${id}`);
|
|
261
|
-
}
|
|
262
|
-
};
|
|
263
|
-
var FormsResource = class {
|
|
264
|
-
constructor(http) {
|
|
265
|
-
this.http = http;
|
|
266
|
-
}
|
|
267
|
-
async list() {
|
|
268
|
-
const res = await this.http.get("/api/forms");
|
|
269
|
-
return res.data;
|
|
270
|
-
}
|
|
271
|
-
async get(id) {
|
|
272
|
-
const res = await this.http.get(`/api/forms/${id}`);
|
|
273
|
-
return res.data;
|
|
274
|
-
}
|
|
275
|
-
async create(input) {
|
|
276
|
-
const res = await this.http.post("/api/forms", input);
|
|
277
|
-
return res.data;
|
|
278
|
-
}
|
|
279
|
-
async update(id, input) {
|
|
280
|
-
const res = await this.http.put(`/api/forms/${id}`, input);
|
|
281
|
-
return res.data;
|
|
282
|
-
}
|
|
283
|
-
async delete(id) {
|
|
284
|
-
await this.http.delete(`/api/forms/${id}`);
|
|
285
|
-
}
|
|
286
|
-
async getSubmissions(formId) {
|
|
287
|
-
const res = await this.http.get(
|
|
288
|
-
`/api/forms/${formId}/submissions`
|
|
289
|
-
);
|
|
290
|
-
return res.data;
|
|
291
|
-
}
|
|
292
|
-
};
|
|
293
|
-
var MULTIPLIERS = {
|
|
294
|
-
m: 1,
|
|
295
|
-
h: 60,
|
|
296
|
-
d: 1440,
|
|
297
|
-
w: 10080
|
|
298
|
-
};
|
|
299
|
-
function parseDelay(input) {
|
|
300
|
-
const match = input.match(/^(\d+)([mhdw])$/);
|
|
301
|
-
if (!match) {
|
|
302
|
-
throw new Error(`Invalid delay format: "${input}". Use format like "30m", "1h", "1d", "1w".`);
|
|
303
|
-
}
|
|
304
|
-
return Number(match[1]) * MULTIPLIERS[match[2]];
|
|
305
|
-
}
|
|
306
|
-
var Workflows = class {
|
|
307
|
-
constructor(friends, scenarios, broadcasts) {
|
|
308
|
-
this.friends = friends;
|
|
309
|
-
this.scenarios = scenarios;
|
|
310
|
-
this.broadcasts = broadcasts;
|
|
311
|
-
}
|
|
312
|
-
async createStepScenario(name, triggerType, steps) {
|
|
313
|
-
const scenario = await this.scenarios.create({ name, triggerType });
|
|
314
|
-
for (let i = 0; i < steps.length; i++) {
|
|
315
|
-
const step = steps[i];
|
|
316
|
-
await this.scenarios.addStep(scenario.id, {
|
|
317
|
-
stepOrder: i + 1,
|
|
318
|
-
delayMinutes: parseDelay(step.delay),
|
|
319
|
-
messageType: step.type,
|
|
320
|
-
messageContent: step.content
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
return this.scenarios.get(scenario.id);
|
|
324
|
-
}
|
|
325
|
-
async broadcastText(text) {
|
|
326
|
-
const broadcast = await this.broadcasts.create({
|
|
327
|
-
title: text.slice(0, 50),
|
|
328
|
-
messageType: "text",
|
|
329
|
-
messageContent: text,
|
|
330
|
-
targetType: "all"
|
|
331
|
-
});
|
|
332
|
-
return this.broadcasts.send(broadcast.id);
|
|
333
|
-
}
|
|
334
|
-
async broadcastToTag(tagId, messageType, content) {
|
|
335
|
-
const broadcast = await this.broadcasts.create({
|
|
336
|
-
title: content.slice(0, 50),
|
|
337
|
-
messageType,
|
|
338
|
-
messageContent: content,
|
|
339
|
-
targetType: "tag",
|
|
340
|
-
targetTagId: tagId
|
|
341
|
-
});
|
|
342
|
-
return this.broadcasts.send(broadcast.id);
|
|
343
|
-
}
|
|
344
|
-
async broadcastToSegment(messageType, content, conditions) {
|
|
345
|
-
const broadcast = await this.broadcasts.create({
|
|
346
|
-
title: content.slice(0, 50),
|
|
347
|
-
messageType,
|
|
348
|
-
messageContent: content,
|
|
349
|
-
targetType: "all"
|
|
350
|
-
});
|
|
351
|
-
return this.broadcasts.sendToSegment(broadcast.id, conditions);
|
|
352
|
-
}
|
|
353
|
-
async sendTextToFriend(friendId, text) {
|
|
354
|
-
return this.friends.sendMessage(friendId, text, "text");
|
|
355
|
-
}
|
|
356
|
-
async sendFlexToFriend(friendId, flexJson) {
|
|
357
|
-
return this.friends.sendMessage(friendId, flexJson, "flex");
|
|
358
|
-
}
|
|
359
|
-
};
|
|
360
|
-
var LineHarness = class {
|
|
361
|
-
friends;
|
|
362
|
-
tags;
|
|
363
|
-
scenarios;
|
|
364
|
-
broadcasts;
|
|
365
|
-
richMenus;
|
|
366
|
-
trackedLinks;
|
|
367
|
-
forms;
|
|
368
|
-
apiUrl;
|
|
369
|
-
defaultAccountId;
|
|
370
|
-
workflows;
|
|
371
|
-
createStepScenario;
|
|
372
|
-
broadcastText;
|
|
373
|
-
broadcastToTag;
|
|
374
|
-
broadcastToSegment;
|
|
375
|
-
sendTextToFriend;
|
|
376
|
-
sendFlexToFriend;
|
|
377
|
-
constructor(config) {
|
|
378
|
-
this.apiUrl = config.apiUrl.replace(/\/$/, "");
|
|
379
|
-
this.defaultAccountId = config.lineAccountId;
|
|
380
|
-
const http = new HttpClient({
|
|
381
|
-
baseUrl: this.apiUrl,
|
|
382
|
-
apiKey: config.apiKey,
|
|
383
|
-
timeout: config.timeout ?? 3e4
|
|
384
|
-
});
|
|
385
|
-
this.friends = new FriendsResource(http, this.defaultAccountId);
|
|
386
|
-
this.tags = new TagsResource(http);
|
|
387
|
-
this.scenarios = new ScenariosResource(http, this.defaultAccountId);
|
|
388
|
-
this.broadcasts = new BroadcastsResource(http, this.defaultAccountId);
|
|
389
|
-
this.richMenus = new RichMenusResource(http);
|
|
390
|
-
this.trackedLinks = new TrackedLinksResource(http);
|
|
391
|
-
this.forms = new FormsResource(http);
|
|
392
|
-
this.workflows = new Workflows(this.friends, this.scenarios, this.broadcasts);
|
|
393
|
-
this.createStepScenario = this.workflows.createStepScenario.bind(this.workflows);
|
|
394
|
-
this.broadcastText = this.workflows.broadcastText.bind(this.workflows);
|
|
395
|
-
this.broadcastToTag = this.workflows.broadcastToTag.bind(this.workflows);
|
|
396
|
-
this.broadcastToSegment = this.workflows.broadcastToSegment.bind(this.workflows);
|
|
397
|
-
this.sendTextToFriend = this.workflows.sendTextToFriend.bind(this.workflows);
|
|
398
|
-
this.sendFlexToFriend = this.workflows.sendFlexToFriend.bind(this.workflows);
|
|
399
|
-
}
|
|
400
|
-
/**
|
|
401
|
-
* Generate friend-add URL with OAuth (bot_prompt=aggressive)
|
|
402
|
-
* This URL does friend-add + UUID in one step.
|
|
403
|
-
*
|
|
404
|
-
* @param ref - Attribution code (e.g., 'lp-a', 'instagram', 'seminar-0322')
|
|
405
|
-
* @param redirect - URL to redirect after completion
|
|
406
|
-
*/
|
|
407
|
-
getAuthUrl(options) {
|
|
408
|
-
const url = new URL(`${this.apiUrl}/auth/line`);
|
|
409
|
-
if (options?.ref) url.searchParams.set("ref", options.ref);
|
|
410
|
-
if (options?.redirect) url.searchParams.set("redirect", options.redirect);
|
|
411
|
-
return url.toString();
|
|
412
|
-
}
|
|
413
|
-
};
|
|
414
|
-
|
|
415
10
|
// src/client.ts
|
|
11
|
+
import { LineHarness } from "@line-harness/sdk";
|
|
416
12
|
var clientInstance = null;
|
|
417
13
|
function getClient() {
|
|
418
14
|
if (clientInstance) return clientInstance;
|
|
@@ -440,21 +36,47 @@ function registerSendMessage(server2) {
|
|
|
440
36
|
"Send a text or flex message to a specific friend. Use messageType 'flex' for rich card layouts.",
|
|
441
37
|
{
|
|
442
38
|
friendId: z.string().describe("The friend's ID to send the message to"),
|
|
443
|
-
content: z.string().describe(
|
|
444
|
-
|
|
39
|
+
content: z.string().describe(
|
|
40
|
+
"Message content. For text: plain string. For flex: JSON string of LINE Flex Message."
|
|
41
|
+
),
|
|
42
|
+
messageType: z.enum(["text", "flex"]).default("text").describe(
|
|
43
|
+
"Message type: 'text' for plain text, 'flex' for Flex Message JSON"
|
|
44
|
+
)
|
|
445
45
|
},
|
|
446
46
|
async ({ friendId, content, messageType }) => {
|
|
447
47
|
try {
|
|
448
48
|
const client = getClient();
|
|
449
|
-
const result = await client.friends.sendMessage(
|
|
49
|
+
const result = await client.friends.sendMessage(
|
|
50
|
+
friendId,
|
|
51
|
+
content,
|
|
52
|
+
messageType
|
|
53
|
+
);
|
|
450
54
|
return {
|
|
451
|
-
content: [
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
55
|
+
content: [
|
|
56
|
+
{
|
|
57
|
+
type: "text",
|
|
58
|
+
text: JSON.stringify(
|
|
59
|
+
{ success: true, messageId: result.messageId },
|
|
60
|
+
null,
|
|
61
|
+
2
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
]
|
|
455
65
|
};
|
|
456
66
|
} catch (error) {
|
|
457
|
-
return {
|
|
67
|
+
return {
|
|
68
|
+
content: [
|
|
69
|
+
{
|
|
70
|
+
type: "text",
|
|
71
|
+
text: JSON.stringify(
|
|
72
|
+
{ success: false, error: String(error) },
|
|
73
|
+
null,
|
|
74
|
+
2
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
isError: true
|
|
79
|
+
};
|
|
458
80
|
}
|
|
459
81
|
}
|
|
460
82
|
);
|
|
@@ -469,25 +91,64 @@ function registerBroadcast(server2) {
|
|
|
469
91
|
{
|
|
470
92
|
title: z2.string().describe("Internal title for this broadcast (not shown to users)"),
|
|
471
93
|
messageType: z2.enum(["text", "flex"]).describe("Message type"),
|
|
472
|
-
messageContent: z2.string().describe(
|
|
473
|
-
|
|
94
|
+
messageContent: z2.string().describe(
|
|
95
|
+
"Message content. For text: plain string. For flex: JSON string."
|
|
96
|
+
),
|
|
97
|
+
targetType: z2.enum(["all", "tag", "segment"]).default("all").describe(
|
|
98
|
+
"Target audience: 'all' for everyone, 'tag' for a tag group, 'segment' for filtered conditions"
|
|
99
|
+
),
|
|
474
100
|
targetTagId: z2.string().optional().describe("Tag ID when targetType is 'tag'"),
|
|
475
|
-
segmentConditions: z2.string().optional().describe(
|
|
101
|
+
segmentConditions: z2.string().optional().describe(
|
|
102
|
+
"JSON string of segment conditions when targetType is 'segment'. Format: { operator: 'AND'|'OR', rules: [{ type: 'tag_exists'|'tag_not_exists'|'metadata_equals'|'metadata_not_equals'|'ref_code'|'is_following', value: string|boolean|{key,value} }] }"
|
|
103
|
+
),
|
|
476
104
|
scheduledAt: z2.string().optional().describe("ISO 8601 datetime to schedule. Omit to send immediately."),
|
|
477
105
|
accountId: z2.string().optional().describe("LINE account ID (uses default if omitted)")
|
|
478
106
|
},
|
|
479
|
-
async ({
|
|
107
|
+
async ({
|
|
108
|
+
title,
|
|
109
|
+
messageType,
|
|
110
|
+
messageContent,
|
|
111
|
+
targetType,
|
|
112
|
+
targetTagId,
|
|
113
|
+
segmentConditions,
|
|
114
|
+
scheduledAt,
|
|
115
|
+
accountId
|
|
116
|
+
}) => {
|
|
480
117
|
try {
|
|
481
118
|
const client = getClient();
|
|
482
119
|
if (targetType === "segment" && !segmentConditions) {
|
|
483
120
|
return {
|
|
484
|
-
content: [
|
|
121
|
+
content: [
|
|
122
|
+
{
|
|
123
|
+
type: "text",
|
|
124
|
+
text: JSON.stringify(
|
|
125
|
+
{
|
|
126
|
+
success: false,
|
|
127
|
+
error: "segmentConditions is required when targetType is 'segment'"
|
|
128
|
+
},
|
|
129
|
+
null,
|
|
130
|
+
2
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
],
|
|
485
134
|
isError: true
|
|
486
135
|
};
|
|
487
136
|
}
|
|
488
137
|
if (targetType === "segment" && scheduledAt) {
|
|
489
138
|
return {
|
|
490
|
-
content: [
|
|
139
|
+
content: [
|
|
140
|
+
{
|
|
141
|
+
type: "text",
|
|
142
|
+
text: JSON.stringify(
|
|
143
|
+
{
|
|
144
|
+
success: false,
|
|
145
|
+
error: "Scheduled segment broadcasts are not supported. Use scheduledAt only with targetType 'all' or 'tag'."
|
|
146
|
+
},
|
|
147
|
+
null,
|
|
148
|
+
2
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
],
|
|
491
152
|
isError: true
|
|
492
153
|
};
|
|
493
154
|
}
|
|
@@ -497,7 +158,19 @@ function registerBroadcast(server2) {
|
|
|
497
158
|
parsedConditions = JSON.parse(segmentConditions);
|
|
498
159
|
} catch {
|
|
499
160
|
return {
|
|
500
|
-
content: [
|
|
161
|
+
content: [
|
|
162
|
+
{
|
|
163
|
+
type: "text",
|
|
164
|
+
text: JSON.stringify(
|
|
165
|
+
{
|
|
166
|
+
success: false,
|
|
167
|
+
error: "segmentConditions must be valid JSON"
|
|
168
|
+
},
|
|
169
|
+
null,
|
|
170
|
+
2
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
],
|
|
501
174
|
isError: true
|
|
502
175
|
};
|
|
503
176
|
}
|
|
@@ -509,8 +182,22 @@ function registerBroadcast(server2) {
|
|
|
509
182
|
lineAccountId: accountId
|
|
510
183
|
});
|
|
511
184
|
try {
|
|
512
|
-
const result2 = await client.broadcasts.sendToSegment(
|
|
513
|
-
|
|
185
|
+
const result2 = await client.broadcasts.sendToSegment(
|
|
186
|
+
broadcast2.id,
|
|
187
|
+
parsedConditions
|
|
188
|
+
);
|
|
189
|
+
return {
|
|
190
|
+
content: [
|
|
191
|
+
{
|
|
192
|
+
type: "text",
|
|
193
|
+
text: JSON.stringify(
|
|
194
|
+
{ success: true, broadcast: result2 },
|
|
195
|
+
null,
|
|
196
|
+
2
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
};
|
|
514
201
|
} catch (sendError) {
|
|
515
202
|
await client.broadcasts.delete(broadcast2.id).catch(() => {
|
|
516
203
|
});
|
|
@@ -527,9 +214,32 @@ function registerBroadcast(server2) {
|
|
|
527
214
|
lineAccountId: accountId
|
|
528
215
|
});
|
|
529
216
|
const result = scheduledAt ? broadcast : await client.broadcasts.send(broadcast.id);
|
|
530
|
-
return {
|
|
217
|
+
return {
|
|
218
|
+
content: [
|
|
219
|
+
{
|
|
220
|
+
type: "text",
|
|
221
|
+
text: JSON.stringify(
|
|
222
|
+
{ success: true, broadcast: result },
|
|
223
|
+
null,
|
|
224
|
+
2
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
};
|
|
531
229
|
} catch (error) {
|
|
532
|
-
return {
|
|
230
|
+
return {
|
|
231
|
+
content: [
|
|
232
|
+
{
|
|
233
|
+
type: "text",
|
|
234
|
+
text: JSON.stringify(
|
|
235
|
+
{ success: false, error: String(error) },
|
|
236
|
+
null,
|
|
237
|
+
2
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
],
|
|
241
|
+
isError: true
|
|
242
|
+
};
|
|
533
243
|
}
|
|
534
244
|
}
|
|
535
245
|
);
|
|
@@ -537,26 +247,47 @@ function registerBroadcast(server2) {
|
|
|
537
247
|
|
|
538
248
|
// src/tools/create-scenario.ts
|
|
539
249
|
import { z as z3 } from "zod";
|
|
250
|
+
import { parseDelay } from "@line-harness/sdk";
|
|
540
251
|
function registerCreateScenario(server2) {
|
|
541
252
|
server2.tool(
|
|
542
253
|
"create_scenario",
|
|
543
254
|
"Create a step delivery scenario with multiple message steps. Each step has a delay and message content. Scenarios auto-trigger on friend_add, tag_added, or manual enrollment.",
|
|
544
255
|
{
|
|
545
256
|
name: z3.string().describe("Scenario name"),
|
|
546
|
-
triggerType: z3.enum(["friend_add", "tag_added", "manual"]).describe(
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
257
|
+
triggerType: z3.enum(["friend_add", "tag_added", "manual"]).describe(
|
|
258
|
+
"When to start: 'friend_add' on new friends, 'tag_added' when a tag is applied, 'manual' for explicit enrollment"
|
|
259
|
+
),
|
|
260
|
+
triggerTagId: z3.string().optional().describe(
|
|
261
|
+
"Required when triggerType is 'tag_added': the tag ID that triggers this scenario"
|
|
262
|
+
),
|
|
263
|
+
steps: z3.array(
|
|
264
|
+
z3.object({
|
|
265
|
+
delay: z3.string().describe(
|
|
266
|
+
"Delay before sending. Format: '0m' for immediate, '30m' for 30 minutes, '24h' for 24 hours"
|
|
267
|
+
),
|
|
268
|
+
type: z3.enum(["text", "flex"]).describe("Message type"),
|
|
269
|
+
content: z3.string().describe("Message content")
|
|
270
|
+
})
|
|
271
|
+
).describe("Ordered list of message steps"),
|
|
553
272
|
accountId: z3.string().optional().describe("LINE account ID (uses default if omitted)")
|
|
554
273
|
},
|
|
555
274
|
async ({ name, triggerType, triggerTagId, steps, accountId }) => {
|
|
556
275
|
try {
|
|
557
276
|
if (triggerType === "tag_added" && !triggerTagId) {
|
|
558
277
|
return {
|
|
559
|
-
content: [
|
|
278
|
+
content: [
|
|
279
|
+
{
|
|
280
|
+
type: "text",
|
|
281
|
+
text: JSON.stringify(
|
|
282
|
+
{
|
|
283
|
+
success: false,
|
|
284
|
+
error: "triggerTagId is required when triggerType is 'tag_added'"
|
|
285
|
+
},
|
|
286
|
+
null,
|
|
287
|
+
2
|
|
288
|
+
)
|
|
289
|
+
}
|
|
290
|
+
],
|
|
560
291
|
isError: true
|
|
561
292
|
};
|
|
562
293
|
}
|
|
@@ -568,11 +299,27 @@ function registerCreateScenario(server2) {
|
|
|
568
299
|
delayMinutes = parseDelay(step.delay);
|
|
569
300
|
} catch {
|
|
570
301
|
return {
|
|
571
|
-
content: [
|
|
302
|
+
content: [
|
|
303
|
+
{
|
|
304
|
+
type: "text",
|
|
305
|
+
text: JSON.stringify(
|
|
306
|
+
{
|
|
307
|
+
success: false,
|
|
308
|
+
error: `Invalid delay format at step ${i + 1}: "${step.delay}". Use formats like '0m', '30m', '24h'.`
|
|
309
|
+
},
|
|
310
|
+
null,
|
|
311
|
+
2
|
|
312
|
+
)
|
|
313
|
+
}
|
|
314
|
+
],
|
|
572
315
|
isError: true
|
|
573
316
|
};
|
|
574
317
|
}
|
|
575
|
-
parsedSteps.push({
|
|
318
|
+
parsedSteps.push({
|
|
319
|
+
delayMinutes,
|
|
320
|
+
type: step.type,
|
|
321
|
+
content: step.content
|
|
322
|
+
});
|
|
576
323
|
}
|
|
577
324
|
const client = getClient();
|
|
578
325
|
const scenario = await client.scenarios.create({
|
|
@@ -598,13 +345,31 @@ function registerCreateScenario(server2) {
|
|
|
598
345
|
}
|
|
599
346
|
const scenarioWithSteps = await client.scenarios.get(scenario.id);
|
|
600
347
|
return {
|
|
601
|
-
content: [
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
348
|
+
content: [
|
|
349
|
+
{
|
|
350
|
+
type: "text",
|
|
351
|
+
text: JSON.stringify(
|
|
352
|
+
{ success: true, scenario: scenarioWithSteps },
|
|
353
|
+
null,
|
|
354
|
+
2
|
|
355
|
+
)
|
|
356
|
+
}
|
|
357
|
+
]
|
|
605
358
|
};
|
|
606
359
|
} catch (error) {
|
|
607
|
-
return {
|
|
360
|
+
return {
|
|
361
|
+
content: [
|
|
362
|
+
{
|
|
363
|
+
type: "text",
|
|
364
|
+
text: JSON.stringify(
|
|
365
|
+
{ success: false, error: String(error) },
|
|
366
|
+
null,
|
|
367
|
+
2
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
],
|
|
371
|
+
isError: true
|
|
372
|
+
};
|
|
608
373
|
}
|
|
609
374
|
}
|
|
610
375
|
);
|
|
@@ -625,13 +390,31 @@ function registerEnrollScenario(server2) {
|
|
|
625
390
|
const client = getClient();
|
|
626
391
|
const enrollment = await client.scenarios.enroll(scenarioId, friendId);
|
|
627
392
|
return {
|
|
628
|
-
content: [
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
393
|
+
content: [
|
|
394
|
+
{
|
|
395
|
+
type: "text",
|
|
396
|
+
text: JSON.stringify(
|
|
397
|
+
{ success: true, enrollment },
|
|
398
|
+
null,
|
|
399
|
+
2
|
|
400
|
+
)
|
|
401
|
+
}
|
|
402
|
+
]
|
|
632
403
|
};
|
|
633
404
|
} catch (error) {
|
|
634
|
-
return {
|
|
405
|
+
return {
|
|
406
|
+
content: [
|
|
407
|
+
{
|
|
408
|
+
type: "text",
|
|
409
|
+
text: JSON.stringify(
|
|
410
|
+
{ success: false, error: String(error) },
|
|
411
|
+
null,
|
|
412
|
+
2
|
|
413
|
+
)
|
|
414
|
+
}
|
|
415
|
+
],
|
|
416
|
+
isError: true
|
|
417
|
+
};
|
|
635
418
|
}
|
|
636
419
|
}
|
|
637
420
|
);
|
|
@@ -648,20 +431,32 @@ function registerManageTags(server2) {
|
|
|
648
431
|
tagName: z5.string().optional().describe("Tag name (for 'create' action)"),
|
|
649
432
|
tagColor: z5.string().optional().describe("Tag color hex code (for 'create' action, e.g. '#FF0000')"),
|
|
650
433
|
tagId: z5.string().optional().describe("Tag ID (for 'add' or 'remove' actions)"),
|
|
651
|
-
friendIds: z5.array(z5.string()).optional().describe(
|
|
434
|
+
friendIds: z5.array(z5.string()).optional().describe(
|
|
435
|
+
"Friend IDs to add/remove the tag from (for 'add' or 'remove' actions)"
|
|
436
|
+
)
|
|
652
437
|
},
|
|
653
438
|
async ({ action, tagName, tagColor, tagId, friendIds }) => {
|
|
654
439
|
try {
|
|
655
440
|
const client = getClient();
|
|
656
441
|
if (action === "create") {
|
|
657
442
|
if (!tagName) throw new Error("tagName is required for create action");
|
|
658
|
-
const tag = await client.tags.create({
|
|
443
|
+
const tag = await client.tags.create({
|
|
444
|
+
name: tagName,
|
|
445
|
+
color: tagColor
|
|
446
|
+
});
|
|
659
447
|
return {
|
|
660
|
-
content: [
|
|
448
|
+
content: [
|
|
449
|
+
{
|
|
450
|
+
type: "text",
|
|
451
|
+
text: JSON.stringify({ success: true, tag }, null, 2)
|
|
452
|
+
}
|
|
453
|
+
]
|
|
661
454
|
};
|
|
662
455
|
}
|
|
663
|
-
if (!tagId)
|
|
664
|
-
|
|
456
|
+
if (!tagId)
|
|
457
|
+
throw new Error("tagId is required for add/remove actions");
|
|
458
|
+
if (!friendIds?.length)
|
|
459
|
+
throw new Error("friendIds is required for add/remove actions");
|
|
665
460
|
const results = [];
|
|
666
461
|
for (const friendId of friendIds) {
|
|
667
462
|
if (action === "add") {
|
|
@@ -672,10 +467,27 @@ function registerManageTags(server2) {
|
|
|
672
467
|
results.push({ friendId, status: "ok" });
|
|
673
468
|
}
|
|
674
469
|
return {
|
|
675
|
-
content: [
|
|
470
|
+
content: [
|
|
471
|
+
{
|
|
472
|
+
type: "text",
|
|
473
|
+
text: JSON.stringify({ success: true, results }, null, 2)
|
|
474
|
+
}
|
|
475
|
+
]
|
|
676
476
|
};
|
|
677
477
|
} catch (error) {
|
|
678
|
-
return {
|
|
478
|
+
return {
|
|
479
|
+
content: [
|
|
480
|
+
{
|
|
481
|
+
type: "text",
|
|
482
|
+
text: JSON.stringify(
|
|
483
|
+
{ success: false, error: String(error) },
|
|
484
|
+
null,
|
|
485
|
+
2
|
|
486
|
+
)
|
|
487
|
+
}
|
|
488
|
+
],
|
|
489
|
+
isError: true
|
|
490
|
+
};
|
|
679
491
|
}
|
|
680
492
|
}
|
|
681
493
|
);
|
|
@@ -690,13 +502,22 @@ function registerCreateForm(server2) {
|
|
|
690
502
|
{
|
|
691
503
|
name: z6.string().describe("Form name"),
|
|
692
504
|
description: z6.string().optional().describe("Form description shown to users"),
|
|
693
|
-
fields: z6.string().describe(
|
|
505
|
+
fields: z6.string().describe(
|
|
506
|
+
"JSON string of form fields. Format: [{ name: string, label: string, type: 'text'|'email'|'tel'|'number'|'textarea'|'select'|'radio'|'checkbox'|'date', required?: boolean, options?: string[], placeholder?: string }]"
|
|
507
|
+
),
|
|
694
508
|
onSubmitTagId: z6.string().optional().describe("Tag ID to auto-apply when form is submitted"),
|
|
695
509
|
onSubmitScenarioId: z6.string().optional().describe("Scenario ID to auto-enroll when form is submitted"),
|
|
696
510
|
saveToMetadata: z6.boolean().default(true).describe("Save form responses to friend metadata"),
|
|
697
511
|
accountId: z6.string().optional().describe("LINE account ID (uses default if omitted)")
|
|
698
512
|
},
|
|
699
|
-
async ({
|
|
513
|
+
async ({
|
|
514
|
+
name,
|
|
515
|
+
description,
|
|
516
|
+
fields,
|
|
517
|
+
onSubmitTagId,
|
|
518
|
+
onSubmitScenarioId,
|
|
519
|
+
saveToMetadata
|
|
520
|
+
}) => {
|
|
700
521
|
try {
|
|
701
522
|
const client = getClient();
|
|
702
523
|
const form = await client.forms.create({
|
|
@@ -708,10 +529,27 @@ function registerCreateForm(server2) {
|
|
|
708
529
|
saveToMetadata
|
|
709
530
|
});
|
|
710
531
|
return {
|
|
711
|
-
content: [
|
|
532
|
+
content: [
|
|
533
|
+
{
|
|
534
|
+
type: "text",
|
|
535
|
+
text: JSON.stringify({ success: true, form }, null, 2)
|
|
536
|
+
}
|
|
537
|
+
]
|
|
712
538
|
};
|
|
713
539
|
} catch (error) {
|
|
714
|
-
return {
|
|
540
|
+
return {
|
|
541
|
+
content: [
|
|
542
|
+
{
|
|
543
|
+
type: "text",
|
|
544
|
+
text: JSON.stringify(
|
|
545
|
+
{ success: false, error: String(error) },
|
|
546
|
+
null,
|
|
547
|
+
2
|
|
548
|
+
)
|
|
549
|
+
}
|
|
550
|
+
],
|
|
551
|
+
isError: true
|
|
552
|
+
};
|
|
715
553
|
}
|
|
716
554
|
}
|
|
717
555
|
);
|
|
@@ -732,12 +570,34 @@ function registerCreateTrackedLink(server2) {
|
|
|
732
570
|
async ({ name, originalUrl, tagId, scenarioId }) => {
|
|
733
571
|
try {
|
|
734
572
|
const client = getClient();
|
|
735
|
-
const link = await client.trackedLinks.create({
|
|
573
|
+
const link = await client.trackedLinks.create({
|
|
574
|
+
name,
|
|
575
|
+
originalUrl,
|
|
576
|
+
tagId,
|
|
577
|
+
scenarioId
|
|
578
|
+
});
|
|
736
579
|
return {
|
|
737
|
-
content: [
|
|
580
|
+
content: [
|
|
581
|
+
{
|
|
582
|
+
type: "text",
|
|
583
|
+
text: JSON.stringify({ success: true, link }, null, 2)
|
|
584
|
+
}
|
|
585
|
+
]
|
|
738
586
|
};
|
|
739
587
|
} catch (error) {
|
|
740
|
-
return {
|
|
588
|
+
return {
|
|
589
|
+
content: [
|
|
590
|
+
{
|
|
591
|
+
type: "text",
|
|
592
|
+
text: JSON.stringify(
|
|
593
|
+
{ success: false, error: String(error) },
|
|
594
|
+
null,
|
|
595
|
+
2
|
|
596
|
+
)
|
|
597
|
+
}
|
|
598
|
+
],
|
|
599
|
+
isError: true
|
|
600
|
+
};
|
|
741
601
|
}
|
|
742
602
|
}
|
|
743
603
|
);
|
|
@@ -757,7 +617,9 @@ function registerCreateRichMenu(server2) {
|
|
|
757
617
|
height: z8.number().default(1686).describe("Menu height: 1686 for full, 843 for half")
|
|
758
618
|
}).default({ width: 2500, height: 1686 }).describe("Menu size in pixels"),
|
|
759
619
|
selected: z8.boolean().default(false).describe("Whether the rich menu is displayed by default"),
|
|
760
|
-
areas: z8.string().describe(
|
|
620
|
+
areas: z8.string().describe(
|
|
621
|
+
"JSON string of menu button areas. Format: [{ bounds: { x, y, width, height }, action: { type: 'uri'|'message'|'postback', uri?, text?, data? } }]"
|
|
622
|
+
),
|
|
761
623
|
setAsDefault: z8.boolean().default(false).describe("Set this as the default rich menu for all friends")
|
|
762
624
|
},
|
|
763
625
|
async ({ name, chatBarText, size, selected, areas, setAsDefault }) => {
|
|
@@ -774,10 +636,35 @@ function registerCreateRichMenu(server2) {
|
|
|
774
636
|
await client.richMenus.setDefault(menu.richMenuId);
|
|
775
637
|
}
|
|
776
638
|
return {
|
|
777
|
-
content: [
|
|
639
|
+
content: [
|
|
640
|
+
{
|
|
641
|
+
type: "text",
|
|
642
|
+
text: JSON.stringify(
|
|
643
|
+
{
|
|
644
|
+
success: true,
|
|
645
|
+
richMenuId: menu.richMenuId,
|
|
646
|
+
isDefault: setAsDefault
|
|
647
|
+
},
|
|
648
|
+
null,
|
|
649
|
+
2
|
|
650
|
+
)
|
|
651
|
+
}
|
|
652
|
+
]
|
|
778
653
|
};
|
|
779
654
|
} catch (error) {
|
|
780
|
-
return {
|
|
655
|
+
return {
|
|
656
|
+
content: [
|
|
657
|
+
{
|
|
658
|
+
type: "text",
|
|
659
|
+
text: JSON.stringify(
|
|
660
|
+
{ success: false, error: String(error) },
|
|
661
|
+
null,
|
|
662
|
+
2
|
|
663
|
+
)
|
|
664
|
+
}
|
|
665
|
+
],
|
|
666
|
+
isError: true
|
|
667
|
+
};
|
|
781
668
|
}
|
|
782
669
|
}
|
|
783
670
|
);
|
|
@@ -798,20 +685,43 @@ function registerListFriends(server2) {
|
|
|
798
685
|
async ({ tagId, limit, offset, accountId }) => {
|
|
799
686
|
try {
|
|
800
687
|
const client = getClient();
|
|
801
|
-
const result = await client.friends.list({
|
|
688
|
+
const result = await client.friends.list({
|
|
689
|
+
tagId,
|
|
690
|
+
limit,
|
|
691
|
+
offset,
|
|
692
|
+
accountId
|
|
693
|
+
});
|
|
802
694
|
return {
|
|
803
|
-
content: [
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
695
|
+
content: [
|
|
696
|
+
{
|
|
697
|
+
type: "text",
|
|
698
|
+
text: JSON.stringify(
|
|
699
|
+
{
|
|
700
|
+
success: true,
|
|
701
|
+
total: result.total,
|
|
702
|
+
hasNextPage: result.hasNextPage,
|
|
703
|
+
friends: result.items
|
|
704
|
+
},
|
|
705
|
+
null,
|
|
706
|
+
2
|
|
707
|
+
)
|
|
708
|
+
}
|
|
709
|
+
]
|
|
812
710
|
};
|
|
813
711
|
} catch (error) {
|
|
814
|
-
return {
|
|
712
|
+
return {
|
|
713
|
+
content: [
|
|
714
|
+
{
|
|
715
|
+
type: "text",
|
|
716
|
+
text: JSON.stringify(
|
|
717
|
+
{ success: false, error: String(error) },
|
|
718
|
+
null,
|
|
719
|
+
2
|
|
720
|
+
)
|
|
721
|
+
}
|
|
722
|
+
],
|
|
723
|
+
isError: true
|
|
724
|
+
};
|
|
815
725
|
}
|
|
816
726
|
}
|
|
817
727
|
);
|
|
@@ -831,10 +741,27 @@ function registerGetFriendDetail(server2) {
|
|
|
831
741
|
const client = getClient();
|
|
832
742
|
const friend = await client.friends.get(friendId);
|
|
833
743
|
return {
|
|
834
|
-
content: [
|
|
744
|
+
content: [
|
|
745
|
+
{
|
|
746
|
+
type: "text",
|
|
747
|
+
text: JSON.stringify({ success: true, friend }, null, 2)
|
|
748
|
+
}
|
|
749
|
+
]
|
|
835
750
|
};
|
|
836
751
|
} catch (error) {
|
|
837
|
-
return {
|
|
752
|
+
return {
|
|
753
|
+
content: [
|
|
754
|
+
{
|
|
755
|
+
type: "text",
|
|
756
|
+
text: JSON.stringify(
|
|
757
|
+
{ success: false, error: String(error) },
|
|
758
|
+
null,
|
|
759
|
+
2
|
|
760
|
+
)
|
|
761
|
+
}
|
|
762
|
+
],
|
|
763
|
+
isError: true
|
|
764
|
+
};
|
|
838
765
|
}
|
|
839
766
|
}
|
|
840
767
|
);
|
|
@@ -854,10 +781,31 @@ function registerGetFormSubmissions(server2) {
|
|
|
854
781
|
const client = getClient();
|
|
855
782
|
const submissions = await client.forms.getSubmissions(formId);
|
|
856
783
|
return {
|
|
857
|
-
content: [
|
|
784
|
+
content: [
|
|
785
|
+
{
|
|
786
|
+
type: "text",
|
|
787
|
+
text: JSON.stringify(
|
|
788
|
+
{ success: true, submissions },
|
|
789
|
+
null,
|
|
790
|
+
2
|
|
791
|
+
)
|
|
792
|
+
}
|
|
793
|
+
]
|
|
858
794
|
};
|
|
859
795
|
} catch (error) {
|
|
860
|
-
return {
|
|
796
|
+
return {
|
|
797
|
+
content: [
|
|
798
|
+
{
|
|
799
|
+
type: "text",
|
|
800
|
+
text: JSON.stringify(
|
|
801
|
+
{ success: false, error: String(error) },
|
|
802
|
+
null,
|
|
803
|
+
2
|
|
804
|
+
)
|
|
805
|
+
}
|
|
806
|
+
],
|
|
807
|
+
isError: true
|
|
808
|
+
};
|
|
861
809
|
}
|
|
862
810
|
}
|
|
863
811
|
);
|
|
@@ -877,10 +825,27 @@ function registerGetLinkClicks(server2) {
|
|
|
877
825
|
const client = getClient();
|
|
878
826
|
const link = await client.trackedLinks.get(linkId);
|
|
879
827
|
return {
|
|
880
|
-
content: [
|
|
828
|
+
content: [
|
|
829
|
+
{
|
|
830
|
+
type: "text",
|
|
831
|
+
text: JSON.stringify({ success: true, link }, null, 2)
|
|
832
|
+
}
|
|
833
|
+
]
|
|
881
834
|
};
|
|
882
835
|
} catch (error) {
|
|
883
|
-
return {
|
|
836
|
+
return {
|
|
837
|
+
content: [
|
|
838
|
+
{
|
|
839
|
+
type: "text",
|
|
840
|
+
text: JSON.stringify(
|
|
841
|
+
{ success: false, error: String(error) },
|
|
842
|
+
null,
|
|
843
|
+
2
|
|
844
|
+
)
|
|
845
|
+
}
|
|
846
|
+
],
|
|
847
|
+
isError: true
|
|
848
|
+
};
|
|
884
849
|
}
|
|
885
850
|
}
|
|
886
851
|
);
|
|
@@ -907,7 +872,12 @@ function registerAccountSummary(server2) {
|
|
|
907
872
|
const accounts = accountsData.success ? accountsData.data : [];
|
|
908
873
|
const accountStats = [];
|
|
909
874
|
for (const acc of accounts) {
|
|
910
|
-
const
|
|
875
|
+
const countRes = await fetch(
|
|
876
|
+
`${apiUrl}/api/friends/count?lineAccountId=${encodeURIComponent(acc.id)}`,
|
|
877
|
+
{ headers: { Authorization: `Bearer ${apiKey}` } }
|
|
878
|
+
);
|
|
879
|
+
const countData = await countRes.json();
|
|
880
|
+
const count = countData.success ? countData.data.count : 0;
|
|
911
881
|
accountStats.push({
|
|
912
882
|
id: acc.id,
|
|
913
883
|
name: acc.name,
|
|
@@ -917,9 +887,10 @@ function registerAccountSummary(server2) {
|
|
|
917
887
|
}
|
|
918
888
|
for (const acc of accountStats) {
|
|
919
889
|
try {
|
|
920
|
-
const healthRes = await fetch(
|
|
921
|
-
|
|
922
|
-
|
|
890
|
+
const healthRes = await fetch(
|
|
891
|
+
`${apiUrl}/api/accounts/${acc.id}/health`,
|
|
892
|
+
{ headers: { Authorization: `Bearer ${apiKey}` } }
|
|
893
|
+
);
|
|
923
894
|
const healthData = await healthRes.json();
|
|
924
895
|
if (healthData.success) {
|
|
925
896
|
acc.riskLevel = healthData.data.riskLevel;
|
|
@@ -928,13 +899,15 @@ function registerAccountSummary(server2) {
|
|
|
928
899
|
}
|
|
929
900
|
}
|
|
930
901
|
const [totalFriends, scenarios, broadcasts, tags, forms] = await Promise.all([
|
|
931
|
-
client.friends.count(
|
|
902
|
+
client.friends.count(),
|
|
932
903
|
client.scenarios.list({ accountId }),
|
|
933
904
|
client.broadcasts.list({ accountId }),
|
|
934
905
|
client.tags.list(),
|
|
935
906
|
client.forms.list()
|
|
936
907
|
]);
|
|
937
|
-
const activeScenarios = scenarios.filter(
|
|
908
|
+
const activeScenarios = scenarios.filter(
|
|
909
|
+
(s) => s.isActive
|
|
910
|
+
);
|
|
938
911
|
const recentBroadcasts = broadcasts.slice(0, 5);
|
|
939
912
|
const summary = {
|
|
940
913
|
friends: {
|
|
@@ -945,26 +918,65 @@ function registerAccountSummary(server2) {
|
|
|
945
918
|
scenarios: {
|
|
946
919
|
total: scenarios.length,
|
|
947
920
|
active: activeScenarios.length,
|
|
948
|
-
activeList: activeScenarios.map(
|
|
921
|
+
activeList: activeScenarios.map(
|
|
922
|
+
(s) => ({
|
|
923
|
+
id: s.id,
|
|
924
|
+
name: s.name,
|
|
925
|
+
triggerType: s.triggerType
|
|
926
|
+
})
|
|
927
|
+
)
|
|
949
928
|
},
|
|
950
929
|
broadcasts: {
|
|
951
930
|
total: broadcasts.length,
|
|
952
|
-
recent: recentBroadcasts.map(
|
|
931
|
+
recent: recentBroadcasts.map(
|
|
932
|
+
(b) => ({
|
|
933
|
+
id: b.id,
|
|
934
|
+
title: b.title,
|
|
935
|
+
status: b.status,
|
|
936
|
+
sentAt: b.sentAt
|
|
937
|
+
})
|
|
938
|
+
)
|
|
953
939
|
},
|
|
954
940
|
tags: {
|
|
955
941
|
total: tags.length,
|
|
956
|
-
list: tags.map((t) => ({
|
|
942
|
+
list: tags.map((t) => ({
|
|
943
|
+
id: t.id,
|
|
944
|
+
name: t.name
|
|
945
|
+
}))
|
|
957
946
|
},
|
|
958
947
|
forms: {
|
|
959
948
|
total: forms.length,
|
|
960
|
-
list: forms.map(
|
|
949
|
+
list: forms.map(
|
|
950
|
+
(f) => ({
|
|
951
|
+
id: f.id,
|
|
952
|
+
name: f.name,
|
|
953
|
+
submitCount: f.submitCount
|
|
954
|
+
})
|
|
955
|
+
)
|
|
961
956
|
}
|
|
962
957
|
};
|
|
963
958
|
return {
|
|
964
|
-
content: [
|
|
959
|
+
content: [
|
|
960
|
+
{
|
|
961
|
+
type: "text",
|
|
962
|
+
text: JSON.stringify(summary, null, 2)
|
|
963
|
+
}
|
|
964
|
+
]
|
|
965
965
|
};
|
|
966
966
|
} catch (error) {
|
|
967
|
-
return {
|
|
967
|
+
return {
|
|
968
|
+
content: [
|
|
969
|
+
{
|
|
970
|
+
type: "text",
|
|
971
|
+
text: JSON.stringify(
|
|
972
|
+
{ success: false, error: String(error) },
|
|
973
|
+
null,
|
|
974
|
+
2
|
|
975
|
+
)
|
|
976
|
+
}
|
|
977
|
+
],
|
|
978
|
+
isError: true
|
|
979
|
+
};
|
|
968
980
|
}
|
|
969
981
|
}
|
|
970
982
|
);
|
|
@@ -977,7 +989,14 @@ function registerListCrmObjects(server2) {
|
|
|
977
989
|
"list_crm_objects",
|
|
978
990
|
"List all CRM objects of a specific type: scenarios, forms, tags, rich menus, tracked links, or broadcasts.",
|
|
979
991
|
{
|
|
980
|
-
objectType: z14.enum([
|
|
992
|
+
objectType: z14.enum([
|
|
993
|
+
"scenarios",
|
|
994
|
+
"forms",
|
|
995
|
+
"tags",
|
|
996
|
+
"rich_menus",
|
|
997
|
+
"tracked_links",
|
|
998
|
+
"broadcasts"
|
|
999
|
+
]).describe("Type of CRM object to list"),
|
|
981
1000
|
accountId: z14.string().optional().describe("LINE account ID (uses default if omitted)")
|
|
982
1001
|
},
|
|
983
1002
|
async ({ objectType, accountId }) => {
|
|
@@ -1005,10 +1024,31 @@ function registerListCrmObjects(server2) {
|
|
|
1005
1024
|
break;
|
|
1006
1025
|
}
|
|
1007
1026
|
return {
|
|
1008
|
-
content: [
|
|
1027
|
+
content: [
|
|
1028
|
+
{
|
|
1029
|
+
type: "text",
|
|
1030
|
+
text: JSON.stringify(
|
|
1031
|
+
{ success: true, objectType, items },
|
|
1032
|
+
null,
|
|
1033
|
+
2
|
|
1034
|
+
)
|
|
1035
|
+
}
|
|
1036
|
+
]
|
|
1009
1037
|
};
|
|
1010
1038
|
} catch (error) {
|
|
1011
|
-
return {
|
|
1039
|
+
return {
|
|
1040
|
+
content: [
|
|
1041
|
+
{
|
|
1042
|
+
type: "text",
|
|
1043
|
+
text: JSON.stringify(
|
|
1044
|
+
{ success: false, error: String(error) },
|
|
1045
|
+
null,
|
|
1046
|
+
2
|
|
1047
|
+
)
|
|
1048
|
+
}
|
|
1049
|
+
],
|
|
1050
|
+
isError: true
|
|
1051
|
+
};
|
|
1012
1052
|
}
|
|
1013
1053
|
}
|
|
1014
1054
|
);
|
|
@@ -1037,7 +1077,7 @@ function registerAllResources(server2) {
|
|
|
1037
1077
|
server2.resource(
|
|
1038
1078
|
"Account Summary",
|
|
1039
1079
|
"line-harness://account/summary",
|
|
1040
|
-
async (
|
|
1080
|
+
async (_uri) => {
|
|
1041
1081
|
const client = getClient();
|
|
1042
1082
|
const [friendCount, scenarios, tags] = await Promise.all([
|
|
1043
1083
|
client.friends.count(),
|
|
@@ -1046,47 +1086,60 @@ function registerAllResources(server2) {
|
|
|
1046
1086
|
]);
|
|
1047
1087
|
const summary = {
|
|
1048
1088
|
friends: friendCount,
|
|
1049
|
-
activeScenarios: scenarios.filter(
|
|
1089
|
+
activeScenarios: scenarios.filter(
|
|
1090
|
+
(s) => s.isActive
|
|
1091
|
+
).length,
|
|
1050
1092
|
totalScenarios: scenarios.length,
|
|
1051
|
-
tags: tags.map((t) => ({
|
|
1093
|
+
tags: tags.map((t) => ({
|
|
1094
|
+
id: t.id,
|
|
1095
|
+
name: t.name
|
|
1096
|
+
}))
|
|
1052
1097
|
};
|
|
1053
1098
|
return {
|
|
1054
|
-
contents: [
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1099
|
+
contents: [
|
|
1100
|
+
{
|
|
1101
|
+
uri: "line-harness://account/summary",
|
|
1102
|
+
mimeType: "application/json",
|
|
1103
|
+
text: JSON.stringify(summary, null, 2)
|
|
1104
|
+
}
|
|
1105
|
+
]
|
|
1059
1106
|
};
|
|
1060
1107
|
}
|
|
1061
1108
|
);
|
|
1062
1109
|
server2.resource(
|
|
1063
1110
|
"Active Scenarios",
|
|
1064
1111
|
"line-harness://scenarios/active",
|
|
1065
|
-
async (
|
|
1112
|
+
async (_uri) => {
|
|
1066
1113
|
const client = getClient();
|
|
1067
1114
|
const scenarios = await client.scenarios.list();
|
|
1068
|
-
const active = scenarios.filter(
|
|
1115
|
+
const active = scenarios.filter(
|
|
1116
|
+
(s) => s.isActive
|
|
1117
|
+
);
|
|
1069
1118
|
return {
|
|
1070
|
-
contents: [
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1119
|
+
contents: [
|
|
1120
|
+
{
|
|
1121
|
+
uri: "line-harness://scenarios/active",
|
|
1122
|
+
mimeType: "application/json",
|
|
1123
|
+
text: JSON.stringify(active, null, 2)
|
|
1124
|
+
}
|
|
1125
|
+
]
|
|
1075
1126
|
};
|
|
1076
1127
|
}
|
|
1077
1128
|
);
|
|
1078
1129
|
server2.resource(
|
|
1079
1130
|
"Tags List",
|
|
1080
1131
|
"line-harness://tags/list",
|
|
1081
|
-
async (
|
|
1132
|
+
async (_uri) => {
|
|
1082
1133
|
const client = getClient();
|
|
1083
1134
|
const tags = await client.tags.list();
|
|
1084
1135
|
return {
|
|
1085
|
-
contents: [
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1136
|
+
contents: [
|
|
1137
|
+
{
|
|
1138
|
+
uri: "line-harness://tags/list",
|
|
1139
|
+
mimeType: "application/json",
|
|
1140
|
+
text: JSON.stringify(tags, null, 2)
|
|
1141
|
+
}
|
|
1142
|
+
]
|
|
1090
1143
|
};
|
|
1091
1144
|
}
|
|
1092
1145
|
);
|
|
@@ -1095,7 +1148,7 @@ function registerAllResources(server2) {
|
|
|
1095
1148
|
// src/index.ts
|
|
1096
1149
|
var server = new McpServer({
|
|
1097
1150
|
name: "line-harness",
|
|
1098
|
-
version: "0.
|
|
1151
|
+
version: "0.3.0"
|
|
1099
1152
|
});
|
|
1100
1153
|
registerAllTools(server);
|
|
1101
1154
|
registerAllResources(server);
|