LLM + SAM Integration via MCP and W3C RDF Protocol

Thinking…

Claude-Sonnet-3.7-Reasoning

In the semantic web context:

  • RDF (Resource Description Framework) is a W3C standard for data interchange on the web
  • It uses triples (subject-predicate-object) to describe resources
  • It allows for knowledge graphs to be built and traversed

Let me create a code example that:

  1. Shows how AICYC API exports knowledge in RDF format
  2. Demonstrates how to process this RDF data
  3. Shows how MCP connects to the W3C RDF data pipes
  4. Connects Claude to SAM through this semantic layer

I’ll need to include:

  • RDF processing (using a library like rdflib in Python)
  • The W3C RDF protocol connection
  • How MCP interfaces with these RDF data pipes
  • The complete flow from Claude to MCP to RDF to SAM and back

The code example should show the actual semantic knowledge exchange through RDF, not just the REST API calls. Code is appended at end.

Claude + SAM Integration via MCP and W3C RDF Protocol

The example demonstrates how to connect Claude to the Semantic AI Model (SAM) using both the Model Context Protocol (MCP) and W3C RDF protocol for semantic knowledge exchange. Code at end

How This RDF-Enabled Integration Works

This enhanced implementation demonstrates a complete semantic knowledge exchange pipeline that leverages W3C RDF standards:

  1. W3C RDF Protocol Support:
  • The RDFConverter class translates between AICYC API JSON responses and RDF (Resource Description Framework) format
  • Uses standard W3C namespaces and the semantic triple structure (subject-predicate-object)
  • Creates a proper knowledge graph from SAM’s hierarchical data
  1. Dual-Format Knowledge Exchange:
  • Supports both JSON and RDF formats
  • The MCP server can retrieve and process knowledge in either format
  • The RDF representation preserves semantic relationships between concepts
  1. Enhanced Semantic Understanding:
  • Claude receives not just isolated facts but semantic knowledge with relationships
  • The RDF graph shows how topics relate to taxonomies, domains, and documents
  • This enables more contextual understanding and reasoning
  1. Complete Knowledge Pipeline:
  • User query → Claude → MCP → SAM (via AICYC API) → W3C RDF export → MCP → Claude → Enhanced response

This implementation shows how SAM functions as a true semantic knowledge source for AI, with the W3C RDF protocol providing the standardized semantic layer that MCP can connect to. This allows Claude to understand not just facts but the relationships between concepts, similar to how humans understand knowledge in an encyclopedia context.

Code example:

