DEV Community

Cover image for Your last MCP to schedule all your social posts! 🀯
Nevo David
Nevo David Subscriber

Posted on

Your last MCP to schedule all your social posts! 🀯

MCP is the future

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 add it!

MCPE


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.

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 come.

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 {};
  }
}
Enter fullscreen mode Exit fullscreen mode

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}`,
      },
    ];
  }
Enter fullscreen mode Exit fullscreen mode

All the code can be found in Postiz here:
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


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.

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()}

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.


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)

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

Top comments (15)

Collapse
 
srbhr profile image
Saurabh Rai

Really awesome @nevodavid I'm gonna plug into my Claude Desktop and try it out!

Collapse
 
nevodavid profile image
Nevo David

Awesome! Make sure you check out
developers.cloudflare.com/agents/g...

As claude desktop uses only Stdio

Collapse
 
srbhr profile image
Saurabh Rai

Okay thanks!

Collapse
 
yonatan_lavy profile image
Yonatan Lavy

Very cool and useful MCP! I'll try using it to send social media updates on the tasks I complete, do you think it will work well?

And thank you for sharing your implementation details and struggles, very informative for when I'll make my own MCP for Dotallio.com !
(Though I wish I had those cool decorators in nextjs as well haha)

Collapse
 
nevodavid profile image
Nevo David

Well MCP is the thing right now, so I am sure!

Collapse
 
arindam_1729 profile image
Arindam Majumder

This is soo cool

I'm working on a playlist using MCPs

I'll give it a try

Collapse
 
nevodavid profile image
Nevo David

❀️

Collapse
 
time121212 profile image
tim brandom

Cool stuff!

Collapse
 
nevodavid profile image
Nevo David

Thank you!

Collapse
 
william123 profile image
William

MCP FTW!!

Collapse
 
nevodavid profile image
Nevo David

πŸš€

Collapse
 
henrywills22 profile image
Henry Willz

Pretty awesome, I want to try to connect it to a Figma MCP to document my work.

Collapse
 
nathan_tarbert profile image
Nathan Tarbert

Really awesome article!

Collapse
 
lovestaco profile image
Athreya aka Maneshwar

Awesome feature @nevodavid :)

Collapse
 
johnywalker21 profile image
johny walker

Awesome!

Some comments may only be visible to logged-in visitors. Sign in to view all comments.