Googolplexic commited on
Commit
08a6cc3
·
1 Parent(s): ff3827e

Documentation

Browse files
Files changed (7) hide show
  1. README.md +7 -3
  2. api_client.py +4 -2
  3. api_monitor.py +16 -54
  4. app.py +1 -1
  5. keygenServer/client/app/main/page.tsx +2 -1
  6. killClaude.ps1 +0 -24
  7. schema.sql +0 -21
README.md CHANGED
@@ -31,7 +31,7 @@ Hermes requires users to generate an MCP API key, which you can do right [here a
31
 
32
  1. **Validate API Configuration**: Use the `validate_api_configuration` function to check if your API configuration is valid. If the request fails, it will return the error. Else, it will save the configuration to the database and return a success message with `config_id`. The user/agent can then decide whether to proceed with monitoring based on the response (etc. can reject the API configuration if it doesn't show the desired data content).
33
  2. **Activate Monitoring**: Use the `activate_monitoring` function with the `config_id` from the previous step to start monitoring the API. This will set up a background task that will periodically check the API and store the responses.
34
- 3. **Get Monitoring Data**: Use the `get_monitoring_data` function with the `config_id` to retrieve the monitoring data. You can specify the mode of data retrieval (summary, details, or full) to get the desired level of information. This can be called anytime after monitoring is activated.
35
 
36
  ### :warning: Important! :warning:
37
 
@@ -39,10 +39,14 @@ Call results and configurations will be deleted 14 days after the last call to t
39
 
40
  ## Problems we ran into
41
 
42
- Since (free) Gradio is stateless, we had to find a way to pass down information between both the different API calls and the user calls, while still ensuring privacy. We ended up using the MCP API key as a user identifier, which allowed us to separate user data while still being able to access it when needed. There was also the problem of having to validate the data and making sure that the user/agent is satisfied with the data before activating monitoring. We solved this by separating the whole process into multiple functions, which allowed us to validate the data before activating monitoring.
 
 
 
 
43
 
44
  ## Future Steps
45
 
46
- Currently, the API call results store the entire response, which can be quite large, especially for LLMs like Claude to handle (or reach conversation limits). In the future, we think that we can improve this by allowing users to specify a format for the response, such as only storing the text content or a specific field in the response. This would allow users to save space and make it easier to analyze the data later on.
47
 
48
  Also, we plan to move the authentication backend off of Render, as Render spins down the backend after a period of inactivity, which can cause a lot of delay. (We used Render because Vercel would not work with our database library.)
 
31
 
32
  1. **Validate API Configuration**: Use the `validate_api_configuration` function to check if your API configuration is valid. If the request fails, it will return the error. Else, it will save the configuration to the database and return a success message with `config_id`. The user/agent can then decide whether to proceed with monitoring based on the response (etc. can reject the API configuration if it doesn't show the desired data content).
33
  2. **Activate Monitoring**: Use the `activate_monitoring` function with the `config_id` from the previous step to start monitoring the API. This will set up a background task that will periodically check the API and store the responses.
34
+ 3. **Retrieve Monitored Data**: Use the `retrieve_monitored_data` function with the `config_id` to retrieve the monitoring data. You can specify the mode of data retrieval (summary, details, or full) to get the desired level of information. This can be called anytime after monitoring is activated.
35
 
36
  ### :warning: Important! :warning:
37
 
 
39
 
40
  ## Problems we ran into
41
 
42
+ Since (free) Gradio is stateless, we had to find a way to pass down information between both the different API calls and the user calls, while still ensuring privacy. We ended up using the MCP API key as a user identifier, which allowed us to separate user data while still being able to access it when needed.
43
+
44
+ There was also the problem of having to validate the data and making sure that the user/agent is satisfied with the data before activating monitoring. We solved this by separating the whole process into multiple functions, which allowed us to validate the data before activating monitoring.
45
+
46
+ Another problem we ran into was finding a way to pass the MCP API key automatically to the API calls without having to manually input it every time. We were unavble to find a way to do this, as the server is remote and we couldn't upload an environment variable to the server.
47
 
48
  ## Future Steps
49
 
50
+ Currently, the API call results store the entire response, which can be quite large, especially for LLMs like Claude to handle (or reach conversation limits), hence the need for three different returns for monitored data. In the future, we think that we can improve this by allowing users to specify a format for the response, such as only storing the text content or a specific field in the response. This would allow users to save space and make it easier to analyze the data later on.
51
 
52
  Also, we plan to move the authentication backend off of Render, as Render spins down the backend after a period of inactivity, which can cause a lot of delay. (We used Render because Vercel would not work with our database library.)
api_client.py CHANGED
@@ -3,7 +3,7 @@ import json
3
 
4
 
5
  def parse_key_value_string(key_value_string):
6
- """Parse a key-value string into a dictionary.
7
 
8
  Parameters:
9
  - key_value_string: String with key-value pairs, one per line, separated by ':'
@@ -89,7 +89,9 @@ def call_api(
89
  x-api-key: your_api_key_here
90
  content-type: application/json
91
  additional_params: {"messages": [{"role": "user", "content": "Hello there!"}]}
92
- """ # Build params and headers dictionaries from key-value pairs
 
 
93
  params = parse_key_value_string(param_keys_values)
94
  headers = parse_key_value_string(header_keys_values)
95
 
 
3
 
4
 
5
  def parse_key_value_string(key_value_string):
6
+ """Parse a key-value string into a dictionary (meant for API arguments).
7
 
8
  Parameters:
9
  - key_value_string: String with key-value pairs, one per line, separated by ':'
 
89
  x-api-key: your_api_key_here
90
  content-type: application/json
91
  additional_params: {"messages": [{"role": "user", "content": "Hello there!"}]}
92
+ """
93
+
94
+ # Build params and headers dictionaries from key-value pairs
95
  params = parse_key_value_string(param_keys_values)
96
  headers = parse_key_value_string(header_keys_values)
97
 
api_monitor.py CHANGED
@@ -200,6 +200,7 @@ def validate_api_configuration(
200
  "config_id": None,
201
  }
202
 
 
203
  if not name or not name.strip():
204
  return {
205
  "success": False,
@@ -429,10 +430,8 @@ async def activate_monitoring(config_id, mcp_api_key):
429
  this
430
  """
431
 
432
- # using time_to_start, schedule_interval_minutes, and stop_after_hours
433
- # label using name and description
434
 
435
- # attempt to create the scheduler
436
  try:
437
  if not mcp_api_key or not mcp_api_key.strip() or mcp_api_key == "":
438
  mcp_api_key = os.getenv("MCP_API_KEY", "")
@@ -442,6 +441,7 @@ async def activate_monitoring(config_id, mcp_api_key):
442
  "message": "MCP API key is required",
443
  "config_id": None,
444
  }
 
445
  # Verify the MCP API key with the key generation server first
446
  key_verification = verify_mcp_api_key(mcp_api_key)
447
  if not key_verification["success"]:
@@ -471,7 +471,9 @@ async def activate_monitoring(config_id, mcp_api_key):
471
  "success": False,
472
  "message": "Invalid mcp_api_key. You are not authorized to activate this configuration.",
473
  "config_id": config_id,
474
- } # Extract scheduling parameters
 
 
475
  name = config.get("name", "Unknown")
476
  schedule_interval_minutes = float(config.get("schedule_interval_minutes", 20))
477
  stop_at = config.get("stop_at")
@@ -487,7 +489,9 @@ async def activate_monitoring(config_id, mcp_api_key):
487
  if not isinstance(stop_at, datetime):
488
  stop_at = datetime.fromisoformat(
489
  str(stop_at)
490
- ) # Job function to make actual API calls
 
 
491
 
492
  def api_monitoring_job():
493
  now = datetime.now()
@@ -643,7 +647,9 @@ async def activate_monitoring(config_id, mcp_api_key):
643
  except Exception as db_exc:
644
  print(
645
  f"Failed to log error to database: {db_exc}"
646
- ) # Setup AsyncIO scheduler
 
 
647
 
648
  scheduler = AsyncIOScheduler()
649
  # Schedule the API monitoring job
@@ -720,7 +726,7 @@ def retrieve_monitored_data(config_id, mcp_api_key, mode="summary"):
720
  "timestamp": "2025-06-05T15:20:00",
721
  "success": true,
722
  "error": null,
723
- "response_preview": "{'alerts': [{'type': 'tornado'}]}..." // truncated
724
  }
725
  // ... up to 5 most recent calls
726
  ],
@@ -935,7 +941,7 @@ def retrieve_monitored_data(config_id, mcp_api_key, mode="summary"):
935
  "success": item.get("is_successful", False),
936
  "response_data": item.get(
937
  "response_data"
938
- ), # Full response data
939
  "error": (
940
  item.get("error_message")
941
  if not item.get("is_successful")
@@ -967,7 +973,7 @@ def retrieve_monitored_data(config_id, mcp_api_key, mode="summary"):
967
  else None
968
  ),
969
  "response_preview": (
970
- str(item.get("response_data", ""))[:100] + "..."
971
  if item.get("response_data")
972
  else None
973
  ),
@@ -990,48 +996,4 @@ def retrieve_monitored_data(config_id, mcp_api_key, mode="summary"):
990
  "success": False,
991
  "message": f"Database connection failed: {str(e)}",
992
  "data": [],
993
- }
994
-
995
-
996
- ## testing
997
- import asyncio
998
-
999
-
1000
- async def main():
1001
-
1002
- validation_response = validate_api_configuration(
1003
- mcp_api_key=os.getenv("MCP_API_KEY"),
1004
- name="Dog Facts API",
1005
- description="Monitor random dog facts from a free API",
1006
- method="GET",
1007
- base_url="https://dogapi.dog",
1008
- endpoint="api/v2/facts",
1009
- param_keys_values="",
1010
- header_keys_values="",
1011
- additional_params="{}",
1012
- schedule_interval_minutes=0.05,
1013
- stop_after_hours=1,
1014
- start_at="",
1015
- )
1016
- print(validation_response)
1017
- print()
1018
- print()
1019
-
1020
- activate_monitoring_response = await activate_monitoring(
1021
- config_id=validation_response.get("config_id"),
1022
- mcp_api_key="os.getenv('MCP_API_KEY')",
1023
- )
1024
- print(activate_monitoring_response)
1025
- print()
1026
- print()
1027
-
1028
- await asyncio.sleep(10)
1029
- response = retrieve_monitored_data(
1030
- config_id=activate_monitoring_response.get("config_id"),
1031
- mcp_api_key=os.getenv("MCP_API_KEY"),
1032
- )
1033
- print(json.dumps(response, indent=2, default=str))
1034
-
1035
-
1036
- if __name__ == "__main__":
1037
- asyncio.run(main())
 
200
  "config_id": None,
201
  }
202
 
203
+ # Validate required parameters
204
  if not name or not name.strip():
205
  return {
206
  "success": False,
 
430
  this
431
  """
432
 
 
 
433
 
434
+ # Attempt to create the scheduler
435
  try:
436
  if not mcp_api_key or not mcp_api_key.strip() or mcp_api_key == "":
437
  mcp_api_key = os.getenv("MCP_API_KEY", "")
 
441
  "message": "MCP API key is required",
442
  "config_id": None,
443
  }
444
+
445
  # Verify the MCP API key with the key generation server first
446
  key_verification = verify_mcp_api_key(mcp_api_key)
447
  if not key_verification["success"]:
 
471
  "success": False,
472
  "message": "Invalid mcp_api_key. You are not authorized to activate this configuration.",
473
  "config_id": config_id,
474
+ }
475
+
476
+ # Extract scheduling parameters
477
  name = config.get("name", "Unknown")
478
  schedule_interval_minutes = float(config.get("schedule_interval_minutes", 20))
479
  stop_at = config.get("stop_at")
 
489
  if not isinstance(stop_at, datetime):
490
  stop_at = datetime.fromisoformat(
491
  str(stop_at)
492
+ )
493
+
494
+ # Job function to make actual API calls
495
 
496
  def api_monitoring_job():
497
  now = datetime.now()
 
647
  except Exception as db_exc:
648
  print(
649
  f"Failed to log error to database: {db_exc}"
650
+ )
651
+
652
+ # Setup AsyncIO scheduler
653
 
654
  scheduler = AsyncIOScheduler()
655
  # Schedule the API monitoring job
 
726
  "timestamp": "2025-06-05T15:20:00",
727
  "success": true,
728
  "error": null,
729
+ "response_preview": "{'alerts': [{'type': 'tornado'}]}..." // truncated to 150 characters
730
  }
731
  // ... up to 5 most recent calls
732
  ],
 
941
  "success": item.get("is_successful", False),
942
  "response_data": item.get(
943
  "response_data"
944
+ ),
945
  "error": (
946
  item.get("error_message")
947
  if not item.get("is_successful")
 
973
  else None
974
  ),
975
  "response_preview": (
976
+ str(item.get("response_data", ""))[:150] + "..."
977
  if item.get("response_data")
978
  else None
979
  ),
 
996
  "success": False,
997
  "message": f"Database connection failed: {str(e)}",
998
  "data": [],
999
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py CHANGED
@@ -165,7 +165,7 @@ retrieve_tab = gr.Interface(
165
  demo = gr.TabbedInterface(
166
  [validation_tab, scheduler_tab, retrieve_tab],
167
  ["Validate & Store", "Activate Scheduler", "Retrieve Data"],
168
- title="MCP API Monitoring System",
169
  )
170
 
171
  if __name__ == "__main__":
 
165
  demo = gr.TabbedInterface(
166
  [validation_tab, scheduler_tab, retrieve_tab],
167
  ["Validate & Store", "Activate Scheduler", "Retrieve Data"],
168
+ title="Hermes - Automated Asynchronous REST API Monitoring",
169
  )
170
 
171
  if __name__ == "__main__":
keygenServer/client/app/main/page.tsx CHANGED
@@ -114,7 +114,8 @@ export default function MainPage() {
114
  </div>
115
 
116
  {/* Main Content */}
117
- <div className="grid grid-cols-1 lg:grid-cols-2 gap-4 md:gap-6"> {/* API Key Generation */}
 
118
  <div className="neuro-card p-4 md:p-6">
119
  <h2 className="text-lg md:text-xl font-semibold text-primary mb-4">
120
  Generate MCP API Key
 
114
  </div>
115
 
116
  {/* Main Content */}
117
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-4 md:gap-6">
118
+ {/* API Key Generation */}
119
  <div className="neuro-card p-4 md:p-6">
120
  <h2 className="text-lg md:text-xl font-semibold text-primary mb-4">
121
  Generate MCP API Key
killClaude.ps1 DELETED
@@ -1,24 +0,0 @@
1
- $claudePath = "C:\Users\colem\AppData\Local\AnthropicClaude\claude.exe"
2
-
3
- # Get all processes named "claude"
4
- $processes = Get-Process -Name "claude" -ErrorAction SilentlyContinue
5
-
6
- foreach ($proc in $processes) {
7
- try {
8
- Write-Output "Killing process: $($proc.Name) (ID: $($proc.Id))"
9
- Stop-Process -Id $proc.Id -Force -ErrorAction Stop
10
- Start-Sleep -Milliseconds 300
11
- }
12
- catch {
13
- Write-Output "Process with ID $($proc.Id) already exited or could not be terminated."
14
- }
15
- }
16
-
17
- # Start the application again
18
- if (Test-Path $claudePath) {
19
- Write-Output "Restarting Claude..."
20
- Start-Process $claudePath
21
- }
22
- else {
23
- Write-Output "Error: Claude executable not found at $claudePath"
24
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
schema.sql CHANGED
@@ -49,24 +49,3 @@ INSERT INTO api_configurations (
49
  '2025-06-04T12:00:00',
50
  '2025-06-11T12:00:00'
51
  );
52
-
53
- INSERT INTO api_call_results (
54
- config_id, response_data, is_successful, error_message
55
- ) VALUES (
56
- 10101,
57
- '{"symbol":"NVDA", "price":1142.50, "timestamp":"2025-06-03T14:20:00Z"}',
58
- TRUE,
59
- NULL
60
- );
61
-
62
- INSERT INTO api_call_results (
63
- config_id, response_data, is_successful, error_message
64
- ) VALUES (
65
- 10101,
66
- NULL,
67
- FALSE,
68
- 'Timeout while contacting the API'
69
- );
70
-
71
- select * from api_configurations;
72
- select * from api_call_results;
 
49
  '2025-06-04T12:00:00',
50
  '2025-06-11T12:00:00'
51
  );