import requests import json import os from http.server import HTTPServer, BaseHTTPRequestHandler import threading import anthropic # For Claude API import rdflib # For RDF processing from rdflib import Graph, Namespace, URIRef, Literal from rdflib.namespace import RDF, RDFS, XSD # Base URL for the AICYC API (replace with actual endpoint in production) AICYC_BASE_URL = "http://localhost:8080" MCP_SERVER_PORT = 8081 # Create namespaces for our RDF data AICYC = Namespace("http://www.aicyc.org/ontology/") SCHEMA = Namespace("http://schema.org/") class RDFConverter: """Convert AICYC API responses to RDF format and vice versa""" @staticmethod def domain_to_rdf(domain_data): """Convert domain data to RDF""" g = Graph() g.bind("aicyc", AICYC) g.bind("schema", SCHEMA) domain_id = domain_data.get('id') domain_uri = URIRef(f"http://www.aicyc.org/domain/{domain_id}") g.add((domain_uri, RDF.type, AICYC.Domain)) g.add((domain_uri, SCHEMA.name, Literal(domain_data.get('name')))) g.add((domain_uri, SCHEMA.description, Literal(domain_data.get('tagLine', '')))) g.add((domain_uri, AICYC.category, Literal(domain_data.get('category', '')))) return g @staticmethod def taxonomy_to_rdf(taxonomy_data, domain_id): """Convert taxonomy data to RDF""" g = Graph() g.bind("aicyc", AICYC) g.bind("schema", SCHEMA) taxonomy_id = taxonomy_data.get('id') taxonomy_uri = URIRef(f"http://www.aicyc.org/taxonomy/{taxonomy_id}") domain_uri = URIRef(f"http://www.aicyc.org/domain/{domain_id}") g.add((taxonomy_uri, RDF.type, AICYC.Taxonomy)) g.add((taxonomy_uri, SCHEMA.name, Literal(taxonomy_data.get('name')))) g.add((taxonomy_uri, SCHEMA.description, Literal(taxonomy_data.get('description', '')))) g.add((taxonomy_uri, AICYC.belongsToDomain, domain_uri)) if 'image' in taxonomy_data: g.add((taxonomy_uri, SCHEMA.image, URIRef(taxonomy_data.get('image')))) return g @staticmethod def topic_to_rdf(topic_data): """Convert topic data to RDF""" g = Graph() g.bind("aicyc", AICYC) g.bind("schema", SCHEMA) topic_id = topic_data.get('id') topic_uri = URIRef(f"http://www.aicyc.org/topic/{topic_id}") g.add((topic_uri, RDF.type, AICYC.Topic)) g.add((topic_uri, SCHEMA.name, Literal(topic_data.get('name')))) if 'description' in topic_data: g.add((topic_uri, SCHEMA.description, Literal(topic_data.get('description')))) if 'domain' in topic_data and 'id' in topic_data['domain']: domain_uri = URIRef(f"http://www.aicyc.org/domain/{topic_data['domain']['id']}") g.add((topic_uri, AICYC.belongsToDomain, domain_uri)) if 'taxonomy' in topic_data and 'id' in topic_data['taxonomy']: taxonomy_uri = URIRef(f"http://www.aicyc.org/taxonomy/{topic_data['taxonomy']['id']}") g.add((topic_uri, AICYC.belongsToTaxonomy, taxonomy_uri)) if 'parent' in topic_data and 'id' in topic_data['parent']: parent_uri = URIRef(f"http://www.aicyc.org/topic/{topic_data['parent']['id']}") g.add((topic_uri, AICYC.hasParent, parent_uri)) if 'image' in topic_data: g.add((topic_uri, SCHEMA.image, URIRef(topic_data.get('image')))) return g @staticmethod def document_to_rdf(document_data, topic_id): """Convert document data to RDF""" g = Graph() g.bind("aicyc", AICYC) g.bind("schema", SCHEMA) doc_id = document_data.get('id') doc_uri = URIRef(f"http://www.aicyc.org/document/{doc_id}") topic_uri = URIRef(f"http://www.aicyc.org/topic/{topic_id}") g.add((doc_uri, RDF.type, AICYC.Document)) g.add((doc_uri, SCHEMA.name, Literal(document_data.get('title', '')))) g.add((doc_uri, SCHEMA.url, URIRef(document_data.get('url', '')))) g.add((doc_uri, SCHEMA.description, Literal(document_data.get('digest', '')))) g.add((doc_uri, AICYC.belongsToTopic, topic_uri)) return g # AICYC API client that supports both JSON and RDF class AICYCClient: @staticmethod def get_domains(limit=5, format="json"): """Get available knowledge domains""" response = requests.get(f"{AICYC_BASE_URL}/domains?limit={limit}") json_data = response.json() if format == "rdf": # Convert to RDF g = Graph() if '_embedded' in json_data and 'domains' in json_data['_embedded']: for domain in json_data['_embedded']['domains']: g += RDFConverter.domain_to_rdf(domain) return g else: return json_data @staticmethod def get_domain_taxonomies(domain_id, limit=5, format="json"): """Get taxonomies for a specific domain""" response = requests.get(f"{AICYC_BASE_URL}/domains/{domain_id}/taxonomies?limit={limit}") json_data = response.json() if format == "rdf": # Convert to RDF g = Graph() if '_embedded' in json_data and 'taxonomies' in json_data['_embedded']: for taxonomy in json_data['_embedded']['taxonomies']: g += RDFConverter.taxonomy_to_rdf(taxonomy, domain_id) return g else: return json_data @staticmethod def get_taxonomy_topics(taxonomy_id, limit=5, format="json"): """Get topics for a specific taxonomy""" response = requests.get(f"{AICYC_BASE_URL}/taxonomies/{taxonomy_id}/topics?limit={limit}") json_data = response.json() if format == "rdf": # Convert to RDF g = Graph() if '_embedded' in json_data and 'topics' in json_data['_embedded']: for topic in json_data['_embedded']['topics']: g += RDFConverter.topic_to_rdf(topic) return g else: return json_data @staticmethod def get_topic_documents(topic_id, limit=5, format="json"): """Get documents for a specific topic""" response = requests.get(f"{AICYC_BASE_URL}/topics/{topic_id}/documents?limit={limit}") json_data = response.json() if format == "rdf": # Convert to RDF g = Graph() if '_embedded' in json_data and 'documents' in json_data['_embedded']: for document in json_data['_embedded']['documents']: g += RDFConverter.document_to_rdf(document, topic_id) return g else: return json_data @staticmethod def search(term, limit=5, format="json"): """Search across the knowledge base""" response = requests.get(f"{AICYC_BASE_URL}/search?term={term}&limit={limit}") json_data = response.json() if format == "rdf": # Convert search results to RDF - simplified for this example g = Graph() if '_embedded' in json_data and 'topics' in json_data['_embedded']: for topic in json_data['_embedded']['topics']: g += RDFConverter.topic_to_rdf(topic) return g else: return json_data # MCP Server to connect Claude to SAM's RDF knowledge class MCPRequestHandler(BaseHTTPRequestHandler): def do_POST(self): """Handle POST requests from Claude""" content_length = int(self.headers['Content-Length']) post_data = self.rfile.read(content_length) request = json.loads(post_data.decode('utf-8')) # Process the request based on the path response_data = {} if self.path == '/query': # Claude is requesting information on a specific topic query = request.get('query', '') format_preference = request.get('format', 'json') # Default to JSON response_data = self.handle_query(query, format_preference) # Send response back to Claude self.send_response(200) self.send_header('Content-type', 'application/json') self.end_headers() self.wfile.write(json.dumps(response_data).encode()) def handle_query(self, query, format_preference): """Process a knowledge query from Claude using RDF or JSON""" # First search for relevant topics if format_preference == 'rdf': # Get RDF data rdf_graph = AICYCClient.search(query, limit=3, format="rdf") # Extract topic information from RDF topics = [] for s, p, o in rdf_graph.triples((None, RDF.type, AICYC.Topic)): topic_uri = str(s) topic_id = topic_uri.split('/')[-1] # Get the name name = "" for _, _, name_literal in rdf_graph.triples((s, SCHEMA.name, None)): name = str(name_literal) topics.append({"id": topic_id, "name": name}) # If we found topics, get documents for the first one documents = [] if topics: topic_id = topics[0]['id'] # Get documents in RDF format doc_graph = AICYCClient.get_topic_documents(topic_id, limit=3, format="rdf") # Extract document information for s, p, o in doc_graph.triples((None, RDF.type, AICYC.Document)): doc_uri = str(s) doc_id = doc_uri.split('/')[-1] # Get document details title = "" url = "" summary = "" for _, _, title_literal in doc_graph.triples((s, SCHEMA.name, None)): title = str(title_literal) for _, _, url_uri in doc_graph.triples((s, SCHEMA.url, None)): url = str(url_uri) for _, _, desc_literal in doc_graph.triples((s, SCHEMA.description, None)): summary = str(desc_literal) documents.append({ "id": doc_id, "title": title, "url": url, "summary": summary }) # Include serialized RDF in the response for Claude serialized_rdf = rdf_graph.serialize(format="turtle").decode('utf-8') return { "query": query, "topics_found": len(topics), "documents": documents, "rdf_data": serialized_rdf } else: # Use JSON (default format) search_results = AICYCClient.search(query, limit=3) # If we found topics, get documents for the first one documents = [] if '_embedded' in search_results and 'topics' in search_results['_embedded']: topics = search_results['_embedded']['topics'] if topics: topic_id = topics[0]['id'] # Get documents for this topic doc_results = AICYCClient.get_topic_documents(topic_id, limit=3) if '_embedded' in doc_results and 'documents' in doc_results['_embedded']: documents = doc_results['_embedded']['documents'] # Format the response for Claude return { "query": query, "topics_found": len(search_results.get('_embedded', {}).get('topics', [])), "documents": [ { "title": doc.get('title', ''), "url": doc.get('url', ''), "summary": doc.get('digest', '') } for doc in documents ] } # Start the MCP server in a separate thread def start_mcp_server(): server = HTTPServer(('localhost', MCP_SERVER_PORT), MCPRequestHandler) print(f"MCP Server started on port {MCP_SERVER_PORT}") server_thread = threading.Thread(target=server.serve_forever) server_thread.daemon = True server_thread.start() return server # Claude client that connects to the MCP server class ClaudeClient: def __init__(self, api_key): self.client = anthropic.Anthropic(api_key=api_key) def query_with_sam_knowledge(self, user_query, format_preference="json"): """Query Claude with knowledge from SAM via MCP and W3C RDF""" # First, get relevant knowledge from SAM through MCP mcp_response = requests.post( f"http://localhost:{MCP_SERVER_PORT}/query", json={"query": user_query, "format": format_preference} ) sam_knowledge = mcp_response.json() # Format knowledge for Claude knowledge_context = self._format_knowledge(sam_knowledge, format_preference) # Query Claude with the additional knowledge context response = self.client.messages.create( model="claude-3-sonnet-20240229", max_tokens=1000, system=f"You are a helpful assistant with access to the Semantic AI Model (SAM) knowledge base through both JSON and W3C RDF protocols. Use this knowledge to enhance your responses.", messages=[ {"role": "user", "content": f"I need information about {user_query}. Please use your knowledge to help me."}, {"role": "assistant", "content": "I'll help you with that. Let me check my knowledge base."}, {"role": "user", "content": f"Here's relevant information from the SAM knowledge base via {format_preference.upper()} format:\n\n{knowledge_context}\n\nBased on this information and your own knowledge, please provide a comprehensive answer."} ] ) return response.content[0].text def _format_knowledge(self, sam_knowledge, format_preference): """Format SAM knowledge for Claude consumption""" formatted = f"Query: {sam_knowledge['query']}\n\n" if format_preference == "rdf" and "rdf_data" in sam_knowledge: formatted += "Knowledge Graph (RDF/Turtle format):\n```\n" formatted += sam_knowledge['rdf_data'] formatted += "\n```\n\n" if not sam_knowledge['documents']: return formatted + "No specific information found in the knowledge base." formatted += "Relevant information:\n\n" for i, doc in enumerate(sam_knowledge['documents'], 1): formatted += f"Source {i}:\n" formatted += f"Title: {doc['title']}\n" formatted += f"Summary: {doc['summary']}\n" formatted += f"URL: {doc['url']}\n\n" return formatted # Demonstrate the complete integration flow def demonstrate_claude_sam_integration(): # Start the MCP server server = start_mcp_server() # Create a Claude client claude = ClaudeClient(api_key=os.environ.get("ANTHROPIC_API_KEY", "your_api_key_here")) # Example queries to demonstrate knowledge exchange example_queries = [ "frequent flyer programs", "astronomy black holes", "health information about arthritis" ] # Demonstrate both JSON and RDF formats for format_preference in ["json", "rdf"]: print(f"\n\n--- USING {format_preference.upper()} FORMAT ---\n") for query in example_queries: print(f"\n--- QUERY: {query} ---\n") response = claude.query_with_sam_knowledge(query, format_preference) print(response) print("\n" + "-"*50) # Note: In a real application, you would properly shut down the server if __name__ == "__main__": demonstrate_claude_sam_integration()

End Code

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.