@line-harness/mcp-server 0.4.1 → 0.6.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 +383 -3
- package/package.json +1 -1
- package/src/tools/auto-track-urls.ts +112 -0
- package/src/tools/broadcast.ts +18 -2
- package/src/tools/get-conversion-logs.ts +61 -0
- package/src/tools/index.ts +6 -0
- package/src/tools/manage-ad-platforms.ts +170 -0
- package/src/tools/manage-staff.ts +104 -0
- package/src/tools/send-message.ts +11 -1
package/dist/index.js
CHANGED
|
@@ -29,6 +29,84 @@ function getClient() {
|
|
|
29
29
|
return clientInstance;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// src/tools/auto-track-urls.ts
|
|
33
|
+
async function autoTrackUrls(client, messageContent, messageType, title) {
|
|
34
|
+
if (messageType !== "flex") {
|
|
35
|
+
return { content: messageContent, trackedUrls: [] };
|
|
36
|
+
}
|
|
37
|
+
let parsed;
|
|
38
|
+
try {
|
|
39
|
+
parsed = JSON.parse(messageContent);
|
|
40
|
+
} catch {
|
|
41
|
+
return { content: messageContent, trackedUrls: [] };
|
|
42
|
+
}
|
|
43
|
+
const urlMap = /* @__PURE__ */ new Map();
|
|
44
|
+
collectUris(parsed, urlMap);
|
|
45
|
+
if (urlMap.size === 0) {
|
|
46
|
+
return { content: messageContent, trackedUrls: [] };
|
|
47
|
+
}
|
|
48
|
+
const trackedUrls = [];
|
|
49
|
+
for (const originalUrl of urlMap.keys()) {
|
|
50
|
+
try {
|
|
51
|
+
const link = await client.trackedLinks.create({
|
|
52
|
+
name: `${title} \u2014 ${truncate(originalUrl, 50)}`,
|
|
53
|
+
originalUrl
|
|
54
|
+
});
|
|
55
|
+
urlMap.set(originalUrl, link.trackingUrl);
|
|
56
|
+
trackedUrls.push({ original: originalUrl, tracking: link.trackingUrl });
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
replaceUris(parsed, urlMap);
|
|
61
|
+
return {
|
|
62
|
+
content: JSON.stringify(parsed),
|
|
63
|
+
trackedUrls
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function collectUris(obj, urlMap) {
|
|
67
|
+
if (obj === null || obj === void 0 || typeof obj !== "object") return;
|
|
68
|
+
if (Array.isArray(obj)) {
|
|
69
|
+
for (const item of obj) {
|
|
70
|
+
collectUris(item, urlMap);
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const record = obj;
|
|
75
|
+
if (record.type === "uri" && typeof record.uri === "string") {
|
|
76
|
+
const uri = record.uri;
|
|
77
|
+
if (uri.startsWith("http://") || uri.startsWith("https://")) {
|
|
78
|
+
if (!urlMap.has(uri)) {
|
|
79
|
+
urlMap.set(uri, uri);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (const value of Object.values(record)) {
|
|
84
|
+
collectUris(value, urlMap);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function replaceUris(obj, urlMap) {
|
|
88
|
+
if (obj === null || obj === void 0 || typeof obj !== "object") return;
|
|
89
|
+
if (Array.isArray(obj)) {
|
|
90
|
+
for (const item of obj) {
|
|
91
|
+
replaceUris(item, urlMap);
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const record = obj;
|
|
96
|
+
if (record.type === "uri" && typeof record.uri === "string") {
|
|
97
|
+
const tracked = urlMap.get(record.uri);
|
|
98
|
+
if (tracked && tracked !== record.uri) {
|
|
99
|
+
record.uri = tracked;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
for (const value of Object.values(record)) {
|
|
103
|
+
replaceUris(value, urlMap);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function truncate(str, max) {
|
|
107
|
+
return str.length > max ? str.slice(0, max) + "\u2026" : str;
|
|
108
|
+
}
|
|
109
|
+
|
|
32
110
|
// src/tools/send-message.ts
|
|
33
111
|
function registerSendMessage(server2) {
|
|
34
112
|
server2.tool(
|
|
@@ -46,9 +124,15 @@ function registerSendMessage(server2) {
|
|
|
46
124
|
async ({ friendId, content, messageType }) => {
|
|
47
125
|
try {
|
|
48
126
|
const client = getClient();
|
|
127
|
+
const { content: trackedContent } = await autoTrackUrls(
|
|
128
|
+
client,
|
|
129
|
+
content,
|
|
130
|
+
messageType,
|
|
131
|
+
`DM to ${friendId.slice(0, 8)}`
|
|
132
|
+
);
|
|
49
133
|
const result = await client.friends.sendMessage(
|
|
50
134
|
friendId,
|
|
51
|
-
|
|
135
|
+
trackedContent,
|
|
52
136
|
messageType
|
|
53
137
|
);
|
|
54
138
|
return {
|
|
@@ -174,10 +258,16 @@ function registerBroadcast(server2) {
|
|
|
174
258
|
isError: true
|
|
175
259
|
};
|
|
176
260
|
}
|
|
261
|
+
const { content: trackedContent2 } = await autoTrackUrls(
|
|
262
|
+
client,
|
|
263
|
+
messageContent,
|
|
264
|
+
messageType,
|
|
265
|
+
title
|
|
266
|
+
);
|
|
177
267
|
const broadcast2 = await client.broadcasts.create({
|
|
178
268
|
title: `[SEGMENT] ${title}`,
|
|
179
269
|
messageType,
|
|
180
|
-
messageContent,
|
|
270
|
+
messageContent: trackedContent2,
|
|
181
271
|
targetType: "all",
|
|
182
272
|
lineAccountId: accountId
|
|
183
273
|
});
|
|
@@ -204,10 +294,16 @@ function registerBroadcast(server2) {
|
|
|
204
294
|
throw sendError;
|
|
205
295
|
}
|
|
206
296
|
}
|
|
297
|
+
const { content: trackedContent, trackedUrls } = await autoTrackUrls(
|
|
298
|
+
client,
|
|
299
|
+
messageContent,
|
|
300
|
+
messageType,
|
|
301
|
+
title
|
|
302
|
+
);
|
|
207
303
|
const broadcast = await client.broadcasts.create({
|
|
208
304
|
title,
|
|
209
305
|
messageType,
|
|
210
|
-
messageContent,
|
|
306
|
+
messageContent: trackedContent,
|
|
211
307
|
targetType,
|
|
212
308
|
targetTagId,
|
|
213
309
|
scheduledAt,
|
|
@@ -1054,6 +1150,287 @@ function registerListCrmObjects(server2) {
|
|
|
1054
1150
|
);
|
|
1055
1151
|
}
|
|
1056
1152
|
|
|
1153
|
+
// src/tools/manage-ad-platforms.ts
|
|
1154
|
+
import { z as z15 } from "zod";
|
|
1155
|
+
function registerManageAdPlatforms(server2) {
|
|
1156
|
+
server2.tool(
|
|
1157
|
+
"manage_ad_platforms",
|
|
1158
|
+
"Manage ad platform integrations for conversion tracking. Supports Meta (Facebook/Instagram), X (Twitter), Google Ads, and TikTok. Use 'list' to see configured platforms, 'create' to add a new one, 'update' to modify settings, 'delete' to remove, or 'test' to verify the connection.",
|
|
1159
|
+
{
|
|
1160
|
+
action: z15.enum(["list", "create", "update", "delete", "test"]).describe("Action to perform"),
|
|
1161
|
+
platformId: z15.string().optional().describe("Platform ID (required for 'update' and 'delete')"),
|
|
1162
|
+
name: z15.enum(["meta", "x", "google", "tiktok"]).optional().describe("Platform name (required for 'create' and 'test')"),
|
|
1163
|
+
displayName: z15.string().optional().describe("Display name for the platform (e.g. 'Meta\u5E83\u544A')"),
|
|
1164
|
+
config: z15.record(z15.unknown()).optional().describe(
|
|
1165
|
+
"Platform config JSON. Meta: {pixel_id, access_token, test_event_code?}. X: {pixel_id, api_key, api_secret}. Google: {customer_id, conversion_action_id, oauth_token}. TikTok: {pixel_code, access_token}"
|
|
1166
|
+
),
|
|
1167
|
+
isActive: z15.boolean().optional().describe("Enable/disable the platform (for 'update')"),
|
|
1168
|
+
eventName: z15.string().optional().describe("Event name for test conversion (for 'test', e.g. 'Lead')"),
|
|
1169
|
+
friendId: z15.string().optional().describe("Friend ID for test conversion (for 'test')")
|
|
1170
|
+
},
|
|
1171
|
+
async ({ action, platformId, name, displayName, config, isActive, eventName, friendId }) => {
|
|
1172
|
+
try {
|
|
1173
|
+
const client = getClient();
|
|
1174
|
+
switch (action) {
|
|
1175
|
+
case "list": {
|
|
1176
|
+
const platforms = await client.adPlatforms.list();
|
|
1177
|
+
return {
|
|
1178
|
+
content: [
|
|
1179
|
+
{
|
|
1180
|
+
type: "text",
|
|
1181
|
+
text: JSON.stringify(
|
|
1182
|
+
{
|
|
1183
|
+
success: true,
|
|
1184
|
+
count: platforms.length,
|
|
1185
|
+
platforms
|
|
1186
|
+
},
|
|
1187
|
+
null,
|
|
1188
|
+
2
|
|
1189
|
+
)
|
|
1190
|
+
}
|
|
1191
|
+
]
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
case "create": {
|
|
1195
|
+
if (!name) throw new Error("name is required for create action");
|
|
1196
|
+
if (!config)
|
|
1197
|
+
throw new Error("config is required for create action");
|
|
1198
|
+
const platform = await client.adPlatforms.create({
|
|
1199
|
+
name,
|
|
1200
|
+
displayName,
|
|
1201
|
+
config
|
|
1202
|
+
});
|
|
1203
|
+
return {
|
|
1204
|
+
content: [
|
|
1205
|
+
{
|
|
1206
|
+
type: "text",
|
|
1207
|
+
text: JSON.stringify({ success: true, platform }, null, 2)
|
|
1208
|
+
}
|
|
1209
|
+
]
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
case "update": {
|
|
1213
|
+
if (!platformId)
|
|
1214
|
+
throw new Error("platformId is required for update action");
|
|
1215
|
+
const platform = await client.adPlatforms.update(platformId, {
|
|
1216
|
+
name,
|
|
1217
|
+
displayName,
|
|
1218
|
+
config,
|
|
1219
|
+
isActive
|
|
1220
|
+
});
|
|
1221
|
+
return {
|
|
1222
|
+
content: [
|
|
1223
|
+
{
|
|
1224
|
+
type: "text",
|
|
1225
|
+
text: JSON.stringify({ success: true, platform }, null, 2)
|
|
1226
|
+
}
|
|
1227
|
+
]
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
case "delete": {
|
|
1231
|
+
if (!platformId)
|
|
1232
|
+
throw new Error("platformId is required for delete action");
|
|
1233
|
+
await client.adPlatforms.delete(platformId);
|
|
1234
|
+
return {
|
|
1235
|
+
content: [
|
|
1236
|
+
{
|
|
1237
|
+
type: "text",
|
|
1238
|
+
text: JSON.stringify({
|
|
1239
|
+
success: true,
|
|
1240
|
+
message: `Platform ${platformId} deleted`
|
|
1241
|
+
})
|
|
1242
|
+
}
|
|
1243
|
+
]
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
case "test": {
|
|
1247
|
+
if (!name) throw new Error("name is required for test action");
|
|
1248
|
+
if (!eventName)
|
|
1249
|
+
throw new Error("eventName is required for test action");
|
|
1250
|
+
const result = await client.adPlatforms.test(
|
|
1251
|
+
name,
|
|
1252
|
+
eventName,
|
|
1253
|
+
friendId
|
|
1254
|
+
);
|
|
1255
|
+
return {
|
|
1256
|
+
content: [
|
|
1257
|
+
{
|
|
1258
|
+
type: "text",
|
|
1259
|
+
text: JSON.stringify({ success: true, ...result }, null, 2)
|
|
1260
|
+
}
|
|
1261
|
+
]
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
default:
|
|
1265
|
+
throw new Error(`Unknown action: ${action}`);
|
|
1266
|
+
}
|
|
1267
|
+
} catch (error) {
|
|
1268
|
+
return {
|
|
1269
|
+
content: [
|
|
1270
|
+
{
|
|
1271
|
+
type: "text",
|
|
1272
|
+
text: JSON.stringify(
|
|
1273
|
+
{ success: false, error: String(error) },
|
|
1274
|
+
null,
|
|
1275
|
+
2
|
|
1276
|
+
)
|
|
1277
|
+
}
|
|
1278
|
+
],
|
|
1279
|
+
isError: true
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
// src/tools/get-conversion-logs.ts
|
|
1287
|
+
import { z as z16 } from "zod";
|
|
1288
|
+
function registerGetConversionLogs(server2) {
|
|
1289
|
+
server2.tool(
|
|
1290
|
+
"get_conversion_logs",
|
|
1291
|
+
"View ad conversion send logs for a specific platform. Shows the history of conversion events sent to Meta CAPI, X, Google Ads, or TikTok, including status (sent/failed) and error details.",
|
|
1292
|
+
{
|
|
1293
|
+
platformId: z16.string().describe(
|
|
1294
|
+
"Ad platform ID to get logs for. Use manage_ad_platforms with action 'list' first to get the ID."
|
|
1295
|
+
),
|
|
1296
|
+
limit: z16.number().optional().default(50).describe("Maximum number of logs to return (default: 50)")
|
|
1297
|
+
},
|
|
1298
|
+
async ({ platformId, limit }) => {
|
|
1299
|
+
try {
|
|
1300
|
+
const client = getClient();
|
|
1301
|
+
const logs = await client.adPlatforms.getLogs(platformId, limit);
|
|
1302
|
+
const summary = {
|
|
1303
|
+
total: logs.length,
|
|
1304
|
+
sent: logs.filter((l) => l.status === "sent").length,
|
|
1305
|
+
failed: logs.filter((l) => l.status === "failed").length
|
|
1306
|
+
};
|
|
1307
|
+
return {
|
|
1308
|
+
content: [
|
|
1309
|
+
{
|
|
1310
|
+
type: "text",
|
|
1311
|
+
text: JSON.stringify(
|
|
1312
|
+
{ success: true, summary, logs },
|
|
1313
|
+
null,
|
|
1314
|
+
2
|
|
1315
|
+
)
|
|
1316
|
+
}
|
|
1317
|
+
]
|
|
1318
|
+
};
|
|
1319
|
+
} catch (error) {
|
|
1320
|
+
return {
|
|
1321
|
+
content: [
|
|
1322
|
+
{
|
|
1323
|
+
type: "text",
|
|
1324
|
+
text: JSON.stringify(
|
|
1325
|
+
{ success: false, error: String(error) },
|
|
1326
|
+
null,
|
|
1327
|
+
2
|
|
1328
|
+
)
|
|
1329
|
+
}
|
|
1330
|
+
],
|
|
1331
|
+
isError: true
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
);
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
// src/tools/manage-staff.ts
|
|
1339
|
+
import { z as z17 } from "zod";
|
|
1340
|
+
function registerManageStaff(server2) {
|
|
1341
|
+
server2.tool(
|
|
1342
|
+
"manage_staff",
|
|
1343
|
+
"\u30B9\u30BF\u30C3\u30D5\u30A2\u30AB\u30A6\u30F3\u30C8\u306E\u8FFD\u52A0\u30FB\u4E00\u89A7\u30FB\u66F4\u65B0\u30FB\u524A\u9664\u30FBAPI\u30AD\u30FC\u518D\u751F\u6210\u3002\u30AA\u30FC\u30CA\u30FC\u6A29\u9650\u304C\u5FC5\u8981\u3067\u3059\u3002",
|
|
1344
|
+
{
|
|
1345
|
+
action: z17.enum(["create", "list", "get", "update", "delete", "regenerate_key", "me"]).describe("Action to perform"),
|
|
1346
|
+
name: z17.string().optional().describe("Staff name (for 'create' action)"),
|
|
1347
|
+
email: z17.string().nullable().optional().describe("Staff email (optional, null to clear)"),
|
|
1348
|
+
role: z17.enum(["admin", "staff"]).optional().describe("Staff role (for 'create'/'update')"),
|
|
1349
|
+
staffId: z17.string().optional().describe("Staff ID (for 'get','update','delete','regenerate_key')"),
|
|
1350
|
+
isActive: z17.boolean().optional().describe("Activate/deactivate (for 'update')")
|
|
1351
|
+
},
|
|
1352
|
+
async ({ action, name, email, role, staffId, isActive }) => {
|
|
1353
|
+
try {
|
|
1354
|
+
const client = getClient();
|
|
1355
|
+
if (action === "me") {
|
|
1356
|
+
const profile = await client.staff.me();
|
|
1357
|
+
return {
|
|
1358
|
+
content: [{ type: "text", text: JSON.stringify({ success: true, profile }, null, 2) }]
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
if (action === "list") {
|
|
1362
|
+
const members = await client.staff.list();
|
|
1363
|
+
return {
|
|
1364
|
+
content: [{ type: "text", text: JSON.stringify({ success: true, members }, null, 2) }]
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
if (action === "create") {
|
|
1368
|
+
if (!name) throw new Error("name is required for create action");
|
|
1369
|
+
if (!role) throw new Error("role is required for create action");
|
|
1370
|
+
const member = await client.staff.create({ name, email, role });
|
|
1371
|
+
return {
|
|
1372
|
+
content: [{
|
|
1373
|
+
type: "text",
|
|
1374
|
+
text: JSON.stringify({
|
|
1375
|
+
success: true,
|
|
1376
|
+
member,
|
|
1377
|
+
note: "API\u30AD\u30FC\u306F\u4E00\u5EA6\u3060\u3051\u8868\u793A\u3055\u308C\u307E\u3059\u3002\u5B89\u5168\u306B\u4FDD\u7BA1\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
1378
|
+
}, null, 2)
|
|
1379
|
+
}]
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
if (action === "get") {
|
|
1383
|
+
if (!staffId) throw new Error("staffId is required for get action");
|
|
1384
|
+
const member = await client.staff.get(staffId);
|
|
1385
|
+
return {
|
|
1386
|
+
content: [{ type: "text", text: JSON.stringify({ success: true, member }, null, 2) }]
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
if (action === "update") {
|
|
1390
|
+
if (!staffId) throw new Error("staffId is required for update action");
|
|
1391
|
+
const updates = {};
|
|
1392
|
+
if (name !== void 0) updates.name = name;
|
|
1393
|
+
if (email !== void 0) updates.email = email;
|
|
1394
|
+
if (role !== void 0) updates.role = role;
|
|
1395
|
+
if (isActive !== void 0) updates.isActive = isActive;
|
|
1396
|
+
const member = await client.staff.update(staffId, updates);
|
|
1397
|
+
return {
|
|
1398
|
+
content: [{ type: "text", text: JSON.stringify({ success: true, member }, null, 2) }]
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
if (action === "delete") {
|
|
1402
|
+
if (!staffId) throw new Error("staffId is required for delete action");
|
|
1403
|
+
await client.staff.delete(staffId);
|
|
1404
|
+
return {
|
|
1405
|
+
content: [{ type: "text", text: JSON.stringify({ success: true, deleted: staffId }, null, 2) }]
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
if (action === "regenerate_key") {
|
|
1409
|
+
if (!staffId) throw new Error("staffId is required for regenerate_key action");
|
|
1410
|
+
const result = await client.staff.regenerateKey(staffId);
|
|
1411
|
+
return {
|
|
1412
|
+
content: [{
|
|
1413
|
+
type: "text",
|
|
1414
|
+
text: JSON.stringify({
|
|
1415
|
+
success: true,
|
|
1416
|
+
staffId,
|
|
1417
|
+
newApiKey: result.apiKey,
|
|
1418
|
+
note: "\u65B0\u3057\u3044API\u30AD\u30FC\u306F\u4E00\u5EA6\u3060\u3051\u8868\u793A\u3055\u308C\u307E\u3059\u3002\u5B89\u5168\u306B\u4FDD\u7BA1\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
1419
|
+
}, null, 2)
|
|
1420
|
+
}]
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
throw new Error(`Unknown action: ${action}`);
|
|
1424
|
+
} catch (error) {
|
|
1425
|
+
return {
|
|
1426
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: String(error) }, null, 2) }],
|
|
1427
|
+
isError: true
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
);
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1057
1434
|
// src/tools/index.ts
|
|
1058
1435
|
function registerAllTools(server2) {
|
|
1059
1436
|
registerSendMessage(server2);
|
|
@@ -1070,6 +1447,9 @@ function registerAllTools(server2) {
|
|
|
1070
1447
|
registerGetLinkClicks(server2);
|
|
1071
1448
|
registerAccountSummary(server2);
|
|
1072
1449
|
registerListCrmObjects(server2);
|
|
1450
|
+
registerManageAdPlatforms(server2);
|
|
1451
|
+
registerGetConversionLogs(server2);
|
|
1452
|
+
registerManageStaff(server2);
|
|
1073
1453
|
}
|
|
1074
1454
|
|
|
1075
1455
|
// src/resources/index.ts
|
package/package.json
CHANGED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { LineHarness } from "@line-harness/sdk";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Recursively find all URI action URLs in a Flex Message object,
|
|
5
|
+
* create tracked links for each, and replace with tracking URLs.
|
|
6
|
+
*/
|
|
7
|
+
export async function autoTrackUrls(
|
|
8
|
+
client: LineHarness,
|
|
9
|
+
messageContent: string,
|
|
10
|
+
messageType: string,
|
|
11
|
+
title: string,
|
|
12
|
+
): Promise<{ content: string; trackedUrls: { original: string; tracking: string }[] }> {
|
|
13
|
+
if (messageType !== "flex") {
|
|
14
|
+
return { content: messageContent, trackedUrls: [] };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let parsed: unknown;
|
|
18
|
+
try {
|
|
19
|
+
parsed = JSON.parse(messageContent);
|
|
20
|
+
} catch {
|
|
21
|
+
return { content: messageContent, trackedUrls: [] };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const urlMap = new Map<string, string>();
|
|
25
|
+
|
|
26
|
+
// Collect all unique URIs from the flex message
|
|
27
|
+
collectUris(parsed, urlMap);
|
|
28
|
+
|
|
29
|
+
if (urlMap.size === 0) {
|
|
30
|
+
return { content: messageContent, trackedUrls: [] };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Create tracked links for each unique URL
|
|
34
|
+
const trackedUrls: { original: string; tracking: string }[] = [];
|
|
35
|
+
for (const originalUrl of urlMap.keys()) {
|
|
36
|
+
try {
|
|
37
|
+
const link = await client.trackedLinks.create({
|
|
38
|
+
name: `${title} — ${truncate(originalUrl, 50)}`,
|
|
39
|
+
originalUrl,
|
|
40
|
+
});
|
|
41
|
+
urlMap.set(originalUrl, link.trackingUrl);
|
|
42
|
+
trackedUrls.push({ original: originalUrl, tracking: link.trackingUrl });
|
|
43
|
+
} catch {
|
|
44
|
+
// If tracked link creation fails, keep original URL
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Replace URLs in the parsed object
|
|
49
|
+
replaceUris(parsed, urlMap);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
content: JSON.stringify(parsed),
|
|
53
|
+
trackedUrls,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function collectUris(obj: unknown, urlMap: Map<string, string>): void {
|
|
58
|
+
if (obj === null || obj === undefined || typeof obj !== "object") return;
|
|
59
|
+
|
|
60
|
+
if (Array.isArray(obj)) {
|
|
61
|
+
for (const item of obj) {
|
|
62
|
+
collectUris(item, urlMap);
|
|
63
|
+
}
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const record = obj as Record<string, unknown>;
|
|
68
|
+
|
|
69
|
+
// Check if this is a URI action
|
|
70
|
+
if (record.type === "uri" && typeof record.uri === "string") {
|
|
71
|
+
const uri = record.uri;
|
|
72
|
+
// Only track http/https URLs, skip LINE-specific URIs
|
|
73
|
+
if (uri.startsWith("http://") || uri.startsWith("https://")) {
|
|
74
|
+
if (!urlMap.has(uri)) {
|
|
75
|
+
urlMap.set(uri, uri); // placeholder, replaced after creation
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Recurse into all values
|
|
81
|
+
for (const value of Object.values(record)) {
|
|
82
|
+
collectUris(value, urlMap);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function replaceUris(obj: unknown, urlMap: Map<string, string>): void {
|
|
87
|
+
if (obj === null || obj === undefined || typeof obj !== "object") return;
|
|
88
|
+
|
|
89
|
+
if (Array.isArray(obj)) {
|
|
90
|
+
for (const item of obj) {
|
|
91
|
+
replaceUris(item, urlMap);
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const record = obj as Record<string, unknown>;
|
|
97
|
+
|
|
98
|
+
if (record.type === "uri" && typeof record.uri === "string") {
|
|
99
|
+
const tracked = urlMap.get(record.uri);
|
|
100
|
+
if (tracked && tracked !== record.uri) {
|
|
101
|
+
record.uri = tracked;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const value of Object.values(record)) {
|
|
106
|
+
replaceUris(value, urlMap);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function truncate(str: string, max: number): string {
|
|
111
|
+
return str.length > max ? str.slice(0, max) + "…" : str;
|
|
112
|
+
}
|
package/src/tools/broadcast.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { getClient } from "../client.js";
|
|
4
|
+
import { autoTrackUrls } from "./auto-track-urls.js";
|
|
4
5
|
|
|
5
6
|
export function registerBroadcast(server: McpServer): void {
|
|
6
7
|
server.tool(
|
|
@@ -117,10 +118,17 @@ export function registerBroadcast(server: McpServer): void {
|
|
|
117
118
|
};
|
|
118
119
|
}
|
|
119
120
|
|
|
121
|
+
const { content: trackedContent } = await autoTrackUrls(
|
|
122
|
+
client,
|
|
123
|
+
messageContent,
|
|
124
|
+
messageType,
|
|
125
|
+
title,
|
|
126
|
+
);
|
|
127
|
+
|
|
120
128
|
const broadcast = await client.broadcasts.create({
|
|
121
129
|
title: `[SEGMENT] ${title}`,
|
|
122
130
|
messageType,
|
|
123
|
-
messageContent,
|
|
131
|
+
messageContent: trackedContent,
|
|
124
132
|
targetType: "all",
|
|
125
133
|
lineAccountId: accountId,
|
|
126
134
|
});
|
|
@@ -148,11 +156,19 @@ export function registerBroadcast(server: McpServer): void {
|
|
|
148
156
|
}
|
|
149
157
|
}
|
|
150
158
|
|
|
159
|
+
// Auto-track URLs in flex messages
|
|
160
|
+
const { content: trackedContent, trackedUrls } = await autoTrackUrls(
|
|
161
|
+
client,
|
|
162
|
+
messageContent,
|
|
163
|
+
messageType,
|
|
164
|
+
title,
|
|
165
|
+
);
|
|
166
|
+
|
|
151
167
|
// At this point targetType is guaranteed to be 'all' or 'tag' (segment handled above)
|
|
152
168
|
const broadcast = await client.broadcasts.create({
|
|
153
169
|
title,
|
|
154
170
|
messageType,
|
|
155
|
-
messageContent,
|
|
171
|
+
messageContent: trackedContent,
|
|
156
172
|
targetType: targetType as "all" | "tag",
|
|
157
173
|
targetTagId,
|
|
158
174
|
scheduledAt,
|
|
@@ -0,0 +1,61 @@
|
|
|
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 registerGetConversionLogs(server: McpServer): void {
|
|
6
|
+
server.tool(
|
|
7
|
+
"get_conversion_logs",
|
|
8
|
+
"View ad conversion send logs for a specific platform. Shows the history of conversion events sent to Meta CAPI, X, Google Ads, or TikTok, including status (sent/failed) and error details.",
|
|
9
|
+
{
|
|
10
|
+
platformId: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe(
|
|
13
|
+
"Ad platform ID to get logs for. Use manage_ad_platforms with action 'list' first to get the ID.",
|
|
14
|
+
),
|
|
15
|
+
limit: z
|
|
16
|
+
.number()
|
|
17
|
+
.optional()
|
|
18
|
+
.default(50)
|
|
19
|
+
.describe("Maximum number of logs to return (default: 50)"),
|
|
20
|
+
},
|
|
21
|
+
async ({ platformId, limit }) => {
|
|
22
|
+
try {
|
|
23
|
+
const client = getClient();
|
|
24
|
+
const logs = await client.adPlatforms.getLogs(platformId, limit);
|
|
25
|
+
|
|
26
|
+
const summary = {
|
|
27
|
+
total: logs.length,
|
|
28
|
+
sent: logs.filter((l) => l.status === "sent").length,
|
|
29
|
+
failed: logs.filter((l) => l.status === "failed").length,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
content: [
|
|
34
|
+
{
|
|
35
|
+
type: "text" as const,
|
|
36
|
+
text: JSON.stringify(
|
|
37
|
+
{ success: true, summary, logs },
|
|
38
|
+
null,
|
|
39
|
+
2,
|
|
40
|
+
),
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return {
|
|
46
|
+
content: [
|
|
47
|
+
{
|
|
48
|
+
type: "text" as const,
|
|
49
|
+
text: JSON.stringify(
|
|
50
|
+
{ success: false, error: String(error) },
|
|
51
|
+
null,
|
|
52
|
+
2,
|
|
53
|
+
),
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
isError: true,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
}
|
package/src/tools/index.ts
CHANGED
|
@@ -13,6 +13,9 @@ import { registerGetFormSubmissions } from "./get-form-submissions.js";
|
|
|
13
13
|
import { registerGetLinkClicks } from "./get-link-clicks.js";
|
|
14
14
|
import { registerAccountSummary } from "./account-summary.js";
|
|
15
15
|
import { registerListCrmObjects } from "./list-crm-objects.js";
|
|
16
|
+
import { registerManageAdPlatforms } from "./manage-ad-platforms.js";
|
|
17
|
+
import { registerGetConversionLogs } from "./get-conversion-logs.js";
|
|
18
|
+
import { registerManageStaff } from "./manage-staff.js";
|
|
16
19
|
|
|
17
20
|
export function registerAllTools(server: McpServer): void {
|
|
18
21
|
registerSendMessage(server);
|
|
@@ -29,4 +32,7 @@ export function registerAllTools(server: McpServer): void {
|
|
|
29
32
|
registerGetLinkClicks(server);
|
|
30
33
|
registerAccountSummary(server);
|
|
31
34
|
registerListCrmObjects(server);
|
|
35
|
+
registerManageAdPlatforms(server);
|
|
36
|
+
registerGetConversionLogs(server);
|
|
37
|
+
registerManageStaff(server);
|
|
32
38
|
}
|
|
@@ -0,0 +1,170 @@
|
|
|
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 registerManageAdPlatforms(server: McpServer): void {
|
|
6
|
+
server.tool(
|
|
7
|
+
"manage_ad_platforms",
|
|
8
|
+
"Manage ad platform integrations for conversion tracking. Supports Meta (Facebook/Instagram), X (Twitter), Google Ads, and TikTok. Use 'list' to see configured platforms, 'create' to add a new one, 'update' to modify settings, 'delete' to remove, or 'test' to verify the connection.",
|
|
9
|
+
{
|
|
10
|
+
action: z
|
|
11
|
+
.enum(["list", "create", "update", "delete", "test"])
|
|
12
|
+
.describe("Action to perform"),
|
|
13
|
+
platformId: z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("Platform ID (required for 'update' and 'delete')"),
|
|
17
|
+
name: z
|
|
18
|
+
.enum(["meta", "x", "google", "tiktok"])
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Platform name (required for 'create' and 'test')"),
|
|
21
|
+
displayName: z
|
|
22
|
+
.string()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Display name for the platform (e.g. 'Meta広告')"),
|
|
25
|
+
config: z
|
|
26
|
+
.record(z.unknown())
|
|
27
|
+
.optional()
|
|
28
|
+
.describe(
|
|
29
|
+
"Platform config JSON. Meta: {pixel_id, access_token, test_event_code?}. X: {pixel_id, api_key, api_secret}. Google: {customer_id, conversion_action_id, oauth_token}. TikTok: {pixel_code, access_token}",
|
|
30
|
+
),
|
|
31
|
+
isActive: z
|
|
32
|
+
.boolean()
|
|
33
|
+
.optional()
|
|
34
|
+
.describe("Enable/disable the platform (for 'update')"),
|
|
35
|
+
eventName: z
|
|
36
|
+
.string()
|
|
37
|
+
.optional()
|
|
38
|
+
.describe("Event name for test conversion (for 'test', e.g. 'Lead')"),
|
|
39
|
+
friendId: z
|
|
40
|
+
.string()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("Friend ID for test conversion (for 'test')"),
|
|
43
|
+
},
|
|
44
|
+
async ({ action, platformId, name, displayName, config, isActive, eventName, friendId }) => {
|
|
45
|
+
try {
|
|
46
|
+
const client = getClient();
|
|
47
|
+
|
|
48
|
+
switch (action) {
|
|
49
|
+
case "list": {
|
|
50
|
+
const platforms = await client.adPlatforms.list();
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
type: "text" as const,
|
|
55
|
+
text: JSON.stringify(
|
|
56
|
+
{
|
|
57
|
+
success: true,
|
|
58
|
+
count: platforms.length,
|
|
59
|
+
platforms,
|
|
60
|
+
},
|
|
61
|
+
null,
|
|
62
|
+
2,
|
|
63
|
+
),
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
case "create": {
|
|
70
|
+
if (!name) throw new Error("name is required for create action");
|
|
71
|
+
if (!config)
|
|
72
|
+
throw new Error("config is required for create action");
|
|
73
|
+
|
|
74
|
+
const platform = await client.adPlatforms.create({
|
|
75
|
+
name,
|
|
76
|
+
displayName,
|
|
77
|
+
config,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
content: [
|
|
82
|
+
{
|
|
83
|
+
type: "text" as const,
|
|
84
|
+
text: JSON.stringify({ success: true, platform }, null, 2),
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
case "update": {
|
|
91
|
+
if (!platformId)
|
|
92
|
+
throw new Error("platformId is required for update action");
|
|
93
|
+
|
|
94
|
+
const platform = await client.adPlatforms.update(platformId, {
|
|
95
|
+
name,
|
|
96
|
+
displayName,
|
|
97
|
+
config,
|
|
98
|
+
isActive,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
content: [
|
|
103
|
+
{
|
|
104
|
+
type: "text" as const,
|
|
105
|
+
text: JSON.stringify({ success: true, platform }, null, 2),
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
case "delete": {
|
|
112
|
+
if (!platformId)
|
|
113
|
+
throw new Error("platformId is required for delete action");
|
|
114
|
+
|
|
115
|
+
await client.adPlatforms.delete(platformId);
|
|
116
|
+
return {
|
|
117
|
+
content: [
|
|
118
|
+
{
|
|
119
|
+
type: "text" as const,
|
|
120
|
+
text: JSON.stringify({
|
|
121
|
+
success: true,
|
|
122
|
+
message: `Platform ${platformId} deleted`,
|
|
123
|
+
}),
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
case "test": {
|
|
130
|
+
if (!name) throw new Error("name is required for test action");
|
|
131
|
+
if (!eventName)
|
|
132
|
+
throw new Error("eventName is required for test action");
|
|
133
|
+
|
|
134
|
+
const result = await client.adPlatforms.test(
|
|
135
|
+
name,
|
|
136
|
+
eventName,
|
|
137
|
+
friendId,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
content: [
|
|
142
|
+
{
|
|
143
|
+
type: "text" as const,
|
|
144
|
+
text: JSON.stringify({ success: true, ...result }, null, 2),
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
default:
|
|
151
|
+
throw new Error(`Unknown action: ${action}`);
|
|
152
|
+
}
|
|
153
|
+
} catch (error) {
|
|
154
|
+
return {
|
|
155
|
+
content: [
|
|
156
|
+
{
|
|
157
|
+
type: "text" as const,
|
|
158
|
+
text: JSON.stringify(
|
|
159
|
+
{ success: false, error: String(error) },
|
|
160
|
+
null,
|
|
161
|
+
2,
|
|
162
|
+
),
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
isError: true,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
);
|
|
170
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
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 registerManageStaff(server: McpServer): void {
|
|
6
|
+
server.tool(
|
|
7
|
+
"manage_staff",
|
|
8
|
+
"スタッフアカウントの追加・一覧・更新・削除・APIキー再生成。オーナー権限が必要です。",
|
|
9
|
+
{
|
|
10
|
+
action: z
|
|
11
|
+
.enum(["create", "list", "get", "update", "delete", "regenerate_key", "me"])
|
|
12
|
+
.describe("Action to perform"),
|
|
13
|
+
name: z.string().optional().describe("Staff name (for 'create' action)"),
|
|
14
|
+
email: z.string().nullable().optional().describe("Staff email (optional, null to clear)"),
|
|
15
|
+
role: z.enum(["admin", "staff"]).optional().describe("Staff role (for 'create'/'update')"),
|
|
16
|
+
staffId: z.string().optional().describe("Staff ID (for 'get','update','delete','regenerate_key')"),
|
|
17
|
+
isActive: z.boolean().optional().describe("Activate/deactivate (for 'update')"),
|
|
18
|
+
},
|
|
19
|
+
async ({ action, name, email, role, staffId, isActive }) => {
|
|
20
|
+
try {
|
|
21
|
+
const client = getClient();
|
|
22
|
+
|
|
23
|
+
if (action === "me") {
|
|
24
|
+
const profile = await client.staff.me();
|
|
25
|
+
return {
|
|
26
|
+
content: [{ type: "text" as const, text: JSON.stringify({ success: true, profile }, null, 2) }],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (action === "list") {
|
|
31
|
+
const members = await client.staff.list();
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: "text" as const, text: JSON.stringify({ success: true, members }, null, 2) }],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (action === "create") {
|
|
38
|
+
if (!name) throw new Error("name is required for create action");
|
|
39
|
+
if (!role) throw new Error("role is required for create action");
|
|
40
|
+
const member = await client.staff.create({ name, email, role });
|
|
41
|
+
return {
|
|
42
|
+
content: [{
|
|
43
|
+
type: "text" as const,
|
|
44
|
+
text: JSON.stringify({
|
|
45
|
+
success: true, member,
|
|
46
|
+
note: "APIキーは一度だけ表示されます。安全に保管してください。",
|
|
47
|
+
}, null, 2),
|
|
48
|
+
}],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (action === "get") {
|
|
53
|
+
if (!staffId) throw new Error("staffId is required for get action");
|
|
54
|
+
const member = await client.staff.get(staffId);
|
|
55
|
+
return {
|
|
56
|
+
content: [{ type: "text" as const, text: JSON.stringify({ success: true, member }, null, 2) }],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (action === "update") {
|
|
61
|
+
if (!staffId) throw new Error("staffId is required for update action");
|
|
62
|
+
const updates: Record<string, unknown> = {};
|
|
63
|
+
if (name !== undefined) updates.name = name;
|
|
64
|
+
if (email !== undefined) updates.email = email;
|
|
65
|
+
if (role !== undefined) updates.role = role;
|
|
66
|
+
if (isActive !== undefined) updates.isActive = isActive;
|
|
67
|
+
const member = await client.staff.update(staffId, updates);
|
|
68
|
+
return {
|
|
69
|
+
content: [{ type: "text" as const, text: JSON.stringify({ success: true, member }, null, 2) }],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (action === "delete") {
|
|
74
|
+
if (!staffId) throw new Error("staffId is required for delete action");
|
|
75
|
+
await client.staff.delete(staffId);
|
|
76
|
+
return {
|
|
77
|
+
content: [{ type: "text" as const, text: JSON.stringify({ success: true, deleted: staffId }, null, 2) }],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (action === "regenerate_key") {
|
|
82
|
+
if (!staffId) throw new Error("staffId is required for regenerate_key action");
|
|
83
|
+
const result = await client.staff.regenerateKey(staffId);
|
|
84
|
+
return {
|
|
85
|
+
content: [{
|
|
86
|
+
type: "text" as const,
|
|
87
|
+
text: JSON.stringify({
|
|
88
|
+
success: true, staffId, newApiKey: result.apiKey,
|
|
89
|
+
note: "新しいAPIキーは一度だけ表示されます。安全に保管してください。",
|
|
90
|
+
}, null, 2),
|
|
91
|
+
}],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
throw new Error(`Unknown action: ${action}`);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
return {
|
|
98
|
+
content: [{ type: "text" as const, text: JSON.stringify({ success: false, error: String(error) }, null, 2) }],
|
|
99
|
+
isError: true,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { getClient } from "../client.js";
|
|
4
|
+
import { autoTrackUrls } from "./auto-track-urls.js";
|
|
4
5
|
|
|
5
6
|
export function registerSendMessage(server: McpServer): void {
|
|
6
7
|
server.tool(
|
|
@@ -23,9 +24,18 @@ export function registerSendMessage(server: McpServer): void {
|
|
|
23
24
|
async ({ friendId, content, messageType }) => {
|
|
24
25
|
try {
|
|
25
26
|
const client = getClient();
|
|
27
|
+
|
|
28
|
+
// Auto-track URLs in flex messages
|
|
29
|
+
const { content: trackedContent } = await autoTrackUrls(
|
|
30
|
+
client,
|
|
31
|
+
content,
|
|
32
|
+
messageType,
|
|
33
|
+
`DM to ${friendId.slice(0, 8)}`,
|
|
34
|
+
);
|
|
35
|
+
|
|
26
36
|
const result = await client.friends.sendMessage(
|
|
27
37
|
friendId,
|
|
28
|
-
|
|
38
|
+
trackedContent,
|
|
29
39
|
messageType,
|
|
30
40
|
);
|
|
31
41
|
return {
|