Skip to content

feat(agent): add postSettingsTool to update a post's settings via the agent - #1877

Merged
nevo-david merged 5 commits into
mainfrom
feat/post-settings-tool
Aug 11, 2026
Merged

feat(agent): add postSettingsTool to update a post's settings via the agent#1877
nevo-david merged 5 commits into
mainfrom
feat/post-settings-tool

Conversation

@giladresisi

@giladresisi giladresisi commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

⛔ DO NOT MERGE BEFORE #1816

This PR is stacked on #1816 (feat/agent-list-posts) and must not be merged until #1816 is merged. The agent needs postsListTool (from #1816) to find the post ids this tool operates on, and merging a stacked PR before its base folds it into the base branch instead of main.

Why

Completes the agent side of the batch-settings-update flow (a user scheduled dozens of TikTok posts with the wrong posting method and wanted an agent to fix them): #1816 lets the agent list posts, #1817 (merged) added the update capability as PUT /public/v1/posts/:id/settings + PostsService.updatePostSettings, and this PR exposes that same service method as an agent/MCP tool. This is the settings-only rework of the closed #1689, per its review feedback.

What changed

  • postSettingsTool (agent / MCP): thin wrapper over PostsService.updatePostSettings - converts the agent's [{key, value}] settings into an object and calls the shared method, so the tool and the public endpoint share one path (Controller -> Service -> Repository) and cannot drift. Merges only the passed keys into the existing settings; content and publish date stay untouched, so the running publish workflow is never restarted. Errors come back as {errors} so the agent can read and retry.
  • Registered in the tool list; agent prompt updated: find posts with postsListTool, apply settings changes directly with postSettingsTool, never open the populated modal for an existing post (it only creates, so editing through it would duplicate), and there is no delete capability (never offer deletion).

Testing (end to end)

Verified with three MCP clients: Claude Code (local MCP), and claude.ai + ChatGPT custom connectors over a cloudflared tunnel - in all of them the agent listed the posts, updated the draft's settings via postSettingsTool, and relayed the guards correctly.

  • postSettingsTool on a future TikTok draft: content_posting_method UPLOAD -> DIRECT_POST (the exact original use case) - applied, and merge semantics verified: only the passed key changed; title, privacy, toggles, content, publish date and state untouched.
  • Happy-path settings update on a scheduled (QUEUE) post as well.
  • Guards, each rejecting with no side effects: a published post ("Only scheduled posts that were not published yet..."), a nonexistent id ("Post not found"), and settings that fail validation on a scheduled post ("Carousel can only be created with 2 or more images and no videos") - the rejected update wrote nothing.
  • Nothing was published externally during testing (draft-only targets; the one scheduled test post was deleted).

Found in passing (pre-existing, all surfaces, not addressed here): the LinkedIn providers never declare their dto, so validatePosts skips settings-DTO validation for LinkedIn entirely (LinkedinDto exists but is unwired) - a wrongly-typed settings value is accepted. Follow-up candidate.

Related changes in other repos

  • postiz-agent PR #13 (agent guidance for the settings-update flow).
  • postiz-docs: postSettingsTool in the MCP docs + the Update Post Settings endpoint page (branch feat/update-post-settings-docs).

giladresisi and others added 4 commits August 11, 2026 15:28
Adds postsListTool (agent / MCP) so an agent can find the org's posts.
It calls the same PostsService.getPosts as the public "List Posts"
endpoint (GET /public/v1/posts), with the same inputs (startDate,
endDate, optional customer) and the same source data, so the tool and
the API stay aligned instead of drifting into a separate query. Each
item carries the post id (to pass to updatePostSettingsTool), publish
date, state, content, channel and current provider settings.

Also selects `settings` in getPosts, so both the tool and the public
List Posts endpoint now return each post's provider settings (needed to
know which posts require a settings change). This adds a `settings`
field to the GET /public/v1/posts response.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tomer

getPosts declared the integration relation filter twice in the same where
object: once with { deletedAt: null, organizationId } and again, only when
a customer is passed, with { customerId }. The second key overwrote the
first, so filtering by customer dropped the soft-delete and org checks and
surfaced posts on soft-deleted integrations. Merge customerId into the
single integration filter instead (as getPostsList already does).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
An external MCP agent (Claude Code), right after listing posts, offered
the user to delete one - there is no delete tool, so the offer was a
hallucination. External clients never see the internal agent prompt;
the only text they read is the tool descriptions and the agent/ask_postiz
description, so the guard has to live there:

- postsListTool description: posts cannot be deleted through these
  tools; never offer deletion, point the user to the Postiz app.
- Agent description: "manage and schedule" -> "schedule and list", so
  external clients don't infer edit/delete capabilities from "manage".

Validated: re-ran the same flow in Claude Code, claude.ai and ChatGPT
over MCP - no delete offers.
… agent

Thin agent/MCP wrapper over PostsService.updatePostSettings (shipped in
the public-API PR): converts the agent's [{key,value}] settings into an
object and calls the shared service method, so the tool and the
PUT /public/v1/posts/:id/settings endpoint share one path and cannot
drift. Errors are returned as {errors} so the agent can read and retry.

Registered in the tool list; the agent prompt tells it to find posts
with postsListTool, apply settings changes directly with
postSettingsTool (never the populated modal, which would duplicate the
post), and that there is no delete capability.
@postiz-contribution postiz-contribution Bot added the contribution:approved Approved contributor label Aug 11, 2026
@postiz-agent

postiz-agent Bot commented Aug 11, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Comment on lines +769 to +772
const coverPath = isArticle
? firstPost?.settings?.article_cover?.path
: undefined;
const coverMediaId = coverPath

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The article_cover image path is not resolved to an absolute path before being passed to readOrFetch, causing file-not-found errors for images stored with relative paths.
Severity: HIGH

Suggested Fix

The article_cover.path should be resolved to an absolute path before being passed to readOrFetch. This can be done by applying the same logic used in updateMedia, which prepends process.env.UPLOAD_DIRECTORY to relative paths. Ensure the path is resolved before it is used.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: libraries/nestjs-libraries/src/integrations/social/x.provider.ts#L769-L772

Potential issue: The `article_cover` path for X articles is retrieved from post
`settings` and used directly in `readOrFetch`. Unlike regular media paths processed via
`updateMedia`, this path is not resolved to an absolute filesystem path. If the
`article_cover.path` is stored as a relative path (e.g., `uploads/image.jpg`),
`readOrFetch` will fail when calling `readFileSync` because it requires an absolute path
or a full HTTP URL. This will cause the finalization of any X article post with a
locally-hosted cover image to fail.

Did we get this right? 👍 / 👎 to inform future reviews.

Base automatically changed from feat/agent-list-posts to main August 11, 2026 10:13
# Conflicts:
#	libraries/nestjs-libraries/src/chat/load.tools.service.ts
#	libraries/nestjs-libraries/src/chat/tools/tool.list.ts
@nevo-david
nevo-david added this pull request to the merge queue Aug 11, 2026
Merged via the queue into main with commit 013db1d Aug 11, 2026
11 checks passed
@nevo-david
nevo-david deleted the feat/post-settings-tool branch August 11, 2026 10:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contribution:approved Approved contributor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants