@publishq/mcp 0.0.6 → 0.0.8
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 +8 -5
- package/dist/index.js +107 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -51,10 +51,7 @@ Add this to your `claude_desktop_config.json`:
|
|
|
51
51
|
"mcpServers": {
|
|
52
52
|
"publishq": {
|
|
53
53
|
"command": "npx",
|
|
54
|
-
"args": ["-y", "@publishq/mcp@latest"]
|
|
55
|
-
"env": {
|
|
56
|
-
"PUBLISHQ_API_KEY": "pq_live_your_key_here"
|
|
57
|
-
}
|
|
54
|
+
"args": ["-y", "@publishq/mcp@latest"]
|
|
58
55
|
}
|
|
59
56
|
}
|
|
60
57
|
}
|
|
@@ -91,9 +88,15 @@ Any MCP client that launches a stdio server works the same way: run the command
|
|
|
91
88
|
| `publishq_list_media` | List uploaded/generated media |
|
|
92
89
|
| `publishq_get_media` | Get one media file by ID |
|
|
93
90
|
| `publishq_upload_media` | Upload a file and get a media ID: a local path (local server) or a link or chat attachment (hosted server) |
|
|
91
|
+
| `publishq_create_media_upload` | Get a signed URL to upload a file from your own disk, up to 4 GB |
|
|
94
92
|
| `publishq_delete_media` | Delete a media file no draft or scheduled post uses |
|
|
93
|
+
| `publishq_list_media_folders` | List media folders with their file counts |
|
|
94
|
+
| `publishq_move_media` | Move media files to a folder or to the top level |
|
|
95
|
+
| `publishq_rename_media_folder` | Rename a media folder |
|
|
96
|
+
| `publishq_preview_media_folder_deletion` | Count what deleting a folder would remove |
|
|
97
|
+
| `publishq_delete_media_folder` | Delete a folder and its files, after the preview |
|
|
95
98
|
|
|
96
|
-
The tools are thin wrappers over [`@publishq/sdk`](https://www.npmjs.com/package/@publishq/sdk), so their behavior and scopes match the [PublishQ API](https://publishq.com/docs). `publishq_create_post` accepts an `accounts` array whose entries contain `accountId` plus optional `postOverrides` and `platformSpecificSettings`. `publishq_update_post` accepts content, scheduling, complete media replacement or removal, and thumbnail timestamp changes. `publishq_list_media` can filter by media IDs, types, and
|
|
99
|
+
The tools are thin wrappers over [`@publishq/sdk`](https://www.npmjs.com/package/@publishq/sdk), so their behavior and scopes match the [PublishQ API](https://publishq.com/docs). `publishq_create_post` accepts an `accounts` array whose entries contain `accountId` plus optional `postOverrides` and `platformSpecificSettings`. `publishq_update_post` accepts content, scheduling, complete media replacement or removal, and thumbnail timestamp changes. `publishq_list_media` can filter by media IDs, types, sources and folder, and `publishq_upload_media` takes a `folderPath`. Folder paths look like `Channel A/Shorts`. Before `publishq_delete_media_folder`, the agent calls `publishq_preview_media_folder_deletion`, shows the counts to the user and passes the `fileCount` back as `expectedFileCount`. For TikTok, the agent should call `publishq_get_tiktok_creator_info` first and then provide the required privacy and interaction settings in that account object. A tool call that hits an API error returns the API error so the agent can react.
|
|
97
100
|
|
|
98
101
|
## Scopes
|
|
99
102
|
|
package/dist/index.js
CHANGED
|
@@ -16,8 +16,10 @@ import {
|
|
|
16
16
|
McpError
|
|
17
17
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
18
18
|
import {
|
|
19
|
+
zCreateMediaUploadBody,
|
|
19
20
|
zCreatePostBody,
|
|
20
21
|
zCreateWorkspaceBody,
|
|
22
|
+
zDeleteMediaFolderBody,
|
|
21
23
|
zDeleteMediaPath,
|
|
22
24
|
zDeletePostPath,
|
|
23
25
|
zGetMediaPath,
|
|
@@ -27,7 +29,10 @@ import {
|
|
|
27
29
|
zListAccountsQuery,
|
|
28
30
|
zListMediaQuery,
|
|
29
31
|
zListPostsQuery,
|
|
32
|
+
zMoveMediaBody,
|
|
33
|
+
zPreviewMediaFolderDeletionQuery,
|
|
30
34
|
zRemovePostAccountPath,
|
|
35
|
+
zRenameMediaFolderBody,
|
|
31
36
|
zUpdatePostBody,
|
|
32
37
|
zUpdatePostPath
|
|
33
38
|
} from "@publishq/sdk";
|
|
@@ -66,8 +71,38 @@ async function run(call) {
|
|
|
66
71
|
return errorResult(error);
|
|
67
72
|
}
|
|
68
73
|
}
|
|
74
|
+
var isPlainObject = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
75
|
+
var isAnnotationOnly = (node) => isPlainObject(node) && Object.keys(node).every((key) => key === "description" || key === "title");
|
|
76
|
+
var isEmpty = (value) => isPlainObject(value) && Object.keys(value).length === 0;
|
|
77
|
+
function booleanSchema(value) {
|
|
78
|
+
if (isEmpty(value)) return true;
|
|
79
|
+
if (isPlainObject(value) && Object.keys(value).length === 1 && isEmpty(value.not)) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return void 0;
|
|
83
|
+
}
|
|
84
|
+
function portableSchema(node) {
|
|
85
|
+
if (Array.isArray(node)) return node.map(portableSchema);
|
|
86
|
+
if (!isPlainObject(node)) return node;
|
|
87
|
+
const result = {};
|
|
88
|
+
for (const [key, value] of Object.entries(node)) {
|
|
89
|
+
result[key] = key === "additionalProperties" ? booleanSchema(value) ?? portableSchema(value) : portableSchema(value);
|
|
90
|
+
}
|
|
91
|
+
const allOf = result.allOf;
|
|
92
|
+
if (Array.isArray(allOf)) {
|
|
93
|
+
const annotations = allOf.filter(isAnnotationOnly);
|
|
94
|
+
const schemas = allOf.filter((member) => !isAnnotationOnly(member));
|
|
95
|
+
if (annotations.length > 0 && schemas.length === 1 && isPlainObject(schemas[0])) {
|
|
96
|
+
delete result.allOf;
|
|
97
|
+
return { ...Object.assign({}, ...annotations), ...schemas[0], ...result };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
69
102
|
function advertisedSchema(schema) {
|
|
70
|
-
const jsonSchema =
|
|
103
|
+
const jsonSchema = portableSchema(
|
|
104
|
+
toJSONSchema(schema, { io: "input", target: "draft-7" })
|
|
105
|
+
);
|
|
71
106
|
if (jsonSchema.type !== "object") {
|
|
72
107
|
throw new Error("MCP tool input schema must describe an object.");
|
|
73
108
|
}
|
|
@@ -233,6 +268,16 @@ function publishqTools(publishq2) {
|
|
|
233
268
|
},
|
|
234
269
|
(args) => run(publishq2.media.get(args.id))
|
|
235
270
|
);
|
|
271
|
+
register(
|
|
272
|
+
{
|
|
273
|
+
name: "publishq_create_media_upload",
|
|
274
|
+
title: "Start a media upload",
|
|
275
|
+
description: 'Get a signed upload URL for a file of up to 4 GB on the machine you run commands on, and the media ID it becomes. Send the file with a PUT to uploadUrl, with the same Content-Type and no Authorization header, for example `curl -X PUT -H "Content-Type: image/jpeg" --upload-file ./photo.jpg "<uploadUrl>"`. Then attach the media ID to a post through mediaIds. The URL expires after an hour.',
|
|
276
|
+
inputSchema: advertisedSchema(zCreateMediaUploadBody),
|
|
277
|
+
annotations: { readOnlyHint: false, openWorldHint: true }
|
|
278
|
+
},
|
|
279
|
+
(args) => run(publishq2.media.createUpload(args))
|
|
280
|
+
);
|
|
236
281
|
register(
|
|
237
282
|
{
|
|
238
283
|
name: "publishq_delete_media",
|
|
@@ -243,6 +288,56 @@ function publishqTools(publishq2) {
|
|
|
243
288
|
},
|
|
244
289
|
(args) => run(publishq2.media.delete(args.id))
|
|
245
290
|
);
|
|
291
|
+
register(
|
|
292
|
+
{
|
|
293
|
+
name: "publishq_list_media_folders",
|
|
294
|
+
title: "List media folders",
|
|
295
|
+
description: 'List every folder in the media library, parents before subfolders, with the number of files in each folder and its subfolders. Folder paths look like "Channel A/Shorts". Use a path as folderPath in publishq_list_media, publishq_upload_media or publishq_move_media.',
|
|
296
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
297
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
298
|
+
},
|
|
299
|
+
() => run(publishq2.media.listFolders())
|
|
300
|
+
);
|
|
301
|
+
register(
|
|
302
|
+
{
|
|
303
|
+
name: "publishq_move_media",
|
|
304
|
+
title: "Move media to a folder",
|
|
305
|
+
description: 'Move up to 100 media files to a folder by path, for example "Channel A/Shorts", or to the top level with folderPath null. A folder that does not exist yet is created by moving files into it. Posts keep working: they use media IDs, not folders.',
|
|
306
|
+
inputSchema: advertisedSchema(zMoveMediaBody),
|
|
307
|
+
annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: true }
|
|
308
|
+
},
|
|
309
|
+
(args) => run(publishq2.media.move(args))
|
|
310
|
+
);
|
|
311
|
+
register(
|
|
312
|
+
{
|
|
313
|
+
name: "publishq_rename_media_folder",
|
|
314
|
+
title: "Rename a media folder",
|
|
315
|
+
description: "Rename a media folder. Only the last part of the path changes, and its files and subfolders follow. Answers 404 when no folder has that path (check publishq_list_media_folders), and is refused when a folder with the new name already exists at the same level.",
|
|
316
|
+
inputSchema: advertisedSchema(zRenameMediaFolderBody),
|
|
317
|
+
annotations: { readOnlyHint: false, openWorldHint: true }
|
|
318
|
+
},
|
|
319
|
+
(args) => run(publishq2.media.renameFolder(args))
|
|
320
|
+
);
|
|
321
|
+
register(
|
|
322
|
+
{
|
|
323
|
+
name: "publishq_preview_media_folder_deletion",
|
|
324
|
+
title: "Preview deleting a media folder",
|
|
325
|
+
description: "Count what deleting a media folder would remove: files and subfolders at every level, and files kept because a draft or scheduled post uses them. Call this first, show the numbers to the user and get their confirmation before publishq_delete_media_folder.",
|
|
326
|
+
inputSchema: advertisedSchema(zPreviewMediaFolderDeletionQuery),
|
|
327
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
328
|
+
},
|
|
329
|
+
(args) => run(publishq2.media.previewFolderDeletion(args.path))
|
|
330
|
+
);
|
|
331
|
+
register(
|
|
332
|
+
{
|
|
333
|
+
name: "publishq_delete_media_folder",
|
|
334
|
+
title: "Delete a media folder",
|
|
335
|
+
description: "Delete a media folder with every file in it and in its subfolders, from the media library and from storage. This cannot be undone. Files a draft or scheduled post uses are kept. Only call it after the user confirmed the numbers from publishq_preview_media_folder_deletion, and send that fileCount as expectedFileCount; if the folder changed since, nothing is deleted.",
|
|
336
|
+
inputSchema: advertisedSchema(zDeleteMediaFolderBody),
|
|
337
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true }
|
|
338
|
+
},
|
|
339
|
+
(args) => run(publishq2.media.deleteFolder(args))
|
|
340
|
+
);
|
|
246
341
|
return tools;
|
|
247
342
|
}
|
|
248
343
|
function createPublishQServer(tools) {
|
|
@@ -301,6 +396,10 @@ var server = createPublishQServer([
|
|
|
301
396
|
filename: {
|
|
302
397
|
type: "string",
|
|
303
398
|
description: "Override the uploaded file name"
|
|
399
|
+
},
|
|
400
|
+
folderPath: {
|
|
401
|
+
type: "string",
|
|
402
|
+
description: 'Folder to put the file in, for example "Channel A/Shorts". Leave it out for the top level. See publishq_list_media_folders.'
|
|
304
403
|
}
|
|
305
404
|
},
|
|
306
405
|
required: ["filePath"],
|
|
@@ -311,6 +410,7 @@ var server = createPublishQServer([
|
|
|
311
410
|
handler: async (args) => {
|
|
312
411
|
const filePath = args.filePath;
|
|
313
412
|
const filename = args.filename;
|
|
413
|
+
const folderPath = args.folderPath;
|
|
314
414
|
if (statSync(filePath).size >= OPEN_AS_BLOB_MAX_BYTES) {
|
|
315
415
|
return {
|
|
316
416
|
content: [
|
|
@@ -326,7 +426,12 @@ var server = createPublishQServer([
|
|
|
326
426
|
[await openAsBlob(filePath)],
|
|
327
427
|
filename ?? basename(filePath)
|
|
328
428
|
);
|
|
329
|
-
return run(
|
|
429
|
+
return run(
|
|
430
|
+
publishq.media.upload(
|
|
431
|
+
{ file },
|
|
432
|
+
folderPath !== void 0 ? { folderPath } : void 0
|
|
433
|
+
)
|
|
434
|
+
);
|
|
330
435
|
}
|
|
331
436
|
}
|
|
332
437
|
]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@publishq/mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Model Context Protocol (MCP) server for PublishQ. Let AI agents schedule and publish across your social platforms.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
33
33
|
"zod": "4.4.3",
|
|
34
|
-
"@publishq/sdk": "^0.0.
|
|
34
|
+
"@publishq/sdk": "^0.0.8"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^20",
|