MuninMunin
Logg innKom i gang gratis
Home/Journal/Munin med OpenAI Agents SDK.
Engineering · 9 min read

Munin med OpenAI Agents SDK.

Hver eneste andre integrasjonsside her handler om en klient noen andre skrev. Denne handler om tilfellet der du er klienten — én MCPServerStreamableHttp, et verktøyfilter, en godkjenningspolicy du styrer, og en gjennomgangskø under som holder uansett hva koden din bestemmer.

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.
Hele tastaturet er der. Én rad er tillatt.

Det kommer et øyeblikk i ethvert agentprosjekt der de driftede klientene slutter å være nok. Du vil at sløyfen skal kjøre klokka 06:00 uten at noen åpner en laptop. Du vil at den skal lese fra din egen kø, skrive til dine egne logger, og feile på en måte vakttelefonordningen din forstår. På det punktet leter du ikke etter et chat-vindu. Du skriver et program, og programmet trenger verktøy.

Hvordan kobler du OpenAI Agents SDK til et CRM?

Pek en MCPServerStreamableHttp mot en MCP-server som allerede har kundedataene i seg. Munin serverer CRM, samtaler, kunnskapsbase, CMS, utgående kontakt og analyse som MCP-verktøy på ett HTTPS-endepunkt, så ett enkelt serverobjekt i Python-prosessen din gir en Agent hele kundeplattformen — ingen SDK per modul, ingen REST-klient, intet eget skjema å vedlikeholde.

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="Bruk Munin-verktøyene. Siter kundens egne ord.",
            mcp_servers=[server],
        )

        result = await Runner.run(
            agent,
            "Hvilke samtaler fra de siste sju dagene fikk aldri svar?",
        )
        print(result.final_output)


asyncio.run(main())

Det er hele integrasjonen. pip install openai-agents på Python 3.10 eller nyere, en admin-nøkkel laget under Innstillinger → API-nøkler, og agenten kan nå crm_search_contacts, conv_list_conversations, kb_search, cms_create_entry og resten av katalogen ved navn.

To konstruktørargumenter er verdt å sette bevisst framfor å kopiere. cache_tools_list=True hindrer SDK-en i å liste verktøy på nytt ved hver kjøring — verktøydefinisjonene endrer seg ikke mellom 06:00- og 06:05-rundene dine, og å liste en stor katalog over nettverket er det tregeste i sløyfen. Kall invalidate_tools_cache() på serveren hvis du trenger en fersk liste midt i prosessen. max_retry_attempts legger automatiske nye forsøk rundt list_tools() og call_tool(), som betyr mer for en ubemannet cron-jobb enn for et menneske som sitter foran et chat-vindu og bare kan prøve igjen.

Selvdrifter du i stedet for Cloud? Det samme objektet, pekt mot http://localhost:3001/mcp. Prosessen din lager tilkoblingen, så et privat endepunkt er greit her på en måte det ikke er for hver rute — mer om det nedenfor.

Hvilken av SDK-ens fire MCP-integrasjoner bør du bruke?

Python-SDK-en støtter fire, og valget er egentlig et spørsmål om hvor verktøykallet utføres. For en kundeplattform som holder reelle poster, er Streamable HTTP inne i din egen prosess riktig standard: koden din holder legitimasjonen, koden din ser hvert kall, og endepunktet trenger aldri å være nåbart fra annet enn infrastrukturen din.

  • MCPServerStreamableHttpProsessen din åpner tilkoblingen og gjør kallene. Legitimasjonen blir i miljøet ditt, trafikken blir på din nettverksvei, og et privat eller selvdriftet endepunkt fungerer fint. Dette er den å gripe etter.
  • HostedMCPToolDu gir OpenAIs Responses API en server_label og server_url, og OpenAIs infrastruktur kaller serveren på modellens vegne. Mindre kode, og ingen rundtur tilbake til Python — men dokumentasjonen er tydelig på at serveren må være offentlig nåbar, og den fungerer i dag med OpenAI-modeller som støtter Responses API-ets driftede MCP-integrasjon.
  • MCPServerSseDen eldre HTTP-med-SSE-transporten. MCP-prosjektet har utfaset den; SDK-dokumentasjonen sier behold den for eldre servere og foretrekk Streamable HTTP for alt nytt.
  • MCPServerStdioSDK-en starter en lokal underprosess og snakker over stdin og stdout. Riktig for en filsystemserver på din egen maskin, feil for en flerleietakerplattform som bor bak et HTTPS-endepunkt.

