#
tokens: 27015/50000 36/36 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── dist
│   ├── index.js
│   └── index.js.map
├── package-lock.json
├── package.json
├── README.md
├── rollup.config.js
├── src
│   ├── helpers.ts
│   ├── index.ts
│   ├── tools
│   │   ├── build-block.ts
│   │   ├── filesystem
│   │   │   ├── edit-block-file.ts
│   │   │   └── edit-block-json-file.ts
│   │   ├── list-available-plugins-in-site-plugins-path.ts
│   │   ├── list-plugin-files.ts
│   │   ├── scaffold-block.ts
│   │   ├── scaffold-plugin.ts
│   │   ├── wp-api
│   │   │   ├── activate-plugin.ts
│   │   │   ├── create-post.ts
│   │   │   ├── deactivate-plugin.ts
│   │   │   ├── delete-post.ts
│   │   │   ├── get-block-types.ts
│   │   │   ├── get-plugins.ts
│   │   │   ├── get-post-types.ts
│   │   │   ├── get-post.ts
│   │   │   ├── get-posts.ts
│   │   │   ├── get-rest-base-for-post-types.ts
│   │   │   ├── get-templates.ts
│   │   │   ├── post
│   │   │   │   ├── get-post-content-before-changes.ts
│   │   │   │   └── get-post-preview-link.ts
│   │   │   ├── site-settings
│   │   │   │   ├── get-site-settings.ts
│   │   │   │   ├── update-integer-site-setting.ts
│   │   │   │   └── update-string-site-setting.ts
│   │   │   ├── update-post-content.ts
│   │   │   ├── update-post-status.ts
│   │   │   └── update-post.ts
│   │   └── wp-cli
│   │       ├── check_and_instal_wp_cli.ts
│   │       └── instal-and-activate-plugin.ts
│   └── types
│       └── wp-sites.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | /node_modules/
2 | /.idea/
3 | wp-sites.json
4 | /wp-sites.json.bak
5 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
1 | # mcp-wordpress-gutenberg
2 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2020",
 4 |     "module": "ESNext",
 5 |     "moduleResolution": "node",
 6 |     "esModuleInterop": true,
 7 |     "strict": true,
 8 |     "skipLibCheck": true,
 9 |     "forceConsistentCasingInFileNames": true,
10 |     "resolveJsonModule": true,
11 |     "allowJs": true,
12 |     "declaration": false,
13 |     "moduleDetection": "force",
14 |     "paths": {
15 |       "*": ["./src/*"]
16 |     },
17 |     "baseUrl": ".",
18 |     "removeComments": true
19 |   },
20 |   "include": ["src/**/*"],
21 |   "exclude": ["node_modules", "dist"]
22 | }
23 | 
```

--------------------------------------------------------------------------------
/src/types/wp-sites.ts:
--------------------------------------------------------------------------------

```typescript
 1 | // src/types/wp-sites.ts
 2 | export interface WordPressSite {
 3 |     cli?: string;
 4 |     localWpSshEntryFile?: string;
 5 |     name: string;
 6 |     path: string;           // Ścieżka do instalacji WordPress
 7 |     pluginsPath: string;           // Ścieżka do instalacji WordPress
 8 |     apiUrl: string;         // URL do WP REST API
 9 |     apiCredentials?: {
10 |         username: string;
11 |         password: string;
12 |     };
13 |     database?: {
14 |         host: string;
15 |         port: number;
16 |         user: string;
17 |         password: string;
18 |         name: string;
19 |     };
20 | }
21 | 
22 | export interface WPSitesConfig {
23 |     sites: Record<string, WordPressSite>;
24 | }
```

--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------

```javascript
 1 | import typescript from "rollup-plugin-typescript2";
 2 | import resolve from "@rollup/plugin-node-resolve";
 3 | import commonjs from "@rollup/plugin-commonjs";
 4 | import json from "@rollup/plugin-json";
 5 | 
 6 | export default {
 7 |     input: "src/index.ts",
 8 |     output: {
 9 |         file: "dist/index.js",
10 |         format: "esm", // Używamy ESM, aby działało z "type": "module"
11 |         sourcemap: true
12 |     },
13 |     plugins: [
14 |         resolve(),
15 |         commonjs(),
16 |         json(),
17 |         typescript({
18 |             tsconfig: "./tsconfig.json",
19 |         }),
20 |     ],
21 |     external: ["fs", "path", "process"], // Nie bundlujemy wbudowanych modułów Node.js
22 | };
23 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "wordpress-mcp",
 3 |   "version": "1.0.0",
 4 |   "type": "module",
 5 |   "description": "WordPress MCP Server",
 6 |   "main": "dist/index.js",
 7 |   "scripts": {
 8 |     "clean": "rimraf dist",
 9 |     "build": "npm run clean && rollup -c",
10 |     "start": "node dist/index.js"
11 |   },
12 |   "dependencies": {
13 |     "@modelcontextprotocol/sdk": "^1.5.0",
14 |     "node-fetch": "^3.3.2"
15 |   },
16 |   "devDependencies": {
17 |     "@rollup/plugin-commonjs": "^28.0.2",
18 |     "@rollup/plugin-json": "^6.1.0",
19 |     "@rollup/plugin-node-resolve": "^16.0.0",
20 |     "@types/node": "^18.15.11",
21 |     "rimraf": "^5.0.0",
22 |     "rollup": "^4.34.8",
23 |     "rollup-plugin-typescript2": "^0.36.0",
24 |     "typescript": "^4.9.5"
25 |   }
26 | }
27 | 
```

--------------------------------------------------------------------------------
/src/tools/scaffold-plugin.ts:
--------------------------------------------------------------------------------

```typescript
 1 | // src/tools/scaffold-plugin.ts
 2 | import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
 3 | import { execSync } from 'child_process';
 4 | 
 5 | export const scaffoldPluginTool = {
 6 |     name: "wp_scaffold_plugin",
 7 |     description: "Creates a new WordPress plugin using wp-cli scaffold plugin",
 8 |     inputSchema: {
 9 |         type: "object",
10 |         properties: {
11 |             site: { type: "string", description: "Site alias from configuration" },
12 |             name: { type: "string", description: "Plugin name" },
13 |             directory: { type: "string", description: "Optional: Custom directory path" }
14 |         },
15 |         required: ["name"]
16 |     },
17 |     execute: async (args: { name: string; directory: string }) => {
18 |         try {
19 |             const command = `wp scaffold plugin ${args.name} --dir=${args.directory}`;
20 |             const output = execSync(command, {
21 |                 stdio: ['pipe', 'pipe', 'pipe'],
22 |                 encoding: 'utf-8'
23 |             });
24 | 
25 |             return {
26 |                 content: [{
27 |                     type: "text",
28 |                     text: `Plugin "${args.name}" created successfully in ${args.directory}\n\nOutput:\n${output}`
29 |                 }]
30 |             };
31 |         } catch (error) {
32 |             console.error('Plugin scaffolding error:', error);
33 |             if (error instanceof Error) {
34 |                 throw new McpError(ErrorCode.InternalError, `Failed to create plugin: ${error.message}`);
35 |             }
36 |             throw new McpError(ErrorCode.InternalError, 'Unknown error occurred while creating plugin');
37 |         }
38 |     }
39 | };
```

--------------------------------------------------------------------------------
/src/tools/wp-api/post/get-post-content-before-changes.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {WordPressSite} from "types/wp-sites";
 2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
 3 | import {apiGetPost} from "tools/wp-api/get-post";
 4 | 
 5 | export const apiGetPostContentBeforeChanges = {
 6 |     name: "wp_api_get_post_content_before_changes",
 7 |     description: "Retrieve a single post, page, or custom post type CONTENT using REST API. Run it always before doing changes in content.",
 8 |     inputSchema: {
 9 |         type: "object",
10 |         properties: {
11 |             siteKey: {type: "string", description: "Site key"},
12 |             postType: {type: "string", description: "Post type (default: posts)"},
13 |             postId: {type: "integer", description: "Post ID to retrieve"}, // New property to specify post ID
14 |         },
15 |         required: ["siteKey", "postType", "postId"]
16 |     },
17 | 
18 |     async execute(args: { siteKey: string, postType: string, postId: number }, site: WordPressSite) {
19 |         try {
20 | 
21 |             const {post} = await apiGetPost.execute(args, site)
22 | 
23 |             return {
24 |                 post,
25 |                 content: [{
26 |                     type: "text",
27 |                     text: `Post retrieved successfully.\n\nCurrent ${args.postType} #${args.postId} content is:\n\n${post.content.raw}`
28 |                 }]
29 |             };
30 |         } catch (error) {
31 |             if (error instanceof Error) {
32 |                 throw new McpError(ErrorCode.InternalError, `wp_api_get_post_content_before_changes failed: ${error.message}`);
33 |             }
34 |             throw new McpError(ErrorCode.InternalError, 'wp_api_get_post_content_before_changes failed: Unknown error occurred');
35 |         }
36 |     }
37 | };
38 | 
```

--------------------------------------------------------------------------------
/src/tools/wp-cli/instal-and-activate-plugin.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {execSync} from "child_process";
 2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
 3 | import {WordPressSite} from "types/wp-sites";
 4 | 
 5 | export const cliInstallAndActivatePlugin = {
 6 |     name: "wp_cli_install_and_activate_plugin",
 7 |     description: "Installs and activates a specified WordPress plugin via WP-CLI.",
 8 |     inputSchema: {
 9 |         type: "object",
10 |         properties: {
11 |             siteKey: {type: "string", description: "Site key"},
12 |             pluginSlug: {type: "string", description: "Plugin slug to install and activate."}
13 |         },
14 |         required: ["pluginSlug"]
15 |     },
16 | 
17 |     async execute(args: { siteKey: string, pluginSlug: string }, site: WordPressSite, cliWrapper: string) {
18 |         try {
19 |             const pluginSlug = args.pluginSlug;
20 | 
21 |             const cmdInstallAndActivate = cliWrapper.replace('{{cmd}}', `wp plugin install ${pluginSlug} --activate`)
22 |             const outputInstallAndActivate = execSync(cmdInstallAndActivate, {
23 |                 stdio: "pipe",
24 |                 cwd: site.path
25 |             });
26 | 
27 |             return {
28 |                 content: [{
29 |                     type: "text",
30 |                     text: `✅ Plugin '${pluginSlug}' was already installed and has been activated. Output from WP-CLI:\n\n${outputInstallAndActivate}`
31 |                 }]
32 |             };
33 | 
34 |         } catch (error) {
35 |             if (error instanceof Error) {
36 |                 throw new McpError(ErrorCode.InternalError, `wp_cli_install_and_activate_plugin failed: ${error.message}`);
37 |             }
38 |             throw new McpError(ErrorCode.InternalError, 'wp_cli_install_and_activate_plugin failed: Unknown error occurred');
39 |         }
40 |     }
41 | };
42 | 
```

