@zyzy-org/blog-mcp-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAEA,qCAAuC;AACvC,+CAA2C;AAC3C,6CAAyC;AAEzC,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC;QACH,OAAO;QACP,MAAM,MAAM,GAAG,IAAA,oBAAW,GAAE,CAAC;QAE7B,UAAU;QACV,MAAM,UAAU,GAAG,IAAI,wBAAU,CAAC,MAAM,CAAC,CAAC;QAE1C,gBAAgB;QAChB,MAAM,MAAM,GAAG,IAAI,sBAAS,CAAC,UAAU,CAAC,CAAC;QACzC,MAAM,CAAC,KAAK,EAAE,CAAC;IAEjB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC/E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { BlogClient } from './blog-client';
2
+ export declare class MCPServer {
3
+ private rl;
4
+ private blogClient;
5
+ constructor(blogClient: BlogClient);
6
+ private sendResponse;
7
+ private handleRequest;
8
+ start(): void;
9
+ }
10
+ //# sourceMappingURL=mcp-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAG3C,qBAAa,SAAS;IACpB,OAAO,CAAC,EAAE,CAAqB;IAC/B,OAAO,CAAC,UAAU,CAAa;gBAEnB,UAAU,EAAE,UAAU;IAQlC,OAAO,CAAC,YAAY;YAUN,aAAa;IAmE3B,KAAK,IAAI,IAAI;CAkBd"}
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MCPServer = void 0;
7
+ const readline_1 = __importDefault(require("readline"));
8
+ const tools_1 = require("./tools");
9
+ class MCPServer {
10
+ constructor(blogClient) {
11
+ this.blogClient = blogClient;
12
+ this.rl = readline_1.default.createInterface({
13
+ input: process.stdin,
14
+ output: process.stdout
15
+ });
16
+ }
17
+ sendResponse(id, result, error) {
18
+ const response = {
19
+ jsonrpc: "2.0",
20
+ id,
21
+ result: error ? undefined : result,
22
+ error: error ? { code: -1, message: error } : undefined
23
+ };
24
+ console.log(JSON.stringify(response));
25
+ }
26
+ async handleRequest(request) {
27
+ try {
28
+ const { id, method, params } = request;
29
+ switch (method) {
30
+ case "initialize":
31
+ this.sendResponse(id, {
32
+ protocolVersion: "2024-11-05",
33
+ capabilities: {
34
+ tools: {}
35
+ },
36
+ serverInfo: {
37
+ name: "blog-mcp-server",
38
+ version: "1.0.0"
39
+ }
40
+ });
41
+ break;
42
+ case "tools/list":
43
+ this.sendResponse(id, {
44
+ tools: Object.values(tools_1.tools).map(tool => ({
45
+ name: tool.name,
46
+ description: tool.description,
47
+ inputSchema: tool.inputSchema
48
+ }))
49
+ });
50
+ break;
51
+ case "tools/call":
52
+ const { name, arguments: args } = params;
53
+ if (name === "create_blog_post") {
54
+ const result = await this.blogClient.createBlogPost(args);
55
+ this.sendResponse(id, {
56
+ content: [{
57
+ type: "text",
58
+ text: `文章创建成功!\n\n标题: ${result.slug}\n状态: ${result.message}\n\n文章已发布到你的博客。`
59
+ }]
60
+ });
61
+ }
62
+ else if (name === "create_multiple_blog_posts") {
63
+ const result = await this.blogClient.createMultipleBlogPosts(args.articles);
64
+ const successCount = result.filter((r) => r.success).length;
65
+ const failCount = result.length - successCount;
66
+ this.sendResponse(id, {
67
+ content: [{
68
+ type: "text",
69
+ text: `批量创建完成!\n\n成功: ${successCount} 篇\n失败: ${failCount} 篇\n\n详细信息: ${JSON.stringify(result, null, 2)}`
70
+ }]
71
+ });
72
+ }
73
+ else {
74
+ this.sendResponse(id, undefined, `未知工具: ${name}`);
75
+ }
76
+ break;
77
+ default:
78
+ this.sendResponse(id, undefined, `未知方法: ${method}`);
79
+ }
80
+ }
81
+ catch (error) {
82
+ this.sendResponse(request.id, undefined, error instanceof Error ? error.message : String(error));
83
+ }
84
+ }
85
+ start() {
86
+ this.rl.on('line', (line) => {
87
+ try {
88
+ if (!line.trim())
89
+ return;
90
+ const request = JSON.parse(line);
91
+ this.handleRequest(request);
92
+ }
93
+ catch (error) {
94
+ // 忽略解析错误
95
+ }
96
+ });
97
+ // 发送初始化通知
98
+ console.log(JSON.stringify({
99
+ jsonrpc: "2.0",
100
+ method: "notifications/initialized",
101
+ params: {}
102
+ }));
103
+ }
104
+ }
105
+ exports.MCPServer = MCPServer;
106
+ //# sourceMappingURL=mcp-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-server.js","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { MCPTool } from './types';
2
+ export declare const tools: Record<string, MCPTool>;
3
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAqGzC,CAAC"}
package/dist/tools.js ADDED
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.tools = void 0;
4
+ exports.tools = {
5
+ create_blog_post: {
6
+ name: "create_blog_post",
7
+ description: "在你的技术博客中创建新文章",
8
+ inputSchema: {
9
+ type: "object",
10
+ properties: {
11
+ title: {
12
+ type: "string",
13
+ description: "文章标题"
14
+ },
15
+ content: {
16
+ type: "string",
17
+ description: "文章内容,支持 Markdown 格式"
18
+ },
19
+ tags: {
20
+ type: "array",
21
+ items: {
22
+ type: "string"
23
+ },
24
+ description: "文章标签数组"
25
+ },
26
+ author: {
27
+ type: "string",
28
+ description: "作者名称"
29
+ },
30
+ readTime: {
31
+ type: "number",
32
+ description: "预计阅读时间(分钟)"
33
+ },
34
+ date: {
35
+ type: "string",
36
+ description: "发布日期(ISO 8601 格式)"
37
+ },
38
+ github_url: {
39
+ type: "string",
40
+ description: "相关的 GitHub 链接"
41
+ },
42
+ slug: {
43
+ type: "string",
44
+ description: "自定义文章 slug(可选)"
45
+ }
46
+ },
47
+ required: ["title", "content"]
48
+ }
49
+ },
50
+ create_multiple_blog_posts: {
51
+ name: "create_multiple_blog_posts",
52
+ description: "批量创建多篇博客文章",
53
+ inputSchema: {
54
+ type: "object",
55
+ properties: {
56
+ articles: {
57
+ type: "array",
58
+ items: {
59
+ type: "object",
60
+ properties: {
61
+ title: {
62
+ type: "string",
63
+ description: "文章标题"
64
+ },
65
+ content: {
66
+ type: "string",
67
+ description: "文章内容(Markdown 格式)"
68
+ },
69
+ tags: {
70
+ type: "array",
71
+ items: {
72
+ type: "string"
73
+ },
74
+ description: "文章标签"
75
+ },
76
+ author: {
77
+ type: "string",
78
+ description: "作者名称"
79
+ },
80
+ readTime: {
81
+ type: "number",
82
+ description: "阅读时间(分钟)"
83
+ },
84
+ date: {
85
+ type: "string",
86
+ description: "发布日期"
87
+ },
88
+ github_url: {
89
+ type: "string",
90
+ description: "GitHub 链接"
91
+ },
92
+ slug: {
93
+ type: "string",
94
+ description: "自定义 slug"
95
+ }
96
+ },
97
+ required: ["title", "content"]
98
+ },
99
+ description: "要创建的文章数组"
100
+ }
101
+ },
102
+ required: ["articles"]
103
+ }
104
+ }
105
+ };
106
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":";;;AAEa,QAAA,KAAK,GAA4B;IAC5C,gBAAgB,EAAE;QAChB,IAAI,EAAE,kBAAkB;QACxB,WAAW,EAAE,eAAe;QAC5B,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,KAAK,EAAE;oBACL,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,MAAM;iBACpB;gBACD,OAAO,EAAE;oBACP,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,qBAAqB;iBACnC;gBACD,IAAI,EAAE;oBACJ,IAAI,EAAE,OAAO;oBACb,KAAK,EAAE;wBACL,IAAI,EAAE,QAAQ;qBACf;oBACD,WAAW,EAAE,QAAQ;iBACtB;gBACD,MAAM,EAAE;oBACN,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,MAAM;iBACpB;gBACD,QAAQ,EAAE;oBACR,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,YAAY;iBAC1B;gBACD,IAAI,EAAE;oBACJ,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,mBAAmB;iBACjC;gBACD,UAAU,EAAE;oBACV,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,eAAe;iBAC7B;gBACD,IAAI,EAAE;oBACJ,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,gBAAgB;iBAC9B;aACF;YACD,QAAQ,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC;SAC/B;KACF;IACD,0BAA0B,EAAE;QAC1B,IAAI,EAAE,4BAA4B;QAClC,WAAW,EAAE,YAAY;QACzB,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,QAAQ,EAAE;oBACR,IAAI,EAAE,OAAO;oBACb,KAAK,EAAE;wBACL,IAAI,EAAE,QAAQ;wBACd,UAAU,EAAE;4BACV,KAAK,EAAE;gCACL,IAAI,EAAE,QAAQ;gCACd,WAAW,EAAE,MAAM;6BACpB;4BACD,OAAO,EAAE;gCACP,IAAI,EAAE,QAAQ;gCACd,WAAW,EAAE,mBAAmB;6BACjC;4BACD,IAAI,EAAE;gCACJ,IAAI,EAAE,OAAO;gCACb,KAAK,EAAE;oCACL,IAAI,EAAE,QAAQ;iCACf;gCACD,WAAW,EAAE,MAAM;6BACpB;4BACD,MAAM,EAAE;gCACN,IAAI,EAAE,QAAQ;gCACd,WAAW,EAAE,MAAM;6BACpB;4BACD,QAAQ,EAAE;gCACR,IAAI,EAAE,QAAQ;gCACd,WAAW,EAAE,UAAU;6BACxB;4BACD,IAAI,EAAE;gCACJ,IAAI,EAAE,QAAQ;gCACd,WAAW,EAAE,MAAM;6BACpB;4BACD,UAAU,EAAE;gCACV,IAAI,EAAE,QAAQ;gCACd,WAAW,EAAE,WAAW;6BACzB;4BACD,IAAI,EAAE;gCACJ,IAAI,EAAE,QAAQ;gCACd,WAAW,EAAE,UAAU;6BACxB;yBACF;wBACD,QAAQ,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC;qBAC/B;oBACD,WAAW,EAAE,UAAU;iBACxB;aACF;YACD,QAAQ,EAAE,CAAC,UAAU,CAAC;SACvB;KACF;CACF,CAAC"}
@@ -0,0 +1,46 @@
1
+ export interface BlogPostData {
2
+ title: string;
3
+ content: string;
4
+ tags?: string[];
5
+ author?: string;
6
+ readTime?: number;
7
+ date?: string;
8
+ github_url?: string;
9
+ slug?: string;
10
+ }
11
+ export interface SecureBlogPostData extends BlogPostData {
12
+ client_id: string;
13
+ timestamp: number;
14
+ signature?: string;
15
+ }
16
+ export interface MCPConfig {
17
+ blogUrl: string;
18
+ secret: string;
19
+ clientId: string;
20
+ signatureSecret?: string;
21
+ }
22
+ export interface MCPRequest {
23
+ jsonrpc: "2.0";
24
+ id: string | number;
25
+ method: string;
26
+ params?: any;
27
+ }
28
+ export interface MCPResponse {
29
+ jsonrpc: "2.0";
30
+ id: string | number;
31
+ result?: any;
32
+ error?: {
33
+ code: number;
34
+ message: string;
35
+ };
36
+ }
37
+ export interface MCPTool {
38
+ name: string;
39
+ description: string;
40
+ inputSchema: {
41
+ type: string;
42
+ properties: Record<string, any>;
43
+ required?: string[];
44
+ };
45
+ }
46
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,GAAG,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,KAAK,CAAC,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE;QACX,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAChC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;CACH"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,34 @@
1
+ # 配置示例
2
+
3
+ ## Cursor 设置中的 MCP 配置
4
+
5
+ 在你的 Cursor 设置中添加以下 MCP 服务器:
6
+
7
+ ### 基本配置
8
+ ```bash
9
+ npx -y @ziyouzhiyi/blog-mcp-server@latest --blog-url=https://zyzy.info --secret=660649959a92f12949618a919413e83c8aad09afbb39bcc172725583037e6beb
10
+ ```
11
+
12
+ ### 完整配置(推荐)
13
+ ```bash
14
+ npx -y @ziyouzhiyi/blog-mcp-server@latest --blog-url=https://zyzy.info --secret=660649959a92f12949618a919413e83c8aad09afbb39bcc172725583037e6beb --client-id=my-mcp-client --signature-secret=c20f0c7bd9128d05c53ffc7743f483c21169f1562542c30ea6e6bf127b488e5f
15
+ ```
16
+
17
+ ## 参数说明
18
+
19
+ - `--blog-url`: 你的博客域名(必需)
20
+ - `--secret`: MCP 共享密钥,对应你博客中的 `MCP_SHARED_SECRET`(必需)
21
+ - `--client-id`: 客户端标识,对应你博客中的 `MCP_CLIENT_ID`(可选)
22
+ - `--signature-secret`: 签名密钥,对应你博客中的 `MCP_SIGNATURE_SECRET`(可选,用于额外安全验证)
23
+
24
+ ## 本地开发测试
25
+
26
+ 如果你想在发布前测试,可以:
27
+
28
+ 1. 克隆这个仓库
29
+ 2. 运行 `npm install`
30
+ 3. 运行 `npm run build`
31
+ 4. 使用以下命令测试:
32
+ ```bash
33
+ node dist/index.js --blog-url=http://localhost:3000 --secret=your-secret
34
+ ```
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@zyzy-org/blog-mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for automatically creating blog posts in your tech blog",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "blog-mcp-server": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "tsc --watch",
12
+ "start": "node dist/index.js",
13
+ "prepublishOnly": "npm run build",
14
+ "test": "node test-config.js",
15
+ "publish-package": "npm publish --access public"
16
+ },
17
+ "keywords": [
18
+ "mcp",
19
+ "blog",
20
+ "cursor",
21
+ "ai",
22
+ "supabase",
23
+ "nextjs"
24
+ ],
25
+ "author": "ziyouzhiyi",
26
+ "license": "MIT",
27
+ "dependencies": {
28
+ "dotenv": "^16.0.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^20.0.0",
32
+ "typescript": "^5.0.0"
33
+ },
34
+ "engines": {
35
+ "node": ">=18.0.0"
36
+ },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/zyzy-org/blog-mcp-server.git"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/zyzy-org/blog-mcp-server/issues"
43
+ },
44
+ "homepage": "https://github.com/zyzy-org/blog-mcp-server#readme"
45
+ }
@@ -0,0 +1,66 @@
1
+ // 使用原生 fetch API
2
+ import crypto from 'crypto';
3
+ import { BlogPostData, SecureBlogPostData, MCPConfig } from './types';
4
+
5
+ export class BlogClient {
6
+ private config: MCPConfig;
7
+
8
+ constructor(config: MCPConfig) {
9
+ this.config = config;
10
+ }
11
+
12
+ private generateSignature(data: SecureBlogPostData): string {
13
+ if (!this.config.signatureSecret) {
14
+ throw new Error('签名密钥未配置');
15
+ }
16
+
17
+ return crypto
18
+ .createHmac('sha256', this.config.signatureSecret)
19
+ .update(JSON.stringify({ ...data, signature: undefined }))
20
+ .digest('hex');
21
+ }
22
+
23
+ async createBlogPost(postData: BlogPostData): Promise<any> {
24
+ const secureData: SecureBlogPostData = {
25
+ ...postData,
26
+ client_id: this.config.clientId,
27
+ timestamp: Date.now(),
28
+ };
29
+
30
+ if (this.config.signatureSecret) {
31
+ secureData.signature = this.generateSignature(secureData);
32
+ }
33
+
34
+ const response = await fetch(`${this.config.blogUrl}/api/mcp/posts`, {
35
+ method: 'POST',
36
+ headers: {
37
+ 'Content-Type': 'application/json',
38
+ 'x-mcp-secret': this.config.secret,
39
+ },
40
+ body: JSON.stringify(secureData)
41
+ });
42
+
43
+ if (!response.ok) {
44
+ const errorText = await response.text();
45
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
46
+ }
47
+
48
+ return await response.json();
49
+ }
50
+
51
+ async createMultipleBlogPosts(articles: BlogPostData[]): Promise<any[]> {
52
+ const results = [];
53
+ for (const article of articles) {
54
+ try {
55
+ const result = await this.createBlogPost(article);
56
+ results.push({ success: true, ...result });
57
+ } catch (error) {
58
+ results.push({
59
+ success: false,
60
+ error: error instanceof Error ? error.message : String(error)
61
+ });
62
+ }
63
+ }
64
+ return results;
65
+ }
66
+ }
package/src/config.ts ADDED
@@ -0,0 +1,56 @@
1
+ import { MCPConfig } from './types';
2
+ import dotenv from 'dotenv';
3
+
4
+ // 加载环境变量
5
+ dotenv.config();
6
+
7
+ export function parseConfig(): MCPConfig {
8
+ const args = process.argv.slice(2);
9
+
10
+ // 从环境变量获取默认配置
11
+ const config: Partial<MCPConfig> = {
12
+ blogUrl: process.env.BLOG_URL,
13
+ secret: process.env.MCP_SECRET,
14
+ clientId: process.env.MCP_CLIENT_ID || 'blog-mcp-client',
15
+ signatureSecret: process.env.MCP_SIGNATURE_SECRET
16
+ };
17
+
18
+ // 命令行参数覆盖环境变量
19
+ for (let i = 0; i < args.length; i++) {
20
+ const arg = args[i];
21
+
22
+ if (arg.startsWith('--')) {
23
+ const key = arg.slice(2);
24
+ const value = args[i + 1];
25
+
26
+ switch (key) {
27
+ case 'blog-url':
28
+ config.blogUrl = value;
29
+ break;
30
+ case 'secret':
31
+ config.secret = value;
32
+ break;
33
+ case 'client-id':
34
+ config.clientId = value;
35
+ break;
36
+ case 'signature-secret':
37
+ config.signatureSecret = value;
38
+ break;
39
+ case 'local':
40
+ // 使用本地开发环境
41
+ config.blogUrl = process.env.BLOG_LOCAL_URL || 'http://localhost:3000';
42
+ break;
43
+ }
44
+ }
45
+ }
46
+
47
+ // 验证必需参数
48
+ if (!config.blogUrl) {
49
+ throw new Error('BLOG_URL 环境变量或 --blog-url 参数是必需的');
50
+ }
51
+ if (!config.secret) {
52
+ throw new Error('MCP_SECRET 环境变量或 --secret 参数是必需的');
53
+ }
54
+
55
+ return config as MCPConfig;
56
+ }
package/src/index.ts ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { parseConfig } from './config';
4
+ import { BlogClient } from './blog-client';
5
+ import { MCPServer } from './mcp-server';
6
+
7
+ async function main() {
8
+ try {
9
+ // 解析配置
10
+ const config = parseConfig();
11
+
12
+ // 创建博客客户端
13
+ const blogClient = new BlogClient(config);
14
+
15
+ // 创建并启动 MCP 服务器
16
+ const server = new MCPServer(blogClient);
17
+ server.start();
18
+
19
+ } catch (error) {
20
+ console.error('启动失败:', error instanceof Error ? error.message : String(error));
21
+ process.exit(1);
22
+ }
23
+ }
24
+
25
+ main();
@@ -0,0 +1,113 @@
1
+ import readline from 'readline';
2
+ import { MCPRequest, MCPResponse, MCPTool } from './types';
3
+ import { BlogClient } from './blog-client';
4
+ import { tools } from './tools';
5
+
6
+ export class MCPServer {
7
+ private rl: readline.Interface;
8
+ private blogClient: BlogClient;
9
+
10
+ constructor(blogClient: BlogClient) {
11
+ this.blogClient = blogClient;
12
+ this.rl = readline.createInterface({
13
+ input: process.stdin,
14
+ output: process.stdout
15
+ });
16
+ }
17
+
18
+ private sendResponse(id: string | number, result?: any, error?: string): void {
19
+ const response: MCPResponse = {
20
+ jsonrpc: "2.0",
21
+ id,
22
+ result: error ? undefined : result,
23
+ error: error ? { code: -1, message: error } : undefined
24
+ };
25
+ console.log(JSON.stringify(response));
26
+ }
27
+
28
+ private async handleRequest(request: MCPRequest): Promise<void> {
29
+ try {
30
+ const { id, method, params } = request;
31
+
32
+ switch (method) {
33
+ case "initialize":
34
+ this.sendResponse(id, {
35
+ protocolVersion: "2024-11-05",
36
+ capabilities: {
37
+ tools: {}
38
+ },
39
+ serverInfo: {
40
+ name: "blog-mcp-server",
41
+ version: "1.0.0"
42
+ }
43
+ });
44
+ break;
45
+
46
+ case "tools/list":
47
+ this.sendResponse(id, {
48
+ tools: Object.values(tools).map(tool => ({
49
+ name: tool.name,
50
+ description: tool.description,
51
+ inputSchema: tool.inputSchema
52
+ }))
53
+ });
54
+ break;
55
+
56
+ case "tools/call":
57
+ const { name, arguments: args } = params;
58
+
59
+ if (name === "create_blog_post") {
60
+ const result = await this.blogClient.createBlogPost(args);
61
+ this.sendResponse(id, {
62
+ content: [{
63
+ type: "text",
64
+ text: `文章创建成功!\n\n标题: ${result.slug}\n状态: ${result.message}\n\n文章已发布到你的博客。`
65
+ }]
66
+ });
67
+ } else if (name === "create_multiple_blog_posts") {
68
+ const result = await this.blogClient.createMultipleBlogPosts(args.articles);
69
+ const successCount = result.filter((r: any) => r.success).length;
70
+ const failCount = result.length - successCount;
71
+
72
+ this.sendResponse(id, {
73
+ content: [{
74
+ type: "text",
75
+ text: `批量创建完成!\n\n成功: ${successCount} 篇\n失败: ${failCount} 篇\n\n详细信息: ${JSON.stringify(result, null, 2)}`
76
+ }]
77
+ });
78
+ } else {
79
+ this.sendResponse(id, undefined, `未知工具: ${name}`);
80
+ }
81
+ break;
82
+
83
+ default:
84
+ this.sendResponse(id, undefined, `未知方法: ${method}`);
85
+ }
86
+ } catch (error) {
87
+ this.sendResponse(
88
+ request.id,
89
+ undefined,
90
+ error instanceof Error ? error.message : String(error)
91
+ );
92
+ }
93
+ }
94
+
95
+ start(): void {
96
+ this.rl.on('line', (line) => {
97
+ try {
98
+ if (!line.trim()) return;
99
+ const request: MCPRequest = JSON.parse(line);
100
+ this.handleRequest(request);
101
+ } catch (error) {
102
+ // 忽略解析错误
103
+ }
104
+ });
105
+
106
+ // 发送初始化通知
107
+ console.log(JSON.stringify({
108
+ jsonrpc: "2.0",
109
+ method: "notifications/initialized",
110
+ params: {}
111
+ }));
112
+ }
113
+ }