crcdng commited on
Commit
d2ff44f
·
1 Parent(s): 0a35bdf

server / client

Browse files
Files changed (4) hide show
  1. app.py +231 -67
  2. doomsweek_mcp_server copy.py +74 -0
  3. doomsweek_mcp_server.py +85 -0
  4. requirements.txt +3 -0
app.py CHANGED
@@ -1,74 +1,238 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- import gradio as gr
4
- import json
5
  import os
6
- import re
7
- import requests
8
-
9
-
10
-
11
- def calc_asteroid_factor(data, weight=1.0):
12
- """
13
- Calculates the asteroid doom probability.
14
 
15
- Args:
16
- data (object): The data structure returned from NASA
17
- weight (float): The weight to apply to the hazard factor.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
- Returns:
20
- Weighted factor for Near Earth Object impact for the next seven days.
21
- """
22
-
23
- objects = [obj for sublist in data["near_earth_objects"].values() for obj in sublist]
24
-
25
- num_objects = len(objects)
26
-
27
- hazardous_objects = [obj for sublist in data["near_earth_objects"].values() for obj in sublist if obj["is_potentially_hazardous_asteroid"] == True]
28
-
29
- num_hazardous_objects = len(hazardous_objects)
30
-
31
- return (num_hazardous_objects/num_objects * weight) # hazard factor
32
-
33
-
34
- def fetch_asteroid_data(url, api_key):
35
- """
36
- Fetches data from the NASA Near Earth Object Web Service.
37
 
38
- Returns:
39
- Near Earth Objects for the next seven days.
40
- """
41
-
42
- request_data = requests.get(
43
- url,
44
- params={"api_key": api_key},
45
- )
46
-
47
- if request_data.status_code != 200:
48
- return None
49
- else:
50
- return json.loads(request_data.content)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
-
53
- def calc_doom_probability():
54
- """
55
- Calculates the overall doom probability.
 
 
 
 
 
56
 
57
- Returns:
58
- The overall doom probability for the next seven days.
59
- """
60
-
61
- nasa_url = "https://api.nasa.gov/neo/rest/v1/feed"
62
- nasa_api_key = os.getenv("NASA_API_KEY")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
- if not nasa_api_key:
65
- return "ERROR: NASA_API_KEY not set."
66
- else:
67
- nasa_data = fetch_asteroid_data(nasa_url, nasa_api_key)
68
- if not nasa_data:
69
- return "ERROR: Unable to fetch data from NASA API."
70
- else:
71
- return calc_asteroid_factor(nasa_data, weight=1.0)
72
-
73
- demo = gr.Interface(fn=calc_doom_probability, inputs=None, outputs="text")
74
- demo.launch(mcp_server=True)
 
1
+ import asyncio
 
 
 
2
  import os
3
+ import json
4
+ from typing import List, Dict, Any, Union
5
+ from contextlib import AsyncExitStack
 
 
 
 
 
6
 
7
+ import gradio as gr
8
+ from gradio.components.chatbot import ChatMessage
9
+ from mcp import ClientSession, StdioServerParameters
10
+ from mcp.client.stdio import stdio_client
11
+ from anthropic import Anthropic
12
+ from dotenv import load_dotenv
13
+
14
+ load_dotenv()
15
+
16
+ loop = asyncio.new_event_loop()
17
+ asyncio.set_event_loop(loop)
18
+
19
+ class MCPClientWrapper:
20
+ def __init__(self):
21
+ self.session = None
22
+ self.exit_stack = None
23
+ self.anthropic = Anthropic()
24
+ self.tools = []
25
 
26
+ def connect(self, server_path: str) -> str:
27
+ return loop.run_until_complete(self._connect(server_path))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
+ async def _connect(self, server_path: str) -> str:
30
+ if self.exit_stack:
31
+ await self.exit_stack.aclose()
32
+
33
+ self.exit_stack = AsyncExitStack()
34
+
35
+ is_python = server_path.endswith('.py')
36
+ command = "python" if is_python else "node"
37
+
38
+ server_params = StdioServerParameters(
39
+ command=command,
40
+ args=[server_path],
41
+ env={"PYTHONIOENCODING": "utf-8", "PYTHONUNBUFFERED": "1"}
42
+ )
43
+
44
+ stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
45
+ self.stdio, self.write = stdio_transport
46
+
47
+ self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
48
+ await self.session.initialize()
49
+
50
+ response = await self.session.list_tools()
51
+ self.tools = [{
52
+ "name": tool.name,
53
+ "description": tool.description,
54
+ "input_schema": tool.inputSchema
55
+ } for tool in response.tools]
56
+
57
+ tool_names = [tool["name"] for tool in self.tools]
58
+ return f"Connected to MCP server. Available tools: {', '.join(tool_names)}"
59
 
