From 0e1d821fb84310feff40b90d899403d45bced596 Mon Sep 17 00:00:00 2001 From: boojack Date: Wed, 1 Jul 2026 22:15:58 +0800 Subject: [PATCH] feat(mcp): expose create_attachment tool Add AttachmentService_CreateAttachment to the curated MCP allowlist so agents can upload files (inline base64 content) alongside memos, closing the gap where the MCP server could list/get/delete attachments but not create them. Closes #6057 --- server/router/mcp/README.md | 1 + server/router/mcp/catalog.go | 1 + server/router/mcp/catalog_test.go | 38 ++++++++++++++++++++++++++++++- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/server/router/mcp/README.md b/server/router/mcp/README.md index 130018f8..43f2f080 100644 --- a/server/router/mcp/README.md +++ b/server/router/mcp/README.md @@ -154,6 +154,7 @@ resolve its own user — the single allowed auth/identity operation): | `MemoService_ListMemoRelations` | `memo_list_memo_relations` | | `MemoService_SetMemoRelations` | `memo_set_memo_relations` | | `AttachmentService_ListAttachments` | `attachment_list_attachments` | +| `AttachmentService_CreateAttachment` | `attachment_create_attachment` | | `AttachmentService_GetAttachment` | `attachment_get_attachment` | | `AttachmentService_DeleteAttachment` | `attachment_delete_attachment` | | `ShortcutService_ListShortcuts` | `shortcut_list_shortcuts` | diff --git a/server/router/mcp/catalog.go b/server/router/mcp/catalog.go index 44e1caaa..bb2c8807 100644 --- a/server/router/mcp/catalog.go +++ b/server/router/mcp/catalog.go @@ -24,6 +24,7 @@ var curatedOperationIDs = []string{ "MemoService_ListMemoRelations", "MemoService_SetMemoRelations", "AttachmentService_ListAttachments", + "AttachmentService_CreateAttachment", "AttachmentService_GetAttachment", "AttachmentService_DeleteAttachment", "ShortcutService_ListShortcuts", diff --git a/server/router/mcp/catalog_test.go b/server/router/mcp/catalog_test.go index 1b36b112..f4afb12a 100644 --- a/server/router/mcp/catalog_test.go +++ b/server/router/mcp/catalog_test.go @@ -9,7 +9,7 @@ import ( ) func TestCuratedOperationIDsStayMemoFocused(t *testing.T) { - require.Len(t, curatedOperationIDs, 19) + require.Len(t, curatedOperationIDs, 20) for _, operationID := range curatedOperationIDs { require.NotContains(t, operationID, "Admin") @@ -104,6 +104,42 @@ func TestBuildToolFromOperationIncludesRequestBodySchema(t *testing.T) { require.NoError(t, err) } +func TestBuildToolFromOperationExposesCreateAttachment(t *testing.T) { + spec, err := loadOpenAPISpec("../../../proto/gen/openapi.yaml") + require.NoError(t, err) + registry, err := buildOperationRegistry(spec) + require.NoError(t, err) + + tool, operation := buildToolFromOperation(registry["AttachmentService_CreateAttachment"]) + require.Equal(t, "attachment_create_attachment", tool.Name) + require.Equal(t, "POST", operation.Method) + require.False(t, tool.Annotations.ReadOnlyHint) + require.False(t, *tool.Annotations.DestructiveHint) + require.False(t, tool.Annotations.IdempotentHint) + + input, ok := tool.InputSchema.(jsonSchema) + require.True(t, ok) + require.Contains(t, input["required"], "body") + properties, ok := input["properties"].(map[string]any) + require.True(t, ok) + // attachmentId is an optional query parameter; the file itself is the body. + require.Contains(t, properties, "attachmentId") + require.Contains(t, properties, "body") + body, ok := properties["body"].(jsonSchema) + require.True(t, ok) + require.Contains(t, body["properties"], "filename") + require.Contains(t, body["properties"], "content") + + err = validateToolArguments(input, map[string]any{ + "body": map[string]any{ + "filename": "screenshot.png", + "type": "image/png", + "content": "aGVsbG8=", + }, + }) + require.NoError(t, err) +} + func TestBuildToolFromOperationExposesCurrentUser(t *testing.T) { spec, err := loadOpenAPISpec("../../../proto/gen/openapi.yaml") require.NoError(t, err)