diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..0e8966b --- /dev/null +++ b/src/main.ts @@ -0,0 +1,17 @@ +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { server } from "./outline.js"; + +async function main() { + // Add debugging + console.error("Current working directory:", process.cwd()); + console.error("__dirname:", __dirname); + + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error("Outline MCP server is running via stdio transport"); +} + +main().catch((error) => { + console.error("Server error:", error); + process.exit(1); +}); diff --git a/src/outline.ts b/src/outline.ts new file mode 100644 index 0000000..8211dc0 --- /dev/null +++ b/src/outline.ts @@ -0,0 +1,13 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { registerDocumentTools } from "./tools/document"; +import { registerCollectionTools } from "./tools/collection"; + +// Create an MCP server +export const server = new McpServer({ + name: "outline-mcp-server", + version: "1.0.0", +}); + +// Register tools +registerDocumentTools(server); +registerCollectionTools(server); diff --git a/src/tools/collection.ts b/src/tools/collection.ts new file mode 100644 index 0000000..d49aaec --- /dev/null +++ b/src/tools/collection.ts @@ -0,0 +1,23 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { createOutlineClient, handleSuccess, handleError } from "./utils"; + +// Register collection tools +export const registerCollectionTools = (server: McpServer) => { + server.registerTool( + "collections_list", + { + title: "List Collections", + description: "List all collections in the Outline workspace", + inputSchema: {}, + }, + async () => { + try { + const client = createOutlineClient(); + const response = await client.collections.collectionsList(); + return handleSuccess(response); + } catch (error) { + return handleError(error); + } + } + ); +}; diff --git a/src/tools/document.ts b/src/tools/document.ts new file mode 100644 index 0000000..abc30be --- /dev/null +++ b/src/tools/document.ts @@ -0,0 +1,50 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { createOutlineClient, handleSuccess, handleError } from "./utils"; + +// Register document tools +export const registerDocumentTools = (server: McpServer) => { + server.registerTool( + "documents_list", + { + title: "List Documents", + description: "List all documents in the Outline workspace", + inputSchema: {}, + }, + async () => { + try { + const client = createOutlineClient(); + const response = await client.documents.documentsList(); + return handleSuccess(response); + } catch (error) { + return handleError(error); + } + } + ); + + server.registerTool( + "documents_info", + { + title: "Get Document Info", + description: "Retrieve a specific document by its ID or share ID", + inputSchema: { + id: z.string().optional().describe("Document UUID or URL ID"), + shareId: z.string().optional().describe("Document share ID"), + }, + }, + async (args) => { + try { + const client = createOutlineClient(); + const response = await client.documents.documentsInfo({ + documentsInfoRequest: { + id: args?.id, + shareId: args?.shareId, + }, + }); + return handleSuccess(response); + } catch (error) { + return handleError(error); + } + } + ); +}; diff --git a/src/tools/utils.ts b/src/tools/utils.ts new file mode 100644 index 0000000..1417eda --- /dev/null +++ b/src/tools/utils.ts @@ -0,0 +1,47 @@ +import { Configuration } from "../gen/api/outline/runtime"; +import { DocumentsApi } from "../gen/api/outline/apis/DocumentsApi"; +import { CollectionsApi } from "../gen/api/outline/apis/CollectionsApi"; + +// Generic Outline API client factory +export const createOutlineClient = () => { + const apiKey = process.env.OUTLINE_API_KEY; + if (!apiKey) { + throw new Error("OUTLINE_API_KEY environment variable is not set"); + } + + const config = new Configuration({ + basePath: process.env.OUTLINE_BASE_URL, + accessToken: () => Promise.resolve(apiKey), + }); + + return { + documents: new DocumentsApi(config), + collections: new CollectionsApi(config), + }; +}; + +// Generic response handler +export const handleApiResponse = (response: any) => { + return JSON.stringify(response, null, 2); +}; + +// Generic error response handler +export const handleError = (error: unknown) => { + return { + content: [ + { + type: "text" as const, + text: `Error: ${ + error instanceof Error ? error.message : "Unknown error" + }`, + }, + ], + }; +}; + +// Generic success response handler +export const handleSuccess = (data: any) => { + return { + content: [{ type: "text" as const, text: handleApiResponse(data) }], + }; +};