Den driftede ruten har en nettverksform, ikke en kvalitetsforskjell

HostedMCPTool er genuint mindre kode. Det betyr også at OpenAIs servere, ikke dine, må kunne åpne en tilkobling til MCP-endepunktet ditt — så en laptop-installasjon eller en VPC-bare-utrulling er utelukket.

Munin Cloud er offentlig nåbar og fungerer begge veier. Velg den driftede ruten når du vil at Responses API skal eie rundturen; velg Streamable HTTP når du vil ha legitimasjonen og kall-loggen inne i din egen prosess.

Hvordan hindrer jeg agenten i å se hvert eneste verktøy i katalogen?

Bruk tool_filter. En full kundeplattform eksponerer en stor verktøyflate på tvers av seks moduler, og en agent hvis ene jobb er å triagere innboksen har ingenting å gjøre med et verktøy som slår sammen kontakter. create_static_tool_filter tar en tillatelsesliste, en blokkeringsliste, eller begge — SDK-en anvender tillatelseslista først og fjerner så alt blokkert fra det som blir igjen.

Dette er de ti mest verdifulle linjene i fila. Å avgrense verktøy er ikke bare et argument om ventetid og tokens; det er forskjellen mellom en agent som ikke kan gjøre feil ting, og en agent du stoler på at ikke gjø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å framfor servernivå, og begge nøklene der fortjener plassen sin. convert_schemas_to_strict er beste innsats — der et verktøyskjema kan konverteres til strikt JSON-skjema blir det det, og der det ikke kan brukes originalen, så det koster ingenting å la den stå på. include_server_in_tool_names setter servernavnet foran lokale MCP-verktøynavn, som hindrer kollisjoner i det øyeblikket du kobler til en server nummer to; kjører du Munin sammen med en filsystem- eller søkeserver, slå den på før den første navnekollisjonen framfor etterpå.

For logikk en statisk liste ikke kan uttrykke, send inn en callable i stedet. Den får en ToolFilterContext som bærer den aktive run_context, agent-en som ber om verktøyene, og server_name, og returnerer True når verktøyet bør eksponeres — så ett serverobjekt kan presentere en skrivebeskyttet flate for en oppsummerende agent og en videre for agenten som faktisk arkiverer ting.

Hvordan krever jeg godkjenning før et verktøy kjører?

Send require_approval til serveren. MCPServerStdio, MCPServerSse og MCPServerStreamableHttp godtar det alle, i fire former: "always" eller "never" for alt, boolskene True og False som betyr det samme, et kart per verktøy som {"conv_send_message": "always", "kb_search": "never"}, eller et gruppert objekt som navngir verktøy etter policy.

Den grupperte formen er den å skrive, fordi den leses som et policydokument og en anmelder kan sjekke den i en kodegjennomgang.

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

Testen

Hvis noen slettet godkjenningspolicyen din i natt, hva kunne agenten gjort med en kunde innen morgenen?

Hva skjer hvis kjøreren min bare godkjenner alt?

Sammenslåingene og den utgående kontakten fyrer fortsatt ikke. Munin deler skrivinger etter kostnad framfor etter tilgang: billige, reversible som å merke en samtale eller sette emnet dens utføres direkte, mens de du ikke kan ta tilbake — å slå sammen to kontaktposter, å sende utgående kontakt — finnes bare som foreslå-verktøy som arkiverer inn i en gjennomgangskø et innlogget menneske klarerer; skrivinger til kunnskapsbasen og samtaler er direkte, og det er det require_approval er til for.

