remdb 0.3.14__py3-none-any.whl → 0.3.133__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.
Files changed (89) hide show
  1. rem/agentic/README.md +76 -0
  2. rem/agentic/__init__.py +15 -0
  3. rem/agentic/agents/__init__.py +16 -2
  4. rem/agentic/agents/sse_simulator.py +502 -0
  5. rem/agentic/context.py +51 -27
  6. rem/agentic/llm_provider_models.py +301 -0
  7. rem/agentic/mcp/tool_wrapper.py +112 -17
  8. rem/agentic/otel/setup.py +93 -4
  9. rem/agentic/providers/phoenix.py +302 -109
  10. rem/agentic/providers/pydantic_ai.py +215 -26
  11. rem/agentic/schema.py +361 -21
  12. rem/agentic/tools/rem_tools.py +3 -3
  13. rem/api/README.md +215 -1
  14. rem/api/deps.py +255 -0
  15. rem/api/main.py +132 -40
  16. rem/api/mcp_router/resources.py +1 -1
  17. rem/api/mcp_router/server.py +26 -5
  18. rem/api/mcp_router/tools.py +465 -7
  19. rem/api/routers/admin.py +494 -0
  20. rem/api/routers/auth.py +70 -0
  21. rem/api/routers/chat/completions.py +402 -20
  22. rem/api/routers/chat/models.py +88 -10
  23. rem/api/routers/chat/otel_utils.py +33 -0
  24. rem/api/routers/chat/sse_events.py +542 -0
  25. rem/api/routers/chat/streaming.py +642 -45
  26. rem/api/routers/dev.py +81 -0
  27. rem/api/routers/feedback.py +268 -0
  28. rem/api/routers/messages.py +473 -0
  29. rem/api/routers/models.py +78 -0
  30. rem/api/routers/query.py +360 -0
  31. rem/api/routers/shared_sessions.py +406 -0
  32. rem/auth/middleware.py +126 -27
  33. rem/cli/commands/README.md +237 -64
  34. rem/cli/commands/cluster.py +1808 -0
  35. rem/cli/commands/configure.py +1 -3
  36. rem/cli/commands/db.py +386 -143
  37. rem/cli/commands/experiments.py +418 -27
  38. rem/cli/commands/process.py +14 -8
  39. rem/cli/commands/schema.py +97 -50
  40. rem/cli/main.py +27 -6
  41. rem/config.py +10 -3
  42. rem/models/core/core_model.py +7 -1
  43. rem/models/core/experiment.py +54 -0
  44. rem/models/core/rem_query.py +5 -2
  45. rem/models/entities/__init__.py +21 -0
  46. rem/models/entities/domain_resource.py +38 -0
  47. rem/models/entities/feedback.py +123 -0
  48. rem/models/entities/message.py +30 -1
  49. rem/models/entities/session.py +83 -0
  50. rem/models/entities/shared_session.py +180 -0
  51. rem/registry.py +10 -4
  52. rem/schemas/agents/rem.yaml +7 -3
  53. rem/services/content/service.py +92 -20
  54. rem/services/embeddings/api.py +4 -4
  55. rem/services/embeddings/worker.py +16 -16
  56. rem/services/phoenix/client.py +154 -14
  57. rem/services/postgres/README.md +159 -15
  58. rem/services/postgres/__init__.py +2 -1
  59. rem/services/postgres/diff_service.py +531 -0
  60. rem/services/postgres/pydantic_to_sqlalchemy.py +427 -129
  61. rem/services/postgres/repository.py +132 -0
  62. rem/services/postgres/schema_generator.py +205 -4
  63. rem/services/postgres/service.py +6 -6
  64. rem/services/rem/parser.py +44 -9
  65. rem/services/rem/service.py +36 -2
  66. rem/services/session/compression.py +24 -1
  67. rem/services/session/reload.py +1 -1
  68. rem/settings.py +324 -23
  69. rem/sql/background_indexes.sql +21 -16
  70. rem/sql/migrations/001_install.sql +387 -54
  71. rem/sql/migrations/002_install_models.sql +2320 -393
  72. rem/sql/migrations/003_optional_extensions.sql +326 -0
  73. rem/sql/migrations/004_cache_system.sql +548 -0
  74. rem/utils/__init__.py +18 -0
  75. rem/utils/date_utils.py +2 -2
  76. rem/utils/model_helpers.py +156 -1
  77. rem/utils/schema_loader.py +220 -22
  78. rem/utils/sql_paths.py +146 -0
  79. rem/utils/sql_types.py +3 -1
  80. rem/workers/__init__.py +3 -1
  81. rem/workers/db_listener.py +579 -0
  82. rem/workers/unlogged_maintainer.py +463 -0
  83. {remdb-0.3.14.dist-info → remdb-0.3.133.dist-info}/METADATA +335 -226
  84. {remdb-0.3.14.dist-info → remdb-0.3.133.dist-info}/RECORD +86 -66
  85. {remdb-0.3.14.dist-info → remdb-0.3.133.dist-info}/WHEEL +1 -1
  86. rem/sql/002_install_models.sql +0 -1068
  87. rem/sql/install_models.sql +0 -1051
  88. rem/sql/migrations/003_seed_default_user.sql +0 -48
  89. {remdb-0.3.14.dist-info → remdb-0.3.133.dist-info}/entry_points.txt +0 -0
