As part of our ongoing series exploring A2A (Agent-to-Agent) and MCP (Model Context Protocol), most of our earlier work focused on understanding the fundamentals and recreating concepts already explored by the community.
We began with What is MCP? and followed it up with Integrating MCP Servers. Our attention then shifted to A2A with posts like What is A2A Protocol?, A2A Protocol Implementation, and A2A and MCP Combined Implementation.
Until now, these experiments mostly revolved around replicating existing ideas.
In this post, we’re changing things up.
HeetVekariya
/
devto-agent
Build and deploy an autonomous Devto Agent capable of interacting with the Dev.to platform, powered by A2A (Agent-to-Agent) and MCP (Model Context Protocol)
DevTo Agent
A comprehensive multi-agent system for interacting with Dev.to (DevTo) platform, built using Google ADK (Agent Development Kit) and Model Context Protocol (MCP). This project enables automated content creation, article management, and user profile interactions with DevTo through both Agent-to-Agent (A2A) communication and MCP server implementations.
You can find follow along blog here
Project Overview
This project implements a sophisticated agent architecture that can:
- Fetch and manage DevTo articles by tags or authors
- Generate and post markdown content to DevTo
- Retrieve user profiles and reading lists
- Manage article comments and followers
- Provide both SSE (Server-Sent Events) and STDIO interfaces for different integration needs
Table of Contents
Architecture Overview
The project follows a modular architecture with three main communication patterns:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Client App │───▶│ A2A Server │───▶│ DevTo API │
│ (main.py) │ │ (devto_agent) │
…This time, we’re implementing something original, a real-world agent that interacts with the Dev.to platform, where I publish my blogs :).
I have developed an A2A Registry for the AI community, do check it out and share in your network.
What Are We Building?
We are building a system where we will have a single agent, Devto Agent
, supported by a Devto MCP Server
. This setup will allow our agent to access Dev.to data such as:
- Blogs
- Articles
- Comments
- User information
- And even publish new blogs
You might ask, How A2A will be implemented with single agent ?
Good question.
Even though it seems like a single agent is involved, there are actually two:
- Host Agent – Handles incoming user requests and delegates tasks
-
Devto Agent – Specifically addresses Dev.to-related queries using the capabilities provided by the
Devto MCP Server
In the architecture diagram above, we can clearly see the flow:
- A CLI (the A2A client) accepts the user query and sends it to the Host Agent (A2A server)
- The Host Agent then delegates the task to the Devto Agent
- The Devto Agent, powered by the Devto MCP Server, processes the request and interacts with the Dev.to platform
With a good understanding of the architecture, let's move on to building it.
Implementation
We will be building this project from scratch. The first and most essential component is the MCP server, which enables third-party service integration and actionable capabilities for our agent.
Note: We will test the code at various stages throughout development. To test the code, you’ll need a Dev.to API key. The configuration steps for setting this up are provided in the Prerequisite section later in the blog. If you want to follow along and test as you go, head over to that section first and set up your Dev.to API key. Alternatively, you can wait until the end and test the
main.py
after installation.
MCP Server
The MCP server connects third-party services to our agent and enables interaction with external data sources. In our case, the third-party service is Dev.to, and we’ll be using it to:
- Access blogs
- Retrieve user data
- Publish new content
Define Devto Service Class
Rather than hard-coding the logic directly into the MCP tool wrapper, we’ll create a separate DevtoService
class. This class will encapsulate all the logic related to Dev.to operations, while the MCP tool will simply wrap and expose these service methods.
Let’s begin by defining the DevToService
class, which will act as the core integration layer between our agent and the Dev.to API. This service class encapsulates the logic needed to communicate with the platform.
class DevToService:
def __init__(self):
self.api_key = os.getenv('DEVTO_API_KEY')
self.base_url = os.getenv('DEVTO_BASE_URL')
self.headers = {
"api-key": self.api_key,
"Content-Type": "application/json"
}
def send_request(self, method: str, endpoint: str, params=None, data=None):
url = f"{self.base_url}/{endpoint}"
headers = self.headers
if method.lower() == 'get':
response = requests.get(url, headers=headers, params=params)
elif method.lower() == 'post':
response = requests.post(url, headers=headers, json=data)
else:
raise ValueError("Unsupported HTTP method")
if response.status_code == 200:
return response.json()
else:
response.raise_for_status()
# Article-related methods
def get_articles(self, page: int = 1, per_page: int = 30):
endpoint = "articles"
params = {
'page': page,
'per_page': per_page
}
return self.send_request('get', endpoint, params=params)
def get_articles_by_tag(self, tag: str):
endpoint = "articles"
params = {'tag': tag}
return self.send_request('get', endpoint, params=params)
...
The constructor initializes the API key and base URL from environment variables and sets the required headers for authentication. A general-purpose method send_request
is defined to handle both GET and POST requests.
Then, we define specific methods like get_articles
and get_articles_by_tag
which internally call send_request
with appropriate parameters. This design keeps the logic clean and modular, making it easy to expand in the future.
Registering the Service with MCP
Next, we integrate our DevToService
with the MCP framework. We instantiate a FastMCP
server and register tools using the @mcp.tool()
decorator.
These tools are simple wrappers around our service methods and expose the capability to external agents. For example:
mcp = FastMCP("DevTo MCP Server")
devto_service = DevToService()
@mcp.tool()
def get_articles(page: int = 1, per_page: int = 30) -> list:
"""
Fetch articles from DevTo.
Args:
page (int): The page number to fetch.
per_page (int): The number of articles per page.
Returns:
list: A list of articles from DevTo.
"""
return devto_service.get_articles(page=page, per_page=per_page)
@mcp.tool()
def get_articles_by_tag(tag: str) -> list:
"""
Fetch articles by a specific tag from DevTo.
Args:
tag (str): The tag to filter articles by.
Returns:
list: A list of articles with the specified tag.
"""
return devto_service.get_articles_by_tag(tag=tag)
-
get_articles
allows querying paginated articles from Dev.to -
get_articles_by_tag
filters articles based on specific tags
Using SSE-Based Communication
To support a networked agent environment and handle multiple clients effectively, we implement Server-Sent Events (SSE) using the Starlette web framework.
A new function sets up a Starlette application with the following:
def create_starlette_app(
mcp_server: Server = mcp,
*,
debug: bool = False,
) -> Starlette:
"""
Create a Starlette application with the MCP server.
Args:
mcp (Server): The MCP server instance.
debug (bool): Whether to run the application in debug mode.
Returns:
Starlette: The Starlette application instance.
"""
sse = SseServerTransport('/messages/')
async def handle_sse(request: Request):
async with sse.connect_sse(
request.scope,
request.receive,
request._send # noqa: SLF001
) as (read_stream, write_stream):
await mcp_server.run(
read_stream,
write_stream,
mcp_server.create_initialization_options()
)
return Response(status_code=204)
return Starlette(
debug=debug,
routes=[
Route("/sse", endpoint=handle_sse),
Mount("/messages/", app=sse.handle_post_message)
]
)
- An SSE route (
/sse
) that manages incoming stream connections from clients - A message-handling mount point (
/messages/
) to handle incoming data
This architecture allows asynchronous, scalable, real-time communication between the A2A host agent and the Devto MCP server.
Testing the MCP Server
With the MCP server implemented, we now test it using the MCP Inspector tool provided by the MCP framework.
To do this:
- Run the Devto MCP server using
mcp dev mcp_servers/sse/devto_server.py
- Open the MCP Inspector interface.
- Connect to the server.
- Navigate to the Tools tab and list available tools.
- Select any tool (e.g.,
get_articles
) and execute it.
If the tool runs and returns a response successfully, our MCP server is functioning correctly and ready for integration with an A2A host agent.
Example output from a successful test is shown below:
Connector
To allow our agent to access the tools provided by the Devto MCP server, we first define a connector function. This function is responsible for:
- Establishing a connection with the running MCP server
- Retrieving the exposed tools
- Returning those tools in a format the agent can use
async def get_devto_tools() -> tuple:
"""
Retrieves the DevTo tools for agent.
Returns:
tuple: A tuple containing the DevTo toolset and exit stack.
"""
print(colored("Attempting to connect with DevTo MCP server for blogs info...", "yellow"))
server_parameters = SseServerParams(
url="http://localhost:8000/sse/",
)
tools, exit_stack = await MCPToolset.from_server(connection_params=server_parameters)
print(colored("MCP Toolset created successfully.", "green"))
return tools, exit_stack
The function uses an SSE (Server-Sent Events) connection, assuming the MCP server is running at http://localhost:8000/sse/
. On successful connection, it prints confirmation messages and returns both the toolset and a resource cleanup stack (exit_stack
) for proper session handling.
Verifying the Connector with a Test Agent
Before jumping into building the full Devto agent, it's a good idea to validate our connector functionality with a simple test agent. For that purpose, we define a test script located at: test/test_devto_connection.py
async def get_tools() -> list:
"""
Retrieves tools from Devto MCP server.
Returns:
list: A list of tools retrieved from the Devto MCP server.
"""
tools, exit_stack = await get_devto_tools()
return tools, exit_stack
async def get_agent():
"""
Create an agent with Devto tools.
"""
tools, exit_stack = await get_tools()
print(f"Retrieved {len(tools)} tools from Devto MCP server.")
agent = LlmAgent(
model='gemini-2.0-flash', # Replace with your desired model
name='DevtoAgent',
description='An agent to interact with Devto articles and blogs.',
instruction="You are an agent that can fetch articles, user information, and perform actions related to Devto blogs. Use the tools provided to answer user queries.",
tools=tools,
)
return agent, exit_stack
async def main():
session_service = InMemorySessionService()
artifacts_service = InMemoryArtifactService()
print("Creating session...")
session = session_service.create_session(
state={},
app_name='Devto_Agent_App',
user_id='devto_user',
session_id='devto_session',
)
query = "Fetch the latest articles from Devto."
print(f"\nQuery: {query}")
content = types.Content(role='user', parts=[types.Part(text=query)])
agent, exit_stack = await get_agent()
print(colored("Agent created successfully.", "green"))
runner = Runner(
app_name='Devto_Agent_App',
session_service=session_service,
artifact_service=artifacts_service,
agent=agent,
)
event_async = runner.run_async(session_id=session.id, user_id=session.user_id, new_message=content)
print("********************* Agent Response *********************")
async for event in event_async:
if event.is_final_response():
print(event.content.parts[0].text)
await exit_stack.aclose()
if __name__ == "__main__":
dotenv.load_dotenv()
asyncio.run(main())
This script does the following:
- Calls the connector function to get the Devto toolset.
- Creates an
LlmAgent
instance and assigns it the retrieved tools. - Sets up a simple query: "Fetch the latest articles from Devto."
- Uses an in-memory session and artifact service to simulate the run.
- Prints the agent’s final response to the console.
This test acts as a minimal end-to-end validation of our Devto MCP server integration. It helps us ensure that:
- The MCP server is correctly exposing tools
- The tools are correctly loaded by the agent
- The agent can generate meaningful responses using those tools
To perform the test:
- In one terminal, start the Devto MCP server:
uv run mcp_servers/sse/devto_server.py
- In a second terminal, run the test agent script:
uv run test/test_devto_connection.py
If everything is set up correctly, you should see log messages confirming tool retrieval and agent creation, followed by the actual response from the agent, based on the query.
This confirms that your Devto agent is correctly wired to the MCP server and ready to respond to real user queries.
Creating session...
Query: Fetch the latest articles from Devto.
Attempting to connect with DevTo MCP server for blogs info...
MCP Toolset created successfully.
Retrieved 11 tools from Devto MCP server.
Agent created successfully.
********************* Agent Response *********************
Warning: there are non-text parts in the response: ['function_call'], returning concatenated text result from text parts. Check the full candidates.content.parts accessor to get the full model response.
Here are the latest articles from Devto.
...
MCP Server logs...
INFO: Started server process [5888]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://localhost:8000 (Press CTRL+C to quit)
INFO: ::1:53003 - "GET /sse/ HTTP/1.1" 307 Temporary Redirect
INFO: ::1:53003 - "GET /sse HTTP/1.1" 200 OK
INFO: ::1:53004 - "POST /messages/?session_id=29d33a5d6a6041f2afb8475cca54b136 HTTP/1.1" 202 Accepted
INFO: ::1:53004 - "POST /messages/?session_id=29d33a5d6a6041f2afb8475cca54b136 HTTP/1.1" 202 Accepted
INFO: ::1:53004 - "POST /messages/?session_id=29d33a5d6a6041f2afb8475cca54b136 HTTP/1.1" 202 Accepted
INFO Processing request of type ListToolsRequest server.py:551
INFO: ::1:53004 - "POST /messages/?session_id=29d33a5d6a6041f2afb8475cca54b136 HTTP/1.1" 202 Accepted
INFO Processing request of type CallToolRequest
Agents
Now that we have our Devto MCP server running and successfully connected to the agent through the connector, it's time to build the Devto agent using the A2A (Agent-to-Agent) protocol.
Directory Structure Overview
Here's a quick look at the structure inside the a2a_servers
directory:
a2a_servers/ # Agent-to-Agent server implementations
│ ├── agent_server/ # Individual agent definitions
│ │ ├── devto_agent.py # Main DevTo agent server
│ │ ├── host_agent.py # Host agent coordinator
│ │ └── utils.py # Agent utilities
│ ├── agents/ # Agent base classes and implementations
│ │ ├── adk_agent.py # Google ADK agent wrapper
│ │ └── utils/ # Agent utilities
│ └── common/ # Shared components
│ ├── client/ # A2A client implementations
│ ├── server/ # A2A server implementations
│ └── types.py # Common type definitions
Note: The
agents
andcommon
directories are derived directly from the official Google A2A reference repository. These provide the standard A2A protocol implementation, and we will not modify them. Instead, all custom logic for the Devto agent lives within theagent_server
directory.
Core A2A Concepts
Before diving into the implementation, it's important to understand a few foundational components of the A2A protocol:
Agent Card: A metadata document located at
/.well-known/agent.json
. It describes the agent's identity, capabilities, supported tools, and how to communicate with it. Other agents use this card to understand how to initiate interaction.A2A Server: An HTTP server that hosts the agent. It accepts incoming task requests, maintains conversations, and returns results via standard A2A protocols.
A2A Client: Any application or external agent that communicates with the A2A server. Clients initiate tasks or conversations using defined endpoints (
tasks/send
,tasks/sendSubscribe
, etc.).Task: The fundamental unit of execution. Each task has a lifecycle (
submitted
,working
,input-required
,completed
,failed
, etc.) and manages all context for an interaction.Message: Each communication in the task thread consists of a message from either the "user" or "agent", built from one or more parts.
-
Part: The smallest unit of content within a message. Types include:
-
TextPart
: Simple text -
FilePart
: Files (inline or via URL) -
DataPart
: Structured JSON
-
Artifact: Any output generated by the agent could be text, structured data, or files. These are also made up of parts.
Streaming: A2A supports real-time task updates using
tasks/sendSubscribe
, allowing clients to receive server-sent events (SSE) as the task progresses.Push Notifications: Optionally, agents can send task updates to a client-defined webhook. This enables proactive status updates without polling.
In our code we use,
1) AgentCard
class: To prepare agent.json
information
class AgentCard(BaseModel):
name: str
description: str | None = None
url: str
provider: AgentProvider | None = None
version: str
documentationUrl: str | None = None
capabilities: AgentCapabilities
authentication: AgentAuthentication | None = None
defaultInputModes: List[str] = ["text"]
defaultOutputModes: List[str] = ["text"]
skills: List[AgentSkill]
2) ADKAgent
class: A blue print class to define agent with different configuration.
class ADKAgent:
"""
An agent that can delegate tasks to remote agents using the ADK framework.
This agent can list remote agents, send tasks to them, and handle task updates.
It is designed to orchestrate the decomposition of user requests into tasks that can be performed by child agents.
"""
SUPPORTED_CONTENT_TYPES = ["text", "text/plain"]
def __init__(
self,
model: str,
name: str,
description: str,
instructions: str,
tools: List[Any],
is_host_agent: bool = False,
remote_agent_addresses: List[str] = None,
task_callback: TaskUpdateCallback | None = None
):
"""
Initializes the ADKAgent with the given parameters.
"""
3) A2AServer
class: To deploy an agent on a port, which makes it discoverable to other agents.
class A2AServer:
def __init__(
self,
host="localhost",
port=5000,
endpoint="/",
agent_card: AgentCard = None,
task_manager: TaskManager = None,
):
self.host = host
self.port = port
self.endpoint = endpoint
self.task_manager = task_manager
self.agent_card = agent_card
self.app = Starlette()
self.app.add_route(self.endpoint, self._process_request, methods=["POST"])
self.app.add_route(
"/.well-known/agent.json", self._get_agent_card, methods=["GET"]
)
4) A2AClient
class: To handle user queries and sends it to A2AServer i.e. host agent.
class A2AClient:
def __init__(self, agent_card: AgentCard = None, url: str = None):
if agent_card:
self.url = agent_card.url
elif url:
self.url = url
else:
raise ValueError("Must provide either agent_card or url")
Now that we understand the A2A components, let's look at how our Devto Agent and Host Agent are configured using the AgentCard
, ADKAgent
, and A2AServer
classes.
Devto Agent
The Devto Agent is a child agent capable of:
- Fetching articles from Devto
- Generating markdown blog content
- Posting articles
- Retrieving Devto user profile information
It expose its capabilities on a local port (11000
) through /devto-agent
endpoint
async def run_agent():
AGENT_NAME = "Devto_Agent"
AGENT_DESCRIPTION = "An agent to interact with Devto articles and blogs."
PORT = 11000
HOST = "localhost"
AGENT_URL = f"http://{HOST}:{PORT}/"
AGENT_VERSION = "0.1.0"
MODEL = "gemini-2.0-flash" # Replace with your desired model
AGENT_SKILLS = [
AgentSkill(
id="SKILL_DEVTO_CONTENT",
name="DevTo Markdown Content",
description="Generate markdown content for DevTo articles.",
),
AgentSkill(
id="SKILL_DEVTO_ARTICLES",
name="DevTo Articles",
description="Fetch articles from DevTo with or without tags.",
),
AgentSkill(
id="SKILL_DEVTO_USER_INFO",
name="DevTo User Info",
description="Fetch user information from DevTo.",
),
AgentSkill(
id="SKILL_POST_DEVTO_ARTICLE",
name="Post DevTo Article",
description="Create and post article on Devto.",
)
]
AGENT_CARD = generate_agent_card(
agent_name=AGENT_NAME,
agent_description=AGENT_DESCRIPTION,
agent_url=AGENT_URL,
agent_version=AGENT_VERSION,
can_stream=False,
can_push_notifications=False,
can_state_transition_history=True,
default_input_modes=["text"],
default_output_modes=["text"],
skills=AGENT_SKILLS,
)
devto_tools, devto_exit_stack = await get_devto_tools()
devto_blogs_agent = ADKAgent(
model=MODEL,
name=AGENT_NAME,
description=AGENT_DESCRIPTION,
tools=devto_tools,
instructions=(
"You can retrieve information about DevTo articles, user profiles, and even post articles. "
"If user asks for markdown content, generate it for specified topic and add relevant tags. "
"If user asks for articles, you can fetch them by tags or without tags. "
"If user asks for user profile, you can fetch it by username. "
"If user asks to post an article, you need to first ask for the topic, then draft markdown content along with images and text. Add relevant tags and post it on DevTo. "
"Always respond with the result of your action, and if you are unable to perform an action, explain why. "
"If you need to ask for more information, do so in a clear and concise manner. "
"If you are unable to perform an action, explain why. "
"Say 'I'm sorry, I cannot perform that action.' if you are unable to perform an action. "
)
)
task_manager = generate_agent_task_manager(devto_blogs_agent)
server = A2AServer(
host=HOST,
port=PORT,
endpoint="/devto-agent",
agent_card=AGENT_CARD,
task_manager=task_manager,
)
print(colored(f"Starting {AGENT_NAME} A2A Server on {AGENT_URL}", "yellow"))
await server.astart()
if __name__ == "__main__":
dotenv.load_dotenv()
asyncio.run(run_agent())
Host Agent
The Host Agent acts as an orchestrator. It doesn’t implement logic itself, but routes the user's task to appropriate child agents based on the request.
- Runs on port
10000
and listens at/host_agent
- Registers the Devto Agent as a remote agent
- Uses
ADKAgent
withis_host_agent=True
to enable orchestration - Executes decomposition of complex user queries into actionable sub-tasks
async def run_agent():
AGENT_NAME = "Host_Agent"
AGENT_DESCRIPTION = "An agent orchestrates the decomposition of the user request into tasks that can be performed by the child agents."
PORT = 10000
HOST = "localhost"
AGENT_URL = f"http://{HOST}:{PORT}/"
AGENT_VERSION = "0.1.0"
MODEL = "gemini-2.0-flash" # Replace with your desired model
AGENT_SKILLS = [
AgentSkill(
id="COORDINATE_AGENT_TASKS",
name="coordinate_tasks",
description="coordinate tasks between agents.",
)
]
AGENT_CARD = generate_agent_card(
agent_name=AGENT_NAME,
agent_description=AGENT_DESCRIPTION,
agent_url=AGENT_URL,
agent_version=AGENT_VERSION,
can_stream=False,
can_push_notifications=False,
can_state_transition_history=True,
default_input_modes=["text"],
default_output_modes=["text"],
skills=AGENT_SKILLS,
)
remote_agent_urls = [
'http://localhost:11000/devto-agent', # Devto Agent
]
host_agent = ADKAgent(
model=MODEL,
name=AGENT_NAME,
description=AGENT_DESCRIPTION,
tools=[],
instructions=(
"You are a host agent that orchestrates the decomposition of the user request into tasks "
"that can be performed by the child agents. You will coordinate tasks between agents."
),
is_host_agent=True,
remote_agent_addresses=remote_agent_urls,
)
task_manager = generate_agent_task_manager(host_agent)
server = A2AServer(
host=HOST,
port=PORT,
endpoint="/host_agent",
agent_card=AGENT_CARD,
task_manager=task_manager,
)
print(colored(f"Starting {AGENT_NAME} A2A Server on {AGENT_URL}"), "yellow")
await server.astart()
if __name__ == "__main__":
dotenv.load_dotenv()
asyncio.run(run_agent())
A2A Client
Finally, the A2AClient
provides a simple CLI-based interface to communicate with the Host Agent.
SERVER_URL = "http://localhost:10000/host_agent"
async def main():
client = A2AClient(url=SERVER_URL)
task_id = f"echo-task-{uuid4().hex}"
session_id = f"session-{uuid4().hex}"
while True:
print("*" * 50)
user_text = input("Enter your query: ") # Example user input
user_message = Message(
role="user",
parts=[TextPart(text=user_text)]
)
send_params = {
"id": task_id,
"sessionId": session_id,
"message": user_message.model_dump(),
}
try:
logger.info(f"Sending task {task_id} to {SERVER_URL}...")
response = await client.send_task(payload=send_params)
if response.error:
logger.error(f"Task {task_id} failed: {response.error.message} (Code: {response.error.code})")
elif response.result:
task_result = response.result
logger.info(f"Task {task_id} completed with state: {task_result.status.state}")
if task_result.status.message and task_result.status.message.parts:
agent_part = task_result.status.message.parts[0]
print("\n")
print("------------- Agent Response -------------")
print("\n")
print(agent_part.text)
else:
logger.warning("No message part in agent response status")
else:
logger.error(f"Received unexpected response for task {task_id}: {response}")
except Exception as e:
logger.error(traceback.format_exc())
logger.error(f"An error occurred while communicating with the agent: {e}")
if __name__ == "__main__":
asyncio.run(main())
Each interaction:
- Creates a unique
task_id
andsession_id
- Wraps the user query in a
Message
usingTextPart
- Sends it to the Host Agent
- Waits for the response and prints the result
If the response contains any message from the agent, it is displayed in the terminal. Otherwise, appropriate errors or warnings are logged.
Final Setup Summary
With the MCP server running, and both agents configured and discoverable via the A2A protocol, we are now ready to:
- Start the Devto Agent server
- Start the Host Agent server
- Run the A2A Client to begin interacting with our setup
Installation
Prerequisites
Make sure you have the following installed or configured:
- Install UV
- Google API Key
- Devto API Key (At the bottom of the page)
The project is currently configured to use the
gemini-2.0-flash
model. You can replace this with another supported model by updating theMODEL
variable.
To get started with the implementation, follow these steps:
1. Clone the Git Repository
git clone https://github.com/HeetVekariya/devto-agent.git
2. Create a .env
File
In the root folder of the project, create a .env
file and add the following environment variables:
DEVTO_BASE_URL=https://dev.to/api
DEVTO_API_KEY=<your_api_key>
DEVTO_USERNAME=<your_devto_username>
GOOGLE_API_KEY=<your_api_key>
3. Install Dependencies
uv pip install -e .
4. Start the Devto MCP Server
Run the Devto MCP server using the following command:
uv run mcp_servers/sse/devto_server.py
5. Start the Devto Agent
uv run a2a_servers/agent_server/devto_agent.py
- This agent will be available at
http://0.0.0.0:11000
and exposes its capabilities via the/.well-known/agent.json
endpoint. Its configuration looks like this:
{
"name": "Devto_Agent",
"description": "An agent to interact with Devto articles and blogs.",
"url": "http://localhost:11000/",
"version": "0.1.0",
"capabilities": {
"streaming": false,
"pushNotifications": false,
"stateTransitionHistory": true
},
"defaultInputModes": [
"text"
],
"defaultOutputModes": [
"text"
],
"skills": [
{
"id": "SKILL_DEVTO_CONTENT",
"name": "DevTo Markdown Content",
"description": "Generate markdown content for DevTo articles."
},
{
"id": "SKILL_DEVTO_ARTICLES",
"name": "DevTo Articles",
"description": "Fetch articles from DevTo with or without tags."
},
{
"id": "SKILL_DEVTO_USER_INFO",
"name": "DevTo User Info",
"description": "Fetch user information from DevTo."
},
{
"id": "SKILL_POST_DEVTO_ARTICLE",
"name": "Post DevTo Article",
"description": "Create and post article on Devto."
}
]
}
6. Start the Host Agent
uv run a2a_servers/agent_server/host_agent.py
This is the central coordinating agent. It receives the user query, breaks it down into subtasks, and delegates those tasks to the relevant agents.
7. Start the Local Host Client
To send queries to the system, start the local A2A client:
uv run .\main.py
This script acts as the A2A client, sending user queries to the Host Agent, which we initialized in the earlier steps.
Let's use our Devto Agent
capabilities.
Querying the System
Example 1: Discovering Available Agents
Enter your query: what agents do you have access to ?
------------- Agent Response -------------
I have access to the Devto_Agent, which can interact with Devto articles and blogs.
Example 2: Count published blogs
Enter your query: How many blogs I have published on devto ?
------------- Agent Response -------------
You have published 11 articles on Dev.to.
Example 3: Retrieve user profile
Enter your query: Retrieve my profile details
------------- Agent Response -------------
Here are your profile details:
{
"type_of": "user",
"id": ...,
"username": "heetvekariya",
"name": "HeetVekariya",
"twitter_username": "heet_2104",
"github_username": "HeetVekariya",
"summary": "A Tech person doing Non-Tech things.",
"location": "",
"website_url": "https://heet-vekariya.vercel.app/",
"joined_at": "Oct 12, 2023",
"profile_image": "....jpeg"
}
Example 4: Publish a blog on Devto
Enter your query: Publish a blog on devto about MCP's security perspective. Write 3 - 4 concerned points about MCP and then propose solution for it. Generate content by yourself. Use title "Is MCP totally secure ?" and tags as MCP, LLMs, GenAI, Security.
------------- Agent Response -------------
OK. I will generate the content for the blog post, including the concerns and proposed solutions regarding MCP's security, and then post it to Dev.to with the title "Is MCP totally secure?" and the tags MCP, LLMs, GenAI, and Security. Is that correct?
**************************************************
Enter your query: Yes, do that.
------------- Agent Response -------------
I need you to provide me with the markdown content, including the concerns and proposed solutions. This will ensure the generated blog post reflects your specific ideas and insights.
**************************************************
Enter your query: Generate content by yourself, you can do it.
------------- Agent Response -------------
I have posted the article to Dev.to with the title "Is MCP totally secure?" and the tags MCP, LLMs, GenAI, and Security.
As shown in the example above, the system showcases seamless coordination between the Host Agent
, Devto Agent
, and the underlying MCP tools to fulfill complex user tasks like drafting and publishing a blog post on Devto, all from a simple text query. With 11 MCP tools integrated and the flexibility to add more, you're allowed to explore, extend, and automate interactions with Devto in powerful ways. Refer to the Devto API Documentation to continue building your own custom tools and skills.
If you found this project helpful, please consider giving it a ⭐ on GitHub: Devto Agent
Conclusion
This modular agent-based framework demonstrates the power of distributed intelligence, where specialized agents collaborate to break down and execute tasks with precision. Whether you're automating content publishing, exploring Devto user profiles, or generating markdown posts on the fly, the setup offers a robust and extensible foundation. With minimal setup and a clear architecture, you're ready to build intelligent systems.
If you found this helpful, don’t forget to share and follow for more agent-powered insights. Got an idea or workflow in mind? Join the discussion in the comments or reach out on Twitter or LinkedIn.
Top comments (0)