remdb 0.2.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of remdb might be problematic. Click here for more details.

Files changed (187) hide show
  1. rem/__init__.py +2 -0
  2. rem/agentic/README.md +650 -0
  3. rem/agentic/__init__.py +39 -0
  4. rem/agentic/agents/README.md +155 -0
  5. rem/agentic/agents/__init__.py +8 -0
  6. rem/agentic/context.py +148 -0
  7. rem/agentic/context_builder.py +329 -0
  8. rem/agentic/mcp/__init__.py +0 -0
  9. rem/agentic/mcp/tool_wrapper.py +107 -0
  10. rem/agentic/otel/__init__.py +5 -0
  11. rem/agentic/otel/setup.py +151 -0
  12. rem/agentic/providers/phoenix.py +674 -0
  13. rem/agentic/providers/pydantic_ai.py +572 -0
  14. rem/agentic/query.py +117 -0
  15. rem/agentic/query_helper.py +89 -0
  16. rem/agentic/schema.py +396 -0
  17. rem/agentic/serialization.py +245 -0
  18. rem/agentic/tools/__init__.py +5 -0
  19. rem/agentic/tools/rem_tools.py +231 -0
  20. rem/api/README.md +420 -0
  21. rem/api/main.py +324 -0
  22. rem/api/mcp_router/prompts.py +182 -0
  23. rem/api/mcp_router/resources.py +536 -0
  24. rem/api/mcp_router/server.py +213 -0
  25. rem/api/mcp_router/tools.py +584 -0
  26. rem/api/routers/auth.py +229 -0
  27. rem/api/routers/chat/__init__.py +5 -0
  28. rem/api/routers/chat/completions.py +281 -0
  29. rem/api/routers/chat/json_utils.py +76 -0
  30. rem/api/routers/chat/models.py +124 -0
  31. rem/api/routers/chat/streaming.py +185 -0
  32. rem/auth/README.md +258 -0
  33. rem/auth/__init__.py +26 -0
  34. rem/auth/middleware.py +100 -0
  35. rem/auth/providers/__init__.py +13 -0
  36. rem/auth/providers/base.py +376 -0
  37. rem/auth/providers/google.py +163 -0
  38. rem/auth/providers/microsoft.py +237 -0
  39. rem/cli/README.md +455 -0
  40. rem/cli/__init__.py +8 -0
  41. rem/cli/commands/README.md +126 -0
  42. rem/cli/commands/__init__.py +3 -0
  43. rem/cli/commands/ask.py +565 -0
  44. rem/cli/commands/configure.py +423 -0
  45. rem/cli/commands/db.py +493 -0
  46. rem/cli/commands/dreaming.py +324 -0
  47. rem/cli/commands/experiments.py +1124 -0
  48. rem/cli/commands/mcp.py +66 -0
  49. rem/cli/commands/process.py +245 -0
  50. rem/cli/commands/schema.py +183 -0
  51. rem/cli/commands/serve.py +106 -0
  52. rem/cli/dreaming.py +363 -0
  53. rem/cli/main.py +88 -0
  54. rem/config.py +237 -0
  55. rem/mcp_server.py +41 -0
  56. rem/models/core/__init__.py +49 -0
  57. rem/models/core/core_model.py +64 -0
  58. rem/models/core/engram.py +333 -0
  59. rem/models/core/experiment.py +628 -0
  60. rem/models/core/inline_edge.py +132 -0
  61. rem/models/core/rem_query.py +243 -0
  62. rem/models/entities/__init__.py +43 -0
  63. rem/models/entities/file.py +57 -0
  64. rem/models/entities/image_resource.py +88 -0
  65. rem/models/entities/message.py +35 -0
  66. rem/models/entities/moment.py +123 -0
  67. rem/models/entities/ontology.py +191 -0
  68. rem/models/entities/ontology_config.py +131 -0
  69. rem/models/entities/resource.py +95 -0
  70. rem/models/entities/schema.py +87 -0
  71. rem/models/entities/user.py +85 -0
  72. rem/py.typed +0 -0
  73. rem/schemas/README.md +507 -0
  74. rem/schemas/__init__.py +6 -0
  75. rem/schemas/agents/README.md +92 -0
  76. rem/schemas/agents/core/moment-builder.yaml +178 -0
  77. rem/schemas/agents/core/rem-query-agent.yaml +226 -0
  78. rem/schemas/agents/core/resource-affinity-assessor.yaml +99 -0
  79. rem/schemas/agents/core/simple-assistant.yaml +19 -0
  80. rem/schemas/agents/core/user-profile-builder.yaml +163 -0
  81. rem/schemas/agents/examples/contract-analyzer.yaml +317 -0
  82. rem/schemas/agents/examples/contract-extractor.yaml +134 -0
  83. rem/schemas/agents/examples/cv-parser.yaml +263 -0
  84. rem/schemas/agents/examples/hello-world.yaml +37 -0
  85. rem/schemas/agents/examples/query.yaml +54 -0
  86. rem/schemas/agents/examples/simple.yaml +21 -0
  87. rem/schemas/agents/examples/test.yaml +29 -0
  88. rem/schemas/agents/rem.yaml +128 -0
  89. rem/schemas/evaluators/hello-world/default.yaml +77 -0
  90. rem/schemas/evaluators/rem/faithfulness.yaml +219 -0
  91. rem/schemas/evaluators/rem/lookup-correctness.yaml +182 -0
  92. rem/schemas/evaluators/rem/retrieval-precision.yaml +199 -0
  93. rem/schemas/evaluators/rem/retrieval-recall.yaml +211 -0
  94. rem/schemas/evaluators/rem/search-correctness.yaml +192 -0
  95. rem/services/__init__.py +16 -0
  96. rem/services/audio/INTEGRATION.md +308 -0
  97. rem/services/audio/README.md +376 -0
  98. rem/services/audio/__init__.py +15 -0
  99. rem/services/audio/chunker.py +354 -0
  100. rem/services/audio/transcriber.py +259 -0
  101. rem/services/content/README.md +1269 -0
  102. rem/services/content/__init__.py +5 -0
  103. rem/services/content/providers.py +806 -0
  104. rem/services/content/service.py +657 -0
  105. rem/services/dreaming/README.md +230 -0
  106. rem/services/dreaming/__init__.py +53 -0
  107. rem/services/dreaming/affinity_service.py +336 -0
  108. rem/services/dreaming/moment_service.py +264 -0
  109. rem/services/dreaming/ontology_service.py +54 -0
  110. rem/services/dreaming/user_model_service.py +297 -0
  111. rem/services/dreaming/utils.py +39 -0
  112. rem/services/embeddings/__init__.py +11 -0
  113. rem/services/embeddings/api.py +120 -0
  114. rem/services/embeddings/worker.py +421 -0
  115. rem/services/fs/README.md +662 -0
  116. rem/services/fs/__init__.py +62 -0
  117. rem/services/fs/examples.py +206 -0
  118. rem/services/fs/examples_paths.py +204 -0
  119. rem/services/fs/git_provider.py +935 -0
  120. rem/services/fs/local_provider.py +760 -0
  121. rem/services/fs/parsing-hooks-examples.md +172 -0
  122. rem/services/fs/paths.py +276 -0
  123. rem/services/fs/provider.py +460 -0
  124. rem/services/fs/s3_provider.py +1042 -0
  125. rem/services/fs/service.py +186 -0
  126. rem/services/git/README.md +1075 -0
  127. rem/services/git/__init__.py +17 -0
  128. rem/services/git/service.py +469 -0
  129. rem/services/phoenix/EXPERIMENT_DESIGN.md +1146 -0
  130. rem/services/phoenix/README.md +453 -0
  131. rem/services/phoenix/__init__.py +46 -0
  132. rem/services/phoenix/client.py +686 -0
  133. rem/services/phoenix/config.py +88 -0
  134. rem/services/phoenix/prompt_labels.py +477 -0
  135. rem/services/postgres/README.md +575 -0
  136. rem/services/postgres/__init__.py +23 -0
  137. rem/services/postgres/migration_service.py +427 -0
  138. rem/services/postgres/pydantic_to_sqlalchemy.py +232 -0
  139. rem/services/postgres/register_type.py +352 -0
  140. rem/services/postgres/repository.py +337 -0
  141. rem/services/postgres/schema_generator.py +379 -0
  142. rem/services/postgres/service.py +802 -0
  143. rem/services/postgres/sql_builder.py +354 -0
  144. rem/services/rem/README.md +304 -0
  145. rem/services/rem/__init__.py +23 -0
  146. rem/services/rem/exceptions.py +71 -0
  147. rem/services/rem/executor.py +293 -0
  148. rem/services/rem/parser.py +145 -0
  149. rem/services/rem/queries.py +196 -0
  150. rem/services/rem/query.py +371 -0
  151. rem/services/rem/service.py +527 -0
  152. rem/services/session/README.md +374 -0
  153. rem/services/session/__init__.py +6 -0
  154. rem/services/session/compression.py +360 -0
  155. rem/services/session/reload.py +77 -0
  156. rem/settings.py +1235 -0
  157. rem/sql/002_install_models.sql +1068 -0
  158. rem/sql/background_indexes.sql +42 -0
  159. rem/sql/install_models.sql +1038 -0
  160. rem/sql/migrations/001_install.sql +503 -0
  161. rem/sql/migrations/002_install_models.sql +1202 -0
  162. rem/utils/AGENTIC_CHUNKING.md +597 -0
  163. rem/utils/README.md +583 -0
  164. rem/utils/__init__.py +43 -0
  165. rem/utils/agentic_chunking.py +622 -0
  166. rem/utils/batch_ops.py +343 -0
  167. rem/utils/chunking.py +108 -0
  168. rem/utils/clip_embeddings.py +276 -0
  169. rem/utils/dict_utils.py +98 -0
  170. rem/utils/embeddings.py +423 -0
  171. rem/utils/examples/embeddings_example.py +305 -0
  172. rem/utils/examples/sql_types_example.py +202 -0
  173. rem/utils/markdown.py +16 -0
  174. rem/utils/model_helpers.py +236 -0
  175. rem/utils/schema_loader.py +229 -0
  176. rem/utils/sql_types.py +348 -0
  177. rem/utils/user_id.py +81 -0
  178. rem/utils/vision.py +330 -0
  179. rem/workers/README.md +506 -0
  180. rem/workers/__init__.py +5 -0
  181. rem/workers/dreaming.py +502 -0
  182. rem/workers/engram_processor.py +312 -0
  183. rem/workers/sqs_file_processor.py +193 -0
  184. remdb-0.2.6.dist-info/METADATA +1191 -0
  185. remdb-0.2.6.dist-info/RECORD +187 -0
  186. remdb-0.2.6.dist-info/WHEEL +4 -0
  187. remdb-0.2.6.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,536 @@