--------------------------------------------------------------------------------
/src/tools/wp-api/post/get-post-preview-link.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {WordPressSite} from "types/wp-sites";
 2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
 3 | import {apiGetPost} from "tools/wp-api/get-post";
 4 | 
 5 | export const apiGetPostPreviewLink = {
 6 |     name: "wp_api_get_post_preview_link",
 7 |     description: "Retrieve a preview link for single post, page, or custom post type item using REST API. Especially for draft status.",
 8 |     inputSchema: {
 9 |         type: "object",
10 |         properties: {
11 |             siteKey: {type: "string", description: "Site key"},
12 |             postType: {type: "string", description: "Post type (default: posts)"},
13 |             postId: {type: "integer", description: "Post ID to retrieve"},
14 |         },
15 |         required: ["siteKey", "postType", "postId"]
16 |     },
17 | 
18 |     async execute(args: { siteKey: string, postType: string, postId: number }, site: WordPressSite) {
19 |         try {
20 | 
21 |             const {post} = await apiGetPost.execute(args, site)
22 | 
23 |             const postPreviewUrl = `${post?.guid?.rendered}&preview=true`;
24 | 
25 |             return {
26 |                 post: post,
27 |                 previewLink: postPreviewUrl,
28 |                 content: [{
29 |                     type: "text",
30 |                     text: `Post preview link retrieved successfully. Remember it is only for logged in user with proper permissions.\nURL: ${postPreviewUrl}`
31 |                 }]
32 |             };
33 |         } catch (error) {
34 |             if (error instanceof Error) {
35 |                 throw new McpError(ErrorCode.InternalError, `wp_api_get_post_preview_link failed: ${error.message}`);
36 |             }
37 |             throw new McpError(ErrorCode.InternalError, 'wp_api_get_post_preview_link failed: Unknown error occurred');
38 |         }
39 |     }
40 | };
41 | 
```

--------------------------------------------------------------------------------
/src/tools/list-available-plugins-in-site-plugins-path.ts:
--------------------------------------------------------------------------------

```typescript
 1 | // src/tools/edit-file.ts
 2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
 3 | import fs from 'fs';
 4 | import {WordPressSite} from "../types/wp-sites";
 5 | 
 6 | 
 7 | export const listAvailablePluginsInSitePluginsPath = {
 8 |     name: "wp_list_available_plugins_in_site_plugins_path",
 9 |     description: "List a plugins directories which is equivalent of listing available plugins for site",
10 |     inputSchema: {
11 |         type: "object",
12 |         properties: {
13 |             siteKey: {type: "string", description: "Site key"}
14 |         },
15 |         required: ["siteKey"]
16 |     },
17 | 
18 |     async execute(args: { siteKey: string }, site: WordPressSite) {
19 | 
20 |         const pluginsPath = site.pluginsPath
21 | 
22 |         if (!fs.existsSync(pluginsPath)) {
23 |             throw new Error(`wp_list_available_plugins_in_site_plugins_path failed: directory ${pluginsPath} not exists. Please review a plugin directory and site configuration.`);
24 |         }
25 | 
26 |         try {
27 |             const files = fs.readdirSync(pluginsPath, {withFileTypes: true});
28 |             const directories = files
29 |                 .filter(dirent => dirent.isDirectory())
30 |                 .map(dirent => dirent.name);
31 | 
32 |             return {
33 |                 content: [{
34 |                     type: "text",
35 |                     text: `Available plugins directories in ${pluginsPath}:\n\n${directories.join('\n')}`,
36 |                     directories
37 |                 }]
38 |             };
39 | 
40 |         } catch (error) {
41 |             if (error instanceof Error) {
42 |                 throw new McpError(ErrorCode.InternalError, error.message);
43 |             }
44 |             throw new McpError(ErrorCode.InternalError, 'wp_list_available_plugins_in_site_plugins_path: Unknown error occurred');
45 |         }
46 |     }
47 | };
```

--------------------------------------------------------------------------------
/src/tools/wp-api/site-settings/get-site-settings.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {WordPressSite} from "types/wp-sites";
 2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
 3 | 
 4 | export const apiGetSiteSettings = {
 5 |     name: "wp_api_get_site_settings",
 6 |     description: "Retrieves all WordPress site settings using REST API",
 7 |     inputSchema: {
 8 |         type: "object",
 9 |         properties: {
10 |             siteKey: {type: "string", description: "Site key"},
11 |         },
12 |         required: ["siteKey"]
13 |     },
14 |     async execute(args: any, site: WordPressSite) {
15 |         try {
16 |             const credentials = Buffer.from(`${site.apiCredentials?.username}:${site.apiCredentials?.password}`).toString('base64');
17 |             const url = `${site.apiUrl}/wp/v2/settings`;
18 | 
19 |             const response = await fetch(url, {
20 |                 method: 'GET',
21 |                 headers: {
22 |                     'Authorization': `Basic ${credentials}`,
23 |                     'Content-Type': 'application/json'
24 |                 }
25 |             });
26 | 
27 |             if (!response.ok) {
28 |                 const errorData = await response.json();
29 |                 throw new Error(`Failed to retrieve site settings: ${errorData.message || response.statusText}`);
30 |             }
31 | 
32 |             const settings = await response.json();
33 |             return {
34 |                 settings,
35 |                 content: [{
36 |                     type: "text",
37 |                     text: `Site settings retrieved successfully.\n\nSettings: ${JSON.stringify(settings, null, 2)}`
38 |                 }]
39 |             };
40 |         } catch (error) {
41 |             if (error instanceof Error) {
42 |                 throw new McpError(ErrorCode.InternalError, `wp_api_get_site_settings failed: ${error.message}`);
43 |             }
44 |             throw new McpError(ErrorCode.InternalError, 'wp_api_get_site_settings failed: Unknown error occurred');
45 |         }
46 |     }
47 | };
48 | 
```

--------------------------------------------------------------------------------
/src/tools/wp-api/get-block-types.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {WordPressSite} from "types/wp-sites";
 2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
 3 | 
 4 | export const apiGetGutenbergBlocks = {
 5 |     name: "wp_api_get_gutenberg_blocks",
 6 |     description: "Get available Gutenberg blocks for the WordPress site",
 7 |     inputSchema: {
 8 |         type: "object",
 9 |         properties: {
10 |             siteKey: {type: "string", description: "Site key"}
11 |         },
12 |         required: ["siteKey"]
13 |     },
14 | 
15 |     async execute(args: { siteKey: string }, site: WordPressSite) {
16 |         try {
17 |             const credentials = Buffer.from(`${site.apiCredentials?.username}:${site.apiCredentials?.password}`).toString('base64');
18 |             const url = `${site.apiUrl}/wp/v2/block-types`;
19 | 
20 |             const response = await fetch(url, {
21 |                 method: 'GET',
22 |                 headers: {
23 |                     'Authorization': `Basic ${credentials}`,
24 |                     'Content-Type': 'application/json'
25 |                 }
26 |             });
27 | 
28 |             if (!response.ok) {
29 |                 const errorData = await response.json();
30 |                 throw new Error(`Failed to get Gutenberg blocks: ${errorData.message || response.statusText}.\nRequested URL: ${url}`);
31 |             }
32 | 
33 |             const blocks = await response.json();
34 | 
35 |             return {
36 |                 blocks: blocks,
37 |                 content: [{
38 |                     type: "text",
39 |                     text: `Available Gutenberg blocks:\n${JSON.stringify(blocks, null, 2)}`
40 |                 }]
41 |             };
42 |         } catch (error) {
43 |             if (error instanceof Error) {
44 |                 throw new McpError(ErrorCode.InternalError, `wp_api_get_gutenberg_blocks failed: ${error.message}`);
45 |             }
46 |             throw new McpError(ErrorCode.InternalError, 'wp_api_get_gutenberg_blocks failed: Unknown error occurred');
47 |         }
48 |     }
49 | };
50 | 
```

--------------------------------------------------------------------------------
/src/tools/wp-api/get-post-types.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {WordPressSite} from "types/wp-sites";
 2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
 3 | 
 4 | export const apiGetPostTypes = {
 5 |     name: "wp_api_get_post_types",
 6 |     description: "Get a WordPress post types using REST API",
 7 |     inputSchema: {
 8 |         type: "object",
 9 |         properties: {
10 |             siteKey: {type: "string", description: "Site key"},
11 |         },
12 |         required: ["siteKey"]
13 |     },
14 | 
15 |     async execute(args: { siteKey: string }, site: WordPressSite) {
16 |         try {
17 |             const credentials = Buffer.from(`${site.apiCredentials?.username}:${site.apiCredentials?.password}`).toString('base64');
18 |             const url = `${site.apiUrl}/wp/v2/types`
19 | 
20 |             const response = await fetch(url, {
21 |                 method: 'GET',
22 |                 headers: {
23 |                     'Authorization': `Basic ${credentials}`,
24 |                     'Content-Type': 'application/json'
25 |                 }
26 |             });
27 | 
28 |             if (!response.ok) {
29 |                 const errorData = await response.json();
30 |                 throw new Error(`Failed to get post types: ${errorData.message || response.statusText}.\nRequested URL: ${url}`);
31 |             }
32 | 
33 |             const data = await response.json();
34 | 
35 |             const list = Object.values(data).map((postType: any, i: number) => {
36 |                 return (i + 1) + '. ' + postType.name + ' (slug: ' + postType.slug + ')';
37 |             })
38 | 
39 |             return {
40 |                 postTypes: data,
41 |                 postTypesList: list,
42 |                 content: [{
43 |                     type: "text",
44 |                     text: `Available post types:\n${JSON.stringify(data)}`
45 |                 }]
46 |             };
47 |         } catch (error) {
48 |             if (error instanceof Error) {
49 |                 throw new McpError(ErrorCode.InternalError, `wp_api_get_post_types failed: ${error.message}`);
50 |             }
51 |             throw new McpError(ErrorCode.InternalError, 'wp_api_get_post_types failed: Unknown error occurred');
52 |         }
53 |     }
54 | };
```

--------------------------------------------------------------------------------
/src/tools/wp-api/get-templates.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {WordPressSite} from "types/wp-sites";
 2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
 3 | 
 4 | export const apiGetTemplates = {
 5 |     name: "wp_api_get_templates",
 6 |     description: "Get available templates for a specific post type in WordPress",
 7 |     inputSchema: {
 8 |         type: "object",
 9 |         properties: {
10 |             siteKey: {type: "string", description: "Site key"},
11 |             postType: {type: "string", description: "The post type to fetch templates for"},
12 |         },
13 |         required: ["siteKey", "postType"]
14 |     },
15 | 
16 |     async execute(args: { siteKey: string, postType: string }, site: WordPressSite) {
17 |         try {
18 | 
19 |             const credentials = Buffer.from(`${site.apiCredentials?.username}:${site.apiCredentials?.password}`).toString('base64');
20 |             const url = `${site.apiUrl}/wp/v2/templates?post_type=${args.postType}`;
21 | 
22 |             const response = await fetch(url, {
23 |                 method: 'GET',
24 |                 headers: {
25 |                     'Authorization': `Basic ${credentials}`,
26 |                     'Content-Type': 'application/json'
27 |                 }
28 |             });
29 | 
30 |             if (!response.ok) {
31 |                 const errorData = await response.json();
32 |                 throw new Error(`Failed to get templates: ${errorData.message || response.statusText}.\nRequested URL: ${url}`);
33 |             }
34 | 
35 |             const data = await response.json();
36 | 
37 |             const list = data.map((template: any, i: number) => {
38 |                 return (i + 1) + '. ' + template.title.raw + ' (slug: ' + template.slug + ')';
39 |             })
40 | 
41 |             return {
42 |                 templates: data,
43 |                 templatesList: list,
44 |                 content: [{
45 |                     type: "text",
46 |                     text: `Available templates for post type "${args.postType}":\n\n${list.join('\n')}`
47 |                 }]
48 |             };
49 |         } catch (error) {
50 |             if (error instanceof Error) {
51 |                 throw new McpError(ErrorCode.InternalError, `wp_api_get_templates failed: ${error.message}`);
52 |             }
53 |             throw new McpError(ErrorCode.InternalError, 'wp_api_get_templates failed: Unknown error occurred');
54 |         }
55 |     }
56 | };
57 | 
```

--------------------------------------------------------------------------------
/src/tools/wp-api/get-rest-base-for-post-types.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {WordPressSite} from "types/wp-sites";
 2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
 3 | import {apiGetPostTypes} from "./get-post-types.js";
 4 | 
 5 | export const apiGetRestBaseForPostType = {
 6 |     name: "wp_api_get_rest_base_for_post_type",
 7 |     description: "Get a WordPress REST API base for post type to use in REST API",
 8 |     inputSchema: {
 9 |         type: "object",
10 |         properties: {
11 |             siteKey: {type: "string", description: "Site key"},
12 |             postType: {type: "string", description: "Post type (default: page)"},
13 |         },
14 |         required: ["siteKey", "postType"]
15 |     },
16 | 
17 |     async execute(args: { siteKey: string, postType: string }, site: WordPressSite) {
18 |         try {
19 |             const credentials = Buffer.from(`${site.apiCredentials?.username}:${site.apiCredentials?.password}`).toString('base64');
20 |             const url = `${site.apiUrl}/wp/v2/types/${args.postType}`
21 | 
22 |             const response = await fetch(url, {
23 |                 method: 'GET',
24 |                 headers: {
25 |                     'Authorization': `Basic ${credentials}`,
26 |                     'Content-Type': 'application/json'
27 |                 }
28 |             });
29 | 
30 |             if (!response.ok) {
31 |                 const errorData = await response.json();
32 |                 const {postTypesList} = await apiGetPostTypes.execute(args, site)
33 |                 throw new Error(`Failed to get post types: ${errorData.message || response.statusText}.\nRequested URL: ${url}. Please try again with one of the following post type:\n${postTypesList.join('\n')}`);
34 |             }
35 | 
36 |             const data = await response.json();
37 | 
38 |             return {
39 |                 postType: data,
40 |                 restBase: data.rest_base,
41 |                 content: [{
42 |                     type: "text",
43 |                     text: `API REST base for post type: ${args.postType} is a: \n${data.rest_base}`
44 |                 }]
45 |             };
46 |         } catch (error) {
47 |             if (error instanceof Error) {
48 |                 throw new McpError(ErrorCode.InternalError, `wp_api_get_rest_base_for_post_types failed: ${error.message}`);
49 |             }
50 |             throw new McpError(ErrorCode.InternalError, 'wp_api_get_rest_base_for_post_types failed: Unknown error occurred');
51 |         }
52 |     }
53 | };
```

