chore: upgrade framework to v1.12.0, wire login, drop manifest glue
- Bump mcp-framework v1.10.0 → v1.12.0 - Wire Login hook via BitwardenLoginHandler (backend_policy = bitwarden-cli) - Remove ManifestDir/ManifestValidator from config test: le manifest est un artefact de build, pas une contrainte runtime (per framework design) - Drop doctorManifestDir(), validateManifestUpdate() and dead imports - Update tests accordingly (5 checks instead of 6, remove manifest test) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1e0bdfc42d
commit
235727106d
5 changed files with 9 additions and 137 deletions
2
go.mod
2
go.mod
|
|
@ -3,7 +3,7 @@ module email-mcp
|
||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
forge.lclr.dev/AI/mcp-framework v1.10.0
|
forge.lclr.dev/AI/mcp-framework v1.12.0
|
||||||
github.com/emersion/go-imap/v2 v2.0.0-beta.8
|
github.com/emersion/go-imap/v2 v2.0.0-beta.8
|
||||||
github.com/emersion/go-message v0.18.2
|
github.com/emersion/go-message v0.18.2
|
||||||
github.com/godbus/dbus/v5 v5.2.2
|
github.com/godbus/dbus/v5 v5.2.2
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -2,6 +2,8 @@ forge.lclr.dev/AI/mcp-framework v1.9.0 h1:8i2CHQlQo/mRG1BE2UArHptAa/HC7AOhZBIqz8
|
||||||
forge.lclr.dev/AI/mcp-framework v1.9.0/go.mod h1:2xzmFEHGLQzT5PORq35j10pRhsOm0CDwivUZTHvxgh4=
|
forge.lclr.dev/AI/mcp-framework v1.9.0/go.mod h1:2xzmFEHGLQzT5PORq35j10pRhsOm0CDwivUZTHvxgh4=
|
||||||
forge.lclr.dev/AI/mcp-framework v1.10.0 h1:RrTy7K/hSruaVS9Z/oaRpkLs2U5WGs4H3tox7PiErak=
|
forge.lclr.dev/AI/mcp-framework v1.10.0 h1:RrTy7K/hSruaVS9Z/oaRpkLs2U5WGs4H3tox7PiErak=
|
||||||
forge.lclr.dev/AI/mcp-framework v1.10.0/go.mod h1:2xzmFEHGLQzT5PORq35j10pRhsOm0CDwivUZTHvxgh4=
|
forge.lclr.dev/AI/mcp-framework v1.10.0/go.mod h1:2xzmFEHGLQzT5PORq35j10pRhsOm0CDwivUZTHvxgh4=
|
||||||
|
forge.lclr.dev/AI/mcp-framework v1.12.0 h1:pu1cfWcL62BF+f7DBe4IbkigHLcK6YOJ3vEBz1495AY=
|
||||||
|
forge.lclr.dev/AI/mcp-framework v1.12.0/go.mod h1:2xzmFEHGLQzT5PORq35j10pRhsOm0CDwivUZTHvxgh4=
|
||||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
|
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
|
||||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
|
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
|
||||||
github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
|
github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,7 @@ func (a *App) runBootstrap(ctx context.Context, args []string) error {
|
||||||
Setup: func(ctx context.Context, inv frameworkbootstrap.Invocation) error {
|
Setup: func(ctx context.Context, inv frameworkbootstrap.Invocation) error {
|
||||||
return a.runConfig(ctx, frameworkbootstrap.CommandSetup, inv.Args)
|
return a.runConfig(ctx, frameworkbootstrap.CommandSetup, inv.Args)
|
||||||
},
|
},
|
||||||
|
Login: frameworkbootstrap.BitwardenLoginHandler(metadata.BinaryName),
|
||||||
MCP: func(ctx context.Context, inv frameworkbootstrap.Invocation) error {
|
MCP: func(ctx context.Context, inv frameworkbootstrap.Invocation) error {
|
||||||
return a.runMCP(ctx, inv.Args)
|
return a.runMCP(ctx, inv.Args)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -966,7 +966,7 @@ base_url = "https://gitea.lclr.dev"
|
||||||
"[OK] profile: required profile values are resolved",
|
"[OK] profile: required profile values are resolved",
|
||||||
"[OK] password: stored password is present",
|
"[OK] password: stored password is present",
|
||||||
"[OK] connectivity: IMAP server is reachable",
|
"[OK] connectivity: IMAP server is reachable",
|
||||||
"Summary: 6 ok, 0 warning(s), 0 failure(s), 6 total",
|
"Summary: 5 ok, 0 warning(s), 0 failure(s), 5 total",
|
||||||
} {
|
} {
|
||||||
if !strings.Contains(text, needle) {
|
if !strings.Contains(text, needle) {
|
||||||
t.Fatalf("output = %q, want substring %q", text, needle)
|
t.Fatalf("output = %q, want substring %q", text, needle)
|
||||||
|
|
@ -996,16 +996,6 @@ func TestAppRunDoctorReturnsErrorWhenChecksFail(t *testing.T) {
|
||||||
t.Fatalf("Save returned error: %v", err)
|
t.Fatalf("Save returned error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestDir := t.TempDir()
|
|
||||||
if err := os.WriteFile(filepath.Join(manifestDir, "mcp.toml"), []byte(`
|
|
||||||
[update]
|
|
||||||
driver = "gitea"
|
|
||||||
repository = "AI/email-mcp"
|
|
||||||
base_url = "https://gitea.lclr.dev"
|
|
||||||
`), 0o600); err != nil {
|
|
||||||
t.Fatalf("WriteFile returned error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
output := &bytes.Buffer{}
|
output := &bytes.Buffer{}
|
||||||
app := NewAppWithDependencies(
|
app := NewAppWithDependencies(
|
||||||
nil,
|
nil,
|
||||||
|
|
@ -1014,7 +1004,7 @@ base_url = "https://gitea.lclr.dev"
|
||||||
func() mcpserver.MailService { return &doctorMailServiceStub{} },
|
func() mcpserver.MailService { return &doctorMailServiceStub{} },
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
func() (string, error) { return filepath.Join(manifestDir, "email-mcp"), nil },
|
nil,
|
||||||
nil,
|
nil,
|
||||||
output,
|
output,
|
||||||
&bytes.Buffer{},
|
&bytes.Buffer{},
|
||||||
|
|
@ -1057,16 +1047,6 @@ func TestAppRunDoctorAcceptsPasswordFromEnvironment(t *testing.T) {
|
||||||
t.Fatalf("Save returned error: %v", err)
|
t.Fatalf("Save returned error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestDir := t.TempDir()
|
|
||||||
if err := os.WriteFile(filepath.Join(manifestDir, "mcp.toml"), []byte(`
|
|
||||||
[update]
|
|
||||||
driver = "gitea"
|
|
||||||
repository = "AI/email-mcp"
|
|
||||||
base_url = "https://gitea.lclr.dev"
|
|
||||||
`), 0o600); err != nil {
|
|
||||||
t.Fatalf("WriteFile returned error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
output := &bytes.Buffer{}
|
output := &bytes.Buffer{}
|
||||||
app := NewAppWithDependencies(
|
app := NewAppWithDependencies(
|
||||||
nil,
|
nil,
|
||||||
|
|
@ -1075,7 +1055,7 @@ base_url = "https://gitea.lclr.dev"
|
||||||
func() mcpserver.MailService { return &doctorMailServiceStub{} },
|
func() mcpserver.MailService { return &doctorMailServiceStub{} },
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
func() (string, error) { return filepath.Join(manifestDir, "email-mcp"), nil },
|
nil,
|
||||||
nil,
|
nil,
|
||||||
output,
|
output,
|
||||||
&bytes.Buffer{},
|
&bytes.Buffer{},
|
||||||
|
|
@ -1090,73 +1070,6 @@ base_url = "https://gitea.lclr.dev"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAppRunDoctorFailsWhenManifestUpdateConfigIsInvalid(t *testing.T) {
|
|
||||||
tempHome := t.TempDir()
|
|
||||||
t.Setenv("XDG_CONFIG_HOME", tempHome)
|
|
||||||
t.Setenv("HOME", tempHome)
|
|
||||||
t.Setenv(passwordEnv, "env-secret")
|
|
||||||
|
|
||||||
store := frameworkconfig.NewStore[ProfileConfig](binaryName)
|
|
||||||
configPath, err := store.ConfigPath()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ConfigPath returned error: %v", err)
|
|
||||||
}
|
|
||||||
if err := store.Save(configPath, frameworkconfig.FileConfig[ProfileConfig]{
|
|
||||||
Version: frameworkconfig.CurrentVersion,
|
|
||||||
CurrentProfile: "work",
|
|
||||||
Profiles: map[string]ProfileConfig{
|
|
||||||
"work": {
|
|
||||||
Host: "imap.example.com",
|
|
||||||
Username: "alice",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatalf("Save returned error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
manifestDir := t.TempDir()
|
|
||||||
if err := os.WriteFile(filepath.Join(manifestDir, "mcp.toml"), []byte(`
|
|
||||||
[update]
|
|
||||||
driver = "gitea"
|
|
||||||
base_url = "https://gitea.lclr.dev"
|
|
||||||
`), 0o600); err != nil {
|
|
||||||
t.Fatalf("WriteFile returned error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
mail := &doctorMailServiceStub{
|
|
||||||
listMailboxes: []imapclient.Mailbox{{Name: "INBOX"}},
|
|
||||||
}
|
|
||||||
output := &bytes.Buffer{}
|
|
||||||
app := NewAppWithDependencies(
|
|
||||||
nil,
|
|
||||||
store,
|
|
||||||
func() (secretStore, error) { return &secretStoreStub{}, nil },
|
|
||||||
func() mcpserver.MailService { return mail },
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
func() (string, error) { return filepath.Join(manifestDir, "email-mcp"), nil },
|
|
||||||
nil,
|
|
||||||
output,
|
|
||||||
&bytes.Buffer{},
|
|
||||||
"dev",
|
|
||||||
)
|
|
||||||
|
|
||||||
err = app.Run([]string{"doctor"})
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("expected doctor to fail with invalid manifest update config")
|
|
||||||
}
|
|
||||||
if !strings.Contains(err.Error(), "doctor checks failed") {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
text := output.String()
|
|
||||||
if !strings.Contains(text, "[FAIL] manifest: manifest validation failed") {
|
|
||||||
t.Fatalf("unexpected output: %q", text)
|
|
||||||
}
|
|
||||||
if !strings.Contains(text, "requires repository") {
|
|
||||||
t.Fatalf("unexpected output: %q", text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAppRunReturnsClearErrorsWhenDependenciesMissing(t *testing.T) {
|
func TestAppRunReturnsClearErrorsWhenDependenciesMissing(t *testing.T) {
|
||||||
app := NewAppWithDependencies(nil, nil, nil, nil, nil, nil, nil, nil, nil, &bytes.Buffer{}, "dev")
|
app := NewAppWithDependencies(nil, nil, nil, nil, nil, nil, nil, nil, nil, &bytes.Buffer{}, "dev")
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,11 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"email-mcp/mcpgen"
|
"email-mcp/mcpgen"
|
||||||
frameworkcli "forge.lclr.dev/AI/mcp-framework/cli"
|
frameworkcli "forge.lclr.dev/AI/mcp-framework/cli"
|
||||||
frameworkconfig "forge.lclr.dev/AI/mcp-framework/config"
|
frameworkconfig "forge.lclr.dev/AI/mcp-framework/config"
|
||||||
frameworkmanifest "forge.lclr.dev/AI/mcp-framework/manifest"
|
|
||||||
frameworkupdate "forge.lclr.dev/AI/mcp-framework/update"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *App) runDoctor(ctx context.Context, args []string) error {
|
func (a *App) runDoctor(ctx context.Context, args []string) error {
|
||||||
|
|
@ -32,14 +27,9 @@ func (a *App) runDoctor(ctx context.Context, args []string) error {
|
||||||
return fmt.Errorf("mail service is not configured")
|
return fmt.Errorf("mail service is not configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata := a.runtimeMetadata()
|
|
||||||
report := frameworkcli.RunDoctor(ctx, frameworkcli.DoctorOptions{
|
report := frameworkcli.RunDoctor(ctx, frameworkcli.DoctorOptions{
|
||||||
ConfigCheck: frameworkcli.NewConfigCheck(frameworkconfig.NewStore[ProfileConfig](mcpgen.BinaryName)),
|
ConfigCheck: frameworkcli.NewConfigCheck(frameworkconfig.NewStore[ProfileConfig](mcpgen.BinaryName)),
|
||||||
SecretStoreCheck: frameworkcli.SecretStoreAvailabilityCheck(a.openSecretStore),
|
SecretStoreCheck: frameworkcli.SecretStoreAvailabilityCheck(a.openSecretStore),
|
||||||
ManifestDir: a.doctorManifestDir(),
|
|
||||||
ManifestValidator: func(file frameworkmanifest.File, _ string) []string {
|
|
||||||
return validateManifestUpdate(file, metadata.BinaryName)
|
|
||||||
},
|
|
||||||
ConnectivityCheck: a.doctorConnectivityCheck(profileFlag),
|
ConnectivityCheck: a.doctorConnectivityCheck(profileFlag),
|
||||||
ExtraChecks: []frameworkcli.DoctorCheck{
|
ExtraChecks: []frameworkcli.DoctorCheck{
|
||||||
a.doctorRequiredProfileFieldsCheck(profileFlag),
|
a.doctorRequiredProfileFieldsCheck(profileFlag),
|
||||||
|
|
@ -55,18 +45,6 @@ func (a *App) runDoctor(ctx context.Context, args []string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) doctorManifestDir() string {
|
|
||||||
if a.resolveExecutable == nil {
|
|
||||||
return "."
|
|
||||||
}
|
|
||||||
|
|
||||||
executablePath, err := a.resolveExecutable()
|
|
||||||
if err != nil {
|
|
||||||
return "."
|
|
||||||
}
|
|
||||||
return filepath.Dir(executablePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) doctorRequiredProfileFieldsCheck(profileFlag string) frameworkcli.DoctorCheck {
|
func (a *App) doctorRequiredProfileFieldsCheck(profileFlag string) frameworkcli.DoctorCheck {
|
||||||
var (
|
var (
|
||||||
profileValues map[string]string
|
profileValues map[string]string
|
||||||
|
|
@ -213,25 +191,3 @@ func (a *App) resolveDoctorProfileName(profileFlag string) string {
|
||||||
return a.resolveProfileName(profileFlag, cfg.CurrentProfile)
|
return a.resolveProfileName(profileFlag, cfg.CurrentProfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateManifestUpdate(file frameworkmanifest.File, runtimeBinaryName string) []string {
|
|
||||||
source := file.Update.ReleaseSource()
|
|
||||||
issues := make([]string, 0, 2)
|
|
||||||
|
|
||||||
if _, err := frameworkupdate.ResolveLatestReleaseURL("", source); err != nil {
|
|
||||||
issues = append(issues, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
binary := strings.TrimSpace(runtimeBinaryName)
|
|
||||||
if binary == "" {
|
|
||||||
binary = strings.TrimSpace(file.BinaryName)
|
|
||||||
}
|
|
||||||
if binary == "" {
|
|
||||||
binary = binaryName
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := frameworkupdate.AssetNameWithTemplate(binary, runtime.GOOS, runtime.GOARCH, source.AssetNameTemplate); err != nil {
|
|
||||||
issues = append(issues, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return issues
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue