Files
cyi-box/backend/internal/service/memory_routes.go
T
chiyiandDevin 834ecacdb9 cyi-box: Memphis-style toolbox admin platform
Go(fun)+SQLite backend, Vue 3 frontend. Modules: dashboard, pay gateway
(Epay-compatible + CDK stock), license/card system (per-app appid, domain
binding), AI memory bank with built-in MCP server (/mcp, FTS5 search,
signed read links), server SSH monitor + terminal + SFTP, proxy pool with
protocol-level alive checks, DB manager (mysql/pg/sqlite + SQL
autocomplete + import/export), disk share, vault (AES-GCM + TOTP),
bookmarks, dev tools.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-01 01:58:47 +08:00

125 lines
3.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// memory_routes.go 记忆库对外协议端点:
// POST /mcp Streamable HTTP MCP(Bearer 令牌鉴权,令牌见 MemorySvc.McpInfo)
// GET /m 阅读链接索引(Bearer 鉴权——key 可枚举,不能裸奔)
// GET /m/* 记忆阅读链接(?sig= 签名鉴权,HMAC(key, 令牌) 签发,令牌重置即吊销)
package service
import (
"context"
"crypto/subtle"
"net/http"
"github.com/valyala/fasthttp"
"github.com/cyi-cc/fun"
"github.com/mark3labs/mcp-go/server"
"github.com/valyala/fasthttp/fasthttpadaptor"
"github.com/cyi-cc/cyi-box/backend/internal/memx"
"github.com/cyi-cc/cyi-box/backend/internal/store"
)
// requestBase 按当前请求 Host + X-Forwarded-Proto 推导站点基址,
// 反代换域名后生成的 /m/ 链接与 MCP 接入地址始终可点。
func requestBase(ctx *fasthttp.RequestCtx) string {
proto := string(ctx.Request.Header.Peek("X-Forwarded-Proto"))
if proto == "" {
proto = "http"
}
return proto + "://" + string(ctx.Host())
}
// McpHandler POST /mcp:Authorization Bearer 校验后交给 Streamable HTTP 服务。
// 令牌存 settings(mcp.token),首次访问懒生成。
func McpHandler(st *store.Store) fun.RouteHandler {
mcpSrv := server.NewStreamableHTTPServer(
memx.NewMCPServer(st),
server.WithStateLess(true),
server.WithHTTPContextFunc(func(ctx context.Context, r *http.Request) context.Context {
proto := r.Header.Get("X-Forwarded-Proto")
if proto == "" {
proto = "http"
if r.TLS != nil {
proto = "https"
}
}
return memx.WithBase(ctx, proto+"://"+r.Host)
}),
)
h := fasthttpadaptor.NewFastHTTPHandler(mcpSrv)
return func(c *fun.RouteCtx) error {
tok, err := st.McpToken()
if err != nil {
c.RequestCtx.SetStatusCode(500)
c.RequestCtx.WriteString(err.Error())
return nil
}
auth := string(c.RequestCtx.Request.Header.Peek("Authorization"))
if subtle.ConstantTimeCompare([]byte(auth), []byte("Bearer "+tok)) != 1 {
c.RequestCtx.SetStatusCode(401)
c.RequestCtx.WriteString("unauthorized")
return nil
}
h(c.RequestCtx)
return nil
}
}
// MemoryReadHandler GET /m/*:输出 markdown 原文;裸 /m/ 列出全部阅读链接。
func MemoryReadHandler(st *store.Store) fun.RouteHandler {
return func(c *fun.RouteCtx) error {
key := c.Wildcard
base := requestBase(c.RequestCtx)
if key == "" {
// 索引收进 Bearer 鉴权:key 形如 project/topic 可枚举,裸索引等于全库暴露
tok, err := st.McpToken()
if err != nil {
c.RequestCtx.SetStatusCode(500)
c.RequestCtx.WriteString(err.Error())
return nil
}
auth := string(c.RequestCtx.Request.Header.Peek("Authorization"))
if subtle.ConstantTimeCompare([]byte(auth), []byte("Bearer "+tok)) != 1 {
c.RequestCtx.SetStatusCode(401)
c.RequestCtx.WriteString("unauthorized")
return nil
}
ms, err := st.ListMemories("", "", 200)
if err != nil {
c.RequestCtx.SetStatusCode(500)
c.RequestCtx.WriteString(err.Error())
return nil
}
c.RequestCtx.SetContentType("text/plain; charset=utf-8")
for _, m := range ms {
readURL, _ := st.SignMemoryReadURL(base, m.Key)
line := readURL
if m.Project != "" {
line += "\t[" + m.Project + "]"
}
c.RequestCtx.WriteString(line + "\n")
}
return nil
}
if !st.CheckMemoryReadSig(key, string(c.RequestCtx.QueryArgs().Peek("sig"))) {
c.RequestCtx.SetStatusCode(403)
c.RequestCtx.WriteString("bad or missing sig")
return nil
}
m, err := st.GetMemory(key)
if err != nil {
c.RequestCtx.SetStatusCode(500)
c.RequestCtx.WriteString(err.Error())
return nil
}
if m == nil {
c.RequestCtx.SetStatusCode(404)
c.RequestCtx.WriteString("not found")
return nil
}
c.RequestCtx.SetContentType("text/markdown; charset=utf-8")
c.RequestCtx.WriteString(m.Content)
return nil
}
}