--------------------------------------------------------------------------------
/src/tools/wp-api/get-post.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {WordPressSite} from "types/wp-sites";
 2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
 3 | import {apiGetRestBaseForPostType} from "./get-rest-base-for-post-types.js";
 4 | 
 5 | export const apiGetPost = {
 6 |     name: "wp_api_get_post",
 7 |     description: "Retrieve a single post, page, or custom post type item using REST API",
 8 |     inputSchema: {
 9 |         type: "object",
10 |         properties: {
11 |             siteKey: {type: "string", description: "Site key"},
12 |             postType: {type: "string", description: "Post type (default: posts)"},
13 |             postId: {type: "integer", description: "Post ID to retrieve"}, // New property to specify post ID
14 |         },
15 |         required: ["siteKey", "postType", "postId"]
16 |     },
17 | 
18 |     async execute(args: { siteKey: string, postType: string, postId: number }, site: WordPressSite) {
19 |         try {
20 |             const {restBase} = await apiGetRestBaseForPostType.execute(args, site);
21 | 
22 |             const credentials = Buffer.from(`${site.apiCredentials?.username}:${site.apiCredentials?.password}`).toString('base64');
23 |             const url = `${site.apiUrl}/wp/v2/${restBase}/${args.postId}`; // Modified URL to fetch single post by ID
24 | 
25 |             const response = await fetch(url, {
26 |                 method: 'GET',
27 |                 headers: {
28 |                     'Authorization': `Basic ${credentials}`,
29 |                     'Content-Type': 'application/json'
30 |                 }
31 |             });
32 | 
33 |             if (!response.ok) {
34 |                 const errorData = await response.json();
35 |                 throw new Error(`Failed to retrieve post: ${errorData.message || response.statusText}. Requested URL: ${url}`);
36 |             }
37 | 
38 |             const data = await response.json();
39 |             return {
40 |                 post: data,
41 |                 content: [{
42 |                     type: "text",
43 |                     text: `Post retrieved successfully.\n\nPost: ${JSON.stringify(data)}`
44 |                 }]
45 |             };
46 |         } catch (error) {
47 |             if (error instanceof Error) {
48 |                 throw new McpError(ErrorCode.InternalError, `wp_api_get_post failed: ${error.message}`);
49 |             }
50 |             throw new McpError(ErrorCode.InternalError, 'wp_api_get_post failed: Unknown error occurred');
51 |         }
52 |     }
53 | };
54 | 
```

--------------------------------------------------------------------------------
/src/tools/wp-api/delete-post.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {WordPressSite} from "types/wp-sites";
 2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
 3 | import {apiGetRestBaseForPostType} from "tools/wp-api/get-rest-base-for-post-types";
 4 | 
 5 | export const apiDeletePost = {
 6 |     name: "wp_api_delete_post",
 7 |     description: "Deletes a WordPress post, page, or any custom post type using REST API",
 8 |     inputSchema: {
 9 |         type: "object",
10 |         properties: {
11 |             siteKey: {type: "string", description: "Site key"},
12 |             postId: {type: "integer", description: "ID of the post to delete"},
13 |             postType: {type: "string", description: "Type of the post (e.g., post, page, custom type)"},
14 |             force: {type: "boolean", description: "Whether to force delete the post"}
15 |         },
16 |         required: ["siteKey", "postId", "postType"]
17 |     },
18 | 
19 |     async execute(args: { siteKey: string; postId: number; postType: string; force?: boolean }, site: WordPressSite) {
20 |         try {
21 | 
22 |             const {restBase} = await apiGetRestBaseForPostType.execute(args, site)
23 | 
24 |             const credentials = Buffer.from(`${site.apiCredentials?.username}:${site.apiCredentials?.password}`).toString('base64');
25 |             const url = `${site.apiUrl}/wp/v2/${restBase}/${args.postId}?force=${args.force ?? false}`;
26 | 
27 |             const response = await fetch(url, {
28 |                 method: 'DELETE',
29 |                 headers: {
30 |                     'Authorization': `Basic ${credentials}`,
31 |                     'Content-Type': 'application/json'
32 |                 }
33 |             });
34 | 
35 |             if (!response.ok) {
36 |                 const errorData = await response.json();
37 |                 throw new Error(`Failed to delete post: ${errorData.message || response.statusText}.\nRequested URL: ${url}`);
38 |             }
39 | 
40 |             const data = await response.json();
41 | 
42 |             return {
43 |                 postData: data,
44 |                 content: [{
45 |                     type: "text",
46 |                     text: `Post ${args.postId} (${args.postType}) deleted successfully.`
47 |                 }]
48 |             };
49 |         } catch (error) {
50 |             if (error instanceof Error) {
51 |                 throw new McpError(ErrorCode.InternalError, `wp_api_delete_post failed: ${error.message}`);
52 |             }
53 |             throw new McpError(ErrorCode.InternalError, 'wp_api_delete_post failed: Unknown error occurred');
54 |         }
55 |     }
56 | };
57 | 
```

--------------------------------------------------------------------------------
/src/tools/wp-api/get-posts.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WordPressSite } from "types/wp-sites";
 2 | import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
 3 | import {apiGetRestBaseForPostType} from "./get-rest-base-for-post-types.js";
 4 | 
 5 | export const apiGetPosts = {
 6 |     name: "wp_api_get_posts",
 7 |     description: "Retrieve a list of posts, pages, or custom post type items using REST API",
 8 |     inputSchema: {
 9 |         type: "object",
10 |         properties: {
11 |             siteKey: { type: "string", description: "Site key" },
12 |             postType: { type: "string", description: "Post type (default: posts)" },
13 |             perPage: { type: "integer", description: "Number of posts to retrieve (default: 10)" },
14 |             page: { type: "integer", description: "Page number for pagination (default: 1)" }
15 |         },
16 |         required: ["siteKey", "postType"]
17 |     },
18 | 
19 |     async execute(args: { siteKey: string, postType: string, perPage?: number, page?: number }, site: WordPressSite) {
20 |         try {
21 |             const {restBase} = await apiGetRestBaseForPostType.execute(args, site)
22 | 
23 |             const credentials = Buffer.from(`${site.apiCredentials?.username}:${site.apiCredentials?.password}`).toString('base64');
24 |             const url = `${site.apiUrl}/wp/v2/${restBase}?per_page=${args.perPage || 10}&page=${args.page || 1}`;
25 | 
26 |             const response = await fetch(url, {
27 |                 method: 'GET',
28 |                 headers: {
29 |                     'Authorization': `Basic ${credentials}`,
30 |                     'Content-Type': 'application/json'
31 |                 }
32 |             });
33 | 
34 |             if (!response.ok) {
35 |                 const errorData = await response.json();
36 |                 throw new Error(`Failed to retrieve posts: ${errorData.message || response.statusText}.
37 | Requested URL: ${url}`);
38 |             }
39 | 
40 |             const data = await response.json();
41 |             return {
42 |                 pluginData: data,
43 |                 content: [{
44 |                     type: "text",
45 |                     text: `Posts retrieved successfully.\n\nPosts: ${JSON.stringify(data)}`
46 |                 }]
47 |             };
48 |         } catch (error) {
49 |             if (error instanceof Error) {
50 |                 throw new McpError(ErrorCode.InternalError, `wp_api_get_posts failed: ${error.message}`);
51 |             }
52 |             throw new McpError(ErrorCode.InternalError, 'wp_api_get_posts failed: Unknown error occurred');
53 |         }
54 |     }
55 | };
56 | 
```

--------------------------------------------------------------------------------
/src/tools/wp-api/get-plugins.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {WordPressSite} from "types/wp-sites";
 2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
 3 | 
 4 | export const apiGetPlugins = {
 5 |     name: "wp_api_get_plugins",
 6 |     description: "Get WordPress plugins (active, inactive, or all) using REST API",
 7 |     inputSchema: {
 8 |         type: "object",
 9 |         properties: {
10 |             siteKey: {type: "string", description: "Site key"},
11 |             status: {
12 |                 type: "string",
13 |                 enum: ["active", "inactive", "all"],
14 |                 description: "Filter plugins by status. Default is 'active'."
15 |             },
16 |         },
17 |         required: ["siteKey"],
18 |         additionalProperties: false
19 |     },
20 | 
21 |     async execute(args: { siteKey: string, status?: string }, site: WordPressSite) {
22 |         try {
23 |             const credentials = Buffer.from(`${site.apiCredentials?.username}:${site.apiCredentials?.password}`).toString('base64');
24 |             const url = `${site.apiUrl}/wp/v2/plugins`;
25 | 
26 |             // Status wtyczek domyślnie to "active", chyba że użytkownik określi inaczej
27 |             const filterStatus = args.status || "active";
28 | 
29 |             const response = await fetch(url, {
30 |                 method: 'GET',
31 |                 headers: {
32 |                     'Authorization': `Basic ${credentials}`,
33 |                     'Content-Type': 'application/json'
34 |                 }
35 |             });
36 | 
37 |             if (!response.ok) {
38 |                 const errorData = await response.json();
39 |                 throw new Error(`Failed to get plugins: ${errorData.message || response.statusText}.\nRequested URL: ${url}`);
40 |             }
41 | 
42 |             const data = await response.json();
43 | 
44 |             // Filtrujemy wtyczki zgodnie z wybranym statusem
45 |             let filteredPlugins = data;
46 |             if (filterStatus !== "all") {
47 |                 filteredPlugins = data.filter((plugin: { status: string }) => plugin.status === filterStatus);
48 |             }
49 | 
50 |             const pluginsList = filteredPlugins.map((plugin: any) => plugin.name + ' (' + plugin.plugin + ')')
51 | 
52 |             return {
53 |                 plugins: filteredPlugins,
54 |                 pluginsList,
55 |                 content: [{
56 |                     type: "text",
57 |                     text: `${filterStatus.charAt(0).toUpperCase() + filterStatus.slice(1)} plugins:\n${pluginsList.join('\n')}`
58 |                 }]
59 |             };
60 |         } catch (error) {
61 |             if (error instanceof Error) {
62 |                 throw new McpError(ErrorCode.InternalError, `wp_api_get_plugins failed: ${error.message}`);
63 |             }
64 |             throw new McpError(ErrorCode.InternalError, 'wp_api_get_plugins failed: Unknown error occurred');
65 |         }
66 |     }
67 | };
68 | 
```

--------------------------------------------------------------------------------
/src/tools/list-plugin-files.ts:
--------------------------------------------------------------------------------

```typescript
 1 | // src/tools/edit-file.ts
 2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
 3 | import fs from 'fs';
 4 | import path from 'path';
 5 | import {WordPressSite} from "../types/wp-sites";
 6 | import {listAvailablePluginsInSitePluginsPath} from "./list-available-plugins-in-site-plugins-path.js";
 7 | 
 8 | 
 9 | const listFiles = (dir: any, fileList: any[] = []) => {
10 |     if (path.basename(dir) === 'node_modules') {
11 |         return fileList; // Pomijamy katalog node_modules
12 |     }
13 | 
14 |     const files = fs.readdirSync(dir);
15 |     for (const file of files) {
16 |         const filePath = path.join(dir, file);
17 |         const stat = fs.statSync(filePath);
18 | 
19 |         if (stat.isDirectory()) {
20 |             listFiles(filePath, fileList);
21 |         } else {
22 |             fileList.push(filePath);
23 |         }
24 |     }
25 |     return fileList;
26 | }
27 | 
28 | 
29 | export const listPluginFiles = {
30 |     name: "wp_list_plugin_files",
31 |     description: "List a files for a specified plugin directory",
32 |     inputSchema: {
33 |         type: "object",
34 |         properties: {
35 |             siteKey: {type: "string", description: "Site key"},
36 |             pluginDirName: {type: "string", description: "Plugin directory name"}
37 |         },
38 |         required: ["pluginDirName", "siteKey"]
39 |     },
40 | 
41 |     async execute(args: { pluginDirName: string, siteKey: string }, site: WordPressSite) {
42 | 
43 |         if (args.pluginDirName === '.' || args.pluginDirName === '') {
44 |             throw new McpError(ErrorCode.InvalidParams, 'pluginDirName cannot be "." or empty (self directory).');
45 |         }
46 | 
47 |         const blockDir = path.join(site.pluginsPath, args.pluginDirName)
48 | 
49 |         if (!fs.existsSync(blockDir)) {
50 |             const {content: [{directories}]} = await listAvailablePluginsInSitePluginsPath.execute({siteKey: args.siteKey}, site)
51 |             return {
52 |                 isError: true,
53 |                 directories,
54 |                 content: [{
55 |                     type: "text",
56 |                     text: `Directory ${blockDir} not exists. Please review a plugin directory. Available plugins in ${site.pluginsPath}:\n\n${directories.join('\n')}`
57 |                 }]
58 |             };
59 |         }
60 | 
61 |         try {
62 | 
63 |             const files = listFiles(blockDir)
64 | 
65 |             return {
66 |                 files,
67 |                 content: [{
68 |                     type: "text",
69 |                     text: `Files at ${blockDir}:\n\n` + files.map((f) => f.replace(blockDir, '')).join('\n')
70 |                 }]
71 |             };
72 |         } catch (error) {
73 |             if (error instanceof Error) {
74 |                 throw new McpError(ErrorCode.InternalError, error.message);
75 |             }
76 |             throw new McpError(ErrorCode.InternalError, 'wp_list_plugin_files: Unknown error occurred');
77 |         }
78 |     }
79 | };
```

