1<?php 2 3namespace dokuwiki\plugin\mcp; 4 5use dokuwiki\Remote\JsonRpcServer; 6 7/** 8 * Implementation of the Model Context Protocol (MCP) server 9 * 10 * This assumes the streaming HTTP transport. It's a thin wrapper around the JsonRpcServer 11 */ 12class McpServer extends JsonRpcServer 13{ 14 /** @inheritdoc */ 15 public function call($methodname, $args) 16 { 17 switch ($methodname) { 18 case 'initialize': 19 return $this->mcpInitialize(); 20 case 'tools/list': 21 return $this->mcpToolsList(); 22 case 'tools/call': 23 return $this->mcpToolsCall($args); 24 case 'ping': 25 case 'notifications/initialized': 26 case 'notifications/cancelled': 27 return $this->mcpNOP($args); 28 default: 29 return parent::call($methodname, $args); 30 } 31 } 32 33 /** 34 * Handle the MCP call `initialize` 35 * 36 * @link https://modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle#initialization 37 * @return array 38 */ 39 protected function mcpInitialize() 40 { 41 global $conf; 42 /** @var \helper_plugin_mcp $helper */ 43 $helper = plugin_load('helper', 'mcp'); 44 $info = $helper->getInfo(); 45 46 return [ 47 "protocolVersion" => "2025-03-26", 48 "capabilities" => [ 49 // FIXME it might be possible to make pages and media available as resources 50 "tools" => ["listChanged" => false] 51 ], 52 "serverInfo" => [ 53 "name" => "DokuWiki MCP", 54 "version" => $info['date'], 55 ], 56 "instructions" => sprintf( 57 "Access and interact with the DokuWiki instance called '%s'.", 58 $conf['title'] 59 ), 60 ]; 61 } 62 63 /** 64 * Handle the MCP call `tools/list` 65 * 66 * @link https://modelcontextprotocol.io/specification/2025-03-26/server/tools#listing-tools 67 * @return array 68 */ 69 protected function mcpToolsList() 70 { 71 return [ 72 "tools" => (new SchemaGenerator())->getTools() 73 ]; 74 } 75 76 /** 77 * Handle the MCP call `tools/call` 78 * 79 * @link https://modelcontextprotocol.io/specification/2025-03-26/server/tools#calling-tools 80 * @param array $args 81 * @return array 82 */ 83 protected function mcpToolsCall($args) 84 { 85 // Some LLMs (e.g. Claude) don't allow underscores in method names, so we replace them with dots. 86 // We have to convert them back to underscores for the actual call. 87 $method = str_replace('_', '.', $args['name']); 88 $params = $args['arguments']; 89 $result = parent::call($method, $params); 90 91 # MCP only supports Text, Image and Audio. Complex types will be returned as JSON. 92 // FIXME: we could support image and audio in the core.getMedia call 93 return [ 94 "content" => [ 95 [ 96 "type" => "text", 97 "text" => is_scalar($result) ? (string)$result : json_encode($result, JSON_PRETTY_PRINT) 98 ] 99 ], 100 "isError" => false, 101 ]; 102 } 103 104 /** 105 * Handle the MCP calls that only need to be acknowledged, but do not require any response. 106 * 107 * @return object 108 */ 109 protected function mcpNOP() 110 { 111 return (object)[]; 112 } 113} 114