feat(mcp-server): Phase 3 — product namespaces for MindLyst, LysnrAI, JarvisJr (22 new tools)
This commit is contained in:
parent
852cb18a5c
commit
f780f0b409
@ -7,6 +7,11 @@ LOG_LEVEL=info
|
|||||||
PLATFORM_SERVICE_URL=http://localhost:4003
|
PLATFORM_SERVICE_URL=http://localhost:4003
|
||||||
EXTRACTION_SERVICE_URL=http://localhost:4005
|
EXTRACTION_SERVICE_URL=http://localhost:4005
|
||||||
|
|
||||||
|
# Product-specific backend URLs (Phase 3 product namespaces)
|
||||||
|
MINDLYST_BACKEND_URL=http://localhost:4014
|
||||||
|
LYSNRAI_BACKEND_URL=http://localhost:4015
|
||||||
|
JARVISJR_BACKEND_URL=http://localhost:4012
|
||||||
|
|
||||||
# Auth — same JWT_SECRET as platform-service (tokens issued there are validated here)
|
# Auth — same JWT_SECRET as platform-service (tokens issued there are validated here)
|
||||||
JWT_SECRET=change-me-in-production
|
JWT_SECRET=change-me-in-production
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,10 @@ const envSchema = z.object({
|
|||||||
JWT_SECRET: z.string().min(1, 'JWT_SECRET is required'),
|
JWT_SECRET: z.string().min(1, 'JWT_SECRET is required'),
|
||||||
PLATFORM_SERVICE_URL: z.string().default('http://localhost:4003'),
|
PLATFORM_SERVICE_URL: z.string().default('http://localhost:4003'),
|
||||||
EXTRACTION_SERVICE_URL: z.string().default('http://localhost:4005'),
|
EXTRACTION_SERVICE_URL: z.string().default('http://localhost:4005'),
|
||||||
|
/** Product-specific backend URLs */
|
||||||
|
MINDLYST_BACKEND_URL: z.string().default('http://localhost:4014'),
|
||||||
|
LYSNRAI_BACKEND_URL: z.string().default('http://localhost:4015'),
|
||||||
|
JARVISJR_BACKEND_URL: z.string().default('http://localhost:4012'),
|
||||||
/** Max items returned per tool call query (hard cap) */
|
/** Max items returned per tool call query (hard cap) */
|
||||||
QUERY_MAX_LIMIT: z.coerce.number().default(100),
|
QUERY_MAX_LIMIT: z.coerce.number().default(100),
|
||||||
/** Default items per tool call query */
|
/** Default items per tool call query */
|
||||||
|
|||||||
177
services/mcp-server/src/lib/jarvis-client.ts
Normal file
177
services/mcp-server/src/lib/jarvis-client.ts
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
/**
|
||||||
|
* JarvisJr backend client — typed HTTP wrappers for the jarvisjr-backend (port 4012).
|
||||||
|
*
|
||||||
|
* Auth: Bearer token from the caller's JWT (same JWT_SECRET as platform-service).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { config } from './config.js';
|
||||||
|
|
||||||
|
export interface JarvisClientOptions {
|
||||||
|
token?: string;
|
||||||
|
requestId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Shared fetch helper ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async function jarvisFetch<T>(
|
||||||
|
path: string,
|
||||||
|
init: RequestInit,
|
||||||
|
opts: JarvisClientOptions
|
||||||
|
): Promise<T> {
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(opts.token ? { Authorization: `Bearer ${opts.token}` } : {}),
|
||||||
|
...(opts.requestId ? { 'x-request-id': opts.requestId } : {}),
|
||||||
|
};
|
||||||
|
const res = await fetch(`${config.JARVISJR_BACKEND_URL}${path}`, {
|
||||||
|
...init,
|
||||||
|
headers: { ...((init.headers as Record<string, string>) ?? {}), ...headers },
|
||||||
|
signal: AbortSignal.timeout(15_000),
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const body = await res.text().catch(() => '');
|
||||||
|
throw new Error(`jarvisjr-backend ${init.method ?? 'GET'} ${path} → ${res.status}: ${body}`);
|
||||||
|
}
|
||||||
|
return res.json() as Promise<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Agents ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface JarvisAgentDoc {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
productId: string;
|
||||||
|
name: string;
|
||||||
|
role: string;
|
||||||
|
systemPrompt: string;
|
||||||
|
voiceId?: string;
|
||||||
|
coachingFramework?: string;
|
||||||
|
accentColor?: string;
|
||||||
|
welcomeMessage?: string;
|
||||||
|
sessionLength?: number;
|
||||||
|
difficultyLevel?: string;
|
||||||
|
language?: string;
|
||||||
|
privacyLevel?: string;
|
||||||
|
checkInSchedule?: string;
|
||||||
|
isTemplate?: boolean;
|
||||||
|
templateSource?: string;
|
||||||
|
totalSessions: number;
|
||||||
|
lastSessionAt: string | null;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function jarvisAgentsList(
|
||||||
|
params: { limit?: number; offset?: number },
|
||||||
|
opts: JarvisClientOptions
|
||||||
|
): Promise<{ agents: JarvisAgentDoc[]; total: number }> {
|
||||||
|
const qs = new URLSearchParams();
|
||||||
|
if (params.limit !== undefined) qs.set('limit', String(params.limit));
|
||||||
|
if (params.offset !== undefined) qs.set('offset', String(params.offset));
|
||||||
|
const q = qs.toString();
|
||||||
|
return jarvisFetch(`/jarvis/agents${q ? `?${q}` : ''}`, { method: 'GET' }, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function jarvisAgentDuplicate(
|
||||||
|
agentId: string,
|
||||||
|
opts: JarvisClientOptions
|
||||||
|
): Promise<JarvisAgentDoc> {
|
||||||
|
return jarvisFetch(`/jarvis/agents/${agentId}/duplicate`, { method: 'POST' }, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Sessions ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface JarvisSessionDoc {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
productId: string;
|
||||||
|
agentId: string;
|
||||||
|
mode: string;
|
||||||
|
status: 'active' | 'completed';
|
||||||
|
summary?: string;
|
||||||
|
coachingNotes?: string[];
|
||||||
|
skillMetrics?: Record<string, number>;
|
||||||
|
duration?: number;
|
||||||
|
messageCount: number;
|
||||||
|
createdAt: string;
|
||||||
|
completedAt: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JarvisSessionStats {
|
||||||
|
totalSessions: number;
|
||||||
|
totalDurationMinutes: number;
|
||||||
|
currentStreak: number;
|
||||||
|
longestStreak: number;
|
||||||
|
perAgent: Record<string, { sessions: number; avgDuration: number }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function jarvisSessionsList(
|
||||||
|
params: { limit?: number; offset?: number; agentId?: string },
|
||||||
|
opts: JarvisClientOptions
|
||||||
|
): Promise<{ sessions: JarvisSessionDoc[]; total: number }> {
|
||||||
|
const qs = new URLSearchParams();
|
||||||
|
if (params.limit !== undefined) qs.set('limit', String(params.limit));
|
||||||
|
if (params.offset !== undefined) qs.set('offset', String(params.offset));
|
||||||
|
if (params.agentId) qs.set('agentId', params.agentId);
|
||||||
|
const q = qs.toString();
|
||||||
|
return jarvisFetch(`/jarvis/sessions${q ? `?${q}` : ''}`, { method: 'GET' }, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function jarvisSessionsGetStats(opts: JarvisClientOptions): Promise<JarvisSessionStats> {
|
||||||
|
return jarvisFetch('/jarvis/sessions/stats', { method: 'GET' }, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Memory ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface JarvisMemoryDoc {
|
||||||
|
id: string;
|
||||||
|
agentId: string;
|
||||||
|
userId: string;
|
||||||
|
productId: string;
|
||||||
|
sessionId?: string;
|
||||||
|
type: 'skill_note' | 'preference' | 'goal' | 'context' | 'exercise';
|
||||||
|
content: string;
|
||||||
|
importance: number;
|
||||||
|
tags?: string[];
|
||||||
|
createdAt: string;
|
||||||
|
expiresAt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function jarvisMemoryList(
|
||||||
|
agentId: string,
|
||||||
|
params: {
|
||||||
|
type?: string;
|
||||||
|
minImportance?: number;
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
},
|
||||||
|
opts: JarvisClientOptions
|
||||||
|
): Promise<{ memories: JarvisMemoryDoc[]; total: number }> {
|
||||||
|
const qs = new URLSearchParams();
|
||||||
|
if (params.type) qs.set('type', params.type);
|
||||||
|
if (params.minImportance !== undefined) qs.set('minImportance', String(params.minImportance));
|
||||||
|
if (params.limit !== undefined) qs.set('limit', String(params.limit));
|
||||||
|
if (params.offset !== undefined) qs.set('offset', String(params.offset));
|
||||||
|
const q = qs.toString();
|
||||||
|
return jarvisFetch(
|
||||||
|
`/jarvis/agents/${agentId}/memory${q ? `?${q}` : ''}`,
|
||||||
|
{ method: 'GET' },
|
||||||
|
opts
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function jarvisMemoryPrune(
|
||||||
|
agentId: string,
|
||||||
|
opts: JarvisClientOptions
|
||||||
|
): Promise<{ pruned: number }> {
|
||||||
|
return jarvisFetch(`/jarvis/agents/${agentId}/memory/prune`, { method: 'POST' }, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function jarvisMemoryGetContext(
|
||||||
|
agentId: string,
|
||||||
|
limit: number | undefined,
|
||||||
|
opts: JarvisClientOptions
|
||||||
|
): Promise<{ memories: JarvisMemoryDoc[]; count: number }> {
|
||||||
|
const qs = limit !== undefined ? `?limit=${limit}` : '';
|
||||||
|
return jarvisFetch(`/jarvis/agents/${agentId}/memory/context${qs}`, { method: 'GET' }, opts);
|
||||||
|
}
|
||||||
167
services/mcp-server/src/lib/lysnrai-client.ts
Normal file
167
services/mcp-server/src/lib/lysnrai-client.ts
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
/**
|
||||||
|
* LysnrAI backend client — typed HTTP wrappers for the lysnrai-backend (port 4015).
|
||||||
|
*
|
||||||
|
* Auth: Bearer token from the caller's JWT (same JWT_SECRET as platform-service).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { config } from './config.js';
|
||||||
|
|
||||||
|
export interface LysnraiClientOptions {
|
||||||
|
token?: string;
|
||||||
|
requestId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Shared fetch helper ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async function lysnraiFetch<T>(
|
||||||
|
path: string,
|
||||||
|
init: RequestInit,
|
||||||
|
opts: LysnraiClientOptions
|
||||||
|
): Promise<T> {
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(opts.token ? { Authorization: `Bearer ${opts.token}` } : {}),
|
||||||
|
...(opts.requestId ? { 'x-request-id': opts.requestId } : {}),
|
||||||
|
};
|
||||||
|
const res = await fetch(`${config.LYSNRAI_BACKEND_URL}${path}`, {
|
||||||
|
...init,
|
||||||
|
headers: { ...((init.headers as Record<string, string>) ?? {}), ...headers },
|
||||||
|
signal: AbortSignal.timeout(15_000),
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const body = await res.text().catch(() => '');
|
||||||
|
throw new Error(`lysnrai-backend ${init.method ?? 'GET'} ${path} → ${res.status}: ${body}`);
|
||||||
|
}
|
||||||
|
return res.json() as Promise<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Transcripts ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface TranscriptDoc {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
productId: string;
|
||||||
|
rawText?: string;
|
||||||
|
cleanedText?: string;
|
||||||
|
wordCount?: number;
|
||||||
|
duration?: number;
|
||||||
|
tokensUsed?: number;
|
||||||
|
context?: string;
|
||||||
|
source?: string;
|
||||||
|
language?: string;
|
||||||
|
extractions?: unknown[];
|
||||||
|
extractionMetadata?: Record<string, unknown>;
|
||||||
|
extractedAt?: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function lysnraiTranscriptsList(
|
||||||
|
params: { limit?: number; offset?: number },
|
||||||
|
opts: LysnraiClientOptions
|
||||||
|
): Promise<{ transcripts: TranscriptDoc[]; total: number }> {
|
||||||
|
const qs = new URLSearchParams();
|
||||||
|
if (params.limit !== undefined) qs.set('limit', String(params.limit));
|
||||||
|
if (params.offset !== undefined) qs.set('offset', String(params.offset));
|
||||||
|
const q = qs.toString();
|
||||||
|
return lysnraiFetch(`/transcripts${q ? `?${q}` : ''}`, { method: 'GET' }, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function lysnraiTranscriptRunExtraction(
|
||||||
|
transcriptId: string,
|
||||||
|
opts: LysnraiClientOptions
|
||||||
|
): Promise<TranscriptDoc> {
|
||||||
|
return lysnraiFetch(`/transcripts/${transcriptId}/extract`, { method: 'POST' }, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── STT status ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface SttStatusResult {
|
||||||
|
productId: string;
|
||||||
|
primary: 'azure' | 'whisper' | 'none';
|
||||||
|
azureConfigured: boolean;
|
||||||
|
whisperConfigured: boolean;
|
||||||
|
azureRegion: string | null;
|
||||||
|
generatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function lysnraiSttGetBackendStatus(opts: LysnraiClientOptions): Promise<SttStatusResult> {
|
||||||
|
return lysnraiFetch('/transcripts/stt-status', { method: 'GET' }, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Sessions ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface SessionDoc {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
productId: string;
|
||||||
|
title: string;
|
||||||
|
status: 'active' | 'composed' | 'archived';
|
||||||
|
entries: unknown[];
|
||||||
|
composedText: string | null;
|
||||||
|
composedAt: string | null;
|
||||||
|
compositionRating: number | null;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function lysnraiSessionsList(
|
||||||
|
params: { limit?: number; offset?: number; status?: string },
|
||||||
|
opts: LysnraiClientOptions
|
||||||
|
): Promise<{ sessions: SessionDoc[]; total: number }> {
|
||||||
|
const qs = new URLSearchParams();
|
||||||
|
if (params.limit !== undefined) qs.set('limit', String(params.limit));
|
||||||
|
if (params.offset !== undefined) qs.set('offset', String(params.offset));
|
||||||
|
if (params.status) qs.set('status', params.status);
|
||||||
|
const q = qs.toString();
|
||||||
|
return lysnraiFetch(`/sessions${q ? `?${q}` : ''}`, { method: 'GET' }, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Organisations ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface OrgDoc {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
ownerId: string;
|
||||||
|
productId: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function lysnraiOrgsList(
|
||||||
|
params: { limit?: number; offset?: number },
|
||||||
|
opts: LysnraiClientOptions
|
||||||
|
): Promise<{ organizations: OrgDoc[]; total: number }> {
|
||||||
|
const qs = new URLSearchParams();
|
||||||
|
if (params.limit !== undefined) qs.set('limit', String(params.limit));
|
||||||
|
if (params.offset !== undefined) qs.set('offset', String(params.offset));
|
||||||
|
const q = qs.toString();
|
||||||
|
return lysnraiFetch(`/organizations${q ? `?${q}` : ''}`, { method: 'GET' }, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── API tokens ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface ApiTokenDoc {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
productId: string;
|
||||||
|
name: string;
|
||||||
|
lastUsedAt: string | null;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function lysnraiApiTokensList(
|
||||||
|
params: { limit?: number; offset?: number },
|
||||||
|
opts: LysnraiClientOptions
|
||||||
|
): Promise<{ tokens: ApiTokenDoc[]; total: number }> {
|
||||||
|
const qs = new URLSearchParams();
|
||||||
|
if (params.limit !== undefined) qs.set('limit', String(params.limit));
|
||||||
|
if (params.offset !== undefined) qs.set('offset', String(params.offset));
|
||||||
|
const q = qs.toString();
|
||||||
|
return lysnraiFetch(`/api-tokens${q ? `?${q}` : ''}`, { method: 'GET' }, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function lysnraiApiTokenRotate(
|
||||||
|
tokenId: string,
|
||||||
|
opts: LysnraiClientOptions
|
||||||
|
): Promise<{ token: string; id: string }> {
|
||||||
|
return lysnraiFetch(`/api-tokens/${tokenId}/rotate`, { method: 'POST' }, opts);
|
||||||
|
}
|
||||||
221
services/mcp-server/src/lib/mindlyst-client.ts
Normal file
221
services/mcp-server/src/lib/mindlyst-client.ts
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
/**
|
||||||
|
* MindLyst backend client — typed HTTP wrappers for the mindlyst-backend (port 4014).
|
||||||
|
*
|
||||||
|
* Auth: Bearer token from the caller's JWT (same JWT_SECRET as platform-service).
|
||||||
|
* Admin callers may pass x-admin-user-id to operate on behalf of any user.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { config } from './config.js';
|
||||||
|
|
||||||
|
export interface MindlystClientOptions {
|
||||||
|
token?: string;
|
||||||
|
requestId?: string;
|
||||||
|
/** When set (admin only) — overrides the JWT sub used as userId in backend routes */
|
||||||
|
adminUserId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Shared fetch helper ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async function mindlystFetch<T>(
|
||||||
|
path: string,
|
||||||
|
init: RequestInit,
|
||||||
|
opts: MindlystClientOptions
|
||||||
|
): Promise<T> {
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(opts.token ? { Authorization: `Bearer ${opts.token}` } : {}),
|
||||||
|
...(opts.requestId ? { 'x-request-id': opts.requestId } : {}),
|
||||||
|
...(opts.adminUserId ? { 'x-admin-user-id': opts.adminUserId } : {}),
|
||||||
|
};
|
||||||
|
const res = await fetch(`${config.MINDLYST_BACKEND_URL}${path}`, {
|
||||||
|
...init,
|
||||||
|
headers: { ...((init.headers as Record<string, string>) ?? {}), ...headers },
|
||||||
|
signal: AbortSignal.timeout(15_000),
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const body = await res.text().catch(() => '');
|
||||||
|
throw new Error(`mindlyst-backend ${init.method ?? 'GET'} ${path} → ${res.status}: ${body}`);
|
||||||
|
}
|
||||||
|
return res.json() as Promise<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Memory items ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface MemoryItemDoc {
|
||||||
|
id: string;
|
||||||
|
productId: string;
|
||||||
|
userId: string;
|
||||||
|
sourceType: string;
|
||||||
|
captureSurface: string;
|
||||||
|
rawContent: string;
|
||||||
|
triageResult: {
|
||||||
|
contentType: string;
|
||||||
|
summary?: string;
|
||||||
|
urgencyScore: number;
|
||||||
|
emotionScore: number;
|
||||||
|
confidenceScore: number;
|
||||||
|
suggestedBrainId: string;
|
||||||
|
entities: string[];
|
||||||
|
suggestedActions: string[];
|
||||||
|
};
|
||||||
|
brainIds: string[];
|
||||||
|
actedOn: boolean;
|
||||||
|
actedOnAt: string | null;
|
||||||
|
nudgeCount: number;
|
||||||
|
reminderAt?: string;
|
||||||
|
isSensitive: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mindlystMemoryList(
|
||||||
|
params: {
|
||||||
|
brainId?: string;
|
||||||
|
filter?: 'forgotten' | 'completed_today';
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
productId?: string;
|
||||||
|
},
|
||||||
|
opts: MindlystClientOptions
|
||||||
|
): Promise<{ items: MemoryItemDoc[]; limit: number; offset: number }> {
|
||||||
|
const qs = new URLSearchParams();
|
||||||
|
if (params.brainId) qs.set('brainId', params.brainId);
|
||||||
|
if (params.filter) qs.set('filter', params.filter);
|
||||||
|
if (params.limit !== undefined) qs.set('limit', String(params.limit));
|
||||||
|
if (params.offset !== undefined) qs.set('offset', String(params.offset));
|
||||||
|
if (params.productId) qs.set('productId', params.productId);
|
||||||
|
const q = qs.toString();
|
||||||
|
return mindlystFetch(`/memory-items${q ? `?${q}` : ''}`, { method: 'GET' }, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mindlystMemoryRetriage(
|
||||||
|
itemId: string,
|
||||||
|
opts: MindlystClientOptions
|
||||||
|
): Promise<MemoryItemDoc> {
|
||||||
|
return mindlystFetch(`/memory-items/${itemId}/retriage`, { method: 'POST' }, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mindlystMemoryPatch(
|
||||||
|
itemId: string,
|
||||||
|
action: 'mark_done' | 'mark_undone' | 'increment_nudge' | 'set_reminder',
|
||||||
|
reminderAt: string | undefined,
|
||||||
|
opts: MindlystClientOptions
|
||||||
|
): Promise<MemoryItemDoc> {
|
||||||
|
return mindlystFetch(
|
||||||
|
`/memory-items/${itemId}`,
|
||||||
|
{ method: 'PATCH', body: JSON.stringify({ action, ...(reminderAt ? { reminderAt } : {}) }) },
|
||||||
|
opts
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mindlystMemoryReassign(
|
||||||
|
itemId: string,
|
||||||
|
newBrainId: string,
|
||||||
|
opts: MindlystClientOptions
|
||||||
|
): Promise<MemoryItemDoc> {
|
||||||
|
return mindlystFetch(
|
||||||
|
`/memory-items/${itemId}/reassign`,
|
||||||
|
{ method: 'PUT', body: JSON.stringify({ newBrainId }) },
|
||||||
|
opts
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Brains ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface BrainDoc {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
productId: string;
|
||||||
|
name: string;
|
||||||
|
rolePrompt?: string;
|
||||||
|
tone?: string;
|
||||||
|
colorFrom?: string;
|
||||||
|
colorTo?: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mindlystBrainsList(
|
||||||
|
params: { limit?: number; offset?: number },
|
||||||
|
opts: MindlystClientOptions
|
||||||
|
): Promise<{ items: BrainDoc[]; total: number; limit: number; offset: number }> {
|
||||||
|
const qs = new URLSearchParams();
|
||||||
|
if (params.limit !== undefined) qs.set('limit', String(params.limit));
|
||||||
|
if (params.offset !== undefined) qs.set('offset', String(params.offset));
|
||||||
|
const q = qs.toString();
|
||||||
|
return mindlystFetch(`/brains${q ? `?${q}` : ''}`, { method: 'GET' }, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Daily briefs ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface DailyBriefDoc {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
productId: string;
|
||||||
|
date: string;
|
||||||
|
greeting?: string;
|
||||||
|
priorityItems: unknown[];
|
||||||
|
brainSummaries: Record<string, string>;
|
||||||
|
streakMessage?: string;
|
||||||
|
motivationalQuote?: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mindlystBriefsGetToday(opts: MindlystClientOptions): Promise<DailyBriefDoc> {
|
||||||
|
return mindlystFetch('/daily-briefs/today', { method: 'GET' }, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mindlystBriefsList(
|
||||||
|
params: { limit?: number; offset?: number },
|
||||||
|
opts: MindlystClientOptions
|
||||||
|
): Promise<{ items: DailyBriefDoc[]; total: number }> {
|
||||||
|
const qs = new URLSearchParams();
|
||||||
|
if (params.limit !== undefined) qs.set('limit', String(params.limit));
|
||||||
|
if (params.offset !== undefined) qs.set('offset', String(params.offset));
|
||||||
|
const q = qs.toString();
|
||||||
|
return mindlystFetch(`/daily-briefs${q ? `?${q}` : ''}`, { method: 'GET' }, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Streaks ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface StreakDoc {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
productId: string;
|
||||||
|
currentStreak: number;
|
||||||
|
longestStreak: number;
|
||||||
|
lastActiveDate: string;
|
||||||
|
streakFreezeAvailable: boolean;
|
||||||
|
totalActiveDays: number;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mindlystStreaksGet(opts: MindlystClientOptions): Promise<StreakDoc> {
|
||||||
|
return mindlystFetch('/streaks', { method: 'GET' }, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Reflections ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface ReflectionDoc {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
productId: string;
|
||||||
|
weekStartDate: string;
|
||||||
|
themes: string[];
|
||||||
|
highlights: string[];
|
||||||
|
challenges: string[];
|
||||||
|
summary?: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mindlystReflectionsList(
|
||||||
|
params: { limit?: number; offset?: number },
|
||||||
|
opts: MindlystClientOptions
|
||||||
|
): Promise<{ items: ReflectionDoc[]; total: number }> {
|
||||||
|
const qs = new URLSearchParams();
|
||||||
|
if (params.limit !== undefined) qs.set('limit', String(params.limit));
|
||||||
|
if (params.offset !== undefined) qs.set('offset', String(params.offset));
|
||||||
|
const q = qs.toString();
|
||||||
|
return mindlystFetch(`/reflections${q ? `?${q}` : ''}`, { method: 'GET' }, opts);
|
||||||
|
}
|
||||||
150
services/mcp-server/src/modules/jarvis/jarvis-tools.ts
Normal file
150
services/mcp-server/src/modules/jarvis/jarvis-tools.ts
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
/**
|
||||||
|
* JarvisJr MCP tools — jarvis.agents.*, jarvis.sessions.*, jarvis.memory.*
|
||||||
|
*
|
||||||
|
* Backed by: jarvisjr-backend (port 4012).
|
||||||
|
* All tools require admin role.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { registerTool } from '../tools/registry.js';
|
||||||
|
import { config } from '../../lib/config.js';
|
||||||
|
import {
|
||||||
|
jarvisAgentsList,
|
||||||
|
jarvisAgentDuplicate,
|
||||||
|
jarvisSessionsList,
|
||||||
|
jarvisSessionsGetStats,
|
||||||
|
jarvisMemoryList,
|
||||||
|
jarvisMemoryPrune,
|
||||||
|
jarvisMemoryGetContext,
|
||||||
|
} from '../../lib/jarvis-client.js';
|
||||||
|
import type { McpToolRequest } from '../tools/types.js';
|
||||||
|
|
||||||
|
const tokenOf = (req: McpToolRequest) => req.headers.authorization?.slice(7);
|
||||||
|
|
||||||
|
// ── jarvis.agents.list ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'jarvis.agents.list',
|
||||||
|
description:
|
||||||
|
'List coaching agents for the authenticated user (name, role, coachingFramework, totalSessions). Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
limit: z.coerce.number().min(1).max(config.QUERY_MAX_LIMIT).default(config.QUERY_DEFAULT_LIMIT),
|
||||||
|
offset: z.coerce.number().min(0).default(0),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return jarvisAgentsList(args, { token: tokenOf(req), requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── jarvis.agents.duplicate ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'jarvis.agents.duplicate',
|
||||||
|
description:
|
||||||
|
'Duplicate an existing agent (creates a copy with " (Copy)" suffix and zero session count). Useful for iterating on agent prompts. Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
agentId: z.string().min(1).describe('Agent ID to duplicate'),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return jarvisAgentDuplicate(args.agentId, { token: tokenOf(req), requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── jarvis.sessions.list ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'jarvis.sessions.list',
|
||||||
|
description:
|
||||||
|
'List coaching sessions for the authenticated user. Filter by agentId. Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
agentId: z.string().optional().describe('Filter to a specific agent ID'),
|
||||||
|
limit: z.coerce.number().min(1).max(config.QUERY_MAX_LIMIT).default(config.QUERY_DEFAULT_LIMIT),
|
||||||
|
offset: z.coerce.number().min(0).default(0),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return jarvisSessionsList(args, { token: tokenOf(req), requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── jarvis.sessions.stats ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'jarvis.sessions.stats',
|
||||||
|
description:
|
||||||
|
'Get coaching session statistics: total sessions, total duration, current streak, per-agent breakdown. Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({}),
|
||||||
|
async execute(_args, req) {
|
||||||
|
return jarvisSessionsGetStats({ token: tokenOf(req), requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── jarvis.memory.list ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'jarvis.memory.list',
|
||||||
|
description:
|
||||||
|
'List persistent memories for a coaching agent. Filter by type (skill_note, preference, goal, context, exercise) or minimum importance score. Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
agentId: z.string().min(1).describe('Agent ID to query memories for'),
|
||||||
|
type: z
|
||||||
|
.enum(['skill_note', 'preference', 'goal', 'context', 'exercise'])
|
||||||
|
.optional()
|
||||||
|
.describe('Filter by memory type'),
|
||||||
|
minImportance: z.coerce
|
||||||
|
.number()
|
||||||
|
.min(0)
|
||||||
|
.max(1)
|
||||||
|
.optional()
|
||||||
|
.describe('Minimum importance score (0.0–1.0)'),
|
||||||
|
limit: z.coerce.number().min(1).max(config.QUERY_MAX_LIMIT).default(config.QUERY_DEFAULT_LIMIT),
|
||||||
|
offset: z.coerce.number().min(0).default(0),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
const { agentId, ...params } = args;
|
||||||
|
return jarvisMemoryList(agentId, params, { token: tokenOf(req), requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── jarvis.memory.prune ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'jarvis.memory.prune',
|
||||||
|
description:
|
||||||
|
'Prune expired memories for a coaching agent (removes entries where expiresAt < now). Returns count of pruned entries. Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
agentId: z.string().min(1).describe('Agent ID to prune memories for'),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return jarvisMemoryPrune(args.agentId, { token: tokenOf(req), requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── jarvis.memory.getContext ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'jarvis.memory.getContext',
|
||||||
|
description:
|
||||||
|
'Retrieve the top-N memories for a coaching agent to use as session context (sorted by importance). Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
agentId: z.string().min(1).describe('Agent ID'),
|
||||||
|
limit: z.coerce
|
||||||
|
.number()
|
||||||
|
.min(1)
|
||||||
|
.max(50)
|
||||||
|
.default(20)
|
||||||
|
.describe('Number of context memories to return'),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return jarvisMemoryGetContext(args.agentId, args.limit, {
|
||||||
|
token: tokenOf(req),
|
||||||
|
requestId: req.id,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
133
services/mcp-server/src/modules/lysnrai/lysnrai-tools.ts
Normal file
133
services/mcp-server/src/modules/lysnrai/lysnrai-tools.ts
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
/**
|
||||||
|
* LysnrAI MCP tools — lysnrai.transcripts.*, lysnrai.stt.*, lysnrai.sessions.*,
|
||||||
|
* lysnrai.orgs.*, lysnrai.apiTokens.*
|
||||||
|
*
|
||||||
|
* Backed by: lysnrai-backend (port 4015).
|
||||||
|
* All tools require admin role.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { registerTool } from '../tools/registry.js';
|
||||||
|
import { config } from '../../lib/config.js';
|
||||||
|
import {
|
||||||
|
lysnraiTranscriptsList,
|
||||||
|
lysnraiTranscriptRunExtraction,
|
||||||
|
lysnraiSttGetBackendStatus,
|
||||||
|
lysnraiSessionsList,
|
||||||
|
lysnraiOrgsList,
|
||||||
|
lysnraiApiTokensList,
|
||||||
|
lysnraiApiTokenRotate,
|
||||||
|
} from '../../lib/lysnrai-client.js';
|
||||||
|
import type { McpToolRequest } from '../tools/types.js';
|
||||||
|
|
||||||
|
const tokenOf = (req: McpToolRequest) => req.headers.authorization?.slice(7);
|
||||||
|
|
||||||
|
// ── lysnrai.transcripts.list ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'lysnrai.transcripts.list',
|
||||||
|
description: 'List dictation transcripts for the authenticated user. Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
limit: z.coerce.number().min(1).max(config.QUERY_MAX_LIMIT).default(config.QUERY_DEFAULT_LIMIT),
|
||||||
|
offset: z.coerce.number().min(0).default(0),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return lysnraiTranscriptsList(args, { token: tokenOf(req), requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── lysnrai.transcripts.runExtraction ────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'lysnrai.transcripts.runExtraction',
|
||||||
|
description:
|
||||||
|
'Run extraction-service enrichment on a transcript — populates extractions[] and extractedAt. Best-effort: returns partial if extraction-service is unavailable. Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
transcriptId: z.string().min(1).describe('Transcript ID (trx_... prefix)'),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return lysnraiTranscriptRunExtraction(args.transcriptId, {
|
||||||
|
token: tokenOf(req),
|
||||||
|
requestId: req.id,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── lysnrai.stt.getBackendStatus ──────────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'lysnrai.stt.getBackendStatus',
|
||||||
|
description:
|
||||||
|
'Report which speech-to-text backend is active: azure (Azure Speech SDK) or whisper (local Whisper model). Useful for ops health checks. Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({}),
|
||||||
|
async execute(_args, req) {
|
||||||
|
return lysnraiSttGetBackendStatus({ token: tokenOf(req), requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── lysnrai.sessions.list ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'lysnrai.sessions.list',
|
||||||
|
description: 'List dictation sessions for the authenticated user. Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
status: z
|
||||||
|
.enum(['active', 'composed', 'archived'])
|
||||||
|
.optional()
|
||||||
|
.describe('Filter by session status'),
|
||||||
|
limit: z.coerce.number().min(1).max(config.QUERY_MAX_LIMIT).default(config.QUERY_DEFAULT_LIMIT),
|
||||||
|
offset: z.coerce.number().min(0).default(0),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return lysnraiSessionsList(args, { token: tokenOf(req), requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── lysnrai.orgs.list ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'lysnrai.orgs.list',
|
||||||
|
description: 'List organisations for the authenticated user. Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
limit: z.coerce.number().min(1).max(config.QUERY_MAX_LIMIT).default(config.QUERY_DEFAULT_LIMIT),
|
||||||
|
offset: z.coerce.number().min(0).default(0),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return lysnraiOrgsList(args, { token: tokenOf(req), requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── lysnrai.apiTokens.list ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'lysnrai.apiTokens.list',
|
||||||
|
description: 'List API tokens for the authenticated user. Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
limit: z.coerce.number().min(1).max(config.QUERY_MAX_LIMIT).default(config.QUERY_DEFAULT_LIMIT),
|
||||||
|
offset: z.coerce.number().min(0).default(0),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return lysnraiApiTokensList(args, { token: tokenOf(req), requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── lysnrai.apiTokens.rotate ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'lysnrai.apiTokens.rotate',
|
||||||
|
description:
|
||||||
|
'Rotate an API token — invalidates the current secret and issues a new one. Returns the new plaintext token (only visible once). Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
tokenId: z.string().min(1).describe('API token ID to rotate'),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return lysnraiApiTokenRotate(args.tokenId, { token: tokenOf(req), requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
201
services/mcp-server/src/modules/mindlyst/mindlyst-tools.ts
Normal file
201
services/mcp-server/src/modules/mindlyst/mindlyst-tools.ts
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
/**
|
||||||
|
* MindLyst MCP tools — mindlyst.memory.*, mindlyst.brains.*, mindlyst.briefs.*,
|
||||||
|
* mindlyst.streaks.*, mindlyst.reflections.*, mindlyst.extractions.*
|
||||||
|
*
|
||||||
|
* Backed by: mindlyst-backend (port 4014) + extraction-service (port 4005).
|
||||||
|
* All tools require admin role.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { registerTool } from '../tools/registry.js';
|
||||||
|
import { config } from '../../lib/config.js';
|
||||||
|
import {
|
||||||
|
mindlystMemoryList,
|
||||||
|
mindlystMemoryRetriage,
|
||||||
|
mindlystMemoryPatch,
|
||||||
|
mindlystMemoryReassign,
|
||||||
|
mindlystBrainsList,
|
||||||
|
mindlystBriefsGetToday,
|
||||||
|
mindlystBriefsList,
|
||||||
|
mindlystStreaksGet,
|
||||||
|
mindlystReflectionsList,
|
||||||
|
} from '../../lib/mindlyst-client.js';
|
||||||
|
import { extractionRun } from '../../lib/extraction-client.js';
|
||||||
|
import type { McpToolRequest } from '../tools/types.js';
|
||||||
|
|
||||||
|
const tokenOf = (req: McpToolRequest) => req.headers.authorization?.slice(7);
|
||||||
|
|
||||||
|
// ── mindlyst.memory.list ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'mindlyst.memory.list',
|
||||||
|
description:
|
||||||
|
'List MindLyst memory items for the authenticated user. Filter by brain, status, or use free-text. Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
brainId: z.string().optional().describe('Filter to a specific Brain ID (e.g. "work", "home")'),
|
||||||
|
filter: z
|
||||||
|
.enum(['forgotten', 'completed_today'])
|
||||||
|
.optional()
|
||||||
|
.describe('forgotten = actedOn=false, completed_today = actedOn=true today'),
|
||||||
|
limit: z.coerce.number().min(1).max(config.QUERY_MAX_LIMIT).default(config.QUERY_DEFAULT_LIMIT),
|
||||||
|
offset: z.coerce.number().min(0).default(0),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return mindlystMemoryList(args, { token: tokenOf(req), requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── mindlyst.memory.retriage ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'mindlyst.memory.retriage',
|
||||||
|
description:
|
||||||
|
'Re-run extraction on a single memory item to refresh its triage result (urgencyScore, suggestedBrainId, entities). Best-effort: returns current item if extraction-service is unavailable. Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
itemId: z.string().min(1).describe('Memory item ID'),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return mindlystMemoryRetriage(args.itemId, { token: tokenOf(req), requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── mindlyst.memory.patch ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'mindlyst.memory.patch',
|
||||||
|
description:
|
||||||
|
'Update a memory item status: mark_done, mark_undone, increment_nudge, or set_reminder. Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
itemId: z.string().min(1).describe('Memory item ID'),
|
||||||
|
action: z.enum(['mark_done', 'mark_undone', 'increment_nudge', 'set_reminder']),
|
||||||
|
reminderAt: z
|
||||||
|
.string()
|
||||||
|
.datetime()
|
||||||
|
.optional()
|
||||||
|
.describe('ISO 8601 datetime — required when action is set_reminder'),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return mindlystMemoryPatch(args.itemId, args.action, args.reminderAt, {
|
||||||
|
token: tokenOf(req),
|
||||||
|
requestId: req.id,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── mindlyst.memory.reassign ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'mindlyst.memory.reassign',
|
||||||
|
description:
|
||||||
|
'Reassign a memory item to a different Brain. Records a userCorrection for training. Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
itemId: z.string().min(1).describe('Memory item ID'),
|
||||||
|
newBrainId: z.string().min(1).describe('Target Brain ID'),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return mindlystMemoryReassign(args.itemId, args.newBrainId, {
|
||||||
|
token: tokenOf(req),
|
||||||
|
requestId: req.id,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── mindlyst.brains.list ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'mindlyst.brains.list',
|
||||||
|
description:
|
||||||
|
'List all Brains for the authenticated user (role-based life OS categories). Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
limit: z.coerce.number().min(1).max(config.QUERY_MAX_LIMIT).default(config.QUERY_DEFAULT_LIMIT),
|
||||||
|
offset: z.coerce.number().min(0).default(0),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return mindlystBrainsList(args, { token: tokenOf(req), requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── mindlyst.briefs.getToday ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'mindlyst.briefs.getToday',
|
||||||
|
description:
|
||||||
|
"Get today's Daily Brief for the authenticated user. Returns 404 error if none generated yet. Requires admin role.",
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({}),
|
||||||
|
async execute(_args, req) {
|
||||||
|
return mindlystBriefsGetToday({ token: tokenOf(req), requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── mindlyst.briefs.list ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'mindlyst.briefs.list',
|
||||||
|
description: 'List past Daily Briefs for the authenticated user. Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
limit: z.coerce.number().min(1).max(config.QUERY_MAX_LIMIT).default(config.QUERY_DEFAULT_LIMIT),
|
||||||
|
offset: z.coerce.number().min(0).default(0),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return mindlystBriefsList(args, { token: tokenOf(req), requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── mindlyst.streaks.get ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'mindlyst.streaks.get',
|
||||||
|
description:
|
||||||
|
'Get the current engagement streak for the authenticated user (currentStreak, longestStreak, lastActiveDate). Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({}),
|
||||||
|
async execute(_args, req) {
|
||||||
|
return mindlystStreaksGet({ token: tokenOf(req), requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── mindlyst.reflections.list ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'mindlyst.reflections.list',
|
||||||
|
description: 'List reflection journal entries for the authenticated user. Requires admin role.',
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
limit: z.coerce.number().min(1).max(config.QUERY_MAX_LIMIT).default(config.QUERY_DEFAULT_LIMIT),
|
||||||
|
offset: z.coerce.number().min(0).default(0),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return mindlystReflectionsList(args, { token: tokenOf(req), requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── mindlyst.extractions.run ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
registerTool({
|
||||||
|
name: 'mindlyst.extractions.run',
|
||||||
|
description: [
|
||||||
|
'Run text through the MindLyst extraction pipeline with one of the three task IDs:',
|
||||||
|
' • triage — extract brain signals, entities, actions (used for new memory items)',
|
||||||
|
' • memory-insight — summarise a set of memory items into brain-level insights',
|
||||||
|
' • reflection-enrichment — extract themes and highlights from journal text',
|
||||||
|
'Requires admin role.',
|
||||||
|
].join('\n'),
|
||||||
|
requiredRole: 'admin',
|
||||||
|
inputSchema: z.object({
|
||||||
|
text: z.string().min(1).describe('Text to extract from'),
|
||||||
|
taskId: z
|
||||||
|
.enum(['triage', 'memory-insight', 'reflection-enrichment'])
|
||||||
|
.default('triage')
|
||||||
|
.describe('MindLyst-specific extraction task ID'),
|
||||||
|
}),
|
||||||
|
async execute(args, req) {
|
||||||
|
return extractionRun({ text: args.text, taskId: args.taskId }, { requestId: req.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -6,6 +6,9 @@
|
|||||||
* platform.diagnostics.* — manage debug sessions, read logs/traces, cancel
|
* platform.diagnostics.* — manage debug sessions, read logs/traces, cancel
|
||||||
* extraction.* — run extraction, list models, cache stats, sidecar health
|
* extraction.* — run extraction, list models, cache stats, sidecar health
|
||||||
* support.* — compound tools (createDebugPack, runIncidentPipeline)
|
* support.* — compound tools (createDebugPack, runIncidentPipeline)
|
||||||
|
* mindlyst.* — memory, brains, briefs, streaks, reflections, extractions
|
||||||
|
* lysnrai.* — transcripts, sessions, orgs, apiTokens, stt
|
||||||
|
* jarvis.* — agents, sessions, memory (JarvisJr coaching platform)
|
||||||
*
|
*
|
||||||
* Auth: JWT Bearer tokens issued by platform-service (same JWT_SECRET).
|
* Auth: JWT Bearer tokens issued by platform-service (same JWT_SECRET).
|
||||||
* Role gating: viewer / admin / super_admin per tool.
|
* Role gating: viewer / admin / super_admin per tool.
|
||||||
@ -23,12 +26,15 @@ import './modules/platform/diagnostics-tools.js';
|
|||||||
import './modules/extraction/extraction-tools.js';
|
import './modules/extraction/extraction-tools.js';
|
||||||
import './modules/support/debug-pack.js';
|
import './modules/support/debug-pack.js';
|
||||||
import './modules/a2a/pipeline-tool.js';
|
import './modules/a2a/pipeline-tool.js';
|
||||||
|
import './modules/mindlyst/mindlyst-tools.js';
|
||||||
|
import './modules/lysnrai/lysnrai-tools.js';
|
||||||
|
import './modules/jarvis/jarvis-tools.js';
|
||||||
|
|
||||||
const app = await createServiceApp({
|
const app = await createServiceApp({
|
||||||
name: 'mcp-server',
|
name: 'mcp-server',
|
||||||
version: '0.1.0',
|
version: '0.1.0',
|
||||||
description:
|
description:
|
||||||
'ByteLyst MCP Server — platform.telemetry.*, platform.diagnostics.*, extraction.*, support.*',
|
'ByteLyst MCP Server — platform.*, extraction.*, support.*, mindlyst.*, lysnrai.*, jarvis.*',
|
||||||
corsOrigin: config.CORS_ORIGIN,
|
corsOrigin: config.CORS_ORIGIN,
|
||||||
logLevel: config.LOG_LEVEL,
|
logLevel: config.LOG_LEVEL,
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user