Таърихи нав

Your Last MCP to Schedule All Your Social Posts! 🤯

аз ҷониби Nevo David8m2025/04/15
Read on Terminal Reader

Хеле дароз; Хондан

Postiz is an app that lets you schedule all your social posts from a single chat. It's built with NestJS, so when using an SSE route, it closes the observable once it disconnects, allowing you to remove everything from memory.
featured image - Your Last MCP to Schedule All Your Social Posts! 🤯
Nevo David HackerNoon profile picture

I'm pretty addicted to the Last Of Us series. Sorry about the cover.


😉MCPs are everywhere and for a good reason. It's the next step in the evolution of apps.


Being able to use everything from a single chat without accessing any app. It feels native for Postiz to schedule all your social posts from the chat! So, I started to dig into the Postiz code and added to it!

Postiz


MCPE

MCPEMCPE

The MCP Repository Is a Bit Weird 🧐

Each MCP has a transport, which is the method the LLMs use to talk to our system.


There are two primary methods at the moment: Stdio, which is basically a command line, and SSE.

There are two primary methods at the moment


I don't really understand why they chose SSE—it's basically a long request that never ends and streams events to the client.


The problem with this method is that to send information back to the server, you must send another post request (as SSE is a one-way communication), which means you must hold the state.


In their example, they hold the state in the app's memory, and guess what? Many complain about memory leaks because the state is not erased when the user disconnects.


I would use WebSockets. They have a built-in sleep mode, and you don't have to maintain a state for it.


Digging In 🪠

I dug into Anthropic typescript SDK and was not amazed. It feels clunky. Many things are not being used in production, like "Resources." The way they require you to keep everything globally in memory is a disaster waiting to happen.


Also, it is tough to implement authentication and get the user from the context so we can get their details.


I implemented my own "Transport" using rxjs observables - it's fun. Postiz is built with NestJS, so when using an SSE route, it closes the observable once it disconnects, allowing you to remove everything from memory.

import EventEmitter from 'events';
import { finalize, fromEvent, startWith } from 'rxjs';

@Injectable()
export class McpService {
  static event = new EventEmitter();
  constructor(
    private _mainMcp: MainMcp
  ) {
  }

  async runServer(apiKey: string, organization: string) {
    const server = McpSettings.load(organization, this._mainMcp).server();
    const transport = new McpTransport(organization);

    const observer = fromEvent(
      McpService.event,
      `organization-${organization}`
    ).pipe(
      startWith({
        type: 'endpoint',
        data: process.env.NEXT_PUBLIC_BACKEND_URL + '/mcp/' + apiKey + '/messages',
      }),
      finalize(() => {
        transport.close();
      })
    );

    console.log('MCP transport started');
    await server.connect(transport);

    return observer;
  }

  async processPostBody(organization: string, body: object) {
    const server = McpSettings.load(organization, this._mainMcp).server();
    const message = JSONRPCMessageSchema.parse(body);
    const transport = new McpTransport(organization);
    await server.connect(transport);
    transport.handlePostMessage(message);
    return {};
  }
}
import EventEmitter from 'events'; import { finalize, fromEvent, startWith } from 'rxjs'; @Injectable() export class McpService { static event = new EventEmitter(); constructor( private _mainMcp: MainMcp ) { } async runServer(apiKey: string, organization: string) { const server = McpSettings.load(organization, this._mainMcp).server(); const transport = new McpTransport(organization); const observer = fromEvent( McpService.event, `organization-${organization}` ).pipe( startWith({ type: 'endpoint', data: process.env.NEXT_PUBLIC_BACKEND_URL + '/mcp/' + apiKey + '/messages', }), finalize(() => { transport.close(); }) ); console.log('MCP transport started'); await server.connect(transport); return observer; } async processPostBody(organization: string, body: object) { const server = McpSettings.load(organization, this._mainMcp).server(); const message = JSONRPCMessageSchema.parse(body); const transport = new McpTransport(organization); await server.connect(transport); transport.handlePostMessage(message); return {}; } }

Decorators FTW 🎖️

This is for you if you are a big fan of OOP frameworks like NestJS/Laravel/Spring. I created a cool decorator to create tools like API "endpoints."

  @McpTool({ toolName: 'POSTIZ_GET_CONFIG_ID' })
  async preRun() {
    return [
      {
        type: 'text',
        text: `id: ${makeId(10)} Today date is ${dayjs.utc().format()}`,
      },
    ];
  }

  @McpTool({ toolName: 'POSTIZ_PROVIDERS_LIST' })
  async listOfProviders(organization: string) {
    const list = (
      await this._integrationService.getIntegrationsList(organization)
    ).map((org) => ({
      id: org.id,
      name: org.name,
      identifier: org.providerIdentifier,
      picture: org.picture,
      disabled: org.disabled,
      profile: org.profile,
      customer: org.customer
        ? {
            id: org.customer.id,
            name: org.customer.name,
          }
        : undefined,
    }));

    return [{ type: 'text', text: JSON.stringify(list) }];
  }

