/**
* Page and block tool definitions.
* @module
*/
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
export const pageTools: Tool[] = [
{
name: "search_logseq",
description: "Search for pages and blocks in Logseq matching a query string",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "Search query to find pages and blocks",
},
},
required: ["query"],
},
},
{
name: "get_page",
description: "Get a Logseq page by name, including its content as plain text",
inputSchema: {
type: "object",
properties: {
pageName: {
type: "string",
description: "Name of the page to retrieve",
},
},
required: ["pageName"],
},
},
{
name: "get_pages",
description:
"Get multiple Logseq pages in a single batch operation. " +
"More efficient than calling get_page multiple times. " +
"Returns existence status, content, and parsed properties for each page. " +
"Missing pages are returned with exists=false rather than throwing an error.",
inputSchema: {
type: "object",
properties: {
pageNames: {
type: "array",
items: { type: "string" },
description: "Array of page names to retrieve",
},
},
required: ["pageNames"],
},
},
{
name: "get_page_with_context",
description:
"Get a page with its full context including backlinks and forward links. " +
"Combines page content, properties, backlinks (pages linking TO this page), " +
"and forward links (pages this page links TO) in a single call. " +
"Useful for understanding a page's role and relationships in the graph.",
inputSchema: {
type: "object",
properties: {
pageName: {
type: "string",
description: "Name of the page to retrieve with context",
},
},
required: ["pageName"],
},
},
{
name: "list_pages",
description: "List all pages in the current Logseq graph",
inputSchema: {
type: "object",
properties: {},
},
},
{
name: "create_page",
description:
"Create a new page in Logseq with optional structured content. " +
"You can now create a page AND its blocks in a single call using the 'blocks' parameter. " +
"IMPORTANT: Logseq uses an outliner structure where each bullet point is a block. " +
"The 'content' parameter creates the FIRST block (typically properties). " +
"The 'blocks' parameter adds additional structured content with hierarchy.",
inputSchema: {
type: "object",
properties: {
pageName: {
type: "string",
description: "Name of the page to create",
},
content: {
type: "string",
description:
"Initial content for the FIRST block on the page. Typically page properties. " +
"IMPORTANT: If using 'alias::', it MUST be the first property listed. " +
"Example: 'alias:: AML\\ntype:: #Team\\nmanager:: [[John Doe]]'. " +
"Do not include markdown lists or multiple headings.",
},
blocks: {
type: "array",
description:
"Optional array of blocks to add after the initial content. " +
"Each block can have nested children. This eliminates the need to call create_blocks separately. " +
"Example: [{content: '## Overview', children: [{content: 'Description'}]}, {content: '## Links'}]",
items: {
type: "object",
properties: {
content: {
type: "string",
description:
"Content for this block (one logical unit - one heading, paragraph, or list item)",
},
children: {
type: "array",
description: "Child blocks nested under this block",
},
},
required: ["content"],
},
},
},
required: ["pageName"],
},
},
{
name: "delete_page",
description: "Delete a page from Logseq",
inputSchema: {
type: "object",
properties: {
pageName: {
type: "string",
description: "Name of the page to delete",
},
},
required: ["pageName"],
},
},
{
name: "create_block",
description:
"Create a SINGLE block in Logseq. For multiple blocks, use create_blocks instead - it's more efficient. " +
"IMPORTANT: In Logseq, each block is a single bullet point in an outliner. " +
"A block should contain ONE logical unit of content. " +
"Do NOT put multiple markdown headings or unordered lists in a single block - they won't render correctly. " +
"Instead, create a heading block (e.g., '## Section Name') and then create child blocks under it using parentBlockUuid. " +
"For lists, each list item should be a separate child block.",
inputSchema: {
type: "object",
properties: {
content: {
type: "string",
description:
"Content of the block. Should be a single logical unit - one heading, one paragraph, or one list item. " +
"Do NOT include multiple headings or bullet point lists within a single block.",
},
pageName: {
type: "string",
description:
"Name of the page to append the block to as a top-level block (use this OR parentBlockUuid)",
},
parentBlockUuid: {
type: "string",
description:
"UUID of the parent block to nest under as a child (use this OR pageName). " +
"Use this to create hierarchical content - e.g., list items under a heading block.",
},
},
required: ["content"],
},
},
{
name: "update_block",
description:
"Update an existing block's content. " +
"IMPORTANT: Same rules as create_block apply - content should be a single logical unit. " +
"Do NOT include multiple headings or unordered lists in the content.",
inputSchema: {
type: "object",
properties: {
uuid: {
type: "string",
description: "UUID of the block to update",
},
content: {
type: "string",
description:
"New content for the block. Should be a single logical unit - one heading, one paragraph, or one list item.",
},
},
required: ["uuid", "content"],
},
},
{
name: "delete_block",
description: "Delete a block from Logseq",
inputSchema: {
type: "object",
properties: {
uuid: {
type: "string",
description: "UUID of the block to delete",
},
},
required: ["uuid"],
},
},
{
name: "query_logseq",
description: "Run a Datalog query against the Logseq database. Use this for advanced queries.",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description:
"Datalog query string (e.g., '[:find (pull ?b [*]) :where [?b :block/marker \"TODO\"]]')",
},
},
required: ["query"],
},
},
{
name: "get_current_graph",
description: "Get information about the currently open Logseq graph",
inputSchema: {
type: "object",
properties: {},
},
},
{
name: "get_graph_stats",
description:
"Get statistics about the current Logseq graph including total pages, " +
"pages by type, orphan pages (no incoming links), and missing pages (referenced but don't exist). " +
"Useful for understanding graph health and finding areas that need attention.",
inputSchema: {
type: "object",
properties: {},
},
},
{
name: "find_missing_pages",
description:
"Find pages that are referenced via [[links]] but don't actually exist. " +
"Returns missing pages sorted by reference count, along with which pages reference them. " +
"Useful for identifying broken links and pages that should be created.",
inputSchema: {
type: "object",
properties: {
minReferences: {
type: "number",
description: "Minimum number of references to include a page (default: 1)",
},
},
},
},
{
name: "find_pages_by_properties",
description:
"Find pages by their properties. Can filter by specific property values " +
"and/or find pages that are missing certain properties. " +
"Useful for finding incomplete pages that need enrichment (e.g., person pages missing job-title).",
inputSchema: {
type: "object",
properties: {
properties: {
type: "object",
description:
"Properties to match (key-value pairs). Example: { type: '#person', team: '[[MLOps]]' }",
additionalProperties: { type: "string" },
},
missingProperties: {
type: "array",
items: { type: "string" },
description:
"Properties that must be MISSING from the page. Example: ['job-title', 'email']",
},
limit: {
type: "number",
description: "Maximum number of results to return (default: 100)",
},
},
},
},
{
name: "find_orphan_pages",
description:
"Find pages with no incoming links (orphan pages). " +
"These are pages that exist but are not referenced by any other page. " +
"Useful for finding outdated, forgotten, or disconnected content that may need cleanup or linking.",
inputSchema: {
type: "object",
properties: {
type: {
type: "string",
description: "Filter by type property (e.g., '#person', '#Tool')",
},
includeJournals: {
type: "boolean",
description: "Include journal pages in results (default: false)",
},
limit: {
type: "number",
description: "Maximum number of results to return (default: 100)",
},
},
},
},
{
name: "update_page_properties",
description:
"Update properties on an existing page without deleting and recreating it. " +
"Properties are stored in the first block of a page. " +
"By default, merges with existing properties (keeping ones not specified). " +
"Set merge=false to replace all properties. " +
"IMPORTANT: If 'alias' property exists, it will always remain first (Logseq requirement).",
inputSchema: {
type: "object",
properties: {
pageName: {
type: "string",
description: "Name of the page to update",
},
properties: {
type: "object",
description:
"Properties to set or update. Example: { team: '[[New Team]]', 'job-title': 'Senior Engineer' }",
additionalProperties: { type: "string" },
},
merge: {
type: "boolean",
description:
"If true (default), merge with existing properties. If false, replace all properties.",
},
},
required: ["pageName", "properties"],
},
},
{
name: "create_blocks",
description:
"Create multiple blocks at once with proper parent/child hierarchy. " +
"This is MORE EFFICIENT than calling create_block multiple times - use this for structured content. " +
"Each block in the array becomes a top-level block on the page, and each block can have nested children. " +
"IMPORTANT: Each block's content should still follow Logseq rules - " +
"one logical unit per block (one heading, one paragraph, one list item). " +
"Example structure: [{content: '## Section', children: [{content: 'Point 1'}, {content: 'Point 2'}]}]",
inputSchema: {
type: "object",
properties: {
pageName: {
type: "string",
description: "Name of the page to add blocks to (page must already exist)",
},
blocks: {
type: "array",
description: "Array of blocks to create. Each block can have nested children.",
items: {
type: "object",
properties: {
content: {
type: "string",
description:
"Content for this block (one logical unit - one heading, paragraph, or list item)",
},
children: {
type: "array",
description: "Child blocks nested under this block",
},
},
required: ["content"],
},
},
},
required: ["pageName", "blocks"],
},
},
];