1
+ """
2
+ MCP Resources for REM system information.
3
+
4
+ Resources are read-only data sources that LLMs can access for context.
5
+ They provide schema information, documentation, and system status.
6
+
7
+ Design Pattern:
8
+ - Resources are registered with the FastMCP server
9
+ - Resources return structured data (typically as strings or JSON)
10
+ - Resources don't modify system state (read-only)
11
+ - Resources help LLMs understand available operations
12
+
13
+ Available Resources:
14
+ - rem://schema/entities - Entity schemas documentation
15
+ - rem://schema/query-types - REM query types documentation
16
+ - rem://status - System health and statistics
17
+ """
18
+
19
+ from fastmcp import FastMCP
20
+
21
+
22
+ def register_schema_resources(mcp: FastMCP):
23
+ """
24
+ Register schema documentation resources.
25
+
26
+ Args:
27
+ mcp: FastMCP server instance
28
+ """
29
+
30
+ @mcp.resource("rem://schema/entities")
31
+ def get_entity_schemas() -> str:
32
+ """
33
+ Get REM entity schemas documentation.
34
+
35
+ Returns complete schema information for all entity types:
36
+ - Resource: Chunked, embedded content
37
+ - Entity: Domain knowledge nodes
38
+ - Moment: Temporal narratives
39
+ - Message: Conversation messages
40
+ - User: System users
41
+ - File: File uploads
42
+ """
43
+ return """
44
+ # REM Entity Schemas
45
+
46
+ ## Resource
47
+ Chunked, embedded content from documents, files, conversations.
48
+
49
+ Fields:
50
+ - id: UUID (auto-generated)
51
+ - user_id: User identifier (primary data scoping field)
52
+ - tenant_id: Tenant identifier (legacy, set to user_id)
53
+ - name: Resource name/label (used for LOOKUP)
54
+ - content: Main text content
55
+ - category: Optional category (document, conversation, etc.)
56
+ - related_entities: JSONB array of extracted entity references
57
+ - graph_paths: JSONB array of InlineEdge objects
58
+ - resource_timestamp: Timestamp of resource creation
59
+ - metadata: JSONB flexible metadata dict
60
+ - created_at, updated_at, deleted_at: Temporal tracking
61
+
62
+ ## Entity
63
+ Domain knowledge nodes with properties and relationships.
64
+
65
+ NOTE: Entities are stored within resources/moments, not in a separate table.
66
+ Entity IDs are human-readable labels (e.g., "sarah-chen", "api-design-v2").
67
+
68
+ ## Moment
69
+ Temporal narratives and time-bound events.
70
+
71
+ Fields:
72
+ - id: UUID (auto-generated)
73
+ - user_id: User identifier (primary data scoping field)
74
+ - tenant_id: Tenant identifier (legacy, set to user_id)
75
+ - name: Moment name/label (used for LOOKUP)
76
+ - moment_type: Type (meeting, coding_session, conversation, etc.)
77
+ - resource_timestamp: Start time
78
+ - resource_ends_timestamp: End time
79
+ - present_persons: JSONB array of Person objects
80
+ - speakers: JSONB array of Speaker objects
81
+ - emotion_tags: Array of emotion tags
82
+ - topic_tags: Array of topic tags
83
+ - summary: Natural language summary
84
+ - source_resource_ids: Array of referenced resource UUIDs
85
+ - created_at, updated_at, deleted_at: Temporal tracking
86
+
87
+ ## Message
88
+ Conversation messages with agents.
89
+
90
+ Fields:
91
+ - id: UUID (auto-generated)
92
+ - user_id: User identifier (primary data scoping field)
93
+ - tenant_id: Tenant identifier (legacy, set to user_id)
94
+ - role: Message role (user, assistant, system)
95
+ - content: Message text
96
+ - session_id: Conversation session identifier
97
+ - metadata: JSONB flexible metadata dict
98
+ - created_at, updated_at, deleted_at: Temporal tracking
99
+
100
+ ## User
101
+ System users with authentication.
102
+
103
+ Fields:
104
+ - id: UUID (auto-generated)
105
+ - user_id: User identifier (primary data scoping field)
106
+ - tenant_id: Tenant identifier (legacy, set to user_id)
107
+ - name: User name
108
+ - email: User email
109
+ - metadata: JSONB flexible metadata dict
110
+ - created_at, updated_at, deleted_at: Temporal tracking
111
+
112
+ ## File
113
+ File uploads with S3 storage.
114
+
115
+ Fields:
116
+ - id: UUID (auto-generated)
117
+ - user_id: User identifier (primary data scoping field)
118
+ - tenant_id: Tenant identifier (legacy, set to user_id)
119
+ - name: File name
120
+ - s3_key: S3 object key
121
+ - s3_bucket: S3 bucket name
122
+ - content_type: MIME type
123
+ - size_bytes: File size
124
+ - metadata: JSONB flexible metadata dict
125
+ - created_at, updated_at, deleted_at: Temporal tracking
126
+ """
127
+
128
+ @mcp.resource("rem://schema/query-types")
129
+ def get_query_types() -> str:
130
+ """
131
+ Get REM query types documentation.
132
+
133
+ Returns comprehensive documentation for all REM query types
134
+ with examples and parameter specifications.
135
+ """
136
+ return """
137
+ # REM Query Types
138
+
139
+ ## LOOKUP
140
+ O(1) entity resolution across ALL tables using KV_STORE.
141
+
142
+ Parameters:
143
+ - entity_key (required): Entity label/name (e.g., "sarah-chen", "api-design-v2")
144
+ - user_id (optional): User scoping for private entities
145
+
146
+ Example:
147
+ ```
148
+ rem_query(query_type="lookup", entity_key="Sarah Chen", user_id="user-123")
149
+ ```
150
+
151
+ Returns:
152
+ - entity_key: The looked-up key
153
+ - entity_type: Entity type (person, document, etc.)
154
+ - entity_id: UUID of the entity
155
+ - content_summary: Summary of entity content
156
+ - metadata: Additional metadata
157
+
158
+ ## FUZZY
159
+ Fuzzy text matching using pg_trgm similarity.
160
+
161
+ Parameters:
162
+ - query_text (required): Query string
163
+ - threshold (optional): Similarity threshold 0.0-1.0 (default: 0.7)
164
+ - limit (optional): Max results (default: 10)
165
+ - user_id (optional): User scoping
166
+
167
+ Example:
168
+ ```
169
+ rem_query(query_type="fuzzy", query_text="sara", threshold=0.7, user_id="user-123")
170
+ ```
171
+
172
+ Returns:
173
+ - Entities matching query with similarity scores
174
+ - Ordered by similarity (highest first)
175
+
176
+ ## SEARCH
177
+ Semantic vector search using embeddings (table-specific).
178
+
179
+ Parameters:
180
+ - query_text (required): Natural language query
181
+ - table_name (required): Table to search (resources, moments, etc.)
182
+ - field_name (optional): Field to search (defaults to "content")
183
+ - provider (optional): Embedding provider (default: from LLM__EMBEDDING_PROVIDER setting)
184
+ - min_similarity (optional): Minimum similarity 0.0-1.0 (default: 0.7)
185
+ - limit (optional): Max results (default: 10)
186
+ - user_id (optional): User scoping
187
+
188
+ Example:
189
+ ```
190
+ rem_query(
191
+ query_type="search",
192
+ query_text="database migration",
193
+ table_name="resources",
194
+ user_id="user-123"
195
+ )
196
+ ```
197
+
198
+ Returns:
199
+ - Semantically similar entities
200
+ - Ordered by similarity score
201
+
202
+ ## SQL
203
+ Direct SQL queries with WHERE clauses (tenant-scoped).
204
+
205
+ Parameters:
206
+ - table_name (required): Table to query
207
+ - where_clause (optional): SQL WHERE condition
208
+ - limit (optional): Max results
209
+
210
+ Example:
211
+ ```
212
+ rem_query(
213
+ query_type="sql",
214
+ table_name="moments",
215
+ where_clause="moment_type='meeting' AND resource_timestamp > '2025-01-01'",
216
+ user_id="user-123"
217
+ )
218
+ ```
219
+
220
+ Returns:
221
+ - Matching rows from table
222
+ - Automatically scoped to user_id
223
+
224
+ ## TRAVERSE
225
+ Multi-hop graph traversal with depth control.
226
+
227
+ Parameters:
228
+ - start_key (required): Starting entity key
229
+ - max_depth (optional): Maximum traversal depth (default: 1)
230
+ - depth=0: PLAN mode (analyze edges without traversal)
231
+ - depth=1+: Full traversal with cycle detection
232
+ - rel_type (optional): Filter by relationship type (e.g., "manages", "authored_by")
233
+ - user_id (optional): User scoping
234
+
235
+ Example:
236
+ ```
237
+ rem_query(
238
+ query_type="traverse",
239
+ start_key="Sarah Chen",
240
+ max_depth=2,
241
+ rel_type="manages",
242
+ user_id="user-123"
243
+ )
244
+ ```
245
+
246
+ Returns:
247
+ - Traversed entities with depth info
248
+ - Relationship types and weights
249
+ - Path information for each node
250
+
251
+ ## Multi-Turn Exploration
252
+
253
+ REM supports iterated retrieval where LLMs conduct multi-turn conversations
254
+ with the database:
255
+
256
+ Turn 1: Find entry point
257
+ ```
258
+ LOOKUP "Sarah Chen"
259
+ ```
260
+
261
+ Turn 2: Analyze neighborhood (PLAN mode)
262
+ ```
263
+ TRAVERSE start_key="Sarah Chen" max_depth=0
264
+ ```
265
+
266
+ Turn 3: Selective traversal
267
+ ```
268
+ TRAVERSE start_key="Sarah Chen" rel_type="manages" max_depth=2
269
+ ```
270
+ """
271
+
272
+
273
+ def register_agent_resources(mcp: FastMCP):
274
+ """
275
+ Register agent schema resources.
276
+
277
+ Args:
278
+ mcp: FastMCP server instance
279
+ """
280
+
281
+ @mcp.resource("rem://agents")
282
+ def list_available_agents() -> str:
283
+ """
284
+ List all available agent schemas.
285
+
286
+ Returns a list of agent schemas packaged with REM, including:
287
+ - Agent name
288
+ - Description
289
+ - Available tools
290
+ - Version information
291
+
292
+ TODO: Add pagination support if agent count grows large (not needed for now)
293
+ """
294
+ import importlib.resources
295
+ import yaml
296
+ from pathlib import Path
297
+
298
+ try:
299
+ # Find packaged agent schemas
300
+ agents_ref = importlib.resources.files("rem") / "schemas" / "agents"
301
+ agents_dir = Path(str(agents_ref))
302
+
303
+ if not agents_dir.exists():
304
+ return "# Available Agents\n\nNo agent schemas found in package."
305
+
306
+ # Discover all agent schemas recursively
307
+ agent_files = sorted(agents_dir.rglob("*.yaml")) + sorted(agents_dir.rglob("*.yml")) + sorted(agents_dir.rglob("*.json"))
308
+
309
+ if not agent_files:
310
+ return "# Available Agents\n\nNo agent schemas found."
311
+
312
+ output = ["# Available Agent Schemas\n"]
313
+ output.append("Packaged agent schemas available for use:\n")
314
+
315
+ for agent_file in agent_files:
316
+ try:
317
+ with open(agent_file, "r") as f:
318
+ schema = yaml.safe_load(f)
319
+
320
+ agent_name = agent_file.stem
321
+ description = schema.get("description", "No description")
322
+ # Get first 200 characters of description
323
+ desc_snippet = description[:200] + "..." if len(description) > 200 else description
324
+
325
+ # Get additional metadata
326
+ extra = schema.get("json_schema_extra", {})
327
+ version = extra.get("version", "unknown")
328
+ tools = extra.get("tools", [])
329
+
330
+ output.append(f"\n## {agent_name}")
331
+ output.append(f"**Path:** `agents/{agent_file.name}`")
332
+ output.append(f"**Version:** {version}")
333
+ output.append(f"**Description:** {desc_snippet}")
334
+ if tools:
335
+ output.append(f"**Tools:** {', '.join(tools[:5])}" + (" ..." if len(tools) > 5 else ""))
336
+
337
+ # Usage example
338
+ output.append(f"\n**Usage:**")
339
+ output.append(f"```python")
340
+ output.append(f'rem ask agents/{agent_file.name} "Your query here"')
341
+ output.append(f"```")
342
+
343
+ except Exception as e:
344
+ output.append(f"\n## {agent_file.stem}")
345
+ output.append(f"⚠️ Error loading schema: {e}")
346
+
347
+ return "\n".join(output)
348
+
349
+ except Exception as e:
350
+ return f"# Available Agents\n\nError listing agents: {e}"
351
+
352
+
353
+ def register_file_resources(mcp: FastMCP):
354
+ """
355
+ Register file operation resources.
356
+
357
+ Args:
358
+ mcp: FastMCP server instance
359
+ """
360
+
361
+ @mcp.resource("rem://files/presigned-url/{s3_key}")
362
+ def get_presigned_url(s3_key: str, expiration: int = 3600) -> str:
363
+ """
364
+ Generate presigned URL for S3 object download.
365
+
366
+ Args:
367
+ s3_key: S3 object key (e.g., "tenant/files/uuid/file.pdf")
368
+ expiration: URL expiration time in seconds (default: 3600 = 1 hour)
369
+
370
+ Returns:
371
+ Presigned URL for downloading the file
372
+
373
+ Raises:
374
+ RuntimeError: If S3 is not configured
375
+
376
+ Example:
377
+ >>> url = get_presigned_url("acme/files/123/document.pdf")
378
+ >>> # Returns: https://s3.amazonaws.com/bucket/acme/files/123/document.pdf?signature=...
379
+ """
380
+ from ...settings import settings
381
+
382
+ # Check if S3 is configured
383
+ if not settings.s3.bucket_name:
384
+ raise RuntimeError(
385
+ "S3 is not configured. Cannot generate presigned URLs.\n"
386
+ "Configure S3 settings in ~/.rem/config.yaml or environment variables."
387
+ )
388
+
389
+ import aioboto3
390
+ import asyncio
391
+ from botocore.exceptions import ClientError
392
+
393
+ async def _generate_url():
394
+ session = aioboto3.Session()
395
+ async with session.client(
396
+ "s3",
397
+ endpoint_url=settings.s3.endpoint_url,
398
+ aws_access_key_id=settings.s3.access_key_id,
399
+ aws_secret_access_key=settings.s3.secret_access_key,
400
+ region_name=settings.s3.region,
401
+ ) as s3_client:
402
+ try:
403
+ url = await s3_client.generate_presigned_url(
404
+ "get_object",
405
+ Params={
406
+ "Bucket": settings.s3.bucket_name,
407
+ "Key": s3_key,
408
+ },
409
+ ExpiresIn=expiration,
410
+ )
411
+ return url
412
+ except ClientError as e:
413
+ raise RuntimeError(f"Failed to generate presigned URL: {e}")
414
+
415
+ # Run async function
416
+ loop = asyncio.get_event_loop()
417
+ if loop.is_running():
418
+ # If already in async context, create new loop
419
+ import nest_asyncio
420
+ nest_asyncio.apply()
421
+ url = loop.run_until_complete(_generate_url())
422
+ else:
423
+ url = asyncio.run(_generate_url())
424
+
425
+ return url
426
+
427
+
428
+ def register_status_resources(mcp: FastMCP):
429
+ """
430
+ Register system status resources.
431
+
432
+ Args:
433
+ mcp: FastMCP server instance
434
+ """
435
+
436
+ @mcp.resource("rem://status")
437
+ def get_system_status() -> str:
438
+ """
439
+ Get REM system health and statistics.
440
+
441
+ Returns system information including:
442
+ - Service health
443
+ - Database connection status
444
+ - Environment configuration
445
+ - Available query types
446
+ """
447
+ from ...settings import settings
448
+
449
+ return f"""
450
+ # REM System Status
451
+
452
+ ## Environment
453
+ - Environment: {settings.environment}
454
+ - Team: {settings.team}
455
+ - Root Path: {settings.root_path or '/'}
456
+
457
+ ## LLM Configuration
458
+ - Default Model: {settings.llm.default_model}
459
+ - Default Temperature: {settings.llm.default_temperature}
460
+ - Embedding Provider: {settings.llm.embedding_provider}
461
+ - Embedding Model: {settings.llm.embedding_model}
462
+ - OpenAI API Key: {"✓ Configured" if settings.llm.openai_api_key else "✗ Not configured"}
463
+ - Anthropic API Key: {"✓ Configured" if settings.llm.anthropic_api_key else "✗ Not configured"}
464
+
465
+ ## Database
466
+ - PostgreSQL: {settings.postgres.connection_string}
467
+
468
+ ## S3 Storage
469
+ - Bucket: {settings.s3.bucket_name}
470
+ - Region: {settings.s3.region}
471
+
472
+ ## Observability
473
+ - OTEL Enabled: {settings.otel.enabled}
474
+ - Phoenix Enabled: {settings.phoenix.enabled}
475
+
476
+ ## Authentication
477
+ - Auth Enabled: {settings.auth.enabled}
478
+
479
+ ## Available Query Types
480
+ - LOOKUP: O(1) entity resolution
481
+ - FUZZY: Fuzzy text matching
482
+ - SEARCH: Semantic vector search
483
+ - SQL: Direct SQL queries
484
+ - TRAVERSE: Multi-hop graph traversal
485
+
486
+ ## MCP Tools
487
+ - search_rem: Execute REM queries (LOOKUP, FUZZY, SEARCH, SQL, TRAVERSE)
488
+ - ask_rem_agent: Natural language to REM query conversion
489
+ - ingest_into_rem: File ingestion pipeline
490
+ - read_resource: Access MCP resources
491
+
492
+ ## Status
493
+ ✓ System operational
494
+ ✓ Ready to process queries
495
+ """
496
+
497
+
498
+ # Resource dispatcher for read_resource tool
499
+ async def load_resource(uri: str) -> dict | str:
500
+ """
501
+ Load an MCP resource by URI.
502
+
503
+ This function is called by the read_resource tool to dispatch to
504
+ registered resource handlers.
505
+
506
+ Args:
507
+ uri: Resource URI (e.g., "rem://schemas", "rem://status")
508
+
509
+ Returns:
510
+ Resource data (dict or string)
511
+
512
+ Raises:
513
+ ValueError: If URI is invalid or resource not found
514
+ """
515
+ # Create temporary MCP instance with resources
516
+ from fastmcp import FastMCP
517
+
518
+ mcp = FastMCP(name="temp")
519
+
520
+ # Register all resources
521
+ register_schema_resources(mcp)
522
+ register_agent_resources(mcp)
523
+ register_file_resources(mcp)
524
+ register_status_resources(mcp)
525
+
526
+ # Get resource handlers from MCP internal registry
527
+ # FastMCP stores resources in a dict by URI
528
+ if hasattr(mcp, "_resources"):
529
+ if uri in mcp._resources:
530
+ handler = mcp._resources[uri]
531
+ if callable(handler):
532
+ result = handler()
533
+ return result if result else {"error": "Resource returned None"}
534
+
535
+ # If not found, raise error
536
+ raise ValueError(f"Resource not found: {uri}. Available resources: {list(mcp._resources.keys()) if hasattr(mcp, '_resources') else 'unknown'}")