--------------------------------------------------------------------------------
/src/tools/wp-api/activate-plugin.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {WordPressSite} from "types/wp-sites";
 2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
 3 | import {apiGetPlugins} from "tools/wp-api/get-plugins";
 4 | 
 5 | export const apiActivatePlugin = {
 6 |     name: "wp_api_activate_plugin",
 7 |     description: "Activates a WordPress plugin using REST API",
 8 |     inputSchema: {
 9 |         type: "object",
10 |         properties: {
11 |             siteKey: {type: "string", description: "Site key"},
12 |             pluginSlug: {type: "string", description: "Plugin slug to activate"}
13 |         },
14 |         required: ["pluginSlug", "siteKey"]
15 |     },
16 | 
17 |     async execute(args: { siteKey: string; pluginSlug: string }, site: WordPressSite) {
18 |         try {
19 |             const pluginSlug = args.pluginSlug.replace('.php', '')
20 | 
21 |             const allPlugins = await apiGetPlugins.execute({...args, status: 'all'}, site)
22 |             if (!allPlugins.plugins.find((p: any) => p.plugin === pluginSlug)) {
23 |                 return {
24 |                     isError: true,
25 |                     content: [{
26 |                         type: "text",
27 |                         text: `Plugin ${args.pluginSlug} is invalid. Available plugins:\n\n${allPlugins.pluginsList.join('\n')}. Please try again with correct plugin slug.`
28 |                     }]
29 |                 };
30 |             }
31 | 
32 |             const credentials = Buffer.from(`${site.apiCredentials?.username}:${site.apiCredentials?.password}`).toString('base64');
33 |             const url = `${site.apiUrl}/wp/v2/plugins/${pluginSlug}`
34 | 
35 |             const response = await fetch(url, {
36 |                 method: 'PUT',
37 |                 headers: {
38 |                     'Authorization': `Basic ${credentials}`,
39 |                     'Content-Type': 'application/json'
40 |                 },
41 |                 body: JSON.stringify({
42 |                     status: 'active'
43 |                 })
44 |             });
45 | 
46 |             if (!response.ok) {
47 |                 const errorData = await response.json();
48 |                 throw new Error(`Failed to activate plugin: ${errorData.message || response.statusText}.\nRequested URL: ${url}`);
49 |             }
50 | 
51 |             const data = await response.json();
52 | 
53 | 
54 |             return {
55 |                 pluginData: data,
56 |                 content: [{
57 |                     type: "text",
58 |                     text: `Plugin ${args.pluginSlug} activated successfully.\n\nPlugin: ${data}`
59 |                 }]
60 |             };
61 |         } catch (error) {
62 |             if (error instanceof Error) {
63 |                 throw new McpError(ErrorCode.InternalError, `wp_api_activate_plugin failed: ${error.message}`);
64 |             }
65 |             throw new McpError(ErrorCode.InternalError, 'wp_api_activate_plugin failed: Unknown error occurred');
66 |         }
67 |     }
68 | };
```

--------------------------------------------------------------------------------
/src/tools/wp-api/deactivate-plugin.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {WordPressSite} from "types/wp-sites";
 2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
 3 | import {apiGetPlugins} from "tools/wp-api/get-plugins";
 4 | 
 5 | export const apiDeactivatePlugin = {
 6 |     name: "wp_api_deactivate_plugin",
 7 |     description: "Deactivates a WordPress plugin using REST API",
 8 |     inputSchema: {
 9 |         type: "object",
10 |         properties: {
11 |             siteKey: {type: "string", description: "Site key"},
12 |             pluginSlug: {type: "string", description: "Plugin slug to deactivate"}
13 |         },
14 |         required: ["pluginSlug", "siteKey"]
15 |     },
16 | 
17 |     async execute(args: { siteKey: string; pluginSlug: string }, site: WordPressSite) {
18 |         try {
19 |             const pluginSlug = args.pluginSlug.replace('.php', '')
20 | 
21 |             const allPlugins = await apiGetPlugins.execute({...args, status: 'active'}, site)
22 |             if (!allPlugins.plugins.find((p: any) => p.plugin === pluginSlug)) {
23 |                 return {
24 |                     isError: true,
25 |                     content: [{
26 |                         type: "text",
27 |                         text: `Plugin ${args.pluginSlug} is not active or invalid. Active plugins:\n${allPlugins.pluginsList.join('\n')}. Please try again with a correct plugin slug.`
28 |                     }]
29 |                 };
30 |             }
31 | 
32 |             const credentials = Buffer.from(`${site.apiCredentials?.username}:${site.apiCredentials?.password}`).toString('base64');
33 |             const url = `${site.apiUrl}/wp/v2/plugins/${pluginSlug}`
34 | 
35 |             const response = await fetch(url, {
36 |                 method: 'PUT',
37 |                 headers: {
38 |                     'Authorization': `Basic ${credentials}`,
39 |                     'Content-Type': 'application/json'
40 |                 },
41 |                 body: JSON.stringify({
42 |                     status: 'inactive'
43 |                 })
44 |             });
45 | 
46 |             if (!response.ok) {
47 |                 const errorData = await response.json();
48 |                 throw new Error(`Failed to deactivate plugin: ${errorData.message || response.statusText}.\nRequested URL: ${url}`);
49 |             }
50 | 
51 |             const data = await response.json();
52 | 
53 |             return {
54 |                 pluginData: data,
55 |                 content: [{
56 |                     type: "text",
57 |                     text: `Plugin ${args.pluginSlug} deactivated successfully.\n\nPlugin: ${data}`
58 |                 }]
59 |             };
60 |         } catch (error) {
61 |             if (error instanceof Error) {
62 |                 throw new McpError(ErrorCode.InternalError, `wp_api_deactivate_plugin failed: ${error.message}`);
63 |             }
64 |             throw new McpError(ErrorCode.InternalError, 'wp_api_deactivate_plugin failed: Unknown error occurred');
65 |         }
66 |     }
67 | };
68 | 
```

--------------------------------------------------------------------------------
/src/tools/wp-api/site-settings/update-integer-site-setting.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {WordPressSite} from "types/wp-sites";
 2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
 3 | 
 4 | // Lista dozwolonych kluczy ustawień zgodnie z dokumentacją WordPress REST API
 5 | const VALID_SETTING_KEYS = [
 6 |     "post_per_page", "page_on_front", "page_for_posts", "site_logo", "site_icon", "default_category", "start_of_week"
 7 | ] as const;
 8 | 
 9 | type SettingKey = (typeof VALID_SETTING_KEYS)[number];
10 | 
11 | export const apiUpdateIntegerSiteSetting = {
12 |     name: "wp_api_update_integer_site_setting",
13 |     description: "Updates a single WordPress site setting in integer format using REST API",
14 |     inputSchema: {
15 |         type: "object",
16 |         properties: {
17 |             siteKey: {type: "string", description: "Site key"},
18 |             settingKey: {
19 |                 type: "string",
20 |                 enum: VALID_SETTING_KEYS, // Walidacja poprawnych kluczy
21 |                 description: "The key of the setting to update"
22 |             },
23 |             settingValue: {type: "integer", description: "The new value for the setting"}
24 |         },
25 |         required: ["siteKey", "settingKey", "settingValue"]
26 |     },
27 | 
28 |     async execute(args: { siteKey: string; settingKey: SettingKey; settingValue: any }, site: WordPressSite) {
29 |         try {
30 | 
31 |             if (!VALID_SETTING_KEYS.includes(args.settingKey)) {
32 |                 throw new Error(`Invalid setting key: ${args.settingKey}. Allowed values: ${VALID_SETTING_KEYS.join(", ")}`);
33 |             }
34 | 
35 |             const credentials = Buffer.from(`${site.apiCredentials?.username}:${site.apiCredentials?.password}`).toString('base64');
36 |             const url = `${site.apiUrl}/wp/v2/settings`;
37 | 
38 |             const response = await fetch(url, {
39 |                 method: 'POST',
40 |                 headers: {
41 |                     'Authorization': `Basic ${credentials}`,
42 |                     'Content-Type': 'application/json'
43 |                 },
44 |                 body: JSON.stringify({[args.settingKey]: args.settingValue})
45 |             });
46 | 
47 |             if (!response.ok) {
48 |                 const errorData = await response.json();
49 |                 throw new Error(`Failed to update setting: ${errorData.message || response.statusText}`);
50 |             }
51 | 
52 |             const data = await response.json();
53 | 
54 |             return {
55 |                 settings: data,
56 |                 content: [{
57 |                     type: "text",
58 |                     text: `Site settings updated successfully.\n\nNew settings object: ${JSON.stringify(data, null, 2)}`
59 |                 }]
60 |             };
61 | 
62 |         } catch (error) {
63 |             if (error instanceof Error) {
64 |                 throw new McpError(ErrorCode.InternalError, `wp_api_update_integer_site_setting failed: ${error.message}`);
65 |             }
66 |             throw new McpError(ErrorCode.InternalError, 'wp_api_update_integer_site_setting failed: Unknown error occurred');
67 |         }
68 |     }
69 | };
70 | 
```

--------------------------------------------------------------------------------
/src/tools/wp-api/site-settings/update-string-site-setting.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {WordPressSite} from "types/wp-sites";
 2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
 3 | 
 4 | // Lista dozwolonych kluczy ustawień zgodnie z dokumentacją WordPress REST API
 5 | const VALID_SETTING_KEYS = [
 6 |     "title", "description", "url", "email", "timezone", "date_format", "time_format",
 7 |     "language", "default_post_format", "show_on_front",
 8 |     "default_ping_status", "default_comment_status"
 9 | ] as const;
10 | 
11 | type SettingKey = (typeof VALID_SETTING_KEYS)[number];
12 | 
13 | export const apiUpdateStringSiteSetting = {
14 |     name: "wp_api_update_string_site_setting",
15 |     description: "Updates a single WordPress site setting in string format using REST API",
16 |     inputSchema: {
17 |         type: "object",
18 |         properties: {
19 |             siteKey: {type: "string", description: "Site key"},
20 |             settingKey: {
21 |                 type: "string",
22 |                 enum: VALID_SETTING_KEYS, // Walidacja poprawnych kluczy
23 |                 description: "The key of the setting to update"
24 |             },
25 |             settingValue: {type: "string", description: "The new value for the setting"}
26 |         },
27 |         required: ["siteKey", "settingKey", "settingValue"]
28 |     },
29 | 
30 |     async execute(args: { siteKey: string; settingKey: SettingKey; settingValue: any }, site: WordPressSite) {
31 |         try {
32 | 
33 |             if (!VALID_SETTING_KEYS.includes(args.settingKey)) {
34 |                 throw new Error(`Invalid setting key: ${args.settingKey}. Allowed values: ${VALID_SETTING_KEYS.join(", ")}`);
35 |             }
36 | 
37 |             const credentials = Buffer.from(`${site.apiCredentials?.username}:${site.apiCredentials?.password}`).toString('base64');
38 |             const url = `${site.apiUrl}/wp/v2/settings`;
39 | 
40 |             const response = await fetch(url, {
41 |                 method: 'POST',
42 |                 headers: {
43 |                     'Authorization': `Basic ${credentials}`,
44 |                     'Content-Type': 'application/json'
45 |                 },
46 |                 body: JSON.stringify({[args.settingKey]: args.settingValue})
47 |             });
48 | 
49 |             if (!response.ok) {
50 |                 const errorData = await response.json();
51 |                 throw new Error(`Failed to update setting: ${errorData.message || response.statusText}`);
52 |             }
53 | 
54 |             const data = await response.json();
55 | 
56 |             return {
57 |                 settings: data,
58 |                 content: [{
59 |                     type: "text",
60 |                     text: `Site settings updated successfully.\n\nNew settings object: ${JSON.stringify(data, null, 2)}`
61 |                 }]
62 |             };
63 | 
64 |         } catch (error) {
65 |             if (error instanceof Error) {
66 |                 throw new McpError(ErrorCode.InternalError, `wp_api_update_string_site_setting failed: ${error.message}`);
67 |             }
68 |             throw new McpError(ErrorCode.InternalError, 'wp_api_update_string_site_setting failed: Unknown error occurred');
69 |         }
70 |     }
71 | };
72 | 
```

