MCP com FastMCP (4/4): HTTP, autenticação e cliente MCP

MCP com FastMCP (4/4): HTTP, autenticação e cliente MCP

Nas partes anteriores construímos um servidor MCP completo com tools, resources e prompts. Para encerrar o guia, voltamos ao transporte **HTTP**, adicionamos **autenticação** com tokens Bearer e chaves RSA e, por fim, criamos um **cliente MCP** que se conecta ao servidor.

⚠️ Este capítulo continua o código das partes anteriores (Parte 1 · Parte 2 · Parte 3).

Aviso: Este post foi traduzido para o português usando um modelo de tradução automática. Por favor, me avise se encontrar algum erro.

📚 **Esta entrada faz parte da série _MCP com FastMCP_**, dividida em quatro capítulos que são lidos em ordem:

> * Parte 1: Primeiro servidor e tools

* Parte 2: Transporte, contexto e resources

* Parte 3: Recursos avançados e prompts

* 👉 **Parte 4: HTTP, autenticação e cliente**

Retorno a HTTPlink image 23

Vamos configurar http novamente como camada de transporte para as duas últimas coisas que vamos ver

Servidor MCPlink image 24

	
< > Input
Python
%%writefile gitHub_MCP_server/github_server.py
import httpx
from fastmcp import FastMCP, Context
from github import GITHUB_TOKEN, create_github_headers
import datetime
USER_ID = 1234567890
# Create FastMCP server
mcp = FastMCP(
name="GitHubMCP",
instructions="This server provides tools, resources and prompts to interact with the GitHub API.",
include_tags={"public"},
exclude_tags={"first_issue"}
)
sub_mcp = FastMCP(
name="SubMCP",
)
@mcp.tool(
tags={"public", "production"},
exclude_args=["user_id"], # user_id has to be injected by server, not provided by LLM
)
async def list_repository_issues(owner: str, repo_name: str, ctx: Context, user_id: int = USER_ID) -&gt; list[dict]:
"""
Lists open issues for a given GitHub repository.
Args:
owner: The owner of the repository (e.g., 'modelcontextprotocol')
repo_name: The name of the repository (e.g., 'python-sdk')
ctx: The context of the request
user_id: The user ID (automatically injected by the server)
Returns:
list[dict]: A list of dictionaries, each containing information about an issue
"""
# Limit to first 10 issues to avoid very long responses
api_url = f"https://api.github.com/repos/{owner}/{repo_name}/issues?state=open&amp;per_page=10"
ctx.info(f"Fetching issues from {api_url}...")
async with httpx.AsyncClient() as client:
try:
response = await client.get(api_url, headers=create_github_headers())
response.raise_for_status()
issues_data = response.json()
if not issues_data:
ctx.info("No open issues found for this repository.")
return [{"message": "No open issues found for this repository."}]
issues_summary = []
for issue in issues_data:
# Create a more concise summary
summary = f"#{issue.get('number', 'N/A')}: {issue.get('title', 'No title')}"
if issue.get('comments', 0) &gt; 0:
summary += f" ({issue.get('comments')} comments)"
issues_summary.append({
"number": issue.get("number"),
"title": issue.get("title"),
"user": issue.get("user", {}).get("login"),
"url": issue.get("html_url"),
"comments": issue.get("comments"),
"summary": summary
})
ctx.info(f"Found {len(issues_summary)} open issues.")
# Add context information
result = {
"total_found": len(issues_summary),
"repository": f"{owner}/{repo_name}",
"note": "Showing first 10 open issues" if len(issues_summary) == 10 else f"Showing all {len(issues_summary)} open issues",
"issues": issues_summary,
"requested_by_user_id": user_id
}
return [result]
except httpx.HTTPStatusError as e:
error_message = e.response.json().get("message", "No additional message from API.")
if e.response.status_code == 403 and GITHUB_TOKEN:
error_message += " (Rate limit with token or token lacks permissions?)"
elif e.response.status_code == 403 and not GITHUB_TOKEN:
error_message += " (Rate limit without token. Consider creating a .env file with GITHUB_TOKEN.)"
ctx.error(f"GitHub API error: {e.response.status_code}. {error_message}")
return [{
"error": f"GitHub API error: {e.response.status_code}",
"message": error_message
}]
except Exception as e:
ctx.error(f"An unexpected error occurred: {str(e)}")
return [{"error": f"An unexpected error occurred: {str(e)}"}]
@mcp.tool(tags={"private", "development"})
async def list_more_repository_issues(owner: str, repo_name: str) -&gt; list[dict]:
"""
Lists open issues for a given GitHub repository.
Args:
owner: The owner of the repository (e.g., 'modelcontextprotocol')
repo_name: The name of the repository (e.g., 'python-sdk')
Returns:
list[dict]: A list of dictionaries, each containing information about an issue
"""
# Limit to first 100 issues to avoid very long responses
api_url = f"https://api.github.com/repos/{owner}/{repo_name}/issues?state=open&amp;per_page=100"
print(f"Fetching issues from {api_url}...")
async with httpx.AsyncClient() as client:
try:
response = await client.get(api_url, headers=create_github_headers())
response.raise_for_status()
issues_data = response.json()
if not issues_data:
print("No open issues found for this repository.")
return [{"message": "No open issues found for this repository."}]
issues_summary = []
for issue in issues_data:
# Create a more concise summary
summary = f"#{issue.get('number', 'N/A')}: {issue.get('title', 'No title')}"
if issue.get('comments', 0) &gt; 0:
summary += f" ({issue.get('comments')} comments)"
issues_summary.append({
"number": issue.get("number"),
"title": issue.get("title"),
"user": issue.get("user", {}).get("login"),
"url": issue.get("html_url"),
"comments": issue.get("comments"),
"summary": summary
})
print(f"Found {len(issues_summary)} open issues.")
# Add context information
result = {
"total_found": len(issues_summary),
"repository": f"{owner}/{repo_name}",
"note": "Showing first 10 open issues" if len(issues_summary) == 10 else f"Showing all {len(issues_summary)} open issues",
"issues": issues_summary
}
return [result]
except httpx.HTTPStatusError as e:
error_message = e.response.json().get("message", "No additional message from API.")
if e.response.status_code == 403 and GITHUB_TOKEN:
error_message += " (Rate limit with token or token lacks permissions?)"
elif e.response.status_code == 403 and not GITHUB_TOKEN:
error_message += " (Rate limit without token. Consider creating a .env file with GITHUB_TOKEN.)"
print(f"GitHub API error: {e.response.status_code}. {error_message}")
return [{
"error": f"GitHub API error: {e.response.status_code}",
"message": error_message
}]
except Exception as e:
print(f"An unexpected error occurred: {str(e)}")
return [{"error": f"An unexpected error occurred: {str(e)}"}]
@mcp.tool(tags={"public", "first_issue"})
async def first_repository_issue(owner: str, repo_name: str) -&gt; list[dict]:
"""
Gets the first issue ever created in a GitHub repository.
Args:
owner: The owner of the repository (e.g., 'modelcontextprotocol')
repo_name: The name of the repository (e.g., 'python-sdk')
Returns:
list[dict]: A list containing information about the first issue created
"""
# Get the first issue by sorting by creation date in ascending order
api_url = f"https://api.github.com/repos/{owner}/{repo_name}/issues?state=all&amp;sort=created&amp;direction=asc&amp;per_page=1"
print(f"Fetching first issue from {api_url}...")
async with httpx.AsyncClient() as client:
try:
response = await client.get(api_url, headers=create_github_headers())
response.raise_for_status()
issues_data = response.json()
if not issues_data:
print("No issues found for this repository.")
return [{"message": "No issues found for this repository."}]
first_issue = issues_data[0]
# Create a detailed summary of the first issue
summary = f"#{first_issue.get('number', 'N/A')}: {first_issue.get('title', 'No title')}"
if first_issue.get('comments', 0) &gt; 0:
summary += f" ({first_issue.get('comments')} comments)"
issue_info = {
"number": first_issue.get("number"),
"title": first_issue.get("title"),
"user": first_issue.get("user", {}).get("login"),
"url": first_issue.get("html_url"),
"state": first_issue.get("state"),
"comments": first_issue.get("comments"),
"created_at": first_issue.get("created_at"),
"updated_at": first_issue.get("updated_at"),
"body": first_issue.get("body", "")[:500] + "..." if len(first_issue.get("body", "")) &gt; 500 else first_issue.get("body", ""),
"summary": summary
}
print(f"Found first issue: #{first_issue.get('number')} created on {first_issue.get('created_at')}")
# Add context information
result = {
"repository": f"{owner}/{repo_name}",
"note": "This is the very first issue created in this repository",
"first_issue": issue_info
}
return [result]
except httpx.HTTPStatusError as e:
error_message = e.response.json().get("message", "No additional message from API.")
if e.response.status_code == 403 and GITHUB_TOKEN:
error_message += " (Rate limit with token or token lacks permissions?)"
elif e.response.status_code == 403 and not GITHUB_TOKEN:
error_message += " (Rate limit without token. Consider creating a .env file with GITHUB_TOKEN.)"
print(f"GitHub API error: {e.response.status_code}. {error_message}")
return [{
"error": f"GitHub API error: {e.response.status_code}",
"message": error_message
}]
except Exception as e:
print(f"An unexpected error occurred: {str(e)}")
return [{"error": f"An unexpected error occurred: {str(e)}"}]
@mcp.resource("resource://server_info", tags={"public"})
def server_info(ctx: Context) -&gt; str:
"""
Returns information about the server.
"""
return {
"info": "This is the MCP GitHub server development for MaximoFN blog post",
"requested_id": ctx.request_id
}
# Use: ¿Puedes leer el resource github://repo/facebook/react para obtener información detallada del repositorio?
@mcp.resource("github://repo/{owner}/{repo_name}", tags={"public"})
async def repository_info(owner: str, repo_name: str, ctx: Context) -&gt; dict:
"""
Returns detailed information about a GitHub repository.
Args:
owner: The owner of the repository (e.g., 'modelcontextprotocol')
repo_name: The name of the repository (e.g., 'python-sdk')
ctx: The context of the request
Returns:
dict: Repository information including name, description, stats, etc.
"""
api_url = f"https://api.github.com/repos/{owner}/{repo_name}"
ctx.info(f"Fetching repository information from {api_url}...")
async with httpx.AsyncClient() as client:
try:
response = await client.get(api_url, headers=create_github_headers())
response.raise_for_status()
repo_data = response.json()
# Extract relevant repository information
repo_info = {
"name": repo_data.get("name"),
"full_name": repo_data.get("full_name"),
"description": repo_data.get("description"),
"owner": {
"login": repo_data.get("owner", {}).get("login"),
"type": repo_data.get("owner", {}).get("type")
},
"html_url": repo_data.get("html_url"),
"clone_url": repo_data.get("clone_url"),
"ssh_url": repo_data.get("ssh_url"),
"language": repo_data.get("language"),
"size": repo_data.get("size"), # Size in KB
"stargazers_count": repo_data.get("stargazers_count"),
"watchers_count": repo_data.get("watchers_count"),
"forks_count": repo_data.get("forks_count"),
"open_issues_count": repo_data.get("open_issues_count"),
"default_branch": repo_data.get("default_branch"),
"created_at": repo_data.get("created_at"),
"updated_at": repo_data.get("updated_at"),
"pushed_at": repo_data.get("pushed_at"),
"is_private": repo_data.get("private"),
"is_fork": repo_data.get("fork"),
"is_archived": repo_data.get("archived"),
"has_issues": repo_data.get("has_issues"),
"has_projects": repo_data.get("has_projects"),
"has_wiki": repo_data.get("has_wiki"),
"license": repo_data.get("license", {}).get("name") if repo_data.get("license") else None,
"topics": repo_data.get("topics", [])
}
ctx.info(f"Successfully retrieved information for repository {owner}/{repo_name}")
return repo_info
except httpx.HTTPStatusError as e:
error_message = e.response.json().get("message", "No additional message from API.")
if e.response.status_code == 404:
error_message = f"Repository {owner}/{repo_name} not found or is private."
elif e.response.status_code == 403 and GITHUB_TOKEN:
error_message += " (Rate limit with token or token lacks permissions?)"
elif e.response.status_code == 403 and not GITHUB_TOKEN:
error_message += " (Rate limit without token. Consider creating a .env file with GITHUB_TOKEN.)"
ctx.error(f"GitHub API error: {e.response.status_code}. {error_message}")
return {
"error": f"GitHub API error: {e.response.status_code}",
"message": error_message,
"repository": f"{owner}/{repo_name}"
}
except Exception as e:
ctx.error(f"An unexpected error occurred: {str(e)}")
return {
"error": f"An unexpected error occurred: {str(e)}",
"repository": f"{owner}/{repo_name}"
}
@mcp.prompt(
name="generate_issues_prompt",
description="Generates a structured prompt for asking about GitHub repository issues. Use this when users want to formulate questions about repository issues, or need help creating prompts for issue analysis.",
tags={"public"}
)
def generate_issues_prompt(owner: str, repo_name: str) -&gt; str:
"""
Generates a structured prompt for asking about GitHub repository issues.
This prompt template helps users formulate clear questions about repository issues
and can be used as a starting point for issue analysis or research.
Args:
owner: Repository owner (e.g., 'huggingface', 'microsoft')
repo_name: Repository name (e.g., 'transformers', 'vscode')
Returns:
A formatted prompt asking about repository issues
"""
return f"""Please provide information about the open issues in the repository {owner}/{repo_name}.
I'm interested in:
- Current open issues and their status
- Recent issue trends and patterns
- Common issue categories or topics
- Any critical or high-priority issues
Repository: {owner}/{repo_name}"""
@sub_mcp.tool(tags={"public"})
def hello_world() -&gt; str:
"""
Returns a simple greeting.
"""
return "Hello, world!"
mcp.mount("sub_mcp", sub_mcp)
if __name__ == "__main__":
print("DEBUG: Starting FastMCP GitHub server...")
print(f"DEBUG: Server name: {mcp.name}")
# Initialize and run the server, run with uv run client.py http://localhost:8000/mcp
mcp.run(
transport="streamable-http",
host="0.0.0.0",
port=8000,
)
Copied
>_ Output
			
