minder-cli 0.2.0__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 (132) hide show
  1. minder/__init__.py +12 -0
  2. minder/api/routers/prompts.py +177 -0
  3. minder/application/__init__.py +1 -0
  4. minder/application/admin/__init__.py +11 -0
  5. minder/application/admin/dto.py +453 -0
  6. minder/application/admin/jobs.py +327 -0
  7. minder/application/admin/use_cases.py +1895 -0
  8. minder/auth/__init__.py +12 -0
  9. minder/auth/context.py +26 -0
  10. minder/auth/middleware.py +70 -0
  11. minder/auth/principal.py +59 -0
  12. minder/auth/rate_limiter.py +89 -0
  13. minder/auth/rbac.py +60 -0
  14. minder/auth/service.py +541 -0
  15. minder/bootstrap/__init__.py +9 -0
  16. minder/bootstrap/providers.py +109 -0
  17. minder/bootstrap/transport.py +807 -0
  18. minder/cache/__init__.py +10 -0
  19. minder/cache/providers.py +140 -0
  20. minder/chunking/__init__.py +4 -0
  21. minder/chunking/code_splitter.py +184 -0
  22. minder/chunking/splitter.py +136 -0
  23. minder/cli.py +1542 -0
  24. minder/config.py +179 -0
  25. minder/continuity.py +363 -0
  26. minder/dev.py +160 -0
  27. minder/embedding/__init__.py +9 -0
  28. minder/embedding/base.py +7 -0
  29. minder/embedding/local.py +65 -0
  30. minder/embedding/openai.py +7 -0
  31. minder/graph/__init__.py +11 -0
  32. minder/graph/edges.py +13 -0
  33. minder/graph/executor.py +127 -0
  34. minder/graph/graph.py +263 -0
  35. minder/graph/nodes/__init__.py +27 -0
  36. minder/graph/nodes/evaluator.py +21 -0
  37. minder/graph/nodes/guard.py +64 -0
  38. minder/graph/nodes/llm.py +59 -0
  39. minder/graph/nodes/planning.py +30 -0
  40. minder/graph/nodes/reasoning.py +87 -0
  41. minder/graph/nodes/reranker.py +141 -0
  42. minder/graph/nodes/retriever.py +86 -0
  43. minder/graph/nodes/verification.py +230 -0
  44. minder/graph/nodes/workflow_planner.py +250 -0
  45. minder/graph/runtime.py +15 -0
  46. minder/graph/state.py +26 -0
  47. minder/llm/__init__.py +5 -0
  48. minder/llm/base.py +14 -0
  49. minder/llm/local.py +381 -0
  50. minder/llm/openai.py +89 -0
  51. minder/models/__init__.py +109 -0
  52. minder/models/base.py +10 -0
  53. minder/models/client.py +137 -0
  54. minder/models/document.py +34 -0
  55. minder/models/error.py +32 -0
  56. minder/models/graph.py +114 -0
  57. minder/models/history.py +32 -0
  58. minder/models/job.py +62 -0
  59. minder/models/prompt.py +41 -0
  60. minder/models/repository.py +62 -0
  61. minder/models/rule.py +68 -0
  62. minder/models/session.py +51 -0
  63. minder/models/skill.py +52 -0
  64. minder/models/user.py +41 -0
  65. minder/models/workflow.py +35 -0
  66. minder/observability/__init__.py +57 -0
  67. minder/observability/audit.py +243 -0
  68. minder/observability/logging.py +253 -0
  69. minder/observability/metrics.py +448 -0
  70. minder/observability/tracing.py +215 -0
  71. minder/presentation/__init__.py +1 -0
  72. minder/presentation/http/__init__.py +1 -0
  73. minder/presentation/http/admin/__init__.py +3 -0
  74. minder/presentation/http/admin/api.py +1309 -0
  75. minder/presentation/http/admin/context.py +94 -0
  76. minder/presentation/http/admin/dashboard.py +111 -0
  77. minder/presentation/http/admin/jobs.py +208 -0
  78. minder/presentation/http/admin/memories.py +185 -0
  79. minder/presentation/http/admin/prompts.py +219 -0
  80. minder/presentation/http/admin/routes.py +127 -0
  81. minder/presentation/http/admin/runtime.py +650 -0
  82. minder/presentation/http/admin/search.py +368 -0
  83. minder/presentation/http/admin/skills.py +230 -0
  84. minder/prompts/__init__.py +646 -0
  85. minder/prompts/formatter.py +142 -0
  86. minder/resources/__init__.py +318 -0
  87. minder/retrieval/__init__.py +5 -0
  88. minder/retrieval/hybrid.py +178 -0
  89. minder/retrieval/mmr.py +116 -0
  90. minder/retrieval/multi_hop.py +115 -0
  91. minder/runtime.py +15 -0
  92. minder/server.py +145 -0
  93. minder/store/__init__.py +64 -0
  94. minder/store/document.py +115 -0
  95. minder/store/error.py +82 -0
  96. minder/store/feedback.py +114 -0
  97. minder/store/graph.py +588 -0
  98. minder/store/history.py +57 -0
  99. minder/store/interfaces.py +512 -0
  100. minder/store/milvus/__init__.py +11 -0
  101. minder/store/milvus/client.py +26 -0
  102. minder/store/milvus/collections.py +15 -0
  103. minder/store/milvus/vector_store.py +232 -0
  104. minder/store/mongodb/__init__.py +11 -0
  105. minder/store/mongodb/client.py +49 -0
  106. minder/store/mongodb/indexes.py +90 -0
  107. minder/store/mongodb/operational_store.py +993 -0
  108. minder/store/relational.py +1087 -0
  109. minder/store/repo_state.py +58 -0
  110. minder/store/rule.py +93 -0
  111. minder/store/vector.py +79 -0
  112. minder/tools/__init__.py +47 -0
  113. minder/tools/auth.py +94 -0
  114. minder/tools/graph.py +839 -0
  115. minder/tools/ingest.py +353 -0
  116. minder/tools/memory.py +381 -0
  117. minder/tools/query.py +307 -0
  118. minder/tools/registry.py +269 -0
  119. minder/tools/repo_scanner.py +1266 -0
  120. minder/tools/search.py +15 -0
  121. minder/tools/session.py +316 -0
  122. minder/tools/skills.py +899 -0
  123. minder/tools/workflow.py +215 -0
  124. minder/transport/__init__.py +4 -0
  125. minder/transport/base.py +286 -0
  126. minder/transport/sse.py +252 -0
  127. minder/transport/stdio.py +29 -0
  128. minder_cli-0.2.0.dist-info/METADATA +318 -0
  129. minder_cli-0.2.0.dist-info/RECORD +132 -0
  130. minder_cli-0.2.0.dist-info/WHEEL +4 -0
  131. minder_cli-0.2.0.dist-info/entry_points.txt +2 -0
  132. minder_cli-0.2.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,252 @@
1
+ import json
2
+ import logging
3
+ from contextlib import AsyncExitStack
4
+ from contextlib import asynccontextmanager
5
+ from typing import AsyncIterator
6
+ from typing import Any
7
+
8
+ import uvicorn
9
+ from starlette.applications import Starlette
10
+ from starlette.middleware.cors import CORSMiddleware
11
+ from starlette.requests import Request
12
+ from starlette.responses import JSONResponse
13
+ from starlette.routing import BaseRoute
14
+ from starlette.types import ASGIApp
15
+ from starlette.types import Receive
16
+ from starlette.types import Scope
17
+ from starlette.types import Send
18
+ from minder.auth.context import set_current_principal
19
+ from minder.auth.service import AuthService
20
+ from minder.config import MinderConfig
21
+ from minder.presentation.http.admin.routes import dashboard_dev_origin
22
+ from minder.observability.logging import (
23
+ AccessLogMiddleware,
24
+ CorrelationIdMiddleware,
25
+ GlobalExceptionMiddleware,
26
+ )
27
+ from minder.store.interfaces import ICacheProvider, IOperationalStore
28
+ from minder.transport.base import BaseTransport
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ class MCPCompatApp:
34
+ def __init__(self, *, sse_app: ASGIApp, streamable_http_app: ASGIApp) -> None:
35
+ self._sse_app = sse_app
36
+ self._streamable_http_app = streamable_http_app
37
+
38
+ @staticmethod
39
+ def _rewrite_path(scope: Scope, path: str) -> Scope:
40
+ updated_scope = dict(scope)
41
+ updated_scope["path"] = path
42
+ updated_scope["raw_path"] = path.encode("utf-8")
43
+ return updated_scope
44
+
45
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
46
+ if scope["type"] != "http":
47
+ await self._streamable_http_app(scope, receive, send)
48
+ return
49
+
50
+ path = scope.get("path", "")
51
+ method = scope.get("method", "GET").upper()
52
+
53
+ if path == "/sse" and method in {"GET", "HEAD"}:
54
+ await self._sse_app(scope, receive, send)
55
+ return
56
+
57
+ if path.startswith("/messages"):
58
+ await self._sse_app(scope, receive, send)
59
+ return
60
+
61
+ if path == "/sse" and method in {"POST", "DELETE", "OPTIONS"}:
62
+ await self._streamable_http_app(
63
+ self._rewrite_path(scope, "/mcp"), receive, send
64
+ )
65
+ return
66
+
67
+ await self._streamable_http_app(scope, receive, send)
68
+
69
+
70
+ class SSEAuthMiddleware:
71
+ def __init__(self, app: Any, auth_service: AuthService) -> None:
72
+ self.app = app
73
+ self.auth_service = auth_service
74
+
75
+ async def __call__(self, scope: Any, receive: Any, send: Any) -> Any:
76
+ if scope["type"] != "http":
77
+ return await self.app(scope, receive, send)
78
+
79
+ headers = dict(scope.get("headers", []))
80
+ auth_header = headers.get(b"authorization")
81
+ client_key_header = headers.get(b"x-minder-client-key")
82
+
83
+ # If we have an auth header and it's a POST,
84
+ # we'll intercept the body to inject the authorization token as a hidden param.
85
+ if (auth_header or client_key_header) and scope["method"] == "POST":
86
+ auth_token = auth_header.decode("utf-8") if auth_header else None
87
+ client_key = (
88
+ client_key_header.decode("utf-8") if client_key_header else None
89
+ )
90
+
91
+ async def wrapped_receive() -> Any:
92
+ message = await receive()
93
+ if message["type"] == "http.request":
94
+ body = message.get("body", b"")
95
+ if body:
96
+ try:
97
+ # Attempt to inject _authorization into the JSON-RPC arguments
98
+ data = json.loads(body)
99
+ if (
100
+ isinstance(data, dict)
101
+ and data.get("method") == "tools/call"
102
+ ):
103
+ params = data.setdefault("params", {})
104
+ args = params.setdefault("arguments", {})
105
+ # Only inject if not already present
106
+ if auth_token and "minder_authorization" not in args:
107
+ args["minder_authorization"] = auth_token
108
+ if client_key and "minder_client_key" not in args:
109
+ args["minder_client_key"] = client_key
110
+ new_body = json.dumps(data).encode("utf-8")
111
+ message["body"] = new_body
112
+ except Exception:
113
+ pass # Fallback to original body if parsing fails
114
+ return message
115
+
116
+ # Also set the context var just in case it can propagate
117
+ try:
118
+ if client_key:
119
+ principal = await self.auth_service.get_principal_from_client_key(
120
+ client_key
121
+ )
122
+ else:
123
+ assert auth_token is not None
124
+ token = auth_token.strip()
125
+ if token.lower().startswith("bearer "):
126
+ token = token[7:].strip()
127
+ principal = await self.auth_service.get_principal_from_token(token)
128
+ set_current_principal(principal)
129
+ except Exception:
130
+ pass
131
+
132
+ return await self.app(scope, wrapped_receive, send)
133
+
134
+ return await self.app(scope, receive, send)
135
+
136
+
137
+ class SSETransport(BaseTransport):
138
+ transport_name = "sse"
139
+
140
+ def __init__(
141
+ self,
142
+ *,
143
+ config: MinderConfig,
144
+ store: IOperationalStore | None = None,
145
+ auth_service: AuthService | None = None,
146
+ extra_routes: list[BaseRoute] | None = None,
147
+ cache_provider: ICacheProvider,
148
+ ) -> None:
149
+ super().__init__(
150
+ config=config,
151
+ auth_service=auth_service,
152
+ cache_provider=cache_provider,
153
+ store=store,
154
+ )
155
+ self._extra_routes = list(extra_routes or [])
156
+ self._legacy_sse_app: Starlette | None = None
157
+ self._streamable_http_app: Starlette | None = None
158
+
159
+ def extend_routes(self, routes: list[BaseRoute]) -> None:
160
+ self._extra_routes.extend(routes)
161
+
162
+ @staticmethod
163
+ async def _oauth_protected_resource_metadata(request: Request) -> JSONResponse:
164
+ base_url = str(request.base_url).rstrip("/")
165
+ return JSONResponse(
166
+ {
167
+ "resource": f"{base_url}/mcp",
168
+ "authorization_servers": [],
169
+ }
170
+ )
171
+
172
+ @asynccontextmanager
173
+ async def _app_lifespan(self, app: Starlette) -> AsyncIterator[None]:
174
+ del app
175
+ async with AsyncExitStack() as stack:
176
+ if self._legacy_sse_app is not None:
177
+ await stack.enter_async_context(
178
+ self._legacy_sse_app.router.lifespan_context(self._legacy_sse_app)
179
+ )
180
+ if self._streamable_http_app is not None:
181
+ await stack.enter_async_context(
182
+ self._streamable_http_app.router.lifespan_context(
183
+ self._streamable_http_app
184
+ )
185
+ )
186
+ yield
187
+
188
+ def build_starlette_app(self) -> Starlette:
189
+ legacy_sse_app: Starlette = self._server.sse_app()
190
+ streamable_http_app: Starlette = self._server.streamable_http_app()
191
+ self._legacy_sse_app = legacy_sse_app
192
+ self._streamable_http_app = streamable_http_app
193
+ mcp_app = MCPCompatApp(
194
+ sse_app=legacy_sse_app,
195
+ streamable_http_app=streamable_http_app,
196
+ )
197
+
198
+ app = Starlette(debug=True, lifespan=self._app_lifespan)
199
+ app.state.store = self._store
200
+ app.state.config = self._config
201
+ dev_origin = dashboard_dev_origin(self._config)
202
+ if dev_origin:
203
+ app.add_middleware(
204
+ CORSMiddleware,
205
+ allow_origins=[dev_origin],
206
+ allow_credentials=True,
207
+ allow_methods=["*"],
208
+ allow_headers=["*"],
209
+ )
210
+
211
+ # Observability middleware
212
+ app.add_middleware(GlobalExceptionMiddleware)
213
+ app.add_middleware(CorrelationIdMiddleware)
214
+ app.add_middleware(AccessLogMiddleware)
215
+
216
+ if self._middleware and self._middleware._auth:
217
+ app.add_middleware(SSEAuthMiddleware, auth_service=self._middleware._auth)
218
+
219
+ for route in self._extra_routes:
220
+ app.router.routes.append(route)
221
+
222
+ app.add_route(
223
+ "/.well-known/oauth-protected-resource",
224
+ self._oauth_protected_resource_metadata,
225
+ methods=["GET"],
226
+ )
227
+ app.add_route(
228
+ "/.well-known/oauth-protected-resource/sse",
229
+ self._oauth_protected_resource_metadata,
230
+ methods=["GET"],
231
+ )
232
+ app.add_route(
233
+ "/.well-known/oauth-protected-resource/mcp",
234
+ self._oauth_protected_resource_metadata,
235
+ methods=["GET"],
236
+ )
237
+
238
+ app.mount("/", mcp_app)
239
+ return app
240
+
241
+ async def run(self) -> None:
242
+ """Custom run loop to handle starlette app with middleware."""
243
+ app = self.build_starlette_app()
244
+
245
+ config = uvicorn.Config(
246
+ app,
247
+ host=self._config.server.host,
248
+ port=self._config.server.port,
249
+ log_level="info",
250
+ )
251
+ server = uvicorn.Server(config)
252
+ await server.serve()
@@ -0,0 +1,29 @@
1
+ import os
2
+
3
+ from minder.auth.service import AuthService
4
+ from minder.config import MinderConfig
5
+ from minder.store.interfaces import ICacheProvider, IOperationalStore
6
+ from minder.transport.base import BaseTransport
7
+
8
+
9
+ class StdioTransport(BaseTransport):
10
+ transport_name = "stdio"
11
+
12
+ def __init__(
13
+ self,
14
+ *,
15
+ config: MinderConfig,
16
+ store: IOperationalStore | None = None,
17
+ auth_service: AuthService | None = None,
18
+ cache_provider: ICacheProvider,
19
+ ) -> None:
20
+ super().__init__(
21
+ config=config,
22
+ auth_service=auth_service,
23
+ cache_provider=cache_provider,
24
+ store=store,
25
+ )
26
+
27
+ def _default_client_key(self) -> str | None:
28
+ client_key = os.getenv("MINDER_CLIENT_API_KEY", "").strip()
29
+ return client_key or None
@@ -0,0 +1,318 @@
1
+ Metadata-Version: 2.4
2
+ Name: minder-cli
3
+ Version: 0.2.0
4
+ Summary: Minder is an AI MCP server for manage your LLM flow development and memory of AI agent.
5
+ Project-URL: Homepage, https://github.com/hiimtrung/minder
6
+ Project-URL: Repository, https://github.com/hiimtrung/minder
7
+ Project-URL: Documentation, https://github.com/hiimtrung/minder/tree/main/docs
8
+ License-File: LICENSE
9
+ Keywords: ai,cli,code-search,mcp,workflow
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Requires-Python: >=3.14
18
+ Requires-Dist: fastapi>=0.136.0
19
+ Requires-Dist: httpx>=0.27.0
20
+ Provides-Extra: server
21
+ Requires-Dist: aiosqlite>=0.21.0; extra == 'server'
22
+ Requires-Dist: langgraph>=1.1.4; extra == 'server'
23
+ Requires-Dist: litellm>=1.83.1; extra == 'server'
24
+ Requires-Dist: llama-cpp-python>=0.3.20; extra == 'server'
25
+ Requires-Dist: mcp>=1.26.0; extra == 'server'
26
+ Requires-Dist: motor>=3.7.0; extra == 'server'
27
+ Requires-Dist: passlib[bcrypt]>=1.7.4; extra == 'server'
28
+ Requires-Dist: prometheus-client>=0.24.1; extra == 'server'
29
+ Requires-Dist: pydantic-settings[toml]>=2.13.1; extra == 'server'
30
+ Requires-Dist: pydantic>=2.12.5; extra == 'server'
31
+ Requires-Dist: pyjwt>=2.12.1; extra == 'server'
32
+ Requires-Dist: pymilvus>=2.5.0; extra == 'server'
33
+ Requires-Dist: redis[hiredis]>=6.0.0; extra == 'server'
34
+ Requires-Dist: sqlalchemy[asyncio]>=2.0.48; extra == 'server'
35
+ Description-Content-Type: text/markdown
36
+
37
+ # Minder
38
+
39
+ <p align="center">
40
+ <img src="favicon.png" width="220" alt="Minder logo" />
41
+ </p>
42
+
43
+ <p align="center">
44
+ <strong>Self-hosted MCP platform for repo-aware engineering intelligence.</strong><br/>
45
+ Search smarter, govern workflows, persist memory, onboard clients, and run over <code>SSE</code> or <code>stdio</code>.
46
+ </p>
47
+
48
+ ---
49
+
50
+ ## Why Minder
51
+
52
+ - Repository-aware retrieval across code, docs, and historical errors.
53
+ - Workflow governance that keeps delivery phases explicit and auditable.
54
+ - Persistent memory and session context for long-running engineering tasks.
55
+ - Built-in admin dashboard plus API-first client onboarding.
56
+ - Single platform for Codex, Copilot-style MCP clients, Claude Desktop, and CLI automation.
57
+
58
+ ---
59
+
60
+ ## Quick Start (Local in Minutes)
61
+
62
+ ### 1) Download GGUF models
63
+
64
+ ```bash
65
+ ./scripts/download_models.sh
66
+ ```
67
+
68
+ Models are saved to `~/.minder/models`.
69
+
70
+ ### 2) Prepare environment
71
+
72
+ ```bash
73
+ cp .env.example .env
74
+ cp src/dashboard/.env.example src/dashboard/.env
75
+ ```
76
+
77
+ Default local layout:
78
+ - Minder API/Gateway: `http://localhost:8800`
79
+ - Dashboard dev server: `http://localhost:8808/dashboard`
80
+
81
+ ### 3) Start infra services
82
+
83
+ ```bash
84
+ docker compose -f docker/docker-compose.local.yml up -d
85
+ ```
86
+
87
+ This starts MongoDB, Redis, Milvus Standalone (plus etcd + MinIO).
88
+
89
+ ### 4) Run backend
90
+
91
+ ```bash
92
+ PYTHONPATH=src UV_CACHE_DIR=.uv-cache uv run python -m minder.server
93
+ ```
94
+
95
+ ### 5) Run dashboard (dev mode)
96
+
97
+ ```bash
98
+ cd src/dashboard
99
+ bun install
100
+ bun run dev
101
+ ```
102
+
103
+ Or build static assets for serving via Minder on `:8800`:
104
+
105
+ ```bash
106
+ cd src/dashboard && bun run build
107
+ ```
108
+
109
+ ### 6) Bootstrap first admin
110
+
111
+ Open [http://localhost:8800/dashboard/setup](http://localhost:8800/dashboard/setup) and create the first admin.
112
+
113
+ Minder returns the bootstrap API key (`mk_...`) exactly once. Save it immediately.
114
+
115
+ ### 7) Sign in
116
+
117
+ Open [http://localhost:8800/dashboard/login](http://localhost:8800/dashboard/login) and authenticate with the `mk_...` key.
118
+
119
+ ---
120
+
121
+ ## System Architecture
122
+
123
+ ```mermaid
124
+ flowchart TB
125
+ Browser["Browser Admin"] --> Gateway["Gateway :8800"]
126
+ MCP["Codex · Copilot · Claude Desktop · stdio"] --> Gateway
127
+
128
+ Gateway --> Dashboard["Astro Dashboard\\n/dashboard/*"]
129
+ Gateway --> AdminAPI["Admin HTTP\\n/v1/admin/*"]
130
+ Gateway --> AuthAPI["Token Exchange · Gateway Test\\n/v1/auth/* · /v1/gateway/*"]
131
+ Gateway --> MCPTools["MCP Tool Surface\\nSSE · stdio"]
132
+
133
+ AdminAPI & AuthAPI & MCPTools --> UseCases["Application Use Cases"]
134
+
135
+ UseCases --> Auth["Auth · RBAC · Rate Limiting"]
136
+ UseCases --> Services["Workflow · Memory · Session · Query"]
137
+
138
+ Services --> Mongo["MongoDB"]
139
+ Services --> Redis["Redis"]
140
+ Services --> Milvus["Milvus Standalone"]
141
+ Services --> LLM["Gemma GGUF\\nllama-cpp-python"]
142
+ ```
143
+
144
+ ### Clean runtime layers
145
+
146
+ ```text
147
+ Presentation -> src/minder/presentation/http/admin (HTTP routes, DTOs)
148
+ src/dashboard (Astro admin console)
149
+ Application -> src/minder/application/admin (use cases)
150
+ Domain -> src/minder/models (entities, value objects)
151
+ Infrastructure -> src/minder/store (MongoDB, Milvus, Redis adapters)
152
+ src/minder/auth (principals, middleware, rate limiter)
153
+ src/minder/graph (LangGraph pipeline, nodes)
154
+ ```
155
+
156
+ ### Runtime stack
157
+
158
+ | Service | Role | Default Port |
159
+ | --- | --- | --- |
160
+ | Minder API | MCP server, admin HTTP, token exchange | `8800` |
161
+ | Astro Dashboard | Admin console (dev standalone, prod proxied) | `8808` (dev) |
162
+ | MongoDB 7 | Users, clients, sessions | `27017` |
163
+ | Redis 7 | Cache, rate limiting, token sessions | `6379` |
164
+ | Milvus Standalone | Vector index for docs/code/errors | `19530` |
165
+
166
+ ---
167
+
168
+ ## MCP Tool Surface
169
+
170
+ | Tool | Description |
171
+ | --- | --- |
172
+ | `minder_query` | End-to-end RAG pipeline: retrieve + reason + verify |
173
+ | `minder_search_code` | Semantic code retrieval across indexed repositories |
174
+ | `minder_search_errors` | Error retrieval with troubleshooting context |
175
+ | `minder_search` | General semantic search over project knowledge |
176
+ | `minder_memory_recall` | Retrieve persisted memory entries |
177
+ | `minder_workflow_get` | Read current workflow state |
178
+ | `minder_workflow_step` | Move workflow to the next step |
179
+
180
+ ---
181
+
182
+ ## CLI Distribution
183
+
184
+ Install from PyPI:
185
+
186
+ ```bash
187
+ uv tool install minder
188
+ # or
189
+ pipx install minder
190
+ ```
191
+
192
+ Typical flow:
193
+
194
+ ```bash
195
+ minder login --client-key mkc_your_client_key --server-url http://localhost:8800/sse
196
+ minder install-ide --target vscode --target claude-code
197
+ minder sync --repo-id <repository-uuid>
198
+ ```
199
+
200
+ Highlights:
201
+ - `minder install-ide` scaffolds repo-local MCP/instruction assets for VS Code, Cursor, and Claude Code.
202
+ - `minder sync` auto-detects cross-repo `branch_relationships` via `.gitmodules` and optional `.minder/branch-topology.toml`.
203
+
204
+ Update commands:
205
+
206
+ ```bash
207
+ minder check-update
208
+ minder self-update --component cli
209
+ minder self-update --component server
210
+ ```
211
+
212
+ `self-update --component server` uses PowerShell installer on Windows and bash installer on macOS/Linux.
213
+
214
+ ---
215
+
216
+ ## Operator Playbooks
217
+
218
+ ### Onboard an MCP client
219
+
220
+ 1. Open **Client Registry** in dashboard.
221
+ 2. Create client with name, slug, tool scopes, repo scopes.
222
+ 3. Save the issued key (`mkc_...`) immediately (shown once).
223
+ 4. Use the key via:
224
+
225
+ | Transport | Auth Mechanism |
226
+ | --- | --- |
227
+ | SSE | `X-Minder-Client-Key: mkc_...` header |
228
+ | stdio | `MINDER_CLIENT_API_KEY=mkc_...` env var |
229
+ | OAuth-style | `POST /v1/auth/token-exchange` with client key |
230
+
231
+ ### Rotate or revoke key
232
+
233
+ From client detail page:
234
+ - **Rotate Key**: issues new key and invalidates old key.
235
+ - **Revoke**: permanently blocks client.
236
+
237
+ Both actions are audit logged.
238
+
239
+ ### Recover admin access
240
+
241
+ ```bash
242
+ PYTHONPATH=src UV_CACHE_DIR=.uv-cache uv run python scripts/reset_admin_api_key.py \
243
+ --username <admin-username>
244
+ ```
245
+
246
+ ### Production deployment
247
+
248
+ ```bash
249
+ docker compose -f docker/docker-compose.yml up -d
250
+ ```
251
+
252
+ Production topology runs `gateway`, `minder-api`, and `dashboard` behind a single public origin (`:8800`).
253
+
254
+ ---
255
+
256
+ ## Configuration
257
+
258
+ Config load order:
259
+ 1. `minder.toml`
260
+ 2. `MINDER_` environment overrides
261
+
262
+ Key variables:
263
+
264
+ | Variable | Default | Purpose |
265
+ | --- | --- | --- |
266
+ | `MINDER_SERVER__PORT` | `8800` | HTTP listen port |
267
+ | `MINDER_MONGODB__URI` | `mongodb://localhost:27017` | MongoDB URI |
268
+ | `MINDER_REDIS__URI` | `redis://localhost:6379/0` | Redis URI |
269
+ | `MINDER_VECTOR_STORE__URI` | `http://localhost:19530` | Milvus endpoint |
270
+ | `MINDER_LLM__MODEL_PATH` | `~/.minder/models/gemma-4-e2b-it-Q8_0.gguf` | Local LLM model |
271
+ | `MINDER_EMBEDDING__MODEL_PATH` | `~/.minder/models/embeddinggemma-300M-Q8_0.gguf` | Embedding model |
272
+ | `MINDER_CACHE__PROVIDER` | `redis` | `redis` or `lru` |
273
+ | `MINDER_WORKFLOW__ORCHESTRATION_RUNTIME` | `langgraph` | `langgraph` or `simple` |
274
+
275
+ ---
276
+
277
+ ## Prerequisites
278
+
279
+ | Requirement | Version |
280
+ | --- | --- |
281
+ | Python | `>= 3.14` |
282
+ | uv | latest |
283
+ | Docker + Compose | v2+ |
284
+ | Bun | `1.2.21+` (dashboard dev only) |
285
+
286
+ ---
287
+
288
+ ## Testing
289
+
290
+ Run all tests:
291
+
292
+ ```bash
293
+ UV_CACHE_DIR=.uv-cache uv run pytest
294
+ ```
295
+
296
+ Phase-specific gates:
297
+
298
+ ```bash
299
+ uv run pytest tests/integration/test_phase3_gate.py
300
+ uv run pytest tests/e2e/test_phase4_gateway_auth.py
301
+ uv run pytest tests/integration/test_phase4_3_console_gate.py
302
+ ```
303
+
304
+ Note: integration tests that hit Milvus/MongoDB/Redis require Docker services running.
305
+
306
+ ---
307
+
308
+ ## Documentation Index
309
+
310
+ - [System Design](docs/system-design.md)
311
+ - [Project Plan](docs/PLAN.md)
312
+ - [Project Progress](docs/PROJECT_PROGRESS.md)
313
+ - [Task Breakdown](docs/TASK_BREAKDOWN.md)
314
+ - [Local Setup Guide](docs/guides/local-setup.md)
315
+ - [Minder CLI Guide](docs/guides/minder-cli.md)
316
+ - [Admin & Client Onboarding](docs/guides/admin-client-onboarding.md)
317
+ - [Production Deployment](docs/guides/production-deployment.md)
318
+ - [Gateway Auth Design](docs/design/mcp-gateway-auth-dashboard.md)