Blog/2025-10-17/Custom GPTs: Difference between revisions
Created page with "thumb|Complete with unique logo I was a bit sceptical about OpenAI's [https://openai.com/index/introducing-gpts/ Custom GPTs] when they launched but that's because I did not know that they have 'Actions'. One of the things that Julie has wanted for a while is an LLM-assisted note taker. She wants to take short hand notes while in meetings and then have an LLM organize these, put a summary at the top, and note action items and..." |
No edit summary |
||
| (6 intermediate revisions by 2 users not shown) | |||
| Line 35: | Line 35: | ||
By the end, I thought perhaps it was the complexity of the API spec that made the whole thing hard to use. Besides, there are some problems that GPT-5 (even with Thinking enabled) just spazzes out on and others that Claude does that on. So I decided to try using Claude to write a constrained MCP that would only update this one Notion 'database' Julie by adding new notes, querying for notes, or updating notes. It did a marvelous job with a few edits, none of which I did with an IDE, and then I used [[One Quick Way To Host A WebApp|my standard flow for hosting this]]. It requires an `X-API-Key` header that is well-supported by ChatGPT's Actions feature, and the MCP server also generates an OpenAPI spec for use here. | By the end, I thought perhaps it was the complexity of the API spec that made the whole thing hard to use. Besides, there are some problems that GPT-5 (even with Thinking enabled) just spazzes out on and others that Claude does that on. So I decided to try using Claude to write a constrained MCP that would only update this one Notion 'database' Julie by adding new notes, querying for notes, or updating notes. It did a marvelous job with a few edits, none of which I did with an IDE, and then I used [[One Quick Way To Host A WebApp|my standard flow for hosting this]]. It requires an `X-API-Key` header that is well-supported by ChatGPT's Actions feature, and the MCP server also generates an OpenAPI spec for use here. | ||
This worked wonderfully, and with a little work with the prompt Julie and I were able to finish her custom Julie Notes GPT. | This worked wonderfully, and with a little work with the prompt Julie and I were able to finish her custom Julie Notes GPT. The final architecture looks like: | ||
# A minimal Notion API | |||
# A wrapper around it to provide MCP functionality | |||
# An auth check around that for an X-API-Key | |||
# The whole thing wrapped into a container image and pushed to my homeserver registry (itself a quadlet) | |||
# All that hosted as a quadlet on Ubuntu | |||
# Cloudflare Tunnel proxying to the quadlet | |||
== Things We Learned == | == Things We Learned == | ||
| Line 49: | Line 56: | ||
Overall, the end result is pretty useful already, and I keep thinking that I should build some kind of MCP so that I can expose our Apple Reminders (which is what Julie and I use currently to track our home/personal to-do lists). | Overall, the end result is pretty useful already, and I keep thinking that I should build some kind of MCP so that I can expose our Apple Reminders (which is what Julie and I use currently to track our home/personal to-do lists). | ||
== Personal Task Management == | |||
[[File:Screenshot - Home Reminders MCP.png|thumb|The bridge server]] | |||
The next day, Julie and I decided to actually build the Apple Reminders functionality. The problem with Reminders is that there's no way to access the data without using EventKit from a MacOS computer that is logged into an Apple ID that has access to that Reminders list. | |||
So that's what we ended up doing: | |||
# Wrote a small GUI bridge that | |||
## has a button to request permissions | |||
## has a button to start the HTTP server | |||
# Used Amphetamine to keep the MacOS laptop running persistently | |||
# Used VNC to log into the laptop's GUI to run the program | |||
# Had the Cloudflare tunnel continue to terminate on my home server | |||
## And forward requests to the Swift HTTP server on the MacOS laptop | |||
Overall, it works all right. The problem is that the EventKit API is incredibly sparse. Apple doesn't allow you to assign reminders to people, has no visibility of sections or sub-lists, and doesn't know how to do subtasks. | |||
For a short while, I had one version of the HTTP server using accessibility data to indent list items and so on in order to make sublists. In the end, it just is what it is and we don't use it a lot. | |||
{{#seo:|description=Julie's AI-powered note-taking solution leverages a custom GPT model to organize meeting notes, summarize key points, and provide synthesized information}} | |||
[[Category:AI]] | |||
[[Category:Blog]] | [[Category:Blog]] | ||
Latest revision as of 09:13, 2 December 2025

I was a bit sceptical about OpenAI's Custom GPTs when they launched but that's because I did not know that they have 'Actions'. One of the things that Julie has wanted for a while is an LLM-assisted note taker. She wants to take short hand notes while in meetings and then have an LLM organize these, put a summary at the top, and note action items and so on. Then she wants to be able to ask about someone and have the notes references and a synthesis given to her.
Requirements[edit]
- The thing has to work with ChatGPT - Julie is used to it, already uses it all the time, and would prefer to keep that
- The notes have to be human-readable - Julie intends to read the notes and look at them herself
Early Attempts[edit]
Notion MCP[edit]
This is a pretty reasonable use-case for an LLM, and at first I thought it must surely already have been built. In fact, Notion does have a remote MCP that you can use OAuth to authenticate into. This is a pretty good start, and you can then create a Custom GPT that reads from Notion pages and provides information and so on.
Two things that went wrong with this are:
- On read, it scans far too many pages to get to the relevant one
- It doesn't seem to want to write anything
The former is annoying because it makes everything rather slow, but the latter renders things useless. The Notion MCP page says that it should be able to read and write but when I follow the Notion MCP instructions to install it on ChatGPT in either Sync or Deep Research / Agent mode the LLM insists that it cannot write data.
Google Keep MCP[edit]
She'd been using Google Keep, for which you can build an MCP using an unofficial Python API that also requires you to use the legacy API token flow. That whole thing seemed a bit iffy, so I decided it was probably a bad fit. She wasn't particularly wed to Google Keep. She just wanted the idea of separate notes that she could look at with some collating notion. She's used Apple's Notes before as well.
Notion API[edit]
Julie'd gotten quite far with the Custom GPT thing by the time I asked her what she was doing. The GPT Builder LLM had suggested that she get a Notion Integration token, then write the OpenAPI spec that matches Notion's API for the Custom GPT Actions feature and use that as a custom GPT. This wasn't such a bad idea, but when I observed her at work and tried to help out we realized it wasn't going to work. The OpenAPI spec that ChatGPT 5 generated wasn't close to compliant for the GPT Builder (which is quite strict) and the GPT Builder could not format the JSON payload well enough for the API to handle it.
But this is a pretty common kind of error for LLMs: they do better when told a simple thing like "to create a note use `createNote(body: string)`" than if you tell them a complicated thing with multiple nested JSONs and this and that. As it so happened, the generated OpenAPI spec simply wouldn't provide the Custom GPT with a tool that it could use to create notes. Or rather, it would create notes but they'd all be empty. It couldn't update them either.
Final Solution[edit]
By the end, I thought perhaps it was the complexity of the API spec that made the whole thing hard to use. Besides, there are some problems that GPT-5 (even with Thinking enabled) just spazzes out on and others that Claude does that on. So I decided to try using Claude to write a constrained MCP that would only update this one Notion 'database' Julie by adding new notes, querying for notes, or updating notes. It did a marvelous job with a few edits, none of which I did with an IDE, and then I used my standard flow for hosting this. It requires an `X-API-Key` header that is well-supported by ChatGPT's Actions feature, and the MCP server also generates an OpenAPI spec for use here.
This worked wonderfully, and with a little work with the prompt Julie and I were able to finish her custom Julie Notes GPT. The final architecture looks like:
- A minimal Notion API
- A wrapper around it to provide MCP functionality
- An auth check around that for an X-API-Key
- The whole thing wrapped into a container image and pushed to my homeserver registry (itself a quadlet)
- All that hosted as a quadlet on Ubuntu
- Cloudflare Tunnel proxying to the quadlet
Things We Learned[edit]
- The GPT Builder is pretty good at getting a software newbie up to quite a distance
- Cloudflare Tunnels don't work on nested domains (so you can't do a.b.julieyukang.com)
- Writing constrained tools that an LLM can call actually allows for more useful LLMs
- Writing constrained tools allows for far easier testing than trying to implement the API with Actions
- An MCP is a decent structure, but auth etc. is complicated
- A HTTP API is good because Claude Code can test itself and loop on feedback
- You can use the `x-openai-isconsequential` header to allow ChatGPT to enable the option for the user to select "Always Allow" on action calls
Overall, the end result is pretty useful already, and I keep thinking that I should build some kind of MCP so that I can expose our Apple Reminders (which is what Julie and I use currently to track our home/personal to-do lists).
Personal Task Management[edit]

The next day, Julie and I decided to actually build the Apple Reminders functionality. The problem with Reminders is that there's no way to access the data without using EventKit from a MacOS computer that is logged into an Apple ID that has access to that Reminders list.
So that's what we ended up doing:
- Wrote a small GUI bridge that
- has a button to request permissions
- has a button to start the HTTP server
- Used Amphetamine to keep the MacOS laptop running persistently
- Used VNC to log into the laptop's GUI to run the program
- Had the Cloudflare tunnel continue to terminate on my home server
- And forward requests to the Swift HTTP server on the MacOS laptop
Overall, it works all right. The problem is that the EventKit API is incredibly sparse. Apple doesn't allow you to assign reminders to people, has no visibility of sections or sub-lists, and doesn't know how to do subtasks.
For a short while, I had one version of the HTTP server using accessibility data to indent list items and so on in order to make sublists. In the end, it just is what it is and we don't use it a lot.
