feat(mcp): add chronomind.planner.planDay + chronomind.planner.planNl MCP tools (Phase B.2b)

This commit is contained in:
saravanakumardb1 2026-05-14 16:56:43 -07:00
parent 60c34745f4
commit 9c60e5aba0
2 changed files with 194 additions and 0 deletions

View File

@ -260,3 +260,98 @@ export function chronomindAgentActionApprove(
): Promise<AgentActionDoc> { ): Promise<AgentActionDoc> {
return chronomindFetch(`/agent-actions/${actionId}/approve`, { method: 'POST' }, opts); return chronomindFetch(`/agent-actions/${actionId}/approve`, { method: 'POST' }, opts);
} }
// ── Phase B.2b: Day Planner MCP client functions ────────────────────────
export interface ProposedTimer {
label: string;
startTime: string;
endTime: string;
durationMinutes: number;
priority: 'high' | 'medium' | 'low';
urgency: string;
category?: string;
cascade: string;
}
export interface PlanDayResponse {
planId: string;
date: string;
proposed: ProposedTimer[];
overflow: ProposedTimer[];
totalMinutesPlanned: number;
totalMinutesOverflow: number;
existingTimerCount: number;
}
export interface PlanNlResponse extends PlanDayResponse {
parsedActivities: Array<{
label: string;
durationMinutes: number;
priority: string;
category?: string;
}>;
unparsed: string[];
}
export function chronomindPlannerPlanDay(
input: {
date: string;
dayStartHour?: number;
dayEndHour?: number;
activities: Array<{
label: string;
durationMinutes: number;
priority?: string;
category?: string;
}>;
prepTimeMinutes?: number;
},
opts: ChronoMindClientOptions
): Promise<PlanDayResponse> {
return chronomindFetch(
'/planner/plan-day',
{ method: 'POST', body: JSON.stringify(input) },
opts
);
}
export function chronomindPlannerPlanNl(
input: {
text: string;
date?: string;
dayStartHour?: number;
dayEndHour?: number;
prepTimeMinutes?: number;
},
opts: ChronoMindClientOptions
): Promise<PlanNlResponse> {
return chronomindFetch('/planner/plan-nl', { method: 'POST', body: JSON.stringify(input) }, opts);
}
export function chronomindPlannerApply(
input: { planId: string; proposed: ProposedTimer[] },
opts: ChronoMindClientOptions
): Promise<{ planId: string; created: string[] }> {
return chronomindFetch(
'/planner/apply-plan',
{ method: 'POST', body: JSON.stringify(input) },
opts
);
}
export function chronomindPlannerReplan(
input: {
planId: string;
date: string;
keep?: string[];
drop?: string[];
add?: Array<{ label: string; durationMinutes: number; priority?: string; category?: string }>;
dayStartHour?: number;
dayEndHour?: number;
prepTimeMinutes?: number;
},
opts: ChronoMindClientOptions
): Promise<PlanDayResponse> {
return chronomindFetch('/planner/replan', { method: 'POST', body: JSON.stringify(input) }, opts);
}

View File

@ -23,6 +23,8 @@ import {
chronomindAgentActionCreate, chronomindAgentActionCreate,
chronomindAgentActionsList, chronomindAgentActionsList,
chronomindAgentActionApprove, chronomindAgentActionApprove,
chronomindPlannerPlanDay,
chronomindPlannerPlanNl,
} from '../../lib/chronomind-client.js'; } from '../../lib/chronomind-client.js';
import type { McpToolRequest } from '../tools/types.js'; import type { McpToolRequest } from '../tools/types.js';
@ -362,3 +364,100 @@ registerTool({
return chronomindAgentActionApprove(args.actionId, { token: tokenOf(req), requestId: req.id }); return chronomindAgentActionApprove(args.actionId, { token: tokenOf(req), requestId: req.id });
}, },
}); });
// ══════════════════════════════════════════════════════════════════════════════
// Phase B.2b — Day Planner MCP tools
// ══════════════════════════════════════════════════════════════════════════════
// ── chronomind.planner.planDay ───────────────────────────────────────────────
registerTool({
name: 'chronomind.planner.planDay',
description:
'Generate a proposed day plan from a list of activities. The engine computes free slots around existing timers, fits activities by priority, assigns urgency and cascade presets, and handles overflow. Returns proposed timers ready for review and apply. Requires admin role.',
requiredRole: 'admin',
inputSchema: z.object({
date: z.string().describe('Target date in YYYY-MM-DD format'),
dayStartHour: z.coerce.number().min(0).max(23).default(8).describe('Day starts at this hour'),
dayEndHour: z.coerce.number().min(1).max(24).default(22).describe('Day ends at this hour'),
activities: z
.array(
z.object({
label: z.string().min(1).describe('Activity name'),
durationMinutes: z.coerce.number().min(1).max(480).describe('Duration in minutes'),
priority: z.enum(['high', 'medium', 'low']).default('medium'),
category: z.string().optional().describe('Activity category'),
})
)
.min(1)
.max(20)
.describe('Activities to schedule'),
prepTimeMinutes: z.coerce
.number()
.min(0)
.max(30)
.default(5)
.describe('Buffer between activities'),
}),
async execute(args, req) {
const opts = { token: tokenOf(req), requestId: req.id };
await chronomindAgentActionCreate(
{
id: `aa_${crypto.randomUUID()}`,
actorId: 'mcp-server',
actorType: 'mcp',
toolName: 'chronomind.planner.planDay',
actionType: 'planner.plan_day',
payload: { date: args.date, activityCount: args.activities.length },
},
opts
).catch(() => {});
return chronomindPlannerPlanDay(args, opts);
},
});
// ── chronomind.planner.planNl ────────────────────────────────────────────────
registerTool({
name: 'chronomind.planner.planNl',
description:
'Parse natural language text describing a day plan into structured activities, then generate a proposed day plan. Supports semicolon/newline/then separators, durations (30m, 1h, half hour), priority keywords, and time constraints (after 2pm, before 10am). Returns parsed activities, any unparsed segments, and the proposed timeline. Requires admin role.',
requiredRole: 'admin',
inputSchema: z.object({
text: z
.string()
.min(1)
.max(2000)
.describe(
'Natural language description of activities, e.g. "Meeting 30m; Deep work 2h; Gym 1h after 2pm"'
),
date: z.string().optional().describe('Target date in YYYY-MM-DD format (defaults to today)'),
dayStartHour: z.coerce.number().min(0).max(23).default(8).describe('Day starts at this hour'),
dayEndHour: z.coerce.number().min(1).max(24).default(22).describe('Day ends at this hour'),
prepTimeMinutes: z.coerce
.number()
.min(0)
.max(30)
.default(5)
.describe('Buffer between activities'),
}),
async execute(args, req) {
const opts = { token: tokenOf(req), requestId: req.id };
await chronomindAgentActionCreate(
{
id: `aa_${crypto.randomUUID()}`,
actorId: 'mcp-server',
actorType: 'mcp',
toolName: 'chronomind.planner.planNl',
actionType: 'planner.plan_nl',
payload: { textLength: args.text.length },
},
opts
).catch(() => {});
return chronomindPlannerPlanNl(args, opts);
},
});