diff --git a/_test/tests/inc/remote.test.php b/_test/tests/inc/remote.test.php
index 407992ae7e7128e2729ad2542656f4a16525804f..3cbc14f6bc368bd2008814a5ae57b3182ddf39bf 100644
--- a/_test/tests/inc/remote.test.php
+++ b/_test/tests/inc/remote.test.php
@@ -106,14 +106,32 @@ class remote_plugin_testplugin extends DokuWiki_Remote_Plugin {
     function methodString() { return 'success'; }
     function method2($str, $int, $bool = false) { return array($str, $int, $bool); }
     function publicCall() {return true;}
+}
+
+class remote_plugin_testplugin2 extends DokuWiki_Remote_Plugin {
+    /**
+     * This is a dummy method
+     *
+     * @param string $str some more parameter description
+     * @param int $int
+     * @param bool $bool
+     * @param Object $unknown
+     * @return array
+     */
+    public function commented($str, $int, $bool, $unknown) { return array($str, $int, $bool); }
 
+    private function privateMethod() {return true;}
+    protected function protectedMethod() {return true;}
+    public function _underscore() {return true;}
 }
 
 
+
 class remote_test extends DokuWikiTest {
 
     var $userinfo;
 
+    /** @var  RemoteAPI */
     var $remote;
 
     function setUp() {
@@ -125,10 +143,18 @@ class remote_test extends DokuWikiTest {
 
         parent::setUp();
 
+        // mock plugin controller to return our test plugins
         $pluginManager = $this->getMock('Doku_Plugin_Controller');
-        $pluginManager->expects($this->any())->method('getList')->will($this->returnValue(array('testplugin')));
-        $pluginManager->expects($this->any())->method('load')->will($this->returnValue(new remote_plugin_testplugin()));
-
+        $pluginManager->method('getList')->willReturn(array('testplugin', 'testplugin2'));
+        $pluginManager->method('load')->willReturnCallback(
+            function($type, $plugin) {
+                if($plugin == 'testplugin2') {
+                    return new remote_plugin_testplugin2();
+                } else {
+                    return new remote_plugin_testplugin();
+                }
+            }
+        );
         $plugin_controller = $pluginManager;
 
         $conf['remote'] = 1;
@@ -151,11 +177,28 @@ class remote_test extends DokuWikiTest {
         $methods = $this->remote->getPluginMethods();
         $actual = array_keys($methods);
         sort($actual);
-        $expect = array('plugin.testplugin.method1', 'plugin.testplugin.method2', 'plugin.testplugin.methodString', 'plugin.testplugin.method2ext', 'plugin.testplugin.publicCall');
+        $expect = array(
+            'plugin.testplugin.method1',
+            'plugin.testplugin.method2',
+            'plugin.testplugin.methodString',
+            'plugin.testplugin.method2ext',
+            'plugin.testplugin.publicCall',
+
+            'plugin.testplugin2.commented'
+        );
         sort($expect);
         $this->assertEquals($expect,$actual);
     }
 
+    function test_pluginDescriptors() {
+        $methods = $this->remote->getPluginMethods();
+        $this->assertEquals(array('string','int','bool','string'), $methods['plugin.testplugin2.commented']['args']);
+        $this->assertEquals('array', $methods['plugin.testplugin2.commented']['return']);
+        $this->assertEquals(0, $methods['plugin.testplugin2.commented']['public']);
+        $this->assertContains('This is a dummy method', $methods['plugin.testplugin2.commented']['doc']);
+        $this->assertContains('string $str some more parameter description', $methods['plugin.testplugin2.commented']['doc']);
+    }
+
     function test_hasAccessSuccess() {
         global $conf;
         $conf['remoteuser'] = '';
diff --git a/lib/plugins/remote.php b/lib/plugins/remote.php
index 47f954ee6034aa05d4b726a184ee9f9642e135e9..c2253dbd5dad4e146563ee0e96f62adc740f2ae3 100644
--- a/lib/plugins/remote.php
+++ b/lib/plugins/remote.php
@@ -17,10 +17,82 @@ abstract class DokuWiki_Remote_Plugin extends DokuWiki_Plugin {
     /**
      * Get all available methods with remote access.
      *
-     * @abstract
+     * By default it exports all public methods of a remote plugin. Methods beginning
+     * with an underscore are skipped.
+     *
      * @return array Information about all provided methods. {@see RemoteAPI}.
      */
-    public abstract function _getMethods();
+    public function _getMethods() {
+        $result = array();
+
+        $reflection = new \ReflectionClass($this);
+        foreach($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
+            // skip parent methods, only methods further down are exported
+            $declaredin = $method->getDeclaringClass()->name;
+            if($declaredin == 'DokuWiki_Plugin' || $declaredin == 'DokuWiki_Remote_Plugin') continue;
+            $method_name = $method->name;
+            if(substr($method_name, 0, 1) == '_') continue;
+
+            // strip asterisks
+            $doc = $method->getDocComment();
+            $doc = preg_replace(
+                array('/^[ \t]*\/\*+[ \t]*/m', '/[ \t]*\*+[ \t]*/m', '/\*+\/\s*$/m','/\s*\/\s*$/m'),
+                array('', '', '', ''),
+                $doc
+            );
+
+            // prepare data
+            $data = array();
+            $data['name'] = $method_name;
+            $data['public'] = 0;
+            $data['doc'] = $doc;
+            $data['args'] = array();
+
+            // get parameter type from doc block type hint
+            foreach($method->getParameters() as $parameter) {
+                $name = $parameter->name;
+                $type = 'string'; // we default to string
+                if(preg_match('/^@param[ \t]+([\w|\[\]]+)[ \t]\$'.$name.'/m', $doc, $m)){
+                    $type = $this->cleanTypeHint($m[1]);
+                }
+                $data['args'][] = $type;
+            }
+
+            // get return type from doc block type hint
+            if(preg_match('/^@return[ \t]+([\w|\[\]]+)/m', $doc, $m)){
+                $data['return'] = $this->cleanTypeHint($m[1]);
+            } else {
+                $data['return'] = 'string';
+            }
+
+            // add to result
+            $result[$method_name] = $data;
+        }
+
+        return $result;
+    }
+
+    /**
+     * Matches the given type hint against the valid options for the remote API
+     *
+     * @param string $hint
+     * @return string
+     */
+    protected function cleanTypeHint($hint) {
+        $types = explode('|', $hint);
+        foreach($types as $t) {
+            if(substr($t, -2) == '[]') {
+                return 'array';
+            }
+            if($t == 'boolean') {
+                return 'bool';
+            }
+            if(in_array($t, array('array', 'string', 'int', 'double', 'bool', 'null', 'date', 'file'))) {
+                return $t;
+            }
+        }
+        return 'string';
+    }
 
     /**
      * @return RemoteAPI