Skip to main content
Glama
index.ts7.01 kB
#!/usr/bin/env node /** * Outline Wiki MCP Server v3.0 * * Full-featured MCP server for Outline Wiki integration. * Built with TypeScript, Zod validation, and Native Fetch. * * Features: * - Document CRUD with move, archive, restore * - Collection management with hierarchy * - Comments and backlinks * - Batch operations * - Rate limiting with retry * - Access control modes * - Zod schema validation */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { z, ZodError } from 'zod'; import { createConfig, formatConfigError, type Config } from './lib/config.js'; import { createAppContext } from './lib/context.js'; import { allTools } from './lib/tools.js'; import { toolSchemas, type ToolName } from './lib/schemas.js'; import { filterToolsByAccess } from './lib/access-control.js'; import { createAllHandlers } from './lib/handlers/index.js'; // ============================================ // Smithery Configuration Schema // ============================================ export const configSchema = z.object({ outlineUrl: z .string() .url() .default('https://app.getoutline.com') .describe('Your Outline Wiki instance URL'), outlineApiToken: z .string() .min(1) .describe('Outline API token (starts with ol_api_)'), readOnly: z .boolean() .default(false) .describe('Enable read-only mode (disable all write operations)'), disableDelete: z .boolean() .default(false) .describe('Disable delete operations while allowing other modifications'), enableSmartFeatures: z .boolean() .default(false) .describe('Enable AI-powered features (requires OpenAI API key)'), openaiApiKey: z .string() .optional() .describe('OpenAI API key for smart features'), }); export type SmitheryConfig = z.infer<typeof configSchema>; // ============================================ // Create Server Function (Smithery Compatible) // ============================================ export default function createServer({ config: smitheryConfig }: { config: SmitheryConfig }) { // Convert Smithery config to environment-style config const envConfig: Record<string, string | undefined> = { OUTLINE_URL: smitheryConfig.outlineUrl, OUTLINE_API_TOKEN: smitheryConfig.outlineApiToken, READ_ONLY: String(smitheryConfig.readOnly), DISABLE_DELETE: String(smitheryConfig.disableDelete), ENABLE_SMART_FEATURES: String(smitheryConfig.enableSmartFeatures), OPENAI_API_KEY: smitheryConfig.openaiApiKey, }; const config = createConfig(envConfig); const ctx = createAppContext(config); const handlers = createAllHandlers(ctx); const server = new Server( { name: 'outline-wiki-server', version: '3.0.0' }, { capabilities: { tools: {} } } ); // List available tools server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: filterToolsByAccess(allTools, config), })); // Execute tool with Zod validation server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { // Get handler const handler = handlers[name]; if (!handler) { throw new Error(`Unknown tool: ${name}`); } // Validate input with Zod schema const schema = toolSchemas[name as ToolName]; if (!schema) { throw new Error(`No schema found for tool: ${name}`); } const validatedArgs = schema.parse(args); // Execute handler const result = await handler(validatedArgs); return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }], }; } catch (error) { let errorMessage = 'Unknown error'; if (error instanceof ZodError) { const issues = error.issues.map( (issue) => ` - ${issue.path.join('.')}: ${issue.message}` ); errorMessage = `Validation Error:\n${issues.join('\n')}`; } else if (error instanceof Error) { errorMessage = error.message; } return { content: [{ type: 'text' as const, text: `Error: ${errorMessage}` }], isError: true, }; } }); return server; } // ============================================ // Standalone CLI Mode // ============================================ async function main() { // Only run in CLI mode (not when imported by Smithery) if (process.env.SMITHERY_RUNTIME) { return; } let config: Config; try { config = createConfig(process.env); } catch (error) { if (error instanceof ZodError) { console.error('Configuration Error:\n' + formatConfigError(error)); } else { console.error('Configuration Error:', error); } process.exit(1); } const ctx = createAppContext(config); const handlers = createAllHandlers(ctx); const server = new Server( { name: 'outline-wiki-server', version: '3.0.0' }, { capabilities: { tools: {} } } ); // List available tools server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: filterToolsByAccess(allTools, config), })); // Execute tool with Zod validation server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { const handler = handlers[name]; if (!handler) { throw new Error(`Unknown tool: ${name}`); } const schema = toolSchemas[name as ToolName]; if (!schema) { throw new Error(`No schema found for tool: ${name}`); } const validatedArgs = schema.parse(args); const result = await handler(validatedArgs); return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }], }; } catch (error) { let errorMessage = 'Unknown error'; if (error instanceof ZodError) { const issues = error.issues.map( (issue) => ` - ${issue.path.join('.')}: ${issue.message}` ); errorMessage = `Validation Error:\n${issues.join('\n')}`; } else if (error instanceof Error) { errorMessage = error.message; } return { content: [{ type: 'text' as const, text: `Error: ${errorMessage}` }], isError: true, }; } }); const transport = new StdioServerTransport(); await server.connect(transport); const toolCount = filterToolsByAccess(allTools, config).length; console.error(`Outline Wiki MCP Server v3.0 running on stdio`); console.error(`Available tools: ${toolCount}`); console.error(`Read-only mode: ${config.READ_ONLY}`); console.error(`Delete disabled: ${config.DISABLE_DELETE}`); console.error(`Smart features: ${config.ENABLE_SMART_FEATURES}`); } main().catch((error) => { console.error('Server error:', error); process.exit(1); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/huiseo/outline-smart-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server