// Package server builds the MCP server and registers the WikiJS tools. package server import ( "log/slog" "gogs.netdome.biz/paul/go-wikijs-mcp/internal/wikijs" "github.com/modelcontextprotocol/go-sdk/mcp" ) const instructions = `This server exposes a WikiJS knowledge base. Use search_pages to find pages by full-text query, get_page_by_id or get_page_by_path to read a page, and get_all_pages to browse the wiki. Page results include absolute URLs that can be shared with the user. Assets are managed store-wide, not per page: list_asset_folders and list_assets browse the central asset store, and when the write tools are enabled upload_asset adds files and create_asset_folder adds folders. Reference uploaded assets from pages with the returned markdown snippet.` // Options configures the MCP server. type Options struct { // Version is reported to MCP clients. Version string // EnableEdit exposes the create_page and edit_page tools. EnableEdit bool // Logger receives MCP protocol logs. It must write to stderr when the // stdio transport is used. Logger *slog.Logger } // New creates an MCP server with all WikiJS tools registered. func New(client *wikijs.Client, opts Options) *mcp.Server { logger := opts.Logger if logger == nil { logger = slog.New(slog.DiscardHandler) } s := mcp.NewServer(&mcp.Implementation{ Name: "wikijs-mcp-server", Title: "WikiJS MCP Server", Version: opts.Version, Description: "Search, read, and optionally edit pages in a WikiJS instance.", }, &mcp.ServerOptions{ Instructions: instructions, Logger: logger, }) h := &handlers{wiki: client} readOnly := &mcp.ToolAnnotations{ReadOnlyHint: true} mcp.AddTool(s, &mcp.Tool{ Name: "search_pages", Title: "Search pages", Description: "Search for pages in WikiJS by query string.", Annotations: readOnly, }, h.searchPages) mcp.AddTool(s, &mcp.Tool{ Name: "get_page_by_id", Title: "Get page by ID", Description: "Get a WikiJS page by its numeric ID.", Annotations: readOnly, }, h.pageByID) mcp.AddTool(s, &mcp.Tool{ Name: "get_page_by_path", Title: "Get page by path", Description: "Get a WikiJS page by its path and locale.", Annotations: readOnly, }, h.pageByPath) mcp.AddTool(s, &mcp.Tool{ Name: "get_all_pages", Title: "List pages", Description: "List pages in WikiJS, optionally filtered by limit, locale, and tags.", Annotations: readOnly, }, h.listPages) mcp.AddTool(s, &mcp.Tool{ Name: "list_asset_folders", Title: "List asset folders", Description: "List the child folders of a WikiJS asset folder (root by default).", Annotations: readOnly, }, h.listAssetFolders) mcp.AddTool(s, &mcp.Tool{ Name: "list_assets", Title: "List assets", Description: "List the files uploaded to a WikiJS asset folder (root by default), optionally filtered by kind.", Annotations: readOnly, }, h.listAssets) if opts.EnableEdit { notDestructive := false mcp.AddTool(s, &mcp.Tool{ Name: "create_page", Title: "Create page", Description: "Create a new WikiJS page.", Annotations: &mcp.ToolAnnotations{DestructiveHint: ¬Destructive}, }, h.createPage) // edit_page advertises IdempotentHint: editing the same page twice // with the same content converges to the same state, unlike // create_page and upload_asset, which would duplicate work. mcp.AddTool(s, &mcp.Tool{ Name: "edit_page", Title: "Edit page", Description: "Edit an existing WikiJS page by its numeric ID.", Annotations: &mcp.ToolAnnotations{IdempotentHint: true}, }, h.editPage) mcp.AddTool(s, &mcp.Tool{ Name: "upload_asset", Title: "Upload asset", Description: "Upload a file to a WikiJS asset folder so it can be referenced from pages.", Annotations: &mcp.ToolAnnotations{DestructiveHint: ¬Destructive}, }, h.uploadAsset) mcp.AddTool(s, &mcp.Tool{ Name: "create_asset_folder", Title: "Create asset folder", Description: "Create a new WikiJS asset folder.", Annotations: &mcp.ToolAnnotations{DestructiveHint: ¬Destructive}, }, h.createAssetFolder) } return s }