@thenavidm/threads-mcp-cli 1.1.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/LICENSE +21 -0
- package/README.md +1019 -0
- package/SKILL.md +203 -0
- package/dist/api/client.d.ts +105 -0
- package/dist/api/client.js +305 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/errors.d.ts +92 -0
- package/dist/api/errors.js +195 -0
- package/dist/api/errors.js.map +1 -0
- package/dist/api/identity.d.ts +33 -0
- package/dist/api/identity.js +52 -0
- package/dist/api/identity.js.map +1 -0
- package/dist/auth/login.d.ts +32 -0
- package/dist/auth/login.js +204 -0
- package/dist/auth/login.js.map +1 -0
- package/dist/auth/store.d.ts +37 -0
- package/dist/auth/store.js +88 -0
- package/dist/auth/store.js.map +1 -0
- package/dist/auth/tokens.d.ts +54 -0
- package/dist/auth/tokens.js +96 -0
- package/dist/auth/tokens.js.map +1 -0
- package/dist/cli.d.ts +59 -0
- package/dist/cli.js +444 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +98 -0
- package/dist/config.js +185 -0
- package/dist/config.js.map +1 -0
- package/dist/content/containers.d.ts +89 -0
- package/dist/content/containers.js +210 -0
- package/dist/content/containers.js.map +1 -0
- package/dist/content/media.d.ts +61 -0
- package/dist/content/media.js +125 -0
- package/dist/content/media.js.map +1 -0
- package/dist/content/text.d.ts +68 -0
- package/dist/content/text.js +106 -0
- package/dist/content/text.js.map +1 -0
- package/dist/doctor.d.ts +14 -0
- package/dist/doctor.js +218 -0
- package/dist/doctor.js.map +1 -0
- package/dist/format/posts.d.ts +41 -0
- package/dist/format/posts.js +153 -0
- package/dist/format/posts.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +167 -0
- package/dist/index.js.map +1 -0
- package/dist/safety.d.ts +52 -0
- package/dist/safety.js +85 -0
- package/dist/safety.js.map +1 -0
- package/dist/server.d.ts +20 -0
- package/dist/server.js +232 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/accounts.d.ts +27 -0
- package/dist/tools/accounts.js +162 -0
- package/dist/tools/accounts.js.map +1 -0
- package/dist/tools/discover.d.ts +56 -0
- package/dist/tools/discover.js +146 -0
- package/dist/tools/discover.js.map +1 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +16 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/insights.d.ts +55 -0
- package/dist/tools/insights.js +223 -0
- package/dist/tools/insights.js.map +1 -0
- package/dist/tools/kit.d.ts +90 -0
- package/dist/tools/kit.js +119 -0
- package/dist/tools/kit.js.map +1 -0
- package/dist/tools/posts.d.ts +170 -0
- package/dist/tools/posts.js +312 -0
- package/dist/tools/posts.js.map +1 -0
- package/dist/tools/read.d.ts +31 -0
- package/dist/tools/read.js +95 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/replies.d.ts +92 -0
- package/dist/tools/replies.js +218 -0
- package/dist/tools/replies.js.map +1 -0
- package/dist/transport/http.d.ts +28 -0
- package/dist/transport/http.js +103 -0
- package/dist/transport/http.js.map +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Every tool, in the order they should appear in a client's tool list. */
|
|
2
|
+
import { ACCOUNT_TOOLS } from "./accounts.js";
|
|
3
|
+
import { POST_TOOLS } from "./posts.js";
|
|
4
|
+
import { REPLY_TOOLS } from "./replies.js";
|
|
5
|
+
import { READ_TOOLS } from "./read.js";
|
|
6
|
+
import { INSIGHT_TOOLS } from "./insights.js";
|
|
7
|
+
import { DISCOVER_TOOLS } from "./discover.js";
|
|
8
|
+
export const ALL_TOOLS = [
|
|
9
|
+
...ACCOUNT_TOOLS,
|
|
10
|
+
...POST_TOOLS,
|
|
11
|
+
...REPLY_TOOLS,
|
|
12
|
+
...READ_TOOLS,
|
|
13
|
+
...INSIGHT_TOOLS,
|
|
14
|
+
...DISCOVER_TOOLS,
|
|
15
|
+
];
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAE3E,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/C,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,GAAG,aAAa;IAChB,GAAG,UAAU;IACb,GAAG,WAAW;IACd,GAAG,UAAU;IACb,GAAG,aAAa;IAChB,GAAG,cAAc;CACU,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Insights.
|
|
3
|
+
*
|
|
4
|
+
* Threads reports metrics in three different envelopes depending on the metric,
|
|
5
|
+
* and the difference is not documented anywhere near the metric list:
|
|
6
|
+
*
|
|
7
|
+
* total_value {"value": 42} likes, replies, followers
|
|
8
|
+
* time_series {"values":[{"value":…,"end_time":…}]} views
|
|
9
|
+
* breakdown nested by dimension follower_demographics
|
|
10
|
+
*
|
|
11
|
+
* Handed to a model raw, those three shapes mean it has to guess where the
|
|
12
|
+
* number is, and it guesses wrong on `views` about half the time because that
|
|
13
|
+
* is the one that is a series. They are flattened here into one shape.
|
|
14
|
+
*
|
|
15
|
+
* `get_top_posts` is the tool that does not map to an endpoint. Ranking by
|
|
16
|
+
* absolute likes tells you which posts are old; ranking by engagement against
|
|
17
|
+
* views tells you which ones worked. That means a fetch per post, so it is
|
|
18
|
+
* bounded and says what it sampled.
|
|
19
|
+
*/
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
export declare const getPostInsights: import("./kit.js").ToolSpec<{
|
|
22
|
+
account: z.ZodOptional<z.ZodString>;
|
|
23
|
+
id: z.ZodString;
|
|
24
|
+
}>;
|
|
25
|
+
export declare const getAccountInsights: import("./kit.js").ToolSpec<{
|
|
26
|
+
account: z.ZodOptional<z.ZodString>;
|
|
27
|
+
since: z.ZodOptional<z.ZodString>;
|
|
28
|
+
until: z.ZodOptional<z.ZodString>;
|
|
29
|
+
metrics: z.ZodOptional<z.ZodArray<z.ZodEnum<["views", "likes", "replies", "reposts", "quotes", "clicks", "followers_count"]>, "many">>;
|
|
30
|
+
}>;
|
|
31
|
+
export declare const getFollowerDemographics: import("./kit.js").ToolSpec<{
|
|
32
|
+
account: z.ZodOptional<z.ZodString>;
|
|
33
|
+
breakdown: z.ZodEnum<["country", "city", "age", "gender"]>;
|
|
34
|
+
}>;
|
|
35
|
+
export declare const getTopPosts: import("./kit.js").ToolSpec<{
|
|
36
|
+
account: z.ZodOptional<z.ZodString>;
|
|
37
|
+
sample: z.ZodOptional<z.ZodNumber>;
|
|
38
|
+
sort_by: z.ZodOptional<z.ZodEnum<["engagement_rate", "views", "likes", "replies", "reposts", "quotes"]>>;
|
|
39
|
+
}>;
|
|
40
|
+
export declare const INSIGHT_TOOLS: (import("./kit.js").ToolSpec<{
|
|
41
|
+
account: z.ZodOptional<z.ZodString>;
|
|
42
|
+
id: z.ZodString;
|
|
43
|
+
}> | import("./kit.js").ToolSpec<{
|
|
44
|
+
account: z.ZodOptional<z.ZodString>;
|
|
45
|
+
since: z.ZodOptional<z.ZodString>;
|
|
46
|
+
until: z.ZodOptional<z.ZodString>;
|
|
47
|
+
metrics: z.ZodOptional<z.ZodArray<z.ZodEnum<["views", "likes", "replies", "reposts", "quotes", "clicks", "followers_count"]>, "many">>;
|
|
48
|
+
}> | import("./kit.js").ToolSpec<{
|
|
49
|
+
account: z.ZodOptional<z.ZodString>;
|
|
50
|
+
breakdown: z.ZodEnum<["country", "city", "age", "gender"]>;
|
|
51
|
+
}> | import("./kit.js").ToolSpec<{
|
|
52
|
+
account: z.ZodOptional<z.ZodString>;
|
|
53
|
+
sample: z.ZodOptional<z.ZodNumber>;
|
|
54
|
+
sort_by: z.ZodOptional<z.ZodEnum<["engagement_rate", "views", "likes", "replies", "reposts", "quotes"]>>;
|
|
55
|
+
}>)[];
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Insights.
|
|
3
|
+
*
|
|
4
|
+
* Threads reports metrics in three different envelopes depending on the metric,
|
|
5
|
+
* and the difference is not documented anywhere near the metric list:
|
|
6
|
+
*
|
|
7
|
+
* total_value {"value": 42} likes, replies, followers
|
|
8
|
+
* time_series {"values":[{"value":…,"end_time":…}]} views
|
|
9
|
+
* breakdown nested by dimension follower_demographics
|
|
10
|
+
*
|
|
11
|
+
* Handed to a model raw, those three shapes mean it has to guess where the
|
|
12
|
+
* number is, and it guesses wrong on `views` about half the time because that
|
|
13
|
+
* is the one that is a series. They are flattened here into one shape.
|
|
14
|
+
*
|
|
15
|
+
* `get_top_posts` is the tool that does not map to an endpoint. Ranking by
|
|
16
|
+
* absolute likes tells you which posts are old; ranking by engagement against
|
|
17
|
+
* views tells you which ones worked. That means a fetch per post, so it is
|
|
18
|
+
* bounded and says what it sampled.
|
|
19
|
+
*/
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
import { accountArg, clamp, defineTool } from "./kit.js";
|
|
22
|
+
import { POST_FIELDS } from "../api/client.js";
|
|
23
|
+
import { dataOf } from "../format/posts.js";
|
|
24
|
+
import { requireMediaId } from "../api/identity.js";
|
|
25
|
+
const POST_METRICS = ["views", "likes", "replies", "reposts", "quotes", "shares"];
|
|
26
|
+
const ACCOUNT_METRICS = ["views", "likes", "replies", "reposts", "quotes", "clicks", "followers_count"];
|
|
27
|
+
/** Flatten Meta's three insight envelopes into one name-to-number map. */
|
|
28
|
+
function flatten(rows) {
|
|
29
|
+
const out = {};
|
|
30
|
+
for (const row of rows) {
|
|
31
|
+
if (!row.name)
|
|
32
|
+
continue;
|
|
33
|
+
if (typeof row.total_value?.value === "number") {
|
|
34
|
+
out[row.name] = row.total_value.value;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(row.values)) {
|
|
38
|
+
// A time series is summed. For `views` over a window that is the number
|
|
39
|
+
// people mean; the series itself is returned separately when asked for.
|
|
40
|
+
out[row.name] = row.values.reduce((sum, v) => sum + (typeof v.value === "number" ? v.value : 0), 0);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
export const getPostInsights = defineTool({
|
|
46
|
+
name: "get_post_insights",
|
|
47
|
+
title: "Metrics for one post",
|
|
48
|
+
description: "Views, likes, replies, reposts, quotes and shares for one of your posts. Reply metrics count direct replies only, not the whole tree underneath.",
|
|
49
|
+
schema: {
|
|
50
|
+
id: z.string().describe("Numeric post id."),
|
|
51
|
+
...accountArg,
|
|
52
|
+
},
|
|
53
|
+
risk: "read",
|
|
54
|
+
handler: async (args, ctx) => {
|
|
55
|
+
const account = ctx.account(args.account);
|
|
56
|
+
const id = requireMediaId(args.id);
|
|
57
|
+
const response = (await ctx.client.call(account, `/${id}/insights`, {
|
|
58
|
+
params: { metric: POST_METRICS.join(",") },
|
|
59
|
+
}));
|
|
60
|
+
const metrics = flatten(dataOf(response));
|
|
61
|
+
const views = metrics.views ?? 0;
|
|
62
|
+
const interactions = (metrics.likes ?? 0) + (metrics.replies ?? 0) + (metrics.reposts ?? 0) + (metrics.quotes ?? 0);
|
|
63
|
+
return {
|
|
64
|
+
post_id: id,
|
|
65
|
+
...metrics,
|
|
66
|
+
...(views > 0 ? { engagement_rate: Number(((interactions / views) * 100).toFixed(2)) } : {}),
|
|
67
|
+
note: Object.keys(metrics).length
|
|
68
|
+
? undefined
|
|
69
|
+
: "No metrics returned. Reposts of other people's posts have none, and insights need the threads_manage_insights scope.",
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
export const getAccountInsights = defineTool({
|
|
74
|
+
name: "get_account_insights",
|
|
75
|
+
title: "Metrics for the whole profile",
|
|
76
|
+
description: "Profile-level views, likes, replies, reposts, quotes, link clicks and follower count. Data starts on 13 April 2024 and is not reliable before 1 June 2024; earlier windows return nothing.",
|
|
77
|
+
schema: {
|
|
78
|
+
since: z.string().optional().describe("ISO date or Unix timestamp. Nothing before 2024-04-13 is available."),
|
|
79
|
+
until: z.string().optional().describe("ISO date or Unix timestamp."),
|
|
80
|
+
metrics: z
|
|
81
|
+
.array(z.enum(ACCOUNT_METRICS))
|
|
82
|
+
.optional()
|
|
83
|
+
.describe("Which metrics to fetch. Defaults to all of them."),
|
|
84
|
+
...accountArg,
|
|
85
|
+
},
|
|
86
|
+
risk: "read",
|
|
87
|
+
handler: async (args, ctx) => {
|
|
88
|
+
const account = ctx.account(args.account);
|
|
89
|
+
const userId = await ctx.client.userId(account);
|
|
90
|
+
const wanted = args.metrics?.length ? args.metrics : ACCOUNT_METRICS;
|
|
91
|
+
// followers_count rejects a date range, so it is fetched on its own
|
|
92
|
+
// whenever a window was given. Sending it with since/until fails the whole
|
|
93
|
+
// call, taking every other metric down with it.
|
|
94
|
+
const windowed = wanted.filter((m) => m !== "followers_count");
|
|
95
|
+
const wantsFollowers = wanted.includes("followers_count");
|
|
96
|
+
const hasWindow = Boolean(args.since || args.until);
|
|
97
|
+
const results = {};
|
|
98
|
+
if (windowed.length) {
|
|
99
|
+
const response = (await ctx.client.call(account, `/${userId}/threads_insights`, {
|
|
100
|
+
params: { metric: windowed.join(","), since: args.since, until: args.until },
|
|
101
|
+
}));
|
|
102
|
+
Object.assign(results, flatten(dataOf(response)));
|
|
103
|
+
}
|
|
104
|
+
if (wantsFollowers) {
|
|
105
|
+
const response = (await ctx.client.call(account, `/${userId}/threads_insights`, {
|
|
106
|
+
params: { metric: "followers_count" },
|
|
107
|
+
}));
|
|
108
|
+
Object.assign(results, flatten(dataOf(response)));
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
account: account.username ?? userId,
|
|
112
|
+
...(hasWindow ? { since: args.since ?? null, until: args.until ?? null } : { window: "lifetime" }),
|
|
113
|
+
...results,
|
|
114
|
+
...(wantsFollowers && hasWindow
|
|
115
|
+
? { note: "followers_count is a current total and ignores the date range." }
|
|
116
|
+
: {}),
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
export const getFollowerDemographics = defineTool({
|
|
121
|
+
name: "get_follower_demographics",
|
|
122
|
+
title: "Who follows this profile",
|
|
123
|
+
description: "Follower breakdown by country, city, age or gender. One dimension per call: Threads refuses more than one breakdown at a time. Needs at least 100 followers, and ignores any date range.",
|
|
124
|
+
schema: {
|
|
125
|
+
breakdown: z
|
|
126
|
+
.enum(["country", "city", "age", "gender"])
|
|
127
|
+
.describe("Which dimension to break followers down by. Only one per call."),
|
|
128
|
+
...accountArg,
|
|
129
|
+
},
|
|
130
|
+
risk: "read",
|
|
131
|
+
handler: async (args, ctx) => {
|
|
132
|
+
const account = ctx.account(args.account);
|
|
133
|
+
const userId = await ctx.client.userId(account);
|
|
134
|
+
const response = (await ctx.client.call(account, `/${userId}/threads_insights`, {
|
|
135
|
+
params: { metric: "follower_demographics", breakdown: args.breakdown },
|
|
136
|
+
}));
|
|
137
|
+
const row = dataOf(response)[0];
|
|
138
|
+
const results = row?.total_value?.breakdowns?.[0]?.results ?? [];
|
|
139
|
+
const buckets = results
|
|
140
|
+
.map((r) => ({ value: r.dimension_values?.[0] ?? "unknown", followers: r.value ?? 0 }))
|
|
141
|
+
.sort((a, b) => b.followers - a.followers);
|
|
142
|
+
return {
|
|
143
|
+
breakdown: args.breakdown,
|
|
144
|
+
total: buckets.reduce((sum, b) => sum + b.followers, 0),
|
|
145
|
+
buckets,
|
|
146
|
+
...(buckets.length
|
|
147
|
+
? {}
|
|
148
|
+
: { note: "Nothing returned. Follower demographics need at least 100 followers and the threads_manage_insights scope." }),
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
export const getTopPosts = defineTool({
|
|
153
|
+
name: "get_top_posts",
|
|
154
|
+
title: "Rank your posts by what actually worked",
|
|
155
|
+
description: "Fetch recent posts, pull the metrics for each, and rank them. Sorting by engagement rate rather than raw likes is the point: absolute likes mostly rank posts by age, while engagement against views shows which ones landed. Costs one request per post, so keep the sample modest.",
|
|
156
|
+
schema: {
|
|
157
|
+
sample: z
|
|
158
|
+
.number()
|
|
159
|
+
.int()
|
|
160
|
+
.min(1)
|
|
161
|
+
.max(50)
|
|
162
|
+
.optional()
|
|
163
|
+
.describe("How many recent posts to score. Defaults to 20, capped at 50 because each one is a request."),
|
|
164
|
+
sort_by: z
|
|
165
|
+
.enum(["engagement_rate", "views", "likes", "replies", "reposts", "quotes"])
|
|
166
|
+
.optional()
|
|
167
|
+
.describe("Ranking key. Defaults to engagement_rate."),
|
|
168
|
+
...accountArg,
|
|
169
|
+
},
|
|
170
|
+
risk: "read",
|
|
171
|
+
handler: async (args, ctx) => {
|
|
172
|
+
const account = ctx.account(args.account);
|
|
173
|
+
const userId = await ctx.client.userId(account);
|
|
174
|
+
const sample = clamp(args.sample, 20, 50);
|
|
175
|
+
const sortBy = args.sort_by ?? "engagement_rate";
|
|
176
|
+
const listing = (await ctx.client.call(account, `/${userId}/threads`, {
|
|
177
|
+
params: { fields: POST_FIELDS, limit: sample },
|
|
178
|
+
}));
|
|
179
|
+
const posts = dataOf(listing);
|
|
180
|
+
const scored = await Promise.all(posts.map(async (post) => {
|
|
181
|
+
try {
|
|
182
|
+
const response = (await ctx.client.call(account, `/${post.id}/insights`, {
|
|
183
|
+
params: { metric: POST_METRICS.join(",") },
|
|
184
|
+
}));
|
|
185
|
+
const metrics = flatten(dataOf(response));
|
|
186
|
+
const views = metrics.views ?? 0;
|
|
187
|
+
const interactions = (metrics.likes ?? 0) + (metrics.replies ?? 0) + (metrics.reposts ?? 0) + (metrics.quotes ?? 0);
|
|
188
|
+
return {
|
|
189
|
+
id: String(post.id),
|
|
190
|
+
permalink: post.permalink,
|
|
191
|
+
posted_at: post.timestamp,
|
|
192
|
+
excerpt: typeof post.text === "string" ? post.text.replace(/\s+/g, " ").slice(0, 120) : "",
|
|
193
|
+
media_type: post.media_type,
|
|
194
|
+
views,
|
|
195
|
+
likes: metrics.likes ?? 0,
|
|
196
|
+
replies: metrics.replies ?? 0,
|
|
197
|
+
reposts: metrics.reposts ?? 0,
|
|
198
|
+
quotes: metrics.quotes ?? 0,
|
|
199
|
+
engagement_rate: views > 0 ? Number(((interactions / views) * 100).toFixed(2)) : 0,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
}));
|
|
206
|
+
const rows = scored.filter((r) => r !== null);
|
|
207
|
+
rows.sort((a, b) => b[sortBy] - a[sortBy]);
|
|
208
|
+
const withViews = rows.filter((r) => r.views > 0).length;
|
|
209
|
+
return {
|
|
210
|
+
sampled: posts.length,
|
|
211
|
+
scored: rows.length,
|
|
212
|
+
sorted_by: sortBy,
|
|
213
|
+
posts: rows,
|
|
214
|
+
...(sortBy === "engagement_rate" && withViews < rows.length
|
|
215
|
+
? {
|
|
216
|
+
note: `${rows.length - withViews} post(s) reported no views, so their engagement rate is 0 rather than unknown. Sort by likes to include them fairly.`,
|
|
217
|
+
}
|
|
218
|
+
: {}),
|
|
219
|
+
};
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
export const INSIGHT_TOOLS = [getPostInsights, getAccountInsights, getFollowerDemographics, getTopPosts];
|
|
223
|
+
//# sourceMappingURL=insights.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"insights.js","sourceRoot":"","sources":["../../src/tools/insights.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared plumbing every tool uses.
|
|
3
|
+
*
|
|
4
|
+
* Registering thirty tools by hand is thirty chances to forget an annotation,
|
|
5
|
+
* leak a stack trace, or return a shape the model cannot read. This wraps all
|
|
6
|
+
* of it once so a tool module only describes what it actually does.
|
|
7
|
+
*/
|
|
8
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
import { z, type ZodRawShape } from "zod";
|
|
10
|
+
import type { ThreadsClient } from "../api/client.js";
|
|
11
|
+
import type { Account, Config } from "../config.js";
|
|
12
|
+
import { type Risk, type WriteGuard } from "../safety.js";
|
|
13
|
+
export type ToolContext = {
|
|
14
|
+
client: ThreadsClient;
|
|
15
|
+
config: Config;
|
|
16
|
+
guard: WriteGuard;
|
|
17
|
+
/** Resolve which profile this call acts as. */
|
|
18
|
+
account: (hint?: string) => Account;
|
|
19
|
+
};
|
|
20
|
+
export type ToolResult = {
|
|
21
|
+
content: {
|
|
22
|
+
type: "text";
|
|
23
|
+
text: string;
|
|
24
|
+
}[];
|
|
25
|
+
isError?: boolean;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* A tool returns either a pre-rendered string or a value to serialise.
|
|
29
|
+
*
|
|
30
|
+
* The reading tools return the tagged format from `format/posts.ts`, which is
|
|
31
|
+
* already text. The writing tools return a small object, an id and a permalink,
|
|
32
|
+
* where JSON is clearer than tags. Both go through here so neither has to think
|
|
33
|
+
* about the MCP content envelope.
|
|
34
|
+
*/
|
|
35
|
+
export declare function ok(data: unknown): ToolResult;
|
|
36
|
+
/**
|
|
37
|
+
* Errors come back as a normal result with `isError`, not a thrown exception.
|
|
38
|
+
*
|
|
39
|
+
* A thrown MCP error reaches the model as a protocol failure with no structure.
|
|
40
|
+
* A result it can read tells it what went wrong and usually how to fix it,
|
|
41
|
+
* which is the difference between a correct retry and a give-up.
|
|
42
|
+
*/
|
|
43
|
+
export declare function fail(error: unknown): ToolResult;
|
|
44
|
+
/** The optional argument that picks a profile, on every account-scoped tool. */
|
|
45
|
+
export declare const accountArg: {
|
|
46
|
+
account: z.ZodOptional<z.ZodString>;
|
|
47
|
+
};
|
|
48
|
+
/** The confirmation argument required by every public or irreversible tool. */
|
|
49
|
+
export declare const confirmArg: {
|
|
50
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
51
|
+
};
|
|
52
|
+
/** Cursor and limit, on every paginating tool. */
|
|
53
|
+
export declare const pageArgs: {
|
|
54
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
55
|
+
cursor: z.ZodOptional<z.ZodString>;
|
|
56
|
+
};
|
|
57
|
+
export type ToolSpec<S extends ZodRawShape> = {
|
|
58
|
+
name: string;
|
|
59
|
+
/** One line, imperative. Shown in tool pickers. */
|
|
60
|
+
title: string;
|
|
61
|
+
description: string;
|
|
62
|
+
schema: S;
|
|
63
|
+
risk: Risk;
|
|
64
|
+
/** True when calling twice has the same effect as calling once. */
|
|
65
|
+
idempotent?: boolean;
|
|
66
|
+
handler: (args: z.infer<z.ZodObject<S>>, ctx: ToolContext) => Promise<unknown>;
|
|
67
|
+
/** One line for the audit log and the confirm message, when this is a write. */
|
|
68
|
+
summary?: (args: z.infer<z.ZodObject<S>>) => string;
|
|
69
|
+
};
|
|
70
|
+
export declare function defineTool<S extends ZodRawShape>(spec: ToolSpec<S>): ToolSpec<S>;
|
|
71
|
+
/**
|
|
72
|
+
* A tool of any shape, for the one place tools are held together in a list.
|
|
73
|
+
*
|
|
74
|
+
* `ToolSpec` is generic over its schema, so a list of tools with different
|
|
75
|
+
* schemas has no single type: each handler takes a different argument shape and
|
|
76
|
+
* function parameters are contravariant. The type safety that matters lives
|
|
77
|
+
* inside each `defineTool` call, where schema and handler are checked against
|
|
78
|
+
* each other. This only loosens the seam where they are collected.
|
|
79
|
+
*/
|
|
80
|
+
export type AnyToolSpec = Omit<ToolSpec<ZodRawShape>, "handler" | "summary"> & {
|
|
81
|
+
handler: (args: never, ctx: ToolContext) => Promise<unknown>;
|
|
82
|
+
summary?: (args: never) => string;
|
|
83
|
+
};
|
|
84
|
+
/** Register one tool against the server, with guarding and error handling applied. */
|
|
85
|
+
export declare function register(server: McpServer, contextFor: (extra: unknown) => ToolContext, spec: AnyToolSpec): void;
|
|
86
|
+
export declare function makeContext(client: ThreadsClient, config: Config, guard: WriteGuard): ToolContext;
|
|
87
|
+
/** Clamp a caller-supplied limit into a range Threads will accept. */
|
|
88
|
+
export declare function clamp(value: number | undefined, fallback: number, max?: number): number;
|
|
89
|
+
/** Trim a summary to one readable line for the audit log. */
|
|
90
|
+
export declare function snippet(text: string | undefined, length?: number): string;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared plumbing every tool uses.
|
|
3
|
+
*
|
|
4
|
+
* Registering thirty tools by hand is thirty chances to forget an annotation,
|
|
5
|
+
* leak a stack trace, or return a shape the model cannot read. This wraps all
|
|
6
|
+
* of it once so a tool module only describes what it actually does.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { ThreadsError } from "../api/errors.js";
|
|
10
|
+
import { selectAccount } from "../config.js";
|
|
11
|
+
import { annotationsFor } from "../safety.js";
|
|
12
|
+
/**
|
|
13
|
+
* A tool returns either a pre-rendered string or a value to serialise.
|
|
14
|
+
*
|
|
15
|
+
* The reading tools return the tagged format from `format/posts.ts`, which is
|
|
16
|
+
* already text. The writing tools return a small object, an id and a permalink,
|
|
17
|
+
* where JSON is clearer than tags. Both go through here so neither has to think
|
|
18
|
+
* about the MCP content envelope.
|
|
19
|
+
*/
|
|
20
|
+
export function ok(data) {
|
|
21
|
+
const text = typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
22
|
+
return { content: [{ type: "text", text }] };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Errors come back as a normal result with `isError`, not a thrown exception.
|
|
26
|
+
*
|
|
27
|
+
* A thrown MCP error reaches the model as a protocol failure with no structure.
|
|
28
|
+
* A result it can read tells it what went wrong and usually how to fix it,
|
|
29
|
+
* which is the difference between a correct retry and a give-up.
|
|
30
|
+
*/
|
|
31
|
+
export function fail(error) {
|
|
32
|
+
const payload = error instanceof ThreadsError
|
|
33
|
+
? error.toJSON()
|
|
34
|
+
: { error: error?.message ?? String(error) };
|
|
35
|
+
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }], isError: true };
|
|
36
|
+
}
|
|
37
|
+
/** The optional argument that picks a profile, on every account-scoped tool. */
|
|
38
|
+
export const accountArg = {
|
|
39
|
+
account: z
|
|
40
|
+
.string()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("Which connected Threads profile to act as, by username (for example 'thenavidm'). Defaults to the first connected profile. Call list_accounts to see them."),
|
|
43
|
+
};
|
|
44
|
+
/** The confirmation argument required by every public or irreversible tool. */
|
|
45
|
+
export const confirmArg = {
|
|
46
|
+
confirm: z
|
|
47
|
+
.boolean()
|
|
48
|
+
.optional()
|
|
49
|
+
.describe("Must be true for this to run. The result is public immediately or cannot be undone, so it is refused without an explicit confirmation. Threads has no edit endpoint and no unsend."),
|
|
50
|
+
};
|
|
51
|
+
/** Cursor and limit, on every paginating tool. */
|
|
52
|
+
export const pageArgs = {
|
|
53
|
+
limit: z
|
|
54
|
+
.number()
|
|
55
|
+
.int()
|
|
56
|
+
.min(1)
|
|
57
|
+
.max(100)
|
|
58
|
+
.optional()
|
|
59
|
+
.describe("How many to return, 1-100."),
|
|
60
|
+
cursor: z
|
|
61
|
+
.string()
|
|
62
|
+
.optional()
|
|
63
|
+
.describe("Continue from a previous page. Pass the `cursor` attribute from the last result."),
|
|
64
|
+
};
|
|
65
|
+
export function defineTool(spec) {
|
|
66
|
+
return spec;
|
|
67
|
+
}
|
|
68
|
+
/** Register one tool against the server, with guarding and error handling applied. */
|
|
69
|
+
export function register(server, contextFor, spec) {
|
|
70
|
+
server.registerTool(spec.name, {
|
|
71
|
+
title: spec.title,
|
|
72
|
+
description: spec.description,
|
|
73
|
+
inputSchema: spec.schema,
|
|
74
|
+
annotations: {
|
|
75
|
+
title: spec.title,
|
|
76
|
+
...annotationsFor(spec.risk, { idempotent: spec.idempotent }),
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
// The SDK derives its callback type from the schema generic. This wrapper is
|
|
80
|
+
// generic over the same shape, but TypeScript cannot prove the two are equal
|
|
81
|
+
// through the indirection, so the cast lives at this single boundary rather
|
|
82
|
+
// than in every tool definition.
|
|
83
|
+
(async (args, extra) => {
|
|
84
|
+
try {
|
|
85
|
+
const ctx = contextFor(extra);
|
|
86
|
+
if (spec.risk !== "read") {
|
|
87
|
+
const summary = spec.summary?.(args) ?? spec.name;
|
|
88
|
+
const confirm = args.confirm;
|
|
89
|
+
ctx.guard.check(spec.name, spec.risk, confirm, summary);
|
|
90
|
+
}
|
|
91
|
+
return ok(await spec.handler(args, ctx));
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
return fail(error);
|
|
95
|
+
}
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
export function makeContext(client, config, guard) {
|
|
99
|
+
return {
|
|
100
|
+
client,
|
|
101
|
+
config,
|
|
102
|
+
guard,
|
|
103
|
+
account: (hint) => selectAccount(config, hint),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/** Clamp a caller-supplied limit into a range Threads will accept. */
|
|
107
|
+
export function clamp(value, fallback, max = 100) {
|
|
108
|
+
if (value === undefined || !Number.isFinite(value))
|
|
109
|
+
return fallback;
|
|
110
|
+
return Math.min(Math.max(Math.trunc(value), 1), max);
|
|
111
|
+
}
|
|
112
|
+
/** Trim a summary to one readable line for the audit log. */
|
|
113
|
+
export function snippet(text, length = 60) {
|
|
114
|
+
if (!text)
|
|
115
|
+
return "";
|
|
116
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
117
|
+
return flat.length > length ? `${flat.slice(0, length - 1)}…` : flat;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=kit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kit.js","sourceRoot":"","sources":["../../src/tools/kit.ts"],"names":[],"mappings":"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"}
|