OpenAI dropped two-way MCP. Make it wake ChatGPT when something changes in your work.
Before, you called your MCP server. Now your MCP server can start the conversation. Pick one event from your own work, such as a failed deploy, a new order or a new bug report. Paste one prompt into Claude Code or Codex, and watch that event land in a ChatGPT Work chat, where ChatGPT does the task you gave it. The PDF teaches the parts you may not have met yet: webhooks, tunnels, signing secrets and Work chats.
The guide teaches the parts in learning order, then gives the build prompt, the exact ChatGPT steps, the fixes, and what to change so it keeps running.
01Pick one event from your work and the task ChatGPT should do
02Paste one prompt into Claude Code or Codex
03Subscribe in a ChatGPT Work chat and fire a test event
Pasted into Claude Code or Codex
1 prompt
Steps
6
From a sent event to the chat, in our one test
About 5 min
The spec, so names and fields can change
Draft
What you will have when you finish
One event from your own work, arriving in a ChatGPT Work chat, where ChatGPT does the task you gave it.
An MCP server on your computer that offers one event you chose and one read-only tool
A ChatGPT plugin pointed at that server, and a Work chat subscribed to your event
A fire command that sends a signed test event, and ChatGPT doing your task when it lands
A hook, written by your agent and installed on your yes, so your real script, app or watcher fires the same event
A log of every request ChatGPT made, so you can see where a failed run stopped
Six steps
You build in Claude Code or Codex. The result runs in a ChatGPT Work chat. We performed this on 6 October 2026 in Claude Code, with the bug report event from the reel. The specification is a draft, so the prompt tells your agent to read OpenAI's pages before it writes anything.
Claude Code or Codex, Python 3, cloudflared, and a ChatGPT account that can add a custom MCP server. OpenAI names no plans: "Account and workspace policies apply to adding and using custom MCP servers." Tested with Claude Code on Windows. Codex, Mac and Linux are untested.
01
Decide your event and your task before you build anything. An event is one thing that happens in your work and is worth being woken for: a failed build or deploy, a new order or payment, a new form or support entry, a new issue or comment. The task is what ChatGPT does when the event arrives. In the reel the event is bug_report.created and the task is to draft a fix plan.
Your part: write both down as one sentence, such as "When a deploy fails, summarize the error and draft a fix plan." Start with a task that drafts or summarizes. OpenAI's docs say events can arrive out of order and that ChatGPT can group several into one run, so pick a task that is safe to repeat.
02
Make an empty folder, start Claude Code or Codex in it, and paste the prompt. It asks you three questions, proposes an event name and a payload, and waits for your yes. Then it writes one small Python MCP server: one read-only tool, your event, the three event methods (events/list, events/subscribe, events/unsubscribe), webhook signing, a fire command, and a self-test that plays ChatGPT's part on your own computer.
Paste into Claude Code or Codex
Build me a small MCP server that uses MCP Events in ChatGPT: ChatGPT subscribes to one event from my own work, and my server pushes that event into a ChatGPT Work chat with a signed webhook.
START BY ASKING ME THREE QUESTIONS, then wait for my answers
1. What in my work should wake ChatGPT? Offer these as examples: a failed build or deploy, a new order or payment, a new form or support entry, a new issue or comment, a new bug report (bug_report.created).
2. What should ChatGPT do when it arrives? For example: draft a fix plan, summarize it, draft a reply for me to review.
3. Where does that event come from today: an app, a script, a CI job, a form, a folder?
From my answers, propose an event name in the form thing.happened (for example build.failed or bug_report.created), a payload of three to five short fields, and one read-only tool that lists recent items. Show me the proposal and wait for my yes before you write code.
I have not built or hosted an MCP server before. Before each stage, tell me in one or two plain sentences what you are about to do and why.
READ THESE FIRST. The spec is a draft, so follow the pages over anything you remember:
- https://developers.openai.com/plugins/build/mcp-events
- https://developers.openai.com/plugins/deploy/connect-chatgpt.md
- https://developers.openai.com/plugins/quickstart.md
CHECK
- Run python --version (or py -3 --version) and cloudflared --version. If one is missing, stop and give me the install command for my system.
BUILD
- In this folder, one server file plus one self-test file. Use only the Python standard library and hand-write the JSON-RPC over HTTP POST at /mcp (http.server worked for the guide's author). Do not install an MCP SDK.
- Use streamable HTTP with plain JSON responses at /mcp. No Server-Sent Events: Cloudflare Quick Tunnels do not support them.
- Protocol version 2026-07-28. Answer server/discover with supportedVersions ["2026-07-28"] and capabilities {"tools": {}, "events": {}}.
- Also answer the older initialize handshake (echo the client's protocolVersion), ping, tools/list and tools/call. Reply 202 to notifications (requests with no id). Put the resultType field the OpenAI page shows in every result.
- One read-only tool: the list tool we agreed. emit appends each event to a gitignored file and the list tool reads that file, so the list is the same from every process.
- One event: the one we agreed, with a name, a description, delivery ["webhook"], an inputSchema that takes no arguments, and a payloadSchema for the fields we agreed. The payload carries facts. Never put instructions to the model in it.
- events/list returns that event.
- events/subscribe:
- accept only delivery mode "webhook"
- require an https callback URL that resolves to a public address
- require a secret of the form whsec_ + base64 that decodes to 24 to 64 bytes
- POST a signed {"type": "verification", "challenge": "<random>"} to the callback, with its own unique webhook-id, and store the subscription only if a 2xx comes back echoing the same challenge (constant-time compare). On failure return JSON-RPC error -32015.
- same callback URL + event + arguments gives the same subscription id, so a repeat call updates it
- return {id, refreshBefore, cursor: null, truncated: false} with a 24 hour lifetime
- events/unsubscribe removes the subscription and succeeds even if it is already gone.
- Store subscriptions in a file so they survive a restart. That file holds signing secrets: gitignore it and never print a secret. If a delivery comes back 410, delete that subscription.
- Sign with Standard Webhooks. Headers: Content-Type application/json, webhook-id, webhook-timestamp (Unix seconds), webhook-signature = "v1," + base64(HMAC-SHA256(key, "<webhook-id>.<timestamp>.<body>")), X-MCP-Subscription-Id. The key is the base64-decoded part after whsec_. Serialize the body once and send those exact bytes. 10 second timeout, no redirects (urllib follows them, so use http.client), body no larger than 256 KiB.
- One function, emit, that takes the payload fields, builds {eventId, name, timestamp, data, cursor: null} with a fresh eventId (webhook-id equals eventId, timestamp is ISO 8601 with a timezone), and sends it to every live subscription (one whose refreshBefore has not passed). Everything that fires an event calls this one function.
- A fire command for testing, for example: python server.py fire "Checkout button does nothing on mobile Safari". It takes the main text field as its argument, fills the other fields from flags or defaults, calls emit, and prints the subscriber count and the HTTP status for each.
- A second listener on 127.0.0.1:8788, which the tunnel never exposes, with one route: POST /emit with a JSON body calls emit. My own app or script will use it later. /emit accepts only Content-Type application/json and an X-Emit-Token header, compared in constant time with a gitignored token file that the server creates if it is missing. Reject anything else with 403, so a web page open in my browser cannot fire events.
- Log every incoming method with its MCP-Protocol-Version header, and log the client info on server/discover and initialize. I will need that log to debug the ChatGPT side.
- Self-test: a fake receiver that subscribes over HTTP, echoes the challenge and verifies the signature on a delivered event. Compare header names case-insensitively. The self-test starts its own copy of the server on other ports (for /mcp and /emit), with its own temporary data folder and a test-only flag that allows http://127.0.0.1 callbacks, then stops it. The server refuses that flag on port 8787 or with the real data folder.
RUN
- Run the self-test, then start the server on 127.0.0.1:8787. Handle requests on threads (ThreadingHTTPServer, with allow_reuse_address off so a second copy cannot share the port), so one slow callback does not block the next request.
- It needs a public HTTPS URL. Before the tunnel opens, tell me plainly that anyone who learns that URL can call the tool and subscribe, so the payload must hold no secrets, private paths or customer data. The command: cloudflared tunnel --url http://127.0.0.1:8787
- If your permissions or sandbox block the tunnel, stop and give me the command. In Claude Code I will run it with the ! prefix in this session. In Codex I will run it in a second terminal and paste back the URL.
- Once the tunnel is up, call events/list through the public URL and show me the result.
- Do not register this server in your own MCP settings (Claude Code's MCP config, Codex's config.toml). In the author's test a plain MCP connection opened with initialize on an older protocol and fetched tools only. ChatGPT asked for events once the server was added as a plugin.
THEN PRINT THESE STEPS FOR ME, with my real URL, event name and task filled in
1. chatgpt.com/plugins, plus button, Add custom MCP server. A name and a description, Server URL set to the public URL ending in /mcp, Authentication set to No authentication (this test server has no login). Read the warning, select I understand and want to continue, then Create as a plugin.
2. On the plugin page, check my event is listed beside the tool. If it is missing, select Refresh.
3. Open the plugin and select the plus button to install it.
4. On the ChatGPT home page switch Chat to Work (desktop app: Work with Cloud selected). New chat, type @, pick the plugin, and send: Subscribe to the <event name> event. When one arrives, <my task>.
5. Tell you when ChatGPT says it is monitoring.
WHEN I SAY I HAVE SUBSCRIBED
- Confirm the log shows events/subscribe on 2026-07-28 and one stored subscription before firing anything. If there is none, say so and do not fire.
- Fire one event and report the status. Use different text each time. ChatGPT flagged identical text as a duplicate in the author's test.
- A 2xx means ChatGPT received it. ChatGPT processes the event asynchronously, and in the author's test the chat message took about five minutes. Do not fire again because the chat is quiet.
- If the tunnel restarts, the URL changes: tell me to update the plugin's Server URL and select Refresh.
THEN SHOW ME HOW MY REAL SOURCE WOULD FIRE IT
- From my answer to question 3, write the smallest hook that calls emit when the real thing happens: a few lines in my local script that run the fire command by absolute path and log if the hook itself fails, or a POST to the local /emit route from an app on this computer. If my source runs on another machine (hosted CI, a SaaS form, a store), say so, and do not expose /emit. Offer a small local watcher that polls that source and calls emit.
- Show me the hook. Do not install it or touch my real systems until I say yes. When I say yes, install it, then ask me to make the real thing happen once and watch the log with me.
- Check that the task ChatGPT runs cannot cause the same event again. If it could, tell me how the loop would happen.
WHEN I SAY I AM DONE
- Stop the server, remind me to stop the tunnel, and remind me to tell ChatGPT to stop monitoring.
Skip OAuth, retries and a database for this first run. Mark each shortcut with a one-line comment that says what a server I keep would do.
Your part: answer the questions with your real event, approve the file and run commands, and check that the self-test passes before you go on. We ran the bug report build with ChatGPT in Claude Code. The prompt on this page was then run once more on our computer, without ChatGPT, for a failed backup script: the server built and its self-test passed. Codex is untested. Adding the server to your agent's own MCP settings does nothing for ChatGPT, so skip it.
03
Put the server on a public HTTPS URL. ChatGPT runs on the internet and your server runs on your computer, so ChatGPT cannot reach it yet. A tunnel gives your computer a temporary public address. A Cloudflare Quick Tunnel needs no account and no domain, and Cloudflare offers it for testing and development.
Run this yourself if the agent cannot
cloudflared tunnel --url http://127.0.0.1:8787
Your part: if the agent is blocked from opening the tunnel, run the command yourself. In Claude Code, type ! and then the command, which runs it in your session. Auto mode refused the tunnel in our test. In Codex, run it in a second terminal and paste the URL back. Copy the https address that ends in trycloudflare.com and add /mcp to the end. The address changes every time the tunnel restarts. Only cloudflared was tested.
04
Add the server to ChatGPT as a plugin. Go to chatgpt.com/plugins, select the plus button, then Add custom MCP server. Give it a name and a description, paste your address into Server URL with /mcp on the end, and set Authentication to No authentication. Read the risk warning, select I understand and want to continue, then Create as a plugin. Open the plugin and select the plus button to install it. ChatGPT calls this a plugin. The reel says connector.
Add custom MCP server
Your part: check that the plugin page lists your event beside the tool. If you see the tool alone, select Refresh. No authentication is how our test ran on 6 October 2026, and ChatGPT's form accepted it. Treat that as a short test setting: while the tunnel is open, anyone who has the address can reach the server. A server you keep needs a login.
05
Subscribe in a ChatGPT Work chat. On the ChatGPT home page, switch Chat to Work. Start a new chat, type @ to pick your plugin, and send one sentence that names the event and gives the task. OpenAI's docs list Work chats on the web, Work chats in the desktop app with Cloud selected, and dots.
Send in the Work chat, with your own event and task
Subscribe to the <your event name> event. When one arrives, <your task>.
Your part: wait until ChatGPT says it is monitoring, then tell your agent. Ask it to confirm that the server log shows events/subscribe on 2026-07-28 and one stored subscription. The reel's sentence was "Subscribe to the bug_report.created event. When one arrives, draft a fix plan." If ChatGPT says the plugin "exposes only" its tool, the log will show 2025-06-18 and no server/discover: remove it and add it again at chatgpt.com/plugins with Create as a plugin. If ChatGPT says it has nothing to subscribe with, check that you are in a Work chat and that your sentence names the event.
06
Fire a test event and wait. Ask your agent to fire one, or run the command it printed for you. The output should show 1 subscriber and a 200. Then watch the Work chat: the event arrives and ChatGPT does your task. Once that works, tell your agent yes on the hook, and make the real thing happen once: run the script, submit the form, save the order. That arrival is the finish line.
The reel's example. Run the command your agent printed
python server.py fire "Checkout button does nothing on mobile Safari"
Your part: be patient. A 200 means ChatGPT received the event, and in our test the message reached the chat about five minutes later. A quiet chat is no reason to fire again. Change the text on each test, because ChatGPT flagged identical text as a duplicate. When you are done, tell ChatGPT to stop monitoring, then stop the tunnel and the server.
While the tunnel is open, the test server sits on the public internet with no login, so stop it when you finish. The PDF covers what to change before you leave one running: a login, a stable URL, a database for subscriptions, retries, and your real event source.
The specifics
What it costs
The guide and the prompt are free, a Cloudflare Quick Tunnel needs no account or domain, and OpenAI names no plans for custom MCP servers.
Tested on
Claude Code on Windows with cloudflared and ChatGPT, on 6 October 2026. The prompt on this page also built and passed its self-test locally. Codex, Mac and Linux are untested.
Where it runs
OpenAI's docs list Work chats on ChatGPT web, Work chats in the desktop app with Cloud selected, and dots.
How long it took to arrive
In our one test ChatGPT answered 200 and the message reached the chat about five minutes later, an estimate. OpenAI's docs give no delay.
Where your data goes
Your server sends each event over HTTPS to ChatGPT's callback URL, and the signing secrets stay in a file on your computer.
What is a draft
The MCP Events specification is a draft, and ChatGPT supports webhook delivery only, so method names and fields can change.
Questions people ask
What stops ChatGPT acting on a fake event?
ChatGPT hands your server a signing secret when it subscribes, and your server signs every event with it. Guard the subscriptions file, because it holds that secret.
Will ChatGPT act without asking me?
Yes, in our test it did. When the event arrived, ChatGPT ran the task from the subscribe sentence in that Work chat. Start with a task that drafts or summarizes, and check the task cannot trigger the same event again.
Why did nothing show up in my chat?
Wait first. A 200 means ChatGPT received the event, and in our test the message took about five minutes. If the fire command prints 0 subscribers, ChatGPT never subscribed: use a Work chat and name the event in your sentence.
Can I leave it running?
Not yet. The test server has no login, and its address changes whenever the tunnel restarts. To keep one running, add a login, a stable URL, a database for subscriptions and retries. The PDF walks through each change.
That was the free part
The file is yours. Nothing above asked for an email.
If it earned a minute of your time, the paid part is one scroll down.
Everything above this line is free and stays free. Below it is what we sell. The prices are the prices, and the two subscriptions carry 30 days money back on the first month.
Mica Social Media Analyzer
Your Instagram, answered by the ChatGPT or Claude you already use. Connect the account once, then ask in plain words what worked and what to post next.
Who it is for
You post on Instagram and you are done guessing from the Insights tab.
Price
$29/ month
What you get
Which Reels held attention, ranked, without opening Insights
Readings at 1, 24 and 72 hours after every post, so you see what happened while it was moving
Next captions written in the style of your most saved posts
Read only. It never posts or changes anything
The facts
7 days free with a card on file. About a dollar a day after that.
Our guarantee
30 days money back on the first month. Email guru@graniteai.co and we refund it.
Send us access to your repo. We run the nine-lane review, change nothing, and hand back the report with a 30 minute call on the three gaps to close first.
Who it is for
You have a product you charge for, or want to, and you would rather read the verdict than run the agents.
Price
$349One week
What you get
The report at docs/commercial-readiness.md: a verdict, a scorecard, every gap with the file to open and how to verify it
Legal, security, billing, support, marketing, operations and the rest, checked at once
A 30 minute call on the three gaps to close first
Nothing in your repo changed. The workflow prompt is yours to run afterward
The facts
The review is the one in the video. On our own product it found 87 gaps. The overnight run that followed was 148 commits and one pull request.
Our guarantee
If the review finds no gap worth your time, you pay nothing.
The first run, with Jason
We run the review on your repo together, on a call, and you leave with the report in your repo and the three gaps to close first.
Who it is for
You have the kit and a repo, and you would rather do the first run with someone who has done it.
Price
$9960 minutes
What you get
The review running on your machine, in your Claude Code
The verdict read together, and the three gaps picked
The workflow prompt set up so you can run it that night
The call recording
The facts
New. You would be one of the first. The run itself is the one in the video: 190 agents, 148 commits, one pull request.
Our guarantee
If the review will not run on your setup during the call, there is no charge.
Mica Social
An AI that writes, designs, checks and publishes a post to your Facebook and Instagram every day. You send a photo when you have one. It does the rest.
Who it is for
You know the feed should be alive every day and it is not.
Price
$49/ month
What you get
A finished post every day, with a photo made for it. Nothing to shoot
Timed to your local weather, season and county numbers
Five checks before anything publishes. No approvals, no calendar, no logins
Every photo you send becomes its own extra post
The facts
An agency running a daily feed bills $500 to $1,500 a month. 3 days free with a card on file.
Our guarantee
30 days money back on the first month. Email guru@graniteai.co and we refund it.