ai-runtime-engine 1.3.0 → 2.7.0
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/CHANGELOG.md +578 -0
- package/dist/agents/admit.d.ts +69 -0
- package/dist/agents/admit.js +129 -0
- package/dist/agents/definition.d.ts +36 -0
- package/dist/agents/definition.js +9 -0
- package/dist/agents/envelope.d.ts +53 -0
- package/dist/agents/envelope.js +68 -0
- package/dist/agents/finding.d.ts +79 -0
- package/dist/agents/finding.js +80 -0
- package/dist/agents/task.d.ts +60 -0
- package/dist/agents/task.js +32 -0
- package/dist/agents/worker.d.ts +68 -0
- package/dist/agents/worker.js +256 -0
- package/dist/capabilities/capability.d.ts +117 -0
- package/dist/capabilities/capability.js +66 -0
- package/dist/capabilities/registry.d.ts +139 -0
- package/dist/capabilities/registry.js +413 -0
- package/dist/capabilities/vocabulary.d.ts +32 -0
- package/dist/capabilities/vocabulary.js +34 -0
- package/dist/cli/cli.js +55 -4
- package/dist/cli/commands/cleanup.js +29 -27
- package/dist/cli/commands/doctor.d.ts +14 -0
- package/dist/cli/commands/doctor.js +38 -8
- package/dist/cli/commands/executions.js +34 -25
- package/dist/cli/commands/info.d.ts +1 -0
- package/dist/cli/commands/info.js +11 -9
- package/dist/cli/commands/init.js +19 -0
- package/dist/cli/commands/inspect.d.ts +40 -1
- package/dist/cli/commands/inspect.js +157 -2
- package/dist/cli/commands/mcp.d.ts +45 -0
- package/dist/cli/commands/mcp.js +148 -0
- package/dist/cli/commands/route.js +21 -0
- package/dist/cli/commands/run.d.ts +1 -0
- package/dist/cli/commands/run.js +21 -2
- package/dist/cli/commands/skills.d.ts +2 -0
- package/dist/cli/commands/skills.js +29 -7
- package/dist/cli/interactive/ansi.d.ts +41 -0
- package/dist/cli/interactive/ansi.js +43 -0
- package/dist/cli/interactive/complete.d.ts +10 -0
- package/dist/cli/interactive/complete.js +19 -0
- package/dist/cli/interactive/repl.d.ts +3 -0
- package/dist/cli/interactive/repl.js +91 -13
- package/dist/cli/interactive/session.d.ts +8 -0
- package/dist/cli/interactive/session.js +73 -2
- package/dist/cli/render.d.ts +7 -0
- package/dist/cli/render.js +10 -0
- package/dist/cli/runtimeSession.d.ts +11 -0
- package/dist/cli/runtimeSession.js +17 -0
- package/dist/config/defaults.d.ts +3 -1
- package/dist/config/defaults.js +2 -0
- package/dist/config/schema.d.ts +1 -0
- package/dist/config/schema.js +2 -2
- package/dist/context/lossVerifier.d.ts +24 -0
- package/dist/context/lossVerifier.js +45 -0
- package/dist/context/summarize.d.ts +19 -0
- package/dist/context/summarize.js +53 -0
- package/dist/core/fallback/fallback.d.ts +5 -0
- package/dist/core/fallback/fallback.js +3 -1
- package/dist/core/router/router.d.ts +3 -0
- package/dist/core/router/router.js +1 -0
- package/dist/executions/execution.d.ts +13 -2
- package/dist/generation/generateAdapter.d.ts +14 -0
- package/dist/generation/generateAdapter.js +38 -0
- package/dist/generation/generateSkill.d.ts +26 -0
- package/dist/generation/generateSkill.js +51 -0
- package/dist/index.d.ts +43 -4
- package/dist/index.js +26 -2
- package/dist/mcp/client.d.ts +70 -0
- package/dist/mcp/client.js +221 -0
- package/dist/mcp/manager.d.ts +151 -0
- package/dist/mcp/manager.js +493 -0
- package/dist/mcp/protocol.d.ts +216 -0
- package/dist/mcp/protocol.js +149 -0
- package/dist/mcp/toolAdapter.d.ts +44 -0
- package/dist/mcp/toolAdapter.js +94 -0
- package/dist/mcp/transport.d.ts +109 -0
- package/dist/mcp/transport.js +383 -0
- package/dist/memory/embedders/hash.d.ts +12 -0
- package/dist/memory/embedders/hash.js +31 -0
- package/dist/memory/embedders/http.d.ts +25 -0
- package/dist/memory/embedders/http.js +48 -0
- package/dist/memory/memory.d.ts +19 -2
- package/dist/memory/memory.js +75 -11
- package/dist/memory/semantic.d.ts +17 -0
- package/dist/memory/semantic.js +29 -0
- package/dist/orchestration/budget.d.ts +30 -0
- package/dist/orchestration/budget.js +40 -0
- package/dist/orchestration/executor.d.ts +39 -1
- package/dist/orchestration/executor.js +64 -4
- package/dist/orchestration/orchestrator.d.ts +29 -1
- package/dist/orchestration/orchestrator.js +89 -8
- package/dist/orchestration/plan.d.ts +15 -1
- package/dist/orchestration/plan.js +23 -4
- package/dist/orchestration/planner.d.ts +19 -1
- package/dist/orchestration/planner.js +25 -5
- package/dist/plugin/ai.d.ts +4 -0
- package/dist/plugin/ai.js +9 -0
- package/dist/runtime/config.js +50 -6
- package/dist/runtime/intent/aiClassifier.d.ts +19 -0
- package/dist/runtime/intent/aiClassifier.js +74 -0
- package/dist/runtime/models/modelProfile.d.ts +61 -0
- package/dist/runtime/models/modelProfile.js +139 -0
- package/dist/runtime/planning/deriveCapabilities.d.ts +95 -0
- package/dist/runtime/planning/deriveCapabilities.js +146 -0
- package/dist/runtime/policy.d.ts +10 -0
- package/dist/runtime/policy.js +9 -2
- package/dist/runtime/runtime.d.ts +173 -0
- package/dist/runtime/runtime.js +705 -50
- package/dist/runtime/types.d.ts +88 -2
- package/dist/skills/manifest.d.ts +3 -0
- package/dist/skills/manifest.js +24 -0
- package/dist/skills/registry.d.ts +16 -1
- package/dist/skills/registry.js +21 -1
- package/dist/skills/skill.d.ts +6 -1
- package/dist/store/area.d.ts +15 -1
- package/dist/store/area.js +19 -8
- package/dist/store/crypto.d.ts +21 -0
- package/dist/store/crypto.js +49 -0
- package/dist/store/paths.d.ts +5 -1
- package/dist/store/paths.js +6 -0
- package/dist/store/store.d.ts +15 -3
- package/dist/store/store.js +28 -7
- package/dist/telemetry/sinks/otlp.d.ts +31 -0
- package/dist/telemetry/sinks/otlp.js +76 -0
- package/dist/tools/builtins/filesystem.js +1 -0
- package/dist/tools/builtins/git.js +1 -0
- package/dist/tools/builtins/shell.js +1 -0
- package/dist/tools/permissions.d.ts +28 -0
- package/dist/tools/permissions.js +72 -0
- package/dist/tools/registry.d.ts +18 -2
- package/dist/tools/registry.js +22 -2
- package/dist/tools/tool.d.ts +4 -0
- package/dist/types.d.ts +5 -1
- package/dist/util/flatten.d.ts +11 -0
- package/dist/util/flatten.js +18 -0
- package/dist/util/semaphore.d.ts +19 -0
- package/dist/util/semaphore.js +60 -0
- package/package.json +24 -9
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill scaffolding (Phase 21a) — the generative seam for skills, mirroring `generateAdapter`'s
|
|
3
|
+
* "emit validated CONFIG/manifest, never code" rule. A model drafts a `*.skill.yaml` MANIFEST (pure data,
|
|
4
|
+
* parsed by the existing `parseManifest`); we then validate it three ways before offering it:
|
|
5
|
+
* 1. `parseManifest` (shape),
|
|
6
|
+
* 2. every step names an AVAILABLE tool (a `validatePlan`-style membership check — no invented tools),
|
|
7
|
+
* 3. a dry `manifestToSkill` compile (it must not throw).
|
|
8
|
+
* Nothing is written here — the caller writes it only on explicit confirm into the consented
|
|
9
|
+
* `.ai-runtime/skills/` directory. A drafted manifest that fails any check is refused with a reason.
|
|
10
|
+
*/
|
|
11
|
+
import { stringify as toYaml } from 'yaml';
|
|
12
|
+
import { parseManifest, manifestToSkill } from '../skills/manifest.js';
|
|
13
|
+
import { wrapUntrusted } from '../tools/untrusted.js';
|
|
14
|
+
import { extractJson } from '../util/extractJson.js';
|
|
15
|
+
export async function generateSkillManifest(input) {
|
|
16
|
+
const goal = String(input.goal ?? '').trim();
|
|
17
|
+
if (!goal)
|
|
18
|
+
return { ok: false, error: 'a goal is required' };
|
|
19
|
+
const tools = input.tools ?? [];
|
|
20
|
+
if (tools.length === 0)
|
|
21
|
+
return { ok: false, error: 'no tools are available to compose a skill from' };
|
|
22
|
+
const prompt = `Draft an ai-runtime skill MANIFEST (pure data — NOT code) that achieves the goal below. ` +
|
|
23
|
+
`The manifest runs a sequence of tool steps. Use ONLY these registered tools: ${tools.join(', ')}.\n\n` +
|
|
24
|
+
`Goal:\n${wrapUntrusted('scaffold-goal', goal)}\n\n` +
|
|
25
|
+
`Reply ONLY with JSON of this exact shape: {"id":"<kebab-case-id>","version":"1.0.0","name":"<short name>",` +
|
|
26
|
+
`"description":"<one line>","runtimeCompatibility":">=1.0.0","steps":[{"tool":"<one of the tools>","description":"<what this step does>"}]}.`;
|
|
27
|
+
// The whole model round-trip is wrapped: a bad model reply (e.g. non-JSON, which makes `extractJson`
|
|
28
|
+
// throw) must surface as { ok:false }, never as a thrown error — scaffolding is an assist, not a hard op.
|
|
29
|
+
try {
|
|
30
|
+
const run = await input.ai.run({ task: 'generate-skill', input: prompt, output: { format: 'json' }, requirements: { intelligence: ['reasoning'], output: ['json'] } });
|
|
31
|
+
if (!run.ok || !run.response)
|
|
32
|
+
return { ok: false, error: `generation failed (${run.error?.category ?? 'no response'})` };
|
|
33
|
+
const raw = run.response.json ?? extractJson(run.response.text ?? '');
|
|
34
|
+
const { manifest, error } = parseManifest(raw);
|
|
35
|
+
if (!manifest)
|
|
36
|
+
return { ok: false, error: `drafted manifest is invalid: ${error ?? 'unparseable'}` };
|
|
37
|
+
// Membership: a drafted step may only name a tool the host actually has (no invented capabilities).
|
|
38
|
+
const unknown = [...new Set(manifest.steps.map((s) => s.tool).filter((t) => !tools.includes(t)))];
|
|
39
|
+
if (unknown.length)
|
|
40
|
+
return { ok: false, error: `manifest names unregistered tool(s): ${unknown.join(', ')}` };
|
|
41
|
+
// A safe, filesystem-friendly id (also the filename); reject anything that could escape the skills dir.
|
|
42
|
+
if (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(manifest.id))
|
|
43
|
+
return { ok: false, error: `manifest id must be kebab-case ([a-z0-9-]): got ${JSON.stringify(manifest.id)}` };
|
|
44
|
+
// Dry compile — the manifest must produce a runnable Skill without throwing.
|
|
45
|
+
manifestToSkill(manifest, '(scaffold)');
|
|
46
|
+
return { ok: true, manifest, yaml: toYaml(manifest) };
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
return { ok: false, error: `could not scaffold a skill: ${e instanceof Error ? e.message : String(e)}` };
|
|
50
|
+
}
|
|
51
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -44,14 +44,20 @@ export { analyzeOpenApi, analyzeOpenApiFile } from './discovery/openapi.js';
|
|
|
44
44
|
export type { OpenApiAnalysis } from './discovery/openapi.js';
|
|
45
45
|
export { benchmark } from './benchmark/benchmark.js';
|
|
46
46
|
export type { BenchResult, BenchTaskSpec } from './benchmark/benchmark.js';
|
|
47
|
-
export { generateProviderConfig, generateProviderConfigFromFile } from './generation/generateAdapter.js';
|
|
48
|
-
export type { GenerateOptions } from './generation/generateAdapter.js';
|
|
47
|
+
export { generateProviderConfig, generateProviderConfigFromFile, suggestProviderConfig } from './generation/generateAdapter.js';
|
|
48
|
+
export type { GenerateOptions, SuggestResult } from './generation/generateAdapter.js';
|
|
49
|
+
export { generateSkillManifest } from './generation/generateSkill.js';
|
|
50
|
+
export type { GenerateSkillInput, GenerateSkillResult } from './generation/generateSkill.js';
|
|
49
51
|
export { McpRegistry, StaticMcpSource } from './mcp/mcp.js';
|
|
50
52
|
export type { McpToolSource } from './mcp/mcp.js';
|
|
51
53
|
export { setCredentialResolver, Credential } from './security/credentials.js';
|
|
52
54
|
export type { CredentialResolver } from './security/credentials.js';
|
|
53
55
|
export { CallbackSink, MemorySink, MultiSink, NullSink } from './telemetry/telemetry.js';
|
|
54
56
|
export type { TelemetrySink } from './telemetry/telemetry.js';
|
|
57
|
+
export { FileSink } from './telemetry/sinks/file.js';
|
|
58
|
+
export { OtlpSink } from './telemetry/sinks/otlp.js';
|
|
59
|
+
export type { OtlpSinkOptions } from './telemetry/sinks/otlp.js';
|
|
60
|
+
export { KeyedSemaphore } from './util/semaphore.js';
|
|
55
61
|
export { MARKETPLACE, listPresets, findPreset, presetToConfig } from './marketplace/presets.js';
|
|
56
62
|
export type { ProviderPreset } from './marketplace/presets.js';
|
|
57
63
|
export { loadRemoteConfig, loadConfigAsync } from './config/load.js';
|
|
@@ -64,6 +70,7 @@ export { resolvePolicy, defaultAutonomyForMode } from './runtime/policy.js';
|
|
|
64
70
|
export type { ExecutionPolicy, PermissionPolicy, Autonomy, ApprovalLevel, PolicyOverrides } from './runtime/policy.js';
|
|
65
71
|
export { parseRuntimeConfig, loadRuntimeConfig, findRuntimeConfigFile } from './runtime/config.js';
|
|
66
72
|
export { HeuristicIntentClassifier } from './runtime/intent/classifier.js';
|
|
73
|
+
export { AIIntentClassifier } from './runtime/intent/aiClassifier.js';
|
|
67
74
|
export type { IntentClassifier, IntentClassification, IntentInput } from './runtime/intent/classifier.js';
|
|
68
75
|
export { detectWorkspace, summarizeWorkspace } from './runtime/workspace/workspace.js';
|
|
69
76
|
export type { WorkspaceInfo } from './runtime/workspace/workspace.js';
|
|
@@ -77,8 +84,29 @@ export type { AccessState, ProviderView, ModelView, ProviderViewSource } from '.
|
|
|
77
84
|
export { RuntimeStore, STORE_VERSION } from './store/store.js';
|
|
78
85
|
export type { RuntimeStoreOptions, StorePaths } from './store/store.js';
|
|
79
86
|
export { FileArea, NullArea } from './store/area.js';
|
|
80
|
-
export type { Area, IntegrityIssue } from './store/area.js';
|
|
81
|
-
export {
|
|
87
|
+
export type { Area, IntegrityIssue, ContentCodec } from './store/area.js';
|
|
88
|
+
export { makeCodec, deriveKey, encryptString, decryptString, ENVELOPE_PREFIX } from './store/crypto.js';
|
|
89
|
+
export { resolveHome, projectId, repositoryId, organizationId, findRepoRoot } from './store/paths.js';
|
|
90
|
+
export { deriveCapabilities, deriveCapabilitiesOffline, candidatesFrom, candidateSlate, DERIVE_MAX_CANDIDATES, DERIVE_MAX_IDS } from './runtime/planning/deriveCapabilities.js';
|
|
91
|
+
export type { CapabilityCandidate, DeriveCapabilitiesInput, DeriveCapabilitiesResult } from './runtime/planning/deriveCapabilities.js';
|
|
92
|
+
export { ActionCapabilityRegistry, capabilityReportFrom } from './capabilities/registry.js';
|
|
93
|
+
export type { ActionCapabilityQuery, CapabilityConfig, RegistryAccessors } from './capabilities/registry.js';
|
|
94
|
+
export { CURATED_VOCABULARY, curated, isCurated } from './capabilities/vocabulary.js';
|
|
95
|
+
export type { CuratedCapability, PermissionHint } from './capabilities/vocabulary.js';
|
|
96
|
+
export type { ActionCapability, ActionCapabilityDecl, ActionCapabilityEffect, ActionCapabilityProvider, ActionCapabilitySource, CapabilityGap, CapabilityGapReason, CapabilityResolution, GapSuggestion, MissingCapabilityReport, ProviderAvailability, } from './capabilities/capability.js';
|
|
97
|
+
export type { AgentDefinition } from './agents/definition.js';
|
|
98
|
+
export type { AgentEnvelope } from './agents/envelope.js';
|
|
99
|
+
export type { AgentTaskRecord, AgentTaskState } from './agents/task.js';
|
|
100
|
+
export type { Finding, FindingEvidence, FindingEvidenceKind, FindingStatus } from './agents/finding.js';
|
|
101
|
+
export type { AdmissionRejection, AdmissionResult } from './agents/admit.js';
|
|
102
|
+
export type { StepObservationCode } from './orchestration/executor.js';
|
|
103
|
+
export { PLAN_STEP_STATUSES } from './orchestration/plan.js';
|
|
104
|
+
export { mcpToolId } from './mcp/toolAdapter.js';
|
|
105
|
+
export { MCP_PROTOCOL_VERSION } from './mcp/protocol.js';
|
|
106
|
+
export type { McpServerConfig, McpServerState, McpServerStatus, McpTransportKind } from './mcp/manager.js';
|
|
107
|
+
export type { McpServerSettings } from './runtime/types.js';
|
|
108
|
+
export type { McpServerGrant } from './tools/permissions.js';
|
|
109
|
+
export { clampMcpPermissions, clampPermissions } from './tools/permissions.js';
|
|
82
110
|
export type { StoreScope } from './store/paths.js';
|
|
83
111
|
export { ConversationStore } from './conversations/conversations.js';
|
|
84
112
|
export type { ConversationTurn, ConversationMeta, TurnRole } from './conversations/conversations.js';
|
|
@@ -87,9 +115,18 @@ export type { MemoryRecord, MemoryScope, MemorySource, RememberInput, SearchOpti
|
|
|
87
115
|
export { classifyMemory } from './memory/classifier.js';
|
|
88
116
|
export type { MemoryCandidate } from './memory/classifier.js';
|
|
89
117
|
export { bm25Search, tokenize } from './memory/bm25.js';
|
|
118
|
+
export { cosineSimilarity, semanticRank } from './memory/semantic.js';
|
|
119
|
+
export type { VecDoc, RankedHit } from './memory/semantic.js';
|
|
120
|
+
export { HashEmbedder } from './memory/embedders/hash.js';
|
|
121
|
+
export { HttpEmbedder } from './memory/embedders/http.js';
|
|
122
|
+
export type { HttpEmbedderConfig } from './memory/embedders/http.js';
|
|
90
123
|
export type { MemoryTrace } from './runtime/types.js';
|
|
91
124
|
export { compileContext } from './context/compiler.js';
|
|
92
125
|
export type { ContextBlock, CompiledBlock, CompiledContext, CompileMetrics, CompileOptions, ContextValidation, RetentionLevel } from './context/compiler.js';
|
|
126
|
+
export { verifyContextLoss } from './context/lossVerifier.js';
|
|
127
|
+
export type { VerifyLossInput, LossCheck } from './context/lossVerifier.js';
|
|
128
|
+
export { summarizeOverBudget } from './context/summarize.js';
|
|
129
|
+
export type { SummarizeInput } from './context/summarize.js';
|
|
93
130
|
export { TokenEstimator, estimateTokens } from './context/tokens.js';
|
|
94
131
|
export { resolveContextBudget, DEFAULT_CONTEXT_TOKENS } from './context/budget.js';
|
|
95
132
|
export type { ContextBudgetInputs } from './context/budget.js';
|
|
@@ -141,6 +178,8 @@ export type { LearningOutcome, LearnedStat, SkillRef } from './learning/learning
|
|
|
141
178
|
export { classifyFeedback } from './learning/feedback.js';
|
|
142
179
|
export type { FeedbackSignal } from './learning/feedback.js';
|
|
143
180
|
export { resolveRoutingPrefs, withPreferredProviders } from './runtime/routing.js';
|
|
181
|
+
export { parseModelProfile, parseDirective, resolveModelDirective, directiveToOverrides, loadModelProfile } from './runtime/models/modelProfile.js';
|
|
182
|
+
export type { ModelProfile, ModelDirective } from './runtime/models/modelProfile.js';
|
|
144
183
|
export type { RoutingSources } from './runtime/routing.js';
|
|
145
184
|
export { isExcluded, isPreferred } from './core/router/routingPrefs.js';
|
|
146
185
|
export type { RoutingPreferences } from './types.js';
|
package/dist/index.js
CHANGED
|
@@ -40,10 +40,14 @@ export { runVerification } from './verification/verify.js';
|
|
|
40
40
|
export { analyzeOpenApi, analyzeOpenApiFile } from './discovery/openapi.js';
|
|
41
41
|
export { benchmark } from './benchmark/benchmark.js';
|
|
42
42
|
// Enterprise & extensibility — dynamic adapters, MCP, enterprise credentials, observability, policy, marketplace, remote
|
|
43
|
-
export { generateProviderConfig, generateProviderConfigFromFile } from './generation/generateAdapter.js';
|
|
43
|
+
export { generateProviderConfig, generateProviderConfigFromFile, suggestProviderConfig } from './generation/generateAdapter.js';
|
|
44
|
+
export { generateSkillManifest } from './generation/generateSkill.js';
|
|
44
45
|
export { McpRegistry, StaticMcpSource } from './mcp/mcp.js';
|
|
45
46
|
export { setCredentialResolver, Credential } from './security/credentials.js';
|
|
46
47
|
export { CallbackSink, MemorySink, MultiSink, NullSink } from './telemetry/telemetry.js';
|
|
48
|
+
export { FileSink } from './telemetry/sinks/file.js';
|
|
49
|
+
export { OtlpSink } from './telemetry/sinks/otlp.js';
|
|
50
|
+
export { KeyedSemaphore } from './util/semaphore.js';
|
|
47
51
|
export { MARKETPLACE, listPresets, findPreset, presetToConfig } from './marketplace/presets.js';
|
|
48
52
|
export { loadRemoteConfig, loadConfigAsync } from './config/load.js';
|
|
49
53
|
// ── Runtime (Phase 1) — high-level facade over the router: modes, workspace, policy ──
|
|
@@ -52,6 +56,7 @@ export { RUNTIME_MODES } from './runtime/types.js';
|
|
|
52
56
|
export { resolvePolicy, defaultAutonomyForMode } from './runtime/policy.js';
|
|
53
57
|
export { parseRuntimeConfig, loadRuntimeConfig, findRuntimeConfigFile } from './runtime/config.js';
|
|
54
58
|
export { HeuristicIntentClassifier } from './runtime/intent/classifier.js';
|
|
59
|
+
export { AIIntentClassifier } from './runtime/intent/aiClassifier.js';
|
|
55
60
|
export { detectWorkspace, summarizeWorkspace } from './runtime/workspace/workspace.js';
|
|
56
61
|
export { MODE_AVAILABILITY, IMPLEMENTED_MODES } from './runtime/modes/availability.js';
|
|
57
62
|
export { CHAT_TASK } from './runtime/modes/chat.js';
|
|
@@ -61,13 +66,31 @@ export { deriveAccessState, buildProviderViews, ProviderViewCache } from './runt
|
|
|
61
66
|
// ── Runtime (Phase 3) — local store, conversations, memory ──
|
|
62
67
|
export { RuntimeStore, STORE_VERSION } from './store/store.js';
|
|
63
68
|
export { FileArea, NullArea } from './store/area.js';
|
|
64
|
-
export {
|
|
69
|
+
export { makeCodec, deriveKey, encryptString, decryptString, ENVELOPE_PREFIX } from './store/crypto.js';
|
|
70
|
+
export { resolveHome, projectId, repositoryId, organizationId, findRepoRoot } from './store/paths.js';
|
|
71
|
+
// ── Capability-first planning (Phase 3.3) — offline BM25 derivation, one model rung under it ──
|
|
72
|
+
export { deriveCapabilities, deriveCapabilitiesOffline, candidatesFrom, candidateSlate, DERIVE_MAX_CANDIDATES, DERIVE_MAX_IDS } from './runtime/planning/deriveCapabilities.js';
|
|
73
|
+
// ── Action capabilities (Phase 3.1) — what the runtime can DO, and who provides it ──
|
|
74
|
+
export { ActionCapabilityRegistry, capabilityReportFrom } from './capabilities/registry.js';
|
|
75
|
+
export { CURATED_VOCABULARY, curated, isCurated } from './capabilities/vocabulary.js';
|
|
76
|
+
export { PLAN_STEP_STATUSES } from './orchestration/plan.js';
|
|
77
|
+
// ── MCP connectivity (Phase 3.2) — external servers as ordinary Runtime tools ──
|
|
78
|
+
// The CONFIG + STATUS surface is public; the client, transports, and manager internals are not, so the
|
|
79
|
+
// wire implementation stays free to change without a breaking release.
|
|
80
|
+
export { mcpToolId } from './mcp/toolAdapter.js';
|
|
81
|
+
export { MCP_PROTOCOL_VERSION } from './mcp/protocol.js';
|
|
82
|
+
export { clampMcpPermissions, clampPermissions } from './tools/permissions.js';
|
|
65
83
|
export { ConversationStore } from './conversations/conversations.js';
|
|
66
84
|
export { MemoryStore } from './memory/memory.js';
|
|
67
85
|
export { classifyMemory } from './memory/classifier.js';
|
|
68
86
|
export { bm25Search, tokenize } from './memory/bm25.js';
|
|
87
|
+
export { cosineSimilarity, semanticRank } from './memory/semantic.js';
|
|
88
|
+
export { HashEmbedder } from './memory/embedders/hash.js';
|
|
89
|
+
export { HttpEmbedder } from './memory/embedders/http.js';
|
|
69
90
|
// ── Runtime (Phase 4) — context compiler + token budgets ──
|
|
70
91
|
export { compileContext } from './context/compiler.js';
|
|
92
|
+
export { verifyContextLoss } from './context/lossVerifier.js';
|
|
93
|
+
export { summarizeOverBudget } from './context/summarize.js';
|
|
71
94
|
export { TokenEstimator, estimateTokens } from './context/tokens.js';
|
|
72
95
|
export { resolveContextBudget, DEFAULT_CONTEXT_TOKENS } from './context/budget.js';
|
|
73
96
|
// ── Runtime (Phase 5) — tools, permissions, skills, untrusted-data boundary ──
|
|
@@ -104,4 +127,5 @@ export { renderComparison } from './comparison/render.js';
|
|
|
104
127
|
export { LearningStore } from './learning/learningStore.js';
|
|
105
128
|
export { classifyFeedback } from './learning/feedback.js';
|
|
106
129
|
export { resolveRoutingPrefs, withPreferredProviders } from './runtime/routing.js';
|
|
130
|
+
export { parseModelProfile, parseDirective, resolveModelDirective, directiveToOverrides, loadModelProfile } from './runtime/models/modelProfile.js';
|
|
107
131
|
export { isExcluded, isPreferred } from './core/router/routingPrefs.js';
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP client (Phase 3.2) — the JSON-RPC conversation over any `McpTransport`: `initialize` (with
|
|
3
|
+
* version negotiation), `tools/list` (bounded pagination), `tools/call` (per-call timeout + abort), and
|
|
4
|
+
* `ping`. Requests are correlated by a monotonic id through a pending map, so concurrent calls to one
|
|
5
|
+
* server multiplex correctly.
|
|
6
|
+
*
|
|
7
|
+
* Every thrown error is an `AIError` with a redacted, URL-free message and a mapped category, so a
|
|
8
|
+
* caller (the manager → the Tool adapter) can classify auth vs timeout vs transport without ever seeing
|
|
9
|
+
* a credential or an endpoint.
|
|
10
|
+
*/
|
|
11
|
+
import type { Clock } from '../util/clock.js';
|
|
12
|
+
import type { McpTransport } from './transport.js';
|
|
13
|
+
import type { McpCallResult, NormalizedTool } from './protocol.js';
|
|
14
|
+
export interface McpClientOptions {
|
|
15
|
+
serverId: string;
|
|
16
|
+
clock?: Clock;
|
|
17
|
+
/** Default per-request timeout (ms). */
|
|
18
|
+
timeoutMs?: number;
|
|
19
|
+
/** Max `tools/list` pages to follow — a malicious server must not loop us forever. */
|
|
20
|
+
maxPages?: number;
|
|
21
|
+
/** Max tools accepted from one server (registry-flood bound). */
|
|
22
|
+
maxTools?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface McpHandshake {
|
|
25
|
+
protocolVersion: string;
|
|
26
|
+
serverName?: string;
|
|
27
|
+
serverVersion?: string;
|
|
28
|
+
/** True when the server answered with a version we did not ask for (we still proceed, marked degraded). */
|
|
29
|
+
versionMismatch: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface McpToolList {
|
|
32
|
+
tools: NormalizedTool[];
|
|
33
|
+
/** Declarations that failed validation/normalization and were NOT registered. */
|
|
34
|
+
rejected: number;
|
|
35
|
+
/** True when pagination hit `maxPages`/`maxTools` and the list is therefore partial. */
|
|
36
|
+
truncated: boolean;
|
|
37
|
+
}
|
|
38
|
+
export declare class McpClient {
|
|
39
|
+
private readonly transport;
|
|
40
|
+
private readonly opts;
|
|
41
|
+
private nextId;
|
|
42
|
+
private readonly pending;
|
|
43
|
+
private readonly clock;
|
|
44
|
+
private started;
|
|
45
|
+
/** Errors the server sent with no correlatable id, plus discovery anomalies — surfaced as diagnostics. */
|
|
46
|
+
private readonly protocolErrors;
|
|
47
|
+
constructor(transport: McpTransport, opts: McpClientOptions);
|
|
48
|
+
private onMessage;
|
|
49
|
+
/** Drop a pending entry and release everything attached to it (timer + abort listener). */
|
|
50
|
+
private settle;
|
|
51
|
+
private request;
|
|
52
|
+
/** Connect + negotiate. A counter-proposed version is accepted but reported as a mismatch. */
|
|
53
|
+
initialize(): Promise<McpHandshake>;
|
|
54
|
+
/** List tools, following `nextCursor` up to `maxPages`. Every declaration is normalized (or rejected). */
|
|
55
|
+
listTools(opts?: {
|
|
56
|
+
signal?: AbortSignal;
|
|
57
|
+
}): Promise<McpToolList>;
|
|
58
|
+
callTool(name: string, args: unknown, opts?: {
|
|
59
|
+
signal?: AbortSignal;
|
|
60
|
+
timeoutMs?: number;
|
|
61
|
+
}): Promise<McpCallResult>;
|
|
62
|
+
ping(): Promise<{
|
|
63
|
+
ok: boolean;
|
|
64
|
+
latencyMs: number;
|
|
65
|
+
}>;
|
|
66
|
+
close(): Promise<void>;
|
|
67
|
+
diagnostics(): string[];
|
|
68
|
+
/** Whether the transport says our session is gone and only a fresh handshake can recover. */
|
|
69
|
+
sessionExpired(): boolean;
|
|
70
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP client (Phase 3.2) — the JSON-RPC conversation over any `McpTransport`: `initialize` (with
|
|
3
|
+
* version negotiation), `tools/list` (bounded pagination), `tools/call` (per-call timeout + abort), and
|
|
4
|
+
* `ping`. Requests are correlated by a monotonic id through a pending map, so concurrent calls to one
|
|
5
|
+
* server multiplex correctly.
|
|
6
|
+
*
|
|
7
|
+
* Every thrown error is an `AIError` with a redacted, URL-free message and a mapped category, so a
|
|
8
|
+
* caller (the manager → the Tool adapter) can classify auth vs timeout vs transport without ever seeing
|
|
9
|
+
* a credential or an endpoint.
|
|
10
|
+
*/
|
|
11
|
+
import { AIError } from '../core/fallback/errors.js';
|
|
12
|
+
import { redactString } from '../security/redact.js';
|
|
13
|
+
import { systemClock } from '../util/clock.js';
|
|
14
|
+
import { clampText, MCP_CLIENT_INFO, MCP_PROTOCOL_VERSION, JSONRPC_AUTH_CODES, initializeResult, listToolsResult, normalizeToolDecl, flattenCallResult, } from './protocol.js';
|
|
15
|
+
export class McpClient {
|
|
16
|
+
transport;
|
|
17
|
+
opts;
|
|
18
|
+
nextId = 1;
|
|
19
|
+
pending = new Map();
|
|
20
|
+
clock;
|
|
21
|
+
started = false;
|
|
22
|
+
/** Errors the server sent with no correlatable id, plus discovery anomalies — surfaced as diagnostics. */
|
|
23
|
+
protocolErrors = [];
|
|
24
|
+
constructor(transport, opts) {
|
|
25
|
+
this.transport = transport;
|
|
26
|
+
this.opts = opts;
|
|
27
|
+
this.clock = opts.clock ?? systemClock;
|
|
28
|
+
this.transport.onMessage((msg) => this.onMessage(msg));
|
|
29
|
+
}
|
|
30
|
+
onMessage(msg) {
|
|
31
|
+
// A RESPONSE carries an id and exactly one of result/error, and never a method. A server-initiated
|
|
32
|
+
// REQUEST also carries an id — correlating on the id alone would resolve our pending call with
|
|
33
|
+
// `undefined` the moment a server sends one (sampling, roots, elicitation all do).
|
|
34
|
+
const m = msg;
|
|
35
|
+
if (typeof m.method === 'string')
|
|
36
|
+
return; // a request or notification from the server — not ours
|
|
37
|
+
if (m.id === undefined || m.id === null) {
|
|
38
|
+
// A parse/invalid-request error has no id, so it cannot be correlated. Recording it is the honest
|
|
39
|
+
// outcome: the affected request still times out, but the reason is visible in diagnostics.
|
|
40
|
+
if (m.error)
|
|
41
|
+
this.protocolErrors.push(clampText(`unattributable error ${m.error.code}: ${redactString(String(m.error.message ?? ''))}`, 200));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// We only ever send numeric ids; a server that echoes ours as a string is still talking to us.
|
|
45
|
+
const key = typeof m.id === 'number' ? m.id : Number(m.id);
|
|
46
|
+
if (!Number.isFinite(key))
|
|
47
|
+
return;
|
|
48
|
+
const p = this.pending.get(key);
|
|
49
|
+
if (!p)
|
|
50
|
+
return;
|
|
51
|
+
this.settle(key, p);
|
|
52
|
+
if (m.error) {
|
|
53
|
+
const category = JSONRPC_AUTH_CODES.has(m.error.code) ? 'AUTHENTICATION' : 'PROVIDER';
|
|
54
|
+
// The message is server-controlled: redacted, flattened, and clamped before it can reach a status
|
|
55
|
+
// line, a diagnostic, or a replan prompt.
|
|
56
|
+
p.reject(new AIError(`MCP '${this.opts.serverId}' error ${m.error.code}: ${clampText(redactString(String(m.error.message ?? '')), 200)}`, { category, retryable: category !== 'AUTHENTICATION' }));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
p.resolve(m.result);
|
|
60
|
+
}
|
|
61
|
+
/** Drop a pending entry and release everything attached to it (timer + abort listener). */
|
|
62
|
+
settle(id, p) {
|
|
63
|
+
this.pending.delete(id);
|
|
64
|
+
clearTimeout(p.timer);
|
|
65
|
+
p.detach?.(); // an abort listener outlives `{ once: true }` when the signal never fires
|
|
66
|
+
}
|
|
67
|
+
async request(method, params, opts = {}) {
|
|
68
|
+
const id = this.nextId++;
|
|
69
|
+
const req = { jsonrpc: '2.0', id, method, ...(params !== undefined ? { params } : {}) };
|
|
70
|
+
const timeoutMs = opts.timeoutMs ?? this.opts.timeoutMs ?? 30_000;
|
|
71
|
+
// NOTE: `result` is not awaited until `send()` returns, so a timeout that fires DURING send would
|
|
72
|
+
// otherwise be an unhandled rejection (fatal by default). A no-op catch marks it handled; the
|
|
73
|
+
// caller still observes the rejection through the returned promise.
|
|
74
|
+
// An already-aborted caller must not reach the wire at all: sending the request and THEN cancelling
|
|
75
|
+
// it would run the remote tool, and `notifications/cancelled` for an id we never issued is noise.
|
|
76
|
+
if (opts.signal?.aborted)
|
|
77
|
+
throw new AIError(`MCP '${this.opts.serverId}' request cancelled`, { category: 'TIMEOUT', retryable: false });
|
|
78
|
+
let sent = false;
|
|
79
|
+
const result = new Promise((resolve, reject) => {
|
|
80
|
+
const timer = setTimeout(() => {
|
|
81
|
+
this.pending.delete(id);
|
|
82
|
+
reject(new AIError(`MCP '${this.opts.serverId}' request timed out after ${timeoutMs}ms (${method})`, { category: 'TIMEOUT', retryable: true }));
|
|
83
|
+
}, timeoutMs);
|
|
84
|
+
// NOT unref'd on purpose: the timer is how a stalled server becomes a TIMEOUT error. If it were
|
|
85
|
+
// unref'd and the loop drained (stdio child gone), the caller's await would never settle at all.
|
|
86
|
+
const entry = { resolve, reject, timer };
|
|
87
|
+
this.pending.set(id, entry);
|
|
88
|
+
if (opts.signal) {
|
|
89
|
+
const signal = opts.signal;
|
|
90
|
+
const onAbort = () => {
|
|
91
|
+
const p = this.pending.get(id);
|
|
92
|
+
if (!p)
|
|
93
|
+
return;
|
|
94
|
+
this.settle(id, p);
|
|
95
|
+
// Cooperative cancel, but only for a request that actually reached the wire.
|
|
96
|
+
if (sent)
|
|
97
|
+
void this.transport.send({ jsonrpc: '2.0', method: 'notifications/cancelled', params: { requestId: id } }).catch(() => { });
|
|
98
|
+
p.reject(new AIError(`MCP '${this.opts.serverId}' request cancelled`, { category: 'TIMEOUT', retryable: false }));
|
|
99
|
+
};
|
|
100
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
101
|
+
entry.detach = () => signal.removeEventListener('abort', onAbort);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
result.catch(() => { });
|
|
105
|
+
try {
|
|
106
|
+
await this.transport.send(req);
|
|
107
|
+
sent = true;
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
// The request never reached the wire: settle and drop the pending entry NOW. Leaving it behind
|
|
111
|
+
// would strand its timer (holding the process open) and fire an unhandled rejection later.
|
|
112
|
+
const p = this.pending.get(id);
|
|
113
|
+
if (p) {
|
|
114
|
+
this.settle(id, p);
|
|
115
|
+
p.reject(e);
|
|
116
|
+
}
|
|
117
|
+
throw e;
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
/** Connect + negotiate. A counter-proposed version is accepted but reported as a mismatch. */
|
|
122
|
+
async initialize() {
|
|
123
|
+
if (!this.started) {
|
|
124
|
+
await this.transport.start();
|
|
125
|
+
this.started = true;
|
|
126
|
+
}
|
|
127
|
+
const raw = await this.request('initialize', {
|
|
128
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
129
|
+
capabilities: {},
|
|
130
|
+
clientInfo: MCP_CLIENT_INFO,
|
|
131
|
+
});
|
|
132
|
+
const parsed = initializeResult.safeParse(raw ?? {});
|
|
133
|
+
const data = parsed.success ? parsed.data : {};
|
|
134
|
+
// The negotiated version is echoed into status lines and error text, so it is clamped like any
|
|
135
|
+
// other server string (a "version" of 4 KB of prose is otherwise a free prompt-injection channel).
|
|
136
|
+
const version = typeof data.protocolVersion === 'string' && data.protocolVersion ? clampText(data.protocolVersion, 40) : MCP_PROTOCOL_VERSION;
|
|
137
|
+
// The spec requires MCP-Protocol-Version on every request AFTER the handshake; the transport owns
|
|
138
|
+
// the header, so tell it what we settled on before the first post-handshake call.
|
|
139
|
+
this.transport.setProtocolVersion?.(version);
|
|
140
|
+
// Per spec the client confirms readiness with a notification (servers may ignore it).
|
|
141
|
+
await this.transport.send({ jsonrpc: '2.0', method: 'notifications/initialized' }).catch(() => { });
|
|
142
|
+
return {
|
|
143
|
+
protocolVersion: version,
|
|
144
|
+
...(data.serverInfo?.name ? { serverName: clampText(data.serverInfo.name, 80) } : {}),
|
|
145
|
+
...(data.serverInfo?.version ? { serverVersion: clampText(data.serverInfo.version, 40) } : {}),
|
|
146
|
+
versionMismatch: version !== MCP_PROTOCOL_VERSION,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/** List tools, following `nextCursor` up to `maxPages`. Every declaration is normalized (or rejected). */
|
|
150
|
+
async listTools(opts = {}) {
|
|
151
|
+
const maxPages = this.opts.maxPages ?? 20;
|
|
152
|
+
const maxTools = this.opts.maxTools ?? 500;
|
|
153
|
+
const tools = [];
|
|
154
|
+
const seen = new Set();
|
|
155
|
+
let rejected = 0;
|
|
156
|
+
let truncated = false;
|
|
157
|
+
let cursor;
|
|
158
|
+
for (let page = 0; page < maxPages; page += 1) {
|
|
159
|
+
const raw = await this.request('tools/list', cursor ? { cursor } : {}, opts);
|
|
160
|
+
const parsed = listToolsResult.safeParse(raw ?? {});
|
|
161
|
+
if (!parsed.success) {
|
|
162
|
+
// A malformed page means the catalog is PARTIAL — saying otherwise would cache a truncated list
|
|
163
|
+
// as complete and silently hide the server's remaining tools.
|
|
164
|
+
this.protocolErrors.push(`tools/list page ${page + 1} was malformed; the catalog is partial`);
|
|
165
|
+
truncated = true;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
for (const decl of parsed.data.tools ?? []) {
|
|
169
|
+
const norm = normalizeToolDecl(decl);
|
|
170
|
+
if (!norm) {
|
|
171
|
+
rejected += 1;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (seen.has(norm.name))
|
|
175
|
+
continue; // a server repeating a name adds nothing
|
|
176
|
+
if (tools.length >= maxTools) {
|
|
177
|
+
truncated = true;
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
seen.add(norm.name);
|
|
181
|
+
tools.push(norm);
|
|
182
|
+
}
|
|
183
|
+
cursor = parsed.data.nextCursor;
|
|
184
|
+
if (!cursor || truncated)
|
|
185
|
+
break;
|
|
186
|
+
if (page === maxPages - 1)
|
|
187
|
+
truncated = true;
|
|
188
|
+
}
|
|
189
|
+
return { tools, rejected, truncated };
|
|
190
|
+
}
|
|
191
|
+
async callTool(name, args, opts = {}) {
|
|
192
|
+
const raw = await this.request('tools/call', { name, arguments: args ?? {} }, opts);
|
|
193
|
+
return flattenCallResult(raw ?? {});
|
|
194
|
+
}
|
|
195
|
+
async ping() {
|
|
196
|
+
const started = this.clock.now();
|
|
197
|
+
try {
|
|
198
|
+
await this.request('ping', {}, { timeoutMs: Math.min(this.opts.timeoutMs ?? 30_000, 5_000) });
|
|
199
|
+
return { ok: true, latencyMs: this.clock.now() - started };
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return { ok: false, latencyMs: this.clock.now() - started };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
async close() {
|
|
206
|
+
for (const [, p] of this.pending) {
|
|
207
|
+
clearTimeout(p.timer);
|
|
208
|
+
p.detach?.();
|
|
209
|
+
p.reject(new AIError(`MCP '${this.opts.serverId}' closed`, { category: 'NETWORK', retryable: true }));
|
|
210
|
+
}
|
|
211
|
+
this.pending.clear();
|
|
212
|
+
await this.transport.close();
|
|
213
|
+
}
|
|
214
|
+
diagnostics() {
|
|
215
|
+
return [...this.protocolErrors.slice(-5), ...this.transport.diagnostics()];
|
|
216
|
+
}
|
|
217
|
+
/** Whether the transport says our session is gone and only a fresh handshake can recover. */
|
|
218
|
+
sessionExpired() {
|
|
219
|
+
return this.transport.sessionExpired === true;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP server lifecycle (Phase 3.2) — connect, discover, cache, enable/disable/remove, lazy reconnect.
|
|
3
|
+
*
|
|
4
|
+
* State machine: `configured` → `connecting` → { `connected` | `auth_failed` | `unreachable` }; a failed
|
|
5
|
+
* call marks `degraded` (the next use retries with capped backoff); the operator can `disabled` any
|
|
6
|
+
* server. There is NO background daemon or keepalive — reconnection is lazy, on next use, matching this
|
|
7
|
+
* codebase's no-scheduler style.
|
|
8
|
+
*
|
|
9
|
+
* SECURITY:
|
|
10
|
+
* - Credentials are resolved from an env-var NAME through `Credential` (which `registerSecret`s the
|
|
11
|
+
* value so every egress is scrubbed) and are handed straight to the transport. The value is never
|
|
12
|
+
* stored on the manager, never in `McpServerStatus`, and never persisted.
|
|
13
|
+
* - Every persisted file is re-validated with strict zod ON READ: a tampered or corrupt store is
|
|
14
|
+
* discarded, not trusted (a checksum only proves integrity, not provenance).
|
|
15
|
+
* - Approval/permission decisions use LIVE handshake annotations only; the tool cache exists to make
|
|
16
|
+
* the catalog available offline and never to lower a gate.
|
|
17
|
+
*/
|
|
18
|
+
import type { Area } from '../store/area.js';
|
|
19
|
+
import type { Clock } from '../util/clock.js';
|
|
20
|
+
import type { FetchLike } from '../providers/httpClient.js';
|
|
21
|
+
import { McpClient } from './client.js';
|
|
22
|
+
import type { McpTransport } from './transport.js';
|
|
23
|
+
import type { McpCallResult, NormalizedTool } from './protocol.js';
|
|
24
|
+
export type McpTransportKind = 'stdio' | 'http';
|
|
25
|
+
export interface McpServerConfig {
|
|
26
|
+
transport: McpTransportKind;
|
|
27
|
+
url?: string;
|
|
28
|
+
command?: string;
|
|
29
|
+
args?: string[];
|
|
30
|
+
/** Env-var NAME (never a value — the config schema rejects key-like strings). */
|
|
31
|
+
tokenEnv?: string;
|
|
32
|
+
enabled?: boolean;
|
|
33
|
+
timeoutMs?: number;
|
|
34
|
+
/** Reserved: per-server aliasing of this server's tools onto canonical capability ids. NOT consumed —
|
|
35
|
+
* use `runtime.capabilities.aliases.<curatedId>: mcp:<server>.<tool>`, which already claims a
|
|
36
|
+
* curated id for any provider, MCP included. */
|
|
37
|
+
capabilities?: Record<string, string[]>;
|
|
38
|
+
}
|
|
39
|
+
export type McpServerState = 'configured' | 'connecting' | 'connected' | 'degraded' | 'auth_failed' | 'unreachable' | 'disabled';
|
|
40
|
+
export interface McpServerStatus {
|
|
41
|
+
id: string;
|
|
42
|
+
transport: McpTransportKind;
|
|
43
|
+
/** `config` = declared in config.yaml; `dynamic` = added via `mcp add` (persisted in the store). */
|
|
44
|
+
origin: 'config' | 'dynamic';
|
|
45
|
+
state: McpServerState;
|
|
46
|
+
enabled: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Where it points, in a form that CANNOT carry a credential: an http server's ORIGIN (no path, no
|
|
49
|
+
* query — a URL query can hold a token) or a stdio server's bare command (no args — args can hold
|
|
50
|
+
* paths the operator considers sensitive).
|
|
51
|
+
*/
|
|
52
|
+
endpoint?: string;
|
|
53
|
+
/** The env-var NAME and whether it currently resolves — NEVER the value. */
|
|
54
|
+
tokenEnv?: string;
|
|
55
|
+
tokenSet: boolean;
|
|
56
|
+
toolCount: number;
|
|
57
|
+
rejectedTools: number;
|
|
58
|
+
truncated: boolean;
|
|
59
|
+
protocolVersion?: string;
|
|
60
|
+
serverName?: string;
|
|
61
|
+
/** Redacted, bounded reason for the last failure. */
|
|
62
|
+
lastError?: string;
|
|
63
|
+
lastCheckedAt?: number;
|
|
64
|
+
}
|
|
65
|
+
/** A server id: the same shape the config schema enforces, so both origins are equally constrained. */
|
|
66
|
+
export declare const MCP_SERVER_ID_RE: RegExp;
|
|
67
|
+
export interface McpManagerOptions {
|
|
68
|
+
/** Servers declared in config (`mcp.servers`). */
|
|
69
|
+
servers?: Record<string, McpServerConfig>;
|
|
70
|
+
/** The store area for dynamic servers, enable-state, and the tool cache. Omit for stateless mode. */
|
|
71
|
+
store?: Area;
|
|
72
|
+
env?: NodeJS.ProcessEnv;
|
|
73
|
+
fetchImpl?: FetchLike;
|
|
74
|
+
clock?: Clock;
|
|
75
|
+
/** Per-server connect budget. */
|
|
76
|
+
connectTimeoutMs?: number;
|
|
77
|
+
/** Test seam: build a transport instead of spawning/fetching. */
|
|
78
|
+
transportFactory?: (id: string, cfg: McpServerConfig) => McpTransport;
|
|
79
|
+
/** Called after a successful discovery so the host can register tools + capabilities. */
|
|
80
|
+
onToolsDiscovered?: (serverId: string, tools: NormalizedTool[]) => void;
|
|
81
|
+
/** Called when a server is removed/disabled so the host can deregister its tools. */
|
|
82
|
+
onServerRemoved?: (serverId: string) => void;
|
|
83
|
+
}
|
|
84
|
+
export declare class McpManager {
|
|
85
|
+
private readonly opts;
|
|
86
|
+
private readonly entries;
|
|
87
|
+
private readonly env;
|
|
88
|
+
private readonly clock;
|
|
89
|
+
private readonly warnings;
|
|
90
|
+
/** Explicit operator enable/disable decisions — the only thing `state` persists (see writeState). */
|
|
91
|
+
private readonly overrides;
|
|
92
|
+
/** In-flight connect per server, so concurrent lazy callers share one handshake (never two children). */
|
|
93
|
+
private readonly connecting;
|
|
94
|
+
constructor(opts?: McpManagerOptions);
|
|
95
|
+
warningsList(): string[];
|
|
96
|
+
private readDynamic;
|
|
97
|
+
private readState;
|
|
98
|
+
/**
|
|
99
|
+
* Read a cached catalog. Every entry goes back through `normalizeToolDecl` — the SAME discovery gate a
|
|
100
|
+
* live declaration passes — because the file is untrusted input like any other: shape validation alone
|
|
101
|
+
* would let a tampered cache reintroduce an unsafe name or unclamped prompt text. Cached annotations
|
|
102
|
+
* are then forced to their fail-closed values: a stale `readOnlyHint` must not lower the permission
|
|
103
|
+
* gate, and a stale `destructiveHint: false` must not REMOVE confirmation. Only a live handshake can
|
|
104
|
+
* relax either.
|
|
105
|
+
*/
|
|
106
|
+
private readCache;
|
|
107
|
+
private writeDynamic;
|
|
108
|
+
/**
|
|
109
|
+
* Persist only servers the operator has EXPLICITLY toggled. Snapshotting every entry would turn an
|
|
110
|
+
* unrelated `mcp add` into a stored `enabled: true` for every server — which then wins over a later
|
|
111
|
+
* `enabled: false` added to the config file, silently re-enabling something the operator turned off.
|
|
112
|
+
*/
|
|
113
|
+
private writeState;
|
|
114
|
+
private buildTransport;
|
|
115
|
+
/** Connect + handshake + discover one server. Never throws — the state carries the outcome. */
|
|
116
|
+
private connect;
|
|
117
|
+
/** Connect every enabled server in parallel. Resilient: a failure is recorded, never thrown. */
|
|
118
|
+
connectAll(): Promise<McpServerStatus[]>;
|
|
119
|
+
/** Lazily (re)connect on use, honoring a capped backoff after repeated failures. */
|
|
120
|
+
ensureConnected(id: string): Promise<McpClient>;
|
|
121
|
+
/** Whether a server can be called right now (the adapter's honest availability check). */
|
|
122
|
+
usable(id: string): boolean;
|
|
123
|
+
call(id: string, tool: string, args: unknown, opts?: {
|
|
124
|
+
signal?: AbortSignal;
|
|
125
|
+
}): Promise<McpCallResult>;
|
|
126
|
+
/** The LIVE annotations for a tool (approval/permission input — never the cache). */
|
|
127
|
+
liveTool(id: string, tool: string): NormalizedTool | undefined;
|
|
128
|
+
tools(id: string): NormalizedTool[];
|
|
129
|
+
/** Add a dynamic server (persisted to the STORE — the runtime never edits config.yaml). */
|
|
130
|
+
addServer(id: string, cfg: McpServerConfig): Promise<McpServerStatus>;
|
|
131
|
+
/** Remove a DYNAMIC server. Config-declared servers must be removed from config.yaml by the operator. */
|
|
132
|
+
removeServer(id: string): {
|
|
133
|
+
ok: boolean;
|
|
134
|
+
reason?: string;
|
|
135
|
+
};
|
|
136
|
+
setEnabled(id: string, enabled: boolean): {
|
|
137
|
+
ok: boolean;
|
|
138
|
+
reason?: string;
|
|
139
|
+
};
|
|
140
|
+
/** Force an active check: connect + handshake + list + ping. */
|
|
141
|
+
test(id: string): Promise<McpServerStatus & {
|
|
142
|
+
pingMs?: number;
|
|
143
|
+
}>;
|
|
144
|
+
private statusOf;
|
|
145
|
+
list(): McpServerStatus[];
|
|
146
|
+
status(id: string): McpServerStatus | undefined;
|
|
147
|
+
diagnostics(id: string): string[];
|
|
148
|
+
ids(): string[];
|
|
149
|
+
/** Close every client (stdio children included). Idempotent; safe to call on exit. */
|
|
150
|
+
close(): Promise<void>;
|
|
151
|
+
}
|