MuninMunin
Logga inKom igång gratis
Home/Journal/Munin med OpenAI Agents SDK.
Engineering · 9 min read

Munin med OpenAI Agents SDK.

Varenda annan integrationssida här handlar om en klient någon annan skrev. Den här handlar om fallet där du är klienten — en MCPServerStreamableHttp, ett verktygsfilter, en godkännandepolicy du styr, och en granskningskö under som håller oavsett vad din kod bestämmer.

Macro of a dark mechanical keyboard on a desk at night with one row of keycaps swapped for matte cobalt blue ones, the rest falling into soft bokeh.
Hela tangentbordet finns där. En rad är tillåten.

Det kommer ett ögonblick i varje agentprojekt där de driftade klienterna slutar räcka. Du vill att slingan ska köra klockan 06:00 utan att någon öppnar en laptop. Du vill att den ska läsa från din egen kö, skriva till dina egna loggar, och fallera på ett sätt din jourlista förstår. Vid den punkten letar du inte efter ett chattfönster. Du skriver ett program, och programmet behöver verktyg.

Hur kopplar du OpenAI Agents SDK till ett CRM?

Rikta en MCPServerStreamableHttp mot en MCP-server som redan har kunddatan i sig. Munin serverar CRM, konversationer, kunskapsbas, CMS, utgående kontakt och analys som MCP-verktyg på en HTTPS-ändpunkt, så ett enda serverobjekt i din Python-process ger en Agent hela kundplattformen — ingen SDK per modul, ingen REST-klient, inget eget schema att underhålla.

pythonrunner.py
import asyncio
import os

from agents import Agent, Runner
from agents.mcp import MCPServerStreamableHttp


async def main() -> None:
    async with MCPServerStreamableHttp(
        name="Munin",
        params={
            "url": "https://mcp.getmunin.com",
            "headers": {"Authorization": f"Bearer {os.environ['MUNIN_API_KEY']}"},
            "timeout": 30,
        },
        cache_tools_list=True,
        max_retry_attempts=3,
    ) as server:
        agent = Agent(
            name="Desk",
            instructions="Använd Munin-verktygen. Citera kundens egna ord.",
            mcp_servers=[server],
        )

        result = await Runner.run(
            agent,
            "Vilka konversationer från de senaste sju dagarna fick aldrig svar?",
        )
        print(result.final_output)


asyncio.run(main())

Det är hela integrationen. pip install openai-agents på Python 3.10 eller nyare, en adminnyckel skapad under Inställningar → API-nycklar, och agenten når crm_search_contacts, conv_list_conversations, kb_search, cms_create_entry och resten av katalogen vid namn.

Två konstruktorargument är värda att sätta medvetet snarare än att kopiera. cache_tools_list=True hindrar SDK:n från att lista verktyg på nytt vid varje körning — verktygsdefinitionerna ändras inte mellan dina 06:00- och 06:05-rundor, och att lista en stor katalog över nätverket är det långsammaste i slingan. Anropa invalidate_tools_cache() på servern om du behöver en färsk lista mitt i processen. max_retry_attempts lägger automatiska omförsök runt list_tools() och call_tool(), vilket betyder mer för ett obemannat cron-jobb än för en människa som sitter framför ett chattfönster och bara kan försöka igen.

Driftar du själv i stället för Cloud? Samma objekt, riktat mot http://localhost:3001/mcp. Din process gör anslutningen, så en privat ändpunkt fungerar här på ett sätt den inte gör för varje väg — mer om det nedan.

Vilken av SDK:ns fyra MCP-integrationer bör du använda?

Python-SDK:n stöder fyra, och valet är egentligen en fråga om var verktygsanropet utförs. För en kundplattform som håller verkliga poster är Streamable HTTP inne i din egen process rätt standard: din kod håller legitimationen, din kod ser varje anrop, och ändpunkten behöver aldrig vara nåbar från annat än din infrastruktur.

  • MCPServerStreamableHttpDin process öppnar anslutningen och gör anropen. Legitimationen stannar i din miljö, trafiken stannar på din nätverksväg, och en privat eller självdriftad ändpunkt fungerar utmärkt. Det här är den att gripa efter.
  • HostedMCPToolDu ger OpenAI:s Responses API en server_label och server_url, och OpenAI:s infrastruktur anropar servern å modellens vägnar. Mindre kod, och ingen returresa till Python — men dokumentationen är tydlig med att servern måste vara publikt nåbar, och den fungerar i dag med OpenAI-modeller som stöder Responses API:ts driftade MCP-integration.
  • MCPServerSseDen äldre HTTP-med-SSE-transporten. MCP-projektet har fasat ut den; SDK-dokumentationen säger behåll den för äldre servrar och föredra Streamable HTTP för allt nytt.
  • MCPServerStdioSDK:n startar en lokal underprocess och talar över stdin och stdout. Rätt för en filsystemserver på din egen maskin, fel för en flerhyresgästplattform som bor bakom en HTTPS-ändpunkt.

