Thinagents

MCP with ThinAgents

Minimal guide to connect ThinAgents to MCP servers

MCP lets agents call external tools over standard transports (stdio, HTTP, SSE). ThinAgents supports all three. Spec: MCP Transports (2025-06-18).

Configure MCP servers on an Agent

from thinagents import Agent

agent = Agent(
    name="MCP Agent",
    model="gemini/gemini-2.0-flash",
    mcp_servers=[
        {
            "transport": "sse",
            "url": "http://127.0.0.1:8000/sse",
            "headers": {"Authorization": "Bearer <API_TOKEN>"}
        },
        {
            # Local stdio server (e.g., filesystem)
            "transport": "stdio",
            "command": "npx",
            "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"],
            "env": {
                "...": "..." # Environment variables if needed
            }
        },
        {
            # Streamable HTTP server (e.g., GitHub)
            "transport": "http",
            "url": "http://127.0.0.1:8103",
            "headers": {"Authorization": "Bearer <GITHUB_TOKEN>"}
        },
    ],
)

Async usage

# Stream chunks
async for chunk in agent.astream("what tools are available?"):
    print(chunk.content, end="", flush=True)

# Or single response
response = await agent.arun("list repos for user <USER>")
print(response.content)
# server.py
import json
import requests
from fastmcp import FastMCP, Context


def get_bearer_token(ctx: Context) -> str:
    """Extract bearer token from Authorization header."""
    request = ctx.get_http_request()
    headers = request.headers
    authorization_header = headers.get('Authorization')

    if not authorization_header:
        raise ValueError("Authorization header missing")

    parts = authorization_header.split()
    if len(parts) != 2 or parts[0] != 'Bearer':
        raise ValueError("Invalid Authorization header format")

    return parts[1]


# Create MCP server
mcp = FastMCP("Weather_Service", streamable_http_path='/')

@mcp.tool()
def fetch_weather(city: str, ctx: Context) -> dict:
    """
    Fetch current weather data for a given city using OpenWeather API.
    Requires Authorization: Bearer <openweather_api_key> header.
    """
    try:
        api_key = get_bearer_token(ctx)
    except ValueError as e:
        return {
            "status": "Failed",
            "error": f"Authorization error: {str(e)}"
        }

    base_url = "http://api.openweathermap.org/data/2.5/weather"

    params = {
        "q": city,
        "appid": api_key,
        "units": "metric"
    }

    try:
        response = requests.get(base_url, params=params)
        response.raise_for_status()
        weather_data = response.json()

        with open("weather_data.json", "w") as file:
            json.dump(weather_data, file, indent=4)

        return {
            "temperature": weather_data["main"]["temp"],
            "feels_like": weather_data["main"]["feels_like"],
            "humidity": weather_data["main"]["humidity"],
            "description": weather_data["weather"][0]["description"],
            "wind_speed": weather_data["wind"]["speed"]
        }

    except requests.exceptions.RequestException as e:
        return {"error": f"Failed to fetch weather data: {str(e)}"}
    except (KeyError, ValueError) as e:
        return {"error": f"Failed to parse weather data: {str(e)}"}


if __name__ == "__main__":
    mcp.run(transport="sse", port=8000, host="localhost")
import os
import requests
from fastmcp import FastMCP, Context


mcp = FastMCP("GitHub HTTP Server", streamable_http_path='/')

def get_bearer_token(ctx: Context) -> str:
    """Extract bearer token from Authorization header."""
    request = ctx.get_http_request()
    headers = request.headers
    authorization_header = headers.get('Authorization')
    
    if not authorization_header:
        raise ValueError("Authorization header missing")
    
    parts = authorization_header.split()
    if len(parts) != 2 or parts[0] != 'Bearer':
        raise ValueError("Invalid Authorization header format")
    
    return parts[1]


def _github_headers(token: str) -> dict:
    headers = {"Accept": "application/vnd.github+json"}
    if token:
        headers["Authorization"] = f"Bearer {token}"
    return headers


@mcp.tool()
def list_repos(user: str, ctx: Context) -> list[str]:
    """List public repositories for a GitHub user/org. Requires Authorization: Bearer <github_token> header."""
    try:
        token = get_bearer_token(ctx)
    except ValueError as e:
        return [f"Authorization error: {str(e)}"]
    
    url = f"https://api.github.com/users/{user}/repos?per_page=100"
    resp = requests.get(url, headers=_github_headers(token), timeout=20)
    resp.raise_for_status()
    return [r["full_name"] for r in resp.json()]


@mcp.tool()
def repo_readme(repo: str, ctx: Context, ref: str = "") -> str:
    """Fetch README.md for repo ('owner/name'). Optionally specify ref (branch/tag/sha). Requires Authorization: Bearer <github_token> header."""
    try:
        token = get_bearer_token(ctx)
    except ValueError as e:
        return f"Authorization error: {str(e)}"
    
    owner, name = repo.split("/", 1)
    url = f"https://api.github.com/repos/{owner}/{name}/readme"
    if ref:
        url += f"?ref={ref}"
    resp = requests.get(url, headers={**_github_headers(token), "Accept": "application/vnd.github.raw"}, timeout=20)
    if resp.status_code == 404:
        return "README not found."
    resp.raise_for_status()
    return resp.text


@mcp.tool()
def repo_file(repo: str, path: str, ctx: Context, ref: str = "") -> str:
    """Fetch raw file content from a repo at given path. Optionally specify ref. Requires Authorization: Bearer <github_token> header."""
    try:
        token = get_bearer_token(ctx)
    except ValueError as e:
        return f"Authorization error: {str(e)}"
    
    owner, name = repo.split("/", 1)
    base = f"https://api.github.com/repos/{owner}/{name}/contents/{path}"
    url = f"{base}?ref={ref}" if ref else base
    resp = requests.get(url, headers={**_github_headers(token), "Accept": "application/vnd.github.raw"}, timeout=20)
    if resp.status_code == 404:
        return "File not found."
    resp.raise_for_status()
    return resp.text


if __name__ == "__main__":
    host = os.getenv("MCP_HOST", "127.0.0.1")
    port = int(os.getenv("MCP_PORT", "8103"))
    mcp.run(transport="streamable-http", host=host, port=port)

Refer modelcontextprotocol and FastMCP