KiboUP - Deploy AI Agents via HTTP, A2A & MCP
Description: Framework-agnostic Python library for deploying AI agents with built-in observability, prompt management, evaluation, and agent discovery through KiboStudio.

Open Source Python Library

# Deploy AI Agents
with One Codebase

Framework-agnostic library for deploying AI agents via HTTP, A2A, and MCP — with built-in observability, prompt management, evaluation, and agent discovery through KiboStudio.

Get Started View on GitHub
`pip install kiboup[all]`

## Three Protocols, One API

Choose the right protocol for your use case. All share authentication, logging, and Studio integration.

HTTP

### KiboAgentApp

Standard REST API with streaming (SSE), WebSocket, task tracking, and health checks. Works with any frontend.

Web AppsREST APIsMicroservices
`POST /invocations` `GET /ping` `WS /ws`
MCP

### KiboAgentMcp

Expose agent capabilities as discoverable tools via the Model Context Protocol. Compatible with MCP Inspector and IDEs.

IDE IntegrationsTool AgentsPlugins
`@app.tool()` `@app.resource()` `@app.prompt()`
A2A

### KiboAgentA2A

Google's Agent-to-Agent protocol with auto-generated Agent Cards, skill-based routing, and task lifecycle.

Multi-AgentAgent DiscoveryCross-Org
`/.well-known/agent.json` `@app.executor`
Built-in Developer Console

## KiboStudio

Full observability, prompt management, evaluation, and agent discovery in a single web interface.

Your browser does not support the video tag.
📡

### Agent Discovery

Service registry where agents register, send heartbeats, and discover each other at runtime. Monitor health, uptime, memory, and capabilities from the Dashboard.

📊

### Traces & Observability

Automatic trace reporting with full span hierarchy: invocation, agent_run, llm_call, tool_call, retrieval. View input/output data, LLM token usage, duration, and errors.

🔀

### Graph Visualization

ADK-style visual graph rendering of trace span hierarchies. Agent nodes as green ellipses, tool/LLM nodes as boxes, with bezier curve edges and arrowheads.

💬

### Chat Interface

Built-in chat to test any registered agent directly from the browser. Full markdown rendering, message history, and real-time responses.

🚩

### Feature Flags & Params

Control agent behavior at runtime without redeploying. Toggle capabilities on/off with feature flags or change configuration via dynamic parameters. Global and per-agent scopes.

📝

### Prompt Management

Version-controlled prompt templates with variable extraction, active version selection, and model configuration. Fetch prompts at runtime via the SDK.

🧪

### Evaluation (LLM-as-Judge)

Automated quality evaluation using GPT-4o-mini. Scores traces on answer relevancy, coherence, completeness, and harmfulness (0.0-1.0).

🔌

### StudioClient SDK

Full async Python SDK for agents. Auto-registration, heartbeats, flag/param caching (30s TTL), and inline integration with KiboAgentClient and KiboMcpClient.

🗄️

### SQLite Backend

Zero-config persistence with SQLite WAL mode. No external databases required. Just point to a file path and go.

### Launch KiboStudio in 3 lines

    from kiboup.studio import KiboStudio

    studio = KiboStudio(db_path="kibostudio.db", debug=True)
    studio.run(host="0.0.0.0", port=8000)

## Examples

Ready-to-run examples covering every protocol and feature.

Server

    from kiboup import KiboAgentApp, LLMUsage
    from langchain_openai import ChatOpenAI
    from langgraph.graph import START, MessagesState, StateGraph

    app = KiboAgentApp(
        api_keys={"sk-frontend-abc": "web-app"}
    )

    llm = ChatOpenAI(model="gpt-4o-mini")
    builder = StateGraph(MessagesState)

    def chatbot(state):
        return {"messages": [llm.invoke(state["messages"])]}

    builder.add_node("chatbot", chatbot)
    builder.add_edge(START, "chatbot")
    graph = builder.compile()

    @app.entrypoint
    async def invoke(payload, context):
        result = await graph.ainvoke(
            {"messages": [{"role": "user", "content": payload["prompt"]}]}
        )
        return {"response": result["messages"][-1].content}

    app.run(port=8080)

Client

    import asyncio
    from kiboup import KiboAgentClient

    async def main():
        async with KiboAgentClient(
            base_url="http://127.0.0.1:8080",
            api_key="sk-frontend-abc",
        ) as client:
            health = await client.ping()
            result = await client.invoke(
                {"prompt": "What is AI?"}
            )
            print(result["response"])

    asyncio.run(main())

cURL

    curl -X POST http://127.0.0.1:8080/invocations \
      -H "Content-Type: application/json" \
      -H "X-API-Key: sk-frontend-abc" \
      -d '{"prompt": "What is AI?"}'

Streaming Server (SSE)

    from kiboup import KiboAgentApp
    from langchain_openai import ChatOpenAI
    from langgraph.graph import START, MessagesState, StateGraph

    app = KiboAgentApp(api_keys={"sk-chat-abc": "chat"})
    llm = ChatOpenAI(model="gpt-4o-mini", streaming=True)

    builder = StateGraph(MessagesState)
    def chatbot(state):
        return {"messages": [llm.invoke(state["messages"])]}
    builder.add_node("chatbot", chatbot)
    builder.add_edge(START, "chatbot")
    graph = builder.compile()

    @app.entrypoint
    async def invoke(payload, context):
        messages = payload.get("messages",
            [{"role": "user", "content": payload["prompt"]}])

        async def token_stream():
            async for event in graph.astream_events(
                {"messages": messages}, version="v2"
            ):
                if event.get("event") == "on_chat_model_stream":
                    content = event["data"]["chunk"].content
                    if content:
                        yield {"token": content}
            yield {"done": True}

        return token_stream()

    app.run(port=8080)

Streaming Client

    import asyncio, sys
    from kiboup import KiboAgentClient

    async def chat_loop():
        async with KiboAgentClient(
            "http://localhost:8080",
            api_key="sk-chat-abc"
        ) as client:
            while True:
                user_input = input("You: ")
                sys.stdout.write("AI: ")

                async for chunk in client.stream(
                    {"prompt": user_input}
                ):
                    token = chunk.get("token")
                    if token:
                        sys.stdout.write(token)
                        sys.stdout.flush()
                print("\n")

    asyncio.run(chat_loop())

MCP Server

    from kiboup import KiboAgentMcp

    app = KiboAgentMcp(
        name="LangGraph MCP Server",
        api_keys={"sk-mcp-abc": "web-app"},
    )

    @app.tool()
    async def ask(question: str) -> str:
        """Ask a question to GPT-4o-mini."""
        result = await graph.ainvoke(
            {"messages": [{"role": "user",
                          "content": question}]}
        )
        return result["messages"][-1].content

    @app.tool()
    def summarize(text: str) -> str:
        """Summarize text using GPT-4o-mini."""
        result = graph.invoke(
            {"messages": [{"role": "user",
             "content": f"Summarize:\n{text}"}]}
        )
        return result["messages"][-1].content

    app.run(transport="sse")

MCP Client

    import asyncio
    from kiboup import KiboMcpClient

    async def main():
        async with KiboMcpClient(
            "http://localhost:8000/sse",
            api_key="sk-mcp-abc"
        ) as client:
            tools = await client.list_tools()
            print(f"Tools: {tools}")

            result = await client.call_tool(
                "ask",
                {"question": "What is AI?"}
            )
            print(result)

    asyncio.run(main())

A2A Server

    from kiboup.a2a.server import (
        AgentExecutor, AgentSkill,
        KiboAgentA2A, TaskUpdater
    )

    app = KiboAgentA2A(
        name="Chat Agent",
        description="GPT-4o-mini via LangGraph",
        api_keys={"sk-a2a-xyz": "client"},
        skills=[AgentSkill(
            id="chat", name="Chat",
            description="Answer questions",
            tags=["chat", "qa"],
            input_modes=["text/plain"],
            output_modes=["text/plain"],
        )],
    )

    @app.executor
    class ChatAgent(AgentExecutor):
        async def execute(self, ctx, queue):
            from a2a.utils import new_agent_text_message
            user_input = ctx.get_user_input()
            result = await graph.ainvoke(
                {"messages": [{"role": "user",
                              "content": user_input}]}
            )
            await queue.enqueue_event(
                new_agent_text_message(
                    result["messages"][-1].content
                )
            )

        async def cancel(self, ctx, queue):
            updater = TaskUpdater(queue, ctx.task_id, ctx.context_id)
            await updater.cancel()

    app.run()

A2A Client

    import asyncio
    from kiboup import KiboA2AClient

    async def main():
        async with KiboA2AClient(
            "http://localhost:8000",
            api_key="sk-a2a-xyz"
        ) as client:
            card = client.agent_card
            print(f"Agent: {card.name}")
            print(f"Skills: {[s.name for s in card.skills]}")

            response = await client.send(
                "What is the capital of France?"
            )
            print(response)

    asyncio.run(main())

Agent Card

    // GET /.well-known/agent.json
    {
      "name": "Chat Agent",
      "description": "GPT-4o-mini via LangGraph",
      "skills": [{
        "id": "chat",
        "name": "Chat",
        "tags": ["chat", "qa"]
      }]
    }

Researcher Agent

    from kiboup import KiboAgentApp
    from kiboup.studio import StudioClient

    app = KiboAgentApp()
    studio = StudioClient(
        studio_url="http://127.0.0.1:8000",
        agent_id="researcher",
        agent_name="researcher",
        agent_endpoint="http://127.0.0.1:8081",
        capabilities=["research", "delegate"],
    )
    app.attach_studio(studio)

    @app.entrypoint
    async def invoke(payload, context):
        research = await do_research(payload["prompt"])

        # Feature flag: delegate to writer?
        if await studio.is_flag_enabled("delegate_to_writer"):
            # Discover writer agent via Studio
            agents = await studio.list_agents()
            writer = next(
                (a for a in agents
                 if a["agent_id"] == "writer"), None
            )
            if writer:
                style = await studio.get_param(
                    "writer_style", default="markdown"
                )
                # Delegate to writer endpoint
                result = await call_writer(
                    writer["endpoint"], research, style
                )
                return {"response": result, "delegated": True}

        return {"response": research}

    app.run(port=8081)

Writer Agent

    from kiboup import KiboAgentApp
    from kiboup.studio import StudioClient

    app = KiboAgentApp()
    studio = StudioClient(
        studio_url="http://127.0.0.1:8000",
        agent_id="writer",
        agent_name="writer",
        agent_endpoint="http://127.0.0.1:8082",
        capabilities=["writing", "formatting"],
    )
    app.attach_studio(studio)

    @app.entrypoint
    async def invoke(payload, context):
        prompt = payload.get("prompt", "")
        research = payload.get("research", "")
        style = payload.get("style", "markdown")

        content = (
            f"Topic: {prompt}\n"
            f"Research:\n{research}\n"
            f"Format in {style} style."
        )

        result = await graph.ainvoke(
            {"messages": [{"role": "user",
                          "content": content}]}
        )
        return {
            "response": result["messages"][-1].content,
            "agent": "writer",
            "style": style,
        }

    app.run(port=8082)

Run It

    # Terminal 1: KiboStudio
    uv run python examples/studio_example.py

    # Terminal 2: Researcher
    OPENAI_API_KEY=sk-... uv run python \
      examples/multi_agent_example.py --agent researcher

    # Terminal 3: Writer
    OPENAI_API_KEY=sk-... uv run python \
      examples/multi_agent_example.py --agent writer

Chainlit Chat UI with Streaming

    import chainlit as cl
    from kiboup import KiboAgentClient

    SERVER_URL = "http://localhost:8080"
    API_KEY = "sk-chat-abc"

    @cl.on_chat_start
    async def on_start():
        cl.user_session.set("history", [])

    @cl.on_message
    async def on_message(message: cl.Message):
        history = cl.user_session.get("history", [])
        history.append({"role": "user", "content": message.content})

        response = cl.Message(content="")
        await response.send()

        full_response = ""
        async with KiboAgentClient(SERVER_URL, api_key=API_KEY) as client:
            async for chunk in client.stream({
                "prompt": message.content,
                "messages": history,
            }):
                token = chunk.get("token")
                if token:
                    full_response += token
                    await response.stream_token(token)

        await response.update()
        history.append({"role": "assistant", "content": full_response})

Run It

    # 1. Start streaming server
    OPENAI_API_KEY=sk-... uv run python examples/stream_server_example.py

    # 2. Start Chainlit
    uv run chainlit run examples/chainlit_example.py

## Get Started

Install KiboUP and start deploying agents in minutes.

### 1. Install

    # Core (HTTP only)
    pip install kiboup

    # With all protocols + Studio
    pip install kiboup[all]

    # Or with uv
    uv add kiboup[all]

### 2. Create an Agent

    from kiboup import KiboAgentApp

    app = KiboAgentApp()

    @app.entrypoint
    async def invoke(payload, context):
        return {"response": "Hello!"}

    app.run(port=8080)