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.
- minder/__init__.py +12 -0
- minder/api/routers/prompts.py +177 -0
- minder/application/__init__.py +1 -0
- minder/application/admin/__init__.py +11 -0
- minder/application/admin/dto.py +453 -0
- minder/application/admin/jobs.py +327 -0
- minder/application/admin/use_cases.py +1895 -0
- minder/auth/__init__.py +12 -0
- minder/auth/context.py +26 -0
- minder/auth/middleware.py +70 -0
- minder/auth/principal.py +59 -0
- minder/auth/rate_limiter.py +89 -0
- minder/auth/rbac.py +60 -0
- minder/auth/service.py +541 -0
- minder/bootstrap/__init__.py +9 -0
- minder/bootstrap/providers.py +109 -0
- minder/bootstrap/transport.py +807 -0
- minder/cache/__init__.py +10 -0
- minder/cache/providers.py +140 -0
- minder/chunking/__init__.py +4 -0
- minder/chunking/code_splitter.py +184 -0
- minder/chunking/splitter.py +136 -0
- minder/cli.py +1542 -0
- minder/config.py +179 -0
- minder/continuity.py +363 -0
- minder/dev.py +160 -0
- minder/embedding/__init__.py +9 -0
- minder/embedding/base.py +7 -0
- minder/embedding/local.py +65 -0
- minder/embedding/openai.py +7 -0
- minder/graph/__init__.py +11 -0
- minder/graph/edges.py +13 -0
- minder/graph/executor.py +127 -0
- minder/graph/graph.py +263 -0
- minder/graph/nodes/__init__.py +27 -0
- minder/graph/nodes/evaluator.py +21 -0
- minder/graph/nodes/guard.py +64 -0
- minder/graph/nodes/llm.py +59 -0
- minder/graph/nodes/planning.py +30 -0
- minder/graph/nodes/reasoning.py +87 -0
- minder/graph/nodes/reranker.py +141 -0
- minder/graph/nodes/retriever.py +86 -0
- minder/graph/nodes/verification.py +230 -0
- minder/graph/nodes/workflow_planner.py +250 -0
- minder/graph/runtime.py +15 -0
- minder/graph/state.py +26 -0
- minder/llm/__init__.py +5 -0
- minder/llm/base.py +14 -0
- minder/llm/local.py +381 -0
- minder/llm/openai.py +89 -0
- minder/models/__init__.py +109 -0
- minder/models/base.py +10 -0
- minder/models/client.py +137 -0
- minder/models/document.py +34 -0
- minder/models/error.py +32 -0
- minder/models/graph.py +114 -0
- minder/models/history.py +32 -0
- minder/models/job.py +62 -0
- minder/models/prompt.py +41 -0
- minder/models/repository.py +62 -0
- minder/models/rule.py +68 -0
- minder/models/session.py +51 -0
- minder/models/skill.py +52 -0
- minder/models/user.py +41 -0
- minder/models/workflow.py +35 -0
- minder/observability/__init__.py +57 -0
- minder/observability/audit.py +243 -0
- minder/observability/logging.py +253 -0
- minder/observability/metrics.py +448 -0
- minder/observability/tracing.py +215 -0
- minder/presentation/__init__.py +1 -0
- minder/presentation/http/__init__.py +1 -0
- minder/presentation/http/admin/__init__.py +3 -0
- minder/presentation/http/admin/api.py +1309 -0
- minder/presentation/http/admin/context.py +94 -0
- minder/presentation/http/admin/dashboard.py +111 -0
- minder/presentation/http/admin/jobs.py +208 -0
- minder/presentation/http/admin/memories.py +185 -0
- minder/presentation/http/admin/prompts.py +219 -0
- minder/presentation/http/admin/routes.py +127 -0
- minder/presentation/http/admin/runtime.py +650 -0
- minder/presentation/http/admin/search.py +368 -0
- minder/presentation/http/admin/skills.py +230 -0
- minder/prompts/__init__.py +646 -0
- minder/prompts/formatter.py +142 -0
- minder/resources/__init__.py +318 -0
- minder/retrieval/__init__.py +5 -0
- minder/retrieval/hybrid.py +178 -0
- minder/retrieval/mmr.py +116 -0
- minder/retrieval/multi_hop.py +115 -0
- minder/runtime.py +15 -0
- minder/server.py +145 -0
- minder/store/__init__.py +64 -0
- minder/store/document.py +115 -0
- minder/store/error.py +82 -0
- minder/store/feedback.py +114 -0
- minder/store/graph.py +588 -0
- minder/store/history.py +57 -0
- minder/store/interfaces.py +512 -0
- minder/store/milvus/__init__.py +11 -0
- minder/store/milvus/client.py +26 -0
- minder/store/milvus/collections.py +15 -0
- minder/store/milvus/vector_store.py +232 -0
- minder/store/mongodb/__init__.py +11 -0
- minder/store/mongodb/client.py +49 -0
- minder/store/mongodb/indexes.py +90 -0
- minder/store/mongodb/operational_store.py +993 -0
- minder/store/relational.py +1087 -0
- minder/store/repo_state.py +58 -0
- minder/store/rule.py +93 -0
- minder/store/vector.py +79 -0
- minder/tools/__init__.py +47 -0
- minder/tools/auth.py +94 -0
- minder/tools/graph.py +839 -0
- minder/tools/ingest.py +353 -0
- minder/tools/memory.py +381 -0
- minder/tools/query.py +307 -0
- minder/tools/registry.py +269 -0
- minder/tools/repo_scanner.py +1266 -0
- minder/tools/search.py +15 -0
- minder/tools/session.py +316 -0
- minder/tools/skills.py +899 -0
- minder/tools/workflow.py +215 -0
- minder/transport/__init__.py +4 -0
- minder/transport/base.py +286 -0
- minder/transport/sse.py +252 -0
- minder/transport/stdio.py +29 -0
- minder_cli-0.2.0.dist-info/METADATA +318 -0
- minder_cli-0.2.0.dist-info/RECORD +132 -0
- minder_cli-0.2.0.dist-info/WHEEL +4 -0
- minder_cli-0.2.0.dist-info/entry_points.txt +2 -0
- minder_cli-0.2.0.dist-info/licenses/LICENSE +201 -0
minder/transport/sse.py
ADDED
|
@@ -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)
|