Skip to content

Commit c440518

Browse files
committed
Merge branch 'feature/create_post' of https://github.com/swissspidy/ai-command into feature/create_post
2 parents ecd6353 + 801d42c commit c440518

File tree

2 files changed

+77
-46
lines changed

2 files changed

+77
-46
lines changed

settings/rest-routes.php

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,6 @@
11
<?php
2-
2+
// Blocklist.
3+
// For now there is a hardcoded list that will only allow /wp/v2/***
34
return [
4-
'/wp/v2/pages' => [
5-
'GET' => 'Get a list of pages',
6-
'POST' => 'Create a new page',
7-
],
8-
'/wp/v2/posts' => [
9-
'GET' => 'Get a list of posts',
10-
'POST' => 'Create a new post',
11-
],
12-
'/wp/v2/posts/(?P<id>[\d]+)' => [
13-
'GET' => 'Retrieve a specific post by ID.',
14-
'POST' => 'Update a specific post by ID.',
15-
'DELETE' => 'Delete a specific post by ID.'
16-
],
17-
'/wp/v2/categories' => [
18-
'GET' => 'Get a list of categories',
19-
'POST' => 'Create a new category',
20-
],
21-
'/wp/v2/users' => [
22-
'GET' => 'Retrieve a list of users.',
23-
'POST' => 'Create a new user.',
24-
],
25-
'/wp/v2/users/(?P<id>[\\d]+)' => [
26-
'GET' => 'Retrieve a specific user by ID.',
27-
'POST' => 'Update a specific user by ID.',
28-
'DELETE' => 'Delete a specific user by ID.',
29-
],
30-
'/wp/v2/pages/(?P<parent>[\\d]+)/revisions/(?P<id>[\\d]+)' => [
31-
'GET' => 'Get a list of revisions for a specific page.',
32-
'POST' => 'Create a new revision for a specific page.',
33-
],
5+
'/wp/v2' // don't allow the root route.
346
];

src/MapRESTtoMCP.php

Lines changed: 74 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,17 @@ public function args_to_schema( $args = [] ) {
4141
}
4242

4343
protected function sanitize_type( $type) {
44+
45+
$mapping = array(
46+
'string' => 'string',
47+
'integer' => 'integer',
48+
'number' => 'integer',
49+
'boolean' => 'boolean',
50+
);
51+
4452
// Validated types:
45-
if ( $type === 'string' || $type === 'integer' || $type === 'boolean' ) {
46-
return $type;
53+
if ( !\is_array($type) && isset($mapping[ $type ]) ) {
54+
return $mapping[ $type ];
4755
}
4856

4957
if ( $type === 'array' || $type === 'object' ) {
@@ -62,37 +70,38 @@ protected function sanitize_type( $type) {
6270
if ( \in_array( 'string', $type ) ) {
6371
return 'string';
6472
}
65-
if ( \in_array( 'integer', $type ) ) {
73+
if ( \in_array( 'integer', $type ) ) {
6674
return 'integer';
6775
}
6876
// TODO, better types handling.
6977
return 'string';
7078

7179
}
7280

81+
protected function is_route_allowed( $route ) {
82+
if(! \str_starts_with($route, '/wp/v2')) {
83+
return false; // Block all non wp/v2 routes for now.
84+
}
85+
86+
return ! in_array( $route, $this->rest_routes, true );
87+
}
88+
7389
public function map_rest_to_mcp( Server $mcp_server ) {
7490
$server = rest_get_server();
7591
$routes = $server->get_routes();
7692

77-
foreach ( $routes as $route => $endpoints ) {
93+
foreach ( $routes as $route => $endpoints ) {
7894
foreach ( $endpoints as $endpoint ) {
79-
// Only allowed routes
80-
if ( ! isset( $this->rest_routes[ $route ] ) ) {
81-
continue;
95+
if ( ! $this->is_route_allowed($route) ) {
96+
continue; // This route is the block list.
8297
}
8398

84-
// Generate a tool name off route e.g. /wp/v2/posts
85-
$tool_name = sanitize_key($route);
8699

87100
foreach( $endpoint['methods'] as $method_name => $enabled ) {
88-
// Only allowed methods
89-
if ( ! isset( $this->rest_routes[ $route ][ $method_name ] ) ) {
90-
continue;
91-
}
92101

93102
$tool = [
94-
'name' => $tool_name . '_' . strtolower( $method_name ),
95-
'description' => $this->rest_routes[ $route ][ $method_name ],
103+
'name' => $this->generate_tool_name($route, $method_name),
104+
'description' => $this->generate_description( $route, $method_name, $endpoint ),
96105
'inputSchema' => $this->args_to_schema( $endpoint['args'] ),
97106
'callable' => function ( $inputs ) use ( $route, $method_name, $server ){
98107
return $this->rest_callable( $inputs, $route, $method_name, $server );
@@ -106,6 +115,53 @@ public function map_rest_to_mcp( Server $mcp_server ) {
106115
}
107116
}
108117

118+
protected function generate_tool_name($route, $method_name) {
119+
$singular = '';
120+
if ( \str_contains( $route, '(?P<' ) ) {
121+
$singular = 'singular_';
122+
}
123+
return sanitize_title($route) . '_' . $singular . strtolower( $method_name );
124+
}
125+
126+
/**
127+
* Create desciptrion based on route and method.
128+
*
129+
*
130+
* Get a list of posts GET /wp/v2/posts
131+
* Get post with id GET /wp/v2/posts/(?P<id>[\d]+)
132+
*/
133+
protected function generate_description( $route, $method_name, $endpoint ) {
134+
135+
// TODO all validation + exception handling.
136+
$verb = array(
137+
'GET' => 'Get',
138+
'POST' => 'Create',
139+
'PUT' => 'Update',
140+
'PATCH' => 'Update',
141+
'DELETE' => 'Delete',
142+
);
143+
144+
$controller = $endpoint['callback'][0];
145+
if ( !isset($endpoint['callback']) || ! \is_object($endpoint['callback'][0])) {
146+
throw new \Exception('Not an object: ' . $route);
147+
}
148+
if (! \method_exists($endpoint['callback'][0], 'get_public_item_schema')) {
149+
throw new \Exception('missing method: ' . $route);
150+
}
151+
152+
$schema = $controller->get_public_item_schema();
153+
$title = $schema['title'];
154+
155+
// is singular?
156+
$singular = 'a';
157+
if ( $method_name === 'GET' && ! \str_contains( $route, '(?P<' )) {
158+
$singular = 'List of';
159+
160+
}
161+
162+
return $verb[ $method_name ] . ' ' . $singular . ' ' . $title;
163+
}
164+
109165
protected function rest_callable( $inputs, $route, $method_name, $server ) {
110166
preg_match_all( '/\(?P<(\w+)>/', $route, $matches );
111167

@@ -124,6 +180,9 @@ protected function rest_callable( $inputs, $route, $method_name, $server ) {
124180
$request = new WP_REST_Request( $method_name, $route );
125181
$request->set_body_params( $inputs );
126182

183+
/**
184+
* @var WP_REST_Response $response
185+
*/
127186
$response = $server->dispatch( $request );
128187

129188
return $server->response_to_data( $response, false );

0 commit comments

Comments
 (0)