Muutamia viikkoja sitten OpenAI esitteli Apps for ChatGPT. Kuten näet alla, se mahdollistaa yritysten ruiskuttaa tuotteensa suoraan chatiin auttaakseen tyydyttämään käyttäjän kehotuksen. Sovellus voidaan käynnistää joko nimenomaisella maininnalla tai kun malli päättää, että sovellus on hyödyllinen. So, what is a ChatGPT App? Asiakkaalle se on tapa saada rikkaampi käyttäjäkokemus ja toiminnallisuus, joka ylittää tekstin käyttöliittymän rajoitukset. Yritykselle se on tapa tavoittaa yli 800 miljoonaa ChatGPT-käyttäjää juuri oikeaan aikaan. Kehittäjälle se on MCP-palvelin ja web-sovellus, joka toimii iframe <= siitä olemme täällä puhumassa! demoa Tässä viestissä kävelen läpi yksinkertaisen kysely-sovelluksen rakentamisen, joka on esitetty alla, käyttämällä sitä esimerkkinä käytettävissä olevien ominaisuuksien osoittamiseksi. Tärkeä huomautus: Jos haluat seurata, tarvitset ChatGPT-maksullisen tilauksen, jotta voit ottaa käyttöön Kehittäjä-tilan. Jos haluat seurata, Normaali 20 dollarin kuukausittainen asiakassopimus riittää. Important Note: you will need a paid ChatGPT subscription to enable Developer Mode Korkeatasoinen virtaus Näin se toimii korkealla tasolla (vaiheiden todellinen järjestys voi vaihdella hieman): Ensinnäkin sovelluksen kehittäjä rekisteröi sen ChatGPT: ssä Tämä toteuttaa sovelluksen ja seisoo puolesta , ja se mahdollistaa ChatGPT: n kaltaisten mallien tutkimisen ja vuorovaikutuksen muiden palveluiden kanssa. ”ja ” ” tarvitsi luoda ChatGPT quiz sovellus. Tässä vaiheessa, providing a link to the MCP server (1) MCP Model Context Protocol tools resources ChatGPT learns and remembers what our app does and when it can be useful. Kun sovellus on jo olemassa ja käyttäjä pyytää kuten "Make a quiz about Sam Altman", ChatGPT tarkistaa, onko olemassa sovellus, jota se voi käyttää tekstin vastauksen sijaan paremman kokemuksen tarjoamiseksi käyttäjälle . (2) (3) Jos sovellus löytyy, ChatGPT tarkastelee sovelluksen tarvitsemia tietoja. Sovelluksemme tarvitsee vastaanottaa tiedot seuraavassa JSON-muodossa: (4) { questions: [ { question: "Where was Sam Altman born", options: ["San Francisco", ...], correctIndex: 2, ... }, ... ] } Tätä kutsutaan , ja lähettää sen appimme . ChatGPT will generate quiz data exactly in this format toolInput (5) Sovellus käsittelee ja tuottaa ChatGPT näyttää sovelluksen tarjoaman HTML-resurssin chat-ikkunassa ja aloittaa sen dataa . And finally, the user will see the app and will be able to interact with it . toolInput toolOutput toolOutput (6) (7) MCP-palvelimen rakentaminen Koodi repo meidän ChatGPT sovellus: . https://github.com/renal128/quizaurus-tutorial On kaksi hanketta: ja Ensinnäkin keskitymme joka käyttää yksinkertaista JavaScriptia etupäässä pitääkseen asiat yksinkertaisina. quizaurus-plain quizaurus-react quizaurus-plain Kaikki palvelimen koodi on tässä tiedostossa - vain noin 140 riviä koodia! https://github.com/renal128/quizaurus-tutorial/blob/main/quizaurus-plain/src/server.ts Serverin asennus On monia vaihtoehtoja MCP-palvelimen luomiseksi käyttämällä mitä tahansa tässä luetelluista SDK: stä: https://modelcontextprotocol.io/docs/sdk Tällöin käytämme . Typescript MCP SDK Alla oleva koodi osoittaa, miten se asetetaan: // Create an MCP server const mcpServer = new McpServer({ name: 'quizaurus-server', version: '0.0.1' }); // Add the tool that receives and validates questions, and starts a quiz mcpServer.registerTool( ... ); // Add a resource that contains the frontend code for rendering the widget mcpServer.registerResource( ... ); // Create an Express app const expressApp = express(); expressApp.use(express.json()); // Set up /mcp endpoint that will be handled by the MCP server expressApp.post('/mcp', async (req, res) => { const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true }); res.on('close', () => { transport.close(); }); await mcpServer.connect(transport); await transport.handleRequest(req, res, req.body); }); const port = parseInt(process.env.PORT || '8000'); // Start the Express app expressApp.listen(port, () => { console.log(`MCP Server running on http://localhost:${port}/mcp`); }).on('error', error => { console.error('Server error:', error); process.exit(1); }); Tärkeimmät kohdat: Express-sovellus on yleinen palvelin, joka vastaanottaa viestintää (kuten HTTP-pyynnöt) ulkopuolelta (ChatGPT:ltä) Käyttämällä Express, lisäämme /mcp-päätteen, jonka tarjoamme ChatGPT: lle MCP-palvelimemme osoitteeksi (kuten https://mysite.com/mcp) The handling of the endpoint is delegated to the MCP server, that runs within the Express app /mcp all of the MCP protocol that we need is handled within that endpoint by the MCP server mcpServer.registerTool(...) ja mcpServer.registerResource(...) on mitä käytämme toteuttamaan Quiz-sovelluksemme MCP työkalut Täyttäkäämme aukko sisällä Yllä olevassa kohdassa rekisteröidään ”työkalu”. mcpServer.registerTool(…) ChatGPT lukee työkalun määritelmän, kun rekisteröidämme sovelluksen, ja sitten, kun käyttäjä tarvitsee sitä, ChatGPT kutsuu työkalun aloittamaan kyselylomakkeen: // Add the tool that receives and validates questions, and starts a quiz mcpServer.registerTool( 'render-quiz', { title: 'Render Quiz', description: ` Use this when the user requests an interactive quiz. The tool expects to receive high-quality single-answer questions that match the schema in input/structuredContent: each item needs { question, options[], correctIndex, explanation }. Use 5–10 questions unless the user requests a specific number of questions. The questions will be shown to the user by the tool as an interactive quiz. Do not print the questions or answers in chat when you use this tool. Do not provide any sensitive or personal user information to this tool.`, _meta: { "openai/outputTemplate": "ui://widget/interactive-quiz.html", // <- hook to the resource }, inputSchema: { topic: z.string().describe("Quiz topic (e.g., 'US history')."), difficulty: z.enum(["easy", "medium", "hard"]).default("medium"), questions: z.array( z.object({ question: z.string(), options: z.array(z.string()).min(4).max(4), correctIndex: z.number().int(), explanation: z.string().optional(), }) ).min(1).max(40), }, }, async (toolInput) => { const { topic, difficulty, questions } = toolInput; // Here you can run any server-side logic to process the input from ChatGPT and // prepare toolOutput that would be fed into the frontend widget code. // E.g. you can receive search filters and return matching items. return { // Optional narration beneath the component content: [{ type: "text", text: `Starting a ${difficulty} quiz on ${topic}.` }], // `structuredContent` will be available as `toolOutput` in the frontend widget code structuredContent: { topic, difficulty, questions, }, // Private to the component; not visible to the model _meta: { "openai/locale": "en" }, }; } ); Koodin yläosassa on kuvaus työkalusta - ChatGPT luottaa siihen ymmärtääkseen, milloin ja miten sitä käytetään: Kuvaus kuvaa yksityiskohtaisesti, mitä työkalu tekee. ChatGPT käyttää sitä päättämään, sovelletaanko työkalua käyttäjälle. inputSchema on tapa kertoa ChatGPT:lle tarkalleen, mitä tietoja se tarvitsee antamaan työkalulle ja miten sen pitäisi olla jäsennelty. Kuten yllä näet, se sisältää vihjeitä ja rajoituksia, joita ChatGPT voi käyttää oikean hyötykuorman (toolInput) valmisteluun. outputSchema jätetään pois täältä, mutta voit antaa sen kertomaan ChatGPT:lle, mitä järjestelmää strukturoidulla sisällöllä on. Joten tietyssä mielessä työkalu on se, mikä määrittelee ChatGPT-sovelluksen täällä. Katsotaanpa seuraavat kaksi kenttää: _meta[”openai/outputTemplate”] on MCP-resurssin tunniste, jota ChatGPT-sovellus käyttää widgetin näyttämiseen. async (toolInput) => { ... on toiminto, joka vastaanottaa toolInput ChatGPT: stä ja tuottaa toolOutput, joka on widgetille käytettävissä. Täällä voimme suorittaa minkä tahansa palvelinpuolisen logiikan tietojen käsittelemiseksi. MCP Resurssit Seuraavassa on, miten määritämme MCP-resurssin: // Add an MCP resource that contains frontend code for rendering the widget mcpServer.registerResource( 'interactive-quiz', "ui://widget/interactive-quiz.html", // must match `openai/outputTemplate` in the tool definition above {}, async (uri) => { // copy frontend script and css const quizaurusJs = await fs.readFile("./src/dist/QuizaurusWidget.js", "utf8"); const quizaurusCss = await fs.readFile("./src/dist/QuizaurusWidget.css", "utf8"); return { contents: [ { uri: uri.href, mimeType: "text/html+skybridge", // Below is the HTML code for the widget. // It defines a root div and injects our custom script from src/dist/QuizaurusWidget.js, // which finds the root div by its ID and renders the widget components in it. text: ` <div id="quizaurus-root" class="quizaurus-root"></div> <script type="module"> ${quizaurusJs} </script> <style> ${quizaurusCss} </style>` } ] } } ); Pohjimmiltaan ”resurssi” tässä tarjoaa sovelluksen frontend (widget) osan. ui://widget/interactive-quiz.html on resurssitunnus, ja sen pitäisi vastata _meta[“openai/outputTemplate”] työkalun määritelmää edellä olevasta osasta. provides HTML code of the widget contents the HTML here is very simple - we just define the root div and add that will find that root div by ID, create necessary elements (buttons, etc) and define the quiz app logic. We will look at the script in the next section below. quiz-app-root the custom script Widgetin rakentaminen Widgetin käyttöönotto Katsotaanpa nyt pikaisesti joka toteuttaa widgetin (sovelluksen näkyvä osa): Käytä QuizaurusWidget.js // Find the root div defined by the MCP resource const root = document.querySelector('#quiz-app-root'); // create HTML elements inside the root div ... // try to initialize for widgetState to restore the quiz state in case the chat page gets reloaded const selectedAnswers = window.openai.widgetState?.selectedAnswers ?? {}; let currentQuestionIndex = window.openai.widgetState?.currentQuestionIndex ?? 0; function refreshUI() { // Read questions from window.openai.toolOutput - this is the output of the tool defined in server.ts const questions = window.openai.toolOutput?.questions; // Initially the widget will be rendered with empty toolOutput. // It will be populated when ChatGPT receives toolOutput from our tool. if (!questions) { console.log("Questions have not yet been provided. Try again in a few sec.") return; } // Update UI according to the current state ... }; // when an answer button is clicked, we update the state and call refreshUI() optionButtons.forEach((b) => { b.onclick = (event) => { const selectedOption = event.target.textContent selectedAnswers[currentQuestionIndex] = selectedOption; // save and expose selected answers to ChatGPT window.openai.setWidgetState({ selectedAnswers, currentQuestionIndex }); refreshUI(); }; }); ... // at the end of the quiz, the user can click this button to review the answers with ChatGPT reviewResultsButton.onclick = () => { // send a prompt to ChatGPT, it will respond in the chat window.openai.sendFollowUpMessage({ prompt: "Review my answers and explain mistakes" }); reviewResultsButton.disabled = true; }; startQuizButton.onclick = refreshUI; refreshUI(); Muistutus: Tämä koodi käynnistyy HTML: llä, jonka olemme määritelleet edellä olevassa MCP-resurssissa (MCP-resurssi). asia). HTML ja käsikirjoitus ovat ChatGPT-chat-sivun iframen sisällä. <script type="module">… ChatGPT paljastaa joitakin tietoja ja hakkuita Global object.Tässä on mitä käytämme täällä: window.openai window.openai.toolOutput sisältää MCP-työkalun palauttamat kysymystiedot. Aluksi HTML-tiedosto näytetään ennen kuin työkalu palauttaa työkalunOutput, joten window.openai.toolOutput on tyhjä. Tämä on hieman ärsyttävää, mutta korjaamme sen myöhemmin Reactilla. window.openai.widgetState ja window.openai.setWidgetState() antavat meille mahdollisuuden päivittää ja käyttää widget-tilaa. Se voi olla mitä tahansa dataa, jota haluamme, vaikka suositus on pitää se alle 4000 merkkiä. Täällä käytämme sitä muistaa, mitä kysymyksiä käyttäjä on jo vastannut, jotta jos sivu ladataan uudelleen, widget muistaa tilan. window.openai.sendFollowUpMessage ({prompt: ”...”}) on tapa antaa ChatGPT:lle kehotus ikään kuin käyttäjä olisi kirjoittanut sen, ja ChatGPT kirjoittaa vastauksen keskusteluun. Lisää ominaisuuksia löytyy OpenAI-dokumentista täältä: https://developers.openai.com/apps-sdk/build/custom-ux Laita kaikki yhteen Aika kokeilla sitä! Nopea muistutus, tarvitset maksetun ChatGPT-tilauksen, jotta voit ottaa käyttöön kehittäjätilan. Clone this repo [Download the code] https://github.com/renal128/quizaurus-tutorial There are 2 projects in this repo, a minimalistic one, described above, and a slicker-looking React one. We’ll focus on the first one for now. Open a terminal, navigate to the repo directory and run the following commands: [Starting the server] cd quizaurus-plain install NodeJS if you don’t have it https://nodejs.org/en/download/ to install dependencies defined in package.json npm install to start the Express app with MCP server - npm start keep it running [ ] Expose your local server to the web Create a free ngrok account: https://ngrok.com/ Open a (the other one with the Express app should keep running separately) new terminal Install ngrok: https://ngrok.com/docs/getting-started#1-install-the-ngrok-agent-cli on MacOS brew install ngrok Connect ngrok on your laptop to your ngrok account by configuring it with your auth token: https://ngrok.com/docs/getting-started#2-connect-your-account Start ngrok: ngrok http 8000 You should see something like this in the bottom of the output: Forwarding: https://xxxxx-xxxxxxx-xxxxxxxxx.ngrok-free ngrok created a tunnel from your laptop to a public server, so that your local server is available to everyone on the internet, including ChatGPT. Again, , don’t close the terminal keep it running - this is the part that r , $20/month, otherwise you may not see developer mode available. [Enable Developer Mode on ChatGPT] equires a paid customer subscription Go to ChatGPT website => Settings => Apps & Connectors => Advanced settings Enable the “Developer mode” toggle [Add the app] Go back to “Apps & Connectors” and click “Create” in the top-right corner Fill in the details as on the screenshot. For “MCP Server URL” use the URL that ngrok gave you in the terminal output and . add /mcp to it at the end Click on your newly added app You should see the MCP tool under Actions - now ChatGPT knows when and how to use the app. When you make changes to the code, sometimes , otherwise it can remain cached (sometimes I even delete and re-add the app due to avoid caching). you need to click Refresh to make ChatGPT pick up the changes [ ] Finally, we’re ready to test it! Test the app In the chat window you can nudge ChatGPT to use your app by selecting it under the “ ” button. In my experience, it’s not always necessary, but let’s do it anyway. Then try a prompt like “Make an interactive 3-question quiz about Sam Altman”. + You should see ChatGPT asking your approval to call the MCP tool with the displayed . I assume that it’s a feature for unapproved apps, and it won’t happen once the app is properly reviewed by OpenAI (although, as of Nov 2025 there’s no defined process to publish an app yet). So, just click “Confirm” and wait a few seconds. toolInput As I mentioned above, the widget gets rendered before is returned by our MCP server. This means that if you click “Start Quiz” too soon, it won’t do anything - try again a couple seconds later. (we will fix that with React in the next section below). When the data is ready, clicking “Start Quiz” should show the quiz! toolOutput Reagoi käyttämällä Yllä olemme tarkastelleet koodia, joka käyttää yksinkertaista JavaScriptia. , osoittaa, miten ChatGPT-sovellus voidaan toteuttaa Reactin avulla. Kysely reaktiosta Löydät joitakin hyödyllisiä OpenAI-dokumentteja täältä: . https://developers.openai.com/apps-sdk/build/custom-ux/ useOpenAiGlobal helper hooks Näet ne täällä, koodi on kopioitu dokumentaatiosta: https://github.com/renal128/quizaurus-tutorial/blob/main/quizaurus-react/web/src/openAiHooks.ts Yksi hyödyllisimmistä on , jonka avulla voit tilata React-sovelluksen päivityksiin Muista, että edellä olevassa yksinkertaisessa (ei-React) sovelluksessa meillä oli ongelma, että "Start Quiz" -painike ei tehnyt mitään, kunnes tiedot ovat valmiita? useToolOutput window.openai.toolOutput function App() { const toolOutput = useToolOutput() as QuizData | null; if (!toolOutput) { return ( <div className="quiz-container"> <p className="quiz-loading__text">Generating your quiz...</p> </div> ); } // otherwise render the quiz ... } Kun toolOutput täyttyy, React palauttaa sovelluksen automaattisesti ja näyttää kyselylomakkeen lataustilan sijasta. React Router Sovelluksen esittämän iframen navigointihistoria on yhdistetty sivun navigointihistoriaan, joten voit käyttää reititys-API:ia, kuten React Routeria, navigoinnin toteuttamiseen sovelluksessa. Muita piirteitä ja ominaisuuksia Huomautus: ChatGPT-sovelluksen kehitys ei ole tällä hetkellä kovin vakaa, koska ominaisuutta ei ole täysin käyttöönotettu, joten on oikeudenmukaista odottaa API: n ilmoittamattomia muutoksia tai pieniä vikoja. https://developers.openai.com/apps-sdk Miten ja milloin ChatGPT päättää näyttää sovelluksesi käyttäjälle The most important part is that your app’s metadata, such as the tool description, must feel relevant to the conversation. ChatGPT’s goal here is to provide the best UX to the user, so obviously if the app’s description is irrelevant to the prompt, the app won’t be shown. I’ve also seen ChatGPT asking the user to rate if the app was helpful or not, I suppose this feedback is also taken into account. App Metadata. Official recommendations: https://developers.openai.com/apps-sdk/guides/optimize-metadata The in order to be used. How would the user know to link an app? There are 2 ways: App Discovery. app needs to be linked/connected to the user’s account Manual - go to and find the app there. Settings => Apps & Connectors Contextual Suggestion - if the app is not connected, but is highly relevant in the conversation, ChatGPT may offer to connect it. I wasn’t able to make it work with my app, but I saw it working with pre-integrated apps like Zillow or Spotify: Kun sovellus on yhdistetty, ChatGPT voi käyttää sitä keskustelussa tarvittaessa. Käyttäjä voi puristaa sen yksinkertaisesti mainitsemalla sovelluksen nimen tekstissä, kirjoittamalla @AppName tai napsauttamalla +-painiketta ja valitsemalla sovelluksen valikosta siellä. Tuetut alustat Web - koska se toteutetaan iframe, web on helpoin alusta tukea ja minulla ei ollut lähes mitään ongelmia siellä. Mobiilisovellus - jos liität sovelluksen verkkoon, sinun pitäisi pystyä näkemään se mobiilissa. En pystynyt käynnistämään sovellusta mobiilissa - se ei soittanut työkalua, mutta kun käynnistin sovelluksen verkossa, pystyin vuorovaikutukseen sen kanssa mobiilissa. Autentisointi ChatGPT sovellukset tukevat OAuth 2.1: https://developers.openai.com/apps-sdk/build/auth Tämä on iso aihe, kerro minulle, jos olisi hyödyllistä kirjoittaa erillinen viesti siitä! Verkkopyyntöjen tekeminen Tässä on, mitä dokumentti sanoo ( ) ” Työskentele OpenAI-kumppanisi kanssa, jos tarvitset tiettyjä sallittuja verkkotunnuksia.” Lähteet Standard fetch requests are allowed only when they comply with the CSP jossain muualla ( ), it suggests configuring objekti resurssin määritelmän avulla voit sallia verkkotunnuksesi: Täällä _meta _meta: { ... /* Assigns a subdomain for the HTML. When set, the HTML is rendered within `chatgpt-com.web-sandbox.oaiusercontent.com` It's also used to configure the base url for external links. */ "openai/widgetDomain": 'https://chatgpt.com', /* Required to make external network requests from the HTML code. Also used to validate `openai.openExternal()` requests. */ 'openai/widgetCSP': { // Maps to `connect-src` rule in the iframe CSP connect_domains: ['https://chatgpt.com'], // Maps to style-src, style-src-elem, img-src, font-src, media-src etc. in the iframe CSP resource_domains: ['https://*.oaistatic.com'], } } Toinen asia, jota voit käyttää, on Sovelluksen widgetti (frontend) voi käyttää sitä soittamaan työkalun MCP-palvelimellasi - annat MCP-työkalun nimen ja työkalunInput-tiedot ja saat takaisin työkalunOutput: window.openai.callTool await window.openai?.callTool("my_tool_name", { "param_name": "param_value" }); Other frontend features Katso tämä dokumentaatio siitä, mikä on käytettävissä frontend-koodiisi • : window.openai https://developers.openai.com/apps-sdk/build/custom-ux You can access the following fields (e.g. kertoo, onko ChatGPT tällä hetkellä valossa vai pimeässä tilassa): window.openai.theme theme: Theme; userAgent: UserAgent; locale: string; // layout maxHeight: number; displayMode: DisplayMode; safeArea: SafeArea; // state toolInput: ToolInput; toolOutput: ToolOutput | null; toolResponseMetadata: ToolResponseMetadata | null; widgetState: WidgetState | null; Voit myös käyttää seuraavia puhelinsoittoja (esim. yritä tehdä sovelluksestasi täysnäyttöinen): await window.openai?.requestDisplayMode({ mode: "fullscreen" }); /** Calls a tool on your MCP. Returns the full response. */ callTool: ( name: string, args: Record<string, unknown> ) => Promise<CallToolResponse>; /** Triggers a followup turn in the ChatGPT conversation */ sendFollowUpMessage: (args: { prompt: string }) => Promise<void>; /** Opens an external link, redirects web page or mobile app */ openExternal(payload: { href: string }): void; /** For transitioning an app from inline to fullscreen or pip */ requestDisplayMode: (args: { mode: DisplayMode }) => Promise<{ /** * The granted display mode. The host may reject the request. * For mobile, PiP is always coerced to fullscreen. */ mode: DisplayMode; }>; /** Update widget state */ setWidgetState: (state: WidgetState) => Promise<void>; Kiitos teille! That’s all, thank you for reading and best of luck with whatever you’re building!