rem/api/deps.py ADDED
@@ -0,0 +1,255 @@
1
+ """
2
+ Shared FastAPI dependencies for authentication and authorization.
3
+
4
+ Provides dependency injection utilities for:
5
+ - Extracting current user from session
6
+ - Requiring authentication
7
+ - Requiring specific roles (admin, user)
8
+ - User-scoped data filtering with admin override
9
+
10
+ Design Pattern:
11
+ - Use as FastAPI dependencies via Depends()
12
+ - Middleware sets request.state.user and request.state.is_anonymous
13
+ - Dependencies extract and validate from request.state
14
+ - Admin users can access any user's data via filters
15
+
16
+ Roles:
17
+ - "admin": Full access to all data across all users
18
+ - "user": Default role, access limited to own data
19
+ - Anonymous: Rate-limited access, no persistent data
20
+
21
+ Usage:
22
+ from rem.api.deps import require_auth, require_admin, get_user_filter
23
+
24
+ @router.get("/items")
25
+ async def list_items(user: dict = Depends(require_auth)):
26
+ # user is guaranteed to be authenticated
27
+ ...
28
+
29
+ @router.post("/admin/action")
30
+ async def admin_action(user: dict = Depends(require_admin)):
31
+ # user is guaranteed to have admin role
32
+ ...
33
+
34
+ @router.get("/sessions/{session_id}")
35
+ async def get_session(
36
+ session_id: str,
37
+ filters: dict = Depends(get_user_filter),
38
+ ):
39
+ # filters includes user_id constraint (unless admin)
40
+ ...
41
+ """
42
+
43
+ from typing import Any
44
+
45
+ from fastapi import Depends, HTTPException, Request
46
+ from loguru import logger
47
+
48
+
49
+ class AuthError(HTTPException):
50
+ """Authentication/Authorization error."""
51
+
52
+ def __init__(self, detail: str, status_code: int = 401):
53
+ super().__init__(status_code=status_code, detail=detail)
54
+
55
+
56
+ def get_current_user(request: Request) -> dict | None:
57
+ """
58
+ Get current user from request state (set by AuthMiddleware).
59
+
60
+ Returns None if no user authenticated.
61
+ Use require_auth() if authentication is mandatory.
62
+
63
+ Args:
64
+ request: FastAPI request
65
+
66
+ Returns:
67
+ User dict from session or None
68
+ """
69
+ return getattr(request.state, "user", None)
70
+
71
+
72
+ def get_is_anonymous(request: Request) -> bool:
73
+ """
74
+ Check if current request is anonymous.
75
+
76
+ Args:
77
+ request: FastAPI request
78
+
79
+ Returns:
80
+ True if anonymous, False if authenticated
81
+ """
82
+ return getattr(request.state, "is_anonymous", True)
83
+
84
+
85
+ def require_auth(request: Request) -> dict:
86
+ """
87
+ Require authenticated user.
88
+
89
+ Use as FastAPI dependency to enforce authentication.
90
+
91
+ Args:
92
+ request: FastAPI request
93
+
94
+ Returns:
95
+ User dict from session
96
+
97
+ Raises:
98
+ HTTPException 401 if not authenticated
99
+ """
100
+ user = get_current_user(request)
101
+ if not user:
102
+ raise AuthError("Authentication required", status_code=401)
103
+ return user
104
+
105
+
106
+ def require_admin(request: Request) -> dict:
107
+ """
108
+ Require authenticated user with admin role.
109
+
110
+ Use as FastAPI dependency to protect admin-only endpoints.
111
+
112
+ Args:
113
+ request: FastAPI request
114
+
115
+ Returns:
116
+ User dict from session
117
+
118
+ Raises:
119
+ HTTPException 401 if not authenticated
120
+ HTTPException 403 if not admin
121
+ """
122
+ user = require_auth(request)
123
+ roles = user.get("roles", [])
124
+
125
+ if "admin" not in roles:
126
+ logger.warning(f"Admin access denied for user {user.get('email')}")
127
+ raise AuthError("Admin access required", status_code=403)
128
+
129
+ return user
130
+
131
+
132
+ def is_admin(user: dict | None) -> bool:
133
+ """
134
+ Check if user has admin role.
135
+
136
+ Args:
137
+ user: User dict or None
138
+
139
+ Returns:
140
+ True if user is admin
141
+ """
142
+ if not user:
143
+ return False
144
+ return "admin" in user.get("roles", [])
145
+
146
+
147
+ async def get_user_filter(
148
+ request: Request,
149
+ x_user_id: str | None = None,
150
+ x_tenant_id: str = "default",
151
+ ) -> dict[str, Any]:
152
+ """
153
+ Get user-scoped filter dict for database queries.
154
+
155
+ For regular users: Always filters by their own user_id.
156
+ For admin users: Can filter by any user_id (or no filter for all users).
157
+
158
+ Args:
159
+ request: FastAPI request
160
+ x_user_id: Optional user_id filter (admin only for cross-user)
161
+ x_tenant_id: Tenant ID for multi-tenancy
162
+
163
+ Returns:
164
+ Filter dict with appropriate user_id constraint
165
+
166
+ Usage:
167
+ @router.get("/items")
168
+ async def list_items(filters: dict = Depends(get_user_filter)):
169
+ return await repo.find(filters)
170
+ """
171
+ user = get_current_user(request)
172
+ filters: dict[str, Any] = {"tenant_id": x_tenant_id}
173
+
174
+ if is_admin(user):
175
+ # Admin can filter by any user or see all
176
+ if x_user_id:
177
+ filters["user_id"] = x_user_id
178
+ # If no user_id specified, admin sees all (no user_id filter)
179
+ logger.debug(f"Admin access: filters={filters}")
180
+ elif user:
181
+ # Regular authenticated user: always filter by own user_id
182
+ filters["user_id"] = user.get("id")
183
+ if x_user_id and x_user_id != user.get("id"):
184
+ logger.warning(
185
+ f"User {user.get('email')} attempted to filter by user_id={x_user_id}"
186
+ )
187
+ else:
188
+ # Anonymous: could use anonymous tracking ID or restrict access
189
+ # For now, anonymous can't access user-scoped data
190
+ anon_id = getattr(request.state, "anon_id", None)
191
+ if anon_id:
192
+ filters["user_id"] = f"anon:{anon_id}"
193
+ else:
194
+ filters["user_id"] = "anonymous"
195
+
196
+ return filters
197
+
198
+
199
+ async def require_owner_or_admin(
200
+ request: Request,
201
+ resource_user_id: str,
202
+ ) -> dict:
203
+ """
204
+ Require that current user owns the resource or is admin.
205
+
206
+ Use for parametric endpoints (GET /resource/{id}) where
207
+ only the owner or admin should access.
208
+
209
+ Args:
210
+ request: FastAPI request
211
+ resource_user_id: The user_id of the resource being accessed
212
+
213
+ Returns:
214
+ User dict from session
215
+
216
+ Raises:
217
+ HTTPException 401 if not authenticated
218
+ HTTPException 403 if not owner and not admin
219
+ """
220
+ user = require_auth(request)
221
+
222
+ if is_admin(user):
223
+ return user
224
+
225
+ if user.get("id") != resource_user_id:
226
+ logger.warning(
227
+ f"Access denied: user {user.get('email')} tried to access "
228
+ f"resource owned by {resource_user_id}"
229
+ )
230
+ raise AuthError("Access denied: not owner", status_code=403)
231
+
232
+ return user
233
+
234
+
235
+ def get_user_id_from_request(request: Request) -> str:
236
+ """
237
+ Get effective user_id for creating resources.
238
+
239
+ Returns authenticated user's ID or anonymous tracking ID.
240
+
241
+ Args:
242
+ request: FastAPI request
243
+
244
+ Returns:
245
+ User ID string
246
+ """
247
+ user = get_current_user(request)
248
+ if user:
249
+ return user.get("id", "unknown")
250
+
251
+ anon_id = getattr(request.state, "anon_id", None)
252
+ if anon_id:
253
+ return f"anon:{anon_id}"
254
+
255
+ return "anonymous"
rem/api/main.py CHANGED
@@ -26,10 +26,10 @@ Endpoints:
26
26
  - /health : Health check
