imprint-mcp 0.4.0 → 0.4.2
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.
- package/README.md +2 -0
- package/package.json +3 -1
- package/src/cli.ts +74 -3
- package/src/imprint/backend-ladder.ts +43 -16
- package/src/imprint/cdp-browser-fetch.ts +277 -170
- package/src/imprint/cron.ts +14 -1
- package/src/imprint/doctor.ts +19 -1
- package/src/imprint/mcp-maintenance.ts +71 -6
- package/src/imprint/mcp-server.ts +29 -4
- package/src/imprint/probe-backends.ts +346 -63
- package/src/imprint/types.ts +12 -0
- package/src/imprint/update.ts +73 -0
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
import * as p from '@clack/prompts';
|
|
28
28
|
import YAML from 'yaml';
|
|
29
29
|
import { imprintHomeDir, localSiteDir } from './paths.ts';
|
|
30
|
+
import { type BackendsCacheStatus, loadBackendsCacheStatus } from './probe-backends.ts';
|
|
30
31
|
import {
|
|
31
32
|
type WorkflowState,
|
|
32
33
|
loadTeachState,
|
|
@@ -38,7 +39,7 @@ import {
|
|
|
38
39
|
|
|
39
40
|
type McpClient = 'claude-code' | 'codex' | 'claude-desktop' | 'openclaw' | 'hermes';
|
|
40
41
|
type LocalDeleteMode = 'none' | 'tool' | 'site';
|
|
41
|
-
type IssueKind = 'missing-session' | 'stale-registration';
|
|
42
|
+
type IssueKind = 'missing-session' | 'stale-registration' | 'stale-backends' | 'invalid-backends';
|
|
42
43
|
|
|
43
44
|
const CLIENTS: McpClient[] = ['claude-code', 'codex', 'claude-desktop', 'openclaw', 'hermes'];
|
|
44
45
|
const DISABLED_STORE_VERSION = 1;
|
|
@@ -81,6 +82,15 @@ interface LocalToolStatus {
|
|
|
81
82
|
hasPlaybook: boolean;
|
|
82
83
|
hasBackends: boolean;
|
|
83
84
|
hasCron: boolean;
|
|
85
|
+
backendCache: PublicBackendsCacheStatus;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface PublicBackendsCacheStatus {
|
|
89
|
+
status: BackendsCacheStatus['status'];
|
|
90
|
+
path: string | null;
|
|
91
|
+
preferredOrder?: string[];
|
|
92
|
+
reason?: string;
|
|
93
|
+
remediation?: string;
|
|
84
94
|
}
|
|
85
95
|
|
|
86
96
|
interface LocalWorkflowStatus {
|
|
@@ -475,7 +485,13 @@ function cmdStatus(argv: string[]): number {
|
|
|
475
485
|
const status = scanMcpStatus({ site });
|
|
476
486
|
if (flags.json === true) console.log(JSON.stringify(status, null, 2));
|
|
477
487
|
else console.log(formatMcpStatus(status));
|
|
478
|
-
return status.issues.some(
|
|
488
|
+
return status.issues.some(
|
|
489
|
+
(i) =>
|
|
490
|
+
i.kind === 'stale-registration' ||
|
|
491
|
+
i.kind === 'missing-session' ||
|
|
492
|
+
i.kind === 'stale-backends' ||
|
|
493
|
+
i.kind === 'invalid-backends',
|
|
494
|
+
)
|
|
479
495
|
? 1
|
|
480
496
|
: 0;
|
|
481
497
|
}
|
|
@@ -691,6 +707,11 @@ function issueFixHint(issue: McpIssue): string | null {
|
|
|
691
707
|
return `choose "Fix an issue" or run: imprint mcp delete ${issue.name ?? `imprint-${issue.site}`} --client ${issue.client ?? 'all'} --yes`;
|
|
692
708
|
case 'missing-session':
|
|
693
709
|
return `choose "Fix an issue" or run: imprint mcp prune-state --site ${issue.site} --missing-session --yes`;
|
|
710
|
+
case 'stale-backends':
|
|
711
|
+
case 'invalid-backends':
|
|
712
|
+
return issue.path
|
|
713
|
+
? `run: imprint probe-backends ${issue.site}${issue.workflow ? ` --tool ${issue.workflow}` : ''}`
|
|
714
|
+
: `run: imprint probe-backends ${issue.site}${issue.workflow ? ` --tool ${issue.workflow}` : ''}`;
|
|
694
715
|
}
|
|
695
716
|
return null;
|
|
696
717
|
}
|
|
@@ -712,26 +733,32 @@ function scanLocalSites(ctx: MaintenanceContext): LocalSiteStatus[] {
|
|
|
712
733
|
if (entry === 'node_modules' || entry.startsWith('.')) continue;
|
|
713
734
|
const dir = pathJoin(ctx.imprintHome, entry);
|
|
714
735
|
if (!safeIsDir(dir)) continue;
|
|
715
|
-
sites.push(scanLocalSite(entry, dir));
|
|
736
|
+
sites.push(scanLocalSite(entry, dir, ctx.imprintHome));
|
|
716
737
|
}
|
|
717
738
|
return sites;
|
|
718
739
|
}
|
|
719
740
|
|
|
720
|
-
function scanLocalSite(site: string, dir: string): LocalSiteStatus {
|
|
741
|
+
function scanLocalSite(site: string, dir: string, imprintHome: string): LocalSiteStatus {
|
|
721
742
|
const tools: LocalToolStatus[] = [];
|
|
722
743
|
for (const entry of readdirSync(dir).sort()) {
|
|
723
744
|
if (entry === 'sessions' || entry === '_shared' || entry.startsWith('.')) continue;
|
|
724
745
|
const toolDir = pathJoin(dir, entry);
|
|
725
746
|
if (!safeIsDir(toolDir)) continue;
|
|
747
|
+
const toolName = workflowJsonToolName(toolDir) ?? entry;
|
|
748
|
+
const cacheStatus = loadBackendsCacheStatus(site, imprintHome, toolDir, {
|
|
749
|
+
warn: false,
|
|
750
|
+
toolName,
|
|
751
|
+
});
|
|
726
752
|
tools.push({
|
|
727
753
|
site,
|
|
728
|
-
toolName
|
|
754
|
+
toolName,
|
|
729
755
|
dir: toolDir,
|
|
730
756
|
complete: existsSync(pathJoin(toolDir, 'index.ts')),
|
|
731
757
|
hasWorkflow: existsSync(pathJoin(toolDir, 'workflow.json')),
|
|
732
758
|
hasPlaybook: existsSync(pathJoin(toolDir, 'playbook.yaml')),
|
|
733
759
|
hasBackends: existsSync(pathJoin(toolDir, 'backends.json')),
|
|
734
760
|
hasCron: existsSync(pathJoin(toolDir, 'cron.json')),
|
|
761
|
+
backendCache: publicBackendsCacheStatus(cacheStatus),
|
|
735
762
|
});
|
|
736
763
|
}
|
|
737
764
|
|
|
@@ -746,6 +773,29 @@ function scanLocalSite(site: string, dir: string): LocalSiteStatus {
|
|
|
746
773
|
return { site, dir, tools, workflows };
|
|
747
774
|
}
|
|
748
775
|
|
|
776
|
+
function publicBackendsCacheStatus(status: BackendsCacheStatus): PublicBackendsCacheStatus {
|
|
777
|
+
if (status.status === 'ok') {
|
|
778
|
+
return {
|
|
779
|
+
status: status.status,
|
|
780
|
+
path: status.path,
|
|
781
|
+
preferredOrder: status.cache.preferredOrder,
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
if (status.status === 'missing') {
|
|
785
|
+
return {
|
|
786
|
+
status: status.status,
|
|
787
|
+
path: status.path,
|
|
788
|
+
remediation: status.remediation,
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
return {
|
|
792
|
+
status: status.status,
|
|
793
|
+
path: status.path,
|
|
794
|
+
reason: status.reason,
|
|
795
|
+
remediation: status.remediation,
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
|
|
749
799
|
function workflowStatus(
|
|
750
800
|
site: string,
|
|
751
801
|
name: string,
|
|
@@ -795,6 +845,21 @@ function collectIssues(opts: {
|
|
|
795
845
|
const sitesByName = new Map(opts.sites.map((s) => [s.site, s]));
|
|
796
846
|
|
|
797
847
|
for (const site of opts.sites) {
|
|
848
|
+
for (const tool of site.tools) {
|
|
849
|
+
if (tool.backendCache.status === 'stale' || tool.backendCache.status === 'invalid') {
|
|
850
|
+
issues.push({
|
|
851
|
+
kind: tool.backendCache.status === 'stale' ? 'stale-backends' : 'invalid-backends',
|
|
852
|
+
site: site.site,
|
|
853
|
+
workflow: tool.toolName,
|
|
854
|
+
path: tool.backendCache.path ?? undefined,
|
|
855
|
+
message:
|
|
856
|
+
tool.backendCache.status === 'stale'
|
|
857
|
+
? `${site.site}/${tool.toolName} has a stale backends.json; runtime will fall back to the default ladder until reprobed`
|
|
858
|
+
: `${site.site}/${tool.toolName} has an invalid backends.json; runtime will fall back to the default ladder until reprobed`,
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
798
863
|
for (const wf of site.workflows) {
|
|
799
864
|
if (wf.missingSession) {
|
|
800
865
|
issues.push({
|
|
@@ -1091,7 +1156,7 @@ function pruneTeachState(
|
|
|
1091
1156
|
for (const site of sites) {
|
|
1092
1157
|
const statePath = teachStatePath(site);
|
|
1093
1158
|
if (!existsSync(statePath)) continue;
|
|
1094
|
-
const status = scanLocalSite(site, localSiteDir(site));
|
|
1159
|
+
const status = scanLocalSite(site, localSiteDir(site), ctx.imprintHome);
|
|
1095
1160
|
const remove = new Set(
|
|
1096
1161
|
status.workflows
|
|
1097
1162
|
.filter(
|
|
@@ -20,7 +20,7 @@ import { resolveLadder, runWithLadder } from './backend-ladder.ts';
|
|
|
20
20
|
import type { CdpBrowserFetch } from './cdp-browser-fetch.ts';
|
|
21
21
|
import { createLog } from './log.ts';
|
|
22
22
|
import { imprintHomeDir } from './paths.ts';
|
|
23
|
-
import {
|
|
23
|
+
import { loadBackendsCacheStatus, persistRuntimeBackendsCache } from './probe-backends.ts';
|
|
24
24
|
import { checkSiteCredentialsReady } from './runtime.ts';
|
|
25
25
|
import { availableSitesHint } from './sites.ts';
|
|
26
26
|
import type { StealthFetch } from './stealth-fetch.ts';
|
|
@@ -173,7 +173,7 @@ function buildServer(
|
|
|
173
173
|
|
|
174
174
|
try {
|
|
175
175
|
const ladder = resolveLadder('auto', tool.preferredOrder);
|
|
176
|
-
const { result, usedBackend } = await runWithLadder(
|
|
176
|
+
const { result, usedBackend, attempts } = await runWithLadder(
|
|
177
177
|
ladder,
|
|
178
178
|
tool,
|
|
179
179
|
args,
|
|
@@ -209,6 +209,24 @@ function buildServer(
|
|
|
209
209
|
content: [{ type: 'text', text: `${text}\n(backend: ${usedBackend})` }],
|
|
210
210
|
};
|
|
211
211
|
}
|
|
212
|
+
try {
|
|
213
|
+
const cache = persistRuntimeBackendsCache({
|
|
214
|
+
tool,
|
|
215
|
+
assetRoot,
|
|
216
|
+
usedBackend,
|
|
217
|
+
attempts,
|
|
218
|
+
});
|
|
219
|
+
if (cache) {
|
|
220
|
+
tool.preferredOrder = cache.preferredOrder;
|
|
221
|
+
log(
|
|
222
|
+
` learned backend order for ${tool.workflow.toolName}: ${cache.preferredOrder.join(' → ')}`,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
} catch (err) {
|
|
226
|
+
log(
|
|
227
|
+
` warning: could not persist backend order for ${tool.workflow.toolName}: ${err instanceof Error ? err.message : String(err)}`,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
212
230
|
const text =
|
|
213
231
|
typeof result.data === 'string' ? result.data : JSON.stringify(result.data, null, 2);
|
|
214
232
|
return { content: [{ type: 'text', text: `${text}\n\n(backend: ${usedBackend})` }] };
|
|
@@ -250,12 +268,19 @@ export async function runMcpServer(opts: RunMcpServerOptions): Promise<void> {
|
|
|
250
268
|
const discovered = await discoverTools(assetRoot, opts.site, '[imprint mcp]');
|
|
251
269
|
const tools: ResolvedTool[] = discovered.map((t) => {
|
|
252
270
|
const playbookPath = pathResolve(t.dir, 'playbook.yaml');
|
|
253
|
-
const
|
|
271
|
+
const cacheStatus = loadBackendsCacheStatus(t.site, assetRoot, t.dir, {
|
|
272
|
+
toolName: t.workflow.toolName,
|
|
273
|
+
});
|
|
274
|
+
if (cacheStatus.status === 'stale' || cacheStatus.status === 'invalid') {
|
|
275
|
+
log(
|
|
276
|
+
` ${t.workflow.toolName}: ${cacheStatus.status} backends.json (${cacheStatus.reason}); run \`${cacheStatus.remediation}\``,
|
|
277
|
+
);
|
|
278
|
+
}
|
|
254
279
|
return {
|
|
255
280
|
...t,
|
|
256
281
|
inputSchema: buildJsonSchema(t.workflow.parameters),
|
|
257
282
|
playbookPath: existsSync(playbookPath) ? playbookPath : undefined,
|
|
258
|
-
preferredOrder: cache
|
|
283
|
+
preferredOrder: cacheStatus.status === 'ok' ? cacheStatus.cache.preferredOrder : undefined,
|
|
259
284
|
};
|
|
260
285
|
});
|
|
261
286
|
if (tools.length === 0) {
|