fix(mcp): normalize local command paths and document env-file usage
This commit is contained in:
parent
f4dbac0dcf
commit
07032df037
5 changed files with 78 additions and 10 deletions
|
|
@ -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).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue