Files
memos/server/router/mcp/resources_memo.go
gugus bb402d4ccc
Some checks failed
Backend Tests / Static Checks (push) Has been cancelled
Backend Tests / Tests (other) (push) Has been cancelled
Backend Tests / Tests (plugin) (push) Has been cancelled
Backend Tests / Tests (server) (push) Has been cancelled
Backend Tests / Tests (store) (push) Has been cancelled
Build Canary Image / build-frontend (push) Has been cancelled
Build Canary Image / build-push (linux/amd64) (push) Has been cancelled
Build Canary Image / build-push (linux/arm64) (push) Has been cancelled
Build Canary Image / merge (push) Has been cancelled
Frontend Tests / Lint (push) Has been cancelled
Frontend Tests / Build (push) Has been cancelled
Proto Linter / Lint Protos (push) Has been cancelled
first commit
2026-03-04 06:30:47 +00:00

86 lines
2.4 KiB
Go

package mcp
import (
"context"
"fmt"
"strings"
"github.com/mark3labs/mcp-go/mcp"
mcpserver "github.com/mark3labs/mcp-go/server"
"github.com/pkg/errors"
"github.com/usememos/memos/server/auth"
"github.com/usememos/memos/store"
)
// Memo resource URI scheme: memo://memos/{uid}
// Clients can read any memo they have access to by URI without calling a tool.
func (s *MCPService) registerMemoResources(mcpSrv *mcpserver.MCPServer) {
mcpSrv.AddResourceTemplate(
mcp.NewResourceTemplate(
"memo://memos/{uid}",
"Memo",
mcp.WithTemplateDescription("A single Memos note identified by its UID. Returns the memo content as Markdown with a YAML frontmatter header containing metadata."),
mcp.WithTemplateMIMEType("text/markdown"),
),
s.handleReadMemoResource,
)
}
func (s *MCPService) handleReadMemoResource(ctx context.Context, req mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
userID := auth.GetUserID(ctx)
// URI format: memo://memos/{uid}
uid := strings.TrimPrefix(req.Params.URI, "memo://memos/")
if uid == req.Params.URI || uid == "" {
return nil, errors.Errorf("invalid memo URI %q: expected memo://memos/<uid>", req.Params.URI)
}
memo, err := s.store.GetMemo(ctx, &store.FindMemo{UID: &uid})
if err != nil {
return nil, errors.Wrap(err, "failed to get memo")
}
if memo == nil {
return nil, errors.Errorf("memo not found: %s", uid)
}
if err := checkMemoAccess(memo, userID); err != nil {
return nil, err
}
j := storeMemoToJSON(memo)
text := formatMemoMarkdown(j)
return []mcp.ResourceContents{
mcp.TextResourceContents{
URI: req.Params.URI,
MIMEType: "text/markdown",
Text: text,
},
}, nil
}
// formatMemoMarkdown renders a memo as Markdown with a YAML frontmatter header.
func formatMemoMarkdown(j memoJSON) string {
var sb strings.Builder
sb.WriteString("---\n")
fmt.Fprintf(&sb, "name: %s\n", j.Name)
fmt.Fprintf(&sb, "creator: %s\n", j.Creator)
fmt.Fprintf(&sb, "visibility: %s\n", j.Visibility)
fmt.Fprintf(&sb, "state: %s\n", j.State)
fmt.Fprintf(&sb, "pinned: %v\n", j.Pinned)
if len(j.Tags) > 0 {
fmt.Fprintf(&sb, "tags: [%s]\n", strings.Join(j.Tags, ", "))
}
fmt.Fprintf(&sb, "create_time: %d\n", j.CreateTime)
fmt.Fprintf(&sb, "update_time: %d\n", j.UpdateTime)
if j.Parent != "" {
fmt.Fprintf(&sb, "parent: %s\n", j.Parent)
}
sb.WriteString("---\n\n")
sb.WriteString(j.Content)
return sb.String()
}