27
27
  - /api/v1/mcp : MCP endpoint (HTTP transport)
28
28
  - /api/v1/chat/completions : OpenAI-compatible chat completions (streaming & non-streaming)
29
- - /api/v1/query : REM query execution (TODO)
29
+ - /api/v1/query : REM query execution (rem-dialect or natural-language)
30
30
  - /api/v1/resources : Resource CRUD (TODO)
31
31
  - /api/v1/moments : Moment CRUD (TODO)
32
- - /api/auth/* : OAuth/OIDC authentication (TODO)
32
+ - /api/auth/* : OAuth/OIDC authentication
33
33
  - /docs : OpenAPI documentation
34
34
 
35
35
  Headers → AgentContext Mapping:
@@ -59,8 +59,16 @@ Running:
59
59
  hypercorn rem.api.main:app --bind 0.0.0.0:8000
60
60
  """
61
61
 
62
+ import importlib.metadata
62
63
  import secrets
64
+ import sys
63
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"
64
72
  from contextlib import asynccontextmanager
65
73
 
66
74
  from fastapi import FastAPI, Request
@@ -73,6 +81,23 @@ from starlette.middleware.sessions import SessionMiddleware
73
81
  from .mcp_router.server import create_mcp_server
74
82
  from ..settings import settings
75
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
+
76
101
 
77
102
  class RequestLoggingMiddleware(BaseHTTPMiddleware):
78
103
  """
@@ -82,26 +107,64 @@ class RequestLoggingMiddleware(BaseHTTPMiddleware):
82
107
  - Logs request method, path, client, user-agent
83
108
  - Logs response status, content-type, duration
84
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
85
112
  """
86
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
+
87
144
  async def dispatch(self, request: Request, call_next):
88
145
  start_time = time.time()
146
+ path = request.url.path
89
147
 
90
- # Log incoming request
148
+ # Log incoming request (preliminary - may adjust after response)
91
149
  client_host = request.client.host if request.client else "unknown"
92
- logger.info(
93
- f"→ REQUEST: {request.method} {request.url.path} | "
94
- f"Client: {client_host} | "
95
- f"User-Agent: {request.headers.get('user-agent', 'unknown')[:100]}"
96
- )
150
+ user_agent = request.headers.get('user-agent', 'unknown')[:100]
97
151
 
98
152
  # Process request
99
153
  response = await call_next(request)
100
154
 
101
- # Log response
155
+ # Determine log level based on path AND response status
102
156
  duration_ms = (time.time() - start_time) * 1000
103
- logger.info(
104
- f"← RESPONSE: {request.method} {request.url.path} | "
157
+ use_debug = self._should_log_at_debug(path, response.status_code)
158
+ log_fn = logger.debug if use_debug else logger.info
159
+
160
+ # Log request and response together
161
+ log_fn(
162
+ f"→ REQUEST: {request.method} {path} | "
163
+ f"Client: {client_host} | "
164
+ f"User-Agent: {user_agent}"
165
+ )
166
+ log_fn(
167
+ f"← RESPONSE: {request.method} {path} | "
105
168
  f"Status: {response.status_code} | "
106
169
  f"Duration: {duration_ms:.2f}ms"
107
170
  )
@@ -154,7 +217,8 @@ async def lifespan(app: FastAPI):
154
217
  "and history lookups are unavailable. Enable database with POSTGRES__ENABLED=true"
155
218
  )
156
219
  else:
157
- logger.info(f"Database enabled: {settings.postgres.connection_string}")
220
+ # Log database host only - never log credentials
221
+ logger.info(f"Database enabled: {settings.postgres.host}:{settings.postgres.port}/{settings.postgres.database}")
158
222
 
159
223
  yield
160
224
 
@@ -214,15 +278,42 @@ def create_app() -> FastAPI:
214
278
  yield
215
279
 
216
280
  app = FastAPI(
217
- title="REM API",
218
- description="Resources Entities Moments system for agentic AI",
219
- version="0.1.0",
281
+ title=f"{settings.app_name} API",
282
+ description=f"{settings.app_name} - Resources Entities Moments system for agentic AI",
283
+ version=__version__,
220
284
  lifespan=combined_lifespan,
221
285
  root_path=settings.root_path if settings.root_path else "",
222
286
  redirect_slashes=False, # Don't redirect /mcp/ -> /mcp
223
287
  )
224
288
 
289
+ # Add request logging middleware
290
+ app.add_middleware(RequestLoggingMiddleware)
291
+
292
+ # Add SSE buffering middleware (for MCP SSE transport)
293
+ app.add_middleware(SSEBufferingMiddleware)
294
+
295
+ # Add Anonymous Tracking & Rate Limiting (Runs AFTER Auth if Auth is enabled)
296
+ # Must be added BEFORE AuthMiddleware in code to be INNER in the stack
297
+ from .middleware.tracking import AnonymousTrackingMiddleware
298
+ app.add_middleware(AnonymousTrackingMiddleware)
299
+
300
+ # Add authentication middleware
301
+ # Always load middleware for dev token support, but allow anonymous when auth disabled
302
+ from ..auth.middleware import AuthMiddleware
303
+
304
+ app.add_middleware(
305
+ AuthMiddleware,
306
+ protected_paths=["/api/v1"],
307
+ excluded_paths=["/api/auth", "/api/dev", "/api/v1/mcp/auth"],
308
+ # Allow anonymous when auth is disabled, otherwise use setting
309
+ allow_anonymous=(not settings.auth.enabled) or settings.auth.allow_anonymous,
310
+ # MCP requires auth only when auth is fully enabled
311
+ mcp_requires_auth=settings.auth.enabled and settings.auth.mcp_requires_auth,
312
+ )
313
+
225
314
  # Add session middleware for OAuth state management
315
+ # Must be added AFTER AuthMiddleware in code so it runs BEFORE (middleware runs in reverse)
316
+ # AuthMiddleware needs request.session to be available
226
317
  session_secret = settings.auth.session_secret or secrets.token_hex(32)
227
318
  if not settings.auth.session_secret:
228
319
  logger.warning(
@@ -239,32 +330,12 @@ def create_app() -> FastAPI:
239
330
  https_only=settings.environment == "production",
240
331
  )
241
332
 
242
- # Add request logging middleware
243
- app.add_middleware(RequestLoggingMiddleware)
244
-
245
- # Add SSE buffering middleware (for MCP SSE transport)
246
- app.add_middleware(SSEBufferingMiddleware)
247
-
248
- # Add Anonymous Tracking & Rate Limiting (Runs AFTER Auth if Auth is enabled)
249
- # Must be added BEFORE AuthMiddleware in code to be INNER in the stack
250
- from .middleware.tracking import AnonymousTrackingMiddleware
251
- app.add_middleware(AnonymousTrackingMiddleware)
252
-
253
- # Add authentication middleware (if enabled)
254
- if settings.auth.enabled:
255
- from ..auth.middleware import AuthMiddleware
256
-
257
- app.add_middleware(
258
- AuthMiddleware,
259
- protected_paths=["/api/v1"],
260
- excluded_paths=["/api/auth", "/api/v1/mcp/auth"],
261
- )
262
-
263
333
  # Add CORS middleware LAST (runs first in middleware chain)
264
334
  # Must expose mcp-session-id header for MCP session management
265
335
  CORS_ORIGIN_WHITELIST = [
266
- "http://localhost:5173", # Local development (Vite)
267
336
  "http://localhost:3000", # Local development (React)
337
+ "http://localhost:5000", # Local development (Flask/other)
338
+ "http://localhost:5173", # Local development (Vite)
268
339
  ]
269
340
 
270
341
  app.add_middleware(
@@ -282,8 +353,8 @@ def create_app() -> FastAPI:
282
353
  """API information endpoint."""
283
354
  # TODO: If auth enabled and no user, return 401 with WWW-Authenticate
284
355
  return {
285
- "name": "REM API",
286
- "version": "0.1.0",
356
+ "name": f"{settings.app_name} API",
357
+ "version": __version__,
287
358
  "mcp_endpoint": "/api/v1/mcp",
288
359
  "docs": "/docs",
289
360
  }
@@ -292,12 +363,27 @@ def create_app() -> FastAPI:
292
363
  @app.get("/health")
293
364
  async def health():
294
365
  """Health check endpoint."""
295
- return {"status": "healthy", "version": "0.1.0"}
366
+ return {"status": "healthy", "version": __version__}
296
367
 
297
368
  # Register API routers
298
369
  from .routers.chat import router as chat_router
370
+ from .routers.models import router as models_router
371
+ from .routers.messages import router as messages_router
372
+ from .routers.feedback import router as feedback_router
373
+ from .routers.admin import router as admin_router
374
+ from .routers.shared_sessions import router as shared_sessions_router
375
+ from .routers.query import router as query_router
299
376
 
300
377
  app.include_router(chat_router)
378
+ app.include_router(models_router)
379
+ # shared_sessions_router MUST be before messages_router
380
+ # because messages_router has /sessions/{session_id} which would match
381
+ # before the more specific /sessions/shared-with-me routes
382
+ app.include_router(shared_sessions_router)
383
+ app.include_router(messages_router)
384
+ app.include_router(feedback_router)
385
+ app.include_router(admin_router)
386
+ app.include_router(query_router)
301
387
 
302
388
  # Register auth router (if enabled)
303
389
  if settings.auth.enabled:
@@ -305,6 +391,12 @@ def create_app() -> FastAPI:
305
391
 
306
392
  app.include_router(auth_router)
307
393
 
394
+ # Register dev router (non-production only)
395
+ if settings.environment != "production":
396
+ from .routers.dev import router as dev_router
397
+
398
+ app.include_router(dev_router)
399
+
308
400
  # TODO: Register additional routers
309
401
  # from .routers.query import router as query_router
310
402
  # from .routers.resources import router as resources_router
@@ -181,7 +181,7 @@ Parameters:
181
181
  - table_name (required): Table to search (resources, moments, etc.)
182
182
  - field_name (optional): Field to search (defaults to "content")
183
183
  - provider (optional): Embedding provider (default: from LLM__EMBEDDING_PROVIDER setting)
184
- - min_similarity (optional): Minimum similarity 0.0-1.0 (default: 0.7)
184
+ - min_similarity (optional): Minimum similarity 0.0-1.0 (default: 0.3)
185
185
  - limit (optional): Max results (default: 10)
186
186
  - user_id (optional): User scoping
187
187
 
@@ -19,10 +19,18 @@ FastMCP Features:
19
19
  - Built-in auth that can be disabled for testing
20
20
  """
21
21
 
22
+ import importlib.metadata
23
+
22
24
  from fastmcp import FastMCP
23
25
 
24
26
  from ...settings import settings
25
27
 
28
+ # Get package version
29
+ try:
30
+ __version__ = importlib.metadata.version("remdb")
31
+ except importlib.metadata.PackageNotFoundError:
32
+ __version__ = "0.0.0-dev"
33
+
26
34
 
27
35
  def create_mcp_server(is_local: bool = False) -> FastMCP:
28
36
  """
@@ -52,7 +60,7 @@ def create_mcp_server(is_local: bool = False) -> FastMCP:
52
60
  """
53
61
  mcp = FastMCP(
54
62
  name=f"REM MCP Server ({settings.team}/{settings.environment})",
55
- version="0.1.0",
63
+ version=__version__,
56
64
  instructions=(
57
65
  "REM (Resource-Entity-Moment) MCP Server - Unified memory infrastructure for agentic systems.\n\n"
58
66
  "═══════════════════════════════════════════════════════════════════════════\n"
@@ -119,10 +127,12 @@ def create_mcp_server(is_local: bool = False) -> FastMCP:
119
127
  "AVAILABLE TOOLS\n"
120
128
  "═══════════════════════════════════════════════════════════════════════════\n"
121
129
  "\n"
122
- "• rem_query - Execute REM queries (LOOKUP, FUZZY, SEARCH, SQL, TRAVERSE)\n"
123
- "• ask_rem - Natural language to REM query conversion\n"
130
+ "• search_rem - Execute REM queries (LOOKUP, FUZZY, SEARCH, SQL, TRAVERSE)\n"
131
+ "• ask_rem_agent - Natural language to REM query conversion\n"
124
132
  " - plan_mode=True: Hints agent to use TRAVERSE with depth=0 for edge analysis\n"
125
- "• parse_and_ingest_file - Ingest files from local paths (local server only), s3://, or https://\n"
133
+ "• ingest_into_rem - Ingest files from local paths (local server only), s3://, or https://\n"
134
+ "• list_schema - List all database schemas (tables) with row counts\n"
135
+ "• get_schema - Get detailed schema for a specific table (columns, types, indexes)\n"
126
136
  "\n"
127
137
  "═══════════════════════════════════════════════════════════════════════════\n"
128
138
  "AVAILABLE RESOURCES (Read-Only)\n"
@@ -165,11 +175,22 @@ def create_mcp_server(is_local: bool = False) -> FastMCP:
165
175
  )
166
176
 
167
177
  # Register REM tools
168
- from .tools import ask_rem_agent, ingest_into_rem, read_resource, search_rem
178
+ from .tools import (
179
+ ask_rem_agent,
180
+ get_schema,
181
+ ingest_into_rem,
182
+ list_schema,
183
+ read_resource,
184
+ register_metadata,
185
+ search_rem,
186
+ )
169
187
 
170
188
  mcp.tool()(search_rem)
171
189
  mcp.tool()(ask_rem_agent)
172
190
  mcp.tool()(read_resource)
191
+ mcp.tool()(register_metadata)
192
+ mcp.tool()(list_schema)
193
+ mcp.tool()(get_schema)
173
194
 
174
195
  # File ingestion tool (with local path support for local servers)
175
196
  # Wrap to inject is_local parameter