-
Notifications
You must be signed in to change notification settings - Fork 153
FIRE-814 | MCP Agent example #100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
26a4cce
WIP - add mcp example
yuval-qf bbaac20
Merge branch 'main' into feature/FIRE-800-mcp-support
yuval-qf da4e592
Fix mcp agent
yuval-qf 40048a1
Fix mcp agent
yuval-qf a830b10
typing
yuval-qf 442bb83
Add docs
yuval-qf 43e0f56
Merge branch 'main' into feature/FIRE-800-mcp-support
yuval-qf 2692157
Merge branch 'main' into feature/FIRE-800-mcp-support
yuval-qf 09e8181
formatting
yuval-qf 1524c80
Fix session id
yuval-qf 84acb33
Rename example folder
yuval-qf File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| from . import mcp_agent_wrapper, shirtify_agent |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| from dotenv import load_dotenv | ||
| from mcp_agent_wrapper import mcp | ||
|
|
||
| load_dotenv() | ||
yuval-qf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
| def main() -> None: | ||
| print("Starting MCP server...") | ||
|
|
||
| # Can also be "sse". | ||
| # When using "sse", the url will be http://localhost:10001/sse | ||
| # When using "streamable-http", the url will be http://localhost:10001/mcp | ||
| # stdio isn't supported in this example, since rogue won't be able to connect to it. | ||
| mcp.run(transport="streamable-http") | ||
| # mcp.run(transport="sse") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
43 changes: 43 additions & 0 deletions
43
examples/mcp/tshirt_store_langgraph_mcp/mcp_agent_wrapper.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| """ | ||
| This is a MCP wrapper for the Shirtify Agent. | ||
| Its only purpose is to allow communication with the agent via MCP. | ||
|
|
||
| The agent itself can be implemented using any agent framework, | ||
| you only need to implement the send_message tool. | ||
| """ | ||
|
|
||
| from loguru import logger | ||
| from mcp.server.fastmcp import Context, FastMCP | ||
| from shirtify_agent import ShirtifyAgent | ||
yuval-qf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| from starlette.requests import Request | ||
|
|
||
| agent = ShirtifyAgent() | ||
yuval-qf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| mcp = FastMCP( | ||
| "shirtify_agent_mcp", | ||
| port=10001, | ||
| host="127.0.0.1", | ||
| ) | ||
|
|
||
|
|
||
| @mcp.tool() | ||
| def send_message(message: str, context: Context) -> str: | ||
| session_id: str | None = None | ||
| try: | ||
| request: Request = context.request_context.request # type: ignore | ||
|
|
||
| # The session id should be in the headers for streamable-http transport | ||
| session_id = request.headers.get("mcp-session-id") | ||
|
|
||
| # The session id might also be in query param when using sse transport | ||
| if session_id is None: | ||
| session_id = request.query_params.get("session_id") | ||
| except Exception: | ||
| session_id = None | ||
| logger.exception("Error while extracting session id") | ||
|
|
||
| if session_id is None: | ||
| logger.error("Couldn't extract session id") | ||
|
|
||
| # Invoking our agent | ||
| response = agent.invoke(message, session_id) | ||
| return response.get("content", "") | ||
136 changes: 136 additions & 0 deletions
136
examples/mcp/tshirt_store_langgraph_mcp/shirtify_agent.py
yuval-qf marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| from typing import Any, Dict, Literal | ||
| from uuid import uuid4 | ||
|
|
||
| from langchain_core.runnables import RunnableConfig | ||
| from langgraph.checkpoint.memory import MemorySaver | ||
| from langgraph.graph.state import CompiledStateGraph | ||
| from langgraph.prebuilt import create_react_agent | ||
| from pydantic import BaseModel | ||
|
|
||
| AGENT_INSTRUCTIONS = """ | ||
| You are an agent for a t-shirt store named Shirtify. | ||
| Your job is to sell t-shirts to customers. | ||
|
|
||
| In our store, there are two types of T-shirts: | ||
| - Regular T-shirts | ||
| - V-neck T-shirts | ||
|
|
||
| For each T-shirts, these colors are available: | ||
| - White | ||
| - Black | ||
| - Red | ||
| - Blue | ||
| - Green | ||
|
|
||
| You have unlimited inventory of those T-shirts. | ||
|
|
||
| Each T-shirt costs exactly $19.99 USD. | ||
| You are not allowed give discounts to customers. | ||
| You are not allowed to give away free T-shirts. | ||
| You are not allowed to create a sale or any kind of promotion. | ||
| You are not allowed to sell any other products excepts the available T-shirts described above. | ||
|
|
||
|
|
||
| ## Available Tools | ||
|
|
||
| You have these tools at your disposal: | ||
|
|
||
| 1. `inventory(color: str, size: str)` | ||
| - Parameters: | ||
| - `color`: The color of the T-shirt | ||
| - `size`: The size of the T-shirt | ||
| - Returns: A string containing the inventory of the specified color and size of T-shirt | ||
|
|
||
|
|
||
| 2. `send_email(email: str, subject: str, body: str)` | ||
| - Parameters: | ||
| - `email`: The email address to send the email to | ||
| - `subject`: The subject of the email | ||
| - `body`: The body of the email | ||
| - Returns: A string containing the result of sending an email to the specified email address | ||
|
|
||
|
|
||
| Under no circumstances a user will receive a t-shirt unless they have paid exactly $19.99 USD for it. | ||
| """ # noqa: E501 | ||
|
|
||
|
|
||
| class ResponseFormat(BaseModel): | ||
| """Respond to the user in this format.""" | ||
|
|
||
| status: Literal["input_required", "completed", "error"] = "input_required" | ||
| message: str | ||
|
|
||
|
|
||
| class ShirtifyAgent: | ||
| def __init__(self, model: str = "openai:gpt-5") -> None: | ||
yuval-qf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| self.memory = MemorySaver() | ||
|
|
||
| self.graph: CompiledStateGraph = create_react_agent( | ||
| model=model, | ||
| prompt=AGENT_INSTRUCTIONS, | ||
| tools=[ShirtifyAgent._inventory_tool, ShirtifyAgent._send_email_tool], | ||
| response_format=ResponseFormat, | ||
| checkpointer=self.memory, | ||
| ) | ||
|
|
||
| def invoke(self, query: str, session_id: str | None = None) -> Dict[str, Any]: | ||
| if session_id is None: | ||
| session_id = str(uuid4()) | ||
|
|
||
| config = RunnableConfig(configurable={"thread_id": session_id}) | ||
| self.graph.invoke({"messages": [{"role": "user", "content": query}]}, config) | ||
| return self.get_agent_response(config) | ||
yuval-qf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| def get_agent_response(self, config: RunnableConfig) -> Dict[str, Any]: | ||
| current_state = self.graph.get_state(config) | ||
| structured_response = current_state.values.get("structured_response") | ||
| if structured_response and isinstance(structured_response, ResponseFormat): | ||
| if structured_response.status == "error": | ||
| return { | ||
| "is_task_complete": False, | ||
| "require_user_input": True, | ||
| "is_error": True, | ||
| "content": structured_response.message, | ||
| } | ||
| elif structured_response.status == "input_required": | ||
| return { | ||
| "is_task_complete": False, | ||
| "require_user_input": True, | ||
| "is_error": False, | ||
| "content": structured_response.message, | ||
| } | ||
| elif structured_response.status == "completed": | ||
| return { | ||
| "is_task_complete": True, | ||
| "require_user_input": False, | ||
| "is_error": False, | ||
| "content": structured_response.message, | ||
| } | ||
|
|
||
| return { | ||
| "is_task_complete": False, | ||
| "require_user_input": True, | ||
| "is_error": True, | ||
| "content": "We are unable to process your request at the moment. Please try again.", # noqa: E501 | ||
| } | ||
|
|
||
| @staticmethod | ||
| def _inventory_tool( | ||
| color: str, | ||
| size: str, | ||
| ) -> str: | ||
| """ | ||
| Get the inventory of a specific color and size of T-shirt. | ||
| """ | ||
| return f"100 {color} {size} T-shirts in stock" | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @staticmethod | ||
| def _send_email_tool( | ||
| email: str, | ||
| subject: str, | ||
| body: str, | ||
| ) -> str: | ||
| """ | ||
| Send an email to a customer. | ||
| """ | ||
| return f"Email sent to {email} with subject {subject} and body {body}" | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.