agharsallah
commited on
Commit
·
9d37196
1
Parent(s):
d770ff9
Adding mcp as a client
Browse files- .gitignore +2 -0
- app.py +6 -1
- requirements.txt +4 -1
- ui/mcp_components.py +48 -0
- util/mcp_utils.py +110 -0
.gitignore
CHANGED
@@ -118,3 +118,5 @@ dmypy.json
|
|
118 |
|
119 |
# Cython debug symbols
|
120 |
cython_debug/
|
|
|
|
|
|
118 |
|
119 |
# Cython debug symbols
|
120 |
cython_debug/
|
121 |
+
|
122 |
+
local-docs
|
app.py
CHANGED
@@ -27,13 +27,14 @@ from ui.components import (
|
|
27 |
create_chapter_accordion,
|
28 |
create_story_melody_section,
|
29 |
)
|
|
|
30 |
from controllers.app_controller import (
|
31 |
generate_audio_with_status,
|
32 |
generate_melody_from_story_with_status,
|
33 |
)
|
34 |
-
|
35 |
from ui.events import setup_event_handlers
|
36 |
|
|
|
37 |
# Configure logging
|
38 |
logging.basicConfig(
|
39 |
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
@@ -147,6 +148,10 @@ def create_app():
|
|
147 |
outputs=[melody_output, melody_status],
|
148 |
)
|
149 |
|
|
|
|
|
|
|
|
|
150 |
# Set up event handlers
|
151 |
setup_event_handlers(
|
152 |
generate_button=generate_button,
|
|
|
27 |
create_chapter_accordion,
|
28 |
create_story_melody_section,
|
29 |
)
|
30 |
+
from ui.mcp_components import mcp_letter_counter
|
31 |
from controllers.app_controller import (
|
32 |
generate_audio_with_status,
|
33 |
generate_melody_from_story_with_status,
|
34 |
)
|
|
|
35 |
from ui.events import setup_event_handlers
|
36 |
|
37 |
+
|
38 |
# Configure logging
|
39 |
logging.basicConfig(
|
40 |
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
|
148 |
outputs=[melody_output, melody_status],
|
149 |
)
|
150 |
|
151 |
+
# TODO DELETE THIS AS IT IRRELEVENT TO STORY GENERATION
|
152 |
+
with gr.Tab("Character Counter"):
|
153 |
+
mcp_letter_counter()
|
154 |
+
|
155 |
# Set up event handlers
|
156 |
setup_event_handlers(
|
157 |
generate_button=generate_button,
|
requirements.txt
CHANGED
@@ -4,4 +4,7 @@ python-dotenv
|
|
4 |
PyPDF2
|
5 |
typing-extensions
|
6 |
types-requests
|
7 |
-
markdown
|
|
|
|
|
|
|
|
4 |
PyPDF2
|
5 |
typing-extensions
|
6 |
types-requests
|
7 |
+
markdown
|
8 |
+
mcp
|
9 |
+
asyncio
|
10 |
+
openai
|
ui/mcp_components.py
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
from util.mcp_utils import interact_with_openai
|
3 |
+
|
4 |
+
|
5 |
+
def mcp_letter_counter():
|
6 |
+
"""
|
7 |
+
Count the occurrences of a specific letter in a given word using the MCP server.
|
8 |
+
|
9 |
+
Args:
|
10 |
+
word (str): The word in which to count the letter.
|
11 |
+
letter (str): The letter to count in the word.
|
12 |
+
|
13 |
+
Returns:
|
14 |
+
str: The response from the MCP server containing the count of the letter.
|
15 |
+
"""
|
16 |
+
with gr.Column(elem_classes="gr-form") as mcp_letter_counter_ui:
|
17 |
+
gr.HTML("<h2>MCP Character Counter</h2>")
|
18 |
+
gr.HTML("<p>Enter text below and the MCP server will count the characters.</p>")
|
19 |
+
|
20 |
+
# Text input for character counting
|
21 |
+
input_text = gr.Textbox(
|
22 |
+
label="Enter text to count characters",
|
23 |
+
placeholder="Type or paste text here...",
|
24 |
+
lines=5,
|
25 |
+
)
|
26 |
+
letter_to_count = gr.Textbox(
|
27 |
+
label="Letter to Count",
|
28 |
+
placeholder="Enter the letter to count in the text",
|
29 |
+
lines=1,
|
30 |
+
)
|
31 |
+
|
32 |
+
# Count button
|
33 |
+
count_button = gr.Button("Count Characters", variant="primary")
|
34 |
+
|
35 |
+
# Result display
|
36 |
+
result_text = gr.Textbox(label="Result", interactive=False)
|
37 |
+
|
38 |
+
# Connect the button to different counting functions based on method
|
39 |
+
count_button.click(
|
40 |
+
fn=interact_with_openai, # Use the async function directly - Gradio handles the async
|
41 |
+
inputs=[input_text, letter_to_count],
|
42 |
+
outputs=[result_text],
|
43 |
+
)
|
44 |
+
|
45 |
+
# MCP Tools Explorer Section
|
46 |
+
gr.HTML("<h3>MCP Tools Explorer</h3>")
|
47 |
+
gr.HTML("<p>Explore the available tools in the MCP server.</p>")
|
48 |
+
return mcp_letter_counter_ui
|
util/mcp_utils.py
ADDED
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import logging
|
3 |
+
from mcp.client.sse import sse_client
|
4 |
+
from mcp.client.session import ClientSession
|
5 |
+
from openai import OpenAI
|
6 |
+
import os
|
7 |
+
|
8 |
+
logger = logging.getLogger(__name__)
|
9 |
+
|
10 |
+
MCP_SERVER_URL = "https://agharsallah-mcp-helper-tool.hf.space/gradio_api/mcp/sse"
|
11 |
+
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY", ""))
|
12 |
+
|
13 |
+
|
14 |
+
async def interact_with_openai(word: str, letter: str):
|
15 |
+
"""Count occurrences of a letter in text using the MCP server."""
|
16 |
+
try:
|
17 |
+
# Connect to the MCP server using proper async context manager
|
18 |
+
async with sse_client(MCP_SERVER_URL) as (read, write):
|
19 |
+
async with ClientSession(read, write) as session:
|
20 |
+
# Initialize the connection
|
21 |
+
await session.initialize()
|
22 |
+
|
23 |
+
# List available tools from the MCP server
|
24 |
+
try:
|
25 |
+
response = await session.list_tools()
|
26 |
+
tools = response.tools
|
27 |
+
|
28 |
+
logger.info(f"Available tools: {[tool.name for tool in tools]}")
|
29 |
+
|
30 |
+
# Check if the 'count_letter' tool exists
|
31 |
+
# update to check if tool.name contains letter_counter
|
32 |
+
# TODO HERE we are checking the tool by name but we can do this step with LLM
|
33 |
+
# The LLM will pick the tool name for us that will be used
|
34 |
+
if not any("letter_counter" in tool.name for tool in tools):
|
35 |
+
return (
|
36 |
+
"Error: Tool 'letter_counter' not found on the MCP server.1"
|
37 |
+
)
|
38 |
+
# get tool specification
|
39 |
+
# Check if the 'letter_counter' tool exists
|
40 |
+
letter_counter_tool = next(
|
41 |
+
(tool for tool in tools if "letter_counter" in tool.name), None
|
42 |
+
)
|
43 |
+
if not letter_counter_tool:
|
44 |
+
return (
|
45 |
+
"Error: Tool 'letter_counter' not found on the MCP server.2"
|
46 |
+
)
|
47 |
+
|
48 |
+
# Prepare the OpenAI function specification
|
49 |
+
fn_spec = [
|
50 |
+
{
|
51 |
+
"name": letter_counter_tool.name,
|
52 |
+
"description": letter_counter_tool.description,
|
53 |
+
"parameters": letter_counter_tool.inputSchema,
|
54 |
+
}
|
55 |
+
]
|
56 |
+
print(f"Function spec: {fn_spec}")
|
57 |
+
# let the ai know which tool to use
|
58 |
+
# Prepare the OpenAI model prompt
|
59 |
+
messages = [
|
60 |
+
{
|
61 |
+
"role": "system",
|
62 |
+
"content": "You can call the 'count_letter' function to count the occurrences of a letter in a word.",
|
63 |
+
},
|
64 |
+
{
|
65 |
+
"role": "user",
|
66 |
+
"content": f'How many times does the letter "{letter}" appear in the word "{word}"?',
|
67 |
+
},
|
68 |
+
]
|
69 |
+
formatted_tools = [{"type": "function", **spec} for spec in fn_spec]
|
70 |
+
# OpenAI function-call completion
|
71 |
+
first_response = client.responses.create(
|
72 |
+
model="gpt-4o-mini", # Choose the right OpenAI model
|
73 |
+
input=messages,
|
74 |
+
tools=formatted_tools,
|
75 |
+
tool_choice="auto",
|
76 |
+
)
|
77 |
+
logger.info(f"OpenAI response: {first_response.output}")
|
78 |
+
# Check if the response contains a tool call
|
79 |
+
tool_call = first_response.output[0]
|
80 |
+
|
81 |
+
if tool_call:
|
82 |
+
logger.info(f"Tool call: {tool_call}")
|
83 |
+
# Call the MCP tool with the provided parameters
|
84 |
+
tool_response = await session.call_tool(
|
85 |
+
name=tool_call.name,
|
86 |
+
arguments=json.loads(tool_call.arguments),
|
87 |
+
)
|
88 |
+
logger.info(f"Tool response: {tool_response}")
|
89 |
+
# Extract the result from the tool response
|
90 |
+
# TODO THE cool thing is that you can send the response back to the AI
|
91 |
+
# and let it parse the response
|
92 |
+
# and return the result
|
93 |
+
if tool_response.content:
|
94 |
+
try:
|
95 |
+
result = int(
|
96 |
+
tool_response.content[0].text
|
97 |
+
) # Convert text to int
|
98 |
+
return result # Return the integer count
|
99 |
+
except ValueError:
|
100 |
+
return "Error: Tool response is not a valid integer."
|
101 |
+
else:
|
102 |
+
return "Error: Unexpected tool response format."
|
103 |
+
else:
|
104 |
+
return "No tool call found in the OpenAI response."
|
105 |
+
except Exception as inner_e:
|
106 |
+
logger.error(f"Error in MCP session: {str(inner_e)}", exc_info=True)
|
107 |
+
return f"Error in MCP session: {str(inner_e)}"
|
108 |
+
except Exception as outer_e:
|
109 |
+
logger.error(f"Error connecting to MCP server: {str(outer_e)}", exc_info=True)
|
110 |
+
return f"Error connecting to MCP server: {str(outer_e)}"
|