Skip to content

Commit e8b264a

Browse files
enhance: API tools to add to page, search blocks and
update a block's content. Also fix search not returning usable uuids in response
1 parent 5df5b5a commit e8b264a

File tree

3 files changed

+87
-23
lines changed

3 files changed

+87
-23
lines changed

deps/cli/src/logseq/cli/commands/mcp_server.cljs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,14 @@
2626

2727
(def ^:private local-tools
2828
"MCP Tools when running with a local graph"
29-
(merge-with
30-
merge
31-
cli-common-mcp-server/api-tools
32-
{:getPage {:fn local-get-page}
33-
:listPages {:fn local-list-pages}
34-
:listProperties {:fn local-list-properties}
35-
:listTags {:fn local-list-tags}}))
29+
(let [tools {:getPage {:fn local-get-page}
30+
:listPages {:fn local-list-pages}
31+
:listProperties {:fn local-list-properties}
32+
:listTags {:fn local-list-tags}}]
33+
(merge-with
34+
merge
35+
(select-keys cli-common-mcp-server/api-tools (keys tools))
36+
tools)))
3637

3738
(defn- create-http-server
3839
[mcp-server opts]

deps/cli/src/logseq/cli/common/mcp/server.cljs

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99
;; Server util fns
1010
;; ===============
11-
(def ^:private transports
12-
"Stores transports by session ID"
11+
;; "Stores transports by session ID"
12+
(defonce ^:private transports
1313
(atom {}))
1414

1515
;; See https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http
@@ -85,11 +85,14 @@
8585

8686
(defn- api-tool
8787
"Calls API method w/ args and returns a MCP response"
88-
[api-fn api-method method-args]
88+
[api-fn api-method method-args & {:keys [write-tool?]}]
8989
(-> (p/let [body (api-fn api-method method-args)]
90-
(if-let [error (aget body "error")]
90+
(if-let [error (and body (aget body "error"))]
9191
(mcp-error-response (str "API Error: " error))
92-
(mcp-success-response body)))
92+
(if write-tool?
93+
;; Writes that return have succeeded since writes fail fast if they don't write
94+
(mcp-success-response {:ok true})
95+
(mcp-success-response body))))
9396
(p/catch unexpected-api-error)))
9497

9598
(defn- api-get-page
@@ -108,22 +111,65 @@
108111
[call-api-fn _args]
109112
(call-api-fn "logseq.cli.listProperties" []))
110113

114+
(defn- api-add-to-page
115+
[call-api-fn args]
116+
(call-api-fn "logseq.editor.appendBlockInPage"
117+
[(aget args "pageName")
118+
(aget args "content")
119+
#js {:mcp-options #js {:force (aget args "force")}}]
120+
{:write-tool? true}))
121+
122+
(defn- api-update-block
123+
[call-api-fn args]
124+
(call-api-fn "logseq.editor.updateBlock"
125+
[(aget args "blockUUID")
126+
(aget args "content")
127+
#js {:mcp true}]
128+
{:write-tool? true}))
129+
130+
(defn- api-search-blocks
131+
[call-api-fn args]
132+
(call-api-fn "logseq.app.search" [(aget args "searchTerm") #js {:enable-snippet? false}]))
133+
111134
(def api-tools
112135
"MCP Tools when calling API server"
113136
{:listPages
114137
{:fn api-list-pages
115-
:config #js {:title "List Pages"}}
138+
:config #js {:title "List Pages"
139+
:description "List all pages in a graph"}}
116140
:getPage
117141
{:fn api-get-page
118142
:config #js {:title "Get Page"
119143
:description "Get a page's content including its blocks"
120144
:inputSchema #js {:pageName (z/string)}}}
145+
:addToPage
146+
{:fn api-add-to-page
147+
:config #js {:title "Add to Page"
148+
:description "Add a block to a page"
149+
:inputSchema #js {:pageName (-> (z/string) (.describe "The page's name or uuid"))
150+
:content (-> (z/string) (.describe "Block content"))
151+
:force (-> (z/boolean)
152+
(z/optional)
153+
(.describe "Force given page to be created"))}}}
154+
:updateBlock
155+
{:fn api-update-block
156+
:config #js {:title "Update Block"
157+
:description "Update block with new content"
158+
:inputSchema #js {:blockUUID (z/string)
159+
:content (-> (z/string) (.describe "Block content"))}}}
160+
:searchBlocks
161+
{:fn api-search-blocks
162+
:config #js {:title "Search Blocks"
163+
:description "Search graph for blocks containing search term"
164+
:inputSchema #js {:searchTerm (z/string)}}}
121165
:listTags
122166
{:fn api-list-tags
123-
:config #js {:title "List Tags"}}
167+
:config #js {:title "List Tags"
168+
:description "List all tags in a graph"}}
124169
:listProperties
125170
{:fn api-list-properties
126-
:config #js {:title "List Properties"}}})
171+
:config #js {:title "List Properties"
172+
:description "List all properties in a graph"}}})
127173

128174
(defn call-api-tool [tool-fn api-fn args]
129175
(tool-fn (partial api-tool api-fn) args))

src/main/logseq/api.cljs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -794,18 +794,24 @@
794794
{:block/uuid (sdk-utils/uuid-or-throw-error block-uuid) :repo repo}))))
795795

