mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-27 07:16:19 +09:00
9ceabf68c4
Replace the host bind-mount + containerd exec approach with a per-bot
in-container gRPC server (ContainerService, port 9090). All file I/O,
exec, and MCP stdio sessions now go through gRPC instead of running
shell commands or reading host-mounted directories.
Architecture changes:
- cmd/mcp: rewritten as a gRPC server (ContainerService) with full
file and exec API (ReadFile, WriteFile, ListDir, ReadRaw, WriteRaw,
Exec, Stat, Mkdir, Rename, DeleteFile)
- internal/mcp/mcpcontainer: protobuf definitions and generated stubs
- internal/mcp/mcpclient: gRPC client wrapper with connection pool
(Pool) and Provider interface for dependency injection
- mcp.Manager: add per-bot IP cache, gRPC connection pool, and
SetContainerIP/MCPClient methods; remove DataDir/Exec helpers
- containerd.Service: remove ExecTask/ExecTaskStreaming; network setup
now returns NetworkResult{IP} for pool routing
- internal/fs/service.go: deleted (replaced by mcpclient)
- handlers/fs.go: deleted; MCP stdio session logic moved to mcp_stdio.go
- container provider Executor: all tools (read/write/list/edit/exec)
now call gRPC client instead of running shell via exec
- storefs, containerfs, media, skills, memory: all I/O ported to
mcpclient.Provider
Database:
- migration 0022: drop host_path column from containers table
One-time data migration:
- migrateBindMountData: on first Start() after upgrade, copies old
bind-mount data into the container via gRPC, then renames src dir
to prevent re-migration; runs in background goroutine
Bug fixes:
- mcp_stdio: callRaw now returns full JSON-RPC envelope
{"jsonrpc","id","result"|"error"} matching protocol spec;
explicit "initialize" call now advances session init state to
prevent duplicate handshake on next non-initialize call
- mcpclient Pool: properly evict stale gRPC connection after snapshot
replace (container process recreated); use SetContainerIP instead
of direct map write so IP changes always evict pool entry
- migrateBindMountData: walkErr on directories now counted as failure
so partially-walked trees don't get incorrectly marked as migrated
- cmd/mcp/Dockerfile: removed dead file (docker/Dockerfile.mcp is the
canonical production build)
Tests:
- provider_test.go: restored with bufconn in-process gRPC mock
(fakeContainerService + staticProvider), 14 cases covering all 5
tools plus edge cases
- mcp_session_test.go: new, covers JSON-RPC envelope, init state
machine, pending cleanup on cancel/close, readLoop cancel
- storefs/service_test.go: restored (pure function roundtrip tests)
33 lines
1.2 KiB
Go
33 lines
1.2 KiB
Go
// Package storage defines the Provider interface for object storage backends.
|
|
package storage
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
)
|
|
|
|
// Provider abstracts object storage operations.
|
|
type Provider interface {
|
|
// Put writes data to storage under the given key.
|
|
Put(ctx context.Context, key string, reader io.Reader) error
|
|
// Open returns a reader for the given storage key.
|
|
Open(ctx context.Context, key string) (io.ReadCloser, error)
|
|
// Delete removes the object at key.
|
|
Delete(ctx context.Context, key string) error
|
|
// AccessPath returns a consumer-accessible reference for a storage key.
|
|
// The format depends on the backend (e.g. container path, signed URL).
|
|
AccessPath(key string) string
|
|
}
|
|
|
|
// ContainerFileOpener is an optional interface that providers can implement
|
|
// to open arbitrary files from a bot's container data directory.
|
|
type ContainerFileOpener interface {
|
|
OpenContainerFile(ctx context.Context, botID, containerPath string) (io.ReadCloser, error)
|
|
}
|
|
|
|
// PrefixLister is an optional interface for providers that can list keys
|
|
// sharing a common prefix (e.g. directory listing on a filesystem backend).
|
|
type PrefixLister interface {
|
|
ListPrefix(ctx context.Context, prefix string) ([]string, error)
|
|
}
|