The GitHub account that hosted this repository is suspended, so the remote is dead and the vanity import github.com/ZacharyZhang-NY/ely-eye/... no longer resolves. The Go module path is that import, so it moves with the host: git.zacharyzhang.com serves a go-import meta tag for the nested ely-eye-mcp submodule, which go build and go get both need. The installers read release assets from the host too, so their API and download URLs move with it. The release workflow's -ldflags symbol path is the module path, so it moves as well; a build stamped with the new path prints the stamped version.
91 lines
3.7 KiB
Go
91 lines
3.7 KiB
Go
package mcpserver
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/ely-eye/ely-eye-mcp/internal/apiclient"
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/ely-eye/ely-eye-mcp/internal/config"
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/ely-eye/ely-eye-mcp/internal/store"
|
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
|
)
|
|
|
|
type Server struct {
|
|
cfg config.Config
|
|
store *store.Store
|
|
api *apiclient.Client
|
|
version string
|
|
}
|
|
|
|
func Run(ctx context.Context, cfg config.Config, version string) error {
|
|
db, err := store.Open(ctx, cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer db.Close()
|
|
server := &Server{cfg: cfg, store: db, api: apiclient.New(cfg.APIBaseURL), version: version}
|
|
mcpServer := mcp.NewServer(&mcp.Implementation{
|
|
Name: "ely-eye",
|
|
Title: "Ely-Eye Context Cartridge MCP",
|
|
Version: version,
|
|
}, &mcp.ServerOptions{
|
|
Instructions: "Use Ely-Eye tools to retrieve local cartridge context, evidence atoms, proof suites, and compiled evidence packs. Read tools mirror local Ely-Eye state without writing. remember, supersede_memory and forget_memory mutate memory through the Ely-Eye API, which must be running (`ely-eye serve`); the server, not the caller, decides who owns a memory. mount_cartridge and switch_cartridge decide what a search may see: mount a cartridge to ask only that cartridge, and unmount everything to ask the whole library. working_set reloads what one session already recorded, in order, without searching; memory_conflicts reports what the store has not reconciled, and settling one is the operator's act, not a tool's. ely_eye_card says what the store can answer before anything is sent: schema version, which senses its memory covers, and who holds what. A read with no ELY_EYE_AUTH_TOKEN sees only memories nobody owns and memories marked for everyone, so a private answer needs the token.",
|
|
})
|
|
server.registerTools(mcpServer)
|
|
server.registerWriteTools(mcpServer)
|
|
server.registerDirectoryTools(mcpServer)
|
|
server.registerMountTools(mcpServer)
|
|
server.registerTierTools(mcpServer)
|
|
server.registerCardTools(mcpServer)
|
|
server.registerResources(mcpServer)
|
|
if err := mcpServer.Run(ctx, &mcp.StdioTransport{}); err != nil {
|
|
if errors.Is(err, io.EOF) || strings.HasSuffix(err.Error(), "EOF") {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) registerResources(server *mcp.Server) {
|
|
addFileResource(server, "ely-eye://project/prd", "Ely-Eye PRD", filepath.Join(s.cfg.ProjectRoot, "PRD.md"))
|
|
addFileResource(server, "ely-eye://project/readme", "Ely-Eye README", filepath.Join(s.cfg.ProjectRoot, "README.md"))
|
|
addFileResource(server, "ely-eye://project/mcp-research", "Ely-Eye MCP research notes", filepath.Join(s.cfg.ProjectRoot, "ely-eye-mcp", "docs", "RESEARCH.md"))
|
|
}
|
|
|
|
// addFileResource registers a project document only when it exists on disk.
|
|
// External deployments rarely ship the Ely-Eye repository documents, so a
|
|
// missing file is a normal condition and the resource is simply omitted.
|
|
func addFileResource(server *mcp.Server, uri string, name string, path string) {
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
return
|
|
}
|
|
size := info.Size()
|
|
server.AddResource(&mcp.Resource{
|
|
URI: uri,
|
|
Name: name,
|
|
Title: name,
|
|
Description: "Local Ely-Eye project resource: " + path,
|
|
MIMEType: "text/markdown",
|
|
Size: size,
|
|
}, func(ctx context.Context, request *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
|
|
_ = ctx
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &mcp.ReadResourceResult{
|
|
Contents: []*mcp.ResourceContents{{
|
|
URI: request.Params.URI,
|
|
MIMEType: "text/markdown",
|
|
Text: string(data),
|
|
}},
|
|
}, nil
|
|
})
|
|
}
|