@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.
@@ -0,0 +1,90 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { getClient } from "../client.js";
4
+
5
+ export function registerCreateRichMenu(server: McpServer): void {
6
+ server.tool(
7
+ "create_rich_menu",
8
+ "Create a LINE rich menu (the persistent menu at the bottom of the chat). Image must be uploaded separately via LINE Developers Console. This creates the menu structure and button areas.",
9
+ {
10
+ name: z.string().describe("Rich menu name"),
11
+ chatBarText: z
12
+ .string()
13
+ .default("\u30E1\u30CB\u30E5\u30FC")
14
+ .describe("Text shown on the chat bar button"),
15
+ size: z
16
+ .object({
17
+ width: z
18
+ .number()
19
+ .default(2500)
20
+ .describe("Menu width in pixels (2500 for full-width)"),
21
+ height: z
22
+ .number()
23
+ .default(1686)
24
+ .describe("Menu height: 1686 for full, 843 for half"),
25
+ })
26
+ .default({ width: 2500, height: 1686 })
27
+ .describe("Menu size in pixels"),
28
+ selected: z
29
+ .boolean()
30
+ .default(false)
31
+ .describe("Whether the rich menu is displayed by default"),
32
+ areas: z
33
+ .string()
34
+ .describe(
35
+ "JSON string of menu button areas. Format: [{ bounds: { x, y, width, height }, action: { type: 'uri'|'message'|'postback', uri?, text?, data? } }]",
36
+ ),
37
+ setAsDefault: z
38
+ .boolean()
39
+ .default(false)
40
+ .describe("Set this as the default rich menu for all friends"),
41
+ },
42
+ async ({ name, chatBarText, size, selected, areas, setAsDefault }) => {
43
+ try {
44
+ const client = getClient();
45
+ const menu = await client.richMenus.create({
46
+ name,
47
+ chatBarText,
48
+ size,
49
+ selected,
50
+ areas: JSON.parse(areas),
51
+ });
52
+
53
+ if (setAsDefault) {
54
+ await client.richMenus.setDefault(menu.richMenuId);
55
+ }
56
+
57
+ return {
58
+ content: [
59
+ {
60
+ type: "text" as const,
61
+ text: JSON.stringify(
62
+ {
63
+ success: true,
64
+ richMenuId: menu.richMenuId,
65
+ isDefault: setAsDefault,
66
+ },
67
+ null,
68
+ 2,
69
+ ),
70
+ },
71
+ ],
72
+ };
73
+ } catch (error) {
74
+ return {
75
+ content: [
76
+ {
77
+ type: "text" as const,
78
+ text: JSON.stringify(
79
+ { success: false, error: String(error) },
80
+ null,
81
+ 2,
82
+ ),
83
+ },
84
+ ],
85
+ isError: true,
86
+ };
87
+ }
88
+ },
89
+ );
90
+ }
@@ -0,0 +1,152 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { parseDelay } from "@line-harness/sdk";
4
+ import { getClient } from "../client.js";
5
+
6
+ export function registerCreateScenario(server: McpServer): void {
7
+ server.tool(
8
+ "create_scenario",
9
+ "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.",
10
+ {
11
+ name: z.string().describe("Scenario name"),
12
+ triggerType: z
13
+ .enum(["friend_add", "tag_added", "manual"])
14
+ .describe(
15
+ "When to start: 'friend_add' on new friends, 'tag_added' when a tag is applied, 'manual' for explicit enrollment",
16
+ ),
17
+ triggerTagId: z
18
+ .string()
19
+ .optional()
20
+ .describe(
21
+ "Required when triggerType is 'tag_added': the tag ID that triggers this scenario",
22
+ ),
23
+ steps: z
24
+ .array(
25
+ z.object({
26
+ delay: z
27
+ .string()
28
+ .describe(
29
+ "Delay before sending. Format: '0m' for immediate, '30m' for 30 minutes, '24h' for 24 hours",
30
+ ),
31
+ type: z.enum(["text", "flex"]).describe("Message type"),
32
+ content: z.string().describe("Message content"),
33
+ }),
34
+ )
35
+ .describe("Ordered list of message steps"),
36
+ accountId: z
37
+ .string()
38
+ .optional()
39
+ .describe("LINE account ID (uses default if omitted)"),
40
+ },
41
+ async ({ name, triggerType, triggerTagId, steps, accountId }) => {
42
+ try {
43
+ if (triggerType === "tag_added" && !triggerTagId) {
44
+ return {
45
+ content: [
46
+ {
47
+ type: "text" as const,
48
+ text: JSON.stringify(
49
+ {
50
+ success: false,
51
+ error:
52
+ "triggerTagId is required when triggerType is 'tag_added'",
53
+ },
54
+ null,
55
+ 2,
56
+ ),
57
+ },
58
+ ],
59
+ isError: true,
60
+ };
61
+ }
62
+
63
+ const parsedSteps: Array<{
64
+ delayMinutes: number;
65
+ type: "text" | "flex";
66
+ content: string;
67
+ }> = [];
68
+
69
+ for (let i = 0; i < steps.length; i++) {
70
+ const step = steps[i];
71
+ let delayMinutes: number;
72
+ try {
73
+ delayMinutes = parseDelay(step.delay);
74
+ } catch {
75
+ return {
76
+ content: [
77
+ {
78
+ type: "text" as const,
79
+ text: JSON.stringify(
80
+ {
81
+ success: false,
82
+ error: `Invalid delay format at step ${i + 1}: "${step.delay}". Use formats like '0m', '30m', '24h'.`,
83
+ },
84
+ null,
85
+ 2,
86
+ ),
87
+ },
88
+ ],
89
+ isError: true,
90
+ };
91
+ }
92
+ parsedSteps.push({
93
+ delayMinutes,
94
+ type: step.type,
95
+ content: step.content,
96
+ });
97
+ }
98
+
99
+ const client = getClient();
100
+ const scenario = await client.scenarios.create({
101
+ name,
102
+ triggerType,
103
+ triggerTagId,
104
+ lineAccountId: accountId,
105
+ });
106
+
107
+ try {
108
+ for (let i = 0; i < parsedSteps.length; i++) {
109
+ const step = parsedSteps[i];
110
+ await client.scenarios.addStep(scenario.id, {
111
+ stepOrder: i + 1,
112
+ delayMinutes: step.delayMinutes,
113
+ messageType: step.type,
114
+ messageContent: step.content,
115
+ });
116
+ }
117
+ } catch (stepError) {
118
+ await client.scenarios.delete(scenario.id).catch(() => {});
119
+ throw stepError;
120
+ }
121
+
122
+ const scenarioWithSteps = await client.scenarios.get(scenario.id);
123
+ return {
124
+ content: [
125
+ {
126
+ type: "text" as const,
127
+ text: JSON.stringify(
128
+ { success: true, scenario: scenarioWithSteps },
129
+ null,
130
+ 2,
131
+ ),
132
+ },
133
+ ],
134
+ };
135
+ } catch (error) {
136
+ return {
137
+ content: [
138
+ {
139
+ type: "text" as const,
140
+ text: JSON.stringify(
141
+ { success: false, error: String(error) },
142
+ null,
143
+ 2,
144
+ ),
145
+ },
146
+ ],
147
+ isError: true,
148
+ };
149
+ }
150
+ },
151
+ );
152
+ }
@@ -0,0 +1,57 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { getClient } from "../client.js";
4
+
5
+ export function registerCreateTrackedLink(server: McpServer): void {
6
+ server.tool(
7
+ "create_tracked_link",
8
+ "Create a click-tracking link. When clicked, can auto-tag the user and enroll them in a scenario.",
9
+ {
10
+ name: z.string().describe("Link name (internal label)"),
11
+ originalUrl: z
12
+ .string()
13
+ .describe("The destination URL to redirect to"),
14
+ tagId: z
15
+ .string()
16
+ .optional()
17
+ .describe("Tag ID to auto-apply on click"),
18
+ scenarioId: z
19
+ .string()
20
+ .optional()
21
+ .describe("Scenario ID to auto-enroll on click"),
22
+ },
23
+ async ({ name, originalUrl, tagId, scenarioId }) => {
24
+ try {
25
+ const client = getClient();
26
+ const link = await client.trackedLinks.create({
27
+ name,
28
+ originalUrl,
29
+ tagId,
30
+ scenarioId,
31
+ });
32
+ return {
33
+ content: [
34
+ {
35
+ type: "text" as const,
36
+ text: JSON.stringify({ success: true, link }, null, 2),
37
+ },
38
+ ],
39
+ };
40
+ } catch (error) {
41
+ return {
42
+ content: [
43
+ {
44
+ type: "text" as const,
45
+ text: JSON.stringify(
46
+ { success: false, error: String(error) },
47
+ null,
48
+ 2,
49
+ ),
50
+ },
51
+ ],
52
+ isError: true,
53
+ };
54
+ }
55
+ },
56
+ );
57
+ }
@@ -0,0 +1,48 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { getClient } from "../client.js";
4
+
5
+ export function registerEnrollScenario(server: McpServer): void {
6
+ server.tool(
7
+ "enroll_in_scenario",
8
+ "Enroll a friend into a scenario. The friend will start receiving the scenario's step messages from step 1.",
9
+ {
10
+ scenarioId: z
11
+ .string()
12
+ .describe("The scenario ID to enroll the friend in"),
13
+ friendId: z.string().describe("The friend's ID to enroll"),
14
+ },
15
+ async ({ scenarioId, friendId }) => {
16
+ try {
17
+ const client = getClient();
18
+ const enrollment = await client.scenarios.enroll(scenarioId, friendId);
19
+ return {
20
+ content: [
21
+ {
22
+ type: "text" as const,
23
+ text: JSON.stringify(
24
+ { success: true, enrollment },
25
+ null,
26
+ 2,
27
+ ),
28
+ },
29
+ ],
30
+ };
31
+ } catch (error) {
32
+ return {
33
+ content: [
34
+ {
35
+ type: "text" as const,
36
+ text: JSON.stringify(
37
+ { success: false, error: String(error) },
38
+ null,
39
+ 2,
40
+ ),
41
+ },
42
+ ],
43
+ isError: true,
44
+ };
45
+ }
46
+ },
47
+ );
48
+ }
@@ -0,0 +1,45 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { getClient } from "../client.js";
4
+
5
+ export function registerGetFormSubmissions(server: McpServer): void {
6
+ server.tool(
7
+ "get_form_submissions",
8
+ "Get all submissions for a specific form. Returns response data with timestamps and friend IDs.",
9
+ {
10
+ formId: z.string().describe("The form ID to get submissions for"),
11
+ },
12
+ async ({ formId }) => {
13
+ try {
14
+ const client = getClient();
15
+ const submissions = await client.forms.getSubmissions(formId);
16
+ return {
17
+ content: [
18
+ {
19
+ type: "text" as const,
20
+ text: JSON.stringify(
21
+ { success: true, submissions },
22
+ null,
23
+ 2,
24
+ ),
25
+ },
26
+ ],
27
+ };
28
+ } catch (error) {
29
+ return {
30
+ content: [
31
+ {
32
+ type: "text" as const,
33
+ text: JSON.stringify(
34
+ { success: false, error: String(error) },
35
+ null,
36
+ 2,
37
+ ),
38
+ },
39
+ ],
40
+ isError: true,
41
+ };
42
+ }
43
+ },
44
+ );
45
+ }
@@ -0,0 +1,41 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { getClient } from "../client.js";
4
+
5
+ export function registerGetFriendDetail(server: McpServer): void {
6
+ server.tool(
7
+ "get_friend_detail",
8
+ "Get detailed information about a specific friend including tags, metadata, and profile.",
9
+ {
10
+ friendId: z.string().describe("The friend's ID"),
11
+ },
12
+ async ({ friendId }) => {
13
+ try {
14
+ const client = getClient();
15
+ const friend = await client.friends.get(friendId);
16
+ return {
17
+ content: [
18
+ {
19
+ type: "text" as const,
20
+ text: JSON.stringify({ success: true, friend }, null, 2),
21
+ },
22
+ ],
23
+ };
24
+ } catch (error) {
25
+ return {
26
+ content: [
27
+ {
28
+ type: "text" as const,
29
+ text: JSON.stringify(
30
+ { success: false, error: String(error) },
31
+ null,
32
+ 2,
33
+ ),
34
+ },
35
+ ],
36
+ isError: true,
37
+ };
38
+ }
39
+ },
40
+ );
41
+ }
@@ -0,0 +1,41 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { getClient } from "../client.js";
4
+
5
+ export function registerGetLinkClicks(server: McpServer): void {
6
+ server.tool(
7
+ "get_link_clicks",
8
+ "Get click analytics for a tracked link including total clicks and per-friend click history.",
9
+ {
10
+ linkId: z.string().describe("The tracked link ID"),
11
+ },
12
+ async ({ linkId }) => {
13
+ try {
14
+ const client = getClient();
15
+ const link = await client.trackedLinks.get(linkId);
16
+ return {
17
+ content: [
18
+ {
19
+ type: "text" as const,
20
+ text: JSON.stringify({ success: true, link }, null, 2),
21
+ },
22
+ ],
23
+ };
24
+ } catch (error) {
25
+ return {
26
+ content: [
27
+ {
28
+ type: "text" as const,
29
+ text: JSON.stringify(
30
+ { success: false, error: String(error) },
31
+ null,
32
+ 2,
33
+ ),
34
+ },
35
+ ],
36
+ isError: true,
37
+ };
38
+ }
39
+ },
40
+ );
41
+ }
@@ -0,0 +1,32 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { registerSendMessage } from "./send-message.js";
3
+ import { registerBroadcast } from "./broadcast.js";
4
+ import { registerCreateScenario } from "./create-scenario.js";
5
+ import { registerEnrollScenario } from "./enroll-scenario.js";
6
+ import { registerManageTags } from "./manage-tags.js";
7
+ import { registerCreateForm } from "./create-form.js";
8
+ import { registerCreateTrackedLink } from "./create-tracked-link.js";
9
+ import { registerCreateRichMenu } from "./create-rich-menu.js";
10
+ import { registerListFriends } from "./list-friends.js";
11
+ import { registerGetFriendDetail } from "./get-friend-detail.js";
12
+ import { registerGetFormSubmissions } from "./get-form-submissions.js";
13
+ import { registerGetLinkClicks } from "./get-link-clicks.js";
14
+ import { registerAccountSummary } from "./account-summary.js";
15
+ import { registerListCrmObjects } from "./list-crm-objects.js";
16
+
17
+ export function registerAllTools(server: McpServer): void {
18
+ registerSendMessage(server);
19
+ registerBroadcast(server);
20
+ registerCreateScenario(server);
21
+ registerEnrollScenario(server);
22
+ registerManageTags(server);
23
+ registerCreateForm(server);
24
+ registerCreateTrackedLink(server);
25
+ registerCreateRichMenu(server);
26
+ registerListFriends(server);
27
+ registerGetFriendDetail(server);
28
+ registerGetFormSubmissions(server);
29
+ registerGetLinkClicks(server);
30
+ registerAccountSummary(server);
31
+ registerListCrmObjects(server);
32
+ }
@@ -0,0 +1,80 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { getClient } from "../client.js";
4
+
5
+ export function registerListCrmObjects(server: McpServer): void {
6
+ server.tool(
7
+ "list_crm_objects",
8
+ "List all CRM objects of a specific type: scenarios, forms, tags, rich menus, tracked links, or broadcasts.",
9
+ {
10
+ objectType: z
11
+ .enum([
12
+ "scenarios",
13
+ "forms",
14
+ "tags",
15
+ "rich_menus",
16
+ "tracked_links",
17
+ "broadcasts",
18
+ ])
19
+ .describe("Type of CRM object to list"),
20
+ accountId: z
21
+ .string()
22
+ .optional()
23
+ .describe("LINE account ID (uses default if omitted)"),
24
+ },
25
+ async ({ objectType, accountId }) => {
26
+ try {
27
+ const client = getClient();
28
+ let items: unknown;
29
+
30
+ switch (objectType) {
31
+ case "scenarios":
32
+ items = await client.scenarios.list({ accountId });
33
+ break;
34
+ case "forms":
35
+ items = await client.forms.list();
36
+ break;
37
+ case "tags":
38
+ items = await client.tags.list();
39
+ break;
40
+ case "rich_menus":
41
+ items = await client.richMenus.list();
42
+ break;
43
+ case "tracked_links":
44
+ items = await client.trackedLinks.list();
45
+ break;
46
+ case "broadcasts":
47
+ items = await client.broadcasts.list({ accountId });
48
+ break;
49
+ }
50
+
51
+ return {
52
+ content: [
53
+ {
54
+ type: "text" as const,
55
+ text: JSON.stringify(
56
+ { success: true, objectType, items },
57
+ null,
58
+ 2,
59
+ ),
60
+ },
61
+ ],
62
+ };
63
+ } catch (error) {
64
+ return {
65
+ content: [
66
+ {
67
+ type: "text" as const,
68
+ text: JSON.stringify(
69
+ { success: false, error: String(error) },
70
+ null,
71
+ 2,
72
+ ),
73
+ },
74
+ ],
75
+ isError: true,
76
+ };
77
+ }
78
+ },
79
+ );
80
+ }
@@ -0,0 +1,64 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { getClient } from "../client.js";
4
+
5
+ export function registerListFriends(server: McpServer): void {
6
+ server.tool(
7
+ "list_friends",
8
+ "List friends with optional filtering by tag. Returns paginated results with friend details.",
9
+ {
10
+ tagId: z.string().optional().describe("Filter by tag ID"),
11
+ limit: z
12
+ .number()
13
+ .default(20)
14
+ .describe("Number of friends to return (max 100)"),
15
+ offset: z.number().default(0).describe("Offset for pagination"),
16
+ accountId: z
17
+ .string()
18
+ .optional()
19
+ .describe("LINE account ID (uses default if omitted)"),
20
+ },
21
+ async ({ tagId, limit, offset, accountId }) => {
22
+ try {
23
+ const client = getClient();
24
+ const result = await client.friends.list({
25
+ tagId,
26
+ limit,
27
+ offset,
28
+ accountId,
29
+ });
30
+ return {
31
+ content: [
32
+ {
33
+ type: "text" as const,
34
+ text: JSON.stringify(
35
+ {
36
+ success: true,
37
+ total: result.total,
38
+ hasNextPage: result.hasNextPage,
39
+ friends: result.items,
40
+ },
41
+ null,
42
+ 2,
43
+ ),
44
+ },
45
+ ],
46
+ };
47
+ } catch (error) {
48
+ return {
49
+ content: [
50
+ {
51
+ type: "text" as const,
52
+ text: JSON.stringify(
53
+ { success: false, error: String(error) },
54
+ null,
55
+ 2,
56
+ ),
57
+ },
58
+ ],
59
+ isError: true,
60
+ };
61
+ }
62
+ },
63
+ );
64
+ }