--------------------------------------------------------------------------------
/src/tools/wp-api/update-post-status.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {WordPressSite} from "types/wp-sites";
 2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
 3 | import {apiGetRestBaseForPostType} from "./get-rest-base-for-post-types.js";
 4 | 
 5 | export const apiUpdatePostStatus = {
 6 |     name: "wp_api_update_post_status",
 7 |     description: "Update the status of an existing WordPress post, page, or custom post type item using REST API",
 8 |     inputSchema: {
 9 |         type: "object",
10 |         properties: {
11 |             siteKey: {type: "string", description: "Site key"},
12 |             postId: {type: "integer", description: "ID of the post to update"},
13 |             postType: {type: "string", description: "Post type (default: posts)"},
14 |             status: {type: "string", enum: ["publish", "draft"], description: "Status to set (publish or draft)"},
15 |             publishDate: {
16 |                 type: "string",
17 |                 format: "date-time",
18 |                 description: "Optional scheduled publication date in ISO 8601 format"
19 |             }
20 |         },
21 |         required: ["siteKey", "postId", "postType", "status"]
22 |     },
23 | 
24 |     async execute(args: {
25 |         siteKey: string,
26 |         postId: number,
27 |         postType: string,
28 |         status: string,
29 |         publishDate?: string
30 |     }, site: WordPressSite) {
31 |         try {
32 |             const {restBase} = await apiGetRestBaseForPostType.execute(args, site)
33 | 
34 |             const credentials = Buffer.from(`${site.apiCredentials?.username}:${site.apiCredentials?.password}`).toString('base64');
35 |             const url = `${site.apiUrl}/wp/v2/${restBase}/${args.postId}`;
36 | 
37 |             const bodyData: any = {status: args.status};
38 |             if (args.status === "publish" && args.publishDate) {
39 |                 bodyData.date = args.publishDate;
40 |             }
41 | 
42 |             const response = await fetch(url, {
43 |                 method: 'POST',
44 |                 headers: {
45 |                     'Authorization': `Basic ${credentials}`,
46 |                     'Content-Type': 'application/json'
47 |                 },
48 |                 body: JSON.stringify(bodyData)
49 |             });
50 | 
51 |             if (!response.ok) {
52 |                 const errorData = await response.json();
53 |                 throw new Error(`Failed to update post status: ${errorData.message || response.statusText}.
54 | Requested URL: ${url}`);
55 |             }
56 | 
57 |             const data = await response.json();
58 |             return {
59 |                 pluginData: data,
60 |                 content: [{
61 |                     type: "text",
62 |                     text: `Post status updated successfully.\nPost #ID: ${data.id}\nPost URL: ${data.link}\nPost status: ${data.status}\nPost type: ${data.type}\nPost title: ${data.title?.rendered}`
63 |                 }]
64 |             };
65 |         } catch (error) {
66 |             if (error instanceof Error) {
67 |                 throw new McpError(ErrorCode.InternalError, `wp_api_update_post_status failed: ${error.message}`);
68 |             }
69 |             throw new McpError(ErrorCode.InternalError, 'wp_api_update_post_status failed: Unknown error occurred');
70 |         }
71 |     }
72 | };
73 | 
```

--------------------------------------------------------------------------------
/src/tools/wp-api/update-post.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {WordPressSite} from "types/wp-sites";
 2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
 3 | import {apiGetRestBaseForPostType} from "./get-rest-base-for-post-types.js";
 4 | 
 5 | export const apiUpdatePost = {
 6 |     name: "wp_api_update_post",
 7 |     description: "Update the settings (i.e. template, title, excerpt) of a WordPress post with post type using REST API. THIS TOOL IS NOT DESIGNED TO UPDATE POST CONTENT.",
 8 |     inputSchema: {
 9 |         type: "object",
10 |         properties: {
11 |             siteKey: {type: "string", description: "Site key"},
12 |             postId: {type: "number", description: "Post ID to update"},
13 |             postType: {type: "string", description: "Type of the post (e.g., posts, pages)"},
14 |             template: {type: "string", description: "Optional: Post template"},
15 |             title: {type: "string", description: "Optional: Post title"},
16 |             excerpt: {type: "string", description: "Optional: Post excerpt"},
17 |         },
18 |         required: ["siteKey", "postId", "postType"]
19 |     },
20 | 
21 |     async execute(args: {
22 |         siteKey: string,
23 |         postId: number,
24 |         postType: string,
25 |         title?: string,
26 |         template?: string
27 |         excerpt?: string
28 |     }, site: WordPressSite) {
29 |         try {
30 | 
31 |             const {restBase} = await apiGetRestBaseForPostType.execute(args, site)
32 | 
33 |             const credentials = Buffer.from(`${site.apiCredentials?.username}:${site.apiCredentials?.password}`).toString('base64');
34 |             const url = `${site.apiUrl}/wp/v2/${restBase}/${args.postId}`;
35 | 
36 |             const bodyData: any = {}
37 |             if (args.title) {
38 |                 bodyData.title = args.title
39 |             }
40 |             if (args.template) {
41 |                 bodyData.template = args.template
42 |             }
43 |             if (args.excerpt) {
44 |                 bodyData.excerpt = args.excerpt
45 |             }
46 |             // @ts-ignore
47 |             if (args.content) {
48 |                 throw new Error(`Failed. To update post content use special tool: wp_api_update_post_content`);
49 |             }
50 | 
51 |             const response = await fetch(url, {
52 |                 method: 'POST',
53 |                 headers: {
54 |                     'Authorization': `Basic ${credentials}`,
55 |                     'Content-Type': 'application/json'
56 |                 },
57 |                 body: JSON.stringify(bodyData)
58 |             });
59 | 
60 |             if (!response.ok) {
61 |                 const errorData = await response.json();
62 |                 throw new Error(`Failed to update post: ${errorData.message || response.statusText}.\nRequested URL: ${url}`);
63 |             }
64 | 
65 |             const updatedPost = await response.json();
66 | 
67 |             return {
68 |                 updatedPost,
69 |                 content: [{
70 |                     type: "text",
71 |                     text: `Post settings updated successfully! Preview URL: ${updatedPost.link}.`
72 |                 }]
73 |             };
74 |         } catch (error) {
75 |             if (error instanceof Error) {
76 |                 throw new McpError(ErrorCode.InternalError, `wp_api_update_post failed: ${error.message}`);
77 |             }
78 |             throw new McpError(ErrorCode.InternalError, 'wp_api_update_post failed: Unknown error occurred');
79 |         }
80 |     }
81 | };
82 | 
```

--------------------------------------------------------------------------------
/src/tools/wp-api/update-post-content.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {WordPressSite} from "types/wp-sites";
 2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
 3 | import {apiGetRestBaseForPostType} from "./get-rest-base-for-post-types.js";
 4 | import {apiGetPost} from "tools/wp-api/get-post";
 5 | 
 6 | export const apiUpdatePostContent = {
 7 |     name: "wp_api_update_post_content",
 8 |     description: "Update the content of a WordPress post with post type using REST API",
 9 |     inputSchema: {
10 |         type: "object",
11 |         properties: {
12 |             siteKey: {type: "string", description: "Site key"},
13 |             postId: {type: "number", description: "Post ID to update"},
14 |             postType: {type: "string", description: "Type of the post (e.g., posts, pages)"},
15 |             content: {type: "string", description: "Optional: New content for the post (only if content changed)"},
16 |             readCurrentContent: {
17 |                 type: "boolean",
18 |                 description: "Determine, you need a current version of post content to update context or just update content."
19 |             },
20 |         },
21 |         required: ["siteKey", "postId", "postType", "readCurrentContent"]
22 |     },
23 | 
24 |     async execute(args: {
25 |         siteKey: string,
26 |         postId: number,
27 |         postType: string,
28 |         content: string,
29 |         readCurrentContent: boolean
30 |     }, site: WordPressSite) {
31 |         try {
32 | 
33 |             if (args.readCurrentContent) {
34 |                 const {post} = await apiGetPost.execute(args, site)
35 |                 return {
36 |                     isError: true,
37 |                     content: [{
38 |                         type: "text",
39 |                         text: `Please generate a new post content from current content revision:\n\n${post?.content?.rendered}`
40 |                     }]
41 |                 };
42 |             }
43 | 
44 |             const {restBase} = await apiGetRestBaseForPostType.execute(args, site)
45 | 
46 |             const credentials = Buffer.from(`${site.apiCredentials?.username}:${site.apiCredentials?.password}`).toString('base64');
47 |             const url = `${site.apiUrl}/wp/v2/${restBase}/${args.postId}`;
48 | 
49 |             const bodyData: any = {
50 |                 content: args.content
51 |             }
52 | 
53 |             const response = await fetch(url, {
54 |                 method: 'POST',
55 |                 headers: {
56 |                     'Authorization': `Basic ${credentials}`,
57 |                     'Content-Type': 'application/json'
58 |                 },
59 |                 body: JSON.stringify(bodyData)
60 |             });
61 | 
62 |             if (!response.ok) {
63 |                 const errorData = await response.json();
64 |                 throw new Error(`Failed to update post content: ${errorData.message || response.statusText}.\nRequested URL: ${url}`);
65 |             }
66 | 
67 |             const updatedPost = await response.json();
68 | 
69 |             return {
70 |                 updatedPost,
71 |                 content: [{
72 |                     type: "text",
73 |                     text: `Post content updated successfully! Preview URL: ${updatedPost.link}.`
74 |                 }]
75 |             };
76 |         } catch (error) {
77 |             if (error instanceof Error) {
78 |                 throw new McpError(ErrorCode.InternalError, `wp_api_update_post_content failed: ${error.message}`);
79 |             }
80 |             throw new McpError(ErrorCode.InternalError, 'wp_api_update_post_content failed: Unknown error occurred');
81 |         }
82 |     }
83 | };
84 | 
```

--------------------------------------------------------------------------------
/src/tools/build-block.ts:
--------------------------------------------------------------------------------

```typescript
 1 | // src/tools/build-block.ts
 2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
 3 | import {execSync} from 'child_process';
 4 | import {isGutenbergBlock} from "../helpers.js";
 5 | import {WordPressSite} from "../types/wp-sites";
 6 | import path from "path";
 7 | 
 8 | export interface BuildBlockArgs {
 9 |     blockPluginDirname: string;
10 |     siteKey: string;
11 | }
12 | 
13 | export const buildBlock = {
14 |     name: "wp_build_block",
15 |     description: "Builds a Gutenberg block using npm run build",
16 |     inputSchema: {
17 |         type: "object",
18 |         properties: {
19 |             siteKey: {type: "string", description: "Site key"},
20 |             blockPluginDirname: {
21 |                 type: "string",
22 |                 description: "Block plugin directory name."
23 |             }
24 |         },
25 |         required: ["blockPluginDirname", "siteKey"]
26 |     },
27 | 
28 |     execute: async (args: BuildBlockArgs, site: WordPressSite) => {
29 | 
30 |         const blockDir = path.join(site.pluginsPath, args.blockPluginDirname);
31 | 
32 |         if (!(await isGutenbergBlock(blockDir))) {
33 |             throw new Error(`wp_build_block failed: directory ${blockDir} does not contain a Gutenberg block. Are you sure ${blockDir} is valid?`);
34 |         }
35 | 
36 |         try {
37 | 
38 |             try {
39 |                 execSync('npm install --no-audit --no-fund --silent', {
40 |                     cwd: blockDir,
41 |                     stdio: ['pipe', 'pipe', 'pipe'],
42 |                     shell: '/bin/bash'
43 |                 });
44 |             } catch (error) {
45 |                 throw new Error(`wp_build_block: npm install failed with error - ${error instanceof Error ? error.message : String(error)} in ${blockDir}`);
46 |             }
47 | 
48 |             try {
49 | 
50 |                 const buildOutput = execSync('npm run build', {
51 |                     cwd: blockDir,
52 |                     stdio: ['pipe', 'pipe', 'pipe'],
53 |                     shell: '/bin/bash',
54 |                     encoding: 'utf-8'
55 |                 });
56 | 
57 |                 const relevantOutput = buildOutput
58 |                     .split('\n')
59 |                     .filter(line =>
60 |                         line.includes('webpack') ||
61 |                         line.includes('compiled') ||
62 |                         line.includes('error') ||
63 |                         line.includes('warning')
64 |                     )
65 |                     .join('\n');
66 | 
67 |                 return {
68 |                     content: [{
69 |                         type: "text",
70 |                         text: `✅ Block "${args.blockPluginDirname}" built successfully!\n\nBuild summary:\n${relevantOutput}`
71 |                     }]
72 |                 };
73 |             } catch (error) {
74 |                 const errorOutput = error instanceof Error ? error.message : String(error);
75 |                 const formattedError = errorOutput
76 |                     .split('\n')
77 |                     .filter(line =>
78 |                         line.includes('error') ||
79 |                         line.includes('failed') ||
80 |                         line.includes('webpack')
81 |                     )
82 |                     .join('\n');
83 | 
84 |                 throw new Error(`wp_build_block failed:\n${formattedError}\n\nTry to fix a problem, but do not create a new structure on your own.`);
85 |             }
86 |         } catch (error) {
87 |             if (error instanceof Error) {
88 |                 throw new McpError(ErrorCode.InternalError, error.message);
89 |             }
90 |             throw new McpError(ErrorCode.InternalError, 'wp_build_block failed: Unknown error occurred');
91 |         }
92 |     }
93 | };
```

--------------------------------------------------------------------------------
/src/tools/wp-cli/check_and_instal_wp_cli.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {execSync} from "child_process";
 2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
 3 | import path from "path";
 4 | import {WordPressSite} from "types/wp-sites";
 5 | 
 6 | export const checkAndInstallWPCLI = {
 7 |     name: "check_and_install_wp_cli",
 8 |     description: "Checks if WP-CLI is installed. If not, installs it locally.",
 9 |     inputSchema: {
10 |         type: "object",
11 |         properties: {
12 |             siteKey: {type: "string", description: "Site key"}
13 |         },
14 |         required: []
15 |     },
16 | 
17 |     async execute(args: any, site: WordPressSite) {
18 |         try {
19 |             // Sprawdzenie, czy PHP jest zainstalowane
20 |             let isPhpInstalled;
21 |             try {
22 |                 execSync("php -v", {stdio: "pipe"}); // Sprawdzenie wersji PHP
23 |                 isPhpInstalled = true;
24 |             } catch (error) {
25 |                 isPhpInstalled = false;
26 |             }
27 | 
28 |             if (!isPhpInstalled) {
29 |                 // Jeśli PHP nie jest zainstalowane, zwróć instrukcje
30 |                 return {
31 |                     content: [{
32 |                         type: "text",
33 |                         text: "❌ PHP is not installed. Please install PHP manually before proceeding. \n\n" +
34 |                             "You can install PHP by running the following commands:\n" +
35 |                             "1. On macOS (using Homebrew): `brew install php`\n" +
36 |                             "2. On Ubuntu/Debian: `sudo apt update && sudo apt install php`\n" +
37 |                             "3. On Windows, download PHP from https://windows.php.net/download/."
38 |                     }]
39 |                 };
40 |             }
41 | 
42 |             // Sprawdzenie, czy WP-CLI jest zainstalowane
43 |             let isInstalled;
44 |             try {
45 |                 execSync("wp --info", {stdio: "pipe"}); // Jeśli WP-CLI jest zainstalowane, to nie rzuci wyjątkiem
46 |                 isInstalled = true;
47 |             } catch (error) {
48 |                 isInstalled = false;
49 |             }
50 | 
51 |             if (isInstalled) {
52 |                 return {
53 |                     content: [{type: "text", text: "✅ WP-CLI is already installed."}]
54 |                 };
55 |             }
56 | 
57 |             // Jeśli WP-CLI nie jest zainstalowane, pobierz i zainstaluj lokalnie
58 |             const wpCliPath = path.resolve(site.path, "wp-cli.phar");
59 | 
60 |             // Pobierz WP-CLI i ustaw odpowiednie uprawnienia
61 |             execSync(`
62 |                 curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar &&
63 |                 chmod +x wp-cli.phar
64 |             `, {stdio: "inherit", cwd: site.path}); // stdio: "inherit" pozwala na wyświetlanie wyników w terminalu
65 | 
66 |             // Sprawdzenie, czy WP-CLI działa poprawnie
67 |             let isNowInstalled;
68 |             try {
69 |                 execSync(`php "${wpCliPath}" --info`, {stdio: "pipe"});
70 |                 isNowInstalled = true;
71 |             } catch (error) {
72 |                 isNowInstalled = false;
73 |             }
74 | 
75 |             if (!isNowInstalled) {
76 |                 throw new Error(`WP-CLI installation failed. CLI Path: ${wpCliPath}`);
77 |             }
78 | 
79 |             return {
80 |                 content: [{type: "text", text: "✅ WP-CLI was successfully installed locally."}]
81 |             };
82 | 
83 |         } catch (error) {
84 |             if (error instanceof Error) {
85 |                 throw new McpError(ErrorCode.InternalError, `check_and_install_wp_cli failed: ${error.message}`);
86 |             }
87 |             throw new McpError(ErrorCode.InternalError, 'check_and_install_wp_cli failed: Unknown error occurred');
88 |         }
89 |     }
90 | };
91 | 
```

--------------------------------------------------------------------------------
/src/tools/scaffold-block.ts:
--------------------------------------------------------------------------------

```typescript
 1 | // src/tools/scaffold-block.ts
 2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
 3 | import {execSync} from 'child_process';
 4 | import path from "path";
 5 | import fs from 'fs';
 6 | import {WordPressSite} from "../types/wp-sites";
 7 | import {listPluginFiles} from "./list-plugin-files.js";
 8 | import {buildBlock} from "./build-block.js";
 9 | 
10 | interface ScaffoldArgs {
11 |     name: string;
12 |     siteKey: string;
13 |     variant: string;
14 |     namespace: string;
15 | }
16 | 
17 | export const scaffoldBlockTool = {
18 |     name: "wp_scaffold_block",
19 |     description: "Creates and builds a new Gutenberg block using @wordpress/create-block",
20 |     inputSchema: {
21 |         type: "object",
22 |         properties: {
23 |             siteKey: {type: "string", description: "Site key"},
24 |             name: {type: "string", description: "Block name"},
25 |             variant: {
26 |                 type: "string",
27 |                 enum: ["static", "dynamic"],
28 |                 description: "Gutenberg block template variant (static or dynamic). Default: static"
29 |             },
30 |             namespace: {type: "string", description: "Gutenberg block namespace"}
31 |         },
32 |         required: ["name", "siteKey", "variant", "namespace"]
33 |     },
34 |     execute: async (args: ScaffoldArgs, site: WordPressSite) => {
35 | 
36 |         if (typeof args.name !== 'string') {
37 |             throw new McpError(ErrorCode.InvalidParams, 'Block name must be a string');
38 |         }
39 | 
40 |         const blockDirname = args.name.toLowerCase().replace(/ /g, '-')
41 |         const namespace = args.namespace.toLowerCase().replace(/ /g, '-')
42 |         const blockDir = path.join(site.pluginsPath, blockDirname)
43 |         const pluginDir = site.pluginsPath
44 | 
45 |         if (fs.existsSync(blockDir)) {
46 |             throw new McpError(ErrorCode.InvalidRequest, `wp_scaffold_block failed: directory ${blockDir} already exists. Please change a block name or remove existing plugin!`);
47 |         }
48 | 
49 |         try {
50 | 
51 |             const variant = args.variant === 'dynamic' ? 'dynamic' : 'static'
52 |             const createBlockCommand = `npx @wordpress/create-block ${args.name} --variant ${variant} --namespace ${namespace}`
53 | 
54 |             execSync(createBlockCommand, {
55 |                 stdio: ['pipe', 'pipe', 'pipe'],
56 |                 encoding: 'utf-8',
57 |                 cwd: pluginDir
58 |             });
59 | 
60 |             const buildResult = await buildBlock.execute({
61 |                 blockPluginDirname: blockDirname,
62 |                 siteKey: args.siteKey
63 |             }, site);
64 | 
65 |             const listedPluginFiles = await listPluginFiles.execute({
66 |                 pluginDirName: path.basename(blockDir),
67 |                 siteKey: args.siteKey
68 |             }, site);
69 | 
70 |             if (listedPluginFiles.isError || !listedPluginFiles?.files) {
71 |                 throw new McpError(ErrorCode.InternalError, `wp_scaffold_block failed: directory ${path.basename(blockDir)} is unreachable or files not created.`);
72 |             }
73 | 
74 |             return {
75 |                 content: [{
76 |                     type: "text",
77 |                     text: `Block "${args.name}" created and built successfully.\n\nCreated files: ${listedPluginFiles?.files?.join('\n')}.\n\nDo you want to do any changes in this block or activate WordPress plugin and create a test page in WordPress with this block embedded?`
78 |                 }]
79 |             };
80 |         } catch (error) {
81 |             console.error('Block scaffolding error:', error);
82 |             if (error instanceof Error) {
83 |                 throw new McpError(ErrorCode.InternalError, `Failed to create/build block: ${error.message}`);
84 |             }
85 |             throw new McpError(ErrorCode.InternalError, 'Unknown error occurred while creating/building block');
86 |         }
87 |     }
88 | };
```

--------------------------------------------------------------------------------
/src/tools/wp-api/create-post.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {WordPressSite} from "types/wp-sites";
  2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
  3 | import {apiGetPostTypes} from "./get-post-types.js";
  4 | import {apiGetRestBaseForPostType} from "./get-rest-base-for-post-types.js";
  5 | import {apiGetTemplates} from "./get-templates.js";
  6 | 
  7 | export const apiCreatePost = {
  8 |     name: "wp_api_create_post",
  9 |     description: "Create a WordPress post, page or custom post type item using REST API",
 10 |     inputSchema: {
 11 |         type: "object",
 12 |         properties: {
 13 |             siteKey: {type: "string", description: "Site key"},
 14 |             postType: {type: "string", description: "Post type (default: pages)"},
 15 |             title: {type: "string", description: "Post title"},
 16 |             content: {type: "string", description: "Post content"},
 17 |             parent: {type: "integer", description: "Post parent ID"},
 18 |             template: {type: "string", description: "Optional: Post template"},
 19 |         },
 20 |         required: ["postType", "siteKey", "title"]
 21 |     },
 22 | 
 23 |     async execute(args: {
 24 |         siteKey: string,
 25 |         postType: string,
 26 |         title: string,
 27 |         content: string,
 28 |         parent: Number,
 29 |         template?: string
 30 |     }, site: WordPressSite) {
 31 |         try {
 32 | 
 33 |             const {restBase} = await apiGetRestBaseForPostType.execute(args, site)
 34 | 
 35 | 
 36 |             const credentials = Buffer.from(`${site.apiCredentials?.username}:${site.apiCredentials?.password}`).toString('base64');
 37 |             const url = `${site.apiUrl}/wp/v2/${restBase}`
 38 | 
 39 |             const bodyData: any = {
 40 |                 title: args.title,
 41 |                 status: 'draft',
 42 |                 content: args.content,
 43 |                 parent: args.parent,
 44 |             }
 45 | 
 46 |             if (args.template) {
 47 |                 const {templates, templatesList} = await apiGetTemplates.execute(args, site)
 48 |                 if (!templates.find((template: any) => template.slug === args.template)) {
 49 |                     throw new Error(`Invalid template.\nUse one of the following:\n${templatesList.join('\n')}`);
 50 |                 }
 51 |                 bodyData.template = args.template
 52 |             }
 53 | 
 54 |             const response = await fetch(url, {
 55 |                 method: 'POST',
 56 |                 headers: {
 57 |                     'Authorization': `Basic ${credentials}`,
 58 |                     'Content-Type': 'application/json'
 59 |                 },
 60 |                 body: JSON.stringify(bodyData)
 61 |             });
 62 | 
 63 |             if (!response.ok) {
 64 |                 const errorData = await response.json();
 65 | 
 66 |                 if (errorData.message.includes('No route was found matching the URL and request method')) {
 67 |                     const postTypes = await apiGetPostTypes.execute({
 68 |                         siteKey: args.siteKey
 69 |                     }, site)
 70 | 
 71 |                     return {
 72 |                         isError: true,
 73 |                         content: [
 74 |                             {
 75 |                                 type: "text",
 76 |                                 text: `Probably ${args.postType} is invalid.\nUsed REST API URL: ${url}.\nPlease select a proper post type from a list: ${Object.keys(postTypes.postTypes).join(', ')}.`
 77 |                             }
 78 |                         ]
 79 |                     }
 80 |                 }
 81 |                 throw new Error(`Failed to create post: ${errorData.message || response.statusText}.\nRequested URL: ${url}`);
 82 |             }
 83 | 
 84 |             const data = await response.json();
 85 | 
 86 |             return {
 87 |                 pluginData: data,
 88 |                 content: [{
 89 |                     type: "text",
 90 |                     text: `Post created successfully.\n\nPost: ${JSON.stringify(data, null, 2)}`
 91 |                 }]
 92 |             };
 93 |         } catch (error) {
 94 |             if (error instanceof Error) {
 95 |                 throw new McpError(ErrorCode.InternalError, `wp_api_create_post failed: ${error.message}`);
 96 |             }
 97 |             throw new McpError(ErrorCode.InternalError, 'wp_api_create_post failed: Unknown error occurred');
 98 |         }
 99 |     }
100 | };
```

--------------------------------------------------------------------------------
/src/helpers.ts:
--------------------------------------------------------------------------------

```typescript
  1 | // src/helpers.ts
  2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
  3 | import fs from 'fs/promises';
  4 | import path from 'path';
  5 | import {execSync} from "child_process";
  6 | 
  7 | export interface WordPressSite {
  8 |     localWpSshEntryFile: string;
  9 |     cli?: string;
 10 |     name: string;
 11 |     path: string;
 12 |     pluginsPath: string;
 13 |     aliases?: string[];
 14 |     apiUrl: string;
 15 |     apiCredentials?: {
 16 |         username: string;
 17 |         password: string;
 18 |     };
 19 | }
 20 | 
 21 | export interface WPSitesConfig {
 22 |     sites: {
 23 |         [key: string]: WordPressSite;
 24 |     };
 25 | }
 26 | 
 27 | export interface ToolArguments {
 28 |     siteKey: string;
 29 | }
 30 | 
 31 | export function similarity(s1: string, s2: string): number {
 32 |     s1 = s1.toLowerCase();
 33 |     s2 = s2.toLowerCase();
 34 | 
 35 |     if (s1.includes(s2) || s2.includes(s1)) return 0.8;
 36 | 
 37 |     let longer = s1;
 38 |     let shorter = s2;
 39 |     if (s1.length < s2.length) {
 40 |         longer = s2;
 41 |         shorter = s1;
 42 |     }
 43 |     const longerLength = longer.length;
 44 |     if (longerLength === 0) return 1.0;
 45 | 
 46 |     const distance = longer.split('')
 47 |         .filter(char => !shorter.includes(char)).length;
 48 |     return (longerLength - distance) / longerLength;
 49 | }
 50 | 
 51 | export function findMatchingSites(config: WPSitesConfig, searchTerm?: string): Array<[string, WordPressSite, number]> {
 52 |     if (!searchTerm) {
 53 |         return Object.entries(config.sites)
 54 |             .map(([key, site]) => [key, site, 1] as [string, WordPressSite, number]);
 55 |     }
 56 | 
 57 |     return Object.entries(config.sites)
 58 |         .map(([key, site]) => {
 59 |             const keyMatch = similarity(key, searchTerm);
 60 |             const nameMatch = similarity(site.name, searchTerm);
 61 |             const aliasMatches = (site.aliases || [])
 62 |                 .map(alias => similarity(alias, searchTerm));
 63 | 
 64 |             const maxScore = Math.max(keyMatch, nameMatch, ...aliasMatches);
 65 |             return [key, site, maxScore] as [string, WordPressSite, number];
 66 |         })
 67 |         .filter(([, , score]) => score > 0.3)
 68 |         .sort((a, b) => b[2] - a[2]);
 69 | }
 70 | 
 71 | export function formatSitesList(sites: Array<[string, WordPressSite, number]>): string {
 72 |     return sites
 73 |         .map(([key, site], index) =>
 74 |             `${index + 1}. ${key} (${site.name})`)
 75 |         .join('\n');
 76 | }
 77 | 
 78 | export async function validateAndGetSite(config: WPSitesConfig, siteArg?: string): Promise<WordPressSite> {
 79 |     const matches = findMatchingSites(config, siteArg);
 80 | 
 81 |     if (matches.length === 0) {
 82 |         const availableSites = formatSitesList(findMatchingSites(config));
 83 |         throw new McpError(
 84 |             ErrorCode.InvalidParams,
 85 |             `No matching sites found. Available sites:\n${availableSites}`
 86 |         );
 87 |     }
 88 | 
 89 |     if (matches.length === 1) {
 90 |         const [key, site] = matches[0];
 91 |         return site;
 92 |     }
 93 | 
 94 |     const sitesList = formatSitesList(matches);
 95 |     throw new McpError(
 96 |         ErrorCode.InvalidParams,
 97 |         `Multiple matching sites found. Please choose which one to use and try again:\n${sitesList}`
 98 |     );
 99 | }
100 | 
101 | export function validateSiteToolArguments(args: unknown): asserts args is ToolArguments {
102 |     if (!args || typeof args !== 'object') {
103 |         throw new McpError(ErrorCode.InvalidParams, 'Arguments must be an object');
104 |     }
105 | 
106 |     const typedArgs = args as Record<string, unknown>;
107 | 
108 |     if (typedArgs.siteKey !== undefined && typeof typedArgs.siteKey !== 'string') {
109 |         throw new McpError(ErrorCode.InvalidParams, 'siteKey must be a string if provided');
110 |     }
111 | }
112 | 
113 | 
114 | export async function isGutenbergBlock(directory: string): Promise<boolean> {
115 |     try {
116 |         // Sprawdź czy istnieje package.json
117 |         const packageJsonPath = path.join(directory, 'package.json');
118 |         const packageJsonExists = await fs.access(packageJsonPath)
119 |             .then(() => true)
120 |             .catch(() => false);
121 | 
122 |         if (!packageJsonExists) return false;
123 | 
124 |         // Sprawdź zawartość package.json
125 |         const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
126 | 
127 |         // Sprawdź charakterystyczne zależności dla bloków Gutenberga
128 |         const dependencies = {
129 |             ...packageJson.dependencies,
130 |             ...packageJson.devDependencies
131 |         };
132 | 
133 |         return (
134 |             '@wordpress/scripts' in dependencies
135 |         );
136 |     } catch {
137 |         return false;
138 |     }
139 | }
140 | 
141 | export async function shouldRebuildBlock(directory: string): Promise<boolean> {
142 |     return isGutenbergBlock(directory);
143 | }
144 | 
145 | 
146 | // Funkcja sprawdzająca, czy WP-CLI jest zainstalowane lokalnie
147 | export const checkLocalWPCLI = (cwd: string) => {
148 |     try {
149 |         execSync(`php "${path.resolve(cwd, 'wp-cli.phar')}" --info`, {stdio: "pipe"});
150 |         return true; // WP-CLI jest zainstalowane lokalnie
151 |     } catch (error) {
152 |         return false; // WP-CLI nie jest zainstalowane lokalnie
153 |     }
154 | };
155 | 
156 | export const checkGlobalWPCLI = () => {
157 |     try {
158 |         execSync("wp --info", {stdio: "pipe"});
159 |         return true; // WP-CLI jest zainstalowane globalnie
160 |     } catch (error) {
161 |         return false; // WP-CLI nie jest zainstalowane globalnie
162 |     }
163 | };
```

--------------------------------------------------------------------------------
/src/tools/filesystem/edit-block-json-file.ts:
--------------------------------------------------------------------------------

```typescript
  1 | // src/tools/edit-file.ts
  2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
  3 | import {promises as fs} from 'fs';
  4 | import path from 'path';
  5 | import {isGutenbergBlock} from '../../helpers.js';
  6 | import {WordPressSite} from "../../types/wp-sites.js";
  7 | import {buildBlock} from "./../build-block.js";
  8 | import {listPluginFiles} from "tools/list-plugin-files";
  9 | 
 10 | 
 11 | interface EditFileArgs {
 12 |     filePath: string;
 13 |     content: object;
 14 |     blockPluginDirname: string;
 15 |     siteKey: string;
 16 |     didUserConfirmChanges: boolean;
 17 | }
 18 | 
 19 | export const editBlockJsonFile = {
 20 |     name: "wp_edit_block_json_file",
 21 |     description: "Edits a file: block.json in WordPress Gutenberg Block Plugin with automatic rebuild detection and block.json file validation.",
 22 |     inputSchema: {
 23 |         type: "object",
 24 |         properties: {
 25 |             siteKey: {type: "string", description: "Site key"},
 26 |             blockPluginDirname: {
 27 |                 type: "string",
 28 |                 description: "Block plugin directory name."
 29 |             },
 30 |             filePath: {
 31 |                 type: "string",
 32 |                 description: "Path to the block.json file relative to the plugin root."
 33 |             },
 34 |             content: {
 35 |                 type: "object",
 36 |                 description: "New content for the block.json file in JSON format"
 37 |             },
 38 |             didUserConfirmChanges: {
 39 |                 type: "boolean",
 40 |                 description: "Are you sure about this changes from new content?"
 41 |             }
 42 |         },
 43 |         required: ["filePath", "blockPluginDirname", "siteKey", "content", "didUserConfirmChanges"]
 44 |     },
 45 | 
 46 |     async execute(args: EditFileArgs, site: WordPressSite) {
 47 | 
 48 |         const blockDir = path.join(site.pluginsPath, args.blockPluginDirname)
 49 |         const fullPath = path.join(blockDir, args.filePath);
 50 | 
 51 |         if (!(await isGutenbergBlock(blockDir))) {
 52 |             throw new Error(`wp_edit_block_json_file failed: directory ${blockDir} does not contain a Gutenberg block. Are you sure ${blockDir} is valid?`);
 53 |         }
 54 | 
 55 |         if (!args.filePath.endsWith('block.json')) {
 56 |             throw new Error(`wp_edit_block_json_file failed: this tool can only edit block.json file. Are you sure ${fullPath} is valid for this action?`);
 57 |         }
 58 | 
 59 |         try {
 60 |             try {
 61 |                 await fs.access(fullPath);
 62 |             } catch {
 63 |                 const availableFiles = await listPluginFiles.execute({
 64 |                     ...args,
 65 |                     pluginDirName: args.blockPluginDirname
 66 |                 }, site)
 67 | 
 68 |                 throw new Error(`File block.json not found: ${fullPath}. Available files within dir ${blockDir}:\n${availableFiles?.files?.join('\n')}`);
 69 |             }
 70 | 
 71 |             let originalContent: string;
 72 |             try {
 73 |                 originalContent = await fs.readFile(fullPath, 'utf-8');
 74 |             } catch (error) {
 75 |                 const errorMessage = error instanceof Error ? error.message : 'Unknown read error';
 76 |                 throw new Error(`Failed to read current block.json file: ${errorMessage}`);
 77 |             }
 78 | 
 79 |             let warnings = [];
 80 |             try {
 81 |                 let newContent: any = args.content;
 82 | 
 83 |                 if (newContent.style !== 'file:./style-index.css') {
 84 |                     warnings.push('Property "style" in block.json is not default. Default value should be: "file:./style-index.css". If you have changed in intentionally it is ok.')
 85 |                 }
 86 |                 if (newContent.editorStyle !== "file:./index.css") {
 87 |                     warnings.push('Property "editorStyle" in block.json is not default. Default value should be: "file:./index.css". If you have changed in intentionally it is ok.')
 88 |                 }
 89 |                 if (newContent.render) {
 90 |                     warnings.push('Property "render" in block.json exists, so it is dynamic block. In this case there should not be save.js file for block, but render file is needed.')
 91 |                 }
 92 | 
 93 |                 if (args.didUserConfirmChanges) {
 94 |                     await fs.writeFile(fullPath, JSON.stringify(newContent), 'utf-8');
 95 |                 } else {
 96 |                     return {
 97 |                         isError: true,
 98 |                         content: [{
 99 |                             type: "text",
100 |                             text: `Are you sure to make changes to block.json file within ${fullPath}\nNew content:\n${JSON.stringify(newContent, null, 2)}.\n\nWarnings:\n${warnings.join('\n')}\n\nPlease ask user what he think! Let user confirm or not your changes.`
101 |                         }]
102 |                     };
103 |                 }
104 |             } catch (error) {
105 |                 throw new Error(`New content cannot be parsed: ${args.content}`);
106 |             }
107 | 
108 |             const buildResult = await buildBlock.execute({
109 |                 blockPluginDirname: args.blockPluginDirname,
110 |                 siteKey: args.siteKey
111 |             }, site);
112 | 
113 |             return {
114 |                 content: [{
115 |                     type: "text",
116 |                     text: `block.json file: ${fullPath} edited successfully.\n\nBuild output:\n${buildResult.content[0].text}\n\nSome warnings about block.json file found:\n${warnings.join('\n')}`
117 |                 }]
118 |             };
119 | 
120 |         } catch (error) {
121 |             if (error instanceof Error) {
122 |                 throw new McpError(ErrorCode.InternalError, error.message);
123 |             }
124 |             throw new McpError(ErrorCode.InternalError, 'wp_edit_block_json_file: Unknown error occurred');
125 |         }
126 |     }
127 | };
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | // src/index.ts
  2 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
  3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  4 | import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from "@modelcontextprotocol/sdk/types.js";
  5 | import fs from 'fs';
  6 | import { validateAndGetSite, validateSiteToolArguments, WPSitesConfig } from './helpers.js';
  7 | import { scaffoldBlockTool } from 'tools/scaffold-block.js';
  8 | import { listPluginFiles } from "tools/list-plugin-files.js";
  9 | import { listAvailablePluginsInSitePluginsPath } from "tools/list-available-plugins-in-site-plugins-path.js";
 10 | import { buildBlock } from "tools/build-block.js";
 11 | import { editBlockFile } from "tools/filesystem/edit-block-file.js";
 12 | import { apiActivatePlugin } from "tools/wp-api/activate-plugin.js";
 13 | import { apiCreatePost } from "tools/wp-api/create-post.js";
 14 | import { apiUpdatePostStatus } from "tools/wp-api/update-post-status.js";
 15 | import { apiGetPosts } from "tools/wp-api/get-posts.js";
 16 | import { apiGetPostTypes } from "tools/wp-api/get-post-types.js";
 17 | import { apiGetPlugins } from "tools/wp-api/get-plugins.js";
 18 | import { apiUpdatePost } from "tools/wp-api/update-post.js";
 19 | import { apiGetGutenbergBlocks } from "tools/wp-api/get-block-types.js";
 20 | import { apiGetTemplates } from "tools/wp-api/get-templates.js";
 21 | import { apiGetRestBaseForPostType } from "tools/wp-api/get-rest-base-for-post-types.js";
 22 | import { apiUpdatePostContent } from "tools/wp-api/update-post-content.js";
 23 | import { apiGetPost } from "tools/wp-api/get-post.js";
 24 | import { cliInstallAndActivatePlugin } from "tools/wp-cli/instal-and-activate-plugin";
 25 | import { editBlockJsonFile } from "tools/filesystem/edit-block-json-file";
 26 | import { WordPressSite } from "types/wp-sites";
 27 | import { apiDeactivatePlugin } from "tools/wp-api/deactivate-plugin";
 28 | import { apiDeletePost } from "tools/wp-api/delete-post";
 29 | import { apiGetSiteSettings } from "tools/wp-api/site-settings/get-site-settings";
 30 | import { apiUpdateStringSiteSetting } from "tools/wp-api/site-settings/update-string-site-setting";
 31 | import { apiGetPostPreviewLink } from "tools/wp-api/post/get-post-preview-link";
 32 | import { apiUpdateIntegerSiteSetting } from "tools/wp-api/site-settings/update-integer-site-setting";
 33 | 
 34 | const tools = [
 35 |     editBlockFile,
 36 |     scaffoldBlockTool,
 37 |     listPluginFiles,
 38 |     listAvailablePluginsInSitePluginsPath,
 39 |     buildBlock,
 40 |     editBlockJsonFile,
 41 | 
 42 |     apiActivatePlugin,
 43 |     apiDeactivatePlugin,
 44 |     apiCreatePost,
 45 |     apiDeletePost,
 46 |     apiUpdatePostStatus,
 47 |     apiGetPosts,
 48 |     apiGetPost,
 49 |     apiGetPostTypes,
 50 |     apiGetPlugins,
 51 |     apiUpdatePost,
 52 |     apiGetGutenbergBlocks,
 53 |     apiGetTemplates,
 54 |     apiGetRestBaseForPostType,
 55 |     apiUpdatePostContent,
 56 |     apiGetSiteSettings,
 57 |     apiUpdateStringSiteSetting,
 58 |     apiUpdateIntegerSiteSetting,
 59 |     apiGetPostPreviewLink,
 60 |     // apiGetPostContentBeforeChanges,
 61 | 
 62 |     cliInstallAndActivatePlugin,
 63 | ];
 64 | 
 65 | 
 66 | async function loadSiteConfig(): Promise<WPSitesConfig> {
 67 |     const configPath = process.env.WP_SITES_PATH || "/Users/michael/Code/mcp-test-server/wp-sites.json";
 68 |     if (!configPath) {
 69 |         throw new Error("WP_SITES_PATH environment variable is required");
 70 |     }
 71 | 
 72 |     try {
 73 |         const configData = await fs.readFileSync(configPath, {encoding: 'utf8'});
 74 |         return JSON.parse(configData) as WPSitesConfig;
 75 |     } catch (error) {
 76 |         if (error instanceof Error) {
 77 |             if ('code' in error && error.code === 'ENOENT') {
 78 |                 throw new Error(`Config file not found at: ${configPath}`);
 79 |             }
 80 |             throw new Error(`Failed to load config: ${error.message}`);
 81 |         }
 82 |         throw new Error('An unknown error occurred while loading config');
 83 |     }
 84 | }
 85 | 
 86 | async function main() {
 87 |     try {
 88 |         const config = await loadSiteConfig();
 89 | 
 90 |         const server = new Server({
 91 |             name: "wordpress-mcp",
 92 |             version: "1.0.0"
 93 |         }, {
 94 |             capabilities: { tools: {} }
 95 |         });
 96 | 
 97 | 
 98 |         server.setRequestHandler(ListToolsRequestSchema, async () => ({
 99 |             tools: tools.map(tool => ({
100 |                 name: tool.name,
101 |                 description: tool.description,
102 |                 inputSchema: tool.inputSchema
103 |             }))
104 |         }));
105 | 
106 |         server.setRequestHandler(CallToolRequestSchema, async (request) => {
107 |             const {name, arguments: rawArgs} = request.params;
108 | 
109 |             validateSiteToolArguments(rawArgs);
110 |             const {siteKey} = rawArgs;
111 | 
112 |             let site: WordPressSite;
113 |             try {
114 |                 site = await validateAndGetSite(config, siteKey);
115 |             } catch (error) {
116 |                 if (error instanceof Error) {
117 |                     return {
118 |                         isError: true,
119 |                         content: [{
120 |                             type: "text",
121 |                             text: `❌ ${error.message}`
122 |                         }]
123 |                     };
124 |                 }
125 |                 throw error
126 |             }
127 | 
128 |             const tool = tools.find(t => t.name === name);
129 |             if (!tool) {
130 |                 throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
131 |             }
132 | 
133 |             if (name.startsWith('wp_cli_')) {
134 |                 let cliWrapper = '{{cmd}}'
135 |                 if (site.cli === 'localwp') {
136 |                     if (!site.localWpSshEntryFile || !fs.existsSync(site.localWpSshEntryFile)) {
137 |                         return {
138 |                             isError: true,
139 |                             content: [{
140 |                                 type: "text",
141 |                                 text: `❌ Option: "localWpSshEntryFile" for site "${siteKey}" is not specified in wp-sites.json while option "cli" is "localwp"`
142 |                             }]
143 |                         };
144 |                     }
145 |                     cliWrapper = `echo $(SHELL= && source "${site.localWpSshEntryFile}" &>/dev/null && {{cmd}})`
146 |                 } else {
147 |                     return {
148 |                         isError: true,
149 |                         content: [
150 |                             {
151 |                                 type: "text",
152 |                                 text: `❌ Option: "cli" for site "${siteKey}" is not specified in wp-sites.json`
153 |                             }
154 |                         ]
155 |                     };
156 |                 }
157 | 
158 |                 // @ts-ignore
159 |                 return await tool.execute({
160 |                     ...rawArgs,
161 |                     siteKey
162 |                 }, site, cliWrapper);
163 |             }
164 | 
165 |             // @ts-ignore
166 |             return await tool.execute({
167 |                 ...rawArgs,
168 |                 siteKey
169 |             }, site);
170 |         });
171 | 
172 |         const transport = new StdioServerTransport();
173 |         await server.connect(transport);
174 | 
175 |         // console.error(`WordPress MCP server started with ${Object.keys(config.sites).length} site(s) configured`);
176 |     } catch (error) {
177 |         console.error(`Server failed to start: ${error instanceof Error ? error.message : String(error)}`);
178 |         process.exit(1);
179 |     }
180 | }
181 | 
182 | main();
```

--------------------------------------------------------------------------------
/src/tools/filesystem/edit-block-file.ts:
--------------------------------------------------------------------------------

```typescript
  1 | // src/tools/edit-file.ts
  2 | import {ErrorCode, McpError} from "@modelcontextprotocol/sdk/types.js";
  3 | import {promises as fs} from 'fs';
  4 | import path from 'path';
  5 | import {isGutenbergBlock} from '../../helpers.js';
  6 | import {WordPressSite} from "../../types/wp-sites.js";
  7 | import {buildBlock} from "./../build-block.js";
  8 | 
  9 | // Definicje typów dla operacji edycji plików
 10 | interface FileOperation {
 11 |     type: 'write' | 'append' | 'modify' | 'smart_modify';
 12 |     content?: string;
 13 |     searchValue?: string;
 14 |     replaceValue?: string;
 15 | }
 16 | 
 17 | interface EditFileArgs {
 18 |     filePath: string;
 19 |     operation: FileOperation['type'];
 20 |     content?: string;
 21 |     searchValue?: string;
 22 |     replaceValue?: string;
 23 |     directory: string;
 24 |     blockPluginDirname: string;
 25 |     siteKey: string;
 26 | }
 27 | 
 28 | // Wzorce komentarzy wskazujące na zachowanie istniejącego kodu
 29 | const KEEP_CODE_PATTERNS = [
 30 |     /\/\/ *(?:rest of|remaining|other) code (?:remains |stays )?(?:the )?same/i,
 31 |     /\/\/ *\.\.\./,
 32 |     /\/\* *\.\.\.? *\*\//,
 33 |     /\{?\s*\.\.\.\s*\}?/,
 34 |     /\/\/ *no changes/i,
 35 |     /\/\/ *unchanged/i
 36 | ];
 37 | 
 38 | // Funkcje pomocnicze
 39 | function hasCodePlaceholder(content: string): boolean {
 40 |     return KEEP_CODE_PATTERNS.some(pattern => pattern.test(content));
 41 | }
 42 | 
 43 | async function mergeWithExistingCode(originalContent: string, newContent: string): Promise<string> {
 44 |     if (!hasCodePlaceholder(newContent)) {
 45 |         return newContent;
 46 |     }
 47 | 
 48 |     const originalLines = originalContent.split('\n');
 49 |     const newLines = newContent.split('\n');
 50 |     const result: string[] = [];
 51 |     let isKeepingOriginal = false;
 52 | 
 53 |     for (let i = 0; i < newLines.length; i++) {
 54 |         const currentLine = newLines[i];
 55 | 
 56 |         if (hasCodePlaceholder(currentLine)) {
 57 |             if (i > 0 && i < newLines.length - 1) {
 58 |                 const previousLine = newLines[i - 1];
 59 |                 const nextLine = newLines[i + 1];
 60 | 
 61 |                 const startIndex = originalLines.findIndex(line => line.includes(previousLine));
 62 |                 const endIndex = originalLines.findIndex((line, idx) => idx > startIndex && line.includes(nextLine));
 63 | 
 64 |                 if (startIndex !== -1 && endIndex !== -1) {
 65 |                     result.push(...originalLines.slice(startIndex + 1, endIndex));
 66 |                     continue;
 67 |                 }
 68 |             }
 69 |             isKeepingOriginal = true;
 70 |             continue;
 71 |         }
 72 | 
 73 |         if (!isKeepingOriginal) {
 74 |             result.push(currentLine);
 75 |         } else {
 76 |             isKeepingOriginal = false;
 77 |         }
 78 |     }
 79 | 
 80 |     return result.join('\n');
 81 | }
 82 | 
 83 | export const editBlockFile = {
 84 |     name: "wp_edit_block_file",
 85 |     description: "Edits a common file in WordPress Gutenberg Block Plugin with automatic rebuild detection",
 86 |     inputSchema: {
 87 |         type: "object",
 88 |         properties: {
 89 |             siteKey: {type: "string", description: "Site key"},
 90 |             blockPluginDirname: {
 91 |                 type: "string",
 92 |                 description: "Block plugin directory name."
 93 |             },
 94 |             filePath: {
 95 |                 type: "string",
 96 |                 description: "Path to the file relative to the plugin root."
 97 |             },
 98 |             content: {
 99 |                 type: "string",
100 |                 description: "New content for the file"
101 |             },
102 |             operation: {
103 |                 type: "string",
104 |                 enum: ["write", "append", "modify", "smart_modify"],
105 |                 description: "Operation to perform on the file"
106 |             },
107 |             searchValue: {
108 |                 type: "string",
109 |                 description: "Text to search for when using modify operation"
110 |             },
111 |             replaceValue: {
112 |                 type: "string",
113 |                 description: "Text to replace with when using modify operation"
114 |             }
115 |         },
116 |         required: ["filePath", "operation", "blockPluginDirname", "siteKey"]
117 |     },
118 | 
119 |     async execute(args: EditFileArgs, site: WordPressSite) {
120 | 
121 |         const blockDir = path.join(site.pluginsPath, args.blockPluginDirname)
122 |         const fullPath = path.join(blockDir, args.filePath);
123 | 
124 |         if (!(await isGutenbergBlock(blockDir))) {
125 |             throw new Error(`wp_edit_block_file failed: directory ${blockDir} does not contain a Gutenberg block. Are you sure ${blockDir} is valid?`);
126 |         }
127 |         if (args.filePath.endsWith('block.json')) {
128 |             throw new Error(`This tool cannot edit block.json file. Please use another tool: wp_edit_block_json_file instead and try again.`);
129 |         }
130 |         if (args.filePath.includes('/build/')) {
131 |             throw new Error(`Files within "build" directory shouldn't be edited directly.`);
132 |         }
133 | 
134 |         try {
135 |             try {
136 |                 await fs.access(fullPath);
137 |             } catch {
138 |                 throw new Error(`File not found: ${fullPath}`);
139 |             }
140 | 
141 |             let originalContent: string;
142 |             try {
143 |                 originalContent = await fs.readFile(fullPath, 'utf-8');
144 |             } catch (error) {
145 |                 const errorMessage = error instanceof Error ? error.message : 'Unknown read error';
146 |                 throw new Error(`Failed to read file: ${errorMessage}`);
147 |             }
148 | 
149 |             let newContent = originalContent;
150 | 
151 |             switch (args.operation) {
152 |                 case 'smart_modify':
153 |                     if (!args.content) {
154 |                         throw new Error('Content is required for smart_modify operation');
155 |                     }
156 |                     newContent = await mergeWithExistingCode(originalContent, args.content);
157 |                     break;
158 | 
159 |                 case 'write':
160 |                     if (!args.content) {
161 |                         throw new Error('Content is required for write operation');
162 |                     }
163 |                     newContent = args.content;
164 |                     break;
165 | 
166 |                 case 'append':
167 |                     if (!args.content) {
168 |                         throw new Error('Content is required for append operation');
169 |                     }
170 |                     newContent = originalContent + '\n' + args.content;
171 |                     break;
172 | 
173 |                 case 'modify':
174 |                     if (!args.searchValue || !args.replaceValue) {
175 |                         throw new Error('searchValue and replaceValue are required for modify operation');
176 |                     }
177 |                     newContent = originalContent.replace(
178 |                         new RegExp(args.searchValue, 'g'),
179 |                         args.replaceValue
180 |                     );
181 |                     break;
182 | 
183 |                 default:
184 |                     throw new Error(`Unknown operation: ${args.operation}`);
185 |             }
186 | 
187 |             await fs.writeFile(fullPath, newContent, 'utf-8');
188 | 
189 |             try {
190 |                 const buildResult = await buildBlock.execute({
191 |                     blockPluginDirname: args.blockPluginDirname,
192 |                     siteKey: args.siteKey
193 |                 }, site);
194 | 
195 |                 return {
196 |                     content: [{
197 |                         type: "text",
198 |                         text: `Block file: ${fullPath} edited successfully.\n\nBuild output:\n${buildResult.content[0].text}`
199 |                     }]
200 |                 };
201 |             } catch (error) {
202 |                 const errorMessage = error instanceof Error ? error.message : 'Unknown build error';
203 |                 throw new Error(`Block file: ${fullPath} edited but build failed: ${errorMessage}. Please try build again on check error logs on your own.`);
204 |             }
205 | 
206 |             return {
207 |                 content: [{
208 |                     type: "text",
209 |                     text: `Block file: ${fullPath} edited successfully.`
210 |                 }]
211 |             };
212 | 
213 |         } catch (error) {
214 |             if (error instanceof Error) {
215 |                 throw new McpError(ErrorCode.InternalError, error.message);
216 |             }
217 |             throw new McpError(ErrorCode.InternalError, 'wp_edit_block_file: Unknown error occurred');
218 |         }
219 |     }
220 | };
```