updating documentation & adding env var ref for model_list as well
This commit is contained in:
parent
2be28f1c21
commit
f2126abe24
3 changed files with 129 additions and 6 deletions
|
|
@ -150,10 +150,37 @@ and injected into the context for a configured number of turns (`ttl`).
|
|||
| `type` | string | no | Transport type: `stdio`, `sse`, `http` |
|
||||
| `command` | string | stdio | Executable command for stdio transport |
|
||||
| `args` | array | no | Command arguments for stdio transport |
|
||||
| `env` | object | no | Environment variables for stdio process |
|
||||
| `env` | object | no | Environment variables for stdio process (supports `${VAR}` references) |
|
||||
| `env_file` | string | no | Path to environment file for stdio process |
|
||||
| `url` | string | sse/http | Endpoint URL for `sse`/`http` transport |
|
||||
| `headers` | object | no | HTTP headers for `sse`/`http` transport |
|
||||
| `headers` | object | no | HTTP headers for `sse`/`http` transport (supports `${VAR}` references) |
|
||||
|
||||
### Secrets via `.env` File
|
||||
|
||||
Values in `env` and `headers` maps support `${VAR_NAME}` substitution. At startup, any value that is exactly `${VAR_NAME}` is replaced with the corresponding environment variable, which can come from a `.env` file in the working directory or from the shell environment.
|
||||
|
||||
This keeps secrets out of `config.json` (which may be committed to source control):
|
||||
|
||||
```json
|
||||
"github": {
|
||||
"enabled": true,
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-github"],
|
||||
"env": {
|
||||
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_PERSONAL_ACCESS_TOKEN}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
# .env (git-ignored)
|
||||
GITHUB_PERSONAL_ACCESS_TOKEN=ghp_your_actual_token
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- Only whole-value references are substituted: `"${FOO}"` → resolved. `"prefix-${FOO}"` → unchanged.
|
||||
- If the referenced variable is unset, the original `${VAR_NAME}` string is left as-is.
|
||||
- Literal values (no `${}`) pass through unchanged — existing configs continue to work.
|
||||
|
||||
### Transport Behavior
|
||||
|
||||
|
|
@ -236,7 +263,7 @@ dynamically only when requested by the user.*
|
|||
"@modelcontextprotocol/server-github"
|
||||
],
|
||||
"env": {
|
||||
"GITHUB_PERSONAL_ACCESS_TOKEN": "YOUR_GITHUB_TOKEN"
|
||||
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_PERSONAL_ACCESS_TOKEN}"
|
||||
}
|
||||
},
|
||||
"postgres": {
|
||||
|
|
@ -256,8 +283,8 @@ dynamically only when requested by the user.*
|
|||
"@modelcontextprotocol/server-slack"
|
||||
],
|
||||
"env": {
|
||||
"SLACK_BOT_TOKEN": "YOUR_SLACK_BOT_TOKEN",
|
||||
"SLACK_TEAM_ID": "YOUR_SLACK_TEAM_ID"
|
||||
"SLACK_BOT_TOKEN": "${SLACK_BOT_TOKEN}",
|
||||
"SLACK_TEAM_ID": "${SLACK_TEAM_ID}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -926,8 +926,22 @@ func applyModelEnvOverrides(cfg *Config) {
|
|||
"ollama": "OLLAMA_API_KEY",
|
||||
}
|
||||
for i, m := range cfg.ModelList {
|
||||
// Resolve ${VAR} references in sensitive/configurable fields first.
|
||||
// This supports custom providers and private endpoints without hardcoding secrets.
|
||||
if resolved := resolveEnvRef(m.APIKey); resolved != m.APIKey {
|
||||
cfg.ModelList[i].APIKey = resolved
|
||||
m.APIKey = resolved
|
||||
}
|
||||
if resolved := resolveEnvRef(m.APIBase); resolved != m.APIBase {
|
||||
cfg.ModelList[i].APIBase = resolved
|
||||
}
|
||||
if resolved := resolveEnvRef(m.Proxy); resolved != m.Proxy {
|
||||
cfg.ModelList[i].Proxy = resolved
|
||||
}
|
||||
|
||||
// Fall back to well-known provider env var if api_key is still empty.
|
||||
if m.APIKey != "" {
|
||||
continue // already set in config.json, don't override
|
||||
continue
|
||||
}
|
||||
provider, _, ok := strings.Cut(m.Model, "/")
|
||||
if !ok {
|
||||
|
|
|
|||
|
|
@ -398,3 +398,85 @@ func TestModelConfig_RequestTimeoutDefaultZeroValue(t *testing.T) {
|
|||
t.Fatalf("RequestTimeout = %d, want 0", cfg.RequestTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyModelEnvOverrides_ExplicitVarRef_APIKey(t *testing.T) {
|
||||
t.Setenv("MY_CUSTOM_API_KEY", "sk-custom-resolved")
|
||||
|
||||
cfg := &Config{
|
||||
ModelList: []ModelConfig{
|
||||
{ModelName: "custom", Model: "myhost/my-model", APIKey: "${MY_CUSTOM_API_KEY}"},
|
||||
},
|
||||
}
|
||||
|
||||
applyModelEnvOverrides(cfg)
|
||||
|
||||
if got := cfg.ModelList[0].APIKey; got != "sk-custom-resolved" {
|
||||
t.Errorf("APIKey = %q, want %q", got, "sk-custom-resolved")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyModelEnvOverrides_ExplicitVarRef_APIBase(t *testing.T) {
|
||||
t.Setenv("MY_PRIVATE_ENDPOINT", "https://private.example.com/v1")
|
||||
|
||||
cfg := &Config{
|
||||
ModelList: []ModelConfig{
|
||||
{ModelName: "local", Model: "openai/gpt-4o", APIKey: "key", APIBase: "${MY_PRIVATE_ENDPOINT}"},
|
||||
},
|
||||
}
|
||||
|
||||
applyModelEnvOverrides(cfg)
|
||||
|
||||
if got := cfg.ModelList[0].APIBase; got != "https://private.example.com/v1" {
|
||||
t.Errorf("APIBase = %q, want %q", got, "https://private.example.com/v1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyModelEnvOverrides_ExplicitVarRef_Proxy(t *testing.T) {
|
||||
t.Setenv("MY_PROXY", "http://proxy.example.com:8080")
|
||||
|
||||
cfg := &Config{
|
||||
ModelList: []ModelConfig{
|
||||
{ModelName: "proxied", Model: "openai/gpt-4o", APIKey: "key", Proxy: "${MY_PROXY}"},
|
||||
},
|
||||
}
|
||||
|
||||
applyModelEnvOverrides(cfg)
|
||||
|
||||
if got := cfg.ModelList[0].Proxy; got != "http://proxy.example.com:8080" {
|
||||
t.Errorf("Proxy = %q, want %q", got, "http://proxy.example.com:8080")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyModelEnvOverrides_VarRef_ThenProviderFallback(t *testing.T) {
|
||||
// ${VAR} on api_key resolves to empty (unset) → provider fallback should still apply
|
||||
t.Setenv("OPENAI_API_KEY", "sk-from-provider-map")
|
||||
|
||||
cfg := &Config{
|
||||
ModelList: []ModelConfig{
|
||||
// api_key is empty — should be filled by provider map fallback
|
||||
{ModelName: "gpt4", Model: "openai/gpt-4o", APIKey: ""},
|
||||
},
|
||||
}
|
||||
|
||||
applyModelEnvOverrides(cfg)
|
||||
|
||||
if got := cfg.ModelList[0].APIKey; got != "sk-from-provider-map" {
|
||||
t.Errorf("APIKey = %q, want %q (provider fallback should apply)", got, "sk-from-provider-map")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyModelEnvOverrides_HardcodedAPIKey_NotOverridden(t *testing.T) {
|
||||
t.Setenv("OPENAI_API_KEY", "sk-from-env")
|
||||
|
||||
cfg := &Config{
|
||||
ModelList: []ModelConfig{
|
||||
{ModelName: "gpt4", Model: "openai/gpt-4o", APIKey: "sk-hardcoded"},
|
||||
},
|
||||
}
|
||||
|
||||
applyModelEnvOverrides(cfg)
|
||||
|
||||
if got := cfg.ModelList[0].APIKey; got != "sk-hardcoded" {
|
||||
t.Errorf("APIKey = %q, want %q (hardcoded value must not be overridden)", got, "sk-hardcoded")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue