remdb 0.3.242__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 (235) hide show
  1. rem/__init__.py +129 -0
  2. rem/agentic/README.md +760 -0
  3. rem/agentic/__init__.py +54 -0
  4. rem/agentic/agents/README.md +155 -0
  5. rem/agentic/agents/__init__.py +38 -0
  6. rem/agentic/agents/agent_manager.py +311 -0
  7. rem/agentic/agents/sse_simulator.py +502 -0
  8. rem/agentic/context.py +425 -0
  9. rem/agentic/context_builder.py +360 -0
  10. rem/agentic/llm_provider_models.py +301 -0
  11. rem/agentic/mcp/__init__.py +0 -0
  12. rem/agentic/mcp/tool_wrapper.py +273 -0
  13. rem/agentic/otel/__init__.py +5 -0
  14. rem/agentic/otel/setup.py +240 -0
  15. rem/agentic/providers/phoenix.py +926 -0
  16. rem/agentic/providers/pydantic_ai.py +854 -0
  17. rem/agentic/query.py +117 -0
  18. rem/agentic/query_helper.py +89 -0
  19. rem/agentic/schema.py +737 -0
  20. rem/agentic/serialization.py +245 -0
  21. rem/agentic/tools/__init__.py +5 -0
  22. rem/agentic/tools/rem_tools.py +242 -0
  23. rem/api/README.md +657 -0
  24. rem/api/deps.py +253 -0
  25. rem/api/main.py +460 -0
  26. rem/api/mcp_router/prompts.py +182 -0
  27. rem/api/mcp_router/resources.py +820 -0
  28. rem/api/mcp_router/server.py +243 -0
  29. rem/api/mcp_router/tools.py +1605 -0
  30. rem/api/middleware/tracking.py +172 -0
  31. rem/api/routers/admin.py +520 -0
  32. rem/api/routers/auth.py +898 -0
  33. rem/api/routers/chat/__init__.py +5 -0
  34. rem/api/routers/chat/child_streaming.py +394 -0
  35. rem/api/routers/chat/completions.py +702 -0
  36. rem/api/routers/chat/json_utils.py +76 -0
  37. rem/api/routers/chat/models.py +202 -0
  38. rem/api/routers/chat/otel_utils.py +33 -0
  39. rem/api/routers/chat/sse_events.py +546 -0
  40. rem/api/routers/chat/streaming.py +950 -0
  41. rem/api/routers/chat/streaming_utils.py +327 -0
  42. rem/api/routers/common.py +18 -0
  43. rem/api/routers/dev.py +87 -0
  44. rem/api/routers/feedback.py +276 -0
  45. rem/api/routers/messages.py +620 -0
  46. rem/api/routers/models.py +86 -0
  47. rem/api/routers/query.py +362 -0
  48. rem/api/routers/shared_sessions.py +422 -0
  49. rem/auth/README.md +258 -0
  50. rem/auth/__init__.py +36 -0
  51. rem/auth/jwt.py +367 -0
  52. rem/auth/middleware.py +318 -0
  53. rem/auth/providers/__init__.py +16 -0
  54. rem/auth/providers/base.py +376 -0
  55. rem/auth/providers/email.py +215 -0
  56. rem/auth/providers/google.py +163 -0
  57. rem/auth/providers/microsoft.py +237 -0
  58. rem/cli/README.md +517 -0
  59. rem/cli/__init__.py +8 -0
  60. rem/cli/commands/README.md +299 -0
  61. rem/cli/commands/__init__.py +3 -0
  62. rem/cli/commands/ask.py +549 -0
  63. rem/cli/commands/cluster.py +1808 -0
  64. rem/cli/commands/configure.py +495 -0
  65. rem/cli/commands/db.py +828 -0
  66. rem/cli/commands/dreaming.py +324 -0
  67. rem/cli/commands/experiments.py +1698 -0
  68. rem/cli/commands/mcp.py +66 -0
  69. rem/cli/commands/process.py +388 -0
  70. rem/cli/commands/query.py +109 -0
  71. rem/cli/commands/scaffold.py +47 -0
  72. rem/cli/commands/schema.py +230 -0
  73. rem/cli/commands/serve.py +106 -0
  74. rem/cli/commands/session.py +453 -0
  75. rem/cli/dreaming.py +363 -0
  76. rem/cli/main.py +123 -0
  77. rem/config.py +244 -0
  78. rem/mcp_server.py +41 -0
  79. rem/models/core/__init__.py +49 -0
  80. rem/models/core/core_model.py +70 -0
  81. rem/models/core/engram.py +333 -0
  82. rem/models/core/experiment.py +672 -0
  83. rem/models/core/inline_edge.py +132 -0
  84. rem/models/core/rem_query.py +246 -0
  85. rem/models/entities/__init__.py +68 -0
  86. rem/models/entities/domain_resource.py +38 -0
  87. rem/models/entities/feedback.py +123 -0
  88. rem/models/entities/file.py +57 -0
  89. rem/models/entities/image_resource.py +88 -0
  90. rem/models/entities/message.py +64 -0
  91. rem/models/entities/moment.py +123 -0
  92. rem/models/entities/ontology.py +181 -0
  93. rem/models/entities/ontology_config.py +131 -0
  94. rem/models/entities/resource.py +95 -0
  95. rem/models/entities/schema.py +87 -0
  96. rem/models/entities/session.py +84 -0
  97. rem/models/entities/shared_session.py +180 -0
  98. rem/models/entities/subscriber.py +175 -0
  99. rem/models/entities/user.py +93 -0
  100. rem/py.typed +0 -0
  101. rem/registry.py +373 -0
  102. rem/schemas/README.md +507 -0
  103. rem/schemas/__init__.py +6 -0
  104. rem/schemas/agents/README.md +92 -0
  105. rem/schemas/agents/core/agent-builder.yaml +235 -0
  106. rem/schemas/agents/core/moment-builder.yaml +178 -0
  107. rem/schemas/agents/core/rem-query-agent.yaml +226 -0
  108. rem/schemas/agents/core/resource-affinity-assessor.yaml +99 -0
  109. rem/schemas/agents/core/simple-assistant.yaml +19 -0
  110. rem/schemas/agents/core/user-profile-builder.yaml +163 -0
  111. rem/schemas/agents/examples/contract-analyzer.yaml +317 -0
  112. rem/schemas/agents/examples/contract-extractor.yaml +134 -0
  113. rem/schemas/agents/examples/cv-parser.yaml +263 -0
  114. rem/schemas/agents/examples/hello-world.yaml +37 -0
  115. rem/schemas/agents/examples/query.yaml +54 -0
  116. rem/schemas/agents/examples/simple.yaml +21 -0
  117. rem/schemas/agents/examples/test.yaml +29 -0
  118. rem/schemas/agents/rem.yaml +132 -0
  119. rem/schemas/evaluators/hello-world/default.yaml +77 -0
  120. rem/schemas/evaluators/rem/faithfulness.yaml +219 -0
  121. rem/schemas/evaluators/rem/lookup-correctness.yaml +182 -0
  122. rem/schemas/evaluators/rem/retrieval-precision.yaml +199 -0
  123. rem/schemas/evaluators/rem/retrieval-recall.yaml +211 -0
  124. rem/schemas/evaluators/rem/search-correctness.yaml +192 -0
  125. rem/services/__init__.py +18 -0
  126. rem/services/audio/INTEGRATION.md +308 -0
  127. rem/services/audio/README.md +376 -0
  128. rem/services/audio/__init__.py +15 -0
  129. rem/services/audio/chunker.py +354 -0
  130. rem/services/audio/transcriber.py +259 -0
  131. rem/services/content/README.md +1269 -0
  132. rem/services/content/__init__.py +5 -0
  133. rem/services/content/providers.py +760 -0
  134. rem/services/content/service.py +762 -0
  135. rem/services/dreaming/README.md +230 -0
  136. rem/services/dreaming/__init__.py +53 -0
  137. rem/services/dreaming/affinity_service.py +322 -0
  138. rem/services/dreaming/moment_service.py +251 -0
  139. rem/services/dreaming/ontology_service.py +54 -0
  140. rem/services/dreaming/user_model_service.py +297 -0
  141. rem/services/dreaming/utils.py +39 -0
  142. rem/services/email/__init__.py +10 -0
  143. rem/services/email/service.py +522 -0
  144. rem/services/email/templates.py +360 -0
  145. rem/services/embeddings/__init__.py +11 -0
  146. rem/services/embeddings/api.py +127 -0
  147. rem/services/embeddings/worker.py +435 -0
  148. rem/services/fs/README.md +662 -0
  149. rem/services/fs/__init__.py +62 -0
  150. rem/services/fs/examples.py +206 -0
  151. rem/services/fs/examples_paths.py +204 -0
  152. rem/services/fs/git_provider.py +935 -0
  153. rem/services/fs/local_provider.py +760 -0
  154. rem/services/fs/parsing-hooks-examples.md +172 -0
  155. rem/services/fs/paths.py +276 -0
  156. rem/services/fs/provider.py +460 -0
  157. rem/services/fs/s3_provider.py +1042 -0
  158. rem/services/fs/service.py +186 -0
  159. rem/services/git/README.md +1075 -0
  160. rem/services/git/__init__.py +17 -0
  161. rem/services/git/service.py +469 -0
  162. rem/services/phoenix/EXPERIMENT_DESIGN.md +1146 -0
  163. rem/services/phoenix/README.md +453 -0
  164. rem/services/phoenix/__init__.py +46 -0
  165. rem/services/phoenix/client.py +960 -0
  166. rem/services/phoenix/config.py +88 -0
  167. rem/services/phoenix/prompt_labels.py +477 -0
  168. rem/services/postgres/README.md +757 -0
  169. rem/services/postgres/__init__.py +49 -0
  170. rem/services/postgres/diff_service.py +599 -0
  171. rem/services/postgres/migration_service.py +427 -0
  172. rem/services/postgres/programmable_diff_service.py +635 -0
  173. rem/services/postgres/pydantic_to_sqlalchemy.py +562 -0
  174. rem/services/postgres/register_type.py +353 -0
  175. rem/services/postgres/repository.py +481 -0
  176. rem/services/postgres/schema_generator.py +661 -0
  177. rem/services/postgres/service.py +802 -0
  178. rem/services/postgres/sql_builder.py +355 -0
  179. rem/services/rate_limit.py +113 -0
  180. rem/services/rem/README.md +318 -0
  181. rem/services/rem/__init__.py +23 -0
  182. rem/services/rem/exceptions.py +71 -0
  183. rem/services/rem/executor.py +293 -0
  184. rem/services/rem/parser.py +180 -0
  185. rem/services/rem/queries.py +196 -0
  186. rem/services/rem/query.py +371 -0
  187. rem/services/rem/service.py +608 -0
  188. rem/services/session/README.md +374 -0
  189. rem/services/session/__init__.py +13 -0
  190. rem/services/session/compression.py +488 -0
  191. rem/services/session/pydantic_messages.py +310 -0
  192. rem/services/session/reload.py +85 -0
  193. rem/services/user_service.py +130 -0
  194. rem/settings.py +1877 -0
  195. rem/sql/background_indexes.sql +52 -0
  196. rem/sql/migrations/001_install.sql +983 -0
  197. rem/sql/migrations/002_install_models.sql +3157 -0
  198. rem/sql/migrations/003_optional_extensions.sql +326 -0
  199. rem/sql/migrations/004_cache_system.sql +282 -0
  200. rem/sql/migrations/005_schema_update.sql +145 -0
  201. rem/sql/migrations/migrate_session_id_to_uuid.sql +45 -0
  202. rem/utils/AGENTIC_CHUNKING.md +597 -0
  203. rem/utils/README.md +628 -0
  204. rem/utils/__init__.py +61 -0
  205. rem/utils/agentic_chunking.py +622 -0
  206. rem/utils/batch_ops.py +343 -0
  207. rem/utils/chunking.py +108 -0
  208. rem/utils/clip_embeddings.py +276 -0
  209. rem/utils/constants.py +97 -0
  210. rem/utils/date_utils.py +228 -0
  211. rem/utils/dict_utils.py +98 -0
  212. rem/utils/embeddings.py +436 -0
  213. rem/utils/examples/embeddings_example.py +305 -0
  214. rem/utils/examples/sql_types_example.py +202 -0
  215. rem/utils/files.py +323 -0
  216. rem/utils/markdown.py +16 -0
  217. rem/utils/mime_types.py +158 -0
  218. rem/utils/model_helpers.py +492 -0
  219. rem/utils/schema_loader.py +649 -0
  220. rem/utils/sql_paths.py +146 -0
  221. rem/utils/sql_types.py +350 -0
  222. rem/utils/user_id.py +81 -0
  223. rem/utils/vision.py +325 -0
  224. rem/workers/README.md +506 -0
  225. rem/workers/__init__.py +7 -0
  226. rem/workers/db_listener.py +579 -0
  227. rem/workers/db_maintainer.py +74 -0
  228. rem/workers/dreaming.py +502 -0
  229. rem/workers/engram_processor.py +312 -0
  230. rem/workers/sqs_file_processor.py +193 -0
  231. rem/workers/unlogged_maintainer.py +463 -0
  232. remdb-0.3.242.dist-info/METADATA +1632 -0
  233. remdb-0.3.242.dist-info/RECORD +235 -0
  234. remdb-0.3.242.dist-info/WHEEL +4 -0
  235. remdb-0.3.242.dist-info/entry_points.txt +2 -0
