feat(mcplocal+mcpd): wake-recipe config + wake-task execution (v2 Stage 1)
First half of v2 — mcplocal can now declare a hibernating backend and
respond to a `wake` task by running a configured recipe. v2 Stage 2
will wire mcpd to dispatch the wake task before relaying inference.
Config (LlmProviderFileEntry):
- New \`wake\` block on a published provider:
wake:
type: http # or: command
url: ... # http only
method: POST # http only, default POST
headers: {...} # http only
body: ... # http only
command: ... # command only
args: [...] # command only
maxWaitSeconds: 60 # how long to poll isAvailable() after wake fires
Registrar (mcplocal):
- At publish time, providers with a wake recipe whose isAvailable()
returns false report initialStatus=hibernating to mcpd. Without a
wake recipe (legacy v1) or when already up, status stays active.
- handleWakeTask: runs the recipe (HTTP request OR child-process
spawn), then polls isAvailable() up to maxWaitSeconds, sending a
heartbeat each loop so mcpd's GC sweep doesn't time us out
mid-boot. Reports { ok, ms } on success or { error } on
timeout/recipe failure via the existing _provider-task/:id/result.
- Replaces the v1 stub that rejected wake tasks with "not implemented".
mcpd VirtualLlmService:
- RegisterProviderInput gains optional initialStatus ('active' |
'hibernating'). The register/upsert path uses it for both new and
reconnecting rows. Defaults to 'active' so v1 publishers still
work unchanged.
- Provider-register route's coercer accepts the new field.
Tests: 3 new in registrar.test.ts cover initialStatus selection
(hibernating when wake configured + unavailable, active otherwise,
active when no wake even if unavailable). 8/8 registrar tests, 833/833
mcpd unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,15 @@ export interface RegisterProviderInput {
|
||||
tier?: string;
|
||||
description?: string;
|
||||
extraConfig?: Record<string, unknown>;
|
||||
/**
|
||||
* Optional. Lets the publisher hint that the underlying backend is
|
||||
* asleep — mcpd records the row as `hibernating` and will dispatch a
|
||||
* `wake` task before any inference. Defaults to `active` (today's
|
||||
* behavior). v2 publishers (mcplocal with a configured wake recipe)
|
||||
* pass 'hibernating' when `LlmProvider.isAvailable()` returns false at
|
||||
* publish time.
|
||||
*/
|
||||
initialStatus?: 'active' | 'hibernating';
|
||||
}
|
||||
|
||||
export interface RegisterResult {
|
||||
@@ -112,6 +121,7 @@ export class VirtualLlmService implements IVirtualLlmService {
|
||||
const llms: Llm[] = [];
|
||||
|
||||
for (const p of input.providers) {
|
||||
const initialStatus = p.initialStatus ?? 'active';
|
||||
const existing = await this.repo.findByName(p.name);
|
||||
if (existing === null) {
|
||||
const created = await this.repo.create({
|
||||
@@ -123,7 +133,7 @@ export class VirtualLlmService implements IVirtualLlmService {
|
||||
...(p.extraConfig !== undefined ? { extraConfig: p.extraConfig } : {}),
|
||||
kind: 'virtual',
|
||||
providerSessionId: sessionId,
|
||||
status: 'active',
|
||||
status: initialStatus,
|
||||
lastHeartbeatAt: now,
|
||||
inactiveSince: null,
|
||||
});
|
||||
@@ -156,7 +166,7 @@ export class VirtualLlmService implements IVirtualLlmService {
|
||||
...(p.extraConfig !== undefined ? { extraConfig: p.extraConfig } : {}),
|
||||
kind: 'virtual',
|
||||
providerSessionId: sessionId,
|
||||
status: 'active',
|
||||
status: initialStatus,
|
||||
lastHeartbeatAt: now,
|
||||
inactiveSince: null,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user