@line-harness/mcp-server 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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("Message content. For text: plain string. For flex: JSON string of LINE Flex Message."),
444
- messageType: z.enum(["text", "flex"]).default("text").describe("Message type: 'text' for plain text, 'flex' for Flex Message JSON")
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(friendId, content, messageType);
49
+ const result = await client.friends.sendMessage(
50
+ friendId,
51
+ content,
52
+ messageType
53
+ );
450
54
  return {
451
- content: [{
452
- type: "text",
453
- text: JSON.stringify({ success: true, messageId: result.messageId }, null, 2)
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 { content: [{ type: "text", text: JSON.stringify({ success: false, error: String(error) }, null, 2) }], isError: true };
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("Message content. For text: plain string. For flex: JSON string."),
473
- targetType: z2.enum(["all", "tag", "segment"]).default("all").describe("Target audience: 'all' for everyone, 'tag' for a tag group, 'segment' for filtered conditions"),
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("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} }] }"),
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 ({ title, messageType, messageContent, targetType, targetTagId, segmentConditions, scheduledAt, accountId }) => {
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: [{ type: "text", text: JSON.stringify({ success: false, error: "segmentConditions is required when targetType is 'segment'" }, null, 2) }],
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: [{ type: "text", text: JSON.stringify({ success: false, error: "Scheduled segment broadcasts are not supported. Use scheduledAt only with targetType 'all' or 'tag'." }, null, 2) }],
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: [{ type: "text", text: JSON.stringify({ success: false, error: "segmentConditions must be valid JSON" }, null, 2) }],
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(broadcast2.id, parsedConditions);
513
- return { content: [{ type: "text", text: JSON.stringify({ success: true, broadcast: result2 }, null, 2) }] };
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 { content: [{ type: "text", text: JSON.stringify({ success: true, broadcast: result }, null, 2) }] };
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 { content: [{ type: "text", text: JSON.stringify({ success: false, error: String(error) }, null, 2) }], isError: true };
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("When to start: 'friend_add' on new friends, 'tag_added' when a tag is applied, 'manual' for explicit enrollment"),
547
- triggerTagId: z3.string().optional().describe("Required when triggerType is 'tag_added': the tag ID that triggers this scenario"),
548
- steps: z3.array(z3.object({
549
- delay: z3.string().describe("Delay before sending. Format: '0m' for immediate, '30m' for 30 minutes, '24h' for 24 hours"),
550
- type: z3.enum(["text", "flex"]).describe("Message type"),
551
- content: z3.string().describe("Message content")
552
- })).describe("Ordered list of message steps"),
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: [{ type: "text", text: JSON.stringify({ success: false, error: "triggerTagId is required when triggerType is 'tag_added'" }, null, 2) }],
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: [{ type: "text", text: JSON.stringify({ success: false, error: `Invalid delay format at step ${i + 1}: "${step.delay}". Use formats like '0m', '30m', '24h'.` }, null, 2) }],
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({ delayMinutes, type: step.type, content: step.content });
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
- type: "text",
603
- text: JSON.stringify({ success: true, scenario: scenarioWithSteps }, null, 2)
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 { content: [{ type: "text", text: JSON.stringify({ success: false, error: String(error) }, null, 2) }], isError: true };
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
- type: "text",
630
- text: JSON.stringify({ success: true, enrollment }, null, 2)
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 { content: [{ type: "text", text: JSON.stringify({ success: false, error: String(error) }, null, 2) }], isError: true };
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("Friend IDs to add/remove the tag from (for 'add' or 'remove' actions)")
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({ name: tagName, color: tagColor });
443
+ const tag = await client.tags.create({
444
+ name: tagName,
445
+ color: tagColor
446
+ });
659
447
  return {
660
- content: [{ type: "text", text: JSON.stringify({ success: true, tag }, null, 2) }]
448
+ content: [
449
+ {
450
+ type: "text",
451
+ text: JSON.stringify({ success: true, tag }, null, 2)
452
+ }
453
+ ]
661
454
  };
662
455
  }
663
- if (!tagId) throw new Error("tagId is required for add/remove actions");
664
- if (!friendIds?.length) throw new Error("friendIds is required for add/remove actions");
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: [{ type: "text", text: JSON.stringify({ success: true, results }, null, 2) }]
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 { content: [{ type: "text", text: JSON.stringify({ success: false, error: String(error) }, null, 2) }], isError: true };
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("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 }]"),
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 ({ name, description, fields, onSubmitTagId, onSubmitScenarioId, saveToMetadata, accountId }) => {
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: [{ type: "text", text: JSON.stringify({ success: true, form }, null, 2) }]
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 { content: [{ type: "text", text: JSON.stringify({ success: false, error: String(error) }, null, 2) }], isError: true };
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({ name, originalUrl, tagId, scenarioId });
573
+ const link = await client.trackedLinks.create({
574
+ name,
575
+ originalUrl,
576
+ tagId,
577
+ scenarioId
578
+ });
736
579
  return {
737
- content: [{ type: "text", text: JSON.stringify({ success: true, link }, null, 2) }]
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 { content: [{ type: "text", text: JSON.stringify({ success: false, error: String(error) }, null, 2) }], isError: true };
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("JSON string of menu button areas. Format: [{ bounds: { x, y, width, height }, action: { type: 'uri'|'message'|'postback', uri?, text?, data? } }]"),
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: [{ type: "text", text: JSON.stringify({ success: true, richMenuId: menu.richMenuId, isDefault: setAsDefault }, null, 2) }]
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 { content: [{ type: "text", text: JSON.stringify({ success: false, error: String(error) }, null, 2) }], isError: true };
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({ tagId, limit, offset, accountId });
688
+ const result = await client.friends.list({
689
+ tagId,
690
+ limit,
691
+ offset,
692
+ accountId
693
+ });
802
694
  return {
803
- content: [{
804
- type: "text",
805
- text: JSON.stringify({
806
- success: true,
807
- total: result.total,
808
- hasNextPage: result.hasNextPage,
809
- friends: result.items
810
- }, null, 2)
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 { content: [{ type: "text", text: JSON.stringify({ success: false, error: String(error) }, null, 2) }], isError: true };
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: [{ type: "text", text: JSON.stringify({ success: true, friend }, null, 2) }]
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 { content: [{ type: "text", text: JSON.stringify({ success: false, error: String(error) }, null, 2) }], isError: true };
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: [{ type: "text", text: JSON.stringify({ success: true, submissions }, null, 2) }]
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 { content: [{ type: "text", text: JSON.stringify({ success: false, error: String(error) }, null, 2) }], isError: true };
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: [{ type: "text", text: JSON.stringify({ success: true, link }, null, 2) }]
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 { content: [{ type: "text", text: JSON.stringify({ success: false, error: String(error) }, null, 2) }], isError: true };
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
  );
@@ -891,47 +856,127 @@ import { z as z13 } from "zod";
891
856
  function registerAccountSummary(server2) {
892
857
  server2.tool(
893
858
  "account_summary",
894
- "Get a high-level summary of the LINE account: friend count, active scenarios, recent broadcasts, tags, and forms. Use this to understand the current state before making changes.",
859
+ "Get a high-level summary of the LINE account: friend count per account (DB + LINE API stats), active scenarios, recent broadcasts, tags, and forms. Use this to understand the current state before making changes.",
895
860
  {
896
861
  accountId: z13.string().optional().describe("LINE account ID (uses default if omitted)")
897
862
  },
898
863
  async ({ accountId }) => {
899
864
  try {
900
865
  const client = getClient();
901
- const [friendCount, scenarios, broadcasts, tags, forms] = await Promise.all([
902
- client.friends.count({ accountId }),
866
+ const apiUrl = process.env.LINE_HARNESS_API_URL;
867
+ const apiKey = process.env.LINE_HARNESS_API_KEY;
868
+ const accountsRes = await fetch(`${apiUrl}/api/line-accounts`, {
869
+ headers: { Authorization: `Bearer ${apiKey}` }
870
+ });
871
+ const accountsData = await accountsRes.json();
872
+ const accounts = accountsData.success ? accountsData.data : [];
873
+ const accountStats = [];
874
+ for (const acc of accounts) {
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;
881
+ accountStats.push({
882
+ id: acc.id,
883
+ name: acc.name,
884
+ channelId: acc.channelId,
885
+ friendsInDb: count
886
+ });
887
+ }
888
+ for (const acc of accountStats) {
889
+ try {
890
+ const healthRes = await fetch(
891
+ `${apiUrl}/api/accounts/${acc.id}/health`,
892
+ { headers: { Authorization: `Bearer ${apiKey}` } }
893
+ );
894
+ const healthData = await healthRes.json();
895
+ if (healthData.success) {
896
+ acc.riskLevel = healthData.data.riskLevel;
897
+ }
898
+ } catch {
899
+ }
900
+ }
901
+ const [totalFriends, scenarios, broadcasts, tags, forms] = await Promise.all([
902
+ client.friends.count(),
903
903
  client.scenarios.list({ accountId }),
904
904
  client.broadcasts.list({ accountId }),
905
905
  client.tags.list(),
906
906
  client.forms.list()
907
907
  ]);
908
- const activeScenarios = scenarios.filter((s) => s.isActive);
908
+ const activeScenarios = scenarios.filter(
909
+ (s) => s.isActive
910
+ );
909
911
  const recentBroadcasts = broadcasts.slice(0, 5);
910
912
  const summary = {
911
- friends: { total: friendCount },
913
+ friends: {
914
+ totalDbRecords: totalFriends,
915
+ note: "totalDbRecords includes both Account \u2460 and \u2461 records. Same user on different accounts = separate records. Use per-account counts below for accurate numbers.",
916
+ perAccount: accountStats
917
+ },
912
918
  scenarios: {
913
919
  total: scenarios.length,
914
920
  active: activeScenarios.length,
915
- activeList: activeScenarios.map((s) => ({ id: s.id, name: s.name, triggerType: s.triggerType }))
921
+ activeList: activeScenarios.map(
922
+ (s) => ({
923
+ id: s.id,
924
+ name: s.name,
925
+ triggerType: s.triggerType
926
+ })
927
+ )
916
928
  },
917
929
  broadcasts: {
918
930
  total: broadcasts.length,
919
- recent: recentBroadcasts.map((b) => ({ id: b.id, title: b.title, status: b.status, sentAt: b.sentAt }))
931
+ recent: recentBroadcasts.map(
932
+ (b) => ({
933
+ id: b.id,
934
+ title: b.title,
935
+ status: b.status,
936
+ sentAt: b.sentAt
937
+ })
938
+ )
920
939
  },
921
940
  tags: {
922
941
  total: tags.length,
923
- list: tags.map((t) => ({ id: t.id, name: t.name }))
942
+ list: tags.map((t) => ({
943
+ id: t.id,
944
+ name: t.name
945
+ }))
924
946
  },
925
947
  forms: {
926
948
  total: forms.length,
927
- list: forms.map((f) => ({ id: f.id, name: f.name }))
949
+ list: forms.map(
950
+ (f) => ({
951
+ id: f.id,
952
+ name: f.name,
953
+ submitCount: f.submitCount
954
+ })
955
+ )
928
956
  }
929
957
  };
930
958
  return {
931
- content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
959
+ content: [
960
+ {
961
+ type: "text",
962
+ text: JSON.stringify(summary, null, 2)
963
+ }
964
+ ]
932
965
  };
933
966
  } catch (error) {
934
- return { content: [{ type: "text", text: JSON.stringify({ success: false, error: String(error) }, null, 2) }], isError: true };
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
+ };
935
980
  }
936
981
  }
937
982
  );
@@ -944,7 +989,14 @@ function registerListCrmObjects(server2) {
944
989
  "list_crm_objects",
945
990
  "List all CRM objects of a specific type: scenarios, forms, tags, rich menus, tracked links, or broadcasts.",
946
991
  {
947
- objectType: z14.enum(["scenarios", "forms", "tags", "rich_menus", "tracked_links", "broadcasts"]).describe("Type of CRM object to list"),
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"),
948
1000
  accountId: z14.string().optional().describe("LINE account ID (uses default if omitted)")
949
1001
  },
950
1002
  async ({ objectType, accountId }) => {
@@ -972,10 +1024,31 @@ function registerListCrmObjects(server2) {
972
1024
  break;
973
1025
  }
974
1026
  return {
975
- content: [{ type: "text", text: JSON.stringify({ success: true, objectType, items }, null, 2) }]
1027
+ content: [
1028
+ {
1029
+ type: "text",
1030
+ text: JSON.stringify(
1031
+ { success: true, objectType, items },
1032
+ null,
1033
+ 2
1034
+ )
1035
+ }
1036
+ ]
976
1037
  };
977
1038
  } catch (error) {
978
- return { content: [{ type: "text", text: JSON.stringify({ success: false, error: String(error) }, null, 2) }], isError: true };
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
+ };
979
1052
  }
980
1053
  }
981
1054
  );
@@ -1004,7 +1077,7 @@ function registerAllResources(server2) {
1004
1077
  server2.resource(
1005
1078
  "Account Summary",
1006
1079
  "line-harness://account/summary",
1007
- async (uri) => {
1080
+ async (_uri) => {
1008
1081
  const client = getClient();
1009
1082
  const [friendCount, scenarios, tags] = await Promise.all([
1010
1083
  client.friends.count(),
@@ -1013,47 +1086,60 @@ function registerAllResources(server2) {
1013
1086
  ]);
1014
1087
  const summary = {
1015
1088
  friends: friendCount,
1016
- activeScenarios: scenarios.filter((s) => s.isActive).length,
1089
+ activeScenarios: scenarios.filter(
1090
+ (s) => s.isActive
1091
+ ).length,
1017
1092
  totalScenarios: scenarios.length,
1018
- tags: tags.map((t) => ({ id: t.id, name: t.name }))
1093
+ tags: tags.map((t) => ({
1094
+ id: t.id,
1095
+ name: t.name
1096
+ }))
1019
1097
  };
1020
1098
  return {
1021
- contents: [{
1022
- uri: "line-harness://account/summary",
1023
- mimeType: "application/json",
1024
- text: JSON.stringify(summary, null, 2)
1025
- }]
1099
+ contents: [
1100
+ {
1101
+ uri: "line-harness://account/summary",
1102
+ mimeType: "application/json",
1103
+ text: JSON.stringify(summary, null, 2)
1104
+ }
1105
+ ]
1026
1106
  };
1027
1107
  }
1028
1108
  );
1029
1109
  server2.resource(
1030
1110
  "Active Scenarios",
1031
1111
  "line-harness://scenarios/active",
1032
- async (uri) => {
1112
+ async (_uri) => {
1033
1113
  const client = getClient();
1034
1114
  const scenarios = await client.scenarios.list();
1035
- const active = scenarios.filter((s) => s.isActive);
1115
+ const active = scenarios.filter(
1116
+ (s) => s.isActive
1117
+ );
1036
1118
  return {
1037
- contents: [{
1038
- uri: "line-harness://scenarios/active",
1039
- mimeType: "application/json",
1040
- text: JSON.stringify(active, null, 2)
1041
- }]
1119
+ contents: [
1120
+ {
1121
+ uri: "line-harness://scenarios/active",
1122
+ mimeType: "application/json",
1123
+ text: JSON.stringify(active, null, 2)
1124
+ }
1125
+ ]
1042
1126
  };
1043
1127
  }
1044
1128
  );
1045
1129
  server2.resource(
1046
1130
  "Tags List",
1047
1131
  "line-harness://tags/list",
1048
- async (uri) => {
1132
+ async (_uri) => {
1049
1133
  const client = getClient();
1050
1134
  const tags = await client.tags.list();
1051
1135
  return {
1052
- contents: [{
1053
- uri: "line-harness://tags/list",
1054
- mimeType: "application/json",
1055
- text: JSON.stringify(tags, null, 2)
1056
- }]
1136
+ contents: [
1137
+ {
1138
+ uri: "line-harness://tags/list",
1139
+ mimeType: "application/json",
1140
+ text: JSON.stringify(tags, null, 2)
1141
+ }
1142
+ ]
1057
1143
  };
1058
1144
  }
1059
1145
  );
@@ -1062,7 +1148,7 @@ function registerAllResources(server2) {
1062
1148
  // src/index.ts
1063
1149
  var server = new McpServer({
1064
1150
  name: "line-harness",
1065
- version: "0.1.0"
1151
+ version: "0.3.0"
1066
1152
  });
1067
1153
  registerAllTools(server);
1068
1154
  registerAllResources(server);