rem/api/main.py ADDED
@@ -0,0 +1,460 @@
1
+ """
2
+ REM API Server - FastAPI application with integrated MCP server.
3
+
4
+ Design Pattern:
5
+ 1. Create FastMCP server with create_mcp_server()
6
+ 2. Get HTTP app with mcp.http_app(path="/", transport="http", stateless_http=True)
7
+ 3. Mount on FastAPI at /api/v1/mcp
8
+ 4. Add middleware in specific order (sessions, logging, auth, CORS)
9
+ 5. Register API routers for v1 endpoints
10
+
11
+ Key Architecture Decisions
12
+ - MCP mounted at /api/v1/mcp (not /mcp) for consistency
13
+ - Stateless HTTP prevents stale session errors across pod restarts
14
+ - Auth middleware excludes /api/auth and /api/v1/mcp/auth paths
15
+ - CORS added LAST so it runs FIRST (middleware runs in reverse)
16
+ - Combined lifespan for proper initialization order
17
+
18
+ Middleware Order (runs in reverse):
19
+ 1. CORS (runs first - adds headers to all responses)
20
+ 2. Auth (protects /api/v1/* paths)
21
+ 3. Logging (logs all requests)
22
+ 4. Sessions (OAuth state management)
23
+
24
+ Endpoints:
25
+ - / : API information
26
+ - /health : Health check
27
+ - /api/v1/mcp : MCP endpoint (HTTP transport)
28
+ - /api/v1/chat/completions : OpenAI-compatible chat completions (streaming & non-streaming)
29
+ - /api/v1/query : REM query execution (rem-dialect or natural-language)
30
+ - /api/v1/resources : Resource CRUD (TODO)
31
+ - /api/v1/moments : Moment CRUD (TODO)
32
+ - /api/auth/* : OAuth/OIDC authentication
33
+ - /docs : OpenAPI documentation
34
+
35
+ Headers → AgentContext Mapping:
36
+ The following HTTP headers are automatically mapped to AgentContext fields:
37
+ - X-User-Id → context.user_id (user identifier)
38
+ - X-Tenant-Id → context.tenant_id (tenant identifier, required for REM)
39
+ - X-Session-Id → context.session_id (session/conversation identifier)
40
+ - X-Agent-Schema → context.agent_schema_uri (agent schema to use)
41
+
42
+ Example:
43
+ POST /api/v1/chat/completions
44
+ X-Tenant-Id: acme-corp
45
+ X-User-Id: user123
46
+ X-Agent-Schema: rem-agents-query-agent
47
+
48
+ {
49
+ "model": "anthropic:claude-sonnet-4-5-20250929",
50
+ "messages": [{"role": "user", "content": "Find Sarah's documents"}],
51
+ "stream": true
52
+ }
53
+
54
+ Running:
55
+ # Development (auto-reload)
56
+ uv run python -m rem.api.main
57
+
58
+ # Production (Docker with hypercorn)
59
+ hypercorn rem.api.main:app --bind 0.0.0.0:8000
60
+ """
61
+
62
+ import importlib.metadata
63
+ import secrets
64
+ import sys
65
+ import time
66
+
67
+ # Get package version for API responses
68
+ try:
69
+ __version__ = importlib.metadata.version("remdb")
70
+ except importlib.metadata.PackageNotFoundError:
71
+ __version__ = "0.0.0-dev"
72
+ from contextlib import asynccontextmanager
73
+
74
+ from fastapi import FastAPI, Request
75
+ from fastapi.middleware.cors import CORSMiddleware
76
+ from fastapi.responses import JSONResponse
77
+ from loguru import logger
78
+ from starlette.middleware.base import BaseHTTPMiddleware
79
+ from starlette.middleware.sessions import SessionMiddleware
80
+
81
+ from .mcp_router.server import create_mcp_server
82
+ from ..settings import settings
83
+
84
+ # Configure loguru based on settings
85
+ # Remove default handler and add one with configured level
86
+ logger.remove()
87
+
88
+ # Configure level icons - only warnings and errors get visual indicators
89
+ logger.level("DEBUG", icon=" ")
90
+ logger.level("INFO", icon=" ")
91
+ logger.level("WARNING", icon="🟠")
92
+ logger.level("ERROR", icon="🔴")
93
+ logger.level("CRITICAL", icon="🔴")
94
+
95
+ logger.add(
96
+ sys.stderr,
97
+ level=settings.api.log_level.upper(),
98
+ format="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | {level.icon} <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
99
+ )
100
+
101
+
102
+ class RequestLoggingMiddleware(BaseHTTPMiddleware):
103
+ """
104
+ Log all incoming HTTP requests and responses.
105
+
106
+ Design Pattern:
107
+ - Logs request method, path, client, user-agent
108
+ - Logs response status, content-type, duration
109
+ - Essential for debugging OAuth flow and MCP sessions
110
+ - Health checks and 404s logged at DEBUG level to reduce noise
111
+ - Scanner/exploit attempts (common vulnerability probes) logged at DEBUG
112
+ """
113
+
114
+ # Paths to log at DEBUG level (health checks, probes)
115
+ DEBUG_PATHS = {"/health", "/healthz", "/ready", "/readyz", "/livez"}
116
+
117
+ # Path patterns that indicate vulnerability scanners (log at DEBUG)
118
+ SCANNER_PATTERNS = (
119
+ "/vendor/", # PHP composer exploits
120
+ "/.git/", # Git config exposure
121
+ "/.env", # Environment file exposure
122
+ "/wp-", # WordPress exploits
123
+ "/phpunit/", # PHPUnit RCE
124
+ "/eval-stdin", # PHP eval exploits
125
+ "/console/", # Console exposure
126
+ "/actuator/", # Spring Boot actuator
127
+ "/debug/", # Debug endpoints
128
+ "/admin/", # Admin panel probes (when we don't have one)
129
+ )
130
+
131
+ def _should_log_at_debug(self, path: str, status_code: int) -> bool:
132
+ """Determine if request should be logged at DEBUG level."""
133
+ # Health checks
134
+ if path in self.DEBUG_PATHS:
135
+ return True
136
+ # 404 responses (not found - includes scanner probes)
137
+ if status_code == 404:
138
+ return True
139
+ # Known scanner patterns
140
+ if any(pattern in path for pattern in self.SCANNER_PATTERNS):
141
+ return True
142
+ return False
143
+
144
+ async def dispatch(self, request: Request, call_next):
145
+ start_time = time.time()
146
+ path = request.url.path
147
+
148
+ # Log incoming request (preliminary - may adjust after response)
149
+ client_host = request.client.host if request.client else "unknown"
150
+ user_agent = request.headers.get('user-agent', 'unknown')[:100]
151
+
152
+ # Extract auth info for logging (first 8 chars of token for debugging)
153
+ auth_header = request.headers.get('authorization', '')
154
+ auth_preview = ""
155
+ if auth_header.startswith('Bearer '):
156
+ token = auth_header[7:]
157
+ auth_preview = f"Bearer {token[:8]}..." if len(token) > 8 else f"Bearer {token}"
158
+
159
+ # Process request
160
+ response = await call_next(request)
161
+
162
+ # Extract user info set by auth middleware (after processing)
163
+ user = getattr(request.state, "user", None)
164
+ user_id = user.get("id", "none")[:12] if user else "anon"
165
+ user_email = user.get("email", "") if user else ""
166
+
167
+ # Determine log level based on path AND response status
168
+ duration_ms = (time.time() - start_time) * 1000
169
+ use_debug = self._should_log_at_debug(path, response.status_code)
170
+ log_fn = logger.debug if use_debug else logger.info
171
+
172
+ # Build user info string
173
+ user_info = f"user={user_id}"
174
+ if user_email:
175
+ user_info += f" ({user_email})"
176
+ if auth_preview:
177
+ user_info += f" | auth={auth_preview}"
178
+
179
+ # Log request and response together with auth info
180
+ log_fn(
181
+ f"→ REQUEST: {request.method} {path} | "
182
+ f"Client: {client_host} | "
183
+ f"{user_info}"
184
+ )
185
+ log_fn(
186
+ f"← RESPONSE: {request.method} {path} | "
187
+ f"Status: {response.status_code} | "
188
+ f"Duration: {duration_ms:.2f}ms"
189
+ )
190
+
191
+ return response
192
+
193
+
194
+ class SSEBufferingMiddleware(BaseHTTPMiddleware):
195
+ """
196
+ Disable proxy buffering for SSE responses.
197
+
198
+ Adds X-Accel-Buffering: no header to prevent Nginx/Traefik
199
+ from buffering Server-Sent Events (critical for MCP SSE transport).
200
+ """
201
+
202
+ async def dispatch(self, request: Request, call_next):
203
+ response = await call_next(request)
204
+
205
+ # Disable buffering for SSE responses
206
+ content_type = response.headers.get("content-type", "")
207
+ if "text/event-stream" in content_type:
208
+ response.headers["X-Accel-Buffering"] = "no"
209
+ response.headers["Cache-Control"] = "no-cache"
210
+
211
+ return response
212
+
213
+
214
+ @asynccontextmanager
215
+ async def lifespan(app: FastAPI):
216
+ """
217
+ Application lifespan manager.
218
+
219
+ Handles startup and shutdown tasks.
220
+ OTEL instrumentation must be initialized at startup before any agents are created.
221
+ """
222
+ logger.info(f"Starting REM API ({settings.environment})")
223
+
224
+ # Initialize OTEL instrumentation if enabled
225
+ # Must be done at startup to instrument Pydantic AI before any agents are created
226
+ if settings.otel.enabled:
227
+ from ..agentic.otel.setup import setup_instrumentation
228
+
229
+ setup_instrumentation()
230
+
231
+ # Check database configuration
232
+ if not settings.postgres.enabled:
233
+ logger.warning(
234
+ "Running in NO-DATABASE mode - database connection disabled. "
235
+ "Agent execution works with file-based schemas, but session storage "
236
+ "and history lookups are unavailable. Enable database with POSTGRES__ENABLED=true"
237
+ )
238
+ else:
239
+ # Log database host only - never log credentials
240
+ logger.info(f"Database enabled: {settings.postgres.host}:{settings.postgres.port}/{settings.postgres.database}")
241
+
242
+ yield
243
+
244
+ logger.info("Shutting down REM API")
245
+
246
+
247
+ def create_app() -> FastAPI:
248
+ """
249
+ Create and configure the FastAPI application with MCP server.
250
+
251
+ The returned app exposes `app.mcp_server` (FastMCP instance) for adding
252
+ custom tools, resources, and prompts:
253
+
254
+ app = create_app()
255
+
256
+ @app.mcp_server.tool()
257
+ async def my_tool(query: str) -> dict:
258
+ '''Custom MCP tool.'''
259
+ return {"result": query}
260
+
261
+ @app.mcp_server.resource("custom://data")
262
+ async def my_resource() -> str:
263
+ '''Custom resource.'''
264
+ return '{"data": "value"}'
265
+
266
+ Design Pattern:
267
+ 1. Create MCP server
268
+ 2. Get HTTP app with stateless_http=True
269
+ 3. Combine lifespans (app + MCP)
270
+ 4. Create FastAPI with combined lifespan
271
+ 5. Add middleware (sessions, logging, auth, CORS) in specific order
272
+ 6. Define health endpoints
273
+ 7. Register API routers
274
+ 8. Mount MCP app
275
+ 9. Expose mcp_server on app for extension
276
+
277
+ Returns:
278
+ Configured FastAPI application with .mcp_server attribute
279
+ """
280
+ # Create MCP server and get HTTP app
281
+ # path="/" creates routes at root, then mount at /api/v1/mcp
282
+ # transport="http" for MCP HTTP protocol
283
+ # stateless_http=True prevents stale session errors (pods can restart)
284
+ mcp_server = create_mcp_server()
285
+ mcp_app = mcp_server.http_app(path="/", transport="http", stateless_http=True)
286
+
287
+ # Disable trailing slash redirects (prevents 307 redirects that strip auth headers)
288
+ if hasattr(mcp_app, "router"):
289
+ mcp_app.router.redirect_slashes = False
290
+
291
+ # Combine MCP and API lifespans
292
+ # Explicit nesting ensures proper initialization order
293
+ @asynccontextmanager
294
+ async def combined_lifespan(app: FastAPI):
295
+ async with lifespan(app):
296
+ async with mcp_app.lifespan(app):
297
+ yield
298
+
299
+ app = FastAPI(
300
+ title=f"{settings.app_name} API",
301
+ description=f"{settings.app_name} - Resources Entities Moments system for agentic AI",
302
+ version=__version__,
303
+ lifespan=combined_lifespan,
304
+ root_path=settings.root_path if settings.root_path else "",
305
+ redirect_slashes=False, # Don't redirect /mcp/ -> /mcp
306
+ )
307
+
308
+ # Add request logging middleware
309
+ app.add_middleware(RequestLoggingMiddleware)
310
+
311
+ # Add SSE buffering middleware (for MCP SSE transport)
312
+ app.add_middleware(SSEBufferingMiddleware)
313
+
314
+ # Add Anonymous Tracking & Rate Limiting (Runs AFTER Auth if Auth is enabled)
315
+ # Must be added BEFORE AuthMiddleware in code to be INNER in the stack
316
+ from .middleware.tracking import AnonymousTrackingMiddleware
317
+ app.add_middleware(AnonymousTrackingMiddleware)
318
+
319
+ # Add authentication middleware
320
+ # Always load middleware for dev token support, but allow anonymous when auth disabled
321
+ from ..auth.middleware import AuthMiddleware
322
+
323
+ app.add_middleware(
324
+ AuthMiddleware,
325
+ protected_paths=["/api/v1", "/api/admin"],
326
+ excluded_paths=["/api/auth", "/api/dev", "/api/v1/mcp/auth", "/api/v1/slack"],
327
+ # Allow anonymous when auth is disabled, otherwise use setting
328
+ allow_anonymous=(not settings.auth.enabled) or settings.auth.allow_anonymous,
329
+ # MCP requires auth only when auth is fully enabled
330
+ mcp_requires_auth=settings.auth.enabled and settings.auth.mcp_requires_auth,
331
+ )
332
+
333
+ # Add session middleware for OAuth state management
334
+ # Must be added AFTER AuthMiddleware in code so it runs BEFORE (middleware runs in reverse)
335
+ # AuthMiddleware needs request.session to be available
336
+ session_secret = settings.auth.session_secret or secrets.token_hex(32)
337
+ if not settings.auth.session_secret:
338
+ logger.warning(
339
+ "AUTH__SESSION_SECRET not set - using generated key "
340
+ "(sessions won't persist across restarts)"
341
+ )
342
+
343
+ app.add_middleware(
344
+ SessionMiddleware,
345
+ secret_key=session_secret,
346
+ session_cookie="rem_session",
347
+ max_age=3600, # 1 hour
348
+ same_site="lax",
349
+ https_only=settings.environment == "production",
350
+ )
351
+
352
+ # Add CORS middleware LAST (runs first in middleware chain)
353
+ # Must expose mcp-session-id header for MCP session management
354
+ CORS_ORIGIN_WHITELIST = [
355
+ "http://localhost:3000", # Local development (React)
356
+ "http://localhost:5000", # Local development (Flask/other)
357
+ "http://localhost:5173", # Local development (Vite)
358
+ ]
359
+
360
+ app.add_middleware(
361
+ CORSMiddleware,
362
+ allow_origins=CORS_ORIGIN_WHITELIST,
363
+ allow_credentials=True,
364
+ allow_methods=["*"],
365
+ allow_headers=["*", "mcp-protocol-version", "mcp-session-id", "authorization"],
366
+ expose_headers=["mcp-session-id"],
367
+ )
368
+
369
+ # Root endpoint
370
+ @app.get("/")
371
+ async def root():
372
+ """API information endpoint."""
373
+ # TODO: If auth enabled and no user, return 401 with WWW-Authenticate
374
+ return {
375
+ "name": f"{settings.app_name} API",
376
+ "version": __version__,
377
+ "mcp_endpoint": "/api/v1/mcp",
378
+ "docs": "/docs",
379
+ }
380
+
381
+ # Health check endpoint
382
+ @app.get("/health")
383
+ async def health():
384
+ """Health check endpoint."""
385
+ return {"status": "healthy", "version": __version__}
386
+
387
+ # Register API routers
388
+ from .routers.chat import router as chat_router
389
+ from .routers.models import router as models_router
390
+ from .routers.messages import router as messages_router
391
+ from .routers.feedback import router as feedback_router
392
+ from .routers.admin import router as admin_router
393
+ from .routers.shared_sessions import router as shared_sessions_router
394
+ from .routers.query import router as query_router
395
+
396
+ app.include_router(chat_router)
397
+ app.include_router(models_router)
398
+ # shared_sessions_router MUST be before messages_router
399
+ # because messages_router has /sessions/{session_id} which would match
400
+ # before the more specific /sessions/shared-with-me routes
401
+ app.include_router(shared_sessions_router)
402
+ app.include_router(messages_router)
403
+ app.include_router(feedback_router)
404
+ app.include_router(admin_router)
405
+ app.include_router(query_router)
406
+
407
+ # Register auth router (if enabled)
408
+ if settings.auth.enabled:
409
+ from .routers.auth import router as auth_router
410
+
411
+ app.include_router(auth_router)
412
+
413
+ # Register dev router (non-production only)
414
+ if settings.environment != "production":
415
+ from .routers.dev import router as dev_router
416
+
417
+ app.include_router(dev_router)
418
+
419
+ # TODO: Register additional routers
420
+ # from .routers.query import router as query_router
421
+ # from .routers.resources import router as resources_router
422
+ # from .routers.moments import router as moments_router
423
+ #
424
+ # app.include_router(query_router)
425
+ # app.include_router(resources_router)
426
+ # app.include_router(moments_router)
427
+
428
+ # Add middleware to rewrite /api/v1/mcp to /api/v1/mcp/
429
+ @app.middleware("http")
430
+ async def mcp_path_rewrite_middleware(request: Request, call_next):
431
+ """Rewrite /api/v1/mcp to /api/v1/mcp/ to handle Claude Desktop requests."""
432
+ if request.url.path == "/api/v1/mcp":
433
+ request.scope["path"] = "/api/v1/mcp/"
434
+ request.scope["raw_path"] = b"/api/v1/mcp/"
435
+ return await call_next(request)
436
+
437
+ # Mount MCP app at /api/v1/mcp
438
+ app.mount("/api/v1/mcp", mcp_app)
439
+
440
+ # Expose MCP server on app for extension
441
+ # Users can add tools/resources/prompts via app.mcp_server
442
+ app.mcp_server = mcp_server # type: ignore[attr-defined]
443
+
444
+ return app
445
+
446
+
447
+ # Create application instance
448
+ app = create_app()
449
+
450
+
451
+ # Main entry point for uvicorn
452
+ if __name__ == "__main__":
453
+ import uvicorn
454
+
455
+ uvicorn.run(
456
+ "rem.api.main:app",
457
+ host="0.0.0.0",
458
+ port=8000,
459
+ reload=True,
460
+ )
@@ -0,0 +1,182 @@
1
+ """
2
+ MCP Prompts for REM operations.
3
+
4
+ Prompts are interactive templates that help users perform complex tasks.
5
+ """
6
+
7
+ from fastmcp import FastMCP
8
+
9
+
10
+ CREATEAGENT_PROMPT = """
11
+ Create a custom REM agent schema.
12
+
13
+ I'll help you create an agent schema that can be uploaded to REM and automatically processed.
14
+
15
+ ## What I need from you:
16
+
17
+ 1. **Agent purpose**: What should this agent do? What domain knowledge does it need?
18
+ 2. **Short name**: Lowercase with hyphens (e.g., "cv-parser", "contract-analyzer")
19
+ 3. **Version**: Semantic version (e.g., "1.0.0")
20
+ 4. **Structured output fields**: What data should the agent extract?
21
+
22
+ ## Agent Schema Format
23
+
24
+ REM agents use JSON Schema format with these sections:
25
+
26
+ ```yaml
27
+ ---
28
+ type: object
29
+ description: |
30
+ System prompt with LLM instructions.
31
+
32
+ Provide clear, detailed guidance on what the agent should do.
33
+
34
+ properties:
35
+ field_name:
36
+ type: string
37
+ description: Field description
38
+
39
+ required:
40
+ - required_field
41
+
42
+ json_schema_extra:
43
+ kind: agent
44
+ name: your-agent
45
+ version: "1.0.0"
46
+ tags: [domain, category]
47
+
48
+ # Optional: Fields to embed for semantic search
49
+ embedding_fields:
50
+ - field1
51
+ - field2
52
+ ```
53
+
54
+ ## Example: CV Parser
55
+
56
+ ```yaml
57
+ ---
58
+ type: object
59
+ description: |
60
+ Parse CV/resume documents to extract candidate information.
61
+
62
+ Extract:
63
+ - Candidate details (name, contact, summary)
64
+ - Work experience with dates
65
+ - Education history
66
+ - Skills and competencies
67
+ - Seniority level assessment
68
+
69
+ properties:
70
+ candidate_name:
71
+ type: string
72
+ description: Full name of the candidate
73
+
74
+ skills:
75
+ type: array
76
+ items:
77
+ type: string
78
+ description: Technical and professional skills
79
+
80
+ experience:
81
+ type: array
82
+ items:
83
+ type: object
84
+ properties:
85
+ company: {type: string}
86
+ title: {type: string}
87
+ start_date: {type: string}
88
+ end_date: {type: string}
89
+ description: Work experience history
90
+
91
+ seniority_level:
92
+ type: string
93
+ enum: ["junior", "mid-level", "senior", "lead", "executive"]
94
+ description: Assessed seniority level
95
+
96
+ required:
97
+ - candidate_name
98
+ - skills
99
+
100
+ json_schema_extra:
101
+ kind: agent
102
+ name: cv-parser
103
+ version: "1.0.0"
104
+
105
+ tags: [recruitment, ontology-extractor]
106
+
107
+ embedding_fields:
108
+ - candidate_name
109
+ - skills
110
+
111
+ category: ontology-extractor
112
+ ```
113
+
114
+ ## Upload Process
115
+
116
+ After creating your schema:
117
+
118
+ 1. **Save to local file system**: `~/.rem/fs/my-agent.yaml` or request an upload path for remote servers.
119
+
120
+ 2. **Upload via ingest_file**:
121
+ ```python
122
+ ingest_file(
123
+ file_uri="LOCAL PATH for local servers or remote S3 path for remote servers",
124
+ category="agent"
125
+ )
126
+ ```
127
+
128
+ 3. **Automatic processing**:
129
+ - File detected by worker
130
+ - Schema validated and stored in schemas table
131
+ - Available for immediate use
132
+
133
+ ## Ready?
134
+
135
+ Tell me:
136
+ 1. What should your agent do?
137
+ 2. What data should it extract?
138
+ 3. What should we name it?
139
+
140
+ I'll generate the complete schema for you!
141
+ """
142
+
143
+
144
+ def register_prompts(mcp: FastMCP):
145
+ """
146
+ Register MCP prompts.
147
+
148
+ Args:
149
+ mcp: FastMCP server instance
150
+ """
151
+
152
+ @mcp.prompt()
153
+ def create_agent(
154
+ purpose: str = "",
155
+ short_name: str = "",
156
+ version: str = "1.0.0",
157
+ ) -> str:
158
+ """
159
+ Interactive prompt for creating custom REM agent schemas.
160
+
161
+ Guides users through creating agent schemas with domain knowledge,
162
+ structured output definitions, and upload instructions.
163
+
164
+ Args:
165
+ purpose: Agent purpose and domain (optional, will prompt if empty)
166
+ short_name: Agent short name in kebab-case (optional, will suggest)
167
+ version: Semantic version (default: "1.0.0")
168
+
169
+ Returns:
170
+ Interactive prompt with examples and upload instructions
171
+ """
172
+ prompt = CREATEAGENT_PROMPT
173
+
174
+ # Add context if parameters provided
175
+ if purpose:
176
+ prompt += f"\n\nYou mentioned: \"{purpose}\"\n"
177
+ if short_name:
178
+ prompt += f"Short name: {short_name}\n"
179
+ if version != "1.0.0":
180
+ prompt += f"Version: {version}\n"
181
+
182
+ return prompt