agharsallah commited on
Commit
9d37196
·
1 Parent(s): d770ff9

Adding mcp as a client

Browse files
Files changed (5) hide show
  1. .gitignore +2 -0
  2. app.py +6 -1
  3. requirements.txt +4 -1
  4. ui/mcp_components.py +48 -0
  5. 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)}"