Skip to main content
Glama
rdf_portal.py11.9 kB
import httpx import os import yaml import sys from typing import Annotated, List, Dict, Any, Literal from pydantic import Field from .server import * @mcp.resource("resource://boilerplate") def boilerplate() -> str: return "Hello! I don't know why this is here. But, the server doesn't work without it." @mcp.tool(name="RDF_Portal_Guide", description="A general guideline for using the RDF Portal.") def rdf_portal_guide() -> str: """ A general guideline for using the RDF Portal. Always use this before constructing any SPARQL queries for the database, and strictly follow the instructions provided there. Returns: str: The content of the RDF Portal Guide. """ toolcall_log("rdf_portal_guide") with open(RDF_PORTAL_GUIDE, "r", encoding="utf-8") as file: prompt = file.read() return prompt # --- Tools for RDF Portal --- # @mcp.tool() async def get_sparql_endpoints() -> Dict[str,str]: """ Get the available SPARQL endpoints for RDF Portal. Returns: Dict[str,str]: Dictionary of dbname-URL pairs. """ toolcall_log("get_sparql_endpoints") return SPARQL_ENDPOINT @mcp.tool(enabled=False) async def get_void( graph_uri: Annotated[str,Field(description="Graph URI to explore. Use `get_graph_list` to get appropriate graph URI.")] ) -> list: """ Get VoID data for the given graph URI. Args: graph_uri (str): Graph URI to explore. Use `get_graph_list` to get appropriate graph URI. Returns: str: A JSON-formatted string containing the VoID data. """ toolcall_log("get_void") query=f""" PREFIX void: <http://rdfs.org/ns/void#> PREFIX sd: <http://www.w3.org/ns/sparql-service-description#> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> SELECT DISTINCT ?total_count ?class_count ?property_count ?class_name ?class_triple_count ?property_name ?property_triple_count WHERE {{ VALUES ?gname {{ <{graph_uri}> }} [ a sd:Service ; sd:defaultDataset [ a sd:Dataset ; sd:namedGraph [ sd:name ?gname ; a sd:NamedGraph ; sd:endpoint ?ep_url ; sd:graph [ a void:Dataset ; void:triples ?total_count ; void:classes ?class_count ; void:properties ?property_count ; void:distinctObjects ?uniq_object_count ; void:distinctSubjects ?uniq_subject_count ; void:classPartition [ void:class ?class_name ; void:entities ?class_triple_count ] ; void:propertyPartition [ void:property ?property_name ; void:triples ?property_triple_count ] ] ] ] ] . }} """ async with httpx.AsyncClient() as client: response = await client.post( "https://plod.dbcls.jp/repositories/RDFPortal_VoID2", data={"query": query}, headers={"Accept": "application/sparql-results+json"} ) response.raise_for_status() bindings = response.json()["results"]["bindings"] if not bindings: return [] results = [{key: binding[key]["value"] for key in binding} for binding in bindings] return results @mcp.tool( enabled=True, name="run_sparql", description="Run a SPARQL query on a specific RDF database." ) async def run_sparql( sparql_query: Annotated[str, Field(description="The SPARQL query to execute")], dbname: Annotated[str, Field(description=DBNAME_DESCRIPTION)] ) -> str: """ Run a SPARQL query on a specific RDF database. Use `get_MIE_file()` to understand the RDF graph structure of the database. Args: sparql_query (str): The SPARQL query to execute. dbname (str): The name of the database to query. Supported values are {', '.join(SPARQL_ENDPOINT_KEYS)}. Returns: str: CSV-formatted results of the SPARQL query. """ toolcall_log("run_sparql") return await execute_sparql(sparql_query, dbname) # --- Tools for exploring RDF databases --- @mcp.tool( enabled=False, name="get_class_list", description="Get a list of classes in the RDF database that match the given URI." ) async def get_class_list( dbname: Annotated[str, Field(description=DBNAME_DESCRIPTION)], uri: Annotated[str, Field(description="The URI to match classes. `http://...`")] ) -> str: f""" Get a list of classes in the RDF database that match the given URI. Args: dbname (str): The name of the database to query. Supported values are {', '.join(SPARQL_ENDPOINT.keys())}. uri (str): The URI to match classes. Returns: list: The list of classes. """ toolcall_log("get_class_list") sparql_query = f""" PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> PREFIX owl: <http://www.w3.org/2002/07/owl#> SELECT DISTINCT ?class WHERE {{ ?class a owl:Class . FILTER STRSTARTS(STR(?class), "{uri}") }} LIMIT 100 """ return await execute_sparql(sparql_query, dbname) @mcp.tool( enabled=False, name="get_property_list", description="Get a list of properties in the RDF database that match the given URI." ) async def get_property_list( dbname: Annotated[str, Field(description=DBNAME_DESCRIPTION)], uri: Annotated[str, Field(description="The URI to match properties. `http://...`")] ) -> str: f""" Get a list of properties in the RDF database that match the given URI. Args: dbname (str): The name of the database to query. Supported values are {', '.join(SPARQL_ENDPOINT.keys())}. uri (str): The URI to match properties. Returns: list: The list of properties. """ toolcall_log("get_property_list") sparql_query = f""" PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> PREFIX owl: <http://www.w3.org/2002/07/owl#> SELECT DISTINCT ?property WHERE {{ ?property a ?proptype . ?proptype rdfs:subClassOf rdf:Property . FILTER STRSTARTS(STR(?property), "{uri}") }} LIMIT 100 """ return await execute_sparql(sparql_query, dbname) @mcp.tool( enabled=True, name="get_graph_list", description="Get a list of named graphs in a specific RDF database." ) async def get_graph_list( dbname: Annotated[str, Field(description=DBNAME_DESCRIPTION)] ) -> str: f""" Get a list of named graphs in a specific RDF database. Args: dbname (str): The name of the database for which to retrieve the named graphs. Supported values are {', '.join(SPARQL_ENDPOINT.keys())}. Returns: str: CSV-formatted list of named graphs. """ toolcall_log("get_graph_list") sparql_query = ''' SELECT DISTINCT ?graph WHERE { GRAPH ?graph { ?s ?p ?o . } }''' return await execute_sparql(sparql_query, dbname) @mcp.tool( enabled=True, name="get_MIE_file", description="Get the MIE file containing the ShEx schema, RDF and SPARQL examples of a specific RDF database. Use this before constructing any SPARQL queries for the database." ) async def get_MIE_file( dbname: Annotated[str, Field(description=DBNAME_DESCRIPTION)] ) -> str: f""" Get the MIE file containing the ShEx schema, RDF and SPARQL examples of a specific RDF database in YAML format, which can be used as a hint to build SPARQL queries. Args: dbname (str): The name of the database for which to retrieve the shape expression. Supported values are {', '.join(SPARQL_ENDPOINT.keys())}." Returns: str: The MIE file containing the RDF schema information in YAML format. """ toolcall_log("get_MIE_file") mie_file = MIE_DIR + "/" + dbname + ".yaml" drop_keys = [] # drop_keys += ["data_statistics", "architectural_notes"] # drop_keys += ["validation_notes"] if not os.path.exists(mie_file): return f"Error: The MIE file for '{dbname}' was not found." try: with open(mie_file, "r", encoding="utf-8") as file: content = yaml.safe_load(file) content2 = {} if isinstance(content, dict): for key, value in content.items(): if key not in drop_keys: content2[key] = value yaml_dump = yaml.dump(content2, sort_keys=False) else: # If not a dictionary, just dump the original content yaml_dump = yaml.dump(content, sort_keys=False) response_text = f"""Content-type: application/yaml; charset=utf-8 {yaml_dump}""" return response_text except Exception as e: return f"Error reading MIE file for '{dbname}': {e}" @mcp.tool( enabled=True, name="list_databases", description="List available databases and their descriptions." ) def list_databases() -> List[Dict[str, Any]]: """ Reads all YAML files in a given directory and extracts the title and description from the 'schema_info' section. Returns: A list of dictionaries, each containing schema info for a file. """ toolcall_log("list_databases") resources_dir = MIE_DIR if not os.path.isdir(resources_dir): print(f"Error: Directory '{resources_dir}' not found.", file=sys.stderr) return [] all_schemas_info = [] for db_name in sorted(SPARQL_ENDPOINT.keys()): filename = db_name + ".yaml" file_path = os.path.join(resources_dir, filename) try: with open(file_path, "r", encoding="utf-8") as f: data = yaml.safe_load(f) if not isinstance(data, dict): raise yaml.YAMLError("YAML file is not a dictionary.") schema_info = data.get("schema_info") if not isinstance(schema_info, dict): raise yaml.YAMLError("'schema_info' section not found or not a dictionary.") title = schema_info.get("title") description = schema_info.get("description") all_schemas_info.append({ "database": db_name, "title": title or "No title found.", "description": description or "No description found.", }) except yaml.YAMLError as e: all_schemas_info.append( { "database": db_name, "title": "No title found.", "description": f"Error processing YAML file: {e}", }) except (IOError, OSError) as e: all_schemas_info.append( { "database": db_name, "title": "No title found.", "description": f"Error reading file: {e}", }) return all_schemas_info @mcp.tool( enabled=True, description="Get an example SPARQL query for a specific RDF database.", name="get_sparql_example" ) def get_sparql_example( dbname: Annotated[str, Field(description=DBNAME_DESCRIPTION)] ) -> str: """ Read the file in SPARQL_EXAMPLES/{dbname}.rq and return the content. Args: dbname (str): The name of the database for which to retrieve the SPARQL example. Returns: str: The content of the SPARQL example file, or an error message if not found. """ toolcall_log("get_sparql_example") example_file = os.path.join(SPARQL_EXAMPLES, f"{dbname}.rq") if not os.path.exists(example_file): return f"Error: The SPARQL example file for '{dbname}' was not found at '{example_file}'." try: with open(example_file, "r", encoding="utf-8") as file: return file.read() except Exception as e: return f"Error reading SPARQL example file for '{dbname}': {e}"

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/arkinjo/togo-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server