mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-10 06:45:28 -05:00
chore(mcp): Remove dev artifacts and simplify credential lookup
- Remove MCP_BLOCK_IMPLEMENTATION.md development doc - Remove console.log debug statements from OAuth callback - Simplify credential lookup to single call (get_creds_by_provider already handles Python 3.13 StrEnum bug via _provider_matches) - Remove unused Credentials import from routes.py
This commit is contained in:
@@ -1,76 +0,0 @@
|
||||
# MCP Block Implementation Plan
|
||||
|
||||
## Overview
|
||||
|
||||
Create a single **MCPBlock** that dynamically integrates with any MCP (Model Context Protocol)
|
||||
server. Users provide a server URL, the block discovers available tools, presents them as a
|
||||
dropdown, and dynamically adjusts input/output schema based on the selected tool — exactly like
|
||||
`AgentExecutorBlock` handles dynamic schemas.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
User provides MCP server URL + credentials
|
||||
↓
|
||||
MCPBlock fetches tools via MCP protocol (tools/list)
|
||||
↓
|
||||
User selects tool from dropdown (stored in constantInput)
|
||||
↓
|
||||
Input schema dynamically updates based on selected tool's inputSchema
|
||||
↓
|
||||
On execution: MCPBlock calls the tool via MCP protocol (tools/call)
|
||||
↓
|
||||
Result yielded as block output
|
||||
```
|
||||
|
||||
## Design Decisions
|
||||
|
||||
1. **Single block, not many blocks** — One `MCPBlock` handles all MCP servers/tools
|
||||
2. **Dynamic schema via AgentExecutorBlock pattern** — Override `get_input_schema()`,
|
||||
`get_input_defaults()`, `get_missing_input()` on the Input class
|
||||
3. **Auth via API key or OAuth2 credentials** — Use existing `APIKeyCredentials` or
|
||||
`OAuth2Credentials` with `ProviderName.MCP` provider. API keys are sent as Bearer tokens;
|
||||
OAuth2 uses the access token.
|
||||
4. **HTTP-based MCP client** — Use `aiohttp` (already a dependency) to implement MCP Streamable
|
||||
HTTP transport directly. No need for the `mcp` Python SDK — the protocol is simple JSON-RPC
|
||||
over HTTP. Handles both JSON and SSE response formats.
|
||||
5. **No new DB tables** — Everything fits in existing `AgentBlock` + `AgentNode` tables
|
||||
|
||||
## Implementation Files
|
||||
|
||||
### New Files
|
||||
- `backend/blocks/mcp/` — MCP block package
|
||||
- `__init__.py`
|
||||
- `block.py` — MCPToolBlock implementation
|
||||
- `client.py` — MCP HTTP client (list_tools, call_tool)
|
||||
- `oauth.py` — MCP OAuth handler for dynamic endpoint discovery
|
||||
- `test_mcp.py` — Unit tests
|
||||
- `test_oauth.py` — OAuth handler tests
|
||||
- `test_integration.py` — Integration tests with local test server
|
||||
- `test_e2e.py` — E2E tests against real MCP servers
|
||||
|
||||
### Modified Files
|
||||
- `backend/integrations/providers.py` — Add `MCP = "mcp"` to ProviderName
|
||||
|
||||
## Dev Loop
|
||||
|
||||
```bash
|
||||
cd autogpt_platform/backend
|
||||
poetry run pytest backend/blocks/mcp/test_mcp.py -xvs # Unit tests
|
||||
poetry run pytest backend/blocks/mcp/test_oauth.py -xvs # OAuth tests
|
||||
poetry run pytest backend/blocks/mcp/test_integration.py -xvs # Integration tests
|
||||
poetry run pytest backend/blocks/mcp/ -xvs # All MCP tests
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
- [x] Research & Design
|
||||
- [x] Add ProviderName.MCP
|
||||
- [x] Implement MCP client (client.py)
|
||||
- [x] Implement MCPToolBlock (block.py)
|
||||
- [x] Add OAuth2 support (oauth.py)
|
||||
- [x] Write unit tests
|
||||
- [x] Write integration tests
|
||||
- [x] Write E2E tests
|
||||
- [x] Run tests & fix issues
|
||||
- [x] Create PR
|
||||
@@ -17,7 +17,7 @@ from pydantic import BaseModel, Field
|
||||
from backend.api.features.integrations.router import CredentialsMetaResponse
|
||||
from backend.blocks.mcp.client import MCPClient, MCPClientError
|
||||
from backend.blocks.mcp.oauth import MCPOAuthHandler
|
||||
from backend.data.model import Credentials, OAuth2Credentials
|
||||
from backend.data.model import OAuth2Credentials
|
||||
from backend.integrations.creds_manager import IntegrationCredentialsManager
|
||||
from backend.integrations.providers import ProviderName
|
||||
from backend.util.request import HTTPClientError, Requests
|
||||
@@ -77,14 +77,11 @@ async def discover_tools(
|
||||
auth_token = request.auth_token
|
||||
|
||||
# Auto-use stored MCP credential when no explicit token is provided.
|
||||
# Also check for the wrong provider string from Python 3.13 str(Enum) bug.
|
||||
if not auth_token:
|
||||
try:
|
||||
mcp_creds: list[Credentials] = []
|
||||
for prov in (ProviderName.MCP.value, "ProviderName.MCP"):
|
||||
mcp_creds.extend(
|
||||
await creds_manager.store.get_creds_by_provider(user_id, prov)
|
||||
)
|
||||
mcp_creds = await creds_manager.store.get_creds_by_provider(
|
||||
user_id, ProviderName.MCP.value
|
||||
)
|
||||
# Find the freshest credential for this server URL
|
||||
best_cred: OAuth2Credentials | None = None
|
||||
for cred in mcp_creds:
|
||||
@@ -359,14 +356,10 @@ async def mcp_oauth_callback(
|
||||
credentials.title = f"MCP: {hostname}"
|
||||
|
||||
# Remove old MCP credentials for the same server to prevent stale token buildup.
|
||||
# Also clean up credentials stored with the wrong provider string
|
||||
# ("ProviderName.MCP" instead of "mcp") from a Python 3.13 str(Enum) bug.
|
||||
try:
|
||||
old_creds: list[Credentials] = []
|
||||
for prov in (ProviderName.MCP.value, "ProviderName.MCP"):
|
||||
old_creds.extend(
|
||||
await creds_manager.store.get_creds_by_provider(user_id, prov)
|
||||
)
|
||||
old_creds = await creds_manager.store.get_creds_by_provider(
|
||||
user_id, ProviderName.MCP.value
|
||||
)
|
||||
for old in old_creds:
|
||||
if (
|
||||
isinstance(old, OAuth2Credentials)
|
||||
|
||||
@@ -215,15 +215,14 @@ class MCPToolBlock(Block):
|
||||
This is a fallback for nodes that don't have ``credentials`` explicitly
|
||||
set (e.g. nodes created before the credential field was wired up).
|
||||
"""
|
||||
from backend.data.model import Credentials
|
||||
from backend.integrations.creds_manager import IntegrationCredentialsManager
|
||||
from backend.integrations.providers import ProviderName
|
||||
|
||||
try:
|
||||
mgr = IntegrationCredentialsManager()
|
||||
mcp_creds: list[Credentials] = []
|
||||
for prov in (ProviderName.MCP.value, "ProviderName.MCP"):
|
||||
mcp_creds.extend(await mgr.store.get_creds_by_provider(user_id, prov))
|
||||
mcp_creds = await mgr.store.get_creds_by_provider(
|
||||
user_id, ProviderName.MCP.value
|
||||
)
|
||||
best: OAuth2Credentials | None = None
|
||||
for cred in mcp_creds:
|
||||
if (
|
||||
|
||||
@@ -47,16 +47,13 @@ export async function GET(request: Request) {
|
||||
var msg = ${safeJsonStringify(message)};
|
||||
var sent = false;
|
||||
|
||||
console.log("[MCP Callback] Script running, success:", msg.success, "code:", !!msg.code, "state:", !!msg.state);
|
||||
|
||||
// Method 1: BroadcastChannel (reliable across tabs/popups, no opener needed)
|
||||
try {
|
||||
var bc = new BroadcastChannel("mcp_oauth");
|
||||
bc.postMessage({ type: "mcp_oauth_result", success: msg.success, code: msg.code, state: msg.state, message: msg.message });
|
||||
bc.close();
|
||||
sent = true;
|
||||
console.log("[MCP Callback] BroadcastChannel message sent");
|
||||
} catch(e) { console.warn("[MCP Callback] BroadcastChannel failed:", e); }
|
||||
} catch(e) { /* BroadcastChannel not supported */ }
|
||||
|
||||
// Method 2: window.opener.postMessage (fallback for same-origin popups)
|
||||
try {
|
||||
@@ -66,18 +63,14 @@ export async function GET(request: Request) {
|
||||
window.location.origin
|
||||
);
|
||||
sent = true;
|
||||
console.log("[MCP Callback] postMessage sent to opener");
|
||||
} else {
|
||||
console.log("[MCP Callback] window.opener not available (COOP or popup blocked)");
|
||||
}
|
||||
} catch(e) { console.warn("[MCP Callback] postMessage failed:", e); }
|
||||
} catch(e) { /* opener not available (COOP) */ }
|
||||
|
||||
// Method 3: localStorage (most reliable cross-tab fallback)
|
||||
try {
|
||||
localStorage.setItem("mcp_oauth_result", JSON.stringify(msg));
|
||||
sent = true;
|
||||
console.log("[MCP Callback] localStorage set");
|
||||
} catch(e) { console.warn("[MCP Callback] localStorage failed:", e); }
|
||||
} catch(e) { /* localStorage not available */ }
|
||||
|
||||
var statusEl = document.getElementById("status");
|
||||
var spinnerEl = document.getElementById("spinner");
|
||||
|
||||
Reference in New Issue
Block a user