Den driftade vägen har en nätverksform, inte en kvalitetsskillnad

HostedMCPTool är genuint mindre kod. Det betyder också att OpenAI:s servrar, inte dina, måste kunna öppna en anslutning till din MCP-ändpunkt — så en laptopinstallation eller en enbart-VPC-utrullning är utesluten.

Munin Cloud är publikt nåbar och fungerar båda vägarna. Välj den driftade vägen när du vill att Responses API ska äga returresan; välj Streamable HTTP när du vill ha legitimationen och anropsloggen inne i din egen process.

Hur hindrar jag agenten från att se varenda verktyg i katalogen?

Använd tool_filter. En full kundplattform exponerar en stor verktygsyta över sex moduler, och en agent vars enda jobb är att triagera inkorgen har inget att göra med ett verktyg som slår ihop kontakter. create_static_tool_filter tar en tillåtlista, en blocklista, eller båda — SDK:n tillämpar tillåtlistan först och tar sedan bort allt blockerat från det som återstår.

Det här är de tio mest värdefulla raderna i filen. Att avgränsa verktyg är inte bara ett argument om latens och tokens; det är skillnaden mellan en agent som inte kan göra fel sak, och en agent du litar på inte gör det.

pythonrunner.py
from agents import Agent
from agents.mcp import MCPServerStreamableHttp, create_static_tool_filter

triage = MCPServerStreamableHttp(
    name="Munin (triage)",
    params={"url": "https://mcp.getmunin.com", "headers": headers},
    tool_filter=create_static_tool_filter(
        allowed_tool_names=[
            "conv_list_conversations",
            "conv_search_messages",
            "conv_set_topic",
            "conv_set_subject",
            "crm_lookup_contact",
            "kb_search",
        ],
    ),
)

agent = Agent(
    name="Triage",
    mcp_servers=[triage],
    mcp_config={
        "convert_schemas_to_strict": True,
        "include_server_in_tool_names": True,
    },
)

mcp_config ligger på agentnivå snarare än servernivå, och båda nycklarna där förtjänar sin plats. convert_schemas_to_strict görs efter bästa förmåga — där ett verktygsschema kan konverteras till strikt JSON-schema blir det det, och där det inte kan används originalet, så det kostar inget att lämna på. include_server_in_tool_names sätter servernamnet före lokala MCP-verktygsnamn, vilket stoppar kollisioner i det ögonblick du kopplar på en andra server; kör du Munin bredvid en filsystem- eller sökserver, slå på den före den första namnkrocken snarare än efter.

För logik en statisk lista inte kan uttrycka, skicka in en callable i stället. Den får en ToolFilterContext som bär den aktiva run_context, den agent som ber om verktygen, och server_name, och returnerar True när verktyget ska exponeras — så ett serverobjekt kan presentera en skrivskyddad yta för en sammanfattande agent och en bredare för agenten som faktiskt arkiverar saker.

Hur kräver jag godkännande innan ett verktyg körs?

Skicka require_approval till servern. MCPServerStdio, MCPServerSse och MCPServerStreamableHttp accepterar det alla, i fyra former: "always" eller "never" för allt, booleanerna True och False som betyder detsamma, en karta per verktyg som {"conv_send_message": "always", "kb_search": "never"}, eller ett grupperat objekt som namnger verktyg efter policy.

Den grupperade formen är den att skriva, för att den läses som ett policydokument och en granskare kan kontrollera den i en kodgranskning.

pythonrunner.py
async with MCPServerStreamableHttp(
    name="Munin",
    params={"url": "https://mcp.getmunin.com", "headers": headers},
    require_approval={
        "always": {"tool_names": ["conv_send_message", "crm_update_contact"]},
        "never": {"tool_names": ["conv_search_messages", "kb_search"]},
    },
) as server:
    ...

Testet

Om någon raderade din godkännandepolicy i natt, vad skulle agenten kunna göra med en kund till morgonen?

