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.
96 lines
4.3 KiB
Go
96 lines
4.3 KiB
Go
package mcpserver
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/ely-eye/ely-eye-mcp/internal/domain"
|
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
|
)
|
|
|
|
type RememberInput struct {
|
|
Text string `json:"text" jsonschema:"the statement to remember, kept verbatim"`
|
|
Scenario string `json:"scenario,omitempty" jsonschema:"optional context label such as home or work"`
|
|
Session string `json:"session_id,omitempty" jsonschema:"the conversation this belongs to; working_set reloads a session in order"`
|
|
EventTime string `json:"event_time,omitempty" jsonschema:"when it was true, ISO 8601 with offset, e.g. 2026-05-01T12:00:00+08:00"`
|
|
EventTimePrecision string `json:"event_time_precision,omitempty" jsonschema:"instant, minute, hour, day, month, or year; required with event_time"`
|
|
RetentionUntil string `json:"retention_until,omitempty" jsonschema:"ISO 8601 with offset; the memory stops being retrievable after this"`
|
|
}
|
|
|
|
type SupersedeInput struct {
|
|
AtomID string `json:"atom_id" jsonschema:"exact Evidence Atom id being replaced"`
|
|
Text string `json:"text" jsonschema:"the newer statement, kept verbatim"`
|
|
Scenario string `json:"scenario,omitempty" jsonschema:"optional context label such as home or work"`
|
|
Session string `json:"session_id,omitempty" jsonschema:"the conversation this belongs to; working_set reloads a session in order"`
|
|
EventTime string `json:"event_time,omitempty" jsonschema:"when the new statement became true, ISO 8601 with offset"`
|
|
EventTimePrecision string `json:"event_time_precision,omitempty" jsonschema:"instant, minute, hour, day, month, or year; required with event_time"`
|
|
}
|
|
|
|
type ForgetInput struct {
|
|
AtomID string `json:"atom_id,omitempty" jsonschema:"exact Evidence Atom id to forget"`
|
|
SourceID string `json:"source_id,omitempty" jsonschema:"exact source id to forget, removing every atom of that source"`
|
|
Reason string `json:"reason,omitempty" jsonschema:"why this memory is gone"`
|
|
}
|
|
|
|
type RememberOutput struct {
|
|
Atom domain.Memory `json:"atom"`
|
|
}
|
|
|
|
type ForgetOutput struct {
|
|
Report domain.ForgetReport `json:"report"`
|
|
}
|
|
|
|
func (s *Server) registerWriteTools(server *mcp.Server) {
|
|
mcp.AddTool(server, &mcp.Tool{
|
|
Name: "remember",
|
|
Title: "Remember",
|
|
Description: "Store a new memory. The Ely-Eye server stamps the holder, origin and ingestion time, so a caller cannot claim to be somebody else.",
|
|
}, s.rememberTool)
|
|
mcp.AddTool(server, &mcp.Tool{
|
|
Name: "supersede_memory",
|
|
Title: "Supersede a memory",
|
|
Description: "Replace a memory with a newer statement. The old one stops being retrievable but keeps its record, so the timeline stays readable.",
|
|
}, s.supersedeTool)
|
|
mcp.AddTool(server, &mcp.Tool{
|
|
Name: "forget_memory",
|
|
Title: "Forget a memory",
|
|
Description: "Delete a memory by atom_id or source_id and drop the derived indexes that would keep surfacing it.",
|
|
}, s.forgetTool)
|
|
}
|
|
|
|
func (s *Server) rememberTool(ctx context.Context, _ *mcp.CallToolRequest, input RememberInput) (*mcp.CallToolResult, RememberOutput, error) {
|
|
if input.Text == "" {
|
|
return nil, RememberOutput{}, errors.New("text is required")
|
|
}
|
|
var atom domain.Memory
|
|
if err := s.api.Post(ctx, "/api/memory", input, &atom); err != nil {
|
|
return nil, RememberOutput{}, err
|
|
}
|
|
return nil, RememberOutput{Atom: atom}, nil
|
|
}
|
|
|
|
func (s *Server) supersedeTool(ctx context.Context, _ *mcp.CallToolRequest, input SupersedeInput) (*mcp.CallToolResult, RememberOutput, error) {
|
|
if input.AtomID == "" {
|
|
return nil, RememberOutput{}, errors.New("atom_id is required")
|
|
}
|
|
if input.Text == "" {
|
|
return nil, RememberOutput{}, errors.New("text is required")
|
|
}
|
|
var atom domain.Memory
|
|
if err := s.api.Post(ctx, "/api/memory/supersede", input, &atom); err != nil {
|
|
return nil, RememberOutput{}, err
|
|
}
|
|
return nil, RememberOutput{Atom: atom}, nil
|
|
}
|
|
|
|
func (s *Server) forgetTool(ctx context.Context, _ *mcp.CallToolRequest, input ForgetInput) (*mcp.CallToolResult, ForgetOutput, error) {
|
|
if (input.AtomID == "") == (input.SourceID == "") {
|
|
return nil, ForgetOutput{}, errors.New("forget takes exactly one of atom_id or source_id")
|
|
}
|
|
var report domain.ForgetReport
|
|
if err := s.api.Post(ctx, "/api/memory/forget", input, &report); err != nil {
|
|
return nil, ForgetOutput{}, err
|
|
}
|
|
return nil, ForgetOutput{Report: report}, nil
|
|
}
|