60
+ def process_message(self, message: str, history: List[Union[Dict[str, Any], ChatMessage]]) -> tuple:
61
+ if not self.session:
62
+ return history + [
63
+ {"role": "user", "content": message},
64
+ {"role": "assistant", "content": "Please connect to an MCP server first."}
65
+ ], gr.Textbox(value="")
66
+
67
+ new_messages = loop.run_until_complete(self._process_query(message, history))
68
+ return history + [{"role": "user", "content": message}] + new_messages, gr.Textbox(value="")
69
 
70
+ async def _process_query(self, message: str, history: List[Union[Dict[str, Any], ChatMessage]]):
71
+ claude_messages = []
72
+ for msg in history:
73
+ if isinstance(msg, ChatMessage):
74
+ role, content = msg.role, msg.content
75
+ else:
76
+ role, content = msg.get("role"), msg.get("content")
77
+
78
+ if role in ["user", "assistant", "system"]:
79
+ claude_messages.append({"role": role, "content": content})
80
+
81
+ claude_messages.append({"role": "user", "content": message})
82
+
83
+ response = self.anthropic.messages.create(
84
+ model="claude-sonnet-4-20250514",
85
+ max_tokens=1000,
86
+ messages=claude_messages,
87
+ tools=self.tools
88
+ )
89
+
90
+ result_messages = []
91
+
92
+ for content in response.content:
93
+ if content.type == 'text':
94
+ result_messages.append({
95
+ "role": "assistant",
96
+ "content": content.text
97
+ })
98
+
99
+ elif content.type == 'tool_use':
100
+ tool_name = content.name
101
+ tool_args = content.input
102
+
103
+ result_messages.append({
104
+ "role": "assistant",
105
+ "content": f"I'll use the {tool_name} tool to help answer your question.",
106
+ "metadata": {
107
+ "title": f"Using tool: {tool_name}",
108
+ "log": f"Parameters: {json.dumps(tool_args, ensure_ascii=True)}",
109
+ "status": "pending",
110
+ "id": f"tool_call_{tool_name}"
111
+ }
112
+ })
113
+
114
+ result_messages.append({
115
+ "role": "assistant",
116
+ "content": "```json\n" + json.dumps(tool_args, indent=2, ensure_ascii=True) + "\n```",
117
+ "metadata": {
118
+ "parent_id": f"tool_call_{tool_name}",
119
+ "id": f"params_{tool_name}",
120
+ "title": "Tool Parameters"
121
+ }
122
+ })
123
+
124
+ result = await self.session.call_tool(tool_name, tool_args)
125
+
126
+ if result_messages and "metadata" in result_messages[-2]:
127
+ result_messages[-2]["metadata"]["status"] = "done"
128
+
129
+ result_messages.append({
130
+ "role": "assistant",
131
+ "content": "Here are the results from the tool:",
132
+ "metadata": {
133
+ "title": f"Tool Result for {tool_name}",
134
+ "status": "done",
135
+ "id": f"result_{tool_name}"
136
+ }
137
+ })
138
+
139
+ result_content = result.content
140
+ if isinstance(result_content, list):
141
+ result_content = "\n".join(str(item) for item in result_content)
142
+
143
+ try:
144
+ result_json = json.loads(result_content)
145
+ if isinstance(result_json, dict) and "type" in result_json:
146
+ if result_json["type"] == "image" and "url" in result_json:
147
+ result_messages.append({
148
+ "role": "assistant",
149
+ "content": {"path": result_json["url"], "alt_text": result_json.get("message", "Generated image")},
150
+ "metadata": {
151
+ "parent_id": f"result_{tool_name}",
152
+ "id": f"image_{tool_name}",
153
+ "title": "Generated Image"
154
+ }
155
+ })
156
+ else:
157
+ result_messages.append({
158
+ "role": "assistant",
159
+ "content": "```\n" + result_content + "\n```",
160
+ "metadata": {
161
+ "parent_id": f"result_{tool_name}",
162
+ "id": f"raw_result_{tool_name}",
163
+ "title": "Raw Output"
164
+ }
165
+ })
166
+ except:
167
+ result_messages.append({
168
+ "role": "assistant",
169
+ "content": "```\n" + result_content + "\n```",
170
+ "metadata": {
171
+ "parent_id": f"result_{tool_name}",
172
+ "id": f"raw_result_{tool_name}",
173
+ "title": "Raw Output"
174
+ }
175
+ })
176
+
177
+ claude_messages.append({"role": "user", "content": f"Tool result for {tool_name}: {result_content}"})
178
+ next_response = self.anthropic.messages.create(
179
+ model="claude-3-5-sonnet-20241022",
180
+ max_tokens=1000,
181
+ messages=claude_messages,
182
+ )
183
+
184
+ if next_response.content and next_response.content[0].type == 'text':
185
+ result_messages.append({
186
+ "role": "assistant",
187
+ "content": next_response.content[0].text
188
+ })
189
+
190
+ return result_messages
191
+
192
+ client = MCPClientWrapper()
193
+
194
+ def gradio_interface():
195
+ with gr.Blocks(title="MCP Doomsweek Client") as demo:
196
+ gr.Markdown("# MCP Doomsweek Assistant")
197
+ gr.Markdown("Connect to your MCP Doomsweek Server and chat with the assistant")
198
+
199
+ with gr.Row(equal_height=True):
200
+ with gr.Column(scale=4):
201
+ server_path = gr.Textbox(
202
+ label="Server Script Path",
203
+ placeholder="Enter path to server script (e.g., weather.py)",
204
+ value="doomsweek_mcp_server.py"
205
+ )
206
+ with gr.Column(scale=1):
207
+ connect_btn = gr.Button("Connect")
208
+
209
+ status = gr.Textbox(label="Connection Status", interactive=False)
210
+
211
+ chatbot = gr.Chatbot(
212
+ value=[],
213
+ height=500,
214
+ type="messages",
215
+ show_copy_button=True,
216
+ avatar_images=("👤", "🤖")
217
+ )
218
+
219
+ with gr.Row(equal_height=True):
220
+ msg = gr.Textbox(
221
+ label="Your Question",
222
+ placeholder="Ask about weather or alerts (e.g., What's the weather in New York?)",
223
+ scale=4
224
+ )
225
+ clear_btn = gr.Button("Clear Chat", scale=1)
226
+
227
+ connect_btn.click(client.connect, inputs=server_path, outputs=status)
228
+ msg.submit(client.process_message, [msg, chatbot], [chatbot, msg])
229
+ clear_btn.click(lambda: [], None, chatbot)
230
+
231
+ return demo
232
 