@McpTool({
    toolName: 'POSTIZ_SCHEDULE_POST',
    zod: {
      type: eenum(['draft', 'scheduled']),
      configId: string(),
      generatePictures: boolean(),
      date: string().describe('UTC TIME'),
      providerId: string().describe('Use POSTIZ_PROVIDERS_LIST to get the id'),
      posts: array(object({ text: string(), images: array(string()) })),
    },
  })
  async schedulePost(
    organization: string,
    obj: {
      type: 'draft' | 'schedule';
      generatePictures: boolean;
      date: string;
      providerId: string;
      posts: { text: string }[];
    }
  ) {
    const create = await this._postsService.createPost(organization, {
      date: obj.date,
      type: obj.type,
      tags: [],
      posts: [
        {
          group: makeId(10),
          value: await Promise.all(
            obj.posts.map(async (post) => ({
              content: post.text,
              id: makeId(10),
              image: !obj.generatePictures
                ? []
                : [
                    {
                      id: makeId(10),
                      path: await this._openAiService.generateImage(
                        post.text,
                        true
                      ),
                    },
                  ],
            }))
          ),
          // @ts-ignore
          settings: {},
          integration: {
            id: obj.providerId,
          },
        },
      ],
    });

    return [
      {
        type: 'text',
        text: `Post created successfully, check it here: ${process.env.FRONTEND_URL}/p/${create[0].postId}`,
      },
    ];
  }
@McpTool({ toolName: 'POSTIZ_GET_CONFIG_ID' }) async preRun() { return [ { type: 'text', text: `id: ${makeId(10)} Today date is ${dayjs.utc().format()}`, }, ]; } @McpTool({ toolName: 'POSTIZ_PROVIDERS_LIST' }) async listOfProviders(organization: string) { const list = ( await this._integrationService.getIntegrationsList(organization) ).map((org) => ({ id: org.id, name: org.name, identifier: org.providerIdentifier, picture: org.picture, disabled: org.disabled, profile: org.profile, customer: org.customer ? { id: org.customer.id, name: org.customer.name, } : undefined, })); return [{ type: 'text', text: JSON.stringify(list) }]; } @McpTool({ toolName: 'POSTIZ_SCHEDULE_POST', zod: { type: eenum(['draft', 'scheduled']), configId: string(), generatePictures: boolean(), date: string().describe('UTC TIME'), providerId: string().describe('Use POSTIZ_PROVIDERS_LIST to get the id'), posts: array(object({ text: string(), images: array(string()) })), }, }) async schedulePost( organization: string, obj: { type: 'draft' | 'schedule'; generatePictures: boolean; date: string; providerId: string; posts: { text: string }[]; } ) { const create = await this._postsService.createPost(organization, { date: obj.date, type: obj.type, tags: [], posts: [ { group: makeId(10), value: await Promise.all( obj.posts.map(async (post) => ({ content: post.text, id: makeId(10), image: !obj.generatePictures ? [] : [ { id: makeId(10), path: await this._openAiService.generateImage( post.text, true ), }, ], })) ), // @ts-ignore settings: {}, integration: { id: obj.providerId, }, }, ], }); return [ { type: 'text', text: `Post created successfully, check it here: ${process.env.FRONTEND_URL}/p/${create[0].postId}`, }, ]; }

All the code can be found in Postiz here: https://github.com/gitroomhq/postiz-app/tree/main/libraries/nestjs-libraries/src/mcp

https://github.com/gitroomhq/postiz-app/tree/main/libraries/nestjs-libraries/src/mcp


And here: https://github.com/gitroomhq/postiz-app/tree/main/apps/backend/src/mcp

https://github.com/gitroomhq/postiz-app/tree/main/apps/backend/src/mcp

Force the LLM to Do Stuff 💪🏻

It would be nice to have a built-in option to force the LLM to do different stuff before it accesses our stuff.


I faced some interesting problems. Whenever I told Cursor to schedule a post for me, it tried to schedule it for 2024. This is the last time the model was trained.


I needed to pass some config details, so I created the POSTIZ_CONFIGURATION_PRERUN tool. Hopefully, the LLM will always call it before doing stuff.

POSTIZ_CONFIGURATION_PRERUN


But it ignored it many times (typical), so I had to be creative. In my POSTIZ_SCHEDULE_POST, I added a new property called configId and changed the config tool name to POSTIZ_GET_CONFIG_ID. The output of the config is:id: ${makeId(10)} Today date is ${dayjs.utc().format()}

POSTIZ_SCHEDULE_POSTconfigIdPOSTIZ_GET_CONFIG_ID.id: ${makeId(10)} Today date is ${dayjs.utc().format()}

It forced the LLM always to call it before, and the date was fixed! :)


It was even better for me because I knew it would send me UTC dates from now on.


Use-cases

I think that it works best when it is combined with multiple sets of tools, for example:

  • Connect it to Cursor and ask it to schedule a post about your work today.
  • Connect it to Notion and ask to schedule all the team's latest work on social media - check out Composio MCPs.
  • Connect it to any SaaS that has CopilotKit and schedule posts based on the app.
  • Connect it to Cursor and ask it to schedule a post about your work today.
  • Connect it to Notion and ask to schedule all the team's latest work on social media - check out Composio MCPs.
  • Composio MCPs
  • Connect it to any SaaS that has CopilotKit and schedule posts based on the app.
  • CopilotKit

    Postiz MCP

    Postiz is the most robust open-source social media scheduling tool - and now the only scheduler that offers MCP (natively, not with Zapier or something like that)

    Postiz


    With the new MCP, you can schedule all your posts from Cursor/Windsurf and Anthropic clients.


    Everything is 100% free, of course. :)


    If you like it, please don't forget to star us ⭐️https://github.com/gitroomhq/postiz-app
    OG

    https://github.com/gitroomhq/postiz-app
    OG

    OG
    L O A D I N G
    . . . comments & more!

    About Author

    Nevo David HackerNoon profile picture
    Nevo David@nevodavid10
    Head of growth at Novu - 20,000 stars.

    ТЕГИ овезон кунед

    ИН МАКОЛА ДАР...

    Trending Topics

    blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks