bonito-mcp 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bonito_mcp-1.0.0/.env.example +8 -0
- bonito_mcp-1.0.0/.gitignore +12 -0
- bonito_mcp-1.0.0/Dockerfile +6 -0
- bonito_mcp-1.0.0/PKG-INFO +120 -0
- bonito_mcp-1.0.0/README.md +98 -0
- bonito_mcp-1.0.0/bonito_mcp/__init__.py +3 -0
- bonito_mcp-1.0.0/bonito_mcp/client.py +173 -0
- bonito_mcp-1.0.0/bonito_mcp/server.py +356 -0
- bonito_mcp-1.0.0/pyproject.toml +36 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Bonito MCP Server Configuration
|
|
2
|
+
# Copy this file to .env and fill in your values.
|
|
3
|
+
|
|
4
|
+
# Required: Your Bonito API / gateway key
|
|
5
|
+
BONITO_API_KEY=YOUR_KEY_HERE
|
|
6
|
+
|
|
7
|
+
# Optional: Override the API base URL (defaults to https://api.getbonito.com)
|
|
8
|
+
# BONITO_API_URL=https://api.getbonito.com
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bonito-mcp
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: MCP server for the Bonito AI Platform – manage providers, models, agents, and gateway routing.
|
|
5
|
+
Project-URL: Homepage, https://getbonito.com
|
|
6
|
+
Project-URL: Documentation, https://getbonito.com/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/ShabariRepo/bonito
|
|
8
|
+
Author-email: Bonito AI <hello@getbonito.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Keywords: ai,bonito,gateway,llm,mcp
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: httpx>=0.25.0
|
|
20
|
+
Requires-Dist: mcp>=1.0.0
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# Bonito MCP Server
|
|
24
|
+
|
|
25
|
+
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that wraps the **Bonito AI Platform** REST API. Use it from Claude Desktop, Cursor, or any MCP-compatible client to manage your multi-provider AI infrastructure.
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- **Provider Management** – Connect and verify AWS Bedrock, Azure OpenAI, GCP Vertex AI, OpenAI, Anthropic, and Groq
|
|
30
|
+
- **Model Management** – List, sync, and activate models across providers
|
|
31
|
+
- **Unified Gateway** – Send chat completions through Bonito's OpenAI-compatible gateway
|
|
32
|
+
- **Agent Management** – Create and execute BonBon and Bonobot agents
|
|
33
|
+
- **Knowledge Bases** – Create and manage knowledge bases
|
|
34
|
+
- **Cost & Observability** – Track costs per provider and view gateway logs
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
### Install from source
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
git clone https://github.com/ShabariRepo/bonito.git
|
|
42
|
+
cd bonito/mcp-server
|
|
43
|
+
pip install -e .
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Set your API key
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
export BONITO_API_KEY="your-bonito-api-key"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Run (stdio – for Claude Desktop)
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
bonito-mcp
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Run (SSE – for network clients)
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
bonito-mcp --transport sse --port 8080
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Claude Desktop Configuration
|
|
65
|
+
|
|
66
|
+
Add to your `claude_desktop_config.json`:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"mcpServers": {
|
|
71
|
+
"bonito": {
|
|
72
|
+
"command": "bonito-mcp",
|
|
73
|
+
"env": {
|
|
74
|
+
"BONITO_API_KEY": "your-bonito-api-key"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Docker
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
docker build -t bonito-mcp .
|
|
85
|
+
docker run -e BONITO_API_KEY="your-key" -p 8080:8080 bonito-mcp
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Available Tools
|
|
89
|
+
|
|
90
|
+
| Tool | Description |
|
|
91
|
+
|------|-------------|
|
|
92
|
+
| `list_providers` | List connected AI providers |
|
|
93
|
+
| `connect_provider` | Connect a new provider |
|
|
94
|
+
| `verify_provider` | Verify provider credentials |
|
|
95
|
+
| `list_models` | List models (with filters) |
|
|
96
|
+
| `sync_models` | Sync models from providers |
|
|
97
|
+
| `activate_model` | Activate a model for the gateway |
|
|
98
|
+
| `chat_completion` | Send a chat completion request |
|
|
99
|
+
| `list_gateway_keys` | List gateway API keys |
|
|
100
|
+
| `create_gateway_key` | Create a new gateway key |
|
|
101
|
+
| `gateway_usage` | View usage statistics |
|
|
102
|
+
| `list_agents` | List agents in a project |
|
|
103
|
+
| `create_agent` | Create a BonBon or Bonobot agent |
|
|
104
|
+
| `execute_agent` | Execute an agent |
|
|
105
|
+
| `get_agent` | Get agent details |
|
|
106
|
+
| `list_knowledge_bases` | List knowledge bases |
|
|
107
|
+
| `create_knowledge_base` | Create a knowledge base |
|
|
108
|
+
| `get_costs` | Get cost breakdown by provider |
|
|
109
|
+
| `get_gateway_logs` | View gateway request logs |
|
|
110
|
+
|
|
111
|
+
## Environment Variables
|
|
112
|
+
|
|
113
|
+
| Variable | Required | Default | Description |
|
|
114
|
+
|----------|----------|---------|-------------|
|
|
115
|
+
| `BONITO_API_KEY` | Yes | – | Your Bonito API key |
|
|
116
|
+
| `BONITO_API_URL` | No | `https://api.getbonito.com` | API base URL |
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
MIT
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Bonito MCP Server
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that wraps the **Bonito AI Platform** REST API. Use it from Claude Desktop, Cursor, or any MCP-compatible client to manage your multi-provider AI infrastructure.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Provider Management** – Connect and verify AWS Bedrock, Azure OpenAI, GCP Vertex AI, OpenAI, Anthropic, and Groq
|
|
8
|
+
- **Model Management** – List, sync, and activate models across providers
|
|
9
|
+
- **Unified Gateway** – Send chat completions through Bonito's OpenAI-compatible gateway
|
|
10
|
+
- **Agent Management** – Create and execute BonBon and Bonobot agents
|
|
11
|
+
- **Knowledge Bases** – Create and manage knowledge bases
|
|
12
|
+
- **Cost & Observability** – Track costs per provider and view gateway logs
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
### Install from source
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
git clone https://github.com/ShabariRepo/bonito.git
|
|
20
|
+
cd bonito/mcp-server
|
|
21
|
+
pip install -e .
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Set your API key
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
export BONITO_API_KEY="your-bonito-api-key"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Run (stdio – for Claude Desktop)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
bonito-mcp
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Run (SSE – for network clients)
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
bonito-mcp --transport sse --port 8080
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Claude Desktop Configuration
|
|
43
|
+
|
|
44
|
+
Add to your `claude_desktop_config.json`:
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"mcpServers": {
|
|
49
|
+
"bonito": {
|
|
50
|
+
"command": "bonito-mcp",
|
|
51
|
+
"env": {
|
|
52
|
+
"BONITO_API_KEY": "your-bonito-api-key"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Docker
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
docker build -t bonito-mcp .
|
|
63
|
+
docker run -e BONITO_API_KEY="your-key" -p 8080:8080 bonito-mcp
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Available Tools
|
|
67
|
+
|
|
68
|
+
| Tool | Description |
|
|
69
|
+
|------|-------------|
|
|
70
|
+
| `list_providers` | List connected AI providers |
|
|
71
|
+
| `connect_provider` | Connect a new provider |
|
|
72
|
+
| `verify_provider` | Verify provider credentials |
|
|
73
|
+
| `list_models` | List models (with filters) |
|
|
74
|
+
| `sync_models` | Sync models from providers |
|
|
75
|
+
| `activate_model` | Activate a model for the gateway |
|
|
76
|
+
| `chat_completion` | Send a chat completion request |
|
|
77
|
+
| `list_gateway_keys` | List gateway API keys |
|
|
78
|
+
| `create_gateway_key` | Create a new gateway key |
|
|
79
|
+
| `gateway_usage` | View usage statistics |
|
|
80
|
+
| `list_agents` | List agents in a project |
|
|
81
|
+
| `create_agent` | Create a BonBon or Bonobot agent |
|
|
82
|
+
| `execute_agent` | Execute an agent |
|
|
83
|
+
| `get_agent` | Get agent details |
|
|
84
|
+
| `list_knowledge_bases` | List knowledge bases |
|
|
85
|
+
| `create_knowledge_base` | Create a knowledge base |
|
|
86
|
+
| `get_costs` | Get cost breakdown by provider |
|
|
87
|
+
| `get_gateway_logs` | View gateway request logs |
|
|
88
|
+
|
|
89
|
+
## Environment Variables
|
|
90
|
+
|
|
91
|
+
| Variable | Required | Default | Description |
|
|
92
|
+
|----------|----------|---------|-------------|
|
|
93
|
+
| `BONITO_API_KEY` | Yes | – | Your Bonito API key |
|
|
94
|
+
| `BONITO_API_URL` | No | `https://api.getbonito.com` | API base URL |
|
|
95
|
+
|
|
96
|
+
## License
|
|
97
|
+
|
|
98
|
+
MIT
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""HTTP client wrapper for the Bonito REST API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BonitoClient:
|
|
12
|
+
"""Async HTTP client for Bonito API calls."""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
base_url: str | None = None,
|
|
17
|
+
api_key: str | None = None,
|
|
18
|
+
) -> None:
|
|
19
|
+
self.base_url = (base_url or os.environ.get("BONITO_API_URL", "https://api.getbonito.com")).rstrip("/")
|
|
20
|
+
self.api_key = api_key or os.environ.get("BONITO_API_KEY", "")
|
|
21
|
+
if not self.api_key:
|
|
22
|
+
raise ValueError(
|
|
23
|
+
"BONITO_API_KEY is required. Set it as an environment variable or pass it directly."
|
|
24
|
+
)
|
|
25
|
+
self._client: httpx.AsyncClient | None = None
|
|
26
|
+
|
|
27
|
+
async def _get_client(self) -> httpx.AsyncClient:
|
|
28
|
+
if self._client is None or self._client.is_closed:
|
|
29
|
+
self._client = httpx.AsyncClient(
|
|
30
|
+
base_url=self.base_url,
|
|
31
|
+
headers={
|
|
32
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
},
|
|
35
|
+
timeout=httpx.Timeout(30.0),
|
|
36
|
+
)
|
|
37
|
+
return self._client
|
|
38
|
+
|
|
39
|
+
async def close(self) -> None:
|
|
40
|
+
if self._client and not self._client.is_closed:
|
|
41
|
+
await self._client.aclose()
|
|
42
|
+
|
|
43
|
+
# ── Generic request helpers ──────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
async def get(self, path: str, params: dict[str, Any] | None = None) -> Any:
|
|
46
|
+
client = await self._get_client()
|
|
47
|
+
resp = await client.get(path, params=params)
|
|
48
|
+
resp.raise_for_status()
|
|
49
|
+
return resp.json()
|
|
50
|
+
|
|
51
|
+
async def post(self, path: str, json: dict[str, Any] | None = None) -> Any:
|
|
52
|
+
client = await self._get_client()
|
|
53
|
+
resp = await client.post(path, json=json)
|
|
54
|
+
resp.raise_for_status()
|
|
55
|
+
return resp.json()
|
|
56
|
+
|
|
57
|
+
async def put(self, path: str, json: dict[str, Any] | None = None) -> Any:
|
|
58
|
+
client = await self._get_client()
|
|
59
|
+
resp = await client.put(path, json=json)
|
|
60
|
+
resp.raise_for_status()
|
|
61
|
+
return resp.json()
|
|
62
|
+
|
|
63
|
+
async def delete(self, path: str) -> Any:
|
|
64
|
+
client = await self._get_client()
|
|
65
|
+
resp = await client.delete(path)
|
|
66
|
+
resp.raise_for_status()
|
|
67
|
+
return resp.json()
|
|
68
|
+
|
|
69
|
+
# ── Provider Management ──────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
async def list_providers(self) -> Any:
|
|
72
|
+
return await self.get("/api/providers")
|
|
73
|
+
|
|
74
|
+
async def connect_provider(self, provider_type: str, credentials: dict[str, Any]) -> Any:
|
|
75
|
+
return await self.post("/api/providers/connect", json={
|
|
76
|
+
"provider_type": provider_type,
|
|
77
|
+
"credentials": credentials,
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
async def verify_provider(self, provider_id: str) -> Any:
|
|
81
|
+
return await self.post(f"/api/providers/{provider_id}/verify")
|
|
82
|
+
|
|
83
|
+
# ── Model Management ─────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
async def list_models(
|
|
86
|
+
self,
|
|
87
|
+
provider: str | None = None,
|
|
88
|
+
capability: str | None = None,
|
|
89
|
+
active: bool | None = None,
|
|
90
|
+
) -> Any:
|
|
91
|
+
params: dict[str, Any] = {}
|
|
92
|
+
if provider:
|
|
93
|
+
params["provider"] = provider
|
|
94
|
+
if capability:
|
|
95
|
+
params["capability"] = capability
|
|
96
|
+
if active is not None:
|
|
97
|
+
params["active"] = str(active).lower()
|
|
98
|
+
return await self.get("/api/models", params=params or None)
|
|
99
|
+
|
|
100
|
+
async def sync_models(self) -> Any:
|
|
101
|
+
return await self.post("/api/models/sync")
|
|
102
|
+
|
|
103
|
+
async def activate_model(self, model_id: str) -> Any:
|
|
104
|
+
return await self.post(f"/api/models/{model_id}/activate")
|
|
105
|
+
|
|
106
|
+
# ── Gateway ──────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
async def chat_completion(self, model: str, messages: list[dict[str, str]], **kwargs: Any) -> Any:
|
|
109
|
+
payload: dict[str, Any] = {"model": model, "messages": messages, **kwargs}
|
|
110
|
+
return await self.post("/v1/chat/completions", json=payload)
|
|
111
|
+
|
|
112
|
+
async def list_gateway_keys(self) -> Any:
|
|
113
|
+
return await self.get("/api/gateway/keys")
|
|
114
|
+
|
|
115
|
+
async def create_gateway_key(self, name: str | None = None) -> Any:
|
|
116
|
+
payload: dict[str, Any] = {}
|
|
117
|
+
if name:
|
|
118
|
+
payload["name"] = name
|
|
119
|
+
return await self.post("/api/gateway/keys", json=payload)
|
|
120
|
+
|
|
121
|
+
async def gateway_usage(self, period: str | None = None) -> Any:
|
|
122
|
+
params: dict[str, Any] = {}
|
|
123
|
+
if period:
|
|
124
|
+
params["period"] = period
|
|
125
|
+
return await self.get("/api/gateway/usage", params=params or None)
|
|
126
|
+
|
|
127
|
+
# ── Agent Management ─────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
async def list_agents(self, project_id: str) -> Any:
|
|
130
|
+
return await self.get(f"/api/projects/{project_id}/agents")
|
|
131
|
+
|
|
132
|
+
async def create_agent(self, project_id: str, agent_config: dict[str, Any]) -> Any:
|
|
133
|
+
return await self.post(f"/api/projects/{project_id}/agents", json=agent_config)
|
|
134
|
+
|
|
135
|
+
async def execute_agent(self, agent_id: str, message: str) -> Any:
|
|
136
|
+
return await self.post(f"/api/agents/{agent_id}/execute", json={"message": message})
|
|
137
|
+
|
|
138
|
+
async def get_agent(self, agent_id: str) -> Any:
|
|
139
|
+
return await self.get(f"/api/agents/{agent_id}")
|
|
140
|
+
|
|
141
|
+
# ── Knowledge Bases ──────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
async def list_knowledge_bases(self) -> Any:
|
|
144
|
+
return await self.get("/api/knowledge-bases")
|
|
145
|
+
|
|
146
|
+
async def create_knowledge_base(self, name: str, description: str | None = None) -> Any:
|
|
147
|
+
payload: dict[str, Any] = {"name": name}
|
|
148
|
+
if description:
|
|
149
|
+
payload["description"] = description
|
|
150
|
+
return await self.post("/api/knowledge-bases", json=payload)
|
|
151
|
+
|
|
152
|
+
# ── Cost & Observability ─────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
async def get_costs(self, provider_id: str, period: str | None = None) -> Any:
|
|
155
|
+
params: dict[str, Any] = {}
|
|
156
|
+
if period:
|
|
157
|
+
params["period"] = period
|
|
158
|
+
return await self.get(f"/api/providers/{provider_id}/costs", params=params or None)
|
|
159
|
+
|
|
160
|
+
async def get_gateway_logs(
|
|
161
|
+
self,
|
|
162
|
+
limit: int | None = None,
|
|
163
|
+
offset: int | None = None,
|
|
164
|
+
model: str | None = None,
|
|
165
|
+
) -> Any:
|
|
166
|
+
params: dict[str, Any] = {}
|
|
167
|
+
if limit is not None:
|
|
168
|
+
params["limit"] = limit
|
|
169
|
+
if offset is not None:
|
|
170
|
+
params["offset"] = offset
|
|
171
|
+
if model:
|
|
172
|
+
params["model"] = model
|
|
173
|
+
return await self.get("/api/gateway/logs", params=params or None)
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
"""Bonito MCP Server – exposes the Bonito AI Platform REST API as MCP tools."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from contextlib import asynccontextmanager
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from mcp.server.fastmcp import FastMCP
|
|
11
|
+
|
|
12
|
+
from bonito_mcp.client import BonitoClient
|
|
13
|
+
|
|
14
|
+
# ── Lifespan: initialise / tear-down the HTTP client ─────────────────
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@asynccontextmanager
|
|
18
|
+
async def lifespan(server: FastMCP):
|
|
19
|
+
"""Create a shared BonitoClient for the server's lifetime."""
|
|
20
|
+
client = BonitoClient()
|
|
21
|
+
try:
|
|
22
|
+
yield {"client": client}
|
|
23
|
+
finally:
|
|
24
|
+
await client.close()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ── Server instance ──────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
app = FastMCP(
|
|
30
|
+
"Bonito",
|
|
31
|
+
instructions=(
|
|
32
|
+
"Bonito MCP Server – manage multi-provider AI infrastructure. "
|
|
33
|
+
"Connect cloud AI providers, deploy agents, route requests through "
|
|
34
|
+
"the unified gateway, and monitor costs."
|
|
35
|
+
),
|
|
36
|
+
lifespan=lifespan,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _client(ctx) -> BonitoClient:
|
|
41
|
+
"""Retrieve the BonitoClient from the lifespan context."""
|
|
42
|
+
return ctx.request_context.lifespan_context["client"]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _json(data: Any) -> str:
|
|
46
|
+
"""Pretty-print JSON for tool responses."""
|
|
47
|
+
return json.dumps(data, indent=2, default=str)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
51
|
+
# Provider Management
|
|
52
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@app.tool()
|
|
56
|
+
async def list_providers(ctx) -> str:
|
|
57
|
+
"""List all connected AI providers (AWS Bedrock, Azure OpenAI, GCP Vertex, OpenAI, Anthropic, Groq)."""
|
|
58
|
+
result = await _client(ctx).list_providers()
|
|
59
|
+
return _json(result)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@app.tool()
|
|
63
|
+
async def connect_provider(
|
|
64
|
+
provider_type: str,
|
|
65
|
+
credentials: str,
|
|
66
|
+
ctx=None,
|
|
67
|
+
) -> str:
|
|
68
|
+
"""Connect a new AI provider.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
provider_type: Provider type – one of: aws_bedrock, azure_openai, gcp_vertex, openai, anthropic, groq
|
|
72
|
+
credentials: JSON string with provider-specific credentials (e.g. API keys, region, project ID)
|
|
73
|
+
"""
|
|
74
|
+
creds = json.loads(credentials) if isinstance(credentials, str) else credentials
|
|
75
|
+
result = await _client(ctx).connect_provider(provider_type, creds)
|
|
76
|
+
return _json(result)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@app.tool()
|
|
80
|
+
async def verify_provider(provider_id: str, ctx=None) -> str:
|
|
81
|
+
"""Verify connectivity and credentials for a connected provider.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
provider_id: The provider ID to verify
|
|
85
|
+
"""
|
|
86
|
+
result = await _client(ctx).verify_provider(provider_id)
|
|
87
|
+
return _json(result)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
91
|
+
# Model Management
|
|
92
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@app.tool()
|
|
96
|
+
async def list_models(
|
|
97
|
+
provider: str | None = None,
|
|
98
|
+
capability: str | None = None,
|
|
99
|
+
active: bool | None = None,
|
|
100
|
+
ctx=None,
|
|
101
|
+
) -> str:
|
|
102
|
+
"""List available models across all providers with optional filters.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
provider: Filter by provider (e.g. openai, anthropic, aws_bedrock)
|
|
106
|
+
capability: Filter by capability (e.g. chat, embedding, image)
|
|
107
|
+
active: Filter by activation status
|
|
108
|
+
"""
|
|
109
|
+
result = await _client(ctx).list_models(provider=provider, capability=capability, active=active)
|
|
110
|
+
return _json(result)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@app.tool()
|
|
114
|
+
async def sync_models(ctx) -> str:
|
|
115
|
+
"""Sync available models from all connected providers. Discovers newly available models."""
|
|
116
|
+
result = await _client(ctx).sync_models()
|
|
117
|
+
return _json(result)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@app.tool()
|
|
121
|
+
async def activate_model(model_id: str, ctx=None) -> str:
|
|
122
|
+
"""Activate a model to make it available through the gateway.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
model_id: The model ID to activate
|
|
126
|
+
"""
|
|
127
|
+
result = await _client(ctx).activate_model(model_id)
|
|
128
|
+
return _json(result)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
132
|
+
# Gateway
|
|
133
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@app.tool()
|
|
137
|
+
async def chat_completion(
|
|
138
|
+
model: str,
|
|
139
|
+
messages: str,
|
|
140
|
+
temperature: float | None = None,
|
|
141
|
+
max_tokens: int | None = None,
|
|
142
|
+
ctx=None,
|
|
143
|
+
) -> str:
|
|
144
|
+
"""Send a chat completion request through the Bonito gateway (OpenAI-compatible).
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
model: Model identifier (e.g. gpt-4o, claude-sonnet-4-20250514, or a Bonito model alias)
|
|
148
|
+
messages: JSON array of message objects, e.g. [{"role":"user","content":"Hello"}]
|
|
149
|
+
temperature: Sampling temperature (0-2)
|
|
150
|
+
max_tokens: Maximum tokens to generate
|
|
151
|
+
"""
|
|
152
|
+
msgs = json.loads(messages) if isinstance(messages, str) else messages
|
|
153
|
+
kwargs: dict[str, Any] = {}
|
|
154
|
+
if temperature is not None:
|
|
155
|
+
kwargs["temperature"] = temperature
|
|
156
|
+
if max_tokens is not None:
|
|
157
|
+
kwargs["max_tokens"] = max_tokens
|
|
158
|
+
result = await _client(ctx).chat_completion(model, msgs, **kwargs)
|
|
159
|
+
return _json(result)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@app.tool()
|
|
163
|
+
async def list_gateway_keys(ctx) -> str:
|
|
164
|
+
"""List all API keys for the Bonito gateway."""
|
|
165
|
+
result = await _client(ctx).list_gateway_keys()
|
|
166
|
+
return _json(result)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@app.tool()
|
|
170
|
+
async def create_gateway_key(name: str | None = None, ctx=None) -> str:
|
|
171
|
+
"""Create a new gateway API key.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
name: Optional friendly name for the key
|
|
175
|
+
"""
|
|
176
|
+
result = await _client(ctx).create_gateway_key(name=name)
|
|
177
|
+
return _json(result)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@app.tool()
|
|
181
|
+
async def gateway_usage(period: str | None = None, ctx=None) -> str:
|
|
182
|
+
"""Get gateway usage statistics.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
period: Time period – e.g. 24h, 7d, 30d
|
|
186
|
+
"""
|
|
187
|
+
result = await _client(ctx).gateway_usage(period=period)
|
|
188
|
+
return _json(result)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
192
|
+
# Agent Management
|
|
193
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@app.tool()
|
|
197
|
+
async def list_agents(project_id: str, ctx=None) -> str:
|
|
198
|
+
"""List all agents in a project.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
project_id: The project ID
|
|
202
|
+
"""
|
|
203
|
+
result = await _client(ctx).list_agents(project_id)
|
|
204
|
+
return _json(result)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@app.tool()
|
|
208
|
+
async def create_agent(
|
|
209
|
+
project_id: str,
|
|
210
|
+
name: str,
|
|
211
|
+
model: str,
|
|
212
|
+
system_prompt: str | None = None,
|
|
213
|
+
agent_type: str = "bonbon",
|
|
214
|
+
ctx=None,
|
|
215
|
+
) -> str:
|
|
216
|
+
"""Create a new agent (BonBon or Bonobot).
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
project_id: The project ID to create the agent in
|
|
220
|
+
name: Agent name
|
|
221
|
+
model: Model to use (e.g. gpt-4o, claude-sonnet-4-20250514)
|
|
222
|
+
system_prompt: Optional system prompt for the agent
|
|
223
|
+
agent_type: Agent type – bonbon (single model) or bonobot (multi-model orchestrator)
|
|
224
|
+
"""
|
|
225
|
+
config: dict[str, Any] = {
|
|
226
|
+
"name": name,
|
|
227
|
+
"model": model,
|
|
228
|
+
"type": agent_type,
|
|
229
|
+
}
|
|
230
|
+
if system_prompt:
|
|
231
|
+
config["system_prompt"] = system_prompt
|
|
232
|
+
result = await _client(ctx).create_agent(project_id, config)
|
|
233
|
+
return _json(result)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@app.tool()
|
|
237
|
+
async def execute_agent(agent_id: str, message: str, ctx=None) -> str:
|
|
238
|
+
"""Execute an agent with a message.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
agent_id: The agent ID to execute
|
|
242
|
+
message: The message to send to the agent
|
|
243
|
+
"""
|
|
244
|
+
result = await _client(ctx).execute_agent(agent_id, message)
|
|
245
|
+
return _json(result)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@app.tool()
|
|
249
|
+
async def get_agent(agent_id: str, ctx=None) -> str:
|
|
250
|
+
"""Get details of a specific agent.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
agent_id: The agent ID
|
|
254
|
+
"""
|
|
255
|
+
result = await _client(ctx).get_agent(agent_id)
|
|
256
|
+
return _json(result)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
260
|
+
# Knowledge Bases
|
|
261
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@app.tool()
|
|
265
|
+
async def list_knowledge_bases(ctx) -> str:
|
|
266
|
+
"""List all knowledge bases."""
|
|
267
|
+
result = await _client(ctx).list_knowledge_bases()
|
|
268
|
+
return _json(result)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
@app.tool()
|
|
272
|
+
async def create_knowledge_base(name: str, description: str | None = None, ctx=None) -> str:
|
|
273
|
+
"""Create a new knowledge base.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
name: Name for the knowledge base
|
|
277
|
+
description: Optional description
|
|
278
|
+
"""
|
|
279
|
+
result = await _client(ctx).create_knowledge_base(name, description=description)
|
|
280
|
+
return _json(result)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
284
|
+
# Cost & Observability
|
|
285
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@app.tool()
|
|
289
|
+
async def get_costs(provider_id: str, period: str | None = None, ctx=None) -> str:
|
|
290
|
+
"""Get cost breakdown for a specific provider.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
provider_id: The provider ID
|
|
294
|
+
period: Time period – e.g. 24h, 7d, 30d
|
|
295
|
+
"""
|
|
296
|
+
result = await _client(ctx).get_costs(provider_id, period=period)
|
|
297
|
+
return _json(result)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@app.tool()
|
|
301
|
+
async def get_gateway_logs(
|
|
302
|
+
limit: int | None = None,
|
|
303
|
+
offset: int | None = None,
|
|
304
|
+
model: str | None = None,
|
|
305
|
+
ctx=None,
|
|
306
|
+
) -> str:
|
|
307
|
+
"""Get gateway request logs for observability.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
limit: Maximum number of log entries to return
|
|
311
|
+
offset: Offset for pagination
|
|
312
|
+
model: Filter logs by model name
|
|
313
|
+
"""
|
|
314
|
+
result = await _client(ctx).get_gateway_logs(limit=limit, offset=offset, model=model)
|
|
315
|
+
return _json(result)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
319
|
+
# CLI entry point
|
|
320
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def main() -> None:
|
|
324
|
+
"""Run the Bonito MCP server."""
|
|
325
|
+
import argparse
|
|
326
|
+
|
|
327
|
+
parser = argparse.ArgumentParser(description="Bonito MCP Server")
|
|
328
|
+
parser.add_argument(
|
|
329
|
+
"--transport",
|
|
330
|
+
choices=["stdio", "sse"],
|
|
331
|
+
default="stdio",
|
|
332
|
+
help="Transport type (default: stdio)",
|
|
333
|
+
)
|
|
334
|
+
parser.add_argument(
|
|
335
|
+
"--host",
|
|
336
|
+
default="127.0.0.1",
|
|
337
|
+
help="Host for SSE transport (default: 127.0.0.1)",
|
|
338
|
+
)
|
|
339
|
+
parser.add_argument(
|
|
340
|
+
"--port",
|
|
341
|
+
type=int,
|
|
342
|
+
default=8080,
|
|
343
|
+
help="Port for SSE transport (default: 8080)",
|
|
344
|
+
)
|
|
345
|
+
args = parser.parse_args()
|
|
346
|
+
|
|
347
|
+
if args.transport == "sse":
|
|
348
|
+
app.settings.host = args.host
|
|
349
|
+
app.settings.port = args.port
|
|
350
|
+
app.run(transport="sse")
|
|
351
|
+
else:
|
|
352
|
+
app.run(transport="stdio")
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
if __name__ == "__main__":
|
|
356
|
+
main()
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "bonito-mcp"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "MCP server for the Bonito AI Platform – manage providers, models, agents, and gateway routing."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Bonito AI", email = "hello@getbonito.com" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["mcp", "bonito", "ai", "llm", "gateway"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Software Development :: Libraries",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"mcp>=1.0.0",
|
|
27
|
+
"httpx>=0.25.0",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
bonito-mcp = "bonito_mcp.server:main"
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://getbonito.com"
|
|
35
|
+
Documentation = "https://getbonito.com/docs"
|
|
36
|
+
Repository = "https://github.com/ShabariRepo/bonito"
|