@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 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
  );
@@ -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 count = await client.friends.count({ accountId: acc.id });
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(`${apiUrl}/api/accounts/${acc.id}/health`, {
921
- headers: { Authorization: `Bearer ${apiKey}` }
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({ accountId }),
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((s) => s.isActive);
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((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
+ )
949
928
  },
950
929
  broadcasts: {
951
930
  total: broadcasts.length,
952
- 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
+ )
953
939
  },
954
940
  tags: {
955
941
  total: tags.length,
956
- list: tags.map((t) => ({ id: t.id, name: t.name }))
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((f) => ({ id: f.id, name: f.name, submitCount: f.submitCount }))
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: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
959
+ content: [
960
+ {
961
+ type: "text",
962
+ text: JSON.stringify(summary, null, 2)
963
+ }
964
+ ]
965
965
  };
966
966
  } catch (error) {
967
- 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
+ };
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(["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"),
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: [{ 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
+ ]
1009
1037
  };
1010
1038
  } catch (error) {
1011
- 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
+ };
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 (uri) => {
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((s) => s.isActive).length,
1089
+ activeScenarios: scenarios.filter(
1090
+ (s) => s.isActive
1091
+ ).length,
1050
1092
  totalScenarios: scenarios.length,
1051
- tags: tags.map((t) => ({ id: t.id, name: t.name }))
1093
+ tags: tags.map((t) => ({
1094
+ id: t.id,
1095
+ name: t.name
1096
+ }))
1052
1097
  };
1053
1098
  return {
1054
- contents: [{
1055
- uri: "line-harness://account/summary",
1056
- mimeType: "application/json",
1057
- text: JSON.stringify(summary, null, 2)
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 (uri) => {
1112
+ async (_uri) => {
1066
1113
  const client = getClient();
1067
1114
  const scenarios = await client.scenarios.list();
1068
- const active = scenarios.filter((s) => s.isActive);
1115
+ const active = scenarios.filter(
1116
+ (s) => s.isActive
1117
+ );
1069
1118
  return {
1070
- contents: [{
1071
- uri: "line-harness://scenarios/active",
1072
- mimeType: "application/json",
1073
- text: JSON.stringify(active, null, 2)
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 (uri) => {
1132
+ async (_uri) => {
1082
1133
  const client = getClient();
1083
1134
  const tags = await client.tags.list();
1084
1135
  return {
1085
- contents: [{
1086
- uri: "line-harness://tags/list",
1087
- mimeType: "application/json",
1088
- text: JSON.stringify(tags, null, 2)
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.1.0"
1151
+ version: "0.3.0"
1099
1152
  });
1100
1153
  registerAllTools(server);
1101
1154
  registerAllResources(server);