MCPGroqChat / server.py
Omar ID EL MOUMEN
Fix 3
d65c307
from typing import Any, List, Literal, Dict, Optional
import httpx
import traceback
from mcp.server.fastmcp import FastMCP
# Initialize FastMCP server
mcp = FastMCP("patent-problematic-generator-helper")
# API used
ARXIV_BASE = "https://om4r932-arxiv.hf.space"
DUCKDUCKGO_BASE = "https://ychkhan-ptt-endpoints.hf.space"
DOC3GPPFINDER_BASE = "https://organizedprogrammers-3gppdocfinder.hf.space"
# Request function
async def post_data_to_api(url, data = None):
if data is None or data == {}:
return (None, "")
headers = {"Accept": "application/json"}
async with httpx.AsyncClient(verify=False, timeout=180) as client:
try:
response = await client.post(url, headers=headers, json=data)
print(response)
response.raise_for_status()
return response.json()
except Exception as e:
traceback.print_exception(e)
return (None, e)
async def fake_post_data_to_api(url, params = None):
if params is None or params == {}:
return (None, "")
headers = {"Accept": "application/json"}
async with httpx.AsyncClient(verify=False, timeout=180) as client:
try:
response = await client.post(url, headers=headers, params=params)
print(response)
response.raise_for_status()
return response.json()
except Exception as e:
traceback.print_exception(e)
return (None, e)
async def get_data_from_api(url):
headers = {"Accept": "application/json"}
async with httpx.AsyncClient(verify=False, timeout=180) as client:
try:
response = await client.get(url, headers=headers)
print(response)
response.raise_for_status()
return response.json()
except Exception as e:
traceback.print_exception(e)
return (None, e)
# Tools
# arXiv
@mcp.tool()
async def get_arxiv_publications(keywords: str, limit: int):
"""
Search arXiv publications based on keywords and a limit of documents printed
Arguments available: keywords: string [mandatory], limit: integer [mandatory, default = 5]
"""
endpoint = ARXIV_BASE + "/search"
data = await post_data_to_api(endpoint, {"keyword": keywords, "limit": limit})
if isinstance(data, tuple) and data[0] is None:
return f"An error has occured while getting publications: {data[1]}"
if data["error"]:
return data["message"]
if len(data) < 1:
return "No publications has been found"
results = data["message"]
output = []
for pub, metadata in results.items():
output.append(f"arXiv pub ID: {pub}\nTitle: {metadata['title']}\nAuthors: {metadata['authors']}\nPublished on: {metadata['date']}\nAbstract: {metadata['abstract']}\nPDF URL: {metadata['pdf']}\n")
return "-\n".join(output)
# 3GPP Doc Finder
@mcp.tool()
async def get_document_url(doc_id: str, release: int = None):
"""
Find 3GPP document (TSG docs, specifications or workshop files) only by their ID [note that it will only work with keywords] (and release if it's a specification only) and return their position via a URL (and a scope if it's a specification)
Arguments available: doc_id: string [mandatory], release: integer [optional for every case]
"""
endpoint = DOC3GPPFINDER_BASE + "/find"
data = await post_data_to_api(endpoint, {"doc_id": doc_id, "release": release})
if isinstance(data, tuple) and data[0] is None:
return f"An error while searching publications: {data[1]}"
output = f'The document {doc_id} is available via this URL : {data.get("url", "No URL found !")}. '
output += f'\nThe scope of the document: {data["scope"]}' if data.get("scope", None) is not None or data.get("scope", None) == "" else ""
return output
@mcp.tool()
async def search_specs(keywords: str, limit: int, release: str|None = None, wg: str|None = None, spec_type: str|None = None, mode: str = "and"):
"""
Search 3GPP specifications only by their keywords [note that it will only work with keywords](and some filters [see kwargs field])
Arguments available: keywords: string [mandatory, separated by space], limit [mandatory, default = 5], release: string [optional, filter] (the release version (e.g. 18, 9, 19, ...), generally the first number of the full version), wg: string [optional, filter] = working group (S1, C4, SP, ...), spec_type: string [optional, filter] (either TS or TR), mode [mandatory, default = 'and'] = search mode (and = all keywords must be in the search, or = at least one keyword in the search)
Do not include null parameters into the POST body
"""
endpoint = DOC3GPPFINDER_BASE + "/search-spec"
body = {
"keywords": keywords,
"mode": mode
}
if release is not None:
body['release'] = release
if wg is not None:
body['wg'] = wg
if spec_type is not None:
body['spec_type'] = spec_type
data = await post_data_to_api(endpoint, body)
if isinstance(data, tuple) and data[0] is None:
return f"An error has occured while searching specifications"
results = data['results'][:min(len(data['results'])-1, limit)]
output = []
for spec in results:
x = f"Found specification number {spec['id']} version {spec['release']}"
if spec['scope'] != "":
x += f" where {spec['scope'].lower()}\n"
else:
x += "\n"
output.append(x)
return "-\n".join(output)
@mcp.tool()
async def get_multiple_documents_url(doc_ids: List[str], release: int = None):
"""
[BATCH] Search multiple 3GPP documents (TSG docs, specifications or workshop files) [note that it will only work with document ID] (and release if it's a specification only) and return only their position via a URL
Arguments available: doc_ids: list of string [mandatory], release: integer [optional for every case]
"""
endpoint = DOC3GPPFINDER_BASE + "/batch"
body = {
"doc_ids": doc_ids,
}
if release is not None:
body['release'] = release
data = await post_data_to_api(endpoint, body)
if isinstance(data, tuple) and data[0] is None:
return f"An error while searching publications: {data[1]}"
results = data["results"]
output = []
for doc_id, url in results.items():
output.append(f'The document {doc_id} is available via this URL: {url}\n')
return "-\n".join(output)
# PTT Endpoints
@mcp.tool()
async def search_documents_web(query: str, data_type: str = "web", limit: int = 5):
"""
Search on the Web (thanks to DuckDuckGo) documents based on the user's query
Arguments available: query: string [mandatory], data_type: string [optional, either 'pdf', 'patent' or 'web', default = 'web'], limit: integer [optional, default = 5]
"""
endpoint = DUCKDUCKGO_BASE + "/search"
data = await fake_post_data_to_api(endpoint, {"query": query, "data_type": data_type, 'max_references': limit})
if isinstance(data, tuple) and data[0] is None:
return f"An error while searching publications: {data[1]}"
results = data["results"]
output = []
for ref in results:
output.append(f"Title: {ref['title']}\nBody: {ref['body']}\nURL: {ref['url']}")
return "-\n".join(output)
if __name__ == "__main__":
mcp.run(transport="stdio")