envpkt 0.14.1 → 0.16.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/README.md +27 -1
- package/dist/cli.js +398 -99
- package/dist/index.d.ts +69 -5
- package/dist/index.js +221 -54
- package/package.json +10 -10
- package/schemas/envpkt.schema.json +29 -0
package/dist/index.d.ts
CHANGED
|
@@ -31,6 +31,12 @@ declare const AgentIdentitySchema: import("@sinclair/typebox").TObject<{
|
|
|
31
31
|
recipient: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
32
32
|
secrets: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
33
33
|
}>;
|
|
34
|
+
declare const McpHeaderSchema: import("@sinclair/typebox").TObject<{
|
|
35
|
+
server: import("@sinclair/typebox").TString;
|
|
36
|
+
header: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
37
|
+
scheme: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
38
|
+
}>;
|
|
39
|
+
type McpHeader = Static<typeof McpHeaderSchema>;
|
|
34
40
|
declare const SecretMetaSchema: import("@sinclair/typebox").TObject<{
|
|
35
41
|
service: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
36
42
|
expires: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
@@ -50,6 +56,11 @@ declare const SecretMetaSchema: import("@sinclair/typebox").TObject<{
|
|
|
50
56
|
external: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
|
|
51
57
|
tags: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TString>>;
|
|
52
58
|
namespace: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
59
|
+
mcp: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
|
|
60
|
+
server: import("@sinclair/typebox").TString;
|
|
61
|
+
header: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
62
|
+
scheme: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
63
|
+
}>>>;
|
|
53
64
|
}>;
|
|
54
65
|
type SecretMeta = Static<typeof SecretMetaSchema>;
|
|
55
66
|
declare const LifecycleConfigSchema: import("@sinclair/typebox").TObject<{
|
|
@@ -113,6 +124,11 @@ declare const EnvpktConfigSchema: import("@sinclair/typebox").TObject<{
|
|
|
113
124
|
external: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
|
|
114
125
|
tags: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TString>>;
|
|
115
126
|
namespace: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
127
|
+
mcp: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
|
|
128
|
+
server: import("@sinclair/typebox").TString;
|
|
129
|
+
header: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
130
|
+
scheme: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
131
|
+
}>>>;
|
|
116
132
|
}>>>;
|
|
117
133
|
env: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TObject<{
|
|
118
134
|
value: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
@@ -299,6 +315,35 @@ type AliasError = {
|
|
|
299
315
|
readonly kind: "secret" | "env";
|
|
300
316
|
readonly field: string;
|
|
301
317
|
};
|
|
318
|
+
/** Structural problems in `mcp` bindings — caught by validation, independent of secret values. */
|
|
319
|
+
type McpConfigError = {
|
|
320
|
+
readonly _tag: "McpHeaderInvalid";
|
|
321
|
+
readonly key: string;
|
|
322
|
+
readonly server: string;
|
|
323
|
+
readonly header: string;
|
|
324
|
+
} | {
|
|
325
|
+
readonly _tag: "McpSchemeInvalid";
|
|
326
|
+
readonly key: string;
|
|
327
|
+
readonly server: string;
|
|
328
|
+
readonly scheme: string;
|
|
329
|
+
} | {
|
|
330
|
+
readonly _tag: "McpHeaderDuplicate";
|
|
331
|
+
readonly server: string;
|
|
332
|
+
readonly header: string;
|
|
333
|
+
readonly keys: ReadonlyArray<string>;
|
|
334
|
+
};
|
|
335
|
+
type McpHeadersError = McpConfigError | {
|
|
336
|
+
readonly _tag: "McpServerUnknown";
|
|
337
|
+
readonly server: string;
|
|
338
|
+
} | {
|
|
339
|
+
readonly _tag: "McpValueUnresolved";
|
|
340
|
+
readonly server: string;
|
|
341
|
+
readonly keys: ReadonlyArray<string>;
|
|
342
|
+
} | {
|
|
343
|
+
readonly _tag: "McpValueInvalid";
|
|
344
|
+
readonly server: string;
|
|
345
|
+
readonly keys: ReadonlyArray<string>;
|
|
346
|
+
};
|
|
302
347
|
type BootOptions = {
|
|
303
348
|
readonly configPath?: string;
|
|
304
349
|
readonly profile?: string;
|
|
@@ -447,6 +492,19 @@ declare const isEnvAlias: (meta: {
|
|
|
447
492
|
/** Format an alias error into a human-readable message */
|
|
448
493
|
declare const formatAliasError: (error: AliasError) => string;
|
|
449
494
|
//#endregion
|
|
495
|
+
//#region src/core/mcp-headers.d.ts
|
|
496
|
+
/** Check every `mcp` binding in a config. Used by `envpkt validate` and the write gate. */
|
|
497
|
+
declare const validateMcpHeaders: (config: EnvpktConfig) => Either<McpConfigError, void>;
|
|
498
|
+
/**
|
|
499
|
+
* Build the HTTP header object for one MCP server: every secret whose `mcp` list names
|
|
500
|
+
* `server`, merged into one object. `values` is keyed by logical secret name (as in
|
|
501
|
+
* `BootResult.secrets`), so namespaces and aliases are already resolved by the caller.
|
|
502
|
+
* Only this server's bindings are checked, so a problem on another server never blocks it.
|
|
503
|
+
*/
|
|
504
|
+
declare const buildMcpHeaders: (config: EnvpktConfig, values: Readonly<Record<string, string>>, server: string) => Either<McpHeadersError, Record<string, string>>;
|
|
505
|
+
/** Human-readable one-liner for any McpHeadersError tag. Never includes a secret value. */
|
|
506
|
+
declare const formatMcpHeadersError: (error: McpHeadersError) => string;
|
|
507
|
+
//#endregion
|
|
450
508
|
//#region src/core/format.d.ts
|
|
451
509
|
type SecretDisplay = "encrypted" | "plaintext";
|
|
452
510
|
type FormatPacketOptions = {
|
|
@@ -622,13 +680,14 @@ declare const diffConfigs: (a: EnvpktConfig, b: EnvpktConfig) => ConfigDiff;
|
|
|
622
680
|
//#region src/core/copy.d.ts
|
|
623
681
|
/**
|
|
624
682
|
* The SecretMeta to write into the destination on copy.
|
|
625
|
-
* - `created`
|
|
683
|
+
* - `created` comes from the caller, never the source: today for an entry new to the
|
|
684
|
+
* destination, or the destination's own `created` when the copy overwrites it.
|
|
626
685
|
* - `last_rotated_at` is dropped — it's the source's rotation history, not the copy's.
|
|
627
686
|
* - `encryptedValue` re-derives the ciphertext: `Some(cipher)` sets the resealed value,
|
|
628
687
|
* `None` strips it entirely (a metadata-only copy of a secret with no sealed value).
|
|
629
688
|
*/
|
|
630
689
|
declare const copyableSecretMeta: (meta: SecretMeta, opts: {
|
|
631
|
-
readonly
|
|
690
|
+
readonly created: string;
|
|
632
691
|
readonly encryptedValue: Option<string>;
|
|
633
692
|
}) => SecretMeta;
|
|
634
693
|
/** Serialize a `[secret.<name>]` block from its metadata, round-trippable by the TOML parser. */
|
|
@@ -638,10 +697,15 @@ declare const serializeEnvBlock: (name: string, meta: EnvMeta) => string;
|
|
|
638
697
|
//#endregion
|
|
639
698
|
//#region src/core/toml-edit.d.ts
|
|
640
699
|
/**
|
|
641
|
-
* Remove a TOML section (e.g. `[secret.X]`) and all its fields
|
|
642
|
-
*
|
|
700
|
+
* Remove a TOML section (e.g. `[secret.X]`) and all its fields. A comment above the next
|
|
701
|
+
* section stays. The sections on either side end up one blank line apart.
|
|
643
702
|
*/
|
|
644
703
|
declare const removeSection: (raw: string, sectionHeader: string) => Either<TomlEditError, string>;
|
|
704
|
+
/**
|
|
705
|
+
* Replace a TOML section with `block`, in place. Lines outside the section, including the
|
|
706
|
+
* blank and comment lines between it and the next header, are left as they were.
|
|
707
|
+
*/
|
|
708
|
+
declare const replaceSection: (raw: string, sectionHeader: string, block: string) => Either<TomlEditError, string>;
|
|
645
709
|
/**
|
|
646
710
|
* Rename a TOML section header (e.g. `[secret.OLD]` → `[secret.NEW]`).
|
|
647
711
|
* Errors if old doesn't exist or new already exists.
|
|
@@ -721,4 +785,4 @@ type ToolDef = {
|
|
|
721
785
|
declare const toolDefinitions: readonly ToolDef[];
|
|
722
786
|
declare const callTool: (name: string, args: Record<string, unknown>) => CallToolResult;
|
|
723
787
|
//#endregion
|
|
724
|
-
export { type AgentIdentity, AgentIdentitySchema, type AliasError, type AliasTable, type AuditResult, type BootError, type BootOptions, type BootResult, type CallbackConfig, CallbackConfigSchema, type CatalogError, type ChangedEntry, type CheckResult, type ConfidenceLevel, type ConfigDiff, type ConfigError, type ConfigSource, ConsumerType, type CredentialPattern, type DirectLogger, type DirectTestLoggerHandle, type DotenvEntry, type DriftEntry, type DriftStatus, type EnvAuditResult, type EnvDriftEntry, type EnvDriftStatus, type EnvMeta, EnvMetaSchema, EnvpktBootError, type EnvpktConfig, EnvpktConfigSchema, type FieldChange, type FleetAgent, type FleetHealth, type FnoxConfig, type FnoxError, type FnoxSecret, type FormatDotenvOptions, type FormatPacketOptions, type HealthStatus, type Identity, type IdentityError, IdentitySchema, type KeygenError, type KeygenResult, type LifecycleConfig, LifecycleConfigSchema, type LogEntry, type LogLevel, type LogMetadata, type MatchResult, type ResolveOptions, type ResolveResult, type ResolvedPath, type ScanOptions, type ScanResult, type SealError, type SecretDisplay, type SecretHealth, type SecretMeta, SecretMetaSchema, type SecretStatus, type SectionDiff, type TomlEditError, type ToolsConfig, ToolsConfigSchema, ageAvailable, ageDecrypt, ageEncrypt, appendSection, boot, bootSafe, callTool, compareFnoxAndEnvpkt, computeAudit, computeEnvAudit, copyableSecretMeta, createDirectConsoleLogger, createDirectTestLogger, createServer, deriveServiceFromName, detectFnox, diffConfigs, directSilentLogger, discoverConfig, envCheck, envScan, extractFnoxKeys, findConfigPath, fnoxAvailable, fnoxExport, fnoxGet, formatAliasError, formatDotenv, formatPacket, generateKeypair, generateTomlFromScan, isEnvAlias, isSecretAlias, loadCatalog, loadConfig, loadConfigFromCwd, maskValue, matchEnvVar, matchValueShape, parseToml, quoteDotenvValue, readConfigFile, readFnoxConfig, readResource, removeSection, renameSection, resolveConfig, resolveConfigPath, resolveInlineKey, resolveKeyPath, resolveSecrets, resolveValues, resourceDefinitions, scanEnv, scanFleet, sealSecrets, serializeEnvBlock, serializeSecretBlock, startServer, toolDefinitions, unsealSecrets, unwrapAgentKey, updateConfigIdentity, updateSectionFields, validateAliases, validateConfig };
|
|
788
|
+
export { type AgentIdentity, AgentIdentitySchema, type AliasError, type AliasTable, type AuditResult, type BootError, type BootOptions, type BootResult, type CallbackConfig, CallbackConfigSchema, type CatalogError, type ChangedEntry, type CheckResult, type ConfidenceLevel, type ConfigDiff, type ConfigError, type ConfigSource, ConsumerType, type CredentialPattern, type DirectLogger, type DirectTestLoggerHandle, type DotenvEntry, type DriftEntry, type DriftStatus, type EnvAuditResult, type EnvDriftEntry, type EnvDriftStatus, type EnvMeta, EnvMetaSchema, EnvpktBootError, type EnvpktConfig, EnvpktConfigSchema, type FieldChange, type FleetAgent, type FleetHealth, type FnoxConfig, type FnoxError, type FnoxSecret, type FormatDotenvOptions, type FormatPacketOptions, type HealthStatus, type Identity, type IdentityError, IdentitySchema, type KeygenError, type KeygenResult, type LifecycleConfig, LifecycleConfigSchema, type LogEntry, type LogLevel, type LogMetadata, type MatchResult, type McpConfigError, type McpHeader, McpHeaderSchema, type McpHeadersError, type ResolveOptions, type ResolveResult, type ResolvedPath, type ScanOptions, type ScanResult, type SealError, type SecretDisplay, type SecretHealth, type SecretMeta, SecretMetaSchema, type SecretStatus, type SectionDiff, type TomlEditError, type ToolsConfig, ToolsConfigSchema, ageAvailable, ageDecrypt, ageEncrypt, appendSection, boot, bootSafe, buildMcpHeaders, callTool, compareFnoxAndEnvpkt, computeAudit, computeEnvAudit, copyableSecretMeta, createDirectConsoleLogger, createDirectTestLogger, createServer, deriveServiceFromName, detectFnox, diffConfigs, directSilentLogger, discoverConfig, envCheck, envScan, extractFnoxKeys, findConfigPath, fnoxAvailable, fnoxExport, fnoxGet, formatAliasError, formatDotenv, formatMcpHeadersError, formatPacket, generateKeypair, generateTomlFromScan, isEnvAlias, isSecretAlias, loadCatalog, loadConfig, loadConfigFromCwd, maskValue, matchEnvVar, matchValueShape, parseToml, quoteDotenvValue, readConfigFile, readFnoxConfig, readResource, removeSection, renameSection, replaceSection, resolveConfig, resolveConfigPath, resolveInlineKey, resolveKeyPath, resolveSecrets, resolveValues, resourceDefinitions, scanEnv, scanFleet, sealSecrets, serializeEnvBlock, serializeSecretBlock, startServer, toolDefinitions, unsealSecrets, unwrapAgentKey, updateConfigIdentity, updateSectionFields, validateAliases, validateConfig, validateMcpHeaders };
|
package/dist/index.js
CHANGED
|
@@ -46,6 +46,21 @@ const NamespaceSchema = Type.Object({
|
|
|
46
46
|
description: "Separator between prefix and key. Default '__' (shell-safe). Note: '.' and ':' are not valid in shell identifiers."
|
|
47
47
|
}))
|
|
48
48
|
}, { description: "Optional namespace/package prefix for injected environment variable names" });
|
|
49
|
+
const McpHeaderSchema = Type.Object({
|
|
50
|
+
server: Type.String({
|
|
51
|
+
minLength: 1,
|
|
52
|
+
description: "MCP server name, as keyed in .mcp.json"
|
|
53
|
+
}),
|
|
54
|
+
header: Type.Optional(Type.String({
|
|
55
|
+
default: "Authorization",
|
|
56
|
+
minLength: 1,
|
|
57
|
+
description: "HTTP header name to send the secret in"
|
|
58
|
+
})),
|
|
59
|
+
scheme: Type.Optional(Type.String({
|
|
60
|
+
default: "Bearer",
|
|
61
|
+
description: "Prefix placed before the value (e.g. 'Bearer'). Empty string sends the raw value."
|
|
62
|
+
}))
|
|
63
|
+
}, { description: "Send this secret as an HTTP header to an MCP server" });
|
|
49
64
|
const SecretMetaSchema = Type.Object({
|
|
50
65
|
service: Type.Optional(Type.String({ description: "Service or system this secret authenticates to" })),
|
|
51
66
|
expires: Type.Optional(Type.String({
|
|
@@ -76,7 +91,8 @@ const SecretMetaSchema = Type.Object({
|
|
|
76
91
|
required: Type.Optional(Type.Boolean({ description: "Whether this secret is required for operation" })),
|
|
77
92
|
external: Type.Optional(Type.Boolean({ description: "Value is managed outside envpkt (e.g. a Cloudflare/Vault secret). The entry registers the name for inventory and dev/prod parity only: seal never touches it, and audit reports it as 'external' rather than 'missing'." })),
|
|
78
93
|
tags: Type.Optional(Type.Record(Type.String(), Type.String(), { description: "Key-value tags for grouping and filtering" })),
|
|
79
|
-
namespace: Type.Optional(Type.String({ description: "Override the file-level namespace for this entry's injected name. Empty string opts out of any prefix." }))
|
|
94
|
+
namespace: Type.Optional(Type.String({ description: "Override the file-level namespace for this entry's injected name. Empty string opts out of any prefix." })),
|
|
95
|
+
mcp: Type.Optional(Type.Array(McpHeaderSchema, { description: "MCP servers this secret authenticates to. Read by `envpkt headers` (Claude Code headersHelper)." }))
|
|
80
96
|
}, { description: "Metadata about a single secret" });
|
|
81
97
|
const LifecycleConfigSchema = Type.Object({
|
|
82
98
|
stale_warning_days: Type.Optional(Type.Number({
|
|
@@ -209,10 +225,13 @@ const discoverConfig = (cwd) => {
|
|
|
209
225
|
template,
|
|
210
226
|
expanded: expandPath(template)
|
|
211
227
|
})).filter(({ expanded }) => expanded !== "" && !expanded.startsWith("/.envpkt")).map(({ expanded }) => expandGlobPath(expanded)).find((matches) => matches.length > 0);
|
|
212
|
-
if (customMatch)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
228
|
+
if (customMatch) {
|
|
229
|
+
const found = {
|
|
230
|
+
path: customMatch[0],
|
|
231
|
+
source: "search"
|
|
232
|
+
};
|
|
233
|
+
return Option(found);
|
|
234
|
+
}
|
|
216
235
|
const platformMatch = buildSearchPaths().find((p) => Fs.existsSync(p));
|
|
217
236
|
if (platformMatch) return Option({
|
|
218
237
|
path: platformMatch,
|
|
@@ -254,9 +273,10 @@ const parseToml = (raw) => Try(() => parse(raw)).fold((err) => Left({
|
|
|
254
273
|
/** Validate parsed data against the TypeBox schema */
|
|
255
274
|
const validateConfig = (data) => {
|
|
256
275
|
if (compiledSchema.Check(data)) return Right(data);
|
|
276
|
+
const errors = List([...compiledSchema.Errors(data)].map((e) => `${e.path}: ${e.message}`));
|
|
257
277
|
return Left({
|
|
258
278
|
_tag: "ValidationError",
|
|
259
|
-
errors
|
|
279
|
+
errors
|
|
260
280
|
});
|
|
261
281
|
};
|
|
262
282
|
/** Load and validate an envpkt.toml from a file path */
|
|
@@ -505,6 +525,104 @@ const formatAliasError = (error) => {
|
|
|
505
525
|
}
|
|
506
526
|
};
|
|
507
527
|
//#endregion
|
|
528
|
+
//#region src/core/mcp-headers.ts
|
|
529
|
+
const DEFAULT_HEADER = "Authorization";
|
|
530
|
+
const DEFAULT_SCHEME = "Bearer";
|
|
531
|
+
/** RFC 9110 `token`: the legal characters of a header name (and of an auth scheme). */
|
|
532
|
+
const TOKEN_RE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
533
|
+
/** Characters that would split or corrupt an HTTP header value. */
|
|
534
|
+
const UNSAFE_VALUE_RE = /[\r\n\0]/;
|
|
535
|
+
/** Flatten every `[secret.*].mcp` entry into one binding per (secret, server), with defaults applied. */
|
|
536
|
+
const collectBindings = (config) => Object.entries(config.secret ?? {}).flatMap(([key, meta]) => (meta.mcp ?? []).map((m) => ({
|
|
537
|
+
key,
|
|
538
|
+
server: m.server,
|
|
539
|
+
header: m.header ?? DEFAULT_HEADER,
|
|
540
|
+
scheme: m.scheme ?? DEFAULT_SCHEME
|
|
541
|
+
})));
|
|
542
|
+
/**
|
|
543
|
+
* Check bindings for structural problems: header names and schemes that are not HTTP
|
|
544
|
+
* tokens, and two bindings claiming the same (server, header) pair. Header names compare
|
|
545
|
+
* case-insensitively, as HTTP does.
|
|
546
|
+
*/
|
|
547
|
+
const validateBindings = (bindings) => {
|
|
548
|
+
const badHeader = bindings.find((b) => !TOKEN_RE.test(b.header));
|
|
549
|
+
if (badHeader) return Left({
|
|
550
|
+
_tag: "McpHeaderInvalid",
|
|
551
|
+
key: badHeader.key,
|
|
552
|
+
server: badHeader.server,
|
|
553
|
+
header: badHeader.header
|
|
554
|
+
});
|
|
555
|
+
const badScheme = bindings.find((b) => b.scheme !== "" && !TOKEN_RE.test(b.scheme));
|
|
556
|
+
if (badScheme) return Left({
|
|
557
|
+
_tag: "McpSchemeInvalid",
|
|
558
|
+
key: badScheme.key,
|
|
559
|
+
server: badScheme.server,
|
|
560
|
+
scheme: badScheme.scheme
|
|
561
|
+
});
|
|
562
|
+
const claims = bindings.reduce((acc, b) => {
|
|
563
|
+
const id = `${b.server}\u0000${b.header.toLowerCase()}`;
|
|
564
|
+
return {
|
|
565
|
+
...acc,
|
|
566
|
+
[id]: [...acc[id] ?? [], b]
|
|
567
|
+
};
|
|
568
|
+
}, {});
|
|
569
|
+
const clash = Object.values(claims).find((group) => group.length > 1);
|
|
570
|
+
if (clash) {
|
|
571
|
+
const first = clash[0];
|
|
572
|
+
return Left({
|
|
573
|
+
_tag: "McpHeaderDuplicate",
|
|
574
|
+
server: first.server,
|
|
575
|
+
header: first.header,
|
|
576
|
+
keys: Set$1(clash.map((b) => b.key)).toArray()
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
return Right(void 0);
|
|
580
|
+
};
|
|
581
|
+
/** Check every `mcp` binding in a config. Used by `envpkt validate` and the write gate. */
|
|
582
|
+
const validateMcpHeaders = (config) => validateBindings(collectBindings(config));
|
|
583
|
+
/**
|
|
584
|
+
* Build the HTTP header object for one MCP server: every secret whose `mcp` list names
|
|
585
|
+
* `server`, merged into one object. `values` is keyed by logical secret name (as in
|
|
586
|
+
* `BootResult.secrets`), so namespaces and aliases are already resolved by the caller.
|
|
587
|
+
* Only this server's bindings are checked, so a problem on another server never blocks it.
|
|
588
|
+
*/
|
|
589
|
+
const buildMcpHeaders = (config, values, server) => {
|
|
590
|
+
const matches = collectBindings(config).filter((b) => b.server === server);
|
|
591
|
+
if (matches.length === 0) return Left({
|
|
592
|
+
_tag: "McpServerUnknown",
|
|
593
|
+
server
|
|
594
|
+
});
|
|
595
|
+
return validateBindings(matches).flatMap(() => {
|
|
596
|
+
const unresolved = matches.filter((b) => !values[b.key]).map((b) => b.key);
|
|
597
|
+
if (unresolved.length > 0) return Left({
|
|
598
|
+
_tag: "McpValueUnresolved",
|
|
599
|
+
server,
|
|
600
|
+
keys: unresolved
|
|
601
|
+
});
|
|
602
|
+
const unsafe = matches.filter((b) => UNSAFE_VALUE_RE.test(values[b.key])).map((b) => b.key);
|
|
603
|
+
if (unsafe.length > 0) return Left({
|
|
604
|
+
_tag: "McpValueInvalid",
|
|
605
|
+
server,
|
|
606
|
+
keys: unsafe
|
|
607
|
+
});
|
|
608
|
+
return Right(Object.fromEntries(matches.map((b) => {
|
|
609
|
+
const value = values[b.key];
|
|
610
|
+
return [b.header, b.scheme === "" ? value : `${b.scheme} ${value}`];
|
|
611
|
+
})));
|
|
612
|
+
});
|
|
613
|
+
};
|
|
614
|
+
/** Human-readable one-liner for any McpHeadersError tag. Never includes a secret value. */
|
|
615
|
+
const formatMcpHeadersError = (error) => {
|
|
616
|
+
switch (error._tag) {
|
|
617
|
+
case "McpHeaderInvalid": return `[secret.${error.key}] mcp server "${error.server}": header "${error.header}" is not a valid HTTP header name`;
|
|
618
|
+
case "McpSchemeInvalid": return `[secret.${error.key}] mcp server "${error.server}": scheme "${error.scheme}" must be a single word or ""`;
|
|
619
|
+
case "McpHeaderDuplicate": return `MCP server "${error.server}": header "${error.header}" is claimed by more than one binding: ${error.keys.join(", ")}`;
|
|
620
|
+
case "McpServerUnknown": return `No secret declares mcp server "${error.server}"`;
|
|
621
|
+
case "McpValueUnresolved": return `MCP server "${error.server}": no value resolved for ${error.keys.join(", ")}`;
|
|
622
|
+
case "McpValueInvalid": return `MCP server "${error.server}": value of ${error.keys.join(", ")} contains a line break or NUL and cannot be sent as a header`;
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
//#endregion
|
|
508
626
|
//#region src/core/format.ts
|
|
509
627
|
const maskValue = (value) => {
|
|
510
628
|
if (value.length > 8) return `${value.slice(0, 3)}${"•".repeat(5)}${value.slice(-4)}`;
|
|
@@ -690,14 +808,15 @@ const computeAudit = (config, fnoxKeys, today, aliasTable) => {
|
|
|
690
808
|
const staleWarningDays = lifecycle.stale_warning_days ?? 90;
|
|
691
809
|
const requireExpiration = lifecycle.require_expiration ?? false;
|
|
692
810
|
const requireService = lifecycle.require_service ?? false;
|
|
693
|
-
const keys = fnoxKeys ??
|
|
811
|
+
const keys = fnoxKeys ?? Set$1.empty();
|
|
694
812
|
const secretEntries = config.secret ?? {};
|
|
695
813
|
const nonAliasEntries = Object.entries(secretEntries).filter(([, meta]) => meta.from_key === void 0);
|
|
696
814
|
const aliasEntries = Object.entries(secretEntries).filter(([, meta]) => meta.from_key !== void 0);
|
|
697
815
|
const nonAliasHealth = nonAliasEntries.map(([key, meta]) => classifySecret(key, meta, keys, staleWarningDays, requireExpiration, requireService, now));
|
|
698
816
|
const healthByKey = Map$1(nonAliasHealth.map((h) => [h.key, h]));
|
|
699
817
|
const parseTargetKey = (from_key) => {
|
|
700
|
-
|
|
818
|
+
const match = from_key.match(/^secret\.(.+)$/);
|
|
819
|
+
return Option(match?.[1]);
|
|
701
820
|
};
|
|
702
821
|
const aliasHealth = aliasEntries.map(([key, meta]) => {
|
|
703
822
|
const tableEntry = aliasTable?.entries.get(`secret.${key}`);
|
|
@@ -751,8 +870,10 @@ const computeEnvAudit = (config, env = process.env) => {
|
|
|
751
870
|
const envWire = (key) => namer(key, envEntries[key]?.namespace);
|
|
752
871
|
const resolveEffectiveDefault = (entry) => {
|
|
753
872
|
return Do(function* () {
|
|
754
|
-
const
|
|
755
|
-
|
|
873
|
+
const fk = yield* $(Option(entry.from_key));
|
|
874
|
+
const targetKey = yield* $(Option(fk.match(/^env\.(.+)$/)?.[1]));
|
|
875
|
+
const targetEntry = yield* $(Option(envEntries[targetKey]));
|
|
876
|
+
return yield* $(Option(targetEntry.value));
|
|
756
877
|
}).orElse(entry.value ?? "");
|
|
757
878
|
};
|
|
758
879
|
const entries = Object.entries(envEntries).map(([key, entry]) => {
|
|
@@ -1398,7 +1519,8 @@ const VALUE_SHAPE_PATTERNS = [
|
|
|
1398
1519
|
];
|
|
1399
1520
|
/** Detect service from value prefix/shape */
|
|
1400
1521
|
const matchValueShape = (value) => {
|
|
1401
|
-
|
|
1522
|
+
const match = VALUE_SHAPE_PATTERNS.find((vp) => value.startsWith(vp.prefix));
|
|
1523
|
+
return Option(match).map((vp) => ({
|
|
1402
1524
|
service: vp.service,
|
|
1403
1525
|
description: vp.description
|
|
1404
1526
|
}));
|
|
@@ -1438,7 +1560,8 @@ const matchEnvVar = (name, value) => {
|
|
|
1438
1560
|
matchedBy: `exact:${exactMatch.pattern}`
|
|
1439
1561
|
});
|
|
1440
1562
|
return matchValueShape(value).fold(() => {
|
|
1441
|
-
|
|
1563
|
+
const suffixMatch = SUFFIX_PATTERNS.find((sp) => name.endsWith(sp.suffix));
|
|
1564
|
+
return Option(suffixMatch).map((sp) => ({
|
|
1442
1565
|
envVar: name,
|
|
1443
1566
|
value,
|
|
1444
1567
|
service: Option(deriveServiceFromName(name)),
|
|
@@ -2075,7 +2198,8 @@ const bootSafe = (options) => {
|
|
|
2075
2198
|
_tag: "ReadError",
|
|
2076
2199
|
message: "unexpected"
|
|
2077
2200
|
}));
|
|
2078
|
-
const
|
|
2201
|
+
const fnoxKeys = detectFnoxKeys(configDir);
|
|
2202
|
+
const audit = computeAudit(config, fnoxKeys, void 0, aliasTable);
|
|
2079
2203
|
const sealIdentity = hasSealedValues ? resolveSealIdentity(config, configDir) : Option(void 0);
|
|
2080
2204
|
if (hasSealedValues && sealIdentity.isEmpty) return Left({
|
|
2081
2205
|
_tag: "SealKeyUnavailable",
|
|
@@ -2117,37 +2241,39 @@ const bootSafe = (options) => {
|
|
|
2117
2241
|
}
|
|
2118
2242
|
});
|
|
2119
2243
|
const remainingKeys = nonAliasMetaKeys.filter((k) => !sealedKeys.has(k));
|
|
2120
|
-
if (remainingKeys.length > 0)
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
warnings.push(`fnox export failed: ${err.message}`);
|
|
2126
|
-
skipped.push(...remainingKeys);
|
|
2127
|
-
}, (exported) => {
|
|
2128
|
-
const found = remainingKeys.filter((key) => key in exported);
|
|
2129
|
-
const notFound = remainingKeys.filter((key) => !(key in exported));
|
|
2130
|
-
found.forEach((key) => {
|
|
2131
|
-
secrets[key] = exported[key];
|
|
2132
|
-
log.debug("phase.fnox.resolved", {
|
|
2133
|
-
key,
|
|
2134
|
-
profile: opts.profile
|
|
2244
|
+
if (remainingKeys.length > 0) {
|
|
2245
|
+
if (fnoxAvailable()) fnoxExport(opts.profile, identityKey.orUndefined()).fold((err) => {
|
|
2246
|
+
log.warn("phase.fnox.export_failed", {
|
|
2247
|
+
message: err.message,
|
|
2248
|
+
skipped: remainingKeys.length
|
|
2135
2249
|
});
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2250
|
+
warnings.push(`fnox export failed: ${err.message}`);
|
|
2251
|
+
skipped.push(...remainingKeys);
|
|
2252
|
+
}, (exported) => {
|
|
2253
|
+
const found = remainingKeys.filter((key) => key in exported);
|
|
2254
|
+
const notFound = remainingKeys.filter((key) => !(key in exported));
|
|
2255
|
+
found.forEach((key) => {
|
|
2256
|
+
secrets[key] = exported[key];
|
|
2257
|
+
log.debug("phase.fnox.resolved", {
|
|
2258
|
+
key,
|
|
2259
|
+
profile: opts.profile
|
|
2260
|
+
});
|
|
2141
2261
|
});
|
|
2262
|
+
notFound.forEach((key) => {
|
|
2263
|
+
log.debug("phase.fnox.not_in_export", {
|
|
2264
|
+
key,
|
|
2265
|
+
profile: opts.profile
|
|
2266
|
+
});
|
|
2267
|
+
});
|
|
2268
|
+
injected.push(...found);
|
|
2269
|
+
skipped.push(...notFound);
|
|
2142
2270
|
});
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
else warnings.push("fnox not available — unsealed secrets could not be resolved");
|
|
2150
|
-
skipped.push(...remainingKeys);
|
|
2271
|
+
else {
|
|
2272
|
+
log.debug("phase.fnox.unavailable", { skipped: remainingKeys.length });
|
|
2273
|
+
if (!hasSealedValues) warnings.push("fnox not available — no secrets injected");
|
|
2274
|
+
else warnings.push("fnox not available — unsealed secrets could not be resolved");
|
|
2275
|
+
skipped.push(...remainingKeys);
|
|
2276
|
+
}
|
|
2151
2277
|
}
|
|
2152
2278
|
aliasSecretKeys.forEach((aliasKey) => {
|
|
2153
2279
|
const entry = aliasTable.entries.get(`secret.${aliasKey}`);
|
|
@@ -2381,7 +2507,8 @@ const tomlInlineTable = (rec) => {
|
|
|
2381
2507
|
};
|
|
2382
2508
|
/**
|
|
2383
2509
|
* The SecretMeta to write into the destination on copy.
|
|
2384
|
-
* - `created`
|
|
2510
|
+
* - `created` comes from the caller, never the source: today for an entry new to the
|
|
2511
|
+
* destination, or the destination's own `created` when the copy overwrites it.
|
|
2385
2512
|
* - `last_rotated_at` is dropped — it's the source's rotation history, not the copy's.
|
|
2386
2513
|
* - `encryptedValue` re-derives the ciphertext: `Some(cipher)` sets the resealed value,
|
|
2387
2514
|
* `None` strips it entirely (a metadata-only copy of a secret with no sealed value).
|
|
@@ -2390,10 +2517,10 @@ const copyableSecretMeta = (meta, opts) => {
|
|
|
2390
2517
|
const { last_rotated_at: _lra, encrypted_value: _ev, ...rest } = meta;
|
|
2391
2518
|
return opts.encryptedValue.fold(() => ({
|
|
2392
2519
|
...rest,
|
|
2393
|
-
created: opts.
|
|
2520
|
+
created: opts.created
|
|
2394
2521
|
}), (cipher) => ({
|
|
2395
2522
|
...rest,
|
|
2396
|
-
created: opts.
|
|
2523
|
+
created: opts.created,
|
|
2397
2524
|
encrypted_value: cipher
|
|
2398
2525
|
}));
|
|
2399
2526
|
};
|
|
@@ -2468,11 +2595,28 @@ const findSectionRange = (lines, sectionHeader) => {
|
|
|
2468
2595
|
end: List(lines.slice(start + 1)).zipWithIndex().foldLeft(initial)((state, entry) => scanSectionBoundary(state, entry[0], start + 1 + entry[1])).end
|
|
2469
2596
|
};
|
|
2470
2597
|
};
|
|
2598
|
+
/**
|
|
2599
|
+
* End (exclusive) of a section's own lines: through its last field, before the blank and
|
|
2600
|
+
* comment lines that run up to the next header. Those trailing lines separate it from its
|
|
2601
|
+
* neighbour (or are the neighbour's doc comment), so edits to this section leave them alone.
|
|
2602
|
+
*/
|
|
2603
|
+
const sectionBodyEnd = (lines, range) => {
|
|
2604
|
+
const lastContent = lines.slice(range.start, range.end).findLastIndex((l) => {
|
|
2605
|
+
const t = l.trim();
|
|
2606
|
+
return t !== "" && !t.startsWith("#");
|
|
2607
|
+
});
|
|
2608
|
+
return range.start + lastContent + 1;
|
|
2609
|
+
};
|
|
2610
|
+
const dropTrailingBlanks = (xs) => xs.slice(0, xs.findLastIndex((l) => l.trim() !== "") + 1);
|
|
2611
|
+
const dropLeadingBlanks = (xs) => {
|
|
2612
|
+
const first = xs.findIndex((l) => l.trim() !== "");
|
|
2613
|
+
return first === -1 ? [] : xs.slice(first);
|
|
2614
|
+
};
|
|
2471
2615
|
/** Check whether a section header exists in the raw TOML */
|
|
2472
2616
|
const sectionExists = (lines, sectionHeader) => lines.some((l) => l.trim() === sectionHeader);
|
|
2473
2617
|
/**
|
|
2474
|
-
* Remove a TOML section (e.g. `[secret.X]`) and all its fields
|
|
2475
|
-
*
|
|
2618
|
+
* Remove a TOML section (e.g. `[secret.X]`) and all its fields. A comment above the next
|
|
2619
|
+
* section stays. The sections on either side end up one blank line apart.
|
|
2476
2620
|
*/
|
|
2477
2621
|
const removeSection = (raw, sectionHeader) => {
|
|
2478
2622
|
const lines = raw.split("\n");
|
|
@@ -2481,11 +2625,33 @@ const removeSection = (raw, sectionHeader) => {
|
|
|
2481
2625
|
_tag: "SectionNotFound",
|
|
2482
2626
|
section: sectionHeader
|
|
2483
2627
|
});
|
|
2484
|
-
const
|
|
2485
|
-
const
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
return Either.right(
|
|
2628
|
+
const before = dropTrailingBlanks(lines.slice(0, range.start));
|
|
2629
|
+
const after = dropLeadingBlanks(lines.slice(sectionBodyEnd(lines, range)));
|
|
2630
|
+
if (after.length === 0) return Either.right([...before, ...raw.endsWith("\n") ? [""] : []].join("\n"));
|
|
2631
|
+
if (before.length === 0) return Either.right(after.join("\n"));
|
|
2632
|
+
return Either.right([
|
|
2633
|
+
...before,
|
|
2634
|
+
"",
|
|
2635
|
+
...after
|
|
2636
|
+
].join("\n"));
|
|
2637
|
+
};
|
|
2638
|
+
/**
|
|
2639
|
+
* Replace a TOML section with `block`, in place. Lines outside the section, including the
|
|
2640
|
+
* blank and comment lines between it and the next header, are left as they were.
|
|
2641
|
+
*/
|
|
2642
|
+
const replaceSection = (raw, sectionHeader, block) => {
|
|
2643
|
+
const lines = raw.split("\n");
|
|
2644
|
+
const range = findSectionRange(lines, sectionHeader);
|
|
2645
|
+
if (!range) return Either.left({
|
|
2646
|
+
_tag: "SectionNotFound",
|
|
2647
|
+
section: sectionHeader
|
|
2648
|
+
});
|
|
2649
|
+
const replaced = [
|
|
2650
|
+
...lines.slice(0, range.start),
|
|
2651
|
+
...block.trimEnd().split("\n"),
|
|
2652
|
+
...lines.slice(sectionBodyEnd(lines, range))
|
|
2653
|
+
];
|
|
2654
|
+
return Either.right(replaced.join("\n"));
|
|
2489
2655
|
};
|
|
2490
2656
|
/**
|
|
2491
2657
|
* Rename a TOML section header (e.g. `[secret.OLD]` → `[secret.NEW]`).
|
|
@@ -2584,7 +2750,7 @@ function* findEnvpktFiles(dir, maxDepth, currentDepth = 0) {
|
|
|
2584
2750
|
}
|
|
2585
2751
|
const scanFleet = (rootDir, options) => {
|
|
2586
2752
|
const maxDepth = options?.maxDepth ?? 3;
|
|
2587
|
-
const
|
|
2753
|
+
const agents = Array.from(findEnvpktFiles(rootDir, maxDepth)).flatMap((configPath) => loadConfig(configPath).fold(() => [], (config) => {
|
|
2588
2754
|
const audit = computeAudit(config);
|
|
2589
2755
|
return [{
|
|
2590
2756
|
path: configPath,
|
|
@@ -2592,7 +2758,8 @@ const scanFleet = (rootDir, options) => {
|
|
|
2592
2758
|
min_expiry_days: audit.secrets.toArray().reduce((min, s) => s.days_remaining.fold(() => min, (d) => min.fold(() => Option(d), (m) => Option(Math.min(m, d)))), Option(void 0)).orUndefined(),
|
|
2593
2759
|
audit
|
|
2594
2760
|
}];
|
|
2595
|
-
}))
|
|
2761
|
+
}));
|
|
2762
|
+
const agentList = List(agents);
|
|
2596
2763
|
const total_agents = agentList.size;
|
|
2597
2764
|
const total_secrets = agentList.toArray().reduce((acc, a) => acc + a.audit.total, 0);
|
|
2598
2765
|
const expired = agentList.toArray().reduce((acc, a) => acc + a.audit.expired, 0);
|
|
@@ -2929,4 +3096,4 @@ const startServer = async () => {
|
|
|
2929
3096
|
await server.connect(transport);
|
|
2930
3097
|
};
|
|
2931
3098
|
//#endregion
|
|
2932
|
-
export { AgentIdentitySchema, CallbackConfigSchema, ConsumerType, EnvMetaSchema, EnvpktBootError, EnvpktConfigSchema, IdentitySchema, LifecycleConfigSchema, SecretMetaSchema, ToolsConfigSchema, ageAvailable, ageDecrypt, ageEncrypt, appendSection, boot, bootSafe, callTool, compareFnoxAndEnvpkt, computeAudit, computeEnvAudit, copyableSecretMeta, createDirectConsoleLogger, createDirectTestLogger, createServer, deriveServiceFromName, detectFnox, diffConfigs, directSilentLogger, discoverConfig, envCheck, envScan, extractFnoxKeys, findConfigPath, fnoxAvailable, fnoxExport, fnoxGet, formatAliasError, formatDotenv, formatPacket, generateKeypair, generateTomlFromScan, isEnvAlias, isSecretAlias, loadCatalog, loadConfig, loadConfigFromCwd, maskValue, matchEnvVar, matchValueShape, parseToml, quoteDotenvValue, readConfigFile, readFnoxConfig, readResource, removeSection, renameSection, resolveConfig, resolveConfigPath, resolveInlineKey, resolveKeyPath, resolveSecrets, resolveValues, resourceDefinitions, scanEnv, scanFleet, sealSecrets, serializeEnvBlock, serializeSecretBlock, startServer, toolDefinitions, unsealSecrets, unwrapAgentKey, updateConfigIdentity, updateSectionFields, validateAliases, validateConfig };
|
|
3099
|
+
export { AgentIdentitySchema, CallbackConfigSchema, ConsumerType, EnvMetaSchema, EnvpktBootError, EnvpktConfigSchema, IdentitySchema, LifecycleConfigSchema, McpHeaderSchema, SecretMetaSchema, ToolsConfigSchema, ageAvailable, ageDecrypt, ageEncrypt, appendSection, boot, bootSafe, buildMcpHeaders, callTool, compareFnoxAndEnvpkt, computeAudit, computeEnvAudit, copyableSecretMeta, createDirectConsoleLogger, createDirectTestLogger, createServer, deriveServiceFromName, detectFnox, diffConfigs, directSilentLogger, discoverConfig, envCheck, envScan, extractFnoxKeys, findConfigPath, fnoxAvailable, fnoxExport, fnoxGet, formatAliasError, formatDotenv, formatMcpHeadersError, formatPacket, generateKeypair, generateTomlFromScan, isEnvAlias, isSecretAlias, loadCatalog, loadConfig, loadConfigFromCwd, maskValue, matchEnvVar, matchValueShape, parseToml, quoteDotenvValue, readConfigFile, readFnoxConfig, readResource, removeSection, renameSection, replaceSection, resolveConfig, resolveConfigPath, resolveInlineKey, resolveKeyPath, resolveSecrets, resolveValues, resourceDefinitions, scanEnv, scanFleet, sealSecrets, serializeEnvBlock, serializeSecretBlock, startServer, toolDefinitions, unsealSecrets, unwrapAgentKey, updateConfigIdentity, updateSectionFields, validateAliases, validateConfig, validateMcpHeaders };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "envpkt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Credential lifecycle and fleet management for AI agents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"credentials",
|
|
@@ -39,19 +39,19 @@
|
|
|
39
39
|
"prepublishOnly": "pnpm validate"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
43
43
|
"@sinclair/typebox": "^0.34.52",
|
|
44
44
|
"commander": "^15.0.0",
|
|
45
|
-
"functype": "^1.
|
|
46
|
-
"functype-log": "^1.
|
|
47
|
-
"functype-os": "^1.
|
|
48
|
-
"smol-toml": "^1.
|
|
45
|
+
"functype": "^1.15.0",
|
|
46
|
+
"functype-log": "^1.15.0",
|
|
47
|
+
"functype-os": "^1.15.0",
|
|
48
|
+
"smol-toml": "^1.9.1"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@types/node": "^24.
|
|
52
|
-
"ts-builds": "^3.
|
|
51
|
+
"@types/node": "^24.19.2",
|
|
52
|
+
"ts-builds": "^3.4.3",
|
|
53
53
|
"tsdown": "^0.22.14",
|
|
54
|
-
"tsx": "^4.23.
|
|
54
|
+
"tsx": "^4.23.15"
|
|
55
55
|
},
|
|
56
56
|
"type": "module",
|
|
57
57
|
"main": "./dist/index.js",
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
"schemas"
|
|
72
72
|
],
|
|
73
73
|
"prettier": "ts-builds/prettier",
|
|
74
|
-
"packageManager": "pnpm@11.
|
|
74
|
+
"packageManager": "pnpm@11.27.0+sha512.ad12821463bb143295f1fa3ecde77a6b9b97a7990386da800ff57bffc3964dc64b7b1c7559d5dbe5c337aea659ed9c8937d4370bd198ca2009bc6fa0249808e7"
|
|
75
75
|
}
|
|
@@ -211,6 +211,35 @@
|
|
|
211
211
|
"namespace": {
|
|
212
212
|
"description": "Override the file-level namespace for this entry's injected name. Empty string opts out of any prefix.",
|
|
213
213
|
"type": "string"
|
|
214
|
+
},
|
|
215
|
+
"mcp": {
|
|
216
|
+
"description": "MCP servers this secret authenticates to. Read by `envpkt headers` (Claude Code headersHelper).",
|
|
217
|
+
"type": "array",
|
|
218
|
+
"items": {
|
|
219
|
+
"description": "Send this secret as an HTTP header to an MCP server",
|
|
220
|
+
"type": "object",
|
|
221
|
+
"required": [
|
|
222
|
+
"server"
|
|
223
|
+
],
|
|
224
|
+
"properties": {
|
|
225
|
+
"server": {
|
|
226
|
+
"minLength": 1,
|
|
227
|
+
"description": "MCP server name, as keyed in .mcp.json",
|
|
228
|
+
"type": "string"
|
|
229
|
+
},
|
|
230
|
+
"header": {
|
|
231
|
+
"default": "Authorization",
|
|
232
|
+
"minLength": 1,
|
|
233
|
+
"description": "HTTP header name to send the secret in",
|
|
234
|
+
"type": "string"
|
|
235
|
+
},
|
|
236
|
+
"scheme": {
|
|
237
|
+
"default": "Bearer",
|
|
238
|
+
"description": "Prefix placed before the value (e.g. 'Bearer'). Empty string sends the raw value.",
|
|
239
|
+
"type": "string"
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
214
243
|
}
|
|
215
244
|
}
|
|
216
245
|
}
|