Overwriting gitHub_MCP_server/github_server.py

Cliente MCPlink image 25

	
< > Input
Python
%%writefile client_MCP/client.py
import sys
import asyncio
from contextlib import AsyncExitStack
from anthropic import Anthropic
from dotenv import load_dotenv
from fastmcp import Client
# Load environment variables from .env file
load_dotenv()
class FastMCPClient:
"""
FastMCP client that integrates with Claude to process user queries
and use tools and resources exposed by a FastMCP server.
"""
def __init__(self):
"""Initialize the FastMCP client with Anthropic and resource management."""
self.exit_stack = AsyncExitStack()
self.anthropic = Anthropic()
self.client = None
async def connect_to_server(self, server_url: str):
"""
Connect to the specified FastMCP server via HTTP.
Args:
server_url: URL of the HTTP server (e.g., "http://localhost:8000/mcp")
"""
print(f"🔗 Connecting to FastMCP HTTP server: {server_url}")
# Create FastMCP client for HTTP connection using SSE transport
self.client = Client(server_url)
# Note: FastMCP Client automatically detects HTTP URLs and uses SSE transport
print("✅ Client created successfully")
async def list_available_tools(self):
"""List available tools in the FastMCP server."""
try:
# Get list of tools from the server using FastMCP context
async with self.client as client:
tools = await client.list_tools()
if tools:
print(f" 🛠️ Available tools ({len(tools)}):")
print("=" * 50)
for tool in tools:
print(f"📋 {tool.name}")
if tool.description:
print(f" Description: {tool.description}")
# Show parameters if available
if hasattr(tool, 'inputSchema') and tool.inputSchema:
if 'properties' in tool.inputSchema:
params = list(tool.inputSchema['properties'].keys())
print(f" Parameters: {', '.join(params)}")
print()
else:
print("⚠️ No tools found in the server")
except Exception as e:
print(f"❌ Error listing tools: {str(e)}")
async def list_available_resources(self):
"""List available resources in the FastMCP server."""
try:
# Get list of resources from the server using FastMCP context
async with self.client as client:
resources = await client.list_resources()
if resources:
print(f" 📚 Available resources ({len(resources)}):")
print("=" * 50)
for resource in resources:
print(f"📄 {resource.uri}")
if resource.name:
print(f" Name: {resource.name}")
if resource.description:
print(f" Description: {resource.description}")
if resource.mimeType:
print(f" MIME Type: {resource.mimeType}")
print()
else:
print("⚠️ No resources found in the server")
except Exception as e:
print(f"❌ Error listing resources: {str(e)}")
async def read_resource(self, resource_uri: str):
"""
Read a specific resource from the server.
Args:
resource_uri: URI of the resource to read
Returns:
str: Resource content
"""
try:
async with self.client as client:
result = await client.read_resource(resource_uri)
return result
except Exception as e:
print(f"❌ Error reading resource {resource_uri}: {str(e)}")
return None
async def process_query(self, query: str) -&gt; str:
"""
Process a user query, interacting with Claude and FastMCP tools and resources.
Args:
query: User query
Returns:
str: Final processed response
"""
try:
# Use FastMCP context for all operations
async with self.client as client:
# Get available tools and resources
tools_list = await client.list_tools()
resources_list = await client.list_resources()
# Prepare tools for Claude in correct format
claude_tools = []
for tool in tools_list:
claude_tool = {
"name": tool.name,
"description": tool.description or f"Tool {tool.name}",
"input_schema": tool.inputSchema or {"type": "object", "properties": {}}
}
claude_tools.append(claude_tool)
# Add a special tool for reading resources (including template resources)
resource_description = "Read a resource from the MCP server. "
if resources_list:
# Convert URIs to strings to avoid AnyUrl object issues
resource_uris = [str(r.uri) for r in resources_list]
resource_description += f"Available static resources: {', '.join(resource_uris)}. "
resource_description += "Also supports template resources like github://repo/owner/repo_name for GitHub repository information."
claude_tools.append({
"name": "read_mcp_resource",
"description": resource_description,
"input_schema": {
"type": "object",
"properties": {
"resource_uri": {
"type": "string",
"description": "URI of the resource to read. Can be static (like resource://server_info) or template-based (like github://repo/facebook/react)"
}
},
"required": ["resource_uri"]
}
})
# Create initial message for Claude
messages = [
{
"role": "user",
"content": query
}
]
# First call to Claude
response = self.anthropic.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=6000,
messages=messages,
tools=claude_tools if claude_tools else None
)
# Process Claude's response
response_text = ""
for content_block in response.content:
if content_block.type == "text":
response_text += content_block.text
elif content_block.type == "tool_use":
# Claude wants to use a tool
tool_name = content_block.name
tool_args = content_block.input
tool_call_id = content_block.id
print(f"🔧 Claude wants to use: {tool_name}")
print(f"📝 Arguments: {tool_args}")
try:
if tool_name == "read_mcp_resource":
# Handle resource reading
resource_uri = tool_args.get("resource_uri")
if resource_uri:
tool_result = await client.read_resource(resource_uri)
print(f"📖 Resource read successfully: {resource_uri}")
# Better handling of resource result
if hasattr(tool_result, 'content'):
# If it's a resource response object, extract content
if hasattr(tool_result.content, 'text'):
result_content = tool_result.content.text
else:
result_content = str(tool_result.content)
else:
# If it's already a string or simple object
result_content = str(tool_result)
else:
tool_result = "Error: No resource URI provided"
result_content = tool_result
else:
# Execute regular tool on the FastMCP server
tool_result = await client.call_tool(tool_name, tool_args)
print(f"✅ Tool executed successfully")
result_content = str(tool_result)
# Add tool result to the conversation
messages.append({
"role": "assistant",
"content": response.content
})
# Format result for Claude
if tool_result:
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_call_id,
"content": f"Tool result: {result_content}"
}]
})
else:
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_call_id,
"content": "Tool executed without response content"
}]
})
# Second call to Claude with the tool result
final_response = self.anthropic.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=6000,
messages=messages,
tools=claude_tools if claude_tools else None
)
# Extract text from the final response
for final_content in final_response.content:
if final_content.type == "text":
response_text += final_content.text
except Exception as e:
error_msg = f"❌ Error executing {tool_name}: {str(e)}"
print(error_msg)
response_text += f" {error_msg}"
return response_text
except Exception as e:
error_msg = f"❌ Error processing query: {str(e)}"
print(error_msg)
return error_msg
async def chat_loop(self):
"""
Main chat loop with user interaction.
"""
print(" 🤖 FastMCP HTTP client started. Write 'quit', 'q', 'exit', 'salir' to exit.")
print("💬 You can ask questions about GitHub repositories!")
print("📚 The client can use tools and resources from the FastMCP server")
print("-" * 60)
while True:
try:
# Request user input
user_input = input(" 👤 You: ").strip()
if user_input.lower() in ['quit', 'q', 'exit', 'salir']:
print("👋 Bye!")
break
if not user_input:
continue
print(" 🤔 Claude is thinking...")
# Process query
response = await self.process_query(user_input)
# Show response
print(f" 🤖 Claude: {response}")
except KeyboardInterrupt:
print(" 👋 Disconnecting...")
break
except Exception as e:
print(f" ❌ Error in chat: {str(e)}")
continue
async def cleanup(self):
"""Clean up resources and close connections."""
print("🧹 Cleaning up resources...")
# FastMCP Client cleanup is handled automatically by context manager
await self.exit_stack.aclose()
print("✅ Resources released")
async def main():
"""
Main function that initializes and runs the FastMCP client.
"""
# Verify command line arguments
if len(sys.argv) != 2:
print("❌ Usage: python client.py &lt;http_server_url&gt;")
print("📝 Example: python client.py http://localhost:8000/mcp")
print("📝 Note: Now connects to HTTP server instead of executing script")
sys.exit(1)
server_url = sys.argv[1]
# Validate URL format
if not server_url.startswith(('http://', 'https://')):
print("❌ Error: Server URL must start with http:// or https://")
print("📝 Example: python client.py http://localhost:8000")
sys.exit(1)
# Create and run client
client = FastMCPClient()
try:
# Connect to the server
await client.connect_to_server(server_url)
# List available tools and resources after connection
await client.list_available_tools()
await client.list_available_resources()
# Start chat loop
await client.chat_loop()
except Exception as e:
print(f"❌ Fatal error: {str(e)}")
finally:
# Ensure resources are cleaned up
await client.cleanup()
if __name__ == "__main__":
# Entry point of the script
asyncio.run(main())
Copied
>_ Output
			