Vad händer om min körare bara godkänner allt?

Sammanslagningarna och den utgående kontakten avfyras fortfarande inte. Munin delar skrivningar efter kostnad snarare än efter behörighet: billiga reversibla som att tagga en konversation eller sätta dess ämne utförs direkt, medan de du inte kan ta tillbaka — att slå ihop två kontaktposter, att skicka utgående kontakt — finns bara som föreslå-verktyg som arkiverar in i en granskningskö som en inloggad människa rensar; skrivningar till kunskapsbasen och konversationer är direkta, och det är vad require_approval är till för.

Den skillnaden är avsiktlig, och den är skälet till att en egen körare är en rimlig sak att ge en produktionsorganisation. Godkännande på klientsidan är en egenskap hos din kod: den är så bra som din senaste driftssättning, och require_approval="never" är en oaktsam commit bort. Granskningskön är en egenskap hos plattformen, så den överlever att din kod har fel. outreach_propose_first_touch skriver ett mejl och arkiverar det; det finns inget verktyg i katalogen som skickar det. kb_propose_curation_candidate skriver ett utkast bara administratörer ser; att lyfta det är en mänsklig handling. Den utgående kön fungerar likadant från varje klient, vilket är poängen — säkerheten ligger inte i klienten.

Skriv godkännandepolicyn ändå. Två lager som är överens är rätt antal, och det på klientsidan är det som stoppar ett bortkastat modellanrop innan det sker snarare än efteråt.

Hur ser en verklig obemannad runda ut?

Nattrundan på supportdesken, vilket är det jobb folk flest skriver en körare för från början. Fyra steg, alla med riktiga verktygsnamn, inget av dem kräver att en människa är vaken:

  • 01Hitta vad som gick obesvarat. conv_list_conversations filtrerat till öppna trådar, sedan conv_search_messages för att läsa vad som faktiskt frågades. Kundens formulering, inte en sammanfattning av den.
  • 02Ta reda på vem som frågar. crm_lookup_contact löser avsändaren till en kontaktpost; analytics_get_contact_journey säger vilka sidor de läste innan de skrev in. Båda sitter på samma Postgres-rad, så det här är en koppling, inte en integration.
  • 03Kontrollera om du redan besvarat det. kb_search kör vektor- och fulltextsökning över kunskapsbasen. Finns svaret skriver svaret sig självt; finns det inte är den frånvaron det mer användbara fyndet.
  • 04Arkivera, skicka inte. conv_set_topic och conv_set_subject skriver direkt för att de är trivialt reversibla. Allt utgående styrs av din require_approval-policy; utgående kontakt kan bara föreslås.

Din körare läser text kunder skrev

Ett supportmeddelande är opålitlig indata, och det landar i modellens kontext. Behandla ett ärende som en rimlig vektor för prompt-injektion snarare än som data.

Motmedlet är avgränsning, inte vaksamhet: en tool_filter-tillåtlista, require_approval på allt utgående, och en nyckel avgränsad till de moduler jobbet behöver. En Munin-adminnyckel är full åtkomst till organisationen — håll den i miljön, aldrig i repot.

Vem bör inte skriva sin egen körare?

De flesta, ärligt talat, och det är värt att vara tydlig med vad Munin levererar och inte levererar. Munin är en kundplattform, inte ett agentramverk: det finns ingen körare, ingen prompthanterare, ingen eval-rigg, inget spårnings-UI i det. Det är Agents SDK:ns territorium, och SDK:n är välbyggd för det — spårning fångar MCP-verktygslistning och verktygsanrop automatiskt, Runner.run_streamed ger dig inkrementell utdata, och MCPServerManager hanterar att ansluta flera servrar med drop_failed_servers- och reconnect-semantik som redan är genomtänkt. Ta det maskineriet från SDK:n, och låt Munin vara det den anropar.

Vad du får av att skriva slingan själv är de två saker en driftad klient inte kan ge dig: ett schema, och en plats att lägga utdatan. En cron-post, ett Slack-inlägg, en rad i din egen tabell, ett test som fallerar när rundan inte producerar något. Är ditt agentarbete samtalsmässigt — någon som ställer frågor och läser svar — är en klient mindre arbete, och genomgångarna för Claude Code och Cursor täcker den uppsättningen från början till slut. Skriv en körare när det du behöver är ett jobb snarare än ett samtal, och när du vill att slingan, omförsöken och felbeteendet ska vara dina. Verktygen är identiska hur som helst, för det är en ändpunkt och ett schema under.