796796
(def ^:export update_block
797+
"Updates block with 3rd arg being a js map with following keys:
798+
* :mcp - When set behaves as MCP tools expect
799+
* :properties - A map of properties to update
800+
* Remaining keys are passed to save-block!"
797801
(fn [block-uuid content ^js opts]
798802
(p/let [repo (state/get-current-repo)
799803
db-base? (config/db-based-graph? repo)
800804
block (<pull-block block-uuid)
801805
opts (bean/->clj opts)]
802-
(when block
806+
(if block
803807
(p/do!
804808
(when (and db-base? (seq (:properties opts)))
805809
(api-block/save-db-based-block-properties! block (:properties opts)))
806810
(editor-handler/save-block! repo
807811
(sdk-utils/uuid-or-throw-error block-uuid) content
808-
(if db-base? (dissoc opts :properties) opts)))))))
812+
(if db-base? (dissoc opts :properties) opts)))
813+
(when (:mcp opts)
814+
(throw (ex-info (str "Block " (pr-str block-uuid) " not found") {})))))))
809815

810816
(def ^:export move_block
811817
(fn [src-block-uuid target-block-uuid ^js opts]
@@ -1077,23 +1083,34 @@
10771083
(insert_block target content (bean/->js opts)))))))))
10781084

10791085
(defn ^:export append_block_in_page
1086+
"Append a block to a page and creates page if it does not exist.
1087+
If the 'mcp-options' opts key is set, this fn assumes MCP defaults by not creating a page unless explicitly
1088+
forced to. `opts` arg are options passed to `insert_block` except for
1089+
key the 'mcp-options', which has the following keys:
1090+
* :force - When set forces creation of nonexistent page"
10801091
[uuid-or-page-name content ^js opts]
10811092
(let [current-page? (or (and (nil? content) (nil? opts))
10821093
(and (nil? opts) (some->> content (instance? js/Object))))
10831094
opts (if current-page? content opts)
1095+
mcp-options (bean/->clj (aget opts "mcp-options"))
10841096
content (if current-page? uuid-or-page-name content)
10851097
uuid-or-page-name (if current-page?
10861098
(or (state/get-current-page) (date/today))
10871099
uuid-or-page-name)]
10881100
(p/let [_ (<ensure-page-loaded uuid-or-page-name)
10891101
page? (not (util/uuid-string? uuid-or-page-name))
10901102
page-not-exist? (and page? (nil? (db-model/get-page uuid-or-page-name)))
1091-
_ (and page-not-exist? (page-handler/<create! uuid-or-page-name
1092-
{:redirect? false
1093-
:format (state/get-preferred-format)}))]
1094-
(when-let [block (db-model/get-page uuid-or-page-name)]
1103+
_ (when (and page-not-exist?
1104+
(or (nil? mcp-options)
1105+
(:force mcp-options)))
1106+
(page-handler/<create! uuid-or-page-name
1107+
{:redirect? false
1108+
:format (state/get-preferred-format)}))]
1109+
(if-let [block (db-model/get-page uuid-or-page-name)]
10951110
(let [target (str (:block/uuid block))]
1096-
(insert_block target content opts))))))
1111+
(insert_block target content opts))
1112+
(when mcp-options
1113+
(throw (ex-info (str "Page " (pr-str uuid-or-page-name) " not found") {})))))))
10971114

10981115
;; plugins
10991116
(defn ^:export validate_external_plugins [urls]
@@ -1264,7 +1281,7 @@
12641281
(defn ^:export search
12651282
[q' & [opts]]
12661283
(-> (search-handler/search (state/get-current-repo) q' (if opts (js->clj opts :keywordize-keys true) {}))
1267-
(p/then #(bean/->js %))))
1284+
(p/then #(bean/->js (sdk-utils/normalize-keyword-for-json %)))))
12681285

12691286
;; helpers
12701287
(defn ^:export set_focused_settings

0 commit comments

Comments
 (0)