Det skillet er tilsiktet, og det er grunnen til at en egen kjører er en rimelig ting å gi en produksjonsorganisasjon. Godkjenning på klientsiden er en egenskap ved koden din: den er like god som siste utrulling, og require_approval="never" er én uforsiktig commit unna. Gjennomgangskøen er en egenskap ved plattformen, så den overlever at koden din tar feil. outreach_propose_first_touch skriver en e-post og arkiverer den; det finnes intet verktøy i katalogen som sender den. kb_propose_curation_candidate skriver et utkast bare administratorer ser; å løfte det er en menneskelig handling. Den utgående køen virker likt fra hver klient, som er poenget — sikkerheten ligger ikke i klienten.

Skriv godkjenningspolicyen likevel. To lag som er enige med hverandre er riktig antall, og det på klientsiden er det som stopper et bortkastet modellkall før det skjer framfor etterpå.

Hvordan ser en reell ubemannet runde ut?

Nattrunden på supportavdelingen, som er jobben folk flest skriver en kjører for i utgangspunktet. Fire steg, alle med ekte verktøynavn, ingen av dem krever at et menneske er våkent:

  • 01Finn hva som gikk ubesvart. conv_list_conversations filtrert til åpne tråder, så conv_search_messages for å lese hva som faktisk ble spurt om. Kundens formulering, ikke et sammendrag av den.
  • 02Finn ut hvem som spør. crm_lookup_contact løser avsenderen til en kontaktpost; analytics_get_contact_journey sier hvilke sider de leste før de skrev inn. Begge ligger på den samme Postgres-raden, så dette er en kobling, ikke en integrasjon.
  • 03Sjekk om du allerede har svart på det. kb_search kjører vektor- og fulltekstssøk over kunnskapsbasen. Finnes svaret, skriver svaret seg selv; finnes det ikke, er det fraværet det mer nyttige funnet.
  • 04Arkiver, ikke send. conv_set_topic og conv_set_subject skriver direkte fordi de er trivielt reversible. Alt utgående er styrt av require_approval-policyen din; utgående kontakt kan bare foreslås.

Kjøreren din leser tekst kunder skrev

En supportmelding er ikke-tiltrodd inndata, og den lander i modellens kontekst. Behandle en sak som en plausibel vektor for prompt-injeksjon framfor som data.

Mottiltaket er avgrensning, ikke årvåkenhet: en tool_filter-tillatelsesliste, require_approval på alt utgående, og en nøkkel avgrenset til modulene jobben trenger. En Munin-adminnøkkel er full tilgang til organisasjonen — hold den i miljøet, aldri i repoet.

Hvem bør ikke skrive sin egen kjører?

De fleste, ærlig talt, og det er verdt å være tydelig på hva Munin leverer og ikke leverer. Munin er en kundeplattform, ikke et agentrammeverk: det finnes ingen kjører, ingen promptbehandler, ingen eval-rigg, intet sporings-UI i det. Det er Agents SDK-ens territorium, og SDK-en er godt bygd for det — sporing fanger MCP-verktøylisting og verktøykall automatisk, Runner.run_streamed gir deg inkrementell utdata, og MCPServerManager håndterer å koble til flere servere med drop_failed_servers- og reconnect-semantikk som allerede er gjennomtenkt. Ta det maskineriet fra SDK-en, og la Munin være tingen den kaller.

Det du får av å skrive sløyfen selv, er de to tingene en driftet klient ikke kan gi deg: en tidsplan, og et sted å legge resultatet. En cron-oppføring, et Slack-innlegg, en rad i din egen tabell, en test som feiler når runden ikke produserer noe. Er agentarbeidet ditt samtalepreget — noen som stiller spørsmål og leser svar — er en klient mindre arbeid, og gjennomgangene for Claude Code og Cursor dekker det oppsettet fra ende til ende. Skriv en kjører når det du trenger er en jobb framfor en samtale, og når du vil at sløyfen, de nye forsøkene og feilmodusen skal være dine. Verktøyene er identiske uansett, fordi det er ett endepunkt og ett skjema under.

Finnes det en TypeScript-versjon?

Ja. JavaScript- og TypeScript-utgaven av Agents SDK kommer fra @openai/agents og eksponerer de samme to formene under de samme navnene: MCPServerStreamableHttp når prosessen din eier tilkoblingen, og hostedMcpTool når Responses API skal eie den. Begrepene er én-til-én — et serverobjekt med en URL, verktøyfiltrering, en godkjenningspolicy.

Stavemåten på opsjonene avviker fra Python noen steder, og framfor å gjette på dem her, les dem av TypeScript-veiledningen for MCP før du skriver konfigurasjonen. En konfigurasjonsnøkkel oppfunnet i et blogginnlegg koster en ettermiddag.

Ofte stilte spørsmål

Støtter OpenAI Agents SDK eksterne MCP-servere? Ja. MCPServerStreamableHttp kobler til en Streamable HTTP-server hvor som helst prosessen din når, der params tar url, headers og timeout. HostedMCPTool er alternativet, der OpenAIs Responses API kaller en offentlig nåbar server på modellens vegne.

Hvordan autentiserer jeg en MCP-server i Agents SDK? Legg en Authorization-header i params-ordboka: {"url": ..., "headers": {"Authorization": f"Bearer {token}"}}. Les tokenet fra miljøet. For Munin lager du nøkkelen under Innstillinger → API-nøkler og avgrenser den til modulene jobben trenger.

Kan jeg begrense hvilke MCP-verktøy en agent kan kalle? Ja, med tool_filter. Bruk create_static_tool_filter(allowed_tool_names=[...], blocked_tool_names=[...]) for en fast liste — tillatelseslista anvendes først, så fjernes blokkerte navn — eller send inn en callable som får en ToolFilterContext for logikk per kjøring.

Hvordan legger jeg til menneskelig godkjenning på MCP-verktøykall? Send require_approval til serverobjektet som "always", "never", et kart per verktøy, eller et gruppert {"always": {"tool_names": [...]}}-objekt. For driftede verktøy setter du require_approval i tool_config og leverer en on_approval_request-callback for å avgjøre i Python.

Virker dette med en selvdriftet Munin? Ja, over Streamable HTTP. docker compose up serverer MCP på /mcp på backend-porten, så pek params["url"] mot http://localhost:3001/mcp. Den driftede verktøyruten trenger en offentlig nåbar URL, så bruk Streamable HTTP-serveren for en privat utrulling.

Må jeg bruke OpenAI-modeller? For de lokale transportene — Streamable HTTP, SSE, stdio — konverteres MCP-verktøyene til vanlige funksjonsverktøy, så dette er et helt vanlig modellvalg i Agents SDK. HostedMCPTool er unntaket: SDK-en dokumenterer det som fungerende med OpenAI-modeller som støtter Responses API-ets driftede MCP-integrasjon.

Hva koster Munin å kjøre dette mot? Cloud Free koster 0 € i måneden med 5 000 MCP-kall, 250 kontakter og 100 MB lagring — nok til å kjøre en nattlig runde mens du bestemmer deg. Selvdrift er gratis for alltid under MIT, uten en enterprise-katalog holdt tilbake.

Kortversjonen

  • MCPServerStreamableHttp med en url og en Authorization-header gir en Agents SDK-agent hele Munin-katalogen — CRM, samtaler, kunnskapsbase, CMS, utgående kontakt, analyse — på omtrent tjuefem linjer Python.
  • Det finnes fire integrasjonsveier; Streamable HTTP er standarden for reelle kundedata, fordi prosessen din holder legitimasjonen og endepunktet aldri trenger å være offentlig.
  • create_static_tool_filter avgrenser agenten til verktøyene jobben trenger. Sett include_server_in_tool_names før du kobler til en server nummer to, ikke etterpå.
  • require_approval i gruppert form leses som en policy og gjennomgås som en. Under den lar Munin bare agenter foreslå sammenslåinger og utgående kontakt — så gjennomgangskøen holder uansett hva koden din bestemmer.
  • Skriv en kjører når du trenger en tidsplan og et sted å legge resultatet. For samtalepreget arbeid er en eksisterende MCP-klient mindre arbeid og når de samme verktøyene.

Pek en MCPServerStreamableHttp mot organisasjonen din og spør den hvilke samtaler fra forrige uke som aldri fikk svar — MCP-verktøyreferansen har navnene, og Munin Cloud er gratis å starte på.

Skriv sløyfen. Alt den trenger å kalle, er allerede der.

Kjell Rune Monsø, gründer.