@unclick/mcp-server 0.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/README.md +139 -0
- package/dist/amazon-tool.d.ts +5 -0
- package/dist/amazon-tool.d.ts.map +1 -0
- package/dist/amazon-tool.js +307 -0
- package/dist/amazon-tool.js.map +1 -0
- package/dist/bluesky-tool.d.ts +2 -0
- package/dist/bluesky-tool.d.ts.map +1 -0
- package/dist/bluesky-tool.js +368 -0
- package/dist/bluesky-tool.js.map +1 -0
- package/dist/catalog.d.ts +28 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +1865 -0
- package/dist/catalog.js.map +1 -0
- package/dist/client.d.ts +12 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +73 -0
- package/dist/client.js.map +1 -0
- package/dist/converter-tools.d.ts +50 -0
- package/dist/converter-tools.d.ts.map +1 -0
- package/dist/converter-tools.js +137 -0
- package/dist/converter-tools.js.map +1 -0
- package/dist/csuite-tool.d.ts +35 -0
- package/dist/csuite-tool.d.ts.map +1 -0
- package/dist/csuite-tool.js +791 -0
- package/dist/csuite-tool.js.map +1 -0
- package/dist/discord-tool.d.ts +8 -0
- package/dist/discord-tool.d.ts.map +1 -0
- package/dist/discord-tool.js +195 -0
- package/dist/discord-tool.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/local-tools.d.ts +72 -0
- package/dist/local-tools.d.ts.map +1 -0
- package/dist/local-tools.js +563 -0
- package/dist/local-tools.js.map +1 -0
- package/dist/mastodon-tool.d.ts +2 -0
- package/dist/mastodon-tool.d.ts.map +1 -0
- package/dist/mastodon-tool.js +372 -0
- package/dist/mastodon-tool.js.map +1 -0
- package/dist/reddit-tool.d.ts +59 -0
- package/dist/reddit-tool.d.ts.map +1 -0
- package/dist/reddit-tool.js +348 -0
- package/dist/reddit-tool.js.map +1 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +2337 -0
- package/dist/server.js.map +1 -0
- package/dist/shopify-tool.d.ts +8 -0
- package/dist/shopify-tool.d.ts.map +1 -0
- package/dist/shopify-tool.js +305 -0
- package/dist/shopify-tool.js.map +1 -0
- package/dist/slack-tool.d.ts +2 -0
- package/dist/slack-tool.d.ts.map +1 -0
- package/dist/slack-tool.js +316 -0
- package/dist/slack-tool.js.map +1 -0
- package/dist/telegram-tool.d.ts +7 -0
- package/dist/telegram-tool.d.ts.map +1 -0
- package/dist/telegram-tool.js +297 -0
- package/dist/telegram-tool.js.map +1 -0
- package/dist/vault-tool.d.ts +2 -0
- package/dist/vault-tool.d.ts.map +1 -0
- package/dist/vault-tool.js +395 -0
- package/dist/vault-tool.js.map +1 -0
- package/dist/xero-tool.d.ts +9 -0
- package/dist/xero-tool.d.ts.map +1 -0
- package/dist/xero-tool.js +330 -0
- package/dist/xero-tool.js.map +1 -0
- package/package.json +57 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,2337 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { CATALOG, TOOL_MAP, ENDPOINT_MAP } from "./catalog.js";
|
|
5
|
+
import { createClient } from "./client.js";
|
|
6
|
+
import { countText, generateSlug, generateLorem, decodeJwt, lookupHttpStatus, searchEmoji, parseUserAgent, generateReadme, generateChangelog, getFaviconUrls, } from "./local-tools.js";
|
|
7
|
+
import { markdownToHtml, htmlToMarkdown, jsonToYaml, yamlToJson, jsonToXml, xmlToJson, jsonToToml, tomlToJson, csvToJson, jsonToCsv, jsonFormat, jsonToJsonl, jsonlToJson, } from "./converter-tools.js";
|
|
8
|
+
import { csuitAnalyze } from "./csuite-tool.js";
|
|
9
|
+
import { vaultAction } from "./vault-tool.js";
|
|
10
|
+
import { telegramSend, telegramRead, telegramSearch, telegramSendMedia, telegramGetUpdates, telegramManageChat, } from "./telegram-tool.js";
|
|
11
|
+
import { slackAction } from "./slack-tool.js";
|
|
12
|
+
import { discordSend, discordRead, discordThread, discordReact, discordChannels, discordMembers, discordSearch, } from "./discord-tool.js";
|
|
13
|
+
import { redditRead, redditPost, redditComment, redditSearch, redditUser, redditVote, redditSubscribe, } from "./reddit-tool.js";
|
|
14
|
+
import { blueskyAction } from "./bluesky-tool.js";
|
|
15
|
+
import { mastodonAction } from "./mastodon-tool.js";
|
|
16
|
+
import { amazonSearch, amazonProduct, amazonBrowse, amazonVariations, } from "./amazon-tool.js";
|
|
17
|
+
import { xeroInvoices, xeroContacts, xeroAccounts, xeroPayments, xeroBankTransactions, xeroReports, xeroQuotes, xeroOrganisation, } from "./xero-tool.js";
|
|
18
|
+
import { shopifyProducts, shopifyOrders, shopifyCustomers, shopifyInventory, shopifyCollections, shopifyShop, shopifyFulfillments, } from "./shopify-tool.js";
|
|
19
|
+
// ─── Search helper ──────────────────────────────────────────────────────────
|
|
20
|
+
function searchTools(query, category) {
|
|
21
|
+
const q = query.toLowerCase();
|
|
22
|
+
return CATALOG.filter((tool) => {
|
|
23
|
+
const categoryMatch = !category || tool.category === category;
|
|
24
|
+
if (!categoryMatch)
|
|
25
|
+
return false;
|
|
26
|
+
if (!q)
|
|
27
|
+
return true;
|
|
28
|
+
const inToolName = tool.name.toLowerCase().includes(q);
|
|
29
|
+
const inToolDesc = tool.description.toLowerCase().includes(q);
|
|
30
|
+
const inSlug = tool.slug.toLowerCase().includes(q);
|
|
31
|
+
const inEndpoints = tool.endpoints.some((e) => e.name.toLowerCase().includes(q) ||
|
|
32
|
+
e.description.toLowerCase().includes(q) ||
|
|
33
|
+
e.id.toLowerCase().includes(q));
|
|
34
|
+
return inToolName || inToolDesc || inSlug || inEndpoints;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function formatToolSummary(tool) {
|
|
38
|
+
return [
|
|
39
|
+
`**${tool.name}** (slug: \`${tool.slug}\`, category: ${tool.category})`,
|
|
40
|
+
tool.description,
|
|
41
|
+
`Endpoints: ${tool.endpoints.map((e) => `\`${e.id}\``).join(", ")}`,
|
|
42
|
+
].join("\n");
|
|
43
|
+
}
|
|
44
|
+
// ─── MCP Tool definitions ───────────────────────────────────────────────────
|
|
45
|
+
const META_TOOLS = [
|
|
46
|
+
{
|
|
47
|
+
name: "unclick_search",
|
|
48
|
+
description: "Search the UnClick tool marketplace by keyword or description. " +
|
|
49
|
+
"Use this to discover which tools are available for a task. " +
|
|
50
|
+
"Example: 'I need to resize an image' → returns the image tool with its endpoints.",
|
|
51
|
+
inputSchema: {
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: {
|
|
54
|
+
query: {
|
|
55
|
+
type: "string",
|
|
56
|
+
description: "Search term - describe what you want to do",
|
|
57
|
+
},
|
|
58
|
+
category: {
|
|
59
|
+
type: "string",
|
|
60
|
+
enum: ["text", "data", "media", "time", "network", "generation", "storage", "platform"],
|
|
61
|
+
description: "Optional: filter by category",
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
required: ["query"],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "unclick_browse",
|
|
69
|
+
description: "Browse all available UnClick tools, optionally filtered by category. " +
|
|
70
|
+
"Returns a list of tools with their slugs and descriptions.",
|
|
71
|
+
inputSchema: {
|
|
72
|
+
type: "object",
|
|
73
|
+
properties: {
|
|
74
|
+
category: {
|
|
75
|
+
type: "string",
|
|
76
|
+
enum: ["text", "data", "media", "time", "network", "generation", "storage", "platform"],
|
|
77
|
+
description: "Optional: filter to a specific category",
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "unclick_tool_info",
|
|
84
|
+
description: "Get detailed information about a specific UnClick tool including all its endpoints, " +
|
|
85
|
+
"required parameters, and response shapes. Use this after unclick_search to understand " +
|
|
86
|
+
"exactly how to call a tool.",
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: "object",
|
|
89
|
+
properties: {
|
|
90
|
+
slug: {
|
|
91
|
+
type: "string",
|
|
92
|
+
description: "Tool slug, e.g. 'image', 'hash', 'csv', 'cron'. " +
|
|
93
|
+
"Available slugs: " + CATALOG.map((t) => t.slug).join(", "),
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
required: ["slug"],
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: "unclick_call",
|
|
101
|
+
description: "Call any UnClick tool endpoint. Specify the endpoint ID and parameters. " +
|
|
102
|
+
"Use unclick_search or unclick_tool_info to discover endpoint IDs and required params. " +
|
|
103
|
+
"Example: endpoint_id='image.resize', params={image: '<base64>', width: 800, height: 600}",
|
|
104
|
+
inputSchema: {
|
|
105
|
+
type: "object",
|
|
106
|
+
properties: {
|
|
107
|
+
endpoint_id: {
|
|
108
|
+
type: "string",
|
|
109
|
+
description: "Endpoint identifier, e.g. 'image.resize', 'hash.compute', 'csv.parse', 'cron.next'",
|
|
110
|
+
},
|
|
111
|
+
params: {
|
|
112
|
+
type: "object",
|
|
113
|
+
description: "Parameters for the endpoint. Use unclick_tool_info to see required params.",
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
required: ["endpoint_id", "params"],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
];
|
|
120
|
+
const DIRECT_TOOLS = [
|
|
121
|
+
{
|
|
122
|
+
name: "unclick_shorten_url",
|
|
123
|
+
description: "Shorten a URL using UnClick. Returns a short URL and its code.",
|
|
124
|
+
inputSchema: {
|
|
125
|
+
type: "object",
|
|
126
|
+
properties: {
|
|
127
|
+
url: { type: "string", description: "The URL to shorten" },
|
|
128
|
+
},
|
|
129
|
+
required: ["url"],
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "unclick_generate_qr",
|
|
134
|
+
description: "Generate a QR code from text or a URL. Returns base64-encoded PNG or SVG.",
|
|
135
|
+
inputSchema: {
|
|
136
|
+
type: "object",
|
|
137
|
+
properties: {
|
|
138
|
+
text: { type: "string", description: "Text or URL to encode in the QR code" },
|
|
139
|
+
format: { type: "string", enum: ["png", "svg"], default: "png" },
|
|
140
|
+
size: { type: "number", description: "Image size in pixels (100–1000)", default: 300 },
|
|
141
|
+
},
|
|
142
|
+
required: ["text"],
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: "unclick_hash",
|
|
147
|
+
description: "Compute a cryptographic hash (MD5, SHA1, SHA256, SHA512) of text.",
|
|
148
|
+
inputSchema: {
|
|
149
|
+
type: "object",
|
|
150
|
+
properties: {
|
|
151
|
+
text: { type: "string" },
|
|
152
|
+
algorithm: {
|
|
153
|
+
type: "string",
|
|
154
|
+
enum: ["md5", "sha1", "sha256", "sha512"],
|
|
155
|
+
default: "sha256",
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
required: ["text", "algorithm"],
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: "unclick_transform_text",
|
|
163
|
+
description: "Transform text case: upper, lower, title, sentence, camelCase, snake_case, kebab-case, PascalCase.",
|
|
164
|
+
inputSchema: {
|
|
165
|
+
type: "object",
|
|
166
|
+
properties: {
|
|
167
|
+
text: { type: "string" },
|
|
168
|
+
to: {
|
|
169
|
+
type: "string",
|
|
170
|
+
enum: ["upper", "lower", "title", "sentence", "camel", "snake", "kebab", "pascal"],
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
required: ["text", "to"],
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "unclick_validate_email",
|
|
178
|
+
description: "Validate an email address format.",
|
|
179
|
+
inputSchema: {
|
|
180
|
+
type: "object",
|
|
181
|
+
properties: {
|
|
182
|
+
email: { type: "string" },
|
|
183
|
+
},
|
|
184
|
+
required: ["email"],
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
name: "unclick_validate_url",
|
|
189
|
+
description: "Validate a URL format, optionally check if it's reachable.",
|
|
190
|
+
inputSchema: {
|
|
191
|
+
type: "object",
|
|
192
|
+
properties: {
|
|
193
|
+
url: { type: "string" },
|
|
194
|
+
check_reachable: { type: "boolean", default: false },
|
|
195
|
+
},
|
|
196
|
+
required: ["url"],
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: "unclick_resize_image",
|
|
201
|
+
description: "Resize an image (provided as base64) to specified dimensions.",
|
|
202
|
+
inputSchema: {
|
|
203
|
+
type: "object",
|
|
204
|
+
properties: {
|
|
205
|
+
image: { type: "string", description: "Base64-encoded image (with or without data: prefix)" },
|
|
206
|
+
width: { type: "number" },
|
|
207
|
+
height: { type: "number" },
|
|
208
|
+
fit: {
|
|
209
|
+
type: "string",
|
|
210
|
+
enum: ["cover", "contain", "fill", "inside", "outside"],
|
|
211
|
+
default: "cover",
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
required: ["image", "width", "height"],
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: "unclick_parse_csv",
|
|
219
|
+
description: "Parse a CSV string into a JSON array of rows.",
|
|
220
|
+
inputSchema: {
|
|
221
|
+
type: "object",
|
|
222
|
+
properties: {
|
|
223
|
+
csv: { type: "string" },
|
|
224
|
+
header: { type: "boolean", default: true },
|
|
225
|
+
delimiter: { type: "string", default: "," },
|
|
226
|
+
},
|
|
227
|
+
required: ["csv"],
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: "unclick_encode",
|
|
232
|
+
description: "Encode or decode text. Supports base64, URL, HTML, and hex.",
|
|
233
|
+
inputSchema: {
|
|
234
|
+
type: "object",
|
|
235
|
+
properties: {
|
|
236
|
+
text: { type: "string" },
|
|
237
|
+
operation: {
|
|
238
|
+
type: "string",
|
|
239
|
+
enum: [
|
|
240
|
+
"encode_base64", "decode_base64",
|
|
241
|
+
"encode_url", "decode_url",
|
|
242
|
+
"encode_html", "decode_html",
|
|
243
|
+
"encode_hex", "decode_hex",
|
|
244
|
+
],
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
required: ["text", "operation"],
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: "unclick_generate_uuid",
|
|
252
|
+
description: "Generate one or more random UUIDs (v4).",
|
|
253
|
+
inputSchema: {
|
|
254
|
+
type: "object",
|
|
255
|
+
properties: {
|
|
256
|
+
count: { type: "number", minimum: 1, maximum: 100, default: 1 },
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
name: "unclick_random_password",
|
|
262
|
+
description: "Generate a secure random password.",
|
|
263
|
+
inputSchema: {
|
|
264
|
+
type: "object",
|
|
265
|
+
properties: {
|
|
266
|
+
length: { type: "number", minimum: 4, maximum: 512, default: 16 },
|
|
267
|
+
uppercase: { type: "boolean", default: true },
|
|
268
|
+
lowercase: { type: "boolean", default: true },
|
|
269
|
+
numbers: { type: "boolean", default: true },
|
|
270
|
+
symbols: { type: "boolean", default: true },
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
name: "unclick_cron_parse",
|
|
276
|
+
description: "Convert a cron expression to a human-readable description and get next occurrences.",
|
|
277
|
+
inputSchema: {
|
|
278
|
+
type: "object",
|
|
279
|
+
properties: {
|
|
280
|
+
expression: { type: "string", description: "e.g. '0 9 * * 1-5' (weekdays at 9am)" },
|
|
281
|
+
next_count: { type: "number", minimum: 1, maximum: 10, default: 5 },
|
|
282
|
+
},
|
|
283
|
+
required: ["expression"],
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
name: "unclick_ip_parse",
|
|
288
|
+
description: "Parse an IP address - get decimal, binary, hex, and type (private/loopback/multicast).",
|
|
289
|
+
inputSchema: {
|
|
290
|
+
type: "object",
|
|
291
|
+
properties: {
|
|
292
|
+
ip: { type: "string" },
|
|
293
|
+
},
|
|
294
|
+
required: ["ip"],
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
name: "unclick_color_convert",
|
|
299
|
+
description: "Convert a color between hex, RGB, HSL, and HSV formats.",
|
|
300
|
+
inputSchema: {
|
|
301
|
+
type: "object",
|
|
302
|
+
properties: {
|
|
303
|
+
color: {
|
|
304
|
+
description: "Color as hex string (e.g. '#ff6b6b'), RGB object {r,g,b}, or HSL object {h,s,l}",
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
required: ["color"],
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
name: "unclick_regex_test",
|
|
312
|
+
description: "Test a regex pattern against text and get all matches with groups.",
|
|
313
|
+
inputSchema: {
|
|
314
|
+
type: "object",
|
|
315
|
+
properties: {
|
|
316
|
+
pattern: { type: "string", description: "Regex pattern (no surrounding slashes)" },
|
|
317
|
+
flags: { type: "string", description: "Flags like 'gi'", default: "" },
|
|
318
|
+
input: { type: "string" },
|
|
319
|
+
},
|
|
320
|
+
required: ["pattern", "input"],
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
name: "unclick_timestamp_convert",
|
|
325
|
+
description: "Convert a timestamp (ISO, Unix seconds, or Unix ms) to all common formats.",
|
|
326
|
+
inputSchema: {
|
|
327
|
+
type: "object",
|
|
328
|
+
properties: {
|
|
329
|
+
timestamp: {
|
|
330
|
+
description: "ISO string, Unix seconds (e.g. 1700000000), or Unix ms (e.g. 1700000000000)",
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
required: ["timestamp"],
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: "unclick_diff_text",
|
|
338
|
+
description: "Compare two strings and return a unified diff showing what changed.",
|
|
339
|
+
inputSchema: {
|
|
340
|
+
type: "object",
|
|
341
|
+
properties: {
|
|
342
|
+
a: { type: "string", description: "Original text" },
|
|
343
|
+
b: { type: "string", description: "New text" },
|
|
344
|
+
},
|
|
345
|
+
required: ["a", "b"],
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
name: "unclick_kv_set",
|
|
350
|
+
description: "Store a value in the UnClick key-value store with optional TTL.",
|
|
351
|
+
inputSchema: {
|
|
352
|
+
type: "object",
|
|
353
|
+
properties: {
|
|
354
|
+
key: { type: "string" },
|
|
355
|
+
value: { description: "Any JSON-serializable value" },
|
|
356
|
+
ttl: { type: "number", description: "Seconds until expiry (optional)" },
|
|
357
|
+
},
|
|
358
|
+
required: ["key", "value"],
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
name: "unclick_kv_get",
|
|
363
|
+
description: "Retrieve a value from the UnClick key-value store.",
|
|
364
|
+
inputSchema: {
|
|
365
|
+
type: "object",
|
|
366
|
+
properties: {
|
|
367
|
+
key: { type: "string" },
|
|
368
|
+
},
|
|
369
|
+
required: ["key"],
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
// ── Local tools (no API call required) ────────────────────────────────────
|
|
373
|
+
{
|
|
374
|
+
name: "unclick_count_text",
|
|
375
|
+
description: "Count words, characters, sentences, lines, and paragraphs in any text.",
|
|
376
|
+
inputSchema: {
|
|
377
|
+
type: "object",
|
|
378
|
+
properties: {
|
|
379
|
+
text: { type: "string", description: "Text to analyse" },
|
|
380
|
+
},
|
|
381
|
+
required: ["text"],
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
name: "unclick_slug",
|
|
386
|
+
description: "Convert any string into a URL-friendly slug: lowercase, ASCII, words joined by a separator.",
|
|
387
|
+
inputSchema: {
|
|
388
|
+
type: "object",
|
|
389
|
+
properties: {
|
|
390
|
+
text: { type: "string", description: "Text to slugify" },
|
|
391
|
+
separator: { type: "string", default: "-", description: "Word separator (default: '-')" },
|
|
392
|
+
},
|
|
393
|
+
required: ["text"],
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
name: "unclick_lorem_ipsum",
|
|
398
|
+
description: "Generate Lorem Ipsum placeholder text by words, sentences, or paragraphs.",
|
|
399
|
+
inputSchema: {
|
|
400
|
+
type: "object",
|
|
401
|
+
properties: {
|
|
402
|
+
count: { type: "number", minimum: 1, maximum: 100, default: 5 },
|
|
403
|
+
unit: {
|
|
404
|
+
type: "string",
|
|
405
|
+
enum: ["words", "sentences", "paragraphs"],
|
|
406
|
+
default: "sentences",
|
|
407
|
+
},
|
|
408
|
+
start_with_lorem: {
|
|
409
|
+
type: "boolean",
|
|
410
|
+
default: true,
|
|
411
|
+
description: "Start output with 'Lorem ipsum...'",
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
name: "unclick_decode_jwt",
|
|
418
|
+
description: "Decode a JWT token and inspect its header, payload, and expiry. " +
|
|
419
|
+
"Does NOT verify the signature — for inspection only.",
|
|
420
|
+
inputSchema: {
|
|
421
|
+
type: "object",
|
|
422
|
+
properties: {
|
|
423
|
+
token: { type: "string", description: "JWT string (three dot-separated parts)" },
|
|
424
|
+
},
|
|
425
|
+
required: ["token"],
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
name: "unclick_http_status",
|
|
430
|
+
description: "Look up any HTTP status code: get its official phrase, category, and a plain-English description.",
|
|
431
|
+
inputSchema: {
|
|
432
|
+
type: "object",
|
|
433
|
+
properties: {
|
|
434
|
+
code: { type: "number", description: "HTTP status code, e.g. 404, 200, 429" },
|
|
435
|
+
},
|
|
436
|
+
required: ["code"],
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
name: "unclick_emoji_search",
|
|
441
|
+
description: "Find emoji by keyword. Returns matching emoji with names and keywords.",
|
|
442
|
+
inputSchema: {
|
|
443
|
+
type: "object",
|
|
444
|
+
properties: {
|
|
445
|
+
keyword: { type: "string", description: "Search term, e.g. 'fire', 'happy', 'rocket'" },
|
|
446
|
+
limit: { type: "number", minimum: 1, maximum: 30, default: 10 },
|
|
447
|
+
},
|
|
448
|
+
required: ["keyword"],
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
name: "unclick_parse_user_agent",
|
|
453
|
+
description: "Parse a browser User-Agent string into browser, OS, device type, and rendering engine.",
|
|
454
|
+
inputSchema: {
|
|
455
|
+
type: "object",
|
|
456
|
+
properties: {
|
|
457
|
+
user_agent: {
|
|
458
|
+
type: "string",
|
|
459
|
+
description: "Full User-Agent header value",
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
required: ["user_agent"],
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
name: "unclick_readme_template",
|
|
467
|
+
description: "Scaffold a README.md from project info: name, description, install command, usage snippet, license.",
|
|
468
|
+
inputSchema: {
|
|
469
|
+
type: "object",
|
|
470
|
+
properties: {
|
|
471
|
+
name: { type: "string", description: "Project name" },
|
|
472
|
+
description: { type: "string", description: "One-line project description" },
|
|
473
|
+
install: { type: "string", description: "Install command (optional — auto-detected from language)" },
|
|
474
|
+
usage: { type: "string", description: "Usage code snippet (optional)" },
|
|
475
|
+
language: {
|
|
476
|
+
type: "string",
|
|
477
|
+
enum: ["javascript", "typescript", "python", "rust", "go", "other"],
|
|
478
|
+
description: "Primary language — used to pick install command if not provided",
|
|
479
|
+
},
|
|
480
|
+
license: { type: "string", default: "MIT" },
|
|
481
|
+
repo: { type: "string", description: "GitHub repo URL (optional, e.g. https://github.com/owner/repo)" },
|
|
482
|
+
badges: { type: "boolean", default: true },
|
|
483
|
+
},
|
|
484
|
+
required: ["name", "description"],
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
name: "unclick_changelog_entry",
|
|
489
|
+
description: "Format a Keep a Changelog-style entry for a release. " +
|
|
490
|
+
"Provide the version and lists of added/changed/fixed/removed items.",
|
|
491
|
+
inputSchema: {
|
|
492
|
+
type: "object",
|
|
493
|
+
properties: {
|
|
494
|
+
version: { type: "string", description: "Semantic version, e.g. '1.2.0'" },
|
|
495
|
+
date: { type: "string", description: "ISO date (default: today)" },
|
|
496
|
+
added: { type: "array", items: { type: "string" }, description: "New features" },
|
|
497
|
+
changed: { type: "array", items: { type: "string" }, description: "Changes to existing functionality" },
|
|
498
|
+
deprecated: { type: "array", items: { type: "string" } },
|
|
499
|
+
removed: { type: "array", items: { type: "string" } },
|
|
500
|
+
fixed: { type: "array", items: { type: "string" }, description: "Bug fixes" },
|
|
501
|
+
security: { type: "array", items: { type: "string" }, description: "Security fixes" },
|
|
502
|
+
},
|
|
503
|
+
required: ["version"],
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
name: "unclick_favicon_url",
|
|
508
|
+
description: "Get the favicon URLs for any website domain — returns the direct /favicon.ico URL plus " +
|
|
509
|
+
"reliable fallback URLs via Google and DuckDuckGo favicon APIs.",
|
|
510
|
+
inputSchema: {
|
|
511
|
+
type: "object",
|
|
512
|
+
properties: {
|
|
513
|
+
domain: {
|
|
514
|
+
type: "string",
|
|
515
|
+
description: "Domain or URL, e.g. 'github.com' or 'https://github.com/owner/repo'",
|
|
516
|
+
},
|
|
517
|
+
},
|
|
518
|
+
required: ["domain"],
|
|
519
|
+
},
|
|
520
|
+
},
|
|
521
|
+
// ── Converter tools (pure-local, no API call) ─────────────────────────────
|
|
522
|
+
{
|
|
523
|
+
name: "unclick_markdown_to_html",
|
|
524
|
+
description: "Convert Markdown text to HTML.",
|
|
525
|
+
inputSchema: {
|
|
526
|
+
type: "object",
|
|
527
|
+
properties: {
|
|
528
|
+
markdown: { type: "string", description: "Markdown text to convert" },
|
|
529
|
+
},
|
|
530
|
+
required: ["markdown"],
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
name: "unclick_html_to_markdown",
|
|
535
|
+
description: "Convert HTML to Markdown.",
|
|
536
|
+
inputSchema: {
|
|
537
|
+
type: "object",
|
|
538
|
+
properties: {
|
|
539
|
+
html: { type: "string", description: "HTML string to convert" },
|
|
540
|
+
},
|
|
541
|
+
required: ["html"],
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
name: "unclick_json_to_yaml",
|
|
546
|
+
description: "Convert a JSON string to YAML.",
|
|
547
|
+
inputSchema: {
|
|
548
|
+
type: "object",
|
|
549
|
+
properties: {
|
|
550
|
+
json: { type: "string", description: "Valid JSON string" },
|
|
551
|
+
indent: { type: "number", default: 2, description: "YAML indent width (default 2)" },
|
|
552
|
+
},
|
|
553
|
+
required: ["json"],
|
|
554
|
+
},
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
name: "unclick_yaml_to_json",
|
|
558
|
+
description: "Convert a YAML string to JSON.",
|
|
559
|
+
inputSchema: {
|
|
560
|
+
type: "object",
|
|
561
|
+
properties: {
|
|
562
|
+
yaml: { type: "string", description: "Valid YAML string" },
|
|
563
|
+
indent: { type: "number", default: 2, description: "JSON indent width (default 2)" },
|
|
564
|
+
},
|
|
565
|
+
required: ["yaml"],
|
|
566
|
+
},
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
name: "unclick_json_to_xml",
|
|
570
|
+
description: "Convert a JSON string to XML.",
|
|
571
|
+
inputSchema: {
|
|
572
|
+
type: "object",
|
|
573
|
+
properties: {
|
|
574
|
+
json: { type: "string", description: "Valid JSON string" },
|
|
575
|
+
root_key: { type: "string", default: "root", description: "Root element name when input is an array (default: 'root')" },
|
|
576
|
+
},
|
|
577
|
+
required: ["json"],
|
|
578
|
+
},
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
name: "unclick_xml_to_json",
|
|
582
|
+
description: "Convert an XML string to JSON.",
|
|
583
|
+
inputSchema: {
|
|
584
|
+
type: "object",
|
|
585
|
+
properties: {
|
|
586
|
+
xml: { type: "string", description: "Valid XML string" },
|
|
587
|
+
indent: { type: "number", default: 2, description: "JSON indent width (default 2)" },
|
|
588
|
+
},
|
|
589
|
+
required: ["xml"],
|
|
590
|
+
},
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
name: "unclick_json_to_toml",
|
|
594
|
+
description: "Convert a JSON object to TOML. Input must be a top-level object (not an array).",
|
|
595
|
+
inputSchema: {
|
|
596
|
+
type: "object",
|
|
597
|
+
properties: {
|
|
598
|
+
json: { type: "string", description: "Valid JSON object string" },
|
|
599
|
+
},
|
|
600
|
+
required: ["json"],
|
|
601
|
+
},
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
name: "unclick_toml_to_json",
|
|
605
|
+
description: "Convert a TOML string to JSON.",
|
|
606
|
+
inputSchema: {
|
|
607
|
+
type: "object",
|
|
608
|
+
properties: {
|
|
609
|
+
toml: { type: "string", description: "Valid TOML string" },
|
|
610
|
+
indent: { type: "number", default: 2, description: "JSON indent width (default 2)" },
|
|
611
|
+
},
|
|
612
|
+
required: ["toml"],
|
|
613
|
+
},
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
name: "unclick_csv_to_json",
|
|
617
|
+
description: "Convert CSV text to a JSON array.",
|
|
618
|
+
inputSchema: {
|
|
619
|
+
type: "object",
|
|
620
|
+
properties: {
|
|
621
|
+
csv: { type: "string", description: "CSV text to convert" },
|
|
622
|
+
header: { type: "boolean", default: true, description: "First row is a header (default: true)" },
|
|
623
|
+
delimiter: { type: "string", default: ",", description: "Column delimiter (default: ',')" },
|
|
624
|
+
},
|
|
625
|
+
required: ["csv"],
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
name: "unclick_json_to_csv",
|
|
630
|
+
description: "Convert a JSON array to CSV text.",
|
|
631
|
+
inputSchema: {
|
|
632
|
+
type: "object",
|
|
633
|
+
properties: {
|
|
634
|
+
json: { type: "string", description: "JSON array of objects to convert" },
|
|
635
|
+
delimiter: { type: "string", default: ",", description: "Column delimiter (default: ',')" },
|
|
636
|
+
},
|
|
637
|
+
required: ["json"],
|
|
638
|
+
},
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
name: "unclick_json_format",
|
|
642
|
+
description: "Pretty-print or minify a JSON string. Use indent=0 or 'minify' to minify.",
|
|
643
|
+
inputSchema: {
|
|
644
|
+
type: "object",
|
|
645
|
+
properties: {
|
|
646
|
+
json: { type: "string", description: "Valid JSON string" },
|
|
647
|
+
indent: {
|
|
648
|
+
description: "Indent width: 2, 4, 'tab', or 'minify' (default: 2)",
|
|
649
|
+
default: 2,
|
|
650
|
+
},
|
|
651
|
+
},
|
|
652
|
+
required: ["json"],
|
|
653
|
+
},
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
name: "unclick_json_to_jsonl",
|
|
657
|
+
description: "Convert a JSON array to newline-delimited JSON (JSONL), one item per line.",
|
|
658
|
+
inputSchema: {
|
|
659
|
+
type: "object",
|
|
660
|
+
properties: {
|
|
661
|
+
json: { type: "string", description: "JSON array to convert" },
|
|
662
|
+
},
|
|
663
|
+
required: ["json"],
|
|
664
|
+
},
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
name: "unclick_jsonl_to_json",
|
|
668
|
+
description: "Convert newline-delimited JSON (JSONL) to a JSON array.",
|
|
669
|
+
inputSchema: {
|
|
670
|
+
type: "object",
|
|
671
|
+
properties: {
|
|
672
|
+
jsonl: { type: "string", description: "JSONL text (one JSON value per line)" },
|
|
673
|
+
indent: { type: "number", default: 2, description: "JSON indent width (default 2)" },
|
|
674
|
+
},
|
|
675
|
+
required: ["jsonl"],
|
|
676
|
+
},
|
|
677
|
+
},
|
|
678
|
+
// ── Number base converters ───────────────────────────────────────────────
|
|
679
|
+
{
|
|
680
|
+
name: "binary_to_decimal",
|
|
681
|
+
description: "Convert a binary string (base 2) to a decimal number.",
|
|
682
|
+
inputSchema: {
|
|
683
|
+
type: "object",
|
|
684
|
+
properties: {
|
|
685
|
+
binary: { type: "string", description: "Binary string, e.g. '1010'" },
|
|
686
|
+
},
|
|
687
|
+
required: ["binary"],
|
|
688
|
+
},
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
name: "decimal_to_binary",
|
|
692
|
+
description: "Convert a decimal number to a binary string (base 2).",
|
|
693
|
+
inputSchema: {
|
|
694
|
+
type: "object",
|
|
695
|
+
properties: {
|
|
696
|
+
decimal: { type: "number", description: "Decimal integer, e.g. 10" },
|
|
697
|
+
},
|
|
698
|
+
required: ["decimal"],
|
|
699
|
+
},
|
|
700
|
+
},
|
|
701
|
+
{
|
|
702
|
+
name: "hex_to_decimal",
|
|
703
|
+
description: "Convert a hexadecimal string to a decimal number.",
|
|
704
|
+
inputSchema: {
|
|
705
|
+
type: "object",
|
|
706
|
+
properties: {
|
|
707
|
+
hex: { type: "string", description: "Hex string (with or without 0x prefix), e.g. 'FF' or '0xff'" },
|
|
708
|
+
},
|
|
709
|
+
required: ["hex"],
|
|
710
|
+
},
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
name: "decimal_to_hex",
|
|
714
|
+
description: "Convert a decimal number to a hexadecimal string.",
|
|
715
|
+
inputSchema: {
|
|
716
|
+
type: "object",
|
|
717
|
+
properties: {
|
|
718
|
+
decimal: { type: "number", description: "Decimal integer, e.g. 255" },
|
|
719
|
+
},
|
|
720
|
+
required: ["decimal"],
|
|
721
|
+
},
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
name: "octal_to_decimal",
|
|
725
|
+
description: "Convert an octal string (base 8) to a decimal number.",
|
|
726
|
+
inputSchema: {
|
|
727
|
+
type: "object",
|
|
728
|
+
properties: {
|
|
729
|
+
octal: { type: "string", description: "Octal string, e.g. '17'" },
|
|
730
|
+
},
|
|
731
|
+
required: ["octal"],
|
|
732
|
+
},
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
name: "decimal_to_octal",
|
|
736
|
+
description: "Convert a decimal number to an octal string (base 8).",
|
|
737
|
+
inputSchema: {
|
|
738
|
+
type: "object",
|
|
739
|
+
properties: {
|
|
740
|
+
decimal: { type: "number", description: "Decimal integer, e.g. 15" },
|
|
741
|
+
},
|
|
742
|
+
required: ["decimal"],
|
|
743
|
+
},
|
|
744
|
+
},
|
|
745
|
+
// ── Temperature converters ───────────────────────────────────────────────
|
|
746
|
+
{
|
|
747
|
+
name: "celsius_to_fahrenheit",
|
|
748
|
+
description: "Convert a temperature from Celsius to Fahrenheit.",
|
|
749
|
+
inputSchema: {
|
|
750
|
+
type: "object",
|
|
751
|
+
properties: {
|
|
752
|
+
celsius: { type: "number", description: "Temperature in Celsius, e.g. 100" },
|
|
753
|
+
},
|
|
754
|
+
required: ["celsius"],
|
|
755
|
+
},
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
name: "fahrenheit_to_celsius",
|
|
759
|
+
description: "Convert a temperature from Fahrenheit to Celsius.",
|
|
760
|
+
inputSchema: {
|
|
761
|
+
type: "object",
|
|
762
|
+
properties: {
|
|
763
|
+
fahrenheit: { type: "number", description: "Temperature in Fahrenheit, e.g. 212" },
|
|
764
|
+
},
|
|
765
|
+
required: ["fahrenheit"],
|
|
766
|
+
},
|
|
767
|
+
},
|
|
768
|
+
// ── Byte size converters ─────────────────────────────────────────────────
|
|
769
|
+
{
|
|
770
|
+
name: "bytes_to_human",
|
|
771
|
+
description: "Convert a byte count to a human-readable size string (e.g. 1048576 → '1 MB').",
|
|
772
|
+
inputSchema: {
|
|
773
|
+
type: "object",
|
|
774
|
+
properties: {
|
|
775
|
+
bytes: { type: "number", description: "Number of bytes, e.g. 1048576" },
|
|
776
|
+
},
|
|
777
|
+
required: ["bytes"],
|
|
778
|
+
},
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
name: "human_to_bytes",
|
|
782
|
+
description: "Convert a human-readable size string to bytes (e.g. '1.5 GB' → 1610612736).",
|
|
783
|
+
inputSchema: {
|
|
784
|
+
type: "object",
|
|
785
|
+
properties: {
|
|
786
|
+
size: { type: "string", description: "Size string, e.g. '1.5 GB', '512 MB', '2 TB'" },
|
|
787
|
+
},
|
|
788
|
+
required: ["size"],
|
|
789
|
+
},
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
name: "report_bug",
|
|
793
|
+
description: "Report a bug or unexpected behavior encountered while using an UnClick tool. " +
|
|
794
|
+
"Call this whenever a tool returns an error, behaves unexpectedly, or fails silently. " +
|
|
795
|
+
"Severity is auto-classified from the error message: 500/fatal → critical, " +
|
|
796
|
+
"timeout/503 → high, 4xx/invalid → low, everything else → medium.",
|
|
797
|
+
inputSchema: {
|
|
798
|
+
type: "object",
|
|
799
|
+
properties: {
|
|
800
|
+
tool_name: {
|
|
801
|
+
type: "string",
|
|
802
|
+
description: "Name or slug of the UnClick tool that failed (e.g. 'image', 'hash', 'uuid')",
|
|
803
|
+
},
|
|
804
|
+
error_message: {
|
|
805
|
+
type: "string",
|
|
806
|
+
description: "The error message or unexpected output received",
|
|
807
|
+
},
|
|
808
|
+
request_payload: {
|
|
809
|
+
type: "object",
|
|
810
|
+
description: "The request parameters sent to the tool (optional)",
|
|
811
|
+
},
|
|
812
|
+
expected_behavior: {
|
|
813
|
+
type: "string",
|
|
814
|
+
description: "What the tool should have done instead (optional)",
|
|
815
|
+
},
|
|
816
|
+
agent_context: {
|
|
817
|
+
type: "string",
|
|
818
|
+
description: "Brief description of what the agent was trying to accomplish (optional)",
|
|
819
|
+
},
|
|
820
|
+
},
|
|
821
|
+
required: ["tool_name", "error_message"],
|
|
822
|
+
},
|
|
823
|
+
},
|
|
824
|
+
// ── C-Suite analysis (pure local, no API call) ────────────────────────────
|
|
825
|
+
{
|
|
826
|
+
name: "csuite_analyze",
|
|
827
|
+
description: "Run a business decision, scenario, or question through multiple C-suite executive perspectives simultaneously. " +
|
|
828
|
+
"Each 'hat' analyzes the scenario through its unique lens: strategy, operations, finance, technology, people, data, security, product, customer, and AI. " +
|
|
829
|
+
"Returns structured analysis per perspective plus a consensus synthesis. " +
|
|
830
|
+
"Use this to make richer, more well-rounded business decisions by surfacing angles that would otherwise be missed.",
|
|
831
|
+
inputSchema: {
|
|
832
|
+
type: "object",
|
|
833
|
+
properties: {
|
|
834
|
+
scenario: {
|
|
835
|
+
type: "string",
|
|
836
|
+
description: "The business decision, scenario, or question to analyze. Be specific for better analysis.",
|
|
837
|
+
},
|
|
838
|
+
context: {
|
|
839
|
+
type: "string",
|
|
840
|
+
description: "Optional additional context: industry, company stage, size, constraints, current situation.",
|
|
841
|
+
},
|
|
842
|
+
perspectives: {
|
|
843
|
+
type: "array",
|
|
844
|
+
items: {
|
|
845
|
+
type: "string",
|
|
846
|
+
enum: ["CEO", "COO", "CTO", "CFO", "CMO", "CIO", "CHRO", "CDO", "CPO", "CSO", "CCO", "CAIO"],
|
|
847
|
+
},
|
|
848
|
+
description: "Which C-suite roles to include. Defaults to all 12. " +
|
|
849
|
+
"CEO=strategy/vision, COO=operations/scalability, CTO=tech/architecture, CFO=finance/ROI, " +
|
|
850
|
+
"CMO=marketing/brand, CIO=information systems/integration, CHRO=people/culture, " +
|
|
851
|
+
"CDO=data/governance, CPO=product/UX, CSO=security/compliance, CCO=customer/retention, " +
|
|
852
|
+
"CAIO=AI/automation/ethics.",
|
|
853
|
+
},
|
|
854
|
+
depth: {
|
|
855
|
+
type: "string",
|
|
856
|
+
enum: ["quick", "standard", "deep"],
|
|
857
|
+
default: "standard",
|
|
858
|
+
description: "Analysis depth. quick=2-3 points per area, standard=4-5, deep=6-7 with sub-considerations.",
|
|
859
|
+
},
|
|
860
|
+
focus: {
|
|
861
|
+
type: "string",
|
|
862
|
+
description: "Optional aspect to emphasize across all perspectives, e.g. 'risk', 'growth', 'cost', 'speed'.",
|
|
863
|
+
},
|
|
864
|
+
},
|
|
865
|
+
required: ["scenario"],
|
|
866
|
+
},
|
|
867
|
+
},
|
|
868
|
+
// ── Encrypted credential vault (pure local, no API call) ─────────────────
|
|
869
|
+
{
|
|
870
|
+
name: "vault",
|
|
871
|
+
description: "Secure encrypted credential vault. Store, retrieve, rotate, and audit API keys, " +
|
|
872
|
+
"tokens, passwords, and secrets. All data is AES-256-GCM encrypted at rest using a " +
|
|
873
|
+
"master password you control. Secrets are masked by default (****last4) unless reveal=true. " +
|
|
874
|
+
"Vault lives at ~/.unclick/vault.enc. " +
|
|
875
|
+
"Actions: vault_init, vault_store, vault_retrieve, vault_list, vault_delete, vault_rotate, vault_audit.",
|
|
876
|
+
inputSchema: {
|
|
877
|
+
type: "object",
|
|
878
|
+
properties: {
|
|
879
|
+
action: {
|
|
880
|
+
type: "string",
|
|
881
|
+
enum: [
|
|
882
|
+
"vault_init",
|
|
883
|
+
"vault_store",
|
|
884
|
+
"vault_retrieve",
|
|
885
|
+
"vault_list",
|
|
886
|
+
"vault_delete",
|
|
887
|
+
"vault_rotate",
|
|
888
|
+
"vault_audit",
|
|
889
|
+
],
|
|
890
|
+
description: "vault_init: create new vault. " +
|
|
891
|
+
"vault_store: save a secret. " +
|
|
892
|
+
"vault_retrieve: get a secret (masked unless reveal=true). " +
|
|
893
|
+
"vault_list: list key names and metadata (never values). " +
|
|
894
|
+
"vault_delete: remove a secret. " +
|
|
895
|
+
"vault_rotate: replace a secret value with a new IV. " +
|
|
896
|
+
"vault_audit: view access event log.",
|
|
897
|
+
},
|
|
898
|
+
master_password: {
|
|
899
|
+
type: "string",
|
|
900
|
+
description: "Master password used to encrypt/decrypt the vault.",
|
|
901
|
+
},
|
|
902
|
+
key: {
|
|
903
|
+
type: "string",
|
|
904
|
+
description: "Secret name/label (required for store, retrieve, delete, rotate).",
|
|
905
|
+
},
|
|
906
|
+
value: {
|
|
907
|
+
type: "string",
|
|
908
|
+
description: "Secret value to store (required for vault_store).",
|
|
909
|
+
},
|
|
910
|
+
new_value: {
|
|
911
|
+
type: "string",
|
|
912
|
+
description: "Replacement secret value (required for vault_rotate).",
|
|
913
|
+
},
|
|
914
|
+
reveal: {
|
|
915
|
+
type: "boolean",
|
|
916
|
+
default: false,
|
|
917
|
+
description: "If true, returns the full decrypted value. Default false returns ****last4.",
|
|
918
|
+
},
|
|
919
|
+
metadata: {
|
|
920
|
+
type: "object",
|
|
921
|
+
description: "Optional tags/notes for vault_store: " +
|
|
922
|
+
"{ service, tags, expires, notes } - any JSON object.",
|
|
923
|
+
},
|
|
924
|
+
limit: {
|
|
925
|
+
type: "number",
|
|
926
|
+
minimum: 1,
|
|
927
|
+
maximum: 100,
|
|
928
|
+
default: 20,
|
|
929
|
+
description: "Max audit events to return (vault_audit only, default 20).",
|
|
930
|
+
},
|
|
931
|
+
},
|
|
932
|
+
required: ["action", "master_password"],
|
|
933
|
+
},
|
|
934
|
+
},
|
|
935
|
+
// ── Telegram Bot API tools ────────────────────────────────────────────────
|
|
936
|
+
{
|
|
937
|
+
name: "telegram_send",
|
|
938
|
+
description: "Send a text message to a Telegram chat, group, or channel via your bot. " +
|
|
939
|
+
"Supports plain text and Markdown/HTML formatting. " +
|
|
940
|
+
"Get a bot token from @BotFather. The bot must be a member of the target chat.",
|
|
941
|
+
inputSchema: {
|
|
942
|
+
type: "object",
|
|
943
|
+
properties: {
|
|
944
|
+
bot_token: { type: "string", description: "Telegram bot token from @BotFather (e.g. 123456:ABC-DEF...)." },
|
|
945
|
+
chat_id: { type: ["string", "number"], description: "Target chat ID or @username (e.g. -1001234567890 or @mychannel)." },
|
|
946
|
+
text: { type: "string", description: "Message text to send (up to 4096 characters)." },
|
|
947
|
+
parse_mode: { type: "string", enum: ["Markdown", "HTML", "MarkdownV2"], description: "Optional formatting mode. Default is plain text." },
|
|
948
|
+
reply_to_message_id: { type: "number", description: "Optional message ID to reply to." },
|
|
949
|
+
disable_notification: { type: "boolean", description: "Send silently without notification sound. Default false." },
|
|
950
|
+
},
|
|
951
|
+
required: ["bot_token", "chat_id", "text"],
|
|
952
|
+
},
|
|
953
|
+
},
|
|
954
|
+
{
|
|
955
|
+
name: "telegram_read",
|
|
956
|
+
description: "Read recent messages received by your Telegram bot. " +
|
|
957
|
+
"Returns messages the bot has received since the last getUpdates call (or from a given offset). " +
|
|
958
|
+
"Filter by chat_id to see messages from a specific chat.",
|
|
959
|
+
inputSchema: {
|
|
960
|
+
type: "object",
|
|
961
|
+
properties: {
|
|
962
|
+
bot_token: { type: "string", description: "Telegram bot token from @BotFather." },
|
|
963
|
+
chat_id: { type: ["string", "number"], description: "Optional chat ID to filter messages (e.g. -1001234567890)." },
|
|
964
|
+
limit: { type: "number", minimum: 1, maximum: 100, default: 20, description: "Max number of recent messages to return. Default 20." },
|
|
965
|
+
offset: { type: "number", description: "Update ID offset for pagination. Use next_offset from a previous response." },
|
|
966
|
+
},
|
|
967
|
+
required: ["bot_token"],
|
|
968
|
+
},
|
|
969
|
+
},
|
|
970
|
+
{
|
|
971
|
+
name: "telegram_search",
|
|
972
|
+
description: "Search for messages containing a keyword in your bot's recent message history. " +
|
|
973
|
+
"Searches across all updates the bot has received. Filter by chat_id for a specific chat.",
|
|
974
|
+
inputSchema: {
|
|
975
|
+
type: "object",
|
|
976
|
+
properties: {
|
|
977
|
+
bot_token: { type: "string", description: "Telegram bot token from @BotFather." },
|
|
978
|
+
query: { type: "string", description: "Keyword or phrase to search for (case-insensitive)." },
|
|
979
|
+
chat_id: { type: ["string", "number"], description: "Optional chat ID to narrow search to a specific chat." },
|
|
980
|
+
limit: { type: "number", minimum: 1, maximum: 50, default: 10, description: "Max number of matching messages to return. Default 10." },
|
|
981
|
+
offset: { type: "number", description: "Update ID offset to start from." },
|
|
982
|
+
},
|
|
983
|
+
required: ["bot_token", "query"],
|
|
984
|
+
},
|
|
985
|
+
},
|
|
986
|
+
{
|
|
987
|
+
name: "telegram_send_media",
|
|
988
|
+
description: "Send a photo, document, audio, video, or animation to a Telegram chat via your bot. " +
|
|
989
|
+
"The media must be accessible via a public URL. Supports optional captions.",
|
|
990
|
+
inputSchema: {
|
|
991
|
+
type: "object",
|
|
992
|
+
properties: {
|
|
993
|
+
bot_token: { type: "string", description: "Telegram bot token from @BotFather." },
|
|
994
|
+
chat_id: { type: ["string", "number"], description: "Target chat ID or @username." },
|
|
995
|
+
media_type: { type: "string", enum: ["photo", "document", "audio", "video", "animation"], description: "Type of media to send." },
|
|
996
|
+
media_url: { type: "string", description: "Public URL of the media file to send." },
|
|
997
|
+
caption: { type: "string", description: "Optional caption for the media (up to 1024 characters)." },
|
|
998
|
+
parse_mode: { type: "string", enum: ["Markdown", "HTML", "MarkdownV2"], description: "Formatting mode for the caption." },
|
|
999
|
+
disable_notification: { type: "boolean", description: "Send silently without notification sound. Default false." },
|
|
1000
|
+
},
|
|
1001
|
+
required: ["bot_token", "chat_id", "media_type", "media_url"],
|
|
1002
|
+
},
|
|
1003
|
+
},
|
|
1004
|
+
{
|
|
1005
|
+
name: "telegram_get_updates",
|
|
1006
|
+
description: "Fetch pending updates (messages, edited messages, channel posts) from the Telegram Bot API. " +
|
|
1007
|
+
"Use offset to mark previous updates as processed and avoid seeing them again.",
|
|
1008
|
+
inputSchema: {
|
|
1009
|
+
type: "object",
|
|
1010
|
+
properties: {
|
|
1011
|
+
bot_token: { type: "string", description: "Telegram bot token from @BotFather." },
|
|
1012
|
+
limit: { type: "number", minimum: 1, maximum: 100, default: 20, description: "Max number of updates to return." },
|
|
1013
|
+
offset: { type: "number", description: "Update ID offset. Pass next_offset from previous response to mark updates as read." },
|
|
1014
|
+
timeout: { type: "number", minimum: 0, maximum: 30, default: 0, description: "Seconds to wait for long polling. 0 = return immediately." },
|
|
1015
|
+
allowed_updates: { type: "array", items: { type: "string" }, description: "List of update types to receive, e.g. ['message', 'channel_post']." },
|
|
1016
|
+
},
|
|
1017
|
+
required: ["bot_token"],
|
|
1018
|
+
},
|
|
1019
|
+
},
|
|
1020
|
+
{
|
|
1021
|
+
name: "telegram_manage_chat",
|
|
1022
|
+
description: "Manage a Telegram chat: get chat info, list admins, pin a message, or unpin a message. " +
|
|
1023
|
+
"The bot must be an admin with the appropriate permissions to pin/unpin.",
|
|
1024
|
+
inputSchema: {
|
|
1025
|
+
type: "object",
|
|
1026
|
+
properties: {
|
|
1027
|
+
bot_token: { type: "string", description: "Telegram bot token from @BotFather." },
|
|
1028
|
+
chat_id: { type: ["string", "number"], description: "Target chat ID or @username." },
|
|
1029
|
+
action: {
|
|
1030
|
+
type: "string",
|
|
1031
|
+
enum: ["info", "members", "pin", "unpin"],
|
|
1032
|
+
description: "info: get chat details. members: list admins. pin: pin a message (requires message_id). unpin: unpin a message (message_id optional).",
|
|
1033
|
+
},
|
|
1034
|
+
message_id: { type: "number", description: "Message ID to pin or unpin (required for pin, optional for unpin)." },
|
|
1035
|
+
disable_notification: { type: "boolean", description: "Pin without notification. Applies to pin action only. Default false." },
|
|
1036
|
+
},
|
|
1037
|
+
required: ["bot_token", "chat_id", "action"],
|
|
1038
|
+
},
|
|
1039
|
+
},
|
|
1040
|
+
// ── Slack Web API ─────────────────────────────────────────────────────────
|
|
1041
|
+
{
|
|
1042
|
+
name: "slack",
|
|
1043
|
+
description: "Send and receive Slack messages, search conversations, manage reactions, " +
|
|
1044
|
+
"upload files, and list channels via the official Slack Web API. " +
|
|
1045
|
+
"Requires a Bot Token (xoxb-...) with the appropriate OAuth scopes. " +
|
|
1046
|
+
"Actions: slack_send, slack_read, slack_search, slack_thread_reply, " +
|
|
1047
|
+
"slack_channels, slack_react, slack_upload.",
|
|
1048
|
+
inputSchema: {
|
|
1049
|
+
type: "object",
|
|
1050
|
+
properties: {
|
|
1051
|
+
action: {
|
|
1052
|
+
type: "string",
|
|
1053
|
+
enum: ["slack_send", "slack_read", "slack_search", "slack_thread_reply", "slack_channels", "slack_react", "slack_upload"],
|
|
1054
|
+
description: "slack_send: post a message to a channel or DM. " +
|
|
1055
|
+
"slack_read: fetch recent messages from a channel (conversation history). " +
|
|
1056
|
+
"slack_search: search messages across the workspace. " +
|
|
1057
|
+
"slack_thread_reply: reply to an existing message thread. " +
|
|
1058
|
+
"slack_channels: list channels the bot can access. " +
|
|
1059
|
+
"slack_react: add an emoji reaction to a message. " +
|
|
1060
|
+
"slack_upload: upload a text file to a channel.",
|
|
1061
|
+
},
|
|
1062
|
+
bot_token: { type: "string", description: "Slack Bot Token starting with xoxb-. Create one at api.slack.com/apps under 'OAuth and Permissions'." },
|
|
1063
|
+
channel: { type: "string", description: "Channel ID (e.g. C01234ABCDE) or name (e.g. #general). Used by: slack_send, slack_read, slack_thread_reply, slack_react, slack_upload." },
|
|
1064
|
+
text: { type: "string", description: "Message text (supports Slack mrkdwn). Required by slack_send and slack_thread_reply unless blocks is provided." },
|
|
1065
|
+
blocks: { description: "Slack Block Kit payload (array of block objects). Optional alternative/addition to text for rich messages." },
|
|
1066
|
+
thread_ts: { type: "string", description: "Parent message timestamp to reply in a thread. Required for slack_thread_reply. Optional for slack_send." },
|
|
1067
|
+
username: { type: "string", description: "Override the bot display name for this message (slack_send only)." },
|
|
1068
|
+
limit: { type: "number", minimum: 1, maximum: 200, default: 20, description: "Max messages to return (slack_read: 1-200; slack_channels: 1-1000)." },
|
|
1069
|
+
oldest: { type: "string", description: "Return messages after this Unix timestamp (slack_read only)." },
|
|
1070
|
+
latest: { type: "string", description: "Return messages before this Unix timestamp (slack_read only)." },
|
|
1071
|
+
query: { type: "string", description: "Search query string (slack_search only). Supports Slack search modifiers." },
|
|
1072
|
+
count: { type: "number", minimum: 1, maximum: 100, default: 20, description: "Max search results to return (slack_search only)." },
|
|
1073
|
+
sort: { type: "string", enum: ["score", "timestamp"], default: "timestamp", description: "Sort search results by relevance or recency (slack_search only)." },
|
|
1074
|
+
sort_dir: { type: "string", enum: ["asc", "desc"], default: "desc", description: "Sort direction for search results (slack_search only)." },
|
|
1075
|
+
types: { type: "string", default: "public_channel", description: "Comma-separated channel types to include (slack_channels only)." },
|
|
1076
|
+
exclude_archived: { type: "boolean", default: true, description: "Exclude archived channels from results (slack_channels only)." },
|
|
1077
|
+
timestamp: { type: "string", description: "Message timestamp (ts) to react to (slack_react only)." },
|
|
1078
|
+
emoji: { type: "string", description: "Emoji name without colons, e.g. 'thumbsup' or '+1' (slack_react only)." },
|
|
1079
|
+
filename: { type: "string", default: "file.txt", description: "Filename for the uploaded file (slack_upload only)." },
|
|
1080
|
+
content: { type: "string", description: "Text content of the file to upload (slack_upload only)." },
|
|
1081
|
+
channels: { type: "string", description: "Comma-separated channel IDs to share the file with (slack_upload only)." },
|
|
1082
|
+
initial_comment: { type: "string", description: "Optional message to post alongside the uploaded file (slack_upload only)." },
|
|
1083
|
+
},
|
|
1084
|
+
required: ["action", "bot_token"],
|
|
1085
|
+
},
|
|
1086
|
+
},
|
|
1087
|
+
// ── Discord REST API v10 ──────────────────────────────────────────────────
|
|
1088
|
+
{
|
|
1089
|
+
name: "discord_send",
|
|
1090
|
+
description: "Send a message to a Discord channel. Optionally reply to an existing message or enable TTS.",
|
|
1091
|
+
inputSchema: {
|
|
1092
|
+
type: "object",
|
|
1093
|
+
properties: {
|
|
1094
|
+
bot_token: { type: "string", description: "Discord bot token (starts with Bot ...)" },
|
|
1095
|
+
channel_id: { type: "string", description: "ID of the channel to send the message to" },
|
|
1096
|
+
content: { type: "string", description: "Message text (up to 2000 characters)" },
|
|
1097
|
+
reply_to: { type: "string", description: "Message ID to reply to (optional)" },
|
|
1098
|
+
tts: { type: "boolean", default: false, description: "Send as text-to-speech (optional)" },
|
|
1099
|
+
},
|
|
1100
|
+
required: ["bot_token", "channel_id", "content"],
|
|
1101
|
+
},
|
|
1102
|
+
},
|
|
1103
|
+
{
|
|
1104
|
+
name: "discord_read",
|
|
1105
|
+
description: "Read recent messages from a Discord channel.",
|
|
1106
|
+
inputSchema: {
|
|
1107
|
+
type: "object",
|
|
1108
|
+
properties: {
|
|
1109
|
+
bot_token: { type: "string", description: "Discord bot token" },
|
|
1110
|
+
channel_id: { type: "string", description: "ID of the channel to read" },
|
|
1111
|
+
limit: { type: "number", minimum: 1, maximum: 100, default: 50, description: "Number of messages to fetch (default 50, max 100)" },
|
|
1112
|
+
before: { type: "string", description: "Fetch messages before this message ID (optional)" },
|
|
1113
|
+
after: { type: "string", description: "Fetch messages after this message ID (optional)" },
|
|
1114
|
+
},
|
|
1115
|
+
required: ["bot_token", "channel_id"],
|
|
1116
|
+
},
|
|
1117
|
+
},
|
|
1118
|
+
{
|
|
1119
|
+
name: "discord_thread",
|
|
1120
|
+
description: "Create a thread from a message, create a standalone thread in a channel, or reply to an existing thread. " +
|
|
1121
|
+
"Provide thread_id to reply. Provide message_id to start a thread from that message. " +
|
|
1122
|
+
"Provide neither to create a standalone thread.",
|
|
1123
|
+
inputSchema: {
|
|
1124
|
+
type: "object",
|
|
1125
|
+
properties: {
|
|
1126
|
+
bot_token: { type: "string", description: "Discord bot token" },
|
|
1127
|
+
channel_id: { type: "string", description: "Channel to create the thread in" },
|
|
1128
|
+
name: { type: "string", description: "Thread name (required when creating a thread)" },
|
|
1129
|
+
content: { type: "string", description: "Initial or reply message content" },
|
|
1130
|
+
thread_id: { type: "string", description: "Existing thread ID to reply to (optional)" },
|
|
1131
|
+
message_id: { type: "string", description: "Message ID to start a thread from (optional)" },
|
|
1132
|
+
auto_archive_duration: { type: "number", enum: [60, 1440, 4320, 10080], description: "Minutes until the thread auto-archives: 60, 1440, 4320, or 10080 (optional)" },
|
|
1133
|
+
},
|
|
1134
|
+
required: ["bot_token", "channel_id"],
|
|
1135
|
+
},
|
|
1136
|
+
},
|
|
1137
|
+
{
|
|
1138
|
+
name: "discord_react",
|
|
1139
|
+
description: "Add an emoji reaction to a Discord message.",
|
|
1140
|
+
inputSchema: {
|
|
1141
|
+
type: "object",
|
|
1142
|
+
properties: {
|
|
1143
|
+
bot_token: { type: "string", description: "Discord bot token" },
|
|
1144
|
+
channel_id: { type: "string", description: "Channel containing the message" },
|
|
1145
|
+
message_id: { type: "string", description: "ID of the message to react to" },
|
|
1146
|
+
emoji: { type: "string", description: "Unicode emoji (e.g. '👍') or custom emoji in name:id format (e.g. 'thumbsup:123456789')" },
|
|
1147
|
+
},
|
|
1148
|
+
required: ["bot_token", "channel_id", "message_id", "emoji"],
|
|
1149
|
+
},
|
|
1150
|
+
},
|
|
1151
|
+
{
|
|
1152
|
+
name: "discord_channels",
|
|
1153
|
+
description: "List all channels in a Discord guild (server).",
|
|
1154
|
+
inputSchema: {
|
|
1155
|
+
type: "object",
|
|
1156
|
+
properties: {
|
|
1157
|
+
bot_token: { type: "string", description: "Discord bot token" },
|
|
1158
|
+
guild_id: { type: "string", description: "Guild (server) ID" },
|
|
1159
|
+
},
|
|
1160
|
+
required: ["bot_token", "guild_id"],
|
|
1161
|
+
},
|
|
1162
|
+
},
|
|
1163
|
+
{
|
|
1164
|
+
name: "discord_members",
|
|
1165
|
+
description: "List members of a Discord guild. Requires the SERVER MEMBERS INTENT to be enabled on the bot.",
|
|
1166
|
+
inputSchema: {
|
|
1167
|
+
type: "object",
|
|
1168
|
+
properties: {
|
|
1169
|
+
bot_token: { type: "string", description: "Discord bot token" },
|
|
1170
|
+
guild_id: { type: "string", description: "Guild (server) ID" },
|
|
1171
|
+
limit: { type: "number", minimum: 1, maximum: 1000, default: 100, description: "Number of members to return (default 100, max 1000)" },
|
|
1172
|
+
after: { type: "string", description: "Return members after this user ID for pagination (optional)" },
|
|
1173
|
+
},
|
|
1174
|
+
required: ["bot_token", "guild_id"],
|
|
1175
|
+
},
|
|
1176
|
+
},
|
|
1177
|
+
{
|
|
1178
|
+
name: "discord_search",
|
|
1179
|
+
description: "Search messages in a Discord guild using Discord's search endpoint.",
|
|
1180
|
+
inputSchema: {
|
|
1181
|
+
type: "object",
|
|
1182
|
+
properties: {
|
|
1183
|
+
bot_token: { type: "string", description: "Discord bot token" },
|
|
1184
|
+
guild_id: { type: "string", description: "Guild (server) ID to search in" },
|
|
1185
|
+
query: { type: "string", description: "Text to search for in message content" },
|
|
1186
|
+
channel_id: { type: "string", description: "Restrict search to this channel (optional)" },
|
|
1187
|
+
author_id: { type: "string", description: "Restrict search to messages from this user ID (optional)" },
|
|
1188
|
+
has: { type: "string", enum: ["link", "embed", "file", "video", "image", "sound", "sticker"], description: "Filter by attachment type (optional)" },
|
|
1189
|
+
limit: { type: "number", minimum: 1, maximum: 25, default: 25, description: "Max results (default 25)" },
|
|
1190
|
+
offset: { type: "number", default: 0, description: "Pagination offset (optional)" },
|
|
1191
|
+
},
|
|
1192
|
+
required: ["bot_token", "guild_id", "query"],
|
|
1193
|
+
},
|
|
1194
|
+
},
|
|
1195
|
+
// ── Reddit OAuth2 API ─────────────────────────────────────────────────────
|
|
1196
|
+
{
|
|
1197
|
+
name: "reddit_read",
|
|
1198
|
+
description: "Read posts from a subreddit. Supports hot, new, top, and rising feeds. " +
|
|
1199
|
+
"Returns post titles, scores, authors, URLs, and comment counts. " +
|
|
1200
|
+
"Requires a Reddit OAuth2 bearer token with the 'read' scope.",
|
|
1201
|
+
inputSchema: {
|
|
1202
|
+
type: "object",
|
|
1203
|
+
properties: {
|
|
1204
|
+
access_token: { type: "string", description: "Reddit OAuth2 bearer token (scope: read)." },
|
|
1205
|
+
subreddit: { type: "string", description: "Subreddit name, e.g. 'programming' or 'r/programming'." },
|
|
1206
|
+
sort: { type: "string", enum: ["hot", "new", "top", "rising"], default: "hot", description: "Feed sort order (default: hot)." },
|
|
1207
|
+
limit: { type: "number", minimum: 1, maximum: 100, default: 25, description: "Number of posts to return (1-100, default 25)." },
|
|
1208
|
+
after: { type: "string", description: "Pagination cursor from a previous response (optional)." },
|
|
1209
|
+
t: { type: "string", enum: ["hour", "day", "week", "month", "year", "all"], description: "Time filter for 'top' sort (optional)." },
|
|
1210
|
+
},
|
|
1211
|
+
required: ["access_token", "subreddit"],
|
|
1212
|
+
},
|
|
1213
|
+
},
|
|
1214
|
+
{
|
|
1215
|
+
name: "reddit_post",
|
|
1216
|
+
description: "Submit a new post to a subreddit. Supports text (self) posts and link posts. " +
|
|
1217
|
+
"Requires a Reddit OAuth2 bearer token with the 'submit' scope.",
|
|
1218
|
+
inputSchema: {
|
|
1219
|
+
type: "object",
|
|
1220
|
+
properties: {
|
|
1221
|
+
access_token: { type: "string", description: "Reddit OAuth2 bearer token (scope: submit)." },
|
|
1222
|
+
subreddit: { type: "string", description: "Subreddit to post to, e.g. 'test' or 'r/test'." },
|
|
1223
|
+
title: { type: "string", description: "Post title (required)." },
|
|
1224
|
+
kind: { type: "string", enum: ["self", "link"], description: "'self' for text posts, 'link' for URL posts." },
|
|
1225
|
+
text: { type: "string", description: "Post body in Markdown (required when kind is 'self')." },
|
|
1226
|
+
url: { type: "string", description: "URL to submit (required when kind is 'link')." },
|
|
1227
|
+
nsfw: { type: "boolean", default: false, description: "Mark post as NSFW (default false)." },
|
|
1228
|
+
spoiler: { type: "boolean", default: false, description: "Mark post as spoiler (default false)." },
|
|
1229
|
+
flair_id: { type: "string", description: "Flair template ID (optional)." },
|
|
1230
|
+
flair_text: { type: "string", description: "Flair text (optional)." },
|
|
1231
|
+
},
|
|
1232
|
+
required: ["access_token", "subreddit", "title", "kind"],
|
|
1233
|
+
},
|
|
1234
|
+
},
|
|
1235
|
+
{
|
|
1236
|
+
name: "reddit_comment",
|
|
1237
|
+
description: "Post a comment on a Reddit post or reply to an existing comment. " +
|
|
1238
|
+
"Use the fullname of the parent (t3_ for posts, t1_ for comments). " +
|
|
1239
|
+
"Requires a Reddit OAuth2 bearer token with the 'submit' scope.",
|
|
1240
|
+
inputSchema: {
|
|
1241
|
+
type: "object",
|
|
1242
|
+
properties: {
|
|
1243
|
+
access_token: { type: "string", description: "Reddit OAuth2 bearer token (scope: submit)." },
|
|
1244
|
+
parent_id: { type: "string", description: "Fullname of the post or comment to reply to, e.g. 't3_abc123' or 't1_def456'." },
|
|
1245
|
+
text: { type: "string", description: "Comment body in Markdown." },
|
|
1246
|
+
},
|
|
1247
|
+
required: ["access_token", "parent_id", "text"],
|
|
1248
|
+
},
|
|
1249
|
+
},
|
|
1250
|
+
{
|
|
1251
|
+
name: "reddit_search",
|
|
1252
|
+
description: "Search posts across all of Reddit or within a specific subreddit. " +
|
|
1253
|
+
"Supports relevance, hot, top, new, and comments sort orders. " +
|
|
1254
|
+
"Requires a Reddit OAuth2 bearer token with the 'read' scope.",
|
|
1255
|
+
inputSchema: {
|
|
1256
|
+
type: "object",
|
|
1257
|
+
properties: {
|
|
1258
|
+
access_token: { type: "string", description: "Reddit OAuth2 bearer token (scope: read)." },
|
|
1259
|
+
query: { type: "string", description: "Search query string." },
|
|
1260
|
+
subreddit: { type: "string", description: "Restrict search to this subreddit (optional)." },
|
|
1261
|
+
sort: { type: "string", enum: ["relevance", "hot", "top", "new", "comments"], default: "relevance", description: "Result sort order (default: relevance)." },
|
|
1262
|
+
t: { type: "string", enum: ["hour", "day", "week", "month", "year", "all"], description: "Time filter for 'top' sort (optional)." },
|
|
1263
|
+
limit: { type: "number", minimum: 1, maximum: 100, default: 25, description: "Number of results to return (1-100, default 25)." },
|
|
1264
|
+
after: { type: "string", description: "Pagination cursor from a previous response (optional)." },
|
|
1265
|
+
},
|
|
1266
|
+
required: ["access_token", "query"],
|
|
1267
|
+
},
|
|
1268
|
+
},
|
|
1269
|
+
{
|
|
1270
|
+
name: "reddit_user",
|
|
1271
|
+
description: "Get a Reddit user's profile and recent activity (posts and comments). " +
|
|
1272
|
+
"Requires a Reddit OAuth2 bearer token with the 'read' scope.",
|
|
1273
|
+
inputSchema: {
|
|
1274
|
+
type: "object",
|
|
1275
|
+
properties: {
|
|
1276
|
+
access_token: { type: "string", description: "Reddit OAuth2 bearer token (scope: read)." },
|
|
1277
|
+
username: { type: "string", description: "Reddit username, e.g. 'spez' or 'u/spez'." },
|
|
1278
|
+
include_posts: { type: "boolean", default: true, description: "Include recent posts in the response (default true)." },
|
|
1279
|
+
include_comments: { type: "boolean", default: true, description: "Include recent comments in the response (default true)." },
|
|
1280
|
+
limit: { type: "number", minimum: 1, maximum: 100, default: 10, description: "Max recent posts/comments to return per type (default 10)." },
|
|
1281
|
+
},
|
|
1282
|
+
required: ["access_token", "username"],
|
|
1283
|
+
},
|
|
1284
|
+
},
|
|
1285
|
+
{
|
|
1286
|
+
name: "reddit_vote",
|
|
1287
|
+
description: "Upvote, downvote, or remove your vote on a Reddit post or comment. " +
|
|
1288
|
+
"Pass the fullname of the item (t3_ for posts, t1_ for comments). " +
|
|
1289
|
+
"Requires a Reddit OAuth2 bearer token with the 'vote' scope.",
|
|
1290
|
+
inputSchema: {
|
|
1291
|
+
type: "object",
|
|
1292
|
+
properties: {
|
|
1293
|
+
access_token: { type: "string", description: "Reddit OAuth2 bearer token (scope: vote)." },
|
|
1294
|
+
id: { type: "string", description: "Fullname of the post or comment, e.g. 't3_abc123' or 't1_def456'." },
|
|
1295
|
+
dir: { type: "number", enum: [1, 0, -1], description: "Vote direction: 1 = upvote, 0 = remove vote, -1 = downvote." },
|
|
1296
|
+
},
|
|
1297
|
+
required: ["access_token", "id", "dir"],
|
|
1298
|
+
},
|
|
1299
|
+
},
|
|
1300
|
+
{
|
|
1301
|
+
name: "reddit_subscribe",
|
|
1302
|
+
description: "Subscribe to or unsubscribe from a subreddit. " +
|
|
1303
|
+
"Requires a Reddit OAuth2 bearer token with the 'subscribe' scope.",
|
|
1304
|
+
inputSchema: {
|
|
1305
|
+
type: "object",
|
|
1306
|
+
properties: {
|
|
1307
|
+
access_token: { type: "string", description: "Reddit OAuth2 bearer token (scope: subscribe)." },
|
|
1308
|
+
subreddit: { type: "string", description: "Subreddit name, e.g. 'programming' or 'r/programming'." },
|
|
1309
|
+
action: { type: "string", enum: ["sub", "unsub"], description: "'sub' to subscribe, 'unsub' to unsubscribe." },
|
|
1310
|
+
},
|
|
1311
|
+
required: ["access_token", "subreddit", "action"],
|
|
1312
|
+
},
|
|
1313
|
+
},
|
|
1314
|
+
// ── Bluesky / AT Protocol ─────────────────────────────────────────────────
|
|
1315
|
+
{
|
|
1316
|
+
name: "bluesky",
|
|
1317
|
+
description: "Post, read, reply, like, repost, search, and follow on Bluesky social network via the AT Protocol. " +
|
|
1318
|
+
"Authenticates per-call with your Bluesky handle/email and app password. " +
|
|
1319
|
+
"Actions: bluesky_post, bluesky_read_feed, bluesky_reply, bluesky_like, bluesky_repost, " +
|
|
1320
|
+
"bluesky_search, bluesky_profile, bluesky_follow.",
|
|
1321
|
+
inputSchema: {
|
|
1322
|
+
type: "object",
|
|
1323
|
+
properties: {
|
|
1324
|
+
action: {
|
|
1325
|
+
type: "string",
|
|
1326
|
+
enum: ["bluesky_post", "bluesky_read_feed", "bluesky_reply", "bluesky_like", "bluesky_repost", "bluesky_search", "bluesky_profile", "bluesky_follow"],
|
|
1327
|
+
description: "bluesky_post: create a new post (up to 300 chars). " +
|
|
1328
|
+
"bluesky_read_feed: read home timeline or a user's posts. " +
|
|
1329
|
+
"bluesky_reply: reply to an existing post. " +
|
|
1330
|
+
"bluesky_like: like a post by URI. " +
|
|
1331
|
+
"bluesky_repost: repost/share a post by URI. " +
|
|
1332
|
+
"bluesky_search: search posts or users by keyword. " +
|
|
1333
|
+
"bluesky_profile: get a user's profile. " +
|
|
1334
|
+
"bluesky_follow: follow or unfollow a user.",
|
|
1335
|
+
},
|
|
1336
|
+
identifier: { type: "string", description: "Your Bluesky handle (e.g. alice.bsky.social) or email address." },
|
|
1337
|
+
password: { type: "string", description: "Your Bluesky app password. Create one at Settings > Privacy and Security > App Passwords." },
|
|
1338
|
+
text: { type: "string", description: "Post or reply text (max 300 characters). Required for bluesky_post and bluesky_reply." },
|
|
1339
|
+
langs: { type: "array", items: { type: "string" }, description: "BCP-47 language tags for the post, e.g. ['en']. Optional." },
|
|
1340
|
+
feed_type: { type: "string", enum: ["home", "user"], default: "home", description: "bluesky_read_feed: 'home' for your timeline, 'user' for a specific actor's posts." },
|
|
1341
|
+
actor: { type: "string", description: "Handle or DID of the target user. Required for bluesky_profile and bluesky_follow. Optional for bluesky_read_feed." },
|
|
1342
|
+
post_uri: { type: "string", description: "AT URI of the post to like or repost. Required for bluesky_like and bluesky_repost." },
|
|
1343
|
+
parent_uri: { type: "string", description: "AT URI of the post to reply to. Required for bluesky_reply." },
|
|
1344
|
+
root_uri: { type: "string", description: "AT URI of the root post in the thread. Optional for bluesky_reply (defaults to parent_uri)." },
|
|
1345
|
+
query: { type: "string", description: "Search query string. Required for bluesky_search." },
|
|
1346
|
+
type: { type: "string", enum: ["posts", "users"], default: "posts", description: "bluesky_search: search 'posts' (default) or 'users'." },
|
|
1347
|
+
unfollow: { type: "boolean", default: false, description: "bluesky_follow: set true to unfollow instead of follow." },
|
|
1348
|
+
limit: { type: "number", minimum: 1, maximum: 100, default: 20, description: "Max results to return for feed/search operations." },
|
|
1349
|
+
cursor: { type: "string", description: "Pagination cursor from a previous response." },
|
|
1350
|
+
},
|
|
1351
|
+
required: ["action", "identifier", "password"],
|
|
1352
|
+
},
|
|
1353
|
+
},
|
|
1354
|
+
// ── Mastodon (works with any Mastodon-compatible instance) ───────────────
|
|
1355
|
+
{
|
|
1356
|
+
name: "mastodon_post",
|
|
1357
|
+
description: "Create a new toot/status on Mastodon (or any compatible instance: Pleroma, Akkoma, Misskey). Supports visibility settings and content warnings.",
|
|
1358
|
+
inputSchema: {
|
|
1359
|
+
type: "object",
|
|
1360
|
+
properties: {
|
|
1361
|
+
instance_url: { type: "string", description: "Your Mastodon instance URL, e.g. 'mastodon.social'" },
|
|
1362
|
+
access_token: { type: "string", description: "Your Mastodon access token" },
|
|
1363
|
+
status: { type: "string", description: "The text content of the toot" },
|
|
1364
|
+
visibility: { type: "string", enum: ["public", "unlisted", "private", "direct"], default: "public", description: "Who can see this post" },
|
|
1365
|
+
spoiler_text: { type: "string", description: "Content warning text shown before the post body" },
|
|
1366
|
+
sensitive: { type: "boolean", default: false, description: "Mark media as sensitive" },
|
|
1367
|
+
media_ids: { type: "array", items: { type: "string" }, description: "IDs of already-uploaded media attachments to attach (up to 4)" },
|
|
1368
|
+
},
|
|
1369
|
+
required: ["instance_url", "access_token", "status"],
|
|
1370
|
+
},
|
|
1371
|
+
},
|
|
1372
|
+
{
|
|
1373
|
+
name: "mastodon_read_timeline",
|
|
1374
|
+
description: "Read posts from a Mastodon timeline. home = accounts you follow, local = your instance only, public = federated (whole Fediverse).",
|
|
1375
|
+
inputSchema: {
|
|
1376
|
+
type: "object",
|
|
1377
|
+
properties: {
|
|
1378
|
+
instance_url: { type: "string", description: "Your Mastodon instance URL" },
|
|
1379
|
+
access_token: { type: "string", description: "Your Mastodon access token" },
|
|
1380
|
+
timeline: { type: "string", enum: ["home", "local", "public"], default: "home", description: "Which timeline to read" },
|
|
1381
|
+
limit: { type: "number", minimum: 1, maximum: 40, default: 20, description: "Number of posts to return" },
|
|
1382
|
+
max_id: { type: "string", description: "Return posts older than this status ID (for pagination)" },
|
|
1383
|
+
},
|
|
1384
|
+
required: ["instance_url", "access_token"],
|
|
1385
|
+
},
|
|
1386
|
+
},
|
|
1387
|
+
{
|
|
1388
|
+
name: "mastodon_reply",
|
|
1389
|
+
description: "Reply to an existing Mastodon status.",
|
|
1390
|
+
inputSchema: {
|
|
1391
|
+
type: "object",
|
|
1392
|
+
properties: {
|
|
1393
|
+
instance_url: { type: "string", description: "Your Mastodon instance URL" },
|
|
1394
|
+
access_token: { type: "string", description: "Your Mastodon access token" },
|
|
1395
|
+
in_reply_to_id: { type: "string", description: "ID of the status to reply to" },
|
|
1396
|
+
status: { type: "string", description: "Text content of your reply" },
|
|
1397
|
+
visibility: { type: "string", enum: ["public", "unlisted", "private", "direct"], description: "Visibility of the reply (defaults to same as the original post)" },
|
|
1398
|
+
spoiler_text: { type: "string", description: "Optional content warning text" },
|
|
1399
|
+
},
|
|
1400
|
+
required: ["instance_url", "access_token", "in_reply_to_id", "status"],
|
|
1401
|
+
},
|
|
1402
|
+
},
|
|
1403
|
+
{
|
|
1404
|
+
name: "mastodon_boost",
|
|
1405
|
+
description: "Boost (reblog) a Mastodon status, or undo a boost.",
|
|
1406
|
+
inputSchema: {
|
|
1407
|
+
type: "object",
|
|
1408
|
+
properties: {
|
|
1409
|
+
instance_url: { type: "string", description: "Your Mastodon instance URL" },
|
|
1410
|
+
access_token: { type: "string", description: "Your Mastodon access token" },
|
|
1411
|
+
status_id: { type: "string", description: "ID of the status to boost" },
|
|
1412
|
+
unboost: { type: "boolean", default: false, description: "If true, removes the boost instead" },
|
|
1413
|
+
},
|
|
1414
|
+
required: ["instance_url", "access_token", "status_id"],
|
|
1415
|
+
},
|
|
1416
|
+
},
|
|
1417
|
+
{
|
|
1418
|
+
name: "mastodon_favorite",
|
|
1419
|
+
description: "Favorite (like) a Mastodon status, or remove a favorite.",
|
|
1420
|
+
inputSchema: {
|
|
1421
|
+
type: "object",
|
|
1422
|
+
properties: {
|
|
1423
|
+
instance_url: { type: "string", description: "Your Mastodon instance URL" },
|
|
1424
|
+
access_token: { type: "string", description: "Your Mastodon access token" },
|
|
1425
|
+
status_id: { type: "string", description: "ID of the status to favorite" },
|
|
1426
|
+
unfavorite: { type: "boolean", default: false, description: "If true, removes the favorite instead" },
|
|
1427
|
+
},
|
|
1428
|
+
required: ["instance_url", "access_token", "status_id"],
|
|
1429
|
+
},
|
|
1430
|
+
},
|
|
1431
|
+
{
|
|
1432
|
+
name: "mastodon_search",
|
|
1433
|
+
description: "Search Mastodon for posts, accounts, or hashtags. Omit 'type' to search all three categories at once.",
|
|
1434
|
+
inputSchema: {
|
|
1435
|
+
type: "object",
|
|
1436
|
+
properties: {
|
|
1437
|
+
instance_url: { type: "string", description: "Your Mastodon instance URL" },
|
|
1438
|
+
access_token: { type: "string", description: "Your Mastodon access token" },
|
|
1439
|
+
query: { type: "string", description: "Search term, hashtag, or account handle" },
|
|
1440
|
+
type: { type: "string", enum: ["accounts", "statuses", "hashtags"], description: "Limit results to one category (optional)" },
|
|
1441
|
+
limit: { type: "number", minimum: 1, maximum: 40, default: 20 },
|
|
1442
|
+
resolve: { type: "boolean", default: true, description: "Resolve remote accounts/posts via WebFinger" },
|
|
1443
|
+
},
|
|
1444
|
+
required: ["instance_url", "access_token", "query"],
|
|
1445
|
+
},
|
|
1446
|
+
},
|
|
1447
|
+
{
|
|
1448
|
+
name: "mastodon_profile",
|
|
1449
|
+
description: "Get account information for a Mastodon user. Omit account_id and acct to return your own profile.",
|
|
1450
|
+
inputSchema: {
|
|
1451
|
+
type: "object",
|
|
1452
|
+
properties: {
|
|
1453
|
+
instance_url: { type: "string", description: "Your Mastodon instance URL" },
|
|
1454
|
+
access_token: { type: "string", description: "Your Mastodon access token" },
|
|
1455
|
+
account_id: { type: "string", description: "Numeric account ID (takes priority over acct)" },
|
|
1456
|
+
acct: { type: "string", description: "Account handle, e.g. 'user@mastodon.social' or just 'user'" },
|
|
1457
|
+
},
|
|
1458
|
+
required: ["instance_url", "access_token"],
|
|
1459
|
+
},
|
|
1460
|
+
},
|
|
1461
|
+
{
|
|
1462
|
+
name: "mastodon_follow",
|
|
1463
|
+
description: "Follow or unfollow a Mastodon account. Returns the updated relationship.",
|
|
1464
|
+
inputSchema: {
|
|
1465
|
+
type: "object",
|
|
1466
|
+
properties: {
|
|
1467
|
+
instance_url: { type: "string", description: "Your Mastodon instance URL" },
|
|
1468
|
+
access_token: { type: "string", description: "Your Mastodon access token" },
|
|
1469
|
+
account_id: { type: "string", description: "Numeric ID of the account to follow/unfollow" },
|
|
1470
|
+
unfollow: { type: "boolean", default: false, description: "If true, unfollows instead" },
|
|
1471
|
+
},
|
|
1472
|
+
required: ["instance_url", "access_token", "account_id"],
|
|
1473
|
+
},
|
|
1474
|
+
},
|
|
1475
|
+
{
|
|
1476
|
+
name: "mastodon_notifications",
|
|
1477
|
+
description: "Read your Mastodon notifications. Notification types: mention, status, reblog, follow, follow_request, favourite, poll, update.",
|
|
1478
|
+
inputSchema: {
|
|
1479
|
+
type: "object",
|
|
1480
|
+
properties: {
|
|
1481
|
+
instance_url: { type: "string", description: "Your Mastodon instance URL" },
|
|
1482
|
+
access_token: { type: "string", description: "Your Mastodon access token" },
|
|
1483
|
+
limit: { type: "number", minimum: 1, maximum: 30, default: 15, description: "Number of notifications to return" },
|
|
1484
|
+
max_id: { type: "string", description: "Return notifications older than this ID (for pagination)" },
|
|
1485
|
+
types: { type: "array", items: { type: "string", enum: ["mention", "status", "reblog", "follow", "follow_request", "favourite", "poll", "update"] }, description: "Filter to specific notification types (optional)" },
|
|
1486
|
+
},
|
|
1487
|
+
required: ["instance_url", "access_token"],
|
|
1488
|
+
},
|
|
1489
|
+
},
|
|
1490
|
+
// ── Amazon Product Advertising API 5.0 ───────────────────────────────────
|
|
1491
|
+
{
|
|
1492
|
+
name: "amazon_search",
|
|
1493
|
+
description: "Search Amazon products by keyword, category (SearchIndex), browse node, or any combination. " +
|
|
1494
|
+
"Uses Amazon PA-API 5.0 SearchItems endpoint. Returns titles, prices, images, ratings, and direct product URLs. " +
|
|
1495
|
+
"Requires an Amazon Associates account: Access Key, Secret Key, and Partner Tag.",
|
|
1496
|
+
inputSchema: {
|
|
1497
|
+
type: "object",
|
|
1498
|
+
properties: {
|
|
1499
|
+
access_key: { type: "string", description: "Amazon PA-API access key ID" },
|
|
1500
|
+
secret_key: { type: "string", description: "Amazon PA-API secret access key" },
|
|
1501
|
+
partner_tag: { type: "string", description: "Amazon Associates partner/tracking tag (e.g. mytag-20)" },
|
|
1502
|
+
keywords: { type: "string", description: "Search keywords (e.g. 'wireless headphones')" },
|
|
1503
|
+
search_index: { type: "string", description: "Product category / SearchIndex (e.g. Electronics, Books, Apparel, All). Defaults to All." },
|
|
1504
|
+
browse_node_id: { type: "string", description: "Amazon browse node ID to filter results" },
|
|
1505
|
+
sort_by: { type: "string", enum: ["AvgCustomerReviews", "Featured", "NewestArrivals", "Price:HighToLow", "Price:LowToHigh", "Relevance"], description: "Sort order for results" },
|
|
1506
|
+
min_price: { type: "number", description: "Minimum price in cents (e.g. 1000 = $10.00)" },
|
|
1507
|
+
max_price: { type: "number", description: "Maximum price in cents (e.g. 5000 = $50.00)" },
|
|
1508
|
+
item_count: { type: "number", description: "Number of results to return (1-10, default 10)", default: 10 },
|
|
1509
|
+
item_page: { type: "number", description: "Results page number (1-10)", default: 1 },
|
|
1510
|
+
marketplace: { type: "string", enum: ["US", "CA", "MX", "BR", "UK", "DE", "FR", "IT", "ES", "NL", "SE", "PL", "BE", "IN", "JP", "AU", "SG", "AE", "SA", "TR"], default: "US", description: "Amazon marketplace country code (default: US)" },
|
|
1511
|
+
},
|
|
1512
|
+
required: ["access_key", "secret_key", "partner_tag"],
|
|
1513
|
+
},
|
|
1514
|
+
},
|
|
1515
|
+
{
|
|
1516
|
+
name: "amazon_product",
|
|
1517
|
+
description: "Get detailed product information for one or more Amazon products by ASIN. " +
|
|
1518
|
+
"Uses Amazon PA-API 5.0 GetItems endpoint. Returns full details: title, price, features, images, ratings, brand, availability. " +
|
|
1519
|
+
"Requires an Amazon Associates account.",
|
|
1520
|
+
inputSchema: {
|
|
1521
|
+
type: "object",
|
|
1522
|
+
properties: {
|
|
1523
|
+
access_key: { type: "string", description: "Amazon PA-API access key ID" },
|
|
1524
|
+
secret_key: { type: "string", description: "Amazon PA-API secret access key" },
|
|
1525
|
+
partner_tag: { type: "string", description: "Amazon Associates partner/tracking tag" },
|
|
1526
|
+
asin: { type: "string", description: "Single product ASIN (e.g. B08N5WRWNW)" },
|
|
1527
|
+
asins: { type: "array", items: { type: "string" }, description: "Array of ASINs for batch lookup (up to 10)" },
|
|
1528
|
+
marketplace: { type: "string", enum: ["US", "CA", "MX", "BR", "UK", "DE", "FR", "IT", "ES", "NL", "SE", "PL", "BE", "IN", "JP", "AU", "SG", "AE", "SA", "TR"], default: "US", description: "Amazon marketplace country code (default: US)" },
|
|
1529
|
+
},
|
|
1530
|
+
required: ["access_key", "secret_key", "partner_tag"],
|
|
1531
|
+
},
|
|
1532
|
+
},
|
|
1533
|
+
{
|
|
1534
|
+
name: "amazon_browse",
|
|
1535
|
+
description: "Browse Amazon product categories by browse node ID. " +
|
|
1536
|
+
"Uses Amazon PA-API 5.0 GetBrowseNodes endpoint. Returns node name, parent ancestor, and child subcategories.",
|
|
1537
|
+
inputSchema: {
|
|
1538
|
+
type: "object",
|
|
1539
|
+
properties: {
|
|
1540
|
+
access_key: { type: "string", description: "Amazon PA-API access key ID" },
|
|
1541
|
+
secret_key: { type: "string", description: "Amazon PA-API secret access key" },
|
|
1542
|
+
partner_tag: { type: "string", description: "Amazon Associates partner/tracking tag" },
|
|
1543
|
+
browse_node_id: { type: "string", description: "Single browse node ID (e.g. 172282 for Electronics)" },
|
|
1544
|
+
browse_node_ids: { type: "array", items: { type: "string" }, description: "Array of browse node IDs for batch lookup" },
|
|
1545
|
+
marketplace: { type: "string", enum: ["US", "CA", "MX", "BR", "UK", "DE", "FR", "IT", "ES", "NL", "SE", "PL", "BE", "IN", "JP", "AU", "SG", "AE", "SA", "TR"], default: "US", description: "Amazon marketplace country code (default: US)" },
|
|
1546
|
+
},
|
|
1547
|
+
required: ["access_key", "secret_key", "partner_tag"],
|
|
1548
|
+
},
|
|
1549
|
+
},
|
|
1550
|
+
{
|
|
1551
|
+
name: "amazon_variations",
|
|
1552
|
+
description: "Get all product variations for an Amazon parent ASIN (colors, sizes, styles, etc.). " +
|
|
1553
|
+
"Uses Amazon PA-API 5.0 GetVariations endpoint. Returns each variation with its own ASIN, price, and images.",
|
|
1554
|
+
inputSchema: {
|
|
1555
|
+
type: "object",
|
|
1556
|
+
properties: {
|
|
1557
|
+
access_key: { type: "string", description: "Amazon PA-API access key ID" },
|
|
1558
|
+
secret_key: { type: "string", description: "Amazon PA-API secret access key" },
|
|
1559
|
+
partner_tag: { type: "string", description: "Amazon Associates partner/tracking tag" },
|
|
1560
|
+
asin: { type: "string", description: "Parent product ASIN whose variations to retrieve" },
|
|
1561
|
+
variation_count: { type: "number", description: "Number of variations to return (1-10, default 10)", default: 10 },
|
|
1562
|
+
variation_page: { type: "number", description: "Variations page number (1-10)", default: 1 },
|
|
1563
|
+
marketplace: { type: "string", enum: ["US", "CA", "MX", "BR", "UK", "DE", "FR", "IT", "ES", "NL", "SE", "PL", "BE", "IN", "JP", "AU", "SG", "AE", "SA", "TR"], default: "US", description: "Amazon marketplace country code (default: US)" },
|
|
1564
|
+
},
|
|
1565
|
+
required: ["access_key", "secret_key", "partner_tag", "asin"],
|
|
1566
|
+
},
|
|
1567
|
+
},
|
|
1568
|
+
// ── Xero accounting (OAuth 2.0, Xero API v2) ─────────────────────────────
|
|
1569
|
+
{
|
|
1570
|
+
name: "xero_invoices",
|
|
1571
|
+
description: "List, get, or create invoices in Xero. Requires a valid OAuth 2.0 access_token and your Xero tenant_id.",
|
|
1572
|
+
inputSchema: {
|
|
1573
|
+
type: "object",
|
|
1574
|
+
properties: {
|
|
1575
|
+
access_token: { type: "string", description: "Xero OAuth 2.0 Bearer token" },
|
|
1576
|
+
tenant_id: { type: "string", description: "Xero organisation tenant ID" },
|
|
1577
|
+
action: { type: "string", enum: ["list", "get", "create"], default: "list", description: "Operation to perform" },
|
|
1578
|
+
invoice_id: { type: "string", description: "Invoice ID or number (required for action='get')" },
|
|
1579
|
+
body: { type: "object", description: "Invoice object to create (required for action='create'). Must include Type, Contact.ContactID, and LineItems." },
|
|
1580
|
+
where: { type: "string", description: "OData-style filter, e.g. \"Status==\"DRAFT\"\"" },
|
|
1581
|
+
order: { type: "string", description: "Sort field, e.g. \"DueDate DESC\"" },
|
|
1582
|
+
page: { type: "number", description: "Page number (100 records per page)" },
|
|
1583
|
+
page_size: { type: "number", description: "Records per page (max 1000)" },
|
|
1584
|
+
statuses: { type: "string", description: "Comma-separated statuses, e.g. \"DRAFT,SUBMITTED\"" },
|
|
1585
|
+
contact_ids: { type: "string", description: "Comma-separated ContactIDs to filter by" },
|
|
1586
|
+
ids: { type: "string", description: "Comma-separated InvoiceIDs to fetch" },
|
|
1587
|
+
},
|
|
1588
|
+
required: ["access_token", "tenant_id"],
|
|
1589
|
+
},
|
|
1590
|
+
},
|
|
1591
|
+
{
|
|
1592
|
+
name: "xero_contacts",
|
|
1593
|
+
description: "List, get, or create contacts (customers and suppliers) in Xero. Requires access_token and tenant_id.",
|
|
1594
|
+
inputSchema: {
|
|
1595
|
+
type: "object",
|
|
1596
|
+
properties: {
|
|
1597
|
+
access_token: { type: "string", description: "Xero OAuth 2.0 Bearer token" },
|
|
1598
|
+
tenant_id: { type: "string", description: "Xero organisation tenant ID" },
|
|
1599
|
+
action: { type: "string", enum: ["list", "get", "create"], default: "list" },
|
|
1600
|
+
contact_id: { type: "string", description: "Contact ID (required for action='get')" },
|
|
1601
|
+
body: { type: "object", description: "Contact object to create (required for action='create'). Must include Name." },
|
|
1602
|
+
where: { type: "string", description: "OData-style filter" },
|
|
1603
|
+
order: { type: "string", description: "Sort field" },
|
|
1604
|
+
page: { type: "number", description: "Page number" },
|
|
1605
|
+
page_size: { type: "number", description: "Records per page" },
|
|
1606
|
+
search_term: { type: "string", description: "Search contacts by name, email, or account number" },
|
|
1607
|
+
ids: { type: "string", description: "Comma-separated ContactIDs to fetch" },
|
|
1608
|
+
include_archived: { type: "boolean", description: "Include archived contacts (default false)" },
|
|
1609
|
+
},
|
|
1610
|
+
required: ["access_token", "tenant_id"],
|
|
1611
|
+
},
|
|
1612
|
+
},
|
|
1613
|
+
{
|
|
1614
|
+
name: "xero_accounts",
|
|
1615
|
+
description: "List the chart of accounts for a Xero organisation. Returns all accounts with their codes, types, and balances.",
|
|
1616
|
+
inputSchema: {
|
|
1617
|
+
type: "object",
|
|
1618
|
+
properties: {
|
|
1619
|
+
access_token: { type: "string", description: "Xero OAuth 2.0 Bearer token" },
|
|
1620
|
+
tenant_id: { type: "string", description: "Xero organisation tenant ID" },
|
|
1621
|
+
where: { type: "string", description: "OData-style filter, e.g. \"Type==\"BANK\"\"" },
|
|
1622
|
+
order: { type: "string", description: "Sort field, e.g. \"Code ASC\"" },
|
|
1623
|
+
},
|
|
1624
|
+
required: ["access_token", "tenant_id"],
|
|
1625
|
+
},
|
|
1626
|
+
},
|
|
1627
|
+
{
|
|
1628
|
+
name: "xero_payments",
|
|
1629
|
+
description: "List or create payments in Xero. action='list' returns payments; action='create' records a payment against an invoice.",
|
|
1630
|
+
inputSchema: {
|
|
1631
|
+
type: "object",
|
|
1632
|
+
properties: {
|
|
1633
|
+
access_token: { type: "string", description: "Xero OAuth 2.0 Bearer token" },
|
|
1634
|
+
tenant_id: { type: "string", description: "Xero organisation tenant ID" },
|
|
1635
|
+
action: { type: "string", enum: ["list", "create"], default: "list" },
|
|
1636
|
+
body: { type: "object", description: "Payment object (required for action='create'). Must include Invoice.InvoiceID, Account.AccountID, Date, and Amount." },
|
|
1637
|
+
where: { type: "string", description: "OData-style filter" },
|
|
1638
|
+
order: { type: "string", description: "Sort field" },
|
|
1639
|
+
page: { type: "number", description: "Page number" },
|
|
1640
|
+
},
|
|
1641
|
+
required: ["access_token", "tenant_id"],
|
|
1642
|
+
},
|
|
1643
|
+
},
|
|
1644
|
+
{
|
|
1645
|
+
name: "xero_bank_transactions",
|
|
1646
|
+
description: "List bank transactions for a Xero organisation. Returns spend and receive money transactions from bank accounts.",
|
|
1647
|
+
inputSchema: {
|
|
1648
|
+
type: "object",
|
|
1649
|
+
properties: {
|
|
1650
|
+
access_token: { type: "string", description: "Xero OAuth 2.0 Bearer token" },
|
|
1651
|
+
tenant_id: { type: "string", description: "Xero organisation tenant ID" },
|
|
1652
|
+
where: { type: "string", description: "OData-style filter" },
|
|
1653
|
+
order: { type: "string", description: "Sort field" },
|
|
1654
|
+
page: { type: "number", description: "Page number (100 per page)" },
|
|
1655
|
+
},
|
|
1656
|
+
required: ["access_token", "tenant_id"],
|
|
1657
|
+
},
|
|
1658
|
+
},
|
|
1659
|
+
{
|
|
1660
|
+
name: "xero_reports",
|
|
1661
|
+
description: "Retrieve financial reports from Xero. " +
|
|
1662
|
+
"Common report IDs: ProfitAndLoss, BalanceSheet, CashSummary, ExecutiveSummary, TrialBalance, BankSummary, AgedReceivablesByContact, AgedPayablesByContact.",
|
|
1663
|
+
inputSchema: {
|
|
1664
|
+
type: "object",
|
|
1665
|
+
properties: {
|
|
1666
|
+
access_token: { type: "string", description: "Xero OAuth 2.0 Bearer token" },
|
|
1667
|
+
tenant_id: { type: "string", description: "Xero organisation tenant ID" },
|
|
1668
|
+
report_id: { type: "string", description: "Report identifier. Options: ProfitAndLoss, BalanceSheet, CashSummary, ExecutiveSummary, TrialBalance, BankSummary, AgedReceivablesByContact, AgedPayablesByContact." },
|
|
1669
|
+
from_date: { type: "string", description: "Report start date, e.g. '2024-01-01'" },
|
|
1670
|
+
to_date: { type: "string", description: "Report end date, e.g. '2024-12-31'" },
|
|
1671
|
+
date: { type: "string", description: "As-at date for balance sheet reports" },
|
|
1672
|
+
periods: { type: "number", description: "Number of periods to compare" },
|
|
1673
|
+
timeframe: { type: "string", description: "Comparison period: MONTH, QUARTER, YEAR" },
|
|
1674
|
+
contact_id: { type: "string", description: "ContactID for aged receivables/payables reports" },
|
|
1675
|
+
account_id: { type: "string", description: "AccountID to filter by" },
|
|
1676
|
+
},
|
|
1677
|
+
required: ["access_token", "tenant_id", "report_id"],
|
|
1678
|
+
},
|
|
1679
|
+
},
|
|
1680
|
+
{
|
|
1681
|
+
name: "xero_quotes",
|
|
1682
|
+
description: "List, get, or create quotes (sales quotes) in Xero. Requires access_token and tenant_id.",
|
|
1683
|
+
inputSchema: {
|
|
1684
|
+
type: "object",
|
|
1685
|
+
properties: {
|
|
1686
|
+
access_token: { type: "string", description: "Xero OAuth 2.0 Bearer token" },
|
|
1687
|
+
tenant_id: { type: "string", description: "Xero organisation tenant ID" },
|
|
1688
|
+
action: { type: "string", enum: ["list", "get", "create"], default: "list" },
|
|
1689
|
+
quote_id: { type: "string", description: "Quote ID (required for action='get')" },
|
|
1690
|
+
body: { type: "object", description: "Quote object to create (required for action='create'). Must include Contact.ContactID and LineItems." },
|
|
1691
|
+
status: { type: "string", description: "Filter by status: DRAFT, SENT, DECLINED, ACCEPTED, INVOICED, DELETED" },
|
|
1692
|
+
contact_id: { type: "string", description: "Filter by ContactID" },
|
|
1693
|
+
date_from: { type: "string", description: "Filter quotes from this date, e.g. '2024-01-01'" },
|
|
1694
|
+
date_to: { type: "string", description: "Filter quotes to this date, e.g. '2024-12-31'" },
|
|
1695
|
+
page: { type: "number", description: "Page number" },
|
|
1696
|
+
},
|
|
1697
|
+
required: ["access_token", "tenant_id"],
|
|
1698
|
+
},
|
|
1699
|
+
},
|
|
1700
|
+
{
|
|
1701
|
+
name: "xero_organisation",
|
|
1702
|
+
description: "Get organisation and tenant information from Xero. Returns legal name, organisation type, base currency, country, financial year end, and subscription status.",
|
|
1703
|
+
inputSchema: {
|
|
1704
|
+
type: "object",
|
|
1705
|
+
properties: {
|
|
1706
|
+
access_token: { type: "string", description: "Xero OAuth 2.0 Bearer token" },
|
|
1707
|
+
tenant_id: { type: "string", description: "Xero organisation tenant ID" },
|
|
1708
|
+
},
|
|
1709
|
+
required: ["access_token", "tenant_id"],
|
|
1710
|
+
},
|
|
1711
|
+
},
|
|
1712
|
+
// ── Shopify Admin API ─────────────────────────────────────────────────────
|
|
1713
|
+
{
|
|
1714
|
+
name: "shopify_products",
|
|
1715
|
+
description: "Manage Shopify products via the Admin REST API. List all products, get a single product by ID, create a new product, or update an existing one.",
|
|
1716
|
+
inputSchema: {
|
|
1717
|
+
type: "object",
|
|
1718
|
+
properties: {
|
|
1719
|
+
store: { type: "string", description: "Shopify store domain, e.g. 'mystore' or 'mystore.myshopify.com'." },
|
|
1720
|
+
access_token: { type: "string", description: "Shopify Admin API access token (shpat_...)." },
|
|
1721
|
+
action: { type: "string", enum: ["list", "get", "create", "update"], default: "list", description: "list: all products. get: one by ID. create: new product. update: edit existing." },
|
|
1722
|
+
id: { type: "string", description: "Product ID (required for get and update)." },
|
|
1723
|
+
product: { type: "object", description: "Product data for create or update. Keys: title, body_html, vendor, product_type, tags, variants, images, status." },
|
|
1724
|
+
limit: { type: "number", minimum: 1, maximum: 250, default: 50 },
|
|
1725
|
+
page_info: { type: "string", description: "Cursor for paginating through results (from _pagination.next)." },
|
|
1726
|
+
status: { type: "string", enum: ["active", "archived", "draft"], description: "Filter by product status (list only)." },
|
|
1727
|
+
vendor: { type: "string", description: "Filter by vendor (list only)." },
|
|
1728
|
+
product_type: { type: "string", description: "Filter by product type (list only)." },
|
|
1729
|
+
published_status: { type: "string", enum: ["published", "unpublished", "any"], description: "Filter by published status (list only)." },
|
|
1730
|
+
title: { type: "string", description: "Filter by title (list only)." },
|
|
1731
|
+
since_id: { type: "string", description: "Return only products after this ID (list only)." },
|
|
1732
|
+
fields: { type: "string", description: "Comma-separated list of fields to return." },
|
|
1733
|
+
},
|
|
1734
|
+
required: ["store", "access_token"],
|
|
1735
|
+
},
|
|
1736
|
+
},
|
|
1737
|
+
{
|
|
1738
|
+
name: "shopify_orders",
|
|
1739
|
+
description: "List or retrieve Shopify orders via the Admin REST API. Filter by status, financial status, fulfillment status, and date ranges.",
|
|
1740
|
+
inputSchema: {
|
|
1741
|
+
type: "object",
|
|
1742
|
+
properties: {
|
|
1743
|
+
store: { type: "string", description: "Shopify store domain." },
|
|
1744
|
+
access_token: { type: "string", description: "Shopify Admin API access token." },
|
|
1745
|
+
action: { type: "string", enum: ["list", "get"], default: "list", description: "list: all orders. get: one order by ID." },
|
|
1746
|
+
id: { type: "string", description: "Order ID (required for get)." },
|
|
1747
|
+
limit: { type: "number", minimum: 1, maximum: 250, default: 50 },
|
|
1748
|
+
status: { type: "string", enum: ["open", "closed", "cancelled", "any"], default: "any" },
|
|
1749
|
+
financial_status: { type: "string", enum: ["authorized", "pending", "paid", "partially_paid", "refunded", "voided", "partially_refunded", "any"] },
|
|
1750
|
+
fulfillment_status: { type: "string", enum: ["shipped", "partial", "unshipped", "unfulfilled", "any"] },
|
|
1751
|
+
since_id: { type: "string" },
|
|
1752
|
+
created_at_min: { type: "string", description: "ISO 8601 datetime, e.g. '2024-01-01T00:00:00Z'." },
|
|
1753
|
+
created_at_max: { type: "string", description: "ISO 8601 datetime." },
|
|
1754
|
+
page_info: { type: "string", description: "Cursor for next/previous page." },
|
|
1755
|
+
fields: { type: "string", description: "Comma-separated fields to return." },
|
|
1756
|
+
},
|
|
1757
|
+
required: ["store", "access_token"],
|
|
1758
|
+
},
|
|
1759
|
+
},
|
|
1760
|
+
{
|
|
1761
|
+
name: "shopify_customers",
|
|
1762
|
+
description: "List, retrieve, or search Shopify customers via the Admin REST API.",
|
|
1763
|
+
inputSchema: {
|
|
1764
|
+
type: "object",
|
|
1765
|
+
properties: {
|
|
1766
|
+
store: { type: "string", description: "Shopify store domain." },
|
|
1767
|
+
access_token: { type: "string", description: "Shopify Admin API access token." },
|
|
1768
|
+
action: { type: "string", enum: ["list", "get", "search"], default: "list", description: "list: all customers. get: one by ID. search: query by email/name/phone." },
|
|
1769
|
+
id: { type: "string", description: "Customer ID (required for get)." },
|
|
1770
|
+
query: { type: "string", description: "Search query (required for search), e.g. 'email:foo@bar.com' or 'Bob'." },
|
|
1771
|
+
limit: { type: "number", minimum: 1, maximum: 250, default: 50 },
|
|
1772
|
+
since_id: { type: "string" },
|
|
1773
|
+
created_at_min: { type: "string" },
|
|
1774
|
+
created_at_max: { type: "string" },
|
|
1775
|
+
updated_at_min: { type: "string" },
|
|
1776
|
+
page_info: { type: "string" },
|
|
1777
|
+
fields: { type: "string" },
|
|
1778
|
+
},
|
|
1779
|
+
required: ["store", "access_token"],
|
|
1780
|
+
},
|
|
1781
|
+
},
|
|
1782
|
+
{
|
|
1783
|
+
name: "shopify_inventory",
|
|
1784
|
+
description: "Get inventory levels for specific items or locations from Shopify. At least one of inventory_item_ids or location_ids must be provided.",
|
|
1785
|
+
inputSchema: {
|
|
1786
|
+
type: "object",
|
|
1787
|
+
properties: {
|
|
1788
|
+
store: { type: "string", description: "Shopify store domain." },
|
|
1789
|
+
access_token: { type: "string", description: "Shopify Admin API access token." },
|
|
1790
|
+
inventory_item_ids: { description: "One or more inventory item IDs (string or array of strings)." },
|
|
1791
|
+
location_ids: { description: "One or more location IDs (string or array of strings)." },
|
|
1792
|
+
limit: { type: "number", minimum: 1, maximum: 250, default: 50 },
|
|
1793
|
+
},
|
|
1794
|
+
required: ["store", "access_token"],
|
|
1795
|
+
},
|
|
1796
|
+
},
|
|
1797
|
+
{
|
|
1798
|
+
name: "shopify_collections",
|
|
1799
|
+
description: "List Shopify collections (custom and/or smart) via the Admin REST API.",
|
|
1800
|
+
inputSchema: {
|
|
1801
|
+
type: "object",
|
|
1802
|
+
properties: {
|
|
1803
|
+
store: { type: "string", description: "Shopify store domain." },
|
|
1804
|
+
access_token: { type: "string", description: "Shopify Admin API access token." },
|
|
1805
|
+
type: { type: "string", enum: ["all", "custom", "smart"], default: "all", description: "Which collection type to fetch." },
|
|
1806
|
+
limit: { type: "number", minimum: 1, maximum: 250, default: 50 },
|
|
1807
|
+
since_id: { type: "string" },
|
|
1808
|
+
title: { type: "string", description: "Filter by collection title." },
|
|
1809
|
+
fields: { type: "string" },
|
|
1810
|
+
},
|
|
1811
|
+
required: ["store", "access_token"],
|
|
1812
|
+
},
|
|
1813
|
+
},
|
|
1814
|
+
{
|
|
1815
|
+
name: "shopify_shop",
|
|
1816
|
+
description: "Get store information for a Shopify shop: name, email, domain, currency, timezone, plan, and more.",
|
|
1817
|
+
inputSchema: {
|
|
1818
|
+
type: "object",
|
|
1819
|
+
properties: {
|
|
1820
|
+
store: { type: "string", description: "Shopify store domain." },
|
|
1821
|
+
access_token: { type: "string", description: "Shopify Admin API access token." },
|
|
1822
|
+
},
|
|
1823
|
+
required: ["store", "access_token"],
|
|
1824
|
+
},
|
|
1825
|
+
},
|
|
1826
|
+
{
|
|
1827
|
+
name: "shopify_fulfillments",
|
|
1828
|
+
description: "List or create fulfillments for a Shopify order via the Admin REST API.",
|
|
1829
|
+
inputSchema: {
|
|
1830
|
+
type: "object",
|
|
1831
|
+
properties: {
|
|
1832
|
+
store: { type: "string", description: "Shopify store domain." },
|
|
1833
|
+
access_token: { type: "string", description: "Shopify Admin API access token." },
|
|
1834
|
+
order_id: { type: "string", description: "The order ID to list or create fulfillments for." },
|
|
1835
|
+
action: { type: "string", enum: ["list", "create"], default: "list", description: "list: all fulfillments for the order. create: create a new fulfillment." },
|
|
1836
|
+
fulfillment: { type: "object", description: "Fulfillment data for action='create'. Keys: location_id (required), tracking_number, tracking_company, tracking_url, notify_customer, line_items_by_fulfillment_order." },
|
|
1837
|
+
limit: { type: "number", minimum: 1, maximum: 250, default: 50 },
|
|
1838
|
+
since_id: { type: "string" },
|
|
1839
|
+
fields: { type: "string" },
|
|
1840
|
+
},
|
|
1841
|
+
required: ["store", "access_token", "order_id"],
|
|
1842
|
+
},
|
|
1843
|
+
},
|
|
1844
|
+
];
|
|
1845
|
+
const DIRECT_HANDLERS = {
|
|
1846
|
+
unclick_shorten_url: (c, a) => c.call("POST", "/v1/shorten", a),
|
|
1847
|
+
unclick_generate_qr: (c, a) => c.call("POST", "/v1/qr", a),
|
|
1848
|
+
unclick_hash: (c, a) => c.call("POST", "/v1/hash", a),
|
|
1849
|
+
unclick_transform_text: (c, a) => c.call("POST", "/v1/transform/case", a),
|
|
1850
|
+
unclick_validate_email: (c, a) => c.call("POST", "/v1/validate/email", a),
|
|
1851
|
+
unclick_validate_url: (c, a) => c.call("POST", "/v1/validate/url", a),
|
|
1852
|
+
unclick_resize_image: (c, a) => c.call("POST", "/v1/image/resize", a),
|
|
1853
|
+
unclick_parse_csv: (c, a) => c.call("POST", "/v1/csv/parse", a),
|
|
1854
|
+
unclick_encode: async (c, a) => {
|
|
1855
|
+
const op = a.operation;
|
|
1856
|
+
const [action, format] = op.split("_");
|
|
1857
|
+
const path = `/${action}/${format}`.replace("_", "/");
|
|
1858
|
+
return c.call("POST", `/v1${path}`, { text: a.text });
|
|
1859
|
+
},
|
|
1860
|
+
unclick_generate_uuid: (c, a) => c.call("POST", "/v1/uuid/v4", a),
|
|
1861
|
+
unclick_random_password: (c, a) => c.call("POST", "/v1/random/password", a),
|
|
1862
|
+
unclick_cron_parse: async (c, a) => {
|
|
1863
|
+
const [parsed, next] = await Promise.all([
|
|
1864
|
+
c.call("POST", "/v1/cron/parse", { expression: a.expression }),
|
|
1865
|
+
c.call("POST", "/v1/cron/next", {
|
|
1866
|
+
expression: a.expression,
|
|
1867
|
+
count: a.next_count ?? 5,
|
|
1868
|
+
}),
|
|
1869
|
+
]);
|
|
1870
|
+
return { ...parsed, ...next };
|
|
1871
|
+
},
|
|
1872
|
+
unclick_ip_parse: (c, a) => c.call("POST", "/v1/ip/parse", a),
|
|
1873
|
+
unclick_color_convert: (c, a) => c.call("POST", "/v1/color/convert", a),
|
|
1874
|
+
unclick_regex_test: (c, a) => c.call("POST", "/v1/regex/test", a),
|
|
1875
|
+
unclick_timestamp_convert: (c, a) => c.call("POST", "/v1/timestamp/convert", a),
|
|
1876
|
+
unclick_diff_text: (c, a) => c.call("POST", "/v1/diff/lines", a),
|
|
1877
|
+
unclick_kv_set: (c, a) => c.call("POST", "/v1/kv/set", a),
|
|
1878
|
+
unclick_kv_get: (c, a) => c.call("POST", "/v1/kv/get", a),
|
|
1879
|
+
report_bug: (c, a) => c.call("POST", "/v1/report-bug", a),
|
|
1880
|
+
csuite_analyze: async (_c, a) => {
|
|
1881
|
+
const scenario = String(a.scenario ?? "");
|
|
1882
|
+
if (!scenario.trim())
|
|
1883
|
+
return { error: "scenario is required and cannot be empty." };
|
|
1884
|
+
return csuitAnalyze(scenario, {
|
|
1885
|
+
context: a.context ? String(a.context) : undefined,
|
|
1886
|
+
perspectives: Array.isArray(a.perspectives) ? a.perspectives.map(String) : undefined,
|
|
1887
|
+
depth: a.depth ?? "standard",
|
|
1888
|
+
focus: a.focus ? String(a.focus) : undefined,
|
|
1889
|
+
});
|
|
1890
|
+
},
|
|
1891
|
+
// ── Local handlers (pure computation, no API call) ────────────────────────
|
|
1892
|
+
unclick_count_text: async (_c, a) => countText(String(a.text ?? "")),
|
|
1893
|
+
unclick_slug: async (_c, a) => ({ slug: generateSlug(String(a.text ?? ""), String(a.separator ?? "-")) }),
|
|
1894
|
+
unclick_lorem_ipsum: async (_c, a) => {
|
|
1895
|
+
const count = Math.min(100, Math.max(1, Number(a.count ?? 5)));
|
|
1896
|
+
const unit = a.unit ?? "sentences";
|
|
1897
|
+
const startWithLorem = a.start_with_lorem !== false;
|
|
1898
|
+
return { text: generateLorem(count, unit, startWithLorem), unit, count };
|
|
1899
|
+
},
|
|
1900
|
+
unclick_decode_jwt: async (_c, a) => decodeJwt(String(a.token ?? "")),
|
|
1901
|
+
unclick_http_status: async (_c, a) => lookupHttpStatus(Number(a.code)),
|
|
1902
|
+
unclick_emoji_search: async (_c, a) => {
|
|
1903
|
+
const limit = Math.min(30, Math.max(1, Number(a.limit ?? 10)));
|
|
1904
|
+
const results = searchEmoji(String(a.keyword ?? ""), limit);
|
|
1905
|
+
return {
|
|
1906
|
+
keyword: a.keyword,
|
|
1907
|
+
count: results.length,
|
|
1908
|
+
results: results.map((e) => ({ emoji: e.emoji, name: e.name, keywords: e.keywords })),
|
|
1909
|
+
};
|
|
1910
|
+
},
|
|
1911
|
+
unclick_parse_user_agent: async (_c, a) => parseUserAgent(String(a.user_agent ?? "")),
|
|
1912
|
+
unclick_readme_template: async (_c, a) => {
|
|
1913
|
+
const md = generateReadme({
|
|
1914
|
+
name: String(a.name ?? ""),
|
|
1915
|
+
description: String(a.description ?? ""),
|
|
1916
|
+
install: a.install ? String(a.install) : undefined,
|
|
1917
|
+
usage: a.usage ? String(a.usage) : undefined,
|
|
1918
|
+
language: a.language ? String(a.language) : undefined,
|
|
1919
|
+
license: a.license ? String(a.license) : "MIT",
|
|
1920
|
+
repo: a.repo ? String(a.repo) : undefined,
|
|
1921
|
+
badges: a.badges !== false,
|
|
1922
|
+
});
|
|
1923
|
+
return { markdown: md };
|
|
1924
|
+
},
|
|
1925
|
+
unclick_changelog_entry: async (_c, a) => {
|
|
1926
|
+
const toStrArray = (v) => Array.isArray(v) ? v.map(String) : [];
|
|
1927
|
+
const md = generateChangelog({
|
|
1928
|
+
version: String(a.version ?? "0.0.0"),
|
|
1929
|
+
date: a.date ? String(a.date) : undefined,
|
|
1930
|
+
added: toStrArray(a.added),
|
|
1931
|
+
changed: toStrArray(a.changed),
|
|
1932
|
+
deprecated: toStrArray(a.deprecated),
|
|
1933
|
+
removed: toStrArray(a.removed),
|
|
1934
|
+
fixed: toStrArray(a.fixed),
|
|
1935
|
+
security: toStrArray(a.security),
|
|
1936
|
+
});
|
|
1937
|
+
return { markdown: md };
|
|
1938
|
+
},
|
|
1939
|
+
unclick_favicon_url: async (_c, a) => getFaviconUrls(String(a.domain ?? "")),
|
|
1940
|
+
// ── Converter handlers ────────────────────────────────────────────────────
|
|
1941
|
+
unclick_markdown_to_html: async (_c, a) => markdownToHtml(String(a.markdown ?? "")),
|
|
1942
|
+
unclick_html_to_markdown: async (_c, a) => htmlToMarkdown(String(a.html ?? "")),
|
|
1943
|
+
unclick_json_to_yaml: async (_c, a) => jsonToYaml(String(a.json ?? ""), Number(a.indent ?? 2)),
|
|
1944
|
+
unclick_yaml_to_json: async (_c, a) => yamlToJson(String(a.yaml ?? ""), Number(a.indent ?? 2)),
|
|
1945
|
+
unclick_json_to_xml: async (_c, a) => jsonToXml(String(a.json ?? ""), a.root_key ? String(a.root_key) : "root"),
|
|
1946
|
+
unclick_xml_to_json: async (_c, a) => xmlToJson(String(a.xml ?? ""), Number(a.indent ?? 2)),
|
|
1947
|
+
unclick_json_to_toml: async (_c, a) => jsonToToml(String(a.json ?? "")),
|
|
1948
|
+
unclick_toml_to_json: async (_c, a) => tomlToJson(String(a.toml ?? ""), Number(a.indent ?? 2)),
|
|
1949
|
+
unclick_csv_to_json: async (_c, a) => csvToJson(String(a.csv ?? ""), {
|
|
1950
|
+
header: a.header !== false,
|
|
1951
|
+
delimiter: a.delimiter ? String(a.delimiter) : ",",
|
|
1952
|
+
}),
|
|
1953
|
+
unclick_json_to_csv: async (_c, a) => jsonToCsv(String(a.json ?? ""), {
|
|
1954
|
+
delimiter: a.delimiter ? String(a.delimiter) : ",",
|
|
1955
|
+
}),
|
|
1956
|
+
unclick_json_format: async (_c, a) => {
|
|
1957
|
+
const indent = a.indent === "tab" || a.indent === "minify"
|
|
1958
|
+
? a.indent
|
|
1959
|
+
: Number(a.indent ?? 2);
|
|
1960
|
+
return jsonFormat(String(a.json ?? ""), indent);
|
|
1961
|
+
},
|
|
1962
|
+
unclick_json_to_jsonl: async (_c, a) => jsonToJsonl(String(a.json ?? "")),
|
|
1963
|
+
unclick_jsonl_to_json: async (_c, a) => jsonlToJson(String(a.jsonl ?? ""), Number(a.indent ?? 2)),
|
|
1964
|
+
// ── Encoding & utility converter handlers ─────────────────────────────────
|
|
1965
|
+
binary_to_decimal: async (_c, a) => {
|
|
1966
|
+
const bin = String(a.binary ?? "").trim().replace(/^0b/i, "");
|
|
1967
|
+
if (!/^[01]+$/.test(bin))
|
|
1968
|
+
return { error: "Invalid binary string. Use only 0 and 1." };
|
|
1969
|
+
const decimal = parseInt(bin, 2);
|
|
1970
|
+
return { binary: bin, decimal, decimal_string: String(decimal) };
|
|
1971
|
+
},
|
|
1972
|
+
decimal_to_binary: async (_c, a) => {
|
|
1973
|
+
const n = Math.trunc(Number(a.decimal));
|
|
1974
|
+
if (!Number.isFinite(n))
|
|
1975
|
+
return { error: "Invalid decimal number." };
|
|
1976
|
+
const binary = Math.abs(n).toString(2);
|
|
1977
|
+
return { decimal: n, binary: n < 0 ? `-${binary}` : binary };
|
|
1978
|
+
},
|
|
1979
|
+
hex_to_decimal: async (_c, a) => {
|
|
1980
|
+
const hex = String(a.hex ?? "").trim().replace(/^0x/i, "");
|
|
1981
|
+
if (!/^[0-9a-fA-F]+$/.test(hex))
|
|
1982
|
+
return { error: "Invalid hex string." };
|
|
1983
|
+
const decimal = parseInt(hex, 16);
|
|
1984
|
+
return { hex: hex.toUpperCase(), decimal, decimal_string: String(decimal) };
|
|
1985
|
+
},
|
|
1986
|
+
decimal_to_hex: async (_c, a) => {
|
|
1987
|
+
const n = Math.trunc(Number(a.decimal));
|
|
1988
|
+
if (!Number.isFinite(n))
|
|
1989
|
+
return { error: "Invalid decimal number." };
|
|
1990
|
+
const hex = Math.abs(n).toString(16).toUpperCase();
|
|
1991
|
+
return { decimal: n, hex: n < 0 ? `-${hex}` : hex, hex_prefixed: n < 0 ? `-0x${hex}` : `0x${hex}` };
|
|
1992
|
+
},
|
|
1993
|
+
octal_to_decimal: async (_c, a) => {
|
|
1994
|
+
const oct = String(a.octal ?? "").trim().replace(/^0o/i, "");
|
|
1995
|
+
if (!/^[0-7]+$/.test(oct))
|
|
1996
|
+
return { error: "Invalid octal string. Use only digits 0–7." };
|
|
1997
|
+
const decimal = parseInt(oct, 8);
|
|
1998
|
+
return { octal: oct, decimal, decimal_string: String(decimal) };
|
|
1999
|
+
},
|
|
2000
|
+
decimal_to_octal: async (_c, a) => {
|
|
2001
|
+
const n = Math.trunc(Number(a.decimal));
|
|
2002
|
+
if (!Number.isFinite(n))
|
|
2003
|
+
return { error: "Invalid decimal number." };
|
|
2004
|
+
const octal = Math.abs(n).toString(8);
|
|
2005
|
+
return { decimal: n, octal: n < 0 ? `-${octal}` : octal };
|
|
2006
|
+
},
|
|
2007
|
+
celsius_to_fahrenheit: async (_c, a) => {
|
|
2008
|
+
const c = Number(a.celsius);
|
|
2009
|
+
if (!Number.isFinite(c))
|
|
2010
|
+
return { error: "Invalid Celsius value." };
|
|
2011
|
+
const f = (c * 9) / 5 + 32;
|
|
2012
|
+
return { celsius: c, fahrenheit: Math.round(f * 100) / 100 };
|
|
2013
|
+
},
|
|
2014
|
+
fahrenheit_to_celsius: async (_c, a) => {
|
|
2015
|
+
const f = Number(a.fahrenheit);
|
|
2016
|
+
if (!Number.isFinite(f))
|
|
2017
|
+
return { error: "Invalid Fahrenheit value." };
|
|
2018
|
+
const c = ((f - 32) * 5) / 9;
|
|
2019
|
+
return { fahrenheit: f, celsius: Math.round(c * 100) / 100 };
|
|
2020
|
+
},
|
|
2021
|
+
bytes_to_human: async (_c, a) => {
|
|
2022
|
+
const bytes = Number(a.bytes);
|
|
2023
|
+
if (!Number.isFinite(bytes) || bytes < 0)
|
|
2024
|
+
return { error: "Invalid byte count." };
|
|
2025
|
+
const units = ["B", "KB", "MB", "GB", "TB", "PB"];
|
|
2026
|
+
let value = bytes;
|
|
2027
|
+
let unitIndex = 0;
|
|
2028
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
2029
|
+
value /= 1024;
|
|
2030
|
+
unitIndex++;
|
|
2031
|
+
}
|
|
2032
|
+
const rounded = Math.round(value * 100) / 100;
|
|
2033
|
+
return { bytes, human: `${rounded} ${units[unitIndex]}`, value: rounded, unit: units[unitIndex] };
|
|
2034
|
+
},
|
|
2035
|
+
vault: async (_c, a) => {
|
|
2036
|
+
const action = String(a.action ?? "").trim();
|
|
2037
|
+
if (!action)
|
|
2038
|
+
return { error: "action is required." };
|
|
2039
|
+
return vaultAction(action, a);
|
|
2040
|
+
},
|
|
2041
|
+
human_to_bytes: async (_c, a) => {
|
|
2042
|
+
const raw = String(a.size ?? "").trim();
|
|
2043
|
+
const match = raw.match(/^([0-9]*\.?[0-9]+)\s*(B|KB|MB|GB|TB|PB)?$/i);
|
|
2044
|
+
if (!match)
|
|
2045
|
+
return { error: `Cannot parse size string: "${raw}". Expected format like '1.5 GB' or '512 MB'.` };
|
|
2046
|
+
const value = parseFloat(match[1]);
|
|
2047
|
+
const unitMap = {
|
|
2048
|
+
b: 1, kb: 1024, mb: 1024 ** 2, gb: 1024 ** 3, tb: 1024 ** 4, pb: 1024 ** 5,
|
|
2049
|
+
};
|
|
2050
|
+
const unit = (match[2] ?? "b").toLowerCase();
|
|
2051
|
+
const bytes = Math.round(value * (unitMap[unit] ?? 1));
|
|
2052
|
+
return { size: raw, bytes, unit: (match[2] ?? "B").toUpperCase() };
|
|
2053
|
+
},
|
|
2054
|
+
// ── Telegram handlers ─────────────────────────────────────────────────────
|
|
2055
|
+
telegram_send: async (_c, a) => telegramSend(a),
|
|
2056
|
+
telegram_read: async (_c, a) => telegramRead(a),
|
|
2057
|
+
telegram_search: async (_c, a) => telegramSearch(a),
|
|
2058
|
+
telegram_send_media: async (_c, a) => telegramSendMedia(a),
|
|
2059
|
+
telegram_get_updates: async (_c, a) => telegramGetUpdates(a),
|
|
2060
|
+
telegram_manage_chat: async (_c, a) => telegramManageChat(a),
|
|
2061
|
+
// ── Slack handler ─────────────────────────────────────────────────────────
|
|
2062
|
+
slack: async (_c, a) => {
|
|
2063
|
+
const action = String(a.action ?? "").trim();
|
|
2064
|
+
if (!action)
|
|
2065
|
+
return { error: "action is required." };
|
|
2066
|
+
return slackAction(action, a);
|
|
2067
|
+
},
|
|
2068
|
+
// ── Discord handlers ──────────────────────────────────────────────────────
|
|
2069
|
+
discord_send: async (_c, a) => discordSend(a),
|
|
2070
|
+
discord_read: async (_c, a) => discordRead(a),
|
|
2071
|
+
discord_thread: async (_c, a) => discordThread(a),
|
|
2072
|
+
discord_react: async (_c, a) => discordReact(a),
|
|
2073
|
+
discord_channels: async (_c, a) => discordChannels(a),
|
|
2074
|
+
discord_members: async (_c, a) => discordMembers(a),
|
|
2075
|
+
discord_search: async (_c, a) => discordSearch(a),
|
|
2076
|
+
// ── Reddit handlers ───────────────────────────────────────────────────────
|
|
2077
|
+
reddit_read: async (_c, a) => redditRead({
|
|
2078
|
+
access_token: String(a.access_token ?? ""),
|
|
2079
|
+
subreddit: String(a.subreddit ?? ""),
|
|
2080
|
+
sort: a.sort ? String(a.sort) : undefined,
|
|
2081
|
+
limit: a.limit ? Number(a.limit) : undefined,
|
|
2082
|
+
after: a.after ? String(a.after) : undefined,
|
|
2083
|
+
t: a.t ? String(a.t) : undefined,
|
|
2084
|
+
}),
|
|
2085
|
+
reddit_post: async (_c, a) => redditPost({
|
|
2086
|
+
access_token: String(a.access_token ?? ""),
|
|
2087
|
+
subreddit: String(a.subreddit ?? ""),
|
|
2088
|
+
title: String(a.title ?? ""),
|
|
2089
|
+
kind: String(a.kind ?? "self"),
|
|
2090
|
+
text: a.text ? String(a.text) : undefined,
|
|
2091
|
+
url: a.url ? String(a.url) : undefined,
|
|
2092
|
+
nsfw: a.nsfw === true,
|
|
2093
|
+
spoiler: a.spoiler === true,
|
|
2094
|
+
flair_id: a.flair_id ? String(a.flair_id) : undefined,
|
|
2095
|
+
flair_text: a.flair_text ? String(a.flair_text) : undefined,
|
|
2096
|
+
}),
|
|
2097
|
+
reddit_comment: async (_c, a) => redditComment({
|
|
2098
|
+
access_token: String(a.access_token ?? ""),
|
|
2099
|
+
parent_id: String(a.parent_id ?? ""),
|
|
2100
|
+
text: String(a.text ?? ""),
|
|
2101
|
+
}),
|
|
2102
|
+
reddit_search: async (_c, a) => redditSearch({
|
|
2103
|
+
access_token: String(a.access_token ?? ""),
|
|
2104
|
+
query: String(a.query ?? ""),
|
|
2105
|
+
subreddit: a.subreddit ? String(a.subreddit) : undefined,
|
|
2106
|
+
sort: a.sort ? String(a.sort) : undefined,
|
|
2107
|
+
t: a.t ? String(a.t) : undefined,
|
|
2108
|
+
limit: a.limit ? Number(a.limit) : undefined,
|
|
2109
|
+
after: a.after ? String(a.after) : undefined,
|
|
2110
|
+
}),
|
|
2111
|
+
reddit_user: async (_c, a) => redditUser({
|
|
2112
|
+
access_token: String(a.access_token ?? ""),
|
|
2113
|
+
username: String(a.username ?? ""),
|
|
2114
|
+
include_posts: a.include_posts !== false,
|
|
2115
|
+
include_comments: a.include_comments !== false,
|
|
2116
|
+
limit: a.limit ? Number(a.limit) : undefined,
|
|
2117
|
+
}),
|
|
2118
|
+
reddit_vote: async (_c, a) => redditVote({
|
|
2119
|
+
access_token: String(a.access_token ?? ""),
|
|
2120
|
+
id: String(a.id ?? ""),
|
|
2121
|
+
dir: Number(a.dir ?? 0),
|
|
2122
|
+
}),
|
|
2123
|
+
reddit_subscribe: async (_c, a) => redditSubscribe({
|
|
2124
|
+
access_token: String(a.access_token ?? ""),
|
|
2125
|
+
subreddit: String(a.subreddit ?? ""),
|
|
2126
|
+
action: String(a.action ?? "sub"),
|
|
2127
|
+
}),
|
|
2128
|
+
// ── Bluesky handler ───────────────────────────────────────────────────────
|
|
2129
|
+
bluesky: async (_c, a) => {
|
|
2130
|
+
const action = String(a.action ?? "").trim();
|
|
2131
|
+
if (!action)
|
|
2132
|
+
return { error: "action is required." };
|
|
2133
|
+
return blueskyAction(action, a);
|
|
2134
|
+
},
|
|
2135
|
+
// ── Mastodon handlers ─────────────────────────────────────────────────────
|
|
2136
|
+
mastodon_post: async (_c, a) => mastodonAction("mastodon_post", a),
|
|
2137
|
+
mastodon_read_timeline: async (_c, a) => mastodonAction("mastodon_read_timeline", a),
|
|
2138
|
+
mastodon_reply: async (_c, a) => mastodonAction("mastodon_reply", a),
|
|
2139
|
+
mastodon_boost: async (_c, a) => mastodonAction("mastodon_boost", a),
|
|
2140
|
+
mastodon_favorite: async (_c, a) => mastodonAction("mastodon_favorite", a),
|
|
2141
|
+
mastodon_search: async (_c, a) => mastodonAction("mastodon_search", a),
|
|
2142
|
+
mastodon_profile: async (_c, a) => mastodonAction("mastodon_profile", a),
|
|
2143
|
+
mastodon_follow: async (_c, a) => mastodonAction("mastodon_follow", a),
|
|
2144
|
+
mastodon_notifications: async (_c, a) => mastodonAction("mastodon_notifications", a),
|
|
2145
|
+
// ── Amazon handlers ───────────────────────────────────────────────────────
|
|
2146
|
+
amazon_search: async (_c, a) => amazonSearch(a),
|
|
2147
|
+
amazon_product: async (_c, a) => amazonProduct(a),
|
|
2148
|
+
amazon_browse: async (_c, a) => amazonBrowse(a),
|
|
2149
|
+
amazon_variations: async (_c, a) => amazonVariations(a),
|
|
2150
|
+
// ── Xero handlers ─────────────────────────────────────────────────────────
|
|
2151
|
+
xero_invoices: async (_c, a) => xeroInvoices(a),
|
|
2152
|
+
xero_contacts: async (_c, a) => xeroContacts(a),
|
|
2153
|
+
xero_accounts: async (_c, a) => xeroAccounts(a),
|
|
2154
|
+
xero_payments: async (_c, a) => xeroPayments(a),
|
|
2155
|
+
xero_bank_transactions: async (_c, a) => xeroBankTransactions(a),
|
|
2156
|
+
xero_reports: async (_c, a) => xeroReports(a),
|
|
2157
|
+
xero_quotes: async (_c, a) => xeroQuotes(a),
|
|
2158
|
+
xero_organisation: async (_c, a) => xeroOrganisation(a),
|
|
2159
|
+
// ── Shopify handlers ──────────────────────────────────────────────────────
|
|
2160
|
+
shopify_products: async (_c, a) => shopifyProducts(a),
|
|
2161
|
+
shopify_orders: async (_c, a) => shopifyOrders(a),
|
|
2162
|
+
shopify_customers: async (_c, a) => shopifyCustomers(a),
|
|
2163
|
+
shopify_inventory: async (_c, a) => shopifyInventory(a),
|
|
2164
|
+
shopify_collections: async (_c, a) => shopifyCollections(a),
|
|
2165
|
+
shopify_shop: async (_c, a) => shopifyShop(a),
|
|
2166
|
+
shopify_fulfillments: async (_c, a) => shopifyFulfillments(a),
|
|
2167
|
+
};
|
|
2168
|
+
// ─── Server factory ─────────────────────────────────────────────────────────
|
|
2169
|
+
export function createServer() {
|
|
2170
|
+
const server = new Server({
|
|
2171
|
+
name: "@unclick/mcp-server",
|
|
2172
|
+
version: "0.1.0",
|
|
2173
|
+
}, {
|
|
2174
|
+
capabilities: { tools: {} },
|
|
2175
|
+
});
|
|
2176
|
+
// LIST TOOLS
|
|
2177
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
2178
|
+
const tools = [
|
|
2179
|
+
...META_TOOLS,
|
|
2180
|
+
...DIRECT_TOOLS,
|
|
2181
|
+
];
|
|
2182
|
+
return { tools };
|
|
2183
|
+
});
|
|
2184
|
+
// CALL TOOL
|
|
2185
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
2186
|
+
const { name, arguments: rawArgs } = request.params;
|
|
2187
|
+
const args = (rawArgs ?? {});
|
|
2188
|
+
try {
|
|
2189
|
+
// ── Meta tools ──────────────────────────────────────────────
|
|
2190
|
+
if (name === "unclick_search") {
|
|
2191
|
+
const results = searchTools(String(args.query ?? ""), args.category);
|
|
2192
|
+
if (results.length === 0) {
|
|
2193
|
+
return {
|
|
2194
|
+
content: [
|
|
2195
|
+
{
|
|
2196
|
+
type: "text",
|
|
2197
|
+
text: `No tools found matching "${args.query}". Try unclick_browse to see all available tools.`,
|
|
2198
|
+
},
|
|
2199
|
+
],
|
|
2200
|
+
};
|
|
2201
|
+
}
|
|
2202
|
+
const text = results.map(formatToolSummary).join("\n\n---\n\n");
|
|
2203
|
+
return {
|
|
2204
|
+
content: [
|
|
2205
|
+
{
|
|
2206
|
+
type: "text",
|
|
2207
|
+
text: `Found ${results.length} tool(s) matching "${args.query}":\n\n${text}`,
|
|
2208
|
+
},
|
|
2209
|
+
],
|
|
2210
|
+
};
|
|
2211
|
+
}
|
|
2212
|
+
if (name === "unclick_browse") {
|
|
2213
|
+
const filtered = args.category
|
|
2214
|
+
? CATALOG.filter((t) => t.category === args.category)
|
|
2215
|
+
: CATALOG;
|
|
2216
|
+
const byCategory = filtered.reduce((acc, tool) => {
|
|
2217
|
+
(acc[tool.category] ??= []).push(tool);
|
|
2218
|
+
return acc;
|
|
2219
|
+
}, {});
|
|
2220
|
+
const lines = [];
|
|
2221
|
+
for (const [cat, tools] of Object.entries(byCategory)) {
|
|
2222
|
+
lines.push(`## ${cat.toUpperCase()}`);
|
|
2223
|
+
for (const tool of tools) {
|
|
2224
|
+
lines.push(`- **${tool.name}** (\`${tool.slug}\`) - ${tool.description}`);
|
|
2225
|
+
}
|
|
2226
|
+
lines.push("");
|
|
2227
|
+
}
|
|
2228
|
+
return {
|
|
2229
|
+
content: [
|
|
2230
|
+
{
|
|
2231
|
+
type: "text",
|
|
2232
|
+
text: `UnClick Tool Catalog (${filtered.length} tools)\n\n${lines.join("\n")}`,
|
|
2233
|
+
},
|
|
2234
|
+
],
|
|
2235
|
+
};
|
|
2236
|
+
}
|
|
2237
|
+
if (name === "unclick_tool_info") {
|
|
2238
|
+
const slug = String(args.slug ?? "");
|
|
2239
|
+
const tool = TOOL_MAP.get(slug);
|
|
2240
|
+
if (!tool) {
|
|
2241
|
+
const available = CATALOG.map((t) => t.slug).join(", ");
|
|
2242
|
+
return {
|
|
2243
|
+
content: [
|
|
2244
|
+
{
|
|
2245
|
+
type: "text",
|
|
2246
|
+
text: `Tool "${slug}" not found. Available slugs: ${available}`,
|
|
2247
|
+
},
|
|
2248
|
+
],
|
|
2249
|
+
isError: true,
|
|
2250
|
+
};
|
|
2251
|
+
}
|
|
2252
|
+
const lines = [
|
|
2253
|
+
`# ${tool.name}`,
|
|
2254
|
+
`**Slug:** ${tool.slug} | **Category:** ${tool.category} | **Scope:** ${tool.scope}`,
|
|
2255
|
+
"",
|
|
2256
|
+
tool.description,
|
|
2257
|
+
"",
|
|
2258
|
+
"## Endpoints",
|
|
2259
|
+
];
|
|
2260
|
+
for (const ep of tool.endpoints) {
|
|
2261
|
+
lines.push(`### \`${ep.id}\` - ${ep.name}`);
|
|
2262
|
+
lines.push(ep.description);
|
|
2263
|
+
lines.push(`**Method:** ${ep.method} | **Path:** ${ep.path}`);
|
|
2264
|
+
lines.push(`**Input Schema:**`);
|
|
2265
|
+
lines.push("```json");
|
|
2266
|
+
lines.push(JSON.stringify(ep.inputSchema, null, 2));
|
|
2267
|
+
lines.push("```");
|
|
2268
|
+
lines.push("");
|
|
2269
|
+
}
|
|
2270
|
+
lines.push(`\n> Call any endpoint with: \`unclick_call\` → \`{ endpoint_id: "<id>", params: {...} }\``);
|
|
2271
|
+
return {
|
|
2272
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
2273
|
+
};
|
|
2274
|
+
}
|
|
2275
|
+
if (name === "unclick_call") {
|
|
2276
|
+
const endpointId = String(args.endpoint_id ?? "");
|
|
2277
|
+
const params = (args.params ?? {});
|
|
2278
|
+
const entry = ENDPOINT_MAP.get(endpointId);
|
|
2279
|
+
if (!entry) {
|
|
2280
|
+
return {
|
|
2281
|
+
content: [
|
|
2282
|
+
{
|
|
2283
|
+
type: "text",
|
|
2284
|
+
text: `Endpoint "${endpointId}" not found. Use unclick_tool_info to see valid endpoint IDs.`,
|
|
2285
|
+
},
|
|
2286
|
+
],
|
|
2287
|
+
isError: true,
|
|
2288
|
+
};
|
|
2289
|
+
}
|
|
2290
|
+
const client = createClient();
|
|
2291
|
+
const result = await client.call(entry.endpoint.method, entry.endpoint.path, params);
|
|
2292
|
+
return {
|
|
2293
|
+
content: [
|
|
2294
|
+
{
|
|
2295
|
+
type: "text",
|
|
2296
|
+
text: JSON.stringify(result, null, 2),
|
|
2297
|
+
},
|
|
2298
|
+
],
|
|
2299
|
+
};
|
|
2300
|
+
}
|
|
2301
|
+
// ── Direct tools ─────────────────────────────────────────────
|
|
2302
|
+
const handler = DIRECT_HANDLERS[name];
|
|
2303
|
+
if (handler) {
|
|
2304
|
+
const client = createClient();
|
|
2305
|
+
const result = await handler(client, args);
|
|
2306
|
+
return {
|
|
2307
|
+
content: [
|
|
2308
|
+
{
|
|
2309
|
+
type: "text",
|
|
2310
|
+
text: JSON.stringify(result, null, 2),
|
|
2311
|
+
},
|
|
2312
|
+
],
|
|
2313
|
+
};
|
|
2314
|
+
}
|
|
2315
|
+
return {
|
|
2316
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
2317
|
+
isError: true,
|
|
2318
|
+
};
|
|
2319
|
+
}
|
|
2320
|
+
catch (err) {
|
|
2321
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2322
|
+
return {
|
|
2323
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
2324
|
+
isError: true,
|
|
2325
|
+
};
|
|
2326
|
+
}
|
|
2327
|
+
});
|
|
2328
|
+
return server;
|
|
2329
|
+
}
|
|
2330
|
+
export async function startServer() {
|
|
2331
|
+
const server = createServer();
|
|
2332
|
+
const transport = new StdioServerTransport();
|
|
2333
|
+
await server.connect(transport);
|
|
2334
|
+
// Server is running - errors go to stderr so they don't corrupt the MCP stream
|
|
2335
|
+
process.stderr.write("UnClick MCP server running on stdio\n");
|
|
2336
|
+
}
|
|
2337
|
+
//# sourceMappingURL=server.js.map
|