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` |
|
| `type` | string | no | Transport type: `stdio`, `sse`, `http` |
|
||||||
| `command` | string | stdio | Executable command for stdio transport |
|
| `command` | string | stdio | Executable command for stdio transport |
|
||||||
| `args` | array | no | Command arguments 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 |
|
| `env_file` | string | no | Path to environment file for stdio process |
|
||||||
| `url` | string | sse/http | Endpoint URL for `sse`/`http` transport |
|
| `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
|
### Transport Behavior
|
||||||
|
|
||||||
|
|
@ -236,7 +263,7 @@ dynamically only when requested by the user.*
|
||||||
"@modelcontextprotocol/server-github"
|
"@modelcontextprotocol/server-github"
|
||||||
],
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"GITHUB_PERSONAL_ACCESS_TOKEN": "YOUR_GITHUB_TOKEN"
|
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_PERSONAL_ACCESS_TOKEN}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"postgres": {
|
"postgres": {
|
||||||
|
|
@ -256,8 +283,8 @@ dynamically only when requested by the user.*
|
||||||
"@modelcontextprotocol/server-slack"
|
"@modelcontextprotocol/server-slack"
|
||||||
],
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"SLACK_BOT_TOKEN": "YOUR_SLACK_BOT_TOKEN",
|
"SLACK_BOT_TOKEN": "${SLACK_BOT_TOKEN}",
|
||||||
"SLACK_TEAM_ID": "YOUR_SLACK_TEAM_ID"
|
"SLACK_TEAM_ID": "${SLACK_TEAM_ID}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -926,8 +926,22 @@ func applyModelEnvOverrides(cfg *Config) {
|
||||||
"ollama": "OLLAMA_API_KEY",
|
"ollama": "OLLAMA_API_KEY",
|
||||||
}
|
}
|
||||||
for i, m := range cfg.ModelList {
|
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 != "" {
|
if m.APIKey != "" {
|
||||||
continue // already set in config.json, don't override
|
continue
|
||||||
}
|
}
|
||||||
provider, _, ok := strings.Cut(m.Model, "/")
|
provider, _, ok := strings.Cut(m.Model, "/")
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
|
||||||
|
|
@ -398,3 +398,85 @@ func TestModelConfig_RequestTimeoutDefaultZeroValue(t *testing.T) {
|
||||||
t.Fatalf("RequestTimeout = %d, want 0", cfg.RequestTimeout)
|
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