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
86 lines
2.4 KiB
Go
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()
|
|
}
|