|
from typing import Any, List, Literal, Dict, Optional |
|
import httpx |
|
import traceback |
|
from mcp.server.fastmcp import FastMCP |
|
|
|
|
|
mcp = FastMCP("patent-problematic-generator-helper") |
|
|
|
|
|
ARXIV_BASE = "https://om4r932-arxiv.hf.space" |
|
DUCKDUCKGO_BASE = "https://ychkhan-ptt-endpoints.hf.space" |
|
DOC3GPPFINDER_BASE = "https://organizedprogrammers-3gppdocfinder.hf.space" |
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
@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) |
|
|
|
|
|
|
|
@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) |
|
|
|
|
|
|
|
@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") |