Finns det en TypeScript-version?

Ja. JavaScript- och TypeScript-utgåvan av Agents SDK kommer från @openai/agents och exponerar samma två former under samma namn: MCPServerStreamableHttp när din process äger anslutningen, och hostedMcpTool när Responses API ska äga den. Begreppen är ett mot ett — ett serverobjekt med en URL, verktygsfiltrering, en godkännandepolicy.

Stavningen på optionerna skiljer sig från Python på sina ställen, och i stället för att gissa på dem här, läs dem från TypeScript-guiden för MCP innan du skriver konfigurationen. En konfigurationsnyckel uppfunnen i ett blogginlägg kostar en eftermiddag.

Vanliga frågor

Stöder OpenAI Agents SDK fjärr-MCP-servrar? Ja. MCPServerStreamableHttp ansluter till en Streamable HTTP-server var som helst din process når, där params tar url, headers och timeout. HostedMCPTool är alternativet, där OpenAI:s Responses API anropar en publikt nåbar server å modellens vägnar.

Hur autentiserar jag en MCP-server i Agents SDK? Lägg en Authorization-header i params-ordboken: {"url": ..., "headers": {"Authorization": f"Bearer {token}"}}. Läs token från miljön. För Munin skapar du nyckeln under Inställningar → API-nycklar och avgränsar den till de moduler jobbet behöver.

Kan jag begränsa vilka MCP-verktyg en agent kan anropa? Ja, med tool_filter. Använd create_static_tool_filter(allowed_tool_names=[...], blocked_tool_names=[...]) för en fast lista — tillåtlistan tillämpas först, sedan tas blockerade namn bort — eller skicka in en callable som får en ToolFilterContext för logik per körning.

Hur lägger jag till mänskligt godkännande på MCP-verktygsanrop? Skicka require_approval till serverobjektet som "always", "never", en karta per verktyg, eller ett grupperat {"always": {"tool_names": [...]}}-objekt. För driftade verktyg sätter du require_approval i tool_config och levererar en on_approval_request-callback för att avgöra i Python.

Fungerar det här med en självdriftad Munin? Ja, över Streamable HTTP. docker compose up serverar MCP på /mcp på backend-porten, så rikta params["url"] mot http://localhost:3001/mcp. Den driftade verktygsvägen kräver en publikt nåbar URL, så använd Streamable HTTP-servern för en privat utrullning.

Måste jag använda OpenAI-modeller? För de lokala transporterna — Streamable HTTP, SSE, stdio — konverteras MCP-verktygen till vanliga funktionsverktyg, så det här är ett helt vanligt modellval i Agents SDK. HostedMCPTool är undantaget: SDK:n dokumenterar det som fungerande med OpenAI-modeller som stöder Responses API:ts driftade MCP-integration.

Vad kostar Munin att köra det här mot? Cloud Free kostar 0 € i månaden med 5 000 MCP-anrop, 250 kontakter och 100 MB lagring — nog för att köra en nattlig körning medan du bestämmer dig. Egen drift är gratis för alltid under MIT, utan någon enterprise-katalog hållen tillbaka.

Kortversionen

  • MCPServerStreamableHttp med en url och en Authorization-header ger en Agents SDK-agent hela Munin-katalogen — CRM, konversationer, kunskapsbas, CMS, utgående kontakt, analys — på ungefär tjugofem rader Python.
  • Det finns fyra integrationsvägar; Streamable HTTP är standarden för verklig kunddata, för att din process håller legitimationen och ändpunkten aldrig behöver vara publik.
  • create_static_tool_filter avgränsar agenten till de verktyg jobbet behöver. Sätt include_server_in_tool_names innan du kopplar på en andra server, inte efter.
  • require_approval i grupperad form läses som en policy och granskas som en. Under den låter Munin bara agenter föreslå sammanslagningar och utgående kontakt — så granskningskön håller vad din kod än bestämmer.
  • Skriv en körare när du behöver ett schema och någonstans att lägga utdatan. För samtalsmässigt arbete är en befintlig MCP-klient mindre arbete och når samma verktyg.

Rikta en MCPServerStreamableHttp mot din organisation och fråga den vilka konversationer från förra veckan som aldrig fick svar — MCP-verktygsreferensen har namnen, och Munin Cloud är gratis att börja på.

Skriv slingan. Allt den behöver anropa finns redan där.

Kjell Rune Monsø, grundare.