233
+ if __name__ == "__main__":
234
+ if not os.getenv("ANTHROPIC_API_KEY"):
235
+ print("Warning: ANTHROPIC_API_KEY not found in environment. Please set it in your .env file.")
236
+
237
+ interface = gradio_interface()
238
+ interface.launch(debug=True)
 
 
 
 
 
doomsweek_mcp_server copy.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import gradio as gr
4
+ import json
5
+ import os
6
+ import re
7
+ import requests
8
+
9
+
10
+
11
+ def calc_asteroid_factor(data, weight=1.0):
12
+ """
13
+ Calculates the asteroid doom probability.
14
+
15
+ Args:
16
+ data (object): The data structure returned from NASA
17
+ weight (float): The weight to apply to the hazard factor.
18
+
19
+ Returns:
20
+ Weighted factor for Near Earth Object impact for the next seven days.
21
+ """
22
+
23
+ objects = [obj for sublist in data["near_earth_objects"].values() for obj in sublist]
24
+
25
+ num_objects = len(objects)
26
+
27
+ hazardous_objects = [obj for sublist in data["near_earth_objects"].values() for obj in sublist if obj["is_potentially_hazardous_asteroid"] == True]
28
+
29
+ num_hazardous_objects = len(hazardous_objects)
30
+
31
+ return (num_hazardous_objects/num_objects * weight) # hazard factor
32
+
33
+
34
+ def fetch_asteroid_data(url, api_key):
35
+ """
36
+ Fetches data from the NASA Near Earth Object Web Service.
37
+
38
+ Returns:
39
+ Near Earth Objects for the next seven days.
40
+ """
41
+
42
+ request_data = requests.get(
43
+ url,
44
+ params={"api_key": api_key},
45
+ )
46
+
47
+ if request_data.status_code != 200:
48
+ return None
49
+ else:
50
+ return json.loads(request_data.content)
51
+
52
+
53
+ def calc_doom_probability():
54
+ """
55
+ Calculates the overall doom probability.
56
+
57
+ Returns:
58
+ The overall doom probability for the next seven days.
59
+ """
60
+
61
+ nasa_url = "https://api.nasa.gov/neo/rest/v1/feed"
62
+ nasa_api_key = os.getenv("NASA_API_KEY")
63
+
64
+ if not nasa_api_key:
65
+ return "ERROR: NASA_API_KEY not set."
66
+ else:
67
+ nasa_data = fetch_asteroid_data(nasa_url, nasa_api_key)
68
+ if not nasa_data:
69
+ return "ERROR: Unable to fetch data from NASA API."
70
+ else:
71
+ return calc_asteroid_factor(nasa_data, weight=1.0)
72
+
73
+ demo = gr.Interface(fn=calc_doom_probability, inputs=None, outputs="text")
74
+ demo.launch(mcp_server=True)
doomsweek_mcp_server.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from mcp.server.fastmcp import FastMCP
4
+ import gradio as gr
5
+ import io
6
+ import json
7
+ import os
8
+ import re
9
+ import sys
10
+
11
+ import requests
12
+
13
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
14
+ sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
15
+
16
+ mcp = FastMCP("doomsweek_mcp_server")
17
+
18
+
19
+ def calc_asteroid_factor(data, weight=1.0):
20
+ """
21
+ Calculates the asteroid doom probability.
22
+
23
+ Args:
24
+ data (object): The data structure returned from NASA
25
+ weight (float): The weight to apply to the hazard factor.
26
+
27
+ Returns:
28
+ Weighted factor for Near Earth Object impact for the next seven days.
29
+ """
30
+
31
+ objects = [obj for sublist in data["near_earth_objects"].values() for obj in sublist]
32
+
33
+ num_objects = len(objects)
34
+
35
+ hazardous_objects = [obj for sublist in data["near_earth_objects"].values() for obj in sublist if obj["is_potentially_hazardous_asteroid"] == True]
36
+
37
+ num_hazardous_objects = len(hazardous_objects)
38
+
39
+ return (num_hazardous_objects/num_objects * weight) # hazard factor
40
+
41
+
42
+ def fetch_asteroid_data(url, api_key):
43
+ """
44
+ Fetches data from the NASA Near Earth Object Web Service.
45
+
46
+ Returns:
47
+ Near Earth Objects for the next seven days.
48
+ """
49
+
50
+ request_data = requests.get(
51
+ url,
52
+ params={"api_key": api_key},
53
+ )
54
+
55
+ if request_data.status_code != 200:
56
+ return None
57
+ else:
58
+ return json.loads(request_data.content)
59
+
60
+ @mcp.tool()
61
+ def calc_doom_probability():
62
+ """
63
+ Calculates the overall doom probability.
64
+
65
+ Returns:
66
+ The overall doom probability for the next seven days.
67
+ """
68
+
69
+ nasa_url = "https://api.nasa.gov/neo/rest/v1/feed"
70
+ nasa_api_key = os.getenv("NASA_API_KEY")
71
+
72
+ if not nasa_api_key:
73
+ return "ERROR: NASA_API_KEY not set."
74
+ else:
75
+ nasa_data = fetch_asteroid_data(nasa_url, nasa_api_key)
76
+ if not nasa_data:
77
+ return "ERROR: Unable to fetch data from NASA API."
78
+ else:
79
+ return calc_asteroid_factor(nasa_data, weight=1.0)
80
+
81
+ # demo = gr.Interface(fn=calc_doom_probability, inputs=None, outputs="text")
82
+ # demo.launch(mcp_server=True)
83
+
84
+ if __name__ == "__main__":
85
+ mcp.run(transport='stdio')
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio[mcp]
2
+ anthropic
3
+ mcp