RDF Validation Deployment commited on
Commit
28bc89c
Β·
0 Parent(s):

Deploy BIBFRAME MCP Server - 2025-10-04 11:16:33

Browse files
Files changed (4) hide show
  1. .gitignore +33 -0
  2. README.md +167 -0
  3. app.py +516 -0
  4. requirements.txt +3 -0
.gitignore ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.so
5
+ .Python
6
+ build/
7
+ develop-eggs/
8
+ dist/
9
+ downloads/
10
+ eggs/
11
+ .eggs/
12
+ lib/
13
+ lib64/
14
+ parts/
15
+ sdist/
16
+ var/
17
+ wheels/
18
+ *.egg-info/
19
+ .installed.cfg
20
+ *.egg
21
+ .env
22
+ .venv
23
+ env/
24
+ venv/
25
+ ENV/
26
+ env.bak/
27
+ venv.bak/
28
+ .vscode/
29
+ .idea/
30
+ *.swp
31
+ *.swo
32
+ *~
33
+ .DS_Store
README.md ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # πŸ“š BIBFRAME Ontology Documentation MCP Server
2
+
3
+ A Model Context Protocol (MCP) server that provides BIBFRAME ontology documentation for LLMs like Claude. Built with Gradio, this server dynamically loads from the official BIBFRAME ontology at id.loc.gov.
4
+
5
+ ## 🌐 Live Server
6
+
7
+ **Deployed at:** https://jimfhahn-mcp4bibframe-docs.hf.space
8
+ **MCP Endpoint:** https://jimfhahn-mcp4bibframe-docs.hf.space/gradio_api/mcp/
9
+
10
+ ## ✨ Features
11
+
12
+ - βœ… **MCP Server**: Full MCP protocol support for LLM integration
13
+ - πŸ“š **Live Ontology Data**: Loads from official BIBFRAME ontology
14
+ - πŸ” **Four MCP Tools**:
15
+ - `get_property_info` - Detailed property information with examples
16
+ - `get_class_info` - Class documentation with applicable properties
17
+ - `search_ontology` - Search for properties or classes
18
+ - `get_property_usage` - Context-specific usage information
19
+ - 🌐 **Web UI**: Interactive Gradio interface for testing
20
+ - ☁️ **Deployed on HF Spaces**: Hosted for free on Hugging Face
21
+
22
+ ## πŸš€ Quick Start with Claude Desktop
23
+
24
+ Add this to your Claude Desktop configuration:
25
+
26
+ **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
27
+ **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
28
+
29
+ ```json
30
+ {
31
+ "mcpServers": {
32
+ "bibframe-docs": {
33
+ "url": "https://jimfhahn-mcp4bibframe-docs.hf.space/gradio_api/mcp/"
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ For a hosted version on Hugging Face Spaces:
40
+
41
+ ```json
42
+ {
43
+ "mcpServers": {
44
+ "bibframe-docs": {
45
+ "url": "https://YOUR-USERNAME-mcp4bibframe-docs.hf.space/gradio_api/mcp/sse"
46
+ }
47
+ }
48
+ }
49
+ ```
50
+
51
+ **Note**: Claude Desktop currently requires the `mcp-remote` bridge for SSE servers:
52
+
53
+ ```json
54
+ {
55
+ "mcpServers": {
56
+ "bibframe-docs": {
57
+ "command": "npx",
58
+ "args": [
59
+ "mcp-remote",
60
+ "http://localhost:7860/gradio_api/mcp/sse"
61
+ ]
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ ## Deploying to Hugging Face Spaces
68
+
69
+ 1. Create a new Space on [Hugging Face](https://huggingface.co/spaces)
70
+ 2. Choose "Gradio" as the SDK
71
+ 3. Push this repository to your Space
72
+ 4. The MCP server will be available at: `https://YOUR-USERNAME-SPACE-NAME.hf.space/gradio_api/mcp/sse`
73
+
74
+ ### Private Spaces
75
+
76
+ For private Spaces, add your Hugging Face token:
77
+
78
+ ```json
79
+ {
80
+ "mcpServers": {
81
+ "bibframe-docs": {
82
+ "url": "https://YOUR-SPACE.hf.space/gradio_api/mcp/sse",
83
+ "headers": {
84
+ "Authorization": "Bearer YOUR-HF-TOKEN"
85
+ }
86
+ }
87
+ }
88
+ }
89
+ ```
90
+
91
+ ## MCP Tools Reference
92
+
93
+ ### get_property_info
94
+ ```
95
+ Get detailed information about a BIBFRAME property from the official ontology.
96
+
97
+ Args:
98
+ property_uri (str): Full URI or short name (e.g., "bf:assigner" or "assigner")
99
+
100
+ Returns:
101
+ JSON with property information, domain, range, examples, and usage guidance
102
+ ```
103
+
104
+ ### get_class_info
105
+ ```
106
+ Get detailed information about a BIBFRAME class from the official ontology.
107
+
108
+ Args:
109
+ class_name (str): Class name (e.g., "Work", "bf:Work", or full URI)
110
+
111
+ Returns:
112
+ JSON with class information and applicable properties
113
+ ```
114
+
115
+ ### search_ontology
116
+ ```
117
+ Search the BIBFRAME ontology for properties or classes matching a term.
118
+
119
+ Args:
120
+ search_term (str): Term to search for
121
+ search_type (str): Type of search - "properties", "classes", or "all"
122
+
123
+ Returns:
124
+ JSON with matching properties and/or classes
125
+ ```
126
+
127
+ ### get_property_usage
128
+ ```
129
+ Get usage information for a property, optionally in the context of a specific class.
130
+
131
+ Args:
132
+ property_name (str): Property name (e.g., "assigner" or "bf:assigner")
133
+ class_name (str): Optional class context (e.g., "AdminMetadata")
134
+
135
+ Returns:
136
+ JSON with usage information and examples
137
+ ```
138
+
139
+ ## Testing with MCP Inspector
140
+
141
+ ```bash
142
+ # Install MCP Inspector
143
+ npm install -g @modelcontextprotocol/inspector
144
+
145
+ # Test your server
146
+ npx @modelcontextprotocol/inspector http://localhost:7860/gradio_api/mcp/sse
147
+ ```
148
+
149
+ ## Data Sources
150
+
151
+ - [BIBFRAME Ontology](https://id.loc.gov/ontologies/bibframe.html) - Official ontology from Library of Congress
152
+ - [LC BIBFRAME Profiles](https://github.com/lcnetdev/bfe-profiles) - Application profiles
153
+ - [DCTap Validation](https://github.com/bf-interop/DCTap) - Community validation patterns
154
+
155
+ ## Related Projects
156
+
157
+ - **For SHACL validation**: [mcp4rdf validator](https://huggingface.co/spaces/jimfhahn/mcp4rdf)
158
+ - **BIBFRAME Homepage**: https://www.loc.gov/bibframe/
159
+ - **BIBFRAME Documentation**: https://www.loc.gov/bibframe/docs/
160
+
161
+ ## License
162
+
163
+ MIT License - See LICENSE file for details
164
+
165
+ ## Contributing
166
+
167
+ Contributions welcome! Please open an issue or PR on GitHub.
app.py ADDED
@@ -0,0 +1,516 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ BIBFRAME Ontology Documentation MCP Server
4
+
5
+ Provides BIBFRAME ontology documentation via MCP tools.
6
+ Dynamically loads from the official BIBFRAME ontology at id.loc.gov.
7
+
8
+ This server focuses on ontology documentation - properties, classes, domains, ranges, and usage examples.
9
+ For SHACL validation, use the separate mcp4rdf validator.
10
+
11
+ Deploy as a Hugging Face Space for your RDF applications to query.
12
+ """
13
+
14
+ import gradio as gr
15
+ import json
16
+ import requests
17
+ from typing import Dict, List, Optional, Any
18
+ from rdflib import Graph, URIRef, Literal, Namespace, RDFS, RDF
19
+ from functools import lru_cache
20
+ import re
21
+
22
+ # Namespaces
23
+ BF = Namespace("http://id.loc.gov/ontologies/bibframe/")
24
+ BFLC = Namespace("http://id.loc.gov/ontologies/bflc/")
25
+ MADSRDF = Namespace("http://www.loc.gov/mads/rdf/v1#")
26
+
27
+ class BIBFRAMEKnowledgeBase:
28
+ """Loads and caches BIBFRAME ontology data"""
29
+
30
+ def __init__(self):
31
+ self.ontology_graph = Graph()
32
+ self.properties = {}
33
+ self.classes = {}
34
+ self._loaded = False
35
+
36
+ @lru_cache(maxsize=1)
37
+ def load_ontology(self):
38
+ """Load the BIBFRAME ontology from the official source"""
39
+ try:
40
+ print("πŸ“š Loading BIBFRAME ontology from id.loc.gov...")
41
+ self.ontology_graph.parse("http://id.loc.gov/ontologies/bibframe.rdf", format="xml")
42
+
43
+ # Extract properties
44
+ for prop in self.ontology_graph.subjects(RDF.type, RDF.Property):
45
+ if str(prop).startswith(str(BF)):
46
+ local_name = str(prop).replace(str(BF), "")
47
+ self.properties[f"bf:{local_name}"] = {
48
+ "uri": str(prop),
49
+ "label": self._get_label(prop),
50
+ "definition": self._get_comment(prop),
51
+ "domain": self._get_domains(prop),
52
+ "range": self._get_ranges(prop),
53
+ "subPropertyOf": self._get_super_properties(prop)
54
+ }
55
+
56
+ # Extract classes
57
+ for cls in self.ontology_graph.subjects(RDF.type, RDFS.Class):
58
+ if str(cls).startswith(str(BF)):
59
+ local_name = str(cls).replace(str(BF), "")
60
+ self.classes[f"bf:{local_name}"] = {
61
+ "uri": str(cls),
62
+ "label": self._get_label(cls),
63
+ "definition": self._get_comment(cls),
64
+ "subClassOf": self._get_super_classes(cls)
65
+ }
66
+
67
+ self._loaded = True
68
+ print(f"βœ… Loaded {len(self.properties)} properties and {len(self.classes)} classes")
69
+
70
+ except Exception as e:
71
+ print(f"⚠️ Error loading ontology: {e}")
72
+ print("πŸ“¦ Using minimal fallback data")
73
+ self._load_minimal_fallback()
74
+
75
+ def _load_minimal_fallback(self):
76
+ """Minimal fallback data if ontology loading fails"""
77
+ self.properties = {
78
+ "bf:assigner": {
79
+ "uri": "http://id.loc.gov/ontologies/bibframe/assigner",
80
+ "label": "Assigner",
81
+ "definition": "Entity who assigned the identifier or other metadata value",
82
+ "domain": ["http://id.loc.gov/ontologies/bibframe/AdminMetadata"],
83
+ "range": ["http://id.loc.gov/ontologies/bibframe/Agent"],
84
+ "subPropertyOf": []
85
+ }
86
+ }
87
+ self._loaded = True
88
+
89
+ def _get_label(self, resource):
90
+ return str(self.ontology_graph.value(resource, RDFS.label) or "")
91
+
92
+ def _get_comment(self, resource):
93
+ return str(self.ontology_graph.value(resource, RDFS.comment) or "")
94
+
95
+ def _get_domains(self, prop):
96
+ return [str(d) for d in self.ontology_graph.objects(prop, RDFS.domain)]
97
+
98
+ def _get_ranges(self, prop):
99
+ return [str(r) for r in self.ontology_graph.objects(prop, RDFS.range)]
100
+
101
+ def _get_super_properties(self, prop):
102
+ return [str(sp) for sp in self.ontology_graph.objects(prop, RDFS.subPropertyOf)]
103
+
104
+ def _get_super_classes(self, cls):
105
+ return [str(sc) for sc in self.ontology_graph.objects(cls, RDFS.subClassOf)]
106
+
107
+ # Global instance
108
+ kb = BIBFRAMEKnowledgeBase()
109
+ kb.load_ontology()
110
+
111
+ # Helper functions
112
+ def _simplify_uri(uri: str) -> str:
113
+ """Convert full URI to simplified form (e.g., bf:Work)"""
114
+ if not uri:
115
+ return ""
116
+ if "bibframe/" in uri:
117
+ return "bf:" + uri.split("bibframe/")[-1]
118
+ elif "rdf-schema#" in uri:
119
+ return "rdfs:" + uri.split("#")[-1]
120
+ elif "XMLSchema#" in uri:
121
+ return "xsd:" + uri.split("#")[-1]
122
+ return uri
123
+
124
+ def _generate_property_examples(prop_name: str, prop_info: Dict) -> List[str]:
125
+ """Generate RDF/XML usage examples based on property range"""
126
+ examples = []
127
+
128
+ for range_type in prop_info.get("range", []):
129
+ range_simplified = _simplify_uri(range_type)
130
+
131
+ if "Agent" in range_simplified or "Organization" in range_simplified or "Person" in range_simplified:
132
+ examples.append(f'<{prop_name} rdf:resource="http://id.loc.gov/rwo/agents/n123"/>')
133
+ examples.append(f'''<{prop_name}>
134
+ <bf:Organization>
135
+ <rdfs:label>Organization Name</rdfs:label>
136
+ </bf:Organization>
137
+ </{prop_name}>''')
138
+ elif "date" in range_simplified.lower():
139
+ examples.append(f'<{prop_name}>2024</{prop_name}>')
140
+ examples.append(f'<{prop_name} rdf:datatype="http://www.w3.org/2001/XMLSchema#date">2024-01-15</{prop_name}>')
141
+ elif "Place" in range_simplified:
142
+ examples.append(f'<{prop_name} rdf:resource="http://id.loc.gov/vocabulary/countries/nyu"/>')
143
+ examples.append(f'''<{prop_name}>
144
+ <bf:Place>
145
+ <rdfs:label>New York</rdfs:label>
146
+ </bf:Place>
147
+ </{prop_name}>''')
148
+ elif "Title" in range_simplified:
149
+ examples.append(f'''<{prop_name}>
150
+ <bf:Title>
151
+ <bf:mainTitle>Main Title Here</bf:mainTitle>
152
+ </bf:Title>
153
+ </{prop_name}>''')
154
+ elif "string" in range_simplified.lower() or "Literal" in range_simplified:
155
+ examples.append(f'<{prop_name}>Text value here</{prop_name}>')
156
+ else:
157
+ # Generic object property
158
+ examples.append(f'<{prop_name} rdf:resource="http://example.org/resource"/>')
159
+
160
+ if not examples:
161
+ # Fallback if no range specified
162
+ examples.append(f'<{prop_name}>Value</{prop_name}>')
163
+
164
+ return examples
165
+
166
+ def _generate_usage_notes(prop_name: str, prop_info: Dict) -> str:
167
+ """Generate helpful usage notes based on property metadata"""
168
+ notes = []
169
+
170
+ # Note about domains
171
+ if prop_info.get("domain"):
172
+ domains = [_simplify_uri(d) for d in prop_info["domain"]]
173
+ notes.append(f"Used with: {', '.join(domains)}")
174
+
175
+ # Note about ranges
176
+ if prop_info.get("range"):
177
+ ranges = [_simplify_uri(r) for r in prop_info["range"]]
178
+ notes.append(f"Expected values: {', '.join(ranges)}")
179
+
180
+ # Special notes for common properties
181
+ if prop_name == "bf:assigner":
182
+ notes.append("Commonly required for AdminMetadata. Often same as bf:agent or bf:descriptionModifier")
183
+ elif prop_name == "bf:adminMetadata":
184
+ notes.append("Required for Work and Instance. Contains cataloging metadata")
185
+ elif prop_name == "bf:title":
186
+ notes.append("Required for Work and Instance. Use bf:Title with bf:mainTitle")
187
+
188
+ return "; ".join(notes) if notes else "No special usage notes"
189
+
190
+ def get_property_info(property_uri: str) -> str:
191
+ """
192
+ Get detailed information about a BIBFRAME property from the official ontology.
193
+
194
+ Args:
195
+ property_uri (str): Full URI or short name (e.g., "bf:assigner" or "assigner")
196
+
197
+ Returns:
198
+ str: JSON string with property information, examples, and usage guidance
199
+ """
200
+ # Normalize property name
201
+ if not property_uri.startswith("bf:") and not property_uri.startswith("http"):
202
+ property_uri = f"bf:{property_uri}"
203
+
204
+ prop_info = kb.properties.get(property_uri, {})
205
+
206
+ if not prop_info:
207
+ # Find partial matches
208
+ matches = [k for k in kb.properties.keys() if property_uri.lower() in k.lower()]
209
+ return {
210
+ "error": f"Property '{property_uri}' not found in BIBFRAME ontology",
211
+ "suggestions": matches[:10] if matches else list(kb.properties.keys())[:10],
212
+ "total_properties": len(kb.properties)
213
+ }
214
+
215
+ # Generate examples based on range
216
+ examples = _generate_property_examples(property_uri, prop_info)
217
+
218
+ # Generate usage notes
219
+ usage_notes = _generate_usage_notes(property_uri, prop_info)
220
+
221
+ result = {
222
+ "property": property_uri,
223
+ "uri": prop_info["uri"],
224
+ "label": prop_info["label"],
225
+ "definition": prop_info["definition"],
226
+ "domain": [_simplify_uri(d) for d in prop_info["domain"]],
227
+ "range": [_simplify_uri(r) for r in prop_info["range"]],
228
+ "examples": examples,
229
+ "usage_notes": usage_notes,
230
+ "documentation_url": f"https://id.loc.gov/ontologies/bibframe.html#{property_uri.replace('bf:', '')}"
231
+ }
232
+ return json.dumps(result, indent=2)
233
+
234
+ def get_class_info(class_name: str) -> str:
235
+ """
236
+ Get detailed information about a BIBFRAME class from the official ontology.
237
+
238
+ Args:
239
+ class_name (str): Class name (e.g., "Work", "bf:Work", or full URI)
240
+
241
+ Returns:
242
+ str: JSON string with class information and typical properties
243
+ """
244
+ # Normalize class name
245
+ if not class_name.startswith("bf:") and not class_name.startswith("http"):
246
+ class_name = f"bf:{class_name}"
247
+
248
+ class_info = kb.classes.get(class_name, {})
249
+
250
+ if not class_info:
251
+ # Find partial matches
252
+ matches = [k for k in kb.classes.keys() if class_name.lower() in k.lower()]
253
+ return {
254
+ "error": f"Class '{class_name}' not found in BIBFRAME ontology",
255
+ "suggestions": matches[:10] if matches else list(kb.classes.keys())[:10],
256
+ "total_classes": len(kb.classes)
257
+ }
258
+
259
+ # Find properties that have this class in their domain
260
+ applicable_properties = []
261
+ for prop_name, prop_data in kb.properties.items():
262
+ if class_info["uri"] in prop_data.get("domain", []):
263
+ applicable_properties.append({
264
+ "property": prop_name,
265
+ "label": prop_data.get("label", ""),
266
+ "definition": prop_data.get("definition", "")
267
+ })
268
+
269
+ result = {
270
+ "class": class_name,
271
+ "uri": class_info["uri"],
272
+ "label": class_info["label"],
273
+ "definition": class_info["definition"],
274
+ "superClasses": [_simplify_uri(sc) for sc in class_info.get("subClassOf", [])],
275
+ "applicable_properties": applicable_properties[:20], # Limit to first 20
276
+ "total_properties": len(applicable_properties),
277
+ "documentation_url": f"https://id.loc.gov/ontologies/bibframe.html#{class_name.replace('bf:', '')}"
278
+ }
279
+ return json.dumps(result, indent=2)
280
+
281
+ def search_ontology(search_term: str, search_type: str = "all") -> str:
282
+ """
283
+ Search the BIBFRAME ontology for properties or classes matching a term.
284
+
285
+ Args:
286
+ search_term (str): Term to search for
287
+ search_type (str): Type of search - "properties", "classes", or "all"
288
+
289
+ Returns:
290
+ str: JSON string with matching properties and/or classes
291
+ """
292
+ results = {
293
+ "search_term": search_term,
294
+ "properties": [],
295
+ "classes": []
296
+ }
297
+
298
+ search_lower = search_term.lower()
299
+
300
+ # Search properties
301
+ if search_type in ["properties", "all"]:
302
+ for prop_name, prop_data in kb.properties.items():
303
+ if (search_lower in prop_name.lower() or
304
+ search_lower in prop_data.get("label", "").lower() or
305
+ search_lower in prop_data.get("definition", "").lower()):
306
+ results["properties"].append({
307
+ "property": prop_name,
308
+ "label": prop_data.get("label", ""),
309
+ "definition": prop_data.get("definition", "")[:100] + "..." if len(prop_data.get("definition", "")) > 100 else prop_data.get("definition", "")
310
+ })
311
+
312
+ # Search classes
313
+ if search_type in ["classes", "all"]:
314
+ for class_name, class_data in kb.classes.items():
315
+ if (search_lower in class_name.lower() or
316
+ search_lower in class_data.get("label", "").lower() or
317
+ search_lower in class_data.get("definition", "").lower()):
318
+ results["classes"].append({
319
+ "class": class_name,
320
+ "label": class_data.get("label", ""),
321
+ "definition": class_data.get("definition", "")[:100] + "..." if len(class_data.get("definition", "")) > 100 else class_data.get("definition", "")
322
+ })
323
+
324
+ # Limit results
325
+ results["properties"] = results["properties"][:20]
326
+ results["classes"] = results["classes"][:20]
327
+ results["total_found"] = len(results["properties"]) + len(results["classes"])
328
+
329
+ return json.dumps(results, indent=2)
330
+
331
+ def get_property_usage(property_name: str, class_name: str = "") -> str:
332
+ """
333
+ Get usage information for a property, optionally in the context of a specific class.
334
+
335
+ Args:
336
+ property_name (str): Property name (e.g., "assigner" or "bf:assigner")
337
+ class_name (str): Optional class context (e.g., "AdminMetadata")
338
+
339
+ Returns:
340
+ str: JSON string with usage information and examples
341
+ """
342
+ # Normalize property name
343
+ if not property_name.startswith("bf:"):
344
+ property_name = f"bf:{property_name}"
345
+
346
+ prop_info = kb.properties.get(property_name, {})
347
+
348
+ if not prop_info:
349
+ return {"error": f"Property '{property_name}' not found in BIBFRAME ontology"}
350
+
351
+ usage = {
352
+ "property": property_name,
353
+ "label": prop_info.get("label", ""),
354
+ "definition": prop_info.get("definition", ""),
355
+ "domain": [_simplify_uri(d) for d in prop_info.get("domain", [])],
356
+ "range": [_simplify_uri(r) for r in prop_info.get("range", [])],
357
+ "examples": _generate_property_examples(property_name, prop_info)
358
+ }
359
+
360
+ # Add class-specific context if provided
361
+ if class_name:
362
+ if not class_name.startswith("bf:"):
363
+ class_name = f"bf:{class_name}"
364
+
365
+ class_info = kb.classes.get(class_name, {})
366
+ if class_info:
367
+ class_uri = class_info["uri"]
368
+ if class_uri in prop_info.get("domain", []):
369
+ usage["applies_to_class"] = True
370
+ usage["context_note"] = f"{property_name} is applicable to {class_name}"
371
+ else:
372
+ usage["applies_to_class"] = False
373
+ usage["context_note"] = f"{property_name} is not typically used with {class_name}"
374
+ else:
375
+ usage["context_note"] = f"Class '{class_name}' not found in ontology"
376
+
377
+ return json.dumps(usage, indent=2)
378
+
379
+ # Create Gradio interface
380
+ def create_interface():
381
+ with gr.Blocks(title="BIBFRAME Ontology Documentation MCP Server") as demo:
382
+ gr.Markdown(f"""
383
+ # πŸ“š BIBFRAME Ontology Documentation MCP Server
384
+
385
+ **Status:** {'βœ… Loaded' if kb._loaded else '⚠️ Using fallback data'} - {len(kb.properties)} properties, {len(kb.classes)} classes
386
+
387
+ This server provides BIBFRAME ontology documentation via MCP tools.
388
+ Data is loaded from the official BIBFRAME ontology at [id.loc.gov](https://id.loc.gov/ontologies/bibframe.html).
389
+
390
+ ## Available MCP Tools:
391
+ - `get_property_info`: Get detailed information about a BIBFRAME property
392
+ - `get_class_info`: Get detailed information about a BIBFRAME class
393
+ - `search_ontology`: Search for properties or classes by term
394
+ - `get_property_usage`: Get usage information for a property in context
395
+
396
+ ## MCP Configuration:
397
+ ```json
398
+ {{
399
+ "mcpServers": {{
400
+ "bibframe-docs": {{
401
+ "url": "https://your-space.hf.space/gradio_api/mcp/"
402
+ }}
403
+ }}
404
+ }}
405
+ ```
406
+
407
+ ## Data Sources:
408
+ - [BIBFRAME Ontology](https://id.loc.gov/ontologies/bibframe.html) - Official ontology from Library of Congress
409
+ - [LC BIBFRAME Profiles](https://github.com/lcnetdev/bfe-profiles) - Application profiles
410
+ - [DCTap Validation](https://github.com/bf-interop/DCTap) - Community validation patterns
411
+ """)
412
+
413
+ with gr.Tabs():
414
+ # Tab 1: Property Lookup
415
+ with gr.Tab("Property Lookup"):
416
+ with gr.Row():
417
+ with gr.Column():
418
+ property_input = gr.Textbox(
419
+ label="Property Name",
420
+ value="assigner",
421
+ placeholder="e.g., assigner, bf:assigner, or title"
422
+ )
423
+ property_btn = gr.Button("πŸ” Look Up Property", variant="primary")
424
+ property_output = gr.Textbox(label="Property Information", lines=20)
425
+
426
+ property_btn.click(
427
+ fn=get_property_info,
428
+ inputs=[property_input],
429
+ outputs=[property_output]
430
+ )
431
+
432
+ # Tab 2: Class Lookup
433
+ with gr.Tab("Class Lookup"):
434
+ with gr.Row():
435
+ with gr.Column():
436
+ class_input = gr.Textbox(
437
+ label="Class Name",
438
+ value="Work",
439
+ placeholder="e.g., Work, AdminMetadata, or Instance"
440
+ )
441
+ class_btn = gr.Button("πŸ” Look Up Class", variant="primary")
442
+ class_output = gr.Textbox(label="Class Information", lines=20)
443
+
444
+ class_btn.click(
445
+ fn=get_class_info,
446
+ inputs=[class_input],
447
+ outputs=[class_output]
448
+ )
449
+
450
+ # Tab 3: Search
451
+ with gr.Tab("Search Ontology"):
452
+ with gr.Row():
453
+ with gr.Column():
454
+ search_input = gr.Textbox(
455
+ label="Search Term",
456
+ placeholder="e.g., title, agent, date, or admin"
457
+ )
458
+ search_type = gr.Radio(
459
+ label="Search Type",
460
+ choices=["all", "properties", "classes"],
461
+ value="all"
462
+ )
463
+ search_btn = gr.Button("πŸ” Search", variant="primary")
464
+ search_output = gr.Textbox(label="Search Results", lines=20)
465
+
466
+ search_btn.click(
467
+ fn=search_ontology,
468
+ inputs=[search_input, search_type],
469
+ outputs=[search_output]
470
+ )
471
+
472
+ # Tab 4: Property Usage
473
+ with gr.Tab("Property Usage Context"):
474
+ with gr.Row():
475
+ with gr.Column():
476
+ usage_prop_input = gr.Textbox(
477
+ label="Property Name",
478
+ value="assigner",
479
+ placeholder="e.g., assigner, title, or adminMetadata"
480
+ )
481
+ usage_class_input = gr.Textbox(
482
+ label="Class Context (optional)",
483
+ placeholder="e.g., AdminMetadata, Work, or Instance"
484
+ )
485
+ usage_btn = gr.Button("πŸ” Get Usage Info", variant="primary")
486
+ usage_output = gr.Textbox(label="Usage Information", lines=20)
487
+
488
+ usage_btn.click(
489
+ fn=get_property_usage,
490
+ inputs=[usage_prop_input, usage_class_input],
491
+ outputs=[usage_output]
492
+ )
493
+
494
+ gr.Markdown("""
495
+ ---
496
+ ### πŸ’‘ Usage Tips:
497
+ - Property names can be entered with or without the `bf:` prefix
498
+ - Search is case-insensitive and matches against names, labels, and definitions
499
+ - Use the "Property Usage Context" tab to understand how properties work with specific classes
500
+
501
+ ### πŸ”— Related Resources:
502
+ - **For SHACL validation**: Use the [mcp4rdf validator](https://huggingface.co/spaces/jimfhahn/mcp4rdf)
503
+ - **BIBFRAME Homepage**: [https://www.loc.gov/bibframe/](https://www.loc.gov/bibframe/)
504
+ - **BIBFRAME Documentation**: [https://www.loc.gov/bibframe/docs/](https://www.loc.gov/bibframe/docs/)
505
+ """)
506
+
507
+ return demo
508
+
509
+ if __name__ == "__main__":
510
+ demo = create_interface()
511
+ demo.launch(
512
+ server_name="0.0.0.0",
513
+ server_port=7860,
514
+ show_api=True,
515
+ mcp_server=True # Enable MCP server
516
+ )
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio[mcp]>=5.0.0
2
+ rdflib>=7.0.0
3
+ requests>=2.31.0