Overwriting client_MCP/client.py

Autenticaçãolink image 26

Se quisermos criar um servidor MCP ao qual apenas determinados clientes possam se conectar, podemos adicionar autenticação

Servidor MCPlink image 27

Criamos o servidor com autenticação

	
< > Input
Python
%%writefile gitHub_MCP_server/github_server.py
import httpx
from fastmcp import FastMCP, Context
from fastmcp.server.auth import BearerAuthProvider
from fastmcp.server.auth.providers.bearer import RSAKeyPair
from fastmcp.server.dependencies import get_access_token, AccessToken
from github import GITHUB_TOKEN, create_github_headers
import datetime
USER_ID = 1234567890
# Generate RSA key pair for development and testing
print("🔐 Generating RSA key pair for authentication...")
key_pair = RSAKeyPair.generate()
# Configure Bearer authentication provider
auth_provider = BearerAuthProvider(
public_key=key_pair.public_key,
issuer="https://github-mcp.maxfn.dev",
audience="github-mcp-server",
required_scopes=["github:read"] # Global scope required for all requests
)
# Generate a test token for development
development_token = key_pair.create_token(
subject="dev-user-maxfn",
issuer="https://github-mcp.maxfn.dev",
audience="github-mcp-server",
scopes=["github:read", "github:write"],
expires_in_seconds=3600 * 24 # Token is valid for 24 hours
)
print(f"🎫 Development token generated:")
print(f" {development_token}")
print("💡 Use this token in the client to authenticate")
print("-" * 60)
# Create FastMCP server with authentication
mcp = FastMCP(
name="GitHubMCP",
instructions="This server provides tools, resources and prompts to interact with the GitHub API.",
include_tags={"public"},
exclude_tags={"first_issue"},
auth=auth_provider # Add authentication to the server
)
sub_mcp = FastMCP(
name="SubMCP",
)
@mcp.tool(
tags={"public", "production"},
exclude_args=["user_id"], # user_id has to be injected by server, not provided by LLM
)
async def list_repository_issues(owner: str, repo_name: str, ctx: Context, user_id: int = USER_ID) -&gt; list[dict]:
"""
Lists open issues for a given GitHub repository.
Args:
owner: The owner of the repository (e.g., 'modelcontextprotocol')
repo_name: The name of the repository (e.g., 'python-sdk')
ctx: The context of the request
user_id: The user ID (automatically injected by the server)
Returns:
list[dict]: A list of dictionaries, each containing information about an issue
"""
# Limit to first 10 issues to avoid very long responses
api_url = f"https://api.github.com/repos/{owner}/{repo_name}/issues?state=open&amp;per_page=10"
ctx.info(f"Fetching issues from {api_url}...")
async with httpx.AsyncClient() as client:
try:
response = await client.get(api_url, headers=create_github_headers())
response.raise_for_status()
issues_data = response.json()
if not issues_data:
ctx.info("No open issues found for this repository.")
return [{"message": "No open issues found for this repository."}]
issues_summary = []
for issue in issues_data:
# Create a more concise summary
summary = f"#{issue.get('number', 'N/A')}: {issue.get('title', 'No title')}"
if issue.get('comments', 0) &gt; 0:
summary += f" ({issue.get('comments')} comments)"
issues_summary.append({
"number": issue.get("number"),
"title": issue.get("title"),
"user": issue.get("user", {}).get("login"),
"url": issue.get("html_url"),
"comments": issue.get("comments"),
"summary": summary
})
ctx.info(f"Found {len(issues_summary)} open issues.")
# Get authenticated access token information
try:
access_token: AccessToken = get_access_token()
authenticated_user = access_token.client_id
user_scopes = access_token.scopes
ctx.info(f"Request authenticated for user: {authenticated_user} with scopes: {user_scopes}")
except Exception as e:
authenticated_user = "unknown"
user_scopes = []
ctx.warning(f"Could not get access token info: {e}")
# Add context information
result = {
"total_found": len(issues_summary),
"repository": f"{owner}/{repo_name}",
"note": "Showing first 10 open issues" if len(issues_summary) == 10 else f"Showing all {len(issues_summary)} open issues",
"issues": issues_summary,
"requested_by_user_id": user_id,
"authenticated_user": authenticated_user,
"user_scopes": user_scopes
}
return [result]
except httpx.HTTPStatusError as e:
error_message = e.response.json().get("message", "No additional message from API.")
if e.response.status_code == 403 and GITHUB_TOKEN:
error_message += " (Rate limit with token or token lacks permissions?)"
elif e.response.status_code == 403 and not GITHUB_TOKEN:
error_message += " (Rate limit without token. Consider creating a .env file with GITHUB_TOKEN.)"
ctx.error(f"GitHub API error: {e.response.status_code}. {error_message}")
return [{
"error": f"GitHub API error: {e.response.status_code}",
"message": error_message
}]
except Exception as e:
ctx.error(f"An unexpected error occurred: {str(e)}")
return [{"error": f"An unexpected error occurred: {str(e)}"}]
@mcp.tool(tags={"private", "development"})
async def list_more_repository_issues(owner: str, repo_name: str) -&gt; list[dict]:
"""
Lists open issues for a given GitHub repository.
Args:
owner: The owner of the repository (e.g., 'modelcontextprotocol')
repo_name: The name of the repository (e.g., 'python-sdk')
Returns:
list[dict]: A list of dictionaries, each containing information about an issue
"""
# Limit to first 100 issues to avoid very long responses
api_url = f"https://api.github.com/repos/{owner}/{repo_name}/issues?state=open&amp;per_page=100"
print(f"Fetching issues from {api_url}...")
async with httpx.AsyncClient() as client:
try:
response = await client.get(api_url, headers=create_github_headers())
response.raise_for_status()
issues_data = response.json()
if not issues_data:
print("No open issues found for this repository.")
return [{"message": "No open issues found for this repository."}]
issues_summary = []
for issue in issues_data:
# Create a more concise summary
summary = f"#{issue.get('number', 'N/A')}: {issue.get('title', 'No title')}"
if issue.get('comments', 0) &gt; 0:
summary += f" ({issue.get('comments')} comments)"
issues_summary.append({
"number": issue.get("number"),
"title": issue.get("title"),
"user": issue.get("user", {}).get("login"),
"url": issue.get("html_url"),
"comments": issue.get("comments"),
"summary": summary
})
print(f"Found {len(issues_summary)} open issues.")
# Add context information
result = {
"total_found": len(issues_summary),
"repository": f"{owner}/{repo_name}",
"note": "Showing first 10 open issues" if len(issues_summary) == 10 else f"Showing all {len(issues_summary)} open issues",
"issues": issues_summary
}
return [result]
except httpx.HTTPStatusError as e:
error_message = e.response.json().get("message", "No additional message from API.")
if e.response.status_code == 403 and GITHUB_TOKEN:
error_message += " (Rate limit with token or token lacks permissions?)"
elif e.response.status_code == 403 and not GITHUB_TOKEN:
error_message += " (Rate limit without token. Consider creating a .env file with GITHUB_TOKEN.)"
print(f"GitHub API error: {e.response.status_code}. {error_message}")
return [{
"error": f"GitHub API error: {e.response.status_code}",
"message": error_message
}]
except Exception as e:
print(f"An unexpected error occurred: {str(e)}")
return [{"error": f"An unexpected error occurred: {str(e)}"}]
@mcp.tool(tags={"public", "first_issue"})
async def first_repository_issue(owner: str, repo_name: str) -&gt; list[dict]:
"""
Gets the first issue ever created in a GitHub repository.
Args:
owner: The owner of the repository (e.g., 'modelcontextprotocol')
repo_name: The name of the repository (e.g., 'python-sdk')
Returns:
list[dict]: A list containing information about the first issue created
"""
# Get the first issue by sorting by creation date in ascending order
api_url = f"https://api.github.com/repos/{owner}/{repo_name}/issues?state=all&amp;sort=created&amp;direction=asc&amp;per_page=1"
print(f"Fetching first issue from {api_url}...")
async with httpx.AsyncClient() as client:
try:
response = await client.get(api_url, headers=create_github_headers())
response.raise_for_status()
issues_data = response.json()
if not issues_data:
print("No issues found for this repository.")
return [{"message": "No issues found for this repository."}]
first_issue = issues_data[0]
# Create a detailed summary of the first issue
summary = f"#{first_issue.get('number', 'N/A')}: {first_issue.get('title', 'No title')}"
if first_issue.get('comments', 0) &gt; 0:
summary += f" ({first_issue.get('comments')} comments)"
issue_info = {
"number": first_issue.get("number"),
"title": first_issue.get("title"),
"user": first_issue.get("user", {}).get("login"),
"url": first_issue.get("html_url"),
"state": first_issue.get("state"),
"comments": first_issue.get("comments"),
"created_at": first_issue.get("created_at"),
"updated_at": first_issue.get("updated_at"),
"body": first_issue.get("body", "")[:500] + "..." if len(first_issue.get("body", "")) &gt; 500 else first_issue.get("body", ""),
"summary": summary
}
print(f"Found first issue: #{first_issue.get('number')} created on {first_issue.get('created_at')}")
# Add context information
result = {
"repository": f"{owner}/{repo_name}",
"note": "This is the very first issue created in this repository",
"first_issue": issue_info
}
return [result]
except httpx.HTTPStatusError as e:
error_message = e.response.json().get("message", "No additional message from API.")
if e.response.status_code == 403 and GITHUB_TOKEN:
error_message += " (Rate limit with token or token lacks permissions?)"
elif e.response.status_code == 403 and not GITHUB_TOKEN:
error_message += " (Rate limit without token. Consider creating a .env file with GITHUB_TOKEN.)"
print(f"GitHub API error: {e.response.status_code}. {error_message}")
return [{
"error": f"GitHub API error: {e.response.status_code}",
"message": error_message
}]
except Exception as e:
print(f"An unexpected error occurred: {str(e)}")
return [{"error": f"An unexpected error occurred: {str(e)}"}]
@mcp.resource("resource://server_info", tags={"public"})
def server_info(ctx: Context) -&gt; str:
"""
Returns information about the server.
"""
return {
"info": "This is the MCP GitHub server development for MaximoFN blog post",
"requested_id": ctx.request_id
}
@mcp.resource("github://repo/{owner}/{repo_name}", tags={"public"})
async def repository_info(owner: str, repo_name: str, ctx: Context) -&gt; dict:
"""
Returns detailed information about a GitHub repository.
Args:
owner: The owner of the repository (e.g., 'modelcontextprotocol')
repo_name: The name of the repository (e.g., 'python-sdk')
ctx: The context of the request
Returns:
dict: Repository information including name, description, stats, etc.
"""
api_url = f"https://api.github.com/repos/{owner}/{repo_name}"
ctx.info(f"Fetching repository information from {api_url}...")
async with httpx.AsyncClient() as client:
try:
response = await client.get(api_url, headers=create_github_headers())
response.raise_for_status()
repo_data = response.json()
# Extract relevant repository information
repo_info = {
"name": repo_data.get("name"),
"full_name": repo_data.get("full_name"),
"description": repo_data.get("description"),
"owner": {
"login": repo_data.get("owner", {}).get("login"),
"type": repo_data.get("owner", {}).get("type")
},
"html_url": repo_data.get("html_url"),
"clone_url": repo_data.get("clone_url"),
"ssh_url": repo_data.get("ssh_url"),
"language": repo_data.get("language"),
"size": repo_data.get("size"), # Size in KB
"stargazers_count": repo_data.get("stargazers_count"),
"watchers_count": repo_data.get("watchers_count"),
"forks_count": repo_data.get("forks_count"),
"open_issues_count": repo_data.get("open_issues_count"),
"default_branch": repo_data.get("default_branch"),
"created_at": repo_data.get("created_at"),
"updated_at": repo_data.get("updated_at"),
"pushed_at": repo_data.get("pushed_at"),
"is_private": repo_data.get("private"),
"is_fork": repo_data.get("fork"),
"is_archived": repo_data.get("archived"),
"has_issues": repo_data.get("has_issues"),
"has_projects": repo_data.get("has_projects"),
"has_wiki": repo_data.get("has_wiki"),
"license": repo_data.get("license", {}).get("name") if repo_data.get("license") else None,
"topics": repo_data.get("topics", [])
}
ctx.info(f"Successfully retrieved information for repository {owner}/{repo_name}")
return repo_info
except httpx.HTTPStatusError as e:
error_message = e.response.json().get("message", "No additional message from API.")
if e.response.status_code == 404:
error_message = f"Repository {owner}/{repo_name} not found or is private."
elif e.response.status_code == 403 and GITHUB_TOKEN:
error_message += " (Rate limit with token or token lacks permissions?)"
elif e.response.status_code == 403 and not GITHUB_TOKEN:
error_message += " (Rate limit without token. Consider creating a .env file with GITHUB_TOKEN.)"
ctx.error(f"GitHub API error: {e.response.status_code}. {error_message}")
return {
"error": f"GitHub API error: {e.response.status_code}",
"message": error_message,
"repository": f"{owner}/{repo_name}"
}
except Exception as e:
ctx.error(f"An unexpected error occurred: {str(e)}")
return {
"error": f"An unexpected error occurred: {str(e)}",
"repository": f"{owner}/{repo_name}"
}
@mcp.prompt(
name="generate_issues_prompt",
description="Generates a structured prompt for asking about GitHub repository issues. Use this when users want to formulate questions about repository issues, or need help creating prompts for issue analysis.",
tags={"public"}
)
def generate_issues_prompt(owner: str, repo_name: str) -&gt; str:
"""
Generates a structured prompt for asking about GitHub repository issues.
This prompt template helps users formulate clear questions about repository issues
and can be used as a starting point for issue analysis or research.
Args:
owner: Repository owner (e.g., 'huggingface', 'microsoft')
repo_name: Repository name (e.g., 'transformers', 'vscode')
Returns:
A formatted prompt asking about repository issues
"""
return f"""Please provide information about the open issues in the repository {owner}/{repo_name}.
I'm interested in:
- Current open issues and their status
- Recent issue trends and patterns
- Common issue categories or topics
- Any critical or high-priority issues
Repository: {owner}/{repo_name}"""
@sub_mcp.tool(tags={"public"})
def hello_world() -&gt; str:
"""
Returns a simple greeting.
"""
return "Hello, world!"
mcp.mount("sub_mcp", sub_mcp)
if __name__ == "__main__":
print("DEBUG: Starting FastMCP GitHub server...")
print(f"DEBUG: Server name: {mcp.name}")
# Initialize and run the server, run with uv run client.py http://localhost:8000/mcp
# 1. Run server with uv run github_server.py. It gives you a token to use in the client.py
# 2. Run client.py with the token you got from the server.py - uv run client.py http://localhost:8000/mcp &lt;your_bearer_token&gt;
mcp.run(
transport="streamable-http",
host="0.0.0.0",
port=8000,
)
Copied
>_ Output
			
