The product path pulled every atom into Python and rebuilt a BM25Okapi
index per query: p50 20.16s over 165,973 atoms. It now runs one SQLite
statement with LIMIT/OFFSET.
- text_index.py: one normalizer for both sides of the index. CJK runs are
split per character (unicode61 cannot segment them), camelCase and
separators are split so "search atoms" finds SearchAtoms, latin tokens
get a prefix match. Rows written before this existed hold raw text in
atom_fts, so schema v3 rebuilds the index on upgrade.
- retrieval.py: filters (holder / scenario / source / modality / since /
until / min_trust), a base64 cursor, and reciprocal rank fusion in place
of the 0.55/0.35/0.10 blend whose graph term was always zero.
- An empty result is {status: "no_match"}, not a bare array.
- Go: search_atoms and compile_context rank through POST /api/search. The
second Go tokenizer is deleted; two implementations could drift, which
is what the MCP/HTTP parity requirement forbids.
- db.py passed 500 lines, so the row codec moved to row_codec.py.
- Tooling: installed the repo's own pyright. pyrightconfig.json had no
exclude and swept the downloaded corpus (1171 errors); with excludes the
count is 62, all pre-existing (evals 48, profiles 4, training 4,
ingestion 3, runtime 2, cartridge_assets 1). backend/pyproject.toml
carried a second [tool.pyright] that shadowed the root config; deleted.
- bench/scale_check.py measures the product search at corpus size. The
100k-atom p50 number is not written until it is measured on an idle
machine.
Verification: 112 pytest passed; go build/vet/test clean, gofmt clean;
ruff clean on every touched file; mypy clean on the 14 touched Python
files; repo pyright 62 errors, unchanged per-file ledger.
222 lines
8.6 KiB
Go
222 lines
8.6 KiB
Go
package mcpserver
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/ZacharyZhang-NY/ely-eye/ely-eye-mcp/internal/apiclient"
|
|
"github.com/ZacharyZhang-NY/ely-eye/ely-eye-mcp/internal/contextpack"
|
|
"github.com/ZacharyZhang-NY/ely-eye/ely-eye-mcp/internal/domain"
|
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
|
)
|
|
|
|
type EmptyInput struct{}
|
|
|
|
type ListCartridgesInput struct {
|
|
Limit int `json:"limit,omitempty" jsonschema:"maximum number of cartridges to return, capped at 200"`
|
|
}
|
|
|
|
type GetCartridgeInput struct {
|
|
CartridgeID string `json:"cartridge_id,omitempty" jsonschema:"exact Ely-Eye cartridge id"`
|
|
Name string `json:"name,omitempty" jsonschema:"exact Ely-Eye cartridge name"`
|
|
}
|
|
|
|
type SearchAtomsInput struct {
|
|
Query string `json:"query" jsonschema:"search query, matched by the server's own ranking"`
|
|
Modality string `json:"modality,omitempty" jsonschema:"optional exact modality filter such as text, code, image, pdf_page, pdf_region, video_frame, ui_screenshot"`
|
|
Holder string `json:"holder,omitempty" jsonschema:"optional: only this holder's memories may answer"`
|
|
Scenario string `json:"scenario,omitempty" jsonschema:"optional: only this scenario, e.g. home or work"`
|
|
Limit int `json:"limit,omitempty" jsonschema:"maximum number of atoms to return, capped at 100"`
|
|
}
|
|
|
|
type FetchAtomInput struct {
|
|
AtomID string `json:"atom_id" jsonschema:"exact Evidence Atom id"`
|
|
}
|
|
|
|
type CompileContextInput struct {
|
|
Question string `json:"question" jsonschema:"question to compile into an Ely-Eye evidence pack"`
|
|
Profile string `json:"profile,omitempty" jsonschema:"live_demo, extreme_context, library_100m, or research_theater"`
|
|
Limit int `json:"limit,omitempty" jsonschema:"maximum retrieval hits to pack"`
|
|
TokenBudget int64 `json:"token_budget,omitempty" jsonschema:"explicit token budget for the evidence pack"`
|
|
}
|
|
|
|
type ListProofSuitesInput struct {
|
|
Limit int `json:"limit,omitempty" jsonschema:"maximum proof suites to return, capped at 100"`
|
|
IncludePayload bool `json:"include_payload,omitempty" jsonschema:"include full proof_suite.json payloads"`
|
|
}
|
|
|
|
type ListHashHopProofsInput struct {
|
|
Kind string `json:"kind,omitempty" jsonschema:"optional filter: text_hashhop or visual_hashhop"`
|
|
Limit int `json:"limit,omitempty" jsonschema:"maximum example proofs to include, capped at 200"`
|
|
}
|
|
|
|
type GetProofSuiteInput struct {
|
|
ProofID string `json:"proof_id" jsonschema:"exact proof suite id, for example prd_proof_suite_6bc3fa744c60a0e6"`
|
|
}
|
|
|
|
type StatusOutput struct {
|
|
Status domain.Status `json:"status"`
|
|
}
|
|
|
|
type ListCartridgesOutput struct {
|
|
Cartridges []domain.Cartridge `json:"cartridges"`
|
|
}
|
|
|
|
type GetCartridgeOutput struct {
|
|
Cartridge domain.CartridgeDetail `json:"cartridge"`
|
|
}
|
|
|
|
type SearchAtomsOutput struct {
|
|
Hits []domain.AtomSearchHit `json:"hits"`
|
|
}
|
|
|
|
type FetchAtomOutput struct {
|
|
Atom domain.Atom `json:"atom"`
|
|
}
|
|
|
|
type CompileContextOutput struct {
|
|
Context domain.EvidencePack `json:"context"`
|
|
}
|
|
|
|
type ListProofSuitesOutput struct {
|
|
ProofSuites []domain.ProofSuite `json:"proof_suites"`
|
|
}
|
|
|
|
type ListHashHopProofsOutput struct {
|
|
Report domain.HashHopReport `json:"report"`
|
|
}
|
|
|
|
type GetProofSuiteOutput struct {
|
|
ProofSuite domain.ProofSuiteDetail `json:"proof_suite"`
|
|
}
|
|
|
|
func (s *Server) registerTools(server *mcp.Server) {
|
|
mcp.AddTool(server, &mcp.Tool{
|
|
Name: "ely_eye_status",
|
|
Title: "Ely-Eye status",
|
|
Description: "Return read-only counts and cache layer status from the local Ely-Eye database.",
|
|
}, s.statusTool)
|
|
mcp.AddTool(server, &mcp.Tool{
|
|
Name: "list_cartridges",
|
|
Title: "List Context Cartridges",
|
|
Description: "List local Ely-Eye Context Cartridges with DNA, token-equivalent capacity, and manifest JSON.",
|
|
}, s.listCartridgesTool)
|
|
mcp.AddTool(server, &mcp.Tool{
|
|
Name: "get_cartridge",
|
|
Title: "Get Context Cartridge",
|
|
Description: "Read one local Ely-Eye Context Cartridge, including artifacts, memory capsule index, and asset report when present.",
|
|
}, s.getCartridgeTool)
|
|
mcp.AddTool(server, &mcp.Tool{
|
|
Name: "search_atoms",
|
|
Title: "Search Evidence Atoms",
|
|
Description: "Search the local Ely-Eye FTS index and return ranked Evidence Atoms.",
|
|
}, s.searchAtomsTool)
|
|
mcp.AddTool(server, &mcp.Tool{
|
|
Name: "fetch_atom",
|
|
Title: "Fetch Evidence Atom",
|
|
Description: "Read one Evidence Atom by exact atom id.",
|
|
}, s.fetchAtomTool)
|
|
mcp.AddTool(server, &mcp.Tool{
|
|
Name: "compile_context",
|
|
Title: "Compile Evidence Context",
|
|
Description: "Compile a question into an Ely-Eye evidence pack using local retrieval and the same profile names as the app.",
|
|
}, s.compileContextTool)
|
|
mcp.AddTool(server, &mcp.Tool{
|
|
Name: "list_proof_suites",
|
|
Title: "List Proof Suites",
|
|
Description: "List local PRD proof suites from .ely_eye/data/eval_proofs.",
|
|
}, s.listProofSuitesTool)
|
|
mcp.AddTool(server, &mcp.Tool{
|
|
Name: "list_hashhop_proofs",
|
|
Title: "List HashHop Proofs",
|
|
Description: "Aggregate local HashHop and Visual HashHop long-context addressing proofs by kind, hop count, and token budget, with pass counts and example proofs.",
|
|
}, s.listHashHopProofsTool)
|
|
mcp.AddTool(server, &mcp.Tool{
|
|
Name: "get_proof_suite",
|
|
Title: "Get Proof Suite",
|
|
Description: "Read one local PRD proof suite by id, including all checks and a summary of its HashHop proofs.",
|
|
}, s.getProofSuiteTool)
|
|
}
|
|
|
|
func (s *Server) statusTool(ctx context.Context, _ *mcp.CallToolRequest, _ EmptyInput) (*mcp.CallToolResult, StatusOutput, error) {
|
|
status, err := s.store.Status(ctx)
|
|
if err != nil {
|
|
return nil, StatusOutput{}, err
|
|
}
|
|
return nil, StatusOutput{Status: status}, nil
|
|
}
|
|
|
|
func (s *Server) listCartridgesTool(ctx context.Context, _ *mcp.CallToolRequest, input ListCartridgesInput) (*mcp.CallToolResult, ListCartridgesOutput, error) {
|
|
cartridges, err := s.store.ListCartridges(ctx, input.Limit)
|
|
if err != nil {
|
|
return nil, ListCartridgesOutput{}, err
|
|
}
|
|
return nil, ListCartridgesOutput{Cartridges: cartridges}, nil
|
|
}
|
|
|
|
func (s *Server) getCartridgeTool(ctx context.Context, _ *mcp.CallToolRequest, input GetCartridgeInput) (*mcp.CallToolResult, GetCartridgeOutput, error) {
|
|
cartridge, err := s.store.GetCartridge(ctx, input.CartridgeID, input.Name)
|
|
if err != nil {
|
|
return nil, GetCartridgeOutput{}, err
|
|
}
|
|
return nil, GetCartridgeOutput{Cartridge: cartridge}, nil
|
|
}
|
|
|
|
func (s *Server) searchAtomsTool(ctx context.Context, _ *mcp.CallToolRequest, input SearchAtomsInput) (*mcp.CallToolResult, SearchAtomsOutput, error) {
|
|
result, err := s.api.Search(ctx, apiclient.SearchOptions{
|
|
Query: input.Query,
|
|
Modality: input.Modality,
|
|
Holder: input.Holder,
|
|
Scenario: input.Scenario,
|
|
Limit: input.Limit,
|
|
})
|
|
if err != nil {
|
|
return nil, SearchAtomsOutput{}, err
|
|
}
|
|
return nil, SearchAtomsOutput{Hits: contextpack.SummarizeHits(result.Hits, 1200)}, nil
|
|
}
|
|
|
|
func (s *Server) fetchAtomTool(ctx context.Context, _ *mcp.CallToolRequest, input FetchAtomInput) (*mcp.CallToolResult, FetchAtomOutput, error) {
|
|
atom, err := s.store.FetchAtom(ctx, input.AtomID)
|
|
if err != nil {
|
|
return nil, FetchAtomOutput{}, err
|
|
}
|
|
return nil, FetchAtomOutput{Atom: atom}, nil
|
|
}
|
|
|
|
func (s *Server) compileContextTool(ctx context.Context, _ *mcp.CallToolRequest, input CompileContextInput) (*mcp.CallToolResult, CompileContextOutput, error) {
|
|
if input.Question == "" {
|
|
return nil, CompileContextOutput{}, errors.New("question is required")
|
|
}
|
|
hits, err := s.api.Search(ctx, apiclient.SearchOptions{Query: input.Question, Limit: input.Limit})
|
|
if err != nil {
|
|
return nil, CompileContextOutput{}, err
|
|
}
|
|
pack := contextpack.Build(input.Question, input.Profile, hits.Hits, input.TokenBudget)
|
|
return nil, CompileContextOutput{Context: pack}, nil
|
|
}
|
|
|
|
func (s *Server) listProofSuitesTool(ctx context.Context, _ *mcp.CallToolRequest, input ListProofSuitesInput) (*mcp.CallToolResult, ListProofSuitesOutput, error) {
|
|
proofs, err := s.store.ListProofSuites(ctx, input.Limit, input.IncludePayload)
|
|
if err != nil {
|
|
return nil, ListProofSuitesOutput{}, err
|
|
}
|
|
return nil, ListProofSuitesOutput{ProofSuites: proofs}, nil
|
|
}
|
|
|
|
func (s *Server) listHashHopProofsTool(ctx context.Context, _ *mcp.CallToolRequest, input ListHashHopProofsInput) (*mcp.CallToolResult, ListHashHopProofsOutput, error) {
|
|
report, err := s.store.ListHashHopProofs(ctx, input.Kind, input.Limit)
|
|
if err != nil {
|
|
return nil, ListHashHopProofsOutput{}, err
|
|
}
|
|
return nil, ListHashHopProofsOutput{Report: report}, nil
|
|
}
|
|
|
|
func (s *Server) getProofSuiteTool(ctx context.Context, _ *mcp.CallToolRequest, input GetProofSuiteInput) (*mcp.CallToolResult, GetProofSuiteOutput, error) {
|
|
suite, err := s.store.GetProofSuite(ctx, input.ProofID)
|
|
if err != nil {
|
|
return nil, GetProofSuiteOutput{}, err
|
|
}
|
|
return nil, GetProofSuiteOutput{ProofSuite: suite}, nil
|
|
}
|