fix(mcp): normalize local command paths and document env-file usage

This commit is contained in:
afjcjsbx 2026-04-24 10:32:55 +02:00
parent f4dbac0dcf
commit 07032df037
5 changed files with 78 additions and 10 deletions

View file

@ -581,7 +581,8 @@ picoclaw mcp test filesystem
`picoclaw mcp` is a configuration manager: it updates `config.json` under `tools.mcp.servers`, but it does not keep the server process running itself. `picoclaw mcp` is a configuration manager: it updates `config.json` under `tools.mcp.servers`, but it does not keep the server process running itself.
Use `picoclaw mcp edit` when you need advanced fields such as `headers`, `env_file`, or `deferred`. Use `picoclaw mcp edit` when you need advanced fields that are not covered by `picoclaw mcp add`.
For example, `picoclaw mcp add` supports `--deferred` and `--env-file`, while `picoclaw mcp edit` is still useful for direct JSON editing and uncommon MCP settings.
For full MCP configuration (stdio, SSE, HTTP transports, Tool Discovery), see [Tools Configuration - MCP](docs/reference/tools_configuration.md#mcp-tool). For CLI usage and examples, see [MCP Server CLI](docs/reference/mcp-cli.md). For full MCP configuration (stdio, SSE, HTTP transports, Tool Discovery), see [Tools Configuration - MCP](docs/reference/tools_configuration.md#mcp-tool). For CLI usage and examples, see [MCP Server CLI](docs/reference/mcp-cli.md).

View file

@ -12,6 +12,7 @@ import (
type addOptions struct { type addOptions struct {
Env []string Env []string
EnvFile string
Headers []string Headers []string
Transport string Transport string
Force bool Force bool
@ -70,7 +71,8 @@ func newAddCommand() *cobra.Command {
} }
flags := cmd.Flags() flags := cmd.Flags()
flags.StringArrayP("env", "e", nil, "Environment variable in KEY=value format (repeatable)") flags.StringArrayP("env", "e", nil, "Environment variable in KEY=value format (repeatable, saved to config)")
flags.String("env-file", "", "Path to an env file for stdio servers (recommended for secrets)")
flags.StringArrayP("header", "H", nil, "HTTP header in 'Name: Value' or 'Name=Value' format (repeatable)") flags.StringArrayP("header", "H", nil, "HTTP header in 'Name: Value' or 'Name=Value' format (repeatable)")
flags.StringP("transport", "t", "stdio", "Transport type: stdio, http, or sse") flags.StringP("transport", "t", "stdio", "Transport type: stdio, http, or sse")
flags.BoolP("force", "f", false, "Overwrite an existing server without prompting") flags.BoolP("force", "f", false, "Overwrite an existing server without prompting")
@ -119,8 +121,16 @@ func parseAddArgs(args []string) (addOptions, string, string, []string, bool, er
} }
i++ i++
opts.Env = append(opts.Env, args[i]) opts.Env = append(opts.Env, args[i])
case arg == "--env-file":
if i+1 >= len(args) {
return addOptions{}, "", "", nil, false, fmt.Errorf("missing value for %s", arg)
}
i++
opts.EnvFile = args[i]
case strings.HasPrefix(arg, "--env="): case strings.HasPrefix(arg, "--env="):
opts.Env = append(opts.Env, strings.TrimPrefix(arg, "--env=")) opts.Env = append(opts.Env, strings.TrimPrefix(arg, "--env="))
case strings.HasPrefix(arg, "--env-file="):
opts.EnvFile = strings.TrimPrefix(arg, "--env-file=")
case arg == "--header" || arg == "-H": case arg == "--header" || arg == "-H":
if i+1 >= len(args) { if i+1 >= len(args) {
return addOptions{}, "", "", nil, false, fmt.Errorf("missing value for %s", arg) return addOptions{}, "", "", nil, false, fmt.Errorf("missing value for %s", arg)
@ -193,6 +203,9 @@ func buildServerConfig(target string, args []string, opts addOptions) (config.MC
if len(env) > 0 { if len(env) > 0 {
return config.MCPServerConfig{}, fmt.Errorf("--env can only be used with stdio transport") return config.MCPServerConfig{}, fmt.Errorf("--env can only be used with stdio transport")
} }
if strings.TrimSpace(opts.EnvFile) != "" {
return config.MCPServerConfig{}, fmt.Errorf("--env-file can only be used with stdio transport")
}
if len(args) > 0 { if len(args) > 0 {
return config.MCPServerConfig{}, fmt.Errorf("%s transport does not accept command arguments", transport) return config.MCPServerConfig{}, fmt.Errorf("%s transport does not accept command arguments", transport)
} }
@ -230,6 +243,7 @@ func buildServerConfig(target string, args []string, opts addOptions) (config.MC
server.Command = command server.Command = command
server.Args = commandArgs server.Args = commandArgs
server.Env = env server.Env = env
server.EnvFile = strings.TrimSpace(opts.EnvFile)
return server, nil return server, nil
} }

View file

@ -139,6 +139,46 @@ func TestMCPAddSupportsExplicitStdioCommandAfterSeparator(t *testing.T) {
assert.Equal(t, map[string]string{"AIRTABLE_API_KEY": "YOUR_KEY"}, server.Env) assert.Equal(t, map[string]string{"AIRTABLE_API_KEY": "YOUR_KEY"}, server.Env)
} }
func TestMCPAddSupportsEnvFileForStdio(t *testing.T) {
configPath := setupMCPConfigEnv(t)
cmd := NewMCPCommand()
_, err := executeCommand(cmd, []string{
"add",
"--env-file",
".env.mcp",
"filesystem",
"npx",
"-y",
"@modelcontextprotocol/server-filesystem",
}, "")
require.NoError(t, err)
cfg := readMCPConfig(t, configPath)
server := cfg.Tools.MCP.Servers["filesystem"]
assert.Equal(t, "stdio", server.Type)
assert.Equal(t, "npx", server.Command)
assert.Equal(t, []string{"-y", "@modelcontextprotocol/server-filesystem"}, server.Args)
assert.Equal(t, ".env.mcp", server.EnvFile)
}
func TestMCPAddRejectsEnvFileForHTTP(t *testing.T) {
setupMCPConfigEnv(t)
cmd := NewMCPCommand()
_, err := executeCommand(cmd, []string{
"add",
"--transport",
"http",
"--env-file",
".env.mcp",
"context7",
"https://mcp.context7.com/mcp",
}, "")
require.Error(t, err)
assert.Contains(t, err.Error(), "--env-file can only be used with stdio transport")
}
func TestMCPAddRejectsNonExecutableLocalCommand(t *testing.T) { func TestMCPAddRejectsNonExecutableLocalCommand(t *testing.T) {
setupMCPConfigEnv(t) setupMCPConfigEnv(t)

View file

@ -564,7 +564,8 @@ picoclaw mcp test filesystem
`picoclaw mcp` agisce come configuration manager: aggiorna `config.json` sotto `tools.mcp.servers`, ma non mantiene in esecuzione il processo del server. `picoclaw mcp` agisce come configuration manager: aggiorna `config.json` sotto `tools.mcp.servers`, ma non mantiene in esecuzione il processo del server.
Usa `picoclaw mcp edit` quando ti servono campi avanzati come `headers`, `env_file` o `deferred`. Usa `picoclaw mcp edit` quando ti servono campi avanzati che non sono coperti da `picoclaw mcp add`.
Per esempio, `picoclaw mcp add` supporta `--deferred` e `--env-file`, mentre `picoclaw mcp edit` resta utile per modifiche JSON dirette e opzioni MCP meno comuni.
Per la configurazione MCP completa (trasporti stdio, SSE, HTTP, Tool Discovery), vedi [Configurazione degli Strumenti - MCP](../reference/tools_configuration.md#mcp-tool). Per la reference della CLI, vedi [MCP Server CLI](../reference/mcp-cli.md). Per la configurazione MCP completa (trasporti stdio, SSE, HTTP, Tool Discovery), vedi [Configurazione degli Strumenti - MCP](../reference/tools_configuration.md#mcp-tool). Per la reference della CLI, vedi [MCP Server CLI](../reference/mcp-cli.md).

View file

@ -36,12 +36,18 @@ Add a stdio server via `npx`:
picoclaw mcp add filesystem -- npx -y @modelcontextprotocol/server-filesystem /tmp picoclaw mcp add filesystem -- npx -y @modelcontextprotocol/server-filesystem /tmp
``` ```
Add a stdio server with environment variables: Add a stdio server with environment variables saved in config:
```bash ```bash
picoclaw mcp add github --env GITHUB_PERSONAL_ACCESS_TOKEN=ghp_xxx -- npx -y @modelcontextprotocol/server-github picoclaw mcp add github --env GITHUB_PERSONAL_ACCESS_TOKEN=ghp_xxx -- npx -y @modelcontextprotocol/server-github
``` ```
Add a stdio server using an env file for secrets:
```bash
picoclaw mcp add github --env-file .env.github -- npx -y @modelcontextprotocol/server-github
```
Add a remote HTTP server: Add a remote HTTP server:
```bash ```bash
@ -108,7 +114,8 @@ Supported flags:
| Flag | Meaning | | Flag | Meaning |
|------|---------| |------|---------|
| `--env`, `-e` | Add a stdio environment variable in `KEY=value` format. Repeatable. | | `--env`, `-e` | Add a stdio environment variable in `KEY=value` format. Repeatable. Values are saved to config. |
| `--env-file` | Attach an env file path to a stdio server. Recommended for secrets you do not want stored inline in `config.json`. |
| `--header`, `-H` | Add an HTTP header in `Name: Value` or `Name=Value` format. Repeatable. | | `--header`, `-H` | Add an HTTP header in `Name: Value` or `Name=Value` format. Repeatable. |
| `--transport`, `-t` | Transport type: `stdio` (default), `http`, or `sse`. | | `--transport`, `-t` | Transport type: `stdio` (default), `http`, or `sse`. |
| `--force`, `-f` | Overwrite an existing server entry without confirmation. | | `--force`, `-f` | Overwrite an existing server entry without confirmation. |
@ -131,6 +138,11 @@ Parsing behavior:
- use the `--` separator when the stdio command itself has arguments that may look like PicoClaw CLI flags - use the `--` separator when the stdio command itself has arguments that may look like PicoClaw CLI flags
- without `--`, PicoClaw treats the first two non-flag tokens as `<name>` and `<command-or-url>` - without `--`, PicoClaw treats the first two non-flag tokens as `<name>` and `<command-or-url>`
Secret handling:
- `--env KEY=value` stores the resolved value directly in `config.json`
- use `--env-file` instead when the value is sensitive and should stay outside the main config file
Example: Example:
```bash ```bash
@ -182,6 +194,7 @@ For `stdio`:
- `<command-or-url>` is treated as the command - `<command-or-url>` is treated as the command
- `[args...]` are stored in `args` - `[args...]` are stored in `args`
- `--env` is supported - `--env` is supported
- `--env-file` is supported and stored in `env_file`
- `--header` is rejected - `--header` is rejected
- `-- <command> [args...]` is supported and recommended for unambiguous parsing - `-- <command> [args...]` is supported and recommended for unambiguous parsing
@ -190,6 +203,7 @@ For `http` / `sse`:
- `<command-or-url>` must be a valid URL - `<command-or-url>` must be a valid URL
- extra command args are rejected - extra command args are rejected
- `--env` is rejected - `--env` is rejected
- `--env-file` is rejected
- `--header` is supported and stored in `headers` - `--header` is supported and stored in `headers`
Overwrite behavior: Overwrite behavior:
@ -322,9 +336,7 @@ picoclaw mcp edit
This opens the config file in the editor pointed to by `$EDITOR`. This opens the config file in the editor pointed to by `$EDITOR`.
Use it when you need to configure MCP fields that are not exposed directly by `picoclaw mcp add`, such as: Use it when you need to configure MCP fields that are not exposed directly by `picoclaw mcp add`.
- `env_file`
If `$EDITOR` is not set, the command fails with an explicit error. If `$EDITOR` is not set, the command fails with an explicit error.
@ -337,10 +349,10 @@ For common cases:
3. Check all servers at a glance with `picoclaw mcp list --status`. 3. Check all servers at a glance with `picoclaw mcp list --status`.
4. Start PicoClaw normally so the configured MCP server is loaded by the host. 4. Start PicoClaw normally so the configured MCP server is loaded by the host.
For advanced cases (e.g. `env_file`): For advanced cases:
1. Add the base entry with `picoclaw mcp add`. 1. Add the base entry with `picoclaw mcp add`.
2. Run `picoclaw mcp edit` to fill in `env_file` or other fields not exposed as CLI flags. 2. Run `picoclaw mcp edit` to fill in fields that are not exposed as CLI flags.
3. Run `picoclaw mcp show <name>` to confirm the final configuration and tool list. 3. Run `picoclaw mcp show <name>` to confirm the final configuration and tool list.
## Related Docs ## Related Docs