Overwriting gitHub_MCP_server/github_server.py

Criamos um provedor de autenticação para o servidor e um token de desenvolvimento temporário

# Gerar par de chaves RSA para desenvolvimento e testes
print("🔐 Gerando par de chaves RSA para autenticação...")
key_pair = RSAKeyPair.generate()

# Configurar o provedor de autenticação Bearer
auth_provider = BearerAuthProvider(
public_key=key_pair.public_key,
issuer="https://github-mcp.maxfn.dev",
audience="github-mcp-server",
required_scopes=["github:read"] # Escopo global necessário para todas as solicitações
)

# Gerar um token de teste para desenvolvimento
development_token = key_pair.create_token(subject="dev-user-maxfn",
issuer="https://github-mcp.maxfn.dev",
audience="github-mcp-server",
scopes=["github:read", "github:write"],
expires_in_seconds=3600 * 24 # Token é válido por 24 horas
)

python

print(f"🎫 Token de desenvolvimento gerado:")

print(f"   {development_token}")
print("💡 Use este token no cliente para autenticar")
print("-" * 60)

Nós o usamos ao criar o servidor MCP

mcp = FastMCP(
name="GitHubMCP",
instructions="Este servidor fornece ferramentas, recursos e prompts para interagir com a API do GitHub.",
include_tags={"public"},exclude_tags={"first_issue"},
auth=auth_provider # Adicione autenticação ao servidor
)

Cliente MCPlink image 28

Criamos o cliente MCP com autenticação

	
< > Input
Python
%%writefile client_MCP/client.py
import sys
import asyncio
from contextlib import AsyncExitStack
from anthropic import Anthropic
from dotenv import load_dotenv
from fastmcp import Client
from fastmcp.client.auth import BearerAuth
# Load environment variables from .env file
load_dotenv()
class FastMCPClient:
"""
FastMCP client that integrates with Claude to process user queries
and use tools and resources exposed by a FastMCP server.
"""
def __init__(self):
"""Initialize the FastMCP client with Anthropic and resource management."""
self.exit_stack = AsyncExitStack()
self.anthropic = Anthropic()
self.client = None
async def connect_to_server(self, server_url: str, auth_token: str = None):
"""
Connect to the specified FastMCP server via HTTP with optional authentication.
Args:
server_url: URL of the HTTP server (e.g., "http://localhost:8000/mcp")
auth_token: Bearer token for authentication (optional)
"""
print(f"🔗 Connecting to FastMCP HTTP server: {server_url}")
# Create authentication if token is provided
auth = None
if auth_token:
auth = BearerAuth(token=auth_token)
print("🔐 Using Bearer token authentication")
else:
print("⚠️ No authentication token provided - connecting without auth")
# Create FastMCP client for HTTP connection using SSE transport
self.client = Client(server_url, auth=auth)
# Note: FastMCP Client automatically detects HTTP URLs and uses SSE transport
print("✅ Client created successfully")
async def list_available_tools(self):
"""List available tools in the FastMCP server."""
try:
# Get list of tools from the server using FastMCP context
async with self.client as client:
tools = await client.list_tools()
if tools:
print(f" 🛠️ Available tools ({len(tools)}):")
print("=" * 50)
for tool in tools:
print(f"📋 {tool.name}")
if tool.description:
print(f" Description: {tool.description}")
# Show parameters if available
if hasattr(tool, 'inputSchema') and tool.inputSchema:
if 'properties' in tool.inputSchema:
params = list(tool.inputSchema['properties'].keys())
print(f" Parameters: {', '.join(params)}")
print()
else:
print("⚠️ No tools found in the server")
except Exception as e:
print(f"❌ Error listing tools: {str(e)}")
async def list_available_resources(self):
"""List available resources in the FastMCP server."""
try:
# Get list of resources from the server using FastMCP context
async with self.client as client:
resources = await client.list_resources()
if resources:
print(f" 📚 Available resources ({len(resources)}):")
print("=" * 50)
for resource in resources:
print(f"📄 {resource.uri}")
if resource.name:
print(f" Name: {resource.name}")
if resource.description:
print(f" Description: {resource.description}")
if resource.mimeType:
print(f" MIME Type: {resource.mimeType}")
print()
else:
print("⚠️ No resources found in the server")
except Exception as e:
print(f"❌ Error listing resources: {str(e)}")
async def list_available_prompts(self):
"""List available prompts in the FastMCP server."""
try:
# Get list of prompts from the server using FastMCP context
async with self.client as client:
prompts = await client.list_prompts()
if prompts:
print(f" 💭 Available prompts ({len(prompts)}):")
print("=" * 50)
for prompt in prompts:
print(f"🎯 {prompt.name}")
if prompt.description:
print(f" Description: {prompt.description}")
# Show parameters if available
if hasattr(prompt, 'arguments') and prompt.arguments:
params = []
for arg in prompt.arguments:
param_info = f"{arg.name}: {arg.description or 'No description'}"
if arg.required:
param_info += " (required)"
params.append(param_info)
print(f" Parameters: {', '.join(params)}")
print()
else:
print("⚠️ No prompts found in the server")
except Exception as e:
print(f"❌ Error listing prompts: {str(e)}")
async def read_resource(self, resource_uri: str):
"""
Read a specific resource from the server.
Args:
resource_uri: URI of the resource to read
Returns:
str: Resource content
"""
try:
async with self.client as client:
result = await client.read_resource(resource_uri)
return result
except Exception as e:
print(f"❌ Error reading resource {resource_uri}: {str(e)}")
return None
async def get_prompt(self, prompt_name: str, prompt_args: dict = None):
"""
Get/call a specific prompt from the server.
Args:
prompt_name: Name of the prompt to call
prompt_args: Arguments for the prompt (if any)
Returns:
str: Generated prompt content
"""
try:
async with self.client as client:
if prompt_args:
result = await client.get_prompt(prompt_name, prompt_args)
else:
result = await client.get_prompt(prompt_name)
# Extract the prompt text from the response
if hasattr(result, 'messages') and result.messages:
# FastMCP returns prompts as message objects
return ' '.join([msg.content.text for msg in result.messages if hasattr(msg.content, 'text')])
elif hasattr(result, 'content'):
return str(result.content)
else:
return str(result)
except Exception as e:
print(f"❌ Error getting prompt {prompt_name}: {str(e)}")
return None
async def process_query(self, query: str) -&gt; str:
"""
Process a user query, interacting with Claude and FastMCP tools and resources.
Args:
query: User query
Returns:
str: Final processed response
"""
try:
# Use FastMCP context for all operations
async with self.client as client:
# Get available tools and resources
tools_list = await client.list_tools()
resources_list = await client.list_resources()
# Prepare tools for Claude in correct format
claude_tools = []
for tool in tools_list:
claude_tool = {
"name": tool.name,
"description": tool.description or f"Tool {tool.name}",
"input_schema": tool.inputSchema or {"type": "object", "properties": {}}
}
claude_tools.append(claude_tool)
# Add a special tool for reading resources (including template resources)
resource_description = "Read a resource from the MCP server. "
if resources_list:
# Convert URIs to strings to avoid AnyUrl object issues
resource_uris = [str(r.uri) for r in resources_list]
resource_description += f"Available static resources: {', '.join(resource_uris)}. "
resource_description += "Also supports template resources like github://repo/owner/repo_name for GitHub repository information."
claude_tools.append({
"name": "read_mcp_resource",
"description": resource_description,
"input_schema": {
"type": "object",
"properties": {
"resource_uri": {
"type": "string",
"description": "URI of the resource to read. Can be static (like resource://server_info) or template-based (like github://repo/facebook/react)"
}
},
"required": ["resource_uri"]
}
})
# Add a special tool for using prompts
prompt_description = "Generate specialized prompts from the MCP server. Use this when users want to: "
prompt_description += "- Create well-structured questions about repositories "
prompt_description += "- Get help formulating prompts for specific tasks "
prompt_description += "- Generate template questions for analysis "
if prompts_list:
prompt_names = [p.name for p in prompts_list]
prompt_description += f" Available prompts: {', '.join(prompt_names)} "
prompt_description += "- generate_issues_prompt: Creates structured questions about GitHub repository issues"
prompt_description += " IMPORTANT: Use prompts when users explicitly ask for help creating questions or prompts, or when they want to formulate better questions about repositories."
claude_tools.append({
"name": "use_mcp_prompt",
"description": prompt_description,
"input_schema": {
"type": "object",
"properties": {
"prompt_name": {
"type": "string",
"description": "Name of the prompt to use. Available: 'generate_issues_prompt'"
},
"prompt_args": {
"type": "object",
"description": "Arguments for the prompt. For generate_issues_prompt: {'owner': 'repo-owner', 'repo_name': 'repo-name'}",
"properties": {
"owner": {
"type": "string",
"description": "Repository owner (e.g., 'huggingface', 'microsoft')"
},
"repo_name": {
"type": "string",
"description": "Repository name (e.g., 'transformers', 'vscode')"
}
}
}
},
"required": ["prompt_name"]
}
})
# Create initial message for Claude
messages = [
{
"role": "user",
"content": query
}
]
# First call to Claude
response = self.anthropic.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=6000,
messages=messages,
tools=claude_tools if claude_tools else None
)
# Process Claude's response
response_text = ""
for content_block in response.content:
if content_block.type == "text":
response_text += content_block.text
elif content_block.type == "tool_use":
# Claude wants to use a tool
tool_name = content_block.name
tool_args = content_block.input
tool_call_id = content_block.id
print(f"🔧 Claude wants to use: {tool_name}")
print(f"📝 Arguments: {tool_args}")
try:
if tool_name == "read_mcp_resource":
# Handle resource reading
resource_uri = tool_args.get("resource_uri")
if resource_uri:
tool_result = await client.read_resource(resource_uri)
print(f"📖 Resource read successfully: {resource_uri}")
# Better handling of resource result
if hasattr(tool_result, 'content'):
# If it's a resource response object, extract content
if hasattr(tool_result.content, 'text'):
result_content = tool_result.content.text
else:
result_content = str(tool_result.content)
else:
# If it's already a string or simple object
result_content = str(tool_result)
else:
tool_result = "Error: No resource URI provided"
result_content = tool_result
elif tool_name == "use_mcp_prompt":
# Handle prompt usage
prompt_name = tool_args.get("prompt_name")
prompt_args = tool_args.get("prompt_args", {})
if prompt_name:
tool_result = await self.get_prompt(prompt_name, prompt_args)
print(f"💭 Prompt '{prompt_name}' generated successfully")
result_content = str(tool_result) if tool_result else "Error generating prompt"
else:
tool_result = "Error: No prompt name provided"
result_content = tool_result
else:
# Execute regular tool on the FastMCP server
tool_result = await client.call_tool(tool_name, tool_args)
print(f"✅ Tool executed successfully")
result_content = str(tool_result)
# Add tool result to the conversation
messages.append({
"role": "assistant",
"content": response.content
})
# Format result for Claude
if tool_result:
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_call_id,
"content": f"Tool result: {result_content}"
}]
})
else:
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_call_id,
"content": "Tool executed without response content"
}]
})
# Second call to Claude with the tool result
final_response = self.anthropic.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=6000,
messages=messages,
tools=claude_tools if claude_tools else None
)
# Extract text from the final response
for final_content in final_response.content:
if final_content.type == "text":
response_text += final_content.text
except Exception as e:
error_msg = f"❌ Error executing {tool_name}: {str(e)}"
print(error_msg)
response_text += f" {error_msg}"
return response_text
except Exception as e:
error_msg = f"❌ Error processing query: {str(e)}"
print(error_msg)
return error_msg
async def chat_loop(self):
"""
Main chat loop with user interaction.
"""
print(" 🤖 FastMCP HTTP client started. Write 'quit', 'q', 'exit', 'salir' to exit.")
print("💬 You can ask questions about GitHub repositories!")
print("📚 The client can use tools, resources, and prompts from the FastMCP server")
print()
print("💭 PROMPT Examples:")
print(" • 'Generate a prompt for asking about issues in facebook/react'")
print(" • 'Help me create a good question about microsoft/vscode issues'")
print(" • 'I need a structured prompt for analyzing tensorflow/tensorflow'")
print()
print("🔧 DIRECT Examples:")
print(" • 'Show me the issues in huggingface/transformers'")
print(" • 'Get repository info for github://repo/google/chrome'")
print("-" * 60)
while True:
try:
# Request user input
user_input = input(" 👤 You: ").strip()
if user_input.lower() in ['quit', 'q', 'exit', 'salir']:
print("👋 Bye!")
break
if not user_input:
continue
print(" 🤔 Claude is thinking...")
# Process query
response = await self.process_query(user_input)
# Show response
print(f" 🤖 Claude: {response}")
except KeyboardInterrupt:
print(" 👋 Disconnecting...")
break
except Exception as e:
print(f" ❌ Error in chat: {str(e)}")
continue
async def cleanup(self):
"""Clean up resources and close connections."""
print("🧹 Cleaning up resources...")
# FastMCP Client cleanup is handled automatically by context manager
await self.exit_stack.aclose()
print("✅ Resources released")
async def main():
"""
Main function that initializes and runs the FastMCP client.
"""
# Verify command line arguments
if len(sys.argv) &lt; 2 or len(sys.argv) &gt; 3:
print("❌ Usage: python client.py &lt;http_server_url&gt; [auth_token]")
print("📝 Example: python client.py http://localhost:8000/mcp")
print("📝 Example with auth: python client.py http://localhost:8000/mcp &lt;your_bearer_token&gt;")
print("📝 Note: Now connects to HTTP server instead of executing script")
sys.exit(1)
server_url = sys.argv[1]
auth_token = sys.argv[2] if len(sys.argv) == 3 else None
# Validate URL format
if not server_url.startswith(('http://', 'https://')):
print("❌ Error: Server URL must start with http:// or https://")
print("📝 Example: python client.py http://localhost:8000")
sys.exit(1)
# Create and run client
client = FastMCPClient()
try:
# Connect to the server
await client.connect_to_server(server_url, auth_token)
# List available tools, resources, and prompts after connection
await client.list_available_tools()
await client.list_available_resources()
await client.list_available_prompts()
# Start chat loop
await client.chat_loop()
except Exception as e:
print(f"❌ Fatal error: {str(e)}")
finally:
# Ensure resources are cleaned up
await client.cleanup()
if __name__ == "__main__":
# Entry point of the script
asyncio.run(main())
Copied
>_ Output
			
Overwriting client_MCP/client.py

O token de autenticação é criado a partir do token fornecido pelo usuário ao iniciar o cliente

# Criar autenticação se o token for fornecido
auth = None
if auth_token:
auth = BearerAuth(token=auth_token) print("🔐 Usando autenticação por token Bearer")
else:

python

print("⚠️ Nenhum token de autenticação fornecido - conectando sem autenticação")

O cliente é criado com o token de autenticação, que será enviado ao servidor

# Criar cliente FastMCP para conexão HTTP usando transporte SSE
self.client = Client(server_url, auth=auth)

Conecta-se ao servidor enviando o token

# Conectar ao servidor
await client.connect_to_server(server_url, auth_token)

Teste do MCP com autenticaçãolink image 29

Como voltamos para o http, primeiro temos que subir o servidor

	
< > Input
Python
!cd gitHub_MCP_server && source .venv/bin/activate && uv run github_server.py
Copied
>_ Output
			
🔐 Generating RSA key pair for authentication...
🎫 Development token generated:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2dpdGh1Yi1tY3AubWF4Zm4uZGV2Iiwic3ViIjoiZGV2LXVzZXItbWF4Zm4iLCJpYXQiOjE3NTExMDgzMDAsImV4cCI6MTc1MTE5NDcwMCwiYXVkIjoiZ2l0aHViLW1jcC1zZXJ2ZXIiLCJzY29wZSI6ImdpdGh1YjpyZWFkIGdpdGh1Yjp3cml0ZSJ9.PX6BtUhNCv9YVq1ZCh2teAU_LsdGMJx-W2jntTvVgdXv3aDyiOeMuZE9fIcqRy9zcXT1pjexqQQDiRhy8WlRL-mdKooEbIc_ffBVX9LPVaxKAzfzZTnx2lYTt6DgnebjjdNk_OsXF3ujH5s0xmGtY892j-k9P8dJLLrTrqXLhWG2NX_jqHB_kMalFd0LT83D6uXjPako_DKHjYKLc67WvZU_JglVS5eI9YCmmhMlhPHyO4FUlD9xb0DpbOgz8bO1ZExBrB_W2YKomGI_u8R56ItM8bS3eEwybPgEHfHhDNI6PNqsJ3DB1Grmc7KOmGX4LJCfPyB6mpl_bQmChKzcdg
💡 Use this token in the client to authenticate
------------------------------------------------------------
/Users/macm1/Documents/web/portafolio/posts/gitHub_MCP_server/github_server.py:412: DeprecationWarning: Mount prefixes are now optional and the first positional argument should be the server you want to mount.
mcp.mount("sub_mcp", sub_mcp)
DEBUG: Starting FastMCP GitHub server...
DEBUG: Server name: GitHubMCP
[06/28/25 12:58:20] INFO Starting MCP server 'GitHubMCP' with ]8;id=190590;file:///Users/macm1/Documents/web/portafolio/posts/gitHub_MCP_server/.venv/lib/python3.11/site-packages/fastmcp/server/server.py\server.py]8;;\:]8;id=102439;file:///Users/macm1/Documents/web/portafolio/posts/gitHub_MCP_server/.venv/lib/python3.11/site-packages/fastmcp/server/server.py#1297\1297]8;;\
transport 'streamable-http' on
http://0.0.0.0:8000/mcp/
INFO: Started server process [27262]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

Como vemos, nos gerou o token de autenticação eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2dpdGh1Yi1tY3AubWF4Zm4uZGV2Iiwic3ViIjoiZGV2LXVzZXItbWF4Zm4iLCJpYXQiOjE3NTExMDgzMDAsImV4cCI6MTc1MTE5NDcwMCwiYXVkIjoiZ2l0aHViLW1jcC1zZXJ2ZXIiLCJzY29wZSI6ImdpdGh1YjpyZWFkIGdpdGh1Yjp3cml0ZSJ9.PX6BtUhNCv9YVq1ZCh2teAU_LsdGMJx-W2jntTvVgdXv3aDyiOeMuZE9fIcqRy9zcXT1pjexqQQDiRhy8WlRL-mdKooEbIc_ffBVX9LPVaxKAzfzZTnx2lYTt6DgnebjjdNk_OsXF3ujH5s0xmGtY892j-k9P8dJLLrTrqXLhWG2NX_jqHB_kMalFd0LT83D6uXjPako_DKHjYKLc67WvZU_JglVS5eI9YCmmhMlhPHyO4FUlD9xb0DpbOgz8bO1ZExBrB_W2YKomGI_u8R56ItM8bS3eEwybPgEHfHhDNI6PNqsJ3DB1Grmc7KOmGX4LJCfPyB6mpl_bQmChKzcdg, é preciso usá-lo na hora de executar o cliente

E agora executamos o cliente com o token de autenticação que o servidor nos gerou.

	
< > Input
Python
!cd client_MCP && source .venv/bin/activate && uv run client.py http://localhost:8000/mcp eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2dpdGh1Yi1tY3AubWF4Zm4uZGV2Iiwic3ViIjoiZGV2LXVzZXItbWF4Zm4iLCJpYXQiOjE3NTExMDgzMDAsImV4cCI6MTc1MTE5NDcwMCwiYXVkIjoiZ2l0aHViLW1jcC1zZXJ2ZXIiLCJzY29wZSI6ImdpdGh1YjpyZWFkIGdpdGh1Yjp3cml0ZSJ9.PX6BtUhNCv9YVq1ZCh2teAU_LsdGMJx-W2jntTvVgdXv3aDyiOeMuZE9fIcqRy9zcXT1pjexqQQDiRhy8WlRL-mdKooEbIc_ffBVX9LPVaxKAzfzZTnx2lYTt6DgnebjjdNk_OsXF3ujH5s0xmGtY892j-k9P8dJLLrTrqXLhWG2NX_jqHB_kMalFd0LT83D6uXjPako_DKHjYKLc67WvZU_JglVS5eI9YCmmhMlhPHyO4FUlD9xb0DpbOgz8bO1ZExBrB_W2YKomGI_u8R56ItM8bS3eEwybPgEHfHhDNI6PNqsJ3DB1Grmc7KOmGX4LJCfPyB6mpl_bQmChKzcdg
Copied
>_ Output
			
🔗 Connecting to FastMCP HTTP server: http://localhost:8000/mcp
🔐 Using Bearer token authentication
✅ Client created successfully
🛠️ Available tools (2):
==================================================
📋 sub_mcp_hello_world
Description: Returns a simple greeting.
Parameters:
📋 list_repository_issues
Description: Lists open issues for a given GitHub repository.
Args:
owner: The owner of the repository (e.g., 'modelcontextprotocol')
repo_name: The name of the repository (e.g., 'python-sdk')
ctx: The context of the request
user_id: The user ID (automatically injected by the server)
Returns:
list[dict]: A list of dictionaries, each containing information about an issue
Parameters: owner, repo_name
📚 Available resources (1):
==================================================
...
📚 The client can use tools, resources, and prompts from the FastMCP server
💭 PROMPT Examples:
• 'Generate a prompt for asking about issues in facebook/react'
• 'Help me create a good question about microsoft/vscode issues'
• 'I need a structured prompt for analyzing tensorflow/tensorflow'
🔧 DIRECT Examples:
• 'Show me the issues in huggingface/transformers'
• 'Get repository info for github://repo/google/chrome'
------------------------------------------------------------
👤 You:

Como vemos, o cliente se conecta ao servidor e nos fornece uma lista dos tools, resources e prompts disponíveis.

Ping do cliente ao servidorlink image 30

Quando executamos o MCP com http como camada de transporte, o normal é que o cliente e o servidor não estejam no mesmo computador. Portanto, quando executamos o cliente, não podemos saber se o servidor está funcionando, então podemos desenvolver um ping para verificar se o servidor está funcionando.

Cliente MCPlink image 31

Vamos adicionar um ping ao cliente MCP

	
< > Input
Python
%%writefile client_MCP/client.py
import sys
import asyncio
from contextlib import AsyncExitStack
from anthropic import Anthropic
from dotenv import load_dotenv
from fastmcp import Client
from fastmcp.client.auth import BearerAuth
# Load environment variables from .env file
load_dotenv()
class FastMCPClient:
"""
FastMCP client that integrates with Claude to process user queries
and use tools and resources exposed by a FastMCP server.
"""
def __init__(self):
"""Initialize the FastMCP client with Anthropic and resource management."""
self.exit_stack = AsyncExitStack()
self.anthropic = Anthropic()
self.client = None
async def connect_to_server(self, server_url: str, auth_token: str = None):
"""
Connect to the specified FastMCP server via HTTP with optional authentication.
Args:
server_url: URL of the HTTP server (e.g., "http://localhost:8000/mcp")
auth_token: Bearer token for authentication (optional)
"""
print(f"🔗 Connecting to FastMCP HTTP server: {server_url}")
# Create authentication if token is provided
auth = None
if auth_token:
auth = BearerAuth(token=auth_token)
print("🔐 Using Bearer token authentication")
else:
print("⚠️ No authentication token provided - connecting without auth")
# Create FastMCP client for HTTP connection using SSE transport
self.client = Client(server_url, auth=auth)
# Note: FastMCP Client automatically detects HTTP URLs and uses SSE transport
print("✅ Client created successfully")
# Ping to server to check if it's alive
async with self.client as client:
response = await client.ping()
print(f"🏓 Server ping response: {response}")
async def list_available_tools(self):
"""List available tools in the FastMCP server."""
try:
# Get list of tools from the server using FastMCP context
async with self.client as client:
tools = await client.list_tools()
if tools:
print(f" 🛠️ Available tools ({len(tools)}):")
print("=" * 50)
for tool in tools:
print(f"📋 {tool.name}")
if tool.description:
print(f" Description: {tool.description}")
# Show parameters if available
if hasattr(tool, 'inputSchema') and tool.inputSchema:
if 'properties' in tool.inputSchema:
params = list(tool.inputSchema['properties'].keys())
print(f" Parameters: {', '.join(params)}")
print()
else:
print("⚠️ No tools found in the server")
except Exception as e:
print(f"❌ Error listing tools: {str(e)}")
async def list_available_resources(self):
"""List available resources in the FastMCP server."""
try:
# Get list of resources from the server using FastMCP context
async with self.client as client:
resources = await client.list_resources()
if resources:
print(f" 📚 Available resources ({len(resources)}):")
print("=" * 50)
for resource in resources:
print(f"📄 {resource.uri}")
if resource.name:
print(f" Name: {resource.name}")
if resource.description:
print(f" Description: {resource.description}")
if resource.mimeType:
print(f" MIME Type: {resource.mimeType}")
print()
else:
print("⚠️ No resources found in the server")
except Exception as e:
print(f"❌ Error listing resources: {str(e)}")
async def list_available_prompts(self):
"""List available prompts in the FastMCP server."""
try:
# Get list of prompts from the server using FastMCP context
async with self.client as client:
prompts = await client.list_prompts()
if prompts:
print(f" 💭 Available prompts ({len(prompts)}):")
print("=" * 50)
for prompt in prompts:
print(f"🎯 {prompt.name}")
if prompt.description:
print(f" Description: {prompt.description}")
# Show parameters if available
if hasattr(prompt, 'arguments') and prompt.arguments:
params = []
for arg in prompt.arguments:
param_info = f"{arg.name}: {arg.description or 'No description'}"
if arg.required:
param_info += " (required)"
params.append(param_info)
print(f" Parameters: {', '.join(params)}")
print()
else:
print("⚠️ No prompts found in the server")
except Exception as e:
print(f"❌ Error listing prompts: {str(e)}")
async def read_resource(self, resource_uri: str):
"""
Read a specific resource from the server.
Args:
resource_uri: URI of the resource to read
Returns:
str: Resource content
"""
try:
async with self.client as client:
result = await client.read_resource(resource_uri)
return result
except Exception as e:
print(f"❌ Error reading resource {resource_uri}: {str(e)}")
return None
async def get_prompt(self, prompt_name: str, prompt_args: dict = None):
"""
Get/call a specific prompt from the server.
Args:
prompt_name: Name of the prompt to call
prompt_args: Arguments for the prompt (if any)
Returns:
str: Generated prompt content
"""
try:
async with self.client as client:
if prompt_args:
result = await client.get_prompt(prompt_name, prompt_args)
else:
result = await client.get_prompt(prompt_name)
# Extract the prompt text from the response
if hasattr(result, 'messages') and result.messages:
# FastMCP returns prompts as message objects
return ' '.join([msg.content.text for msg in result.messages if hasattr(msg.content, 'text')])
elif hasattr(result, 'content'):
return str(result.content)
else:
return str(result)
except Exception as e:
print(f"❌ Error getting prompt {prompt_name}: {str(e)}")
return None
async def process_query(self, query: str) -&gt; str:
"""
Process a user query, interacting with Claude and FastMCP tools and resources.
Args:
query: User query
Returns:
str: Final processed response
"""
try:
# Use FastMCP context for all operations
async with self.client as client:
# Get available tools and resources
tools_list = await client.list_tools()
resources_list = await client.list_resources()
# Prepare tools for Claude in correct format
claude_tools = []
for tool in tools_list:
claude_tool = {
"name": tool.name,
"description": tool.description or f"Tool {tool.name}",
"input_schema": tool.inputSchema or {"type": "object", "properties": {}}
}
claude_tools.append(claude_tool)
# Add a special tool for reading resources (including template resources)
resource_description = "Read a resource from the MCP server. "
if resources_list:
# Convert URIs to strings to avoid AnyUrl object issues
resource_uris = [str(r.uri) for r in resources_list]
resource_description += f"Available static resources: {', '.join(resource_uris)}. "
resource_description += "Also supports template resources like github://repo/owner/repo_name for GitHub repository information."
claude_tools.append({
"name": "read_mcp_resource",
"description": resource_description,
"input_schema": {
"type": "object",
"properties": {
"resource_uri": {
"type": "string",
"description": "URI of the resource to read. Can be static (like resource://server_info) or template-based (like github://repo/facebook/react)"
}
},
"required": ["resource_uri"]
}
})
# Add a special tool for using prompts
prompt_description = "Generate specialized prompts from the MCP server. Use this when users want to: "
prompt_description += "- Create well-structured questions about repositories "
prompt_description += "- Get help formulating prompts for specific tasks "
prompt_description += "- Generate template questions for analysis "
if prompts_list:
prompt_names = [p.name for p in prompts_list]
prompt_description += f" Available prompts: {', '.join(prompt_names)} "
prompt_description += "- generate_issues_prompt: Creates structured questions about GitHub repository issues"
prompt_description += " IMPORTANT: Use prompts when users explicitly ask for help creating questions or prompts, or when they want to formulate better questions about repositories."
claude_tools.append({
"name": "use_mcp_prompt",
"description": prompt_description,
"input_schema": {
"type": "object",
"properties": {
"prompt_name": {
"type": "string",
"description": "Name of the prompt to use. Available: 'generate_issues_prompt'"
},
"prompt_args": {
"type": "object",
"description": "Arguments for the prompt. For generate_issues_prompt: {'owner': 'repo-owner', 'repo_name': 'repo-name'}",
"properties": {
"owner": {
"type": "string",
"description": "Repository owner (e.g., 'huggingface', 'microsoft')"
},
"repo_name": {
"type": "string",
"description": "Repository name (e.g., 'transformers', 'vscode')"
}
}
}
},
"required": ["prompt_name"]
}
})
# Create initial message for Claude
messages = [
{
"role": "user",
"content": query
}
]
# First call to Claude
response = self.anthropic.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=6000,
messages=messages,
tools=claude_tools if claude_tools else None
)
# Process Claude's response
response_text = ""
for content_block in response.content:
if content_block.type == "text":
response_text += content_block.text
elif content_block.type == "tool_use":
# Claude wants to use a tool
tool_name = content_block.name
tool_args = content_block.input
tool_call_id = content_block.id
print(f"🔧 Claude wants to use: {tool_name}")
print(f"📝 Arguments: {tool_args}")
try:
if tool_name == "read_mcp_resource":
# Handle resource reading
resource_uri = tool_args.get("resource_uri")
if resource_uri:
tool_result = await client.read_resource(resource_uri)
print(f"📖 Resource read successfully: {resource_uri}")
# Better handling of resource result
if hasattr(tool_result, 'content'):
# If it's a resource response object, extract content
if hasattr(tool_result.content, 'text'):
result_content = tool_result.content.text
else:
result_content = str(tool_result.content)
else:
# If it's already a string or simple object
result_content = str(tool_result)
else:
tool_result = "Error: No resource URI provided"
result_content = tool_result
elif tool_name == "use_mcp_prompt":
# Handle prompt usage
prompt_name = tool_args.get("prompt_name")
prompt_args = tool_args.get("prompt_args", {})
if prompt_name:
tool_result = await self.get_prompt(prompt_name, prompt_args)
print(f"💭 Prompt '{prompt_name}' generated successfully")
result_content = str(tool_result) if tool_result else "Error generating prompt"
else:
tool_result = "Error: No prompt name provided"
result_content = tool_result
else:
# Execute regular tool on the FastMCP server
tool_result = await client.call_tool(tool_name, tool_args)
print(f"✅ Tool executed successfully")
result_content = str(tool_result)
# Add tool result to the conversation
messages.append({
"role": "assistant",
"content": response.content
})
# Format result for Claude
if tool_result:
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_call_id,
"content": f"Tool result: {result_content}"
}]
})
else:
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_call_id,
"content": "Tool executed without response content"
}]
})
# Second call to Claude with the tool result
final_response = self.anthropic.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=6000,
messages=messages,
tools=claude_tools if claude_tools else None
)
# Extract text from the final response
for final_content in final_response.content:
if final_content.type == "text":
response_text += final_content.text
except Exception as e:
error_msg = f"❌ Error executing {tool_name}: {str(e)}"
print(error_msg)
response_text += f" {error_msg}"
return response_text
except Exception as e:
error_msg = f"❌ Error processing query: {str(e)}"
print(error_msg)
return error_msg
async def chat_loop(self):
"""
Main chat loop with user interaction.
"""
print(" 🤖 FastMCP HTTP client started. Write 'quit', 'q', 'exit', 'salir' to exit.")
print("💬 You can ask questions about GitHub repositories!")
print("📚 The client can use tools, resources, and prompts from the FastMCP server")
print()
print("💭 PROMPT Examples:")
print(" • 'Generate a prompt for asking about issues in facebook/react'")
print(" • 'Help me create a good question about microsoft/vscode issues'")
print(" • 'I need a structured prompt for analyzing tensorflow/tensorflow'")
print()
print("🔧 DIRECT Examples:")
print(" • 'Show me the issues in huggingface/transformers'")
print(" • 'Get repository info for github://repo/google/chrome'")
print("-" * 60)
while True:
try:
# Request user input
user_input = input(" 👤 You: ").strip()
if user_input.lower() in ['quit', 'q', 'exit', 'salir']:
print("👋 Bye!")
break
if not user_input:
continue
print(" 🤔 Claude is thinking...")
# Process query
response = await self.process_query(user_input)
# Show response
print(f" 🤖 Claude: {response}")
except KeyboardInterrupt:
print(" 👋 Disconnecting...")
break
except Exception as e:
print(f" ❌ Error in chat: {str(e)}")
continue
async def cleanup(self):
"""Clean up resources and close connections."""
print("🧹 Cleaning up resources...")
# FastMCP Client cleanup is handled automatically by context manager
await self.exit_stack.aclose()
print("✅ Resources released")
async def main():
"""
Main function that initializes and runs the FastMCP client.
"""
# Verify command line arguments
if len(sys.argv) &lt; 2 or len(sys.argv) &gt; 3:
print("❌ Usage: python client.py &lt;http_server_url&gt; [auth_token]")
print("📝 Example: python client.py http://localhost:8000/mcp")
print("📝 Example with auth: python client.py http://localhost:8000/mcp &lt;your_bearer_token&gt;")
print("📝 Note: Now connects to HTTP server instead of executing script")
sys.exit(1)
server_url = sys.argv[1]
auth_token = sys.argv[2] if len(sys.argv) == 3 else None
# Validate URL format
if not server_url.startswith(('http://', 'https://')):
print("❌ Error: Server URL must start with http:// or https://")
print("📝 Example: python client.py http://localhost:8000")
sys.exit(1)
# Create and run client
client = FastMCPClient()
try:
# Connect to the server
await client.connect_to_server(server_url, auth_token)
# List available tools, resources, and prompts after connection
await client.list_available_tools()
await client.list_available_resources()
# Start chat loop
await client.chat_loop()
except Exception as e:
print(f"❌ Fatal error: {str(e)}")
finally:
# Ensure resources are cleaned up
await client.cleanup()
if __name__ == "__main__":
# Entry point of the script
asyncio.run(main())
Copied
>_ Output
			
Overwriting client_MCP/client.py

Adicionamos no método connect_to_server o ping

# Ping para o servidor para verificar se está ativo
async with self.client as client:
response = await client.ping()
print(f"🏓 Resposta do ping do servidor: {response}")

Teste de pinglink image 32

Iniciamos primeiro o servidor

	
< > Input
Python
!cd gitHub_MCP_server && source .venv/bin/activate && uv run github_server.py
Copied
>_ Output
			
🔐 Generating RSA key pair for authentication...
🎫 Development token generated:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2dpdGh1Yi1tY3AubWF4Zm4uZGV2Iiwic3ViIjoiZGV2LXVzZXItbWF4Zm4iLCJpYXQiOjE3NTExMDkxMTIsImV4cCI6MTc1MTE5NTUxMiwiYXVkIjoiZ2l0aHViLW1jcC1zZXJ2ZXIiLCJzY29wZSI6ImdpdGh1YjpyZWFkIGdpdGh1Yjp3cml0ZSJ9.N_3QPIHW3BSn1iSSkrcaoelbwA-0D9Z3gelILb8fu1JC2JhCgtnJ0IwNqJrVhAkU0CNcykT36Q3mpCgy0hDhnFKkO9SRGVFgSw71voF5YNOkzzBY14cJERolYy9UDZA6geHxwR0rKyCGYkDH-NAKPuYWC9K7UlGfuOuzh3mp-XQ3Zy4mkyvfhiuwuaJ5_MdR0YtJj6opSRbEsVs1PtFYZETPExx3iBGck2qzLek-LxAJ6mjagPncikWeDwaYShFNPO0Ub3wm2Ok_ak_TChmN3W15MknfBXZrKcIhsNIhCrXJjZkSezp5JX49zoljdK2By9-QH1xmWCQqif_APD-hNQ
💡 Use this token in the client to authenticate
------------------------------------------------------------
/Users/macm1/Documents/web/portafolio/posts/gitHub_MCP_server/github_server.py:412: DeprecationWarning: Mount prefixes are now optional and the first positional argument should be the server you want to mount.
mcp.mount("sub_mcp", sub_mcp)
DEBUG: Starting FastMCP GitHub server...
DEBUG: Server name: GitHubMCP
[06/28/25 13:11:52] INFO Starting MCP server 'GitHubMCP' with ]8;id=186381;file:///Users/macm1/Documents/web/portafolio/posts/gitHub_MCP_server/.venv/lib/python3.11/site-packages/fastmcp/server/server.py\server.py]8;;\:]8;id=502881;file:///Users/macm1/Documents/web/portafolio/posts/gitHub_MCP_server/.venv/lib/python3.11/site-packages/fastmcp/server/server.py#1297\1297]8;;\
transport 'streamable-http' on
http://0.0.0.0:8000/mcp/
INFO: Started server process [31017]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

E agora executamos o cliente com o token de autenticação

	
< > Input
Python
!cd client_MCP && source .venv/bin/activate && uv run client.py http://localhost:8000/mcp eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2dpdGh1Yi1tY3AubWF4Zm4uZGV2Iiwic3ViIjoiZGV2LXVzZXItbWF4Zm4iLCJpYXQiOjE3NTExMDkxMTIsImV4cCI6MTc1MTE5NTUxMiwiYXVkIjoiZ2l0aHViLW1jcC1zZXJ2ZXIiLCJzY29wZSI6ImdpdGh1YjpyZWFkIGdpdGh1Yjp3cml0ZSJ9.N_3QPIHW3BSn1iSSkrcaoelbwA-0D9Z3gelILb8fu1JC2JhCgtnJ0IwNqJrVhAkU0CNcykT36Q3mpCgy0hDhnFKkO9SRGVFgSw71voF5YNOkzzBY14cJERolYy9UDZA6geHxwR0rKyCGYkDH-NAKPuYWC9K7UlGfuOuzh3mp-XQ3Zy4mkyvfhiuwuaJ5_MdR0YtJj6opSRbEsVs1PtFYZETPExx3iBGck2qzLek-LxAJ6mjagPncikWeDwaYShFNPO0Ub3wm2Ok_ak_TChmN3W15MknfBXZrKcIhsNIhCrXJjZkSezp5JX49zoljdK2By9-QH1xmWCQqif_APD-hNQ
Copied
>_ Output
			
🔗 Connecting to FastMCP HTTP server: http://localhost:8000/mcp
🔐 Using Bearer token authentication
✅ Client created successfully
🏓 Server ping response: True
🛠️ Available tools (2):
==================================================
📋 sub_mcp_hello_world
Description: Returns a simple greeting.
Parameters:
📋 list_repository_issues
Description: Lists open issues for a given GitHub repository.
Args:
owner: The owner of the repository (e.g., 'modelcontextprotocol')
repo_name: The name of the repository (e.g., 'python-sdk')
ctx: The context of the request
user_id: The user ID (automatically injected by the server)
Returns:
list[dict]: A list of dictionaries, each containing information about an issue
Parameters: owner, repo_name
📚 Available resources (1):
==================================================
📄 resource://server_info
Name: server_info
Description: Returns information about the server.
MIME Type: text/plain
🤖 FastMCP HTTP client started. Write 'quit', 'q', 'exit', 'salir' to exit.
💬 You can ask questions about GitHub repositories!
📚 The client can use tools, resources, and prompts from the FastMCP server
💭 PROMPT Examples:
• 'Generate a prompt for asking about issues in facebook/react'
• 'Help me create a good question about microsoft/vscode issues'
• 'I need a structured prompt for analyzing tensorflow/tensorflow'
🔧 DIRECT Examples:
• 'Show me the issues in huggingface/transformers'
• 'Get repository info for github://repo/google/chrome'
------------------------------------------------------------
👤 You: q
👋 Bye!
🧹 Cleaning up resources...
✅ Resources released

Como vemos, o servidor respondeu ao ping

🏓 Resposta do ping do servidor: True

---

🎉 **Você completou o guia _MCP com FastMCP_.** Revise os capítulos: Parte 1 · Parte 2 · Parte 3.

Continuar lendo

Últimos posts -->

Você viu esses projetos?

Gymnasia

Gymnasia Gymnasia
React Native
Expo
TypeScript
FastAPI
Next.js
OpenAI
Anthropic

Aplicativo móvel de treino pessoal com assistente de IA, biblioteca de exercícios, acompanhamento de rotinas, dieta e medidas corporais

Horeca chatbot

Horeca chatbot Horeca chatbot
Python
LangChain
PostgreSQL
PGVector
React
Kubernetes
Docker
GitHub Actions

Chatbot conversacional para cozinheiros de hotéis e restaurantes. Um cozinheiro, gerente de cozinha ou serviço de quarto de um hotel ou restaurante pode falar com o chatbot para obter informações sobre receitas e menus. Mas também implementa agentes, com os quais pode editar ou criar novas receitas ou menus

Naviground

Naviground Naviground
Ver todos os projetos -->
>_ Disponível para projetos

Tem um projeto com IA?

Vamos conversar.

maximofn@gmail.com

Especialista em Machine Learning e Inteligência Artificial. Desenvolvo soluções com IA generativa, agentes inteligentes e modelos personalizados.

Quer assistir alguma palestra?

Últimas palestras -->

Quer melhorar com essas dicas?

Últimos tips -->

Use isso localmente

Os espaços do Hugging Face nos permitem executar modelos com demos muito simples, mas e se a demo quebrar? Ou se o usuário a deletar? Por isso, criei contêineres docker com alguns espaços interessantes, para poder usá-los localmente, aconteça o que acontecer. Na verdade, se você clicar em qualquer botão de visualização de projeto, ele pode levá-lo a um espaço que não funciona.

Flow edit

Flow edit Flow edit

Edite imagens com este modelo de Flow. Baseado em SD3 ou FLUX, você pode editar qualquer imagem e gerar novas

FLUX.1-RealismLora

FLUX.1-RealismLora FLUX.1-RealismLora
Ver todos os contêineres -->
>_ Disponível para projetos

Tem um projeto com IA?

Vamos conversar.

maximofn@gmail.com

Especialista em Machine Learning e Inteligência Artificial. Desenvolvo soluções com IA generativa, agentes inteligentes e modelos personalizados.

Você quer treinar seu modelo com esses datasets?

short-jokes-dataset

HuggingFace

Dataset com piadas em inglês

Uso: Fine-tuning de modelos de geração de texto humorístico

231K linhas 2 colunas 45 MB
Ver no HuggingFace →

opus100

HuggingFace

Dataset com traduções de inglês para espanhol

Uso: Treinamento de modelos de tradução inglês-espanhol

1M linhas 2 colunas 210 MB
Ver no HuggingFace →

netflix_titles

HuggingFace

Dataset com filmes e séries da Netflix

Uso: Análise de catálogo Netflix e sistemas de recomendação

8.8K linhas 12 colunas 3.5 MB
Ver no HuggingFace →
Ver mais datasets -->