@dalcontak/blogger-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.
package/dist/server.js ADDED
@@ -0,0 +1,448 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createToolDefinitions = createToolDefinitions;
4
+ exports.initMCPServer = initMCPServer;
5
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
6
+ const zod_1 = require("zod");
7
+ /**
8
+ * Creates the tool definitions for the Blogger MCP server
9
+ * @param bloggerService Blogger service to interact with the API
10
+ * @returns Array of tool definitions
11
+ */
12
+ function createToolDefinitions(bloggerService) {
13
+ return [
14
+ {
15
+ name: 'list_blogs',
16
+ description: 'Lists all accessible blogs',
17
+ args: zod_1.z.object({}),
18
+ handler: async (_args, _extra) => {
19
+ try {
20
+ const blogs = await bloggerService.listBlogs();
21
+ return {
22
+ content: [
23
+ {
24
+ type: 'text',
25
+ text: JSON.stringify({ blogs }, null, 2)
26
+ }
27
+ ]
28
+ };
29
+ }
30
+ catch (error) {
31
+ console.error('Error fetching blogs:', error);
32
+ return {
33
+ content: [
34
+ {
35
+ type: 'text',
36
+ text: `Error fetching blogs: ${error}`
37
+ }
38
+ ],
39
+ isError: true
40
+ };
41
+ }
42
+ }
43
+ },
44
+ {
45
+ name: 'get_blog',
46
+ description: 'Retrieves details of a specific blog',
47
+ args: zod_1.z.object({
48
+ blogId: zod_1.z.string().describe('Blog ID')
49
+ }),
50
+ handler: async (args, _extra) => {
51
+ try {
52
+ const blog = await bloggerService.getBlog(args.blogId);
53
+ return {
54
+ content: [
55
+ {
56
+ type: 'text',
57
+ text: JSON.stringify({ blog }, null, 2)
58
+ }
59
+ ]
60
+ };
61
+ }
62
+ catch (error) {
63
+ console.error(`Error fetching blog ${args.blogId}:`, error);
64
+ return {
65
+ content: [
66
+ {
67
+ type: 'text',
68
+ text: `Error fetching blog: ${error}`
69
+ }
70
+ ],
71
+ isError: true
72
+ };
73
+ }
74
+ }
75
+ },
76
+ {
77
+ name: 'get_blog_by_url',
78
+ description: 'Retrieves a blog by its URL (useful for discovering blog ID)',
79
+ args: zod_1.z.object({
80
+ url: zod_1.z.string().describe('Blog URL')
81
+ }),
82
+ handler: async (args, _extra) => {
83
+ try {
84
+ const blog = await bloggerService.getBlogByUrl(args.url);
85
+ return {
86
+ content: [
87
+ {
88
+ type: 'text',
89
+ text: JSON.stringify({ blog }, null, 2)
90
+ }
91
+ ]
92
+ };
93
+ }
94
+ catch (error) {
95
+ console.error(`Error fetching blog by URL ${args.url}:`, error);
96
+ return {
97
+ content: [
98
+ {
99
+ type: 'text',
100
+ text: `Error fetching blog by URL: ${error}`
101
+ }
102
+ ],
103
+ isError: true
104
+ };
105
+ }
106
+ }
107
+ },
108
+ {
109
+ name: 'create_blog',
110
+ description: 'Creates a new blog (not supported by the Blogger API)',
111
+ args: zod_1.z.object({
112
+ name: zod_1.z.string().describe('Blog name'),
113
+ description: zod_1.z.string().optional().describe('Blog description')
114
+ }),
115
+ handler: async (_args, _extra) => {
116
+ return {
117
+ content: [
118
+ {
119
+ type: 'text',
120
+ text: 'Blog creation is not supported by the Blogger API. Please create a blog via the Blogger web interface.'
121
+ }
122
+ ],
123
+ isError: true
124
+ };
125
+ }
126
+ },
127
+ {
128
+ name: 'list_posts',
129
+ description: 'Lists all posts from a blog',
130
+ args: zod_1.z.object({
131
+ blogId: zod_1.z.string().describe('Blog ID'),
132
+ maxResults: zod_1.z.number().optional().describe('Maximum number of results to return')
133
+ }),
134
+ handler: async (args, _extra) => {
135
+ try {
136
+ const posts = await bloggerService.listPosts(args.blogId, args.maxResults);
137
+ return {
138
+ content: [
139
+ {
140
+ type: 'text',
141
+ text: JSON.stringify({ posts }, null, 2)
142
+ }
143
+ ]
144
+ };
145
+ }
146
+ catch (error) {
147
+ console.error(`Error fetching posts for blog ${args.blogId}:`, error);
148
+ return {
149
+ content: [
150
+ {
151
+ type: 'text',
152
+ text: `Error fetching posts: ${error}`
153
+ }
154
+ ],
155
+ isError: true
156
+ };
157
+ }
158
+ }
159
+ },
160
+ {
161
+ name: 'search_posts',
162
+ description: 'Searches posts in a blog',
163
+ args: zod_1.z.object({
164
+ blogId: zod_1.z.string().describe('Blog ID'),
165
+ query: zod_1.z.string().describe('Search term'),
166
+ maxResults: zod_1.z.number().optional().describe('Maximum number of results to return')
167
+ }),
168
+ handler: async (args, _extra) => {
169
+ try {
170
+ const posts = await bloggerService.searchPosts(args.blogId, args.query, args.maxResults);
171
+ return {
172
+ content: [
173
+ {
174
+ type: 'text',
175
+ text: JSON.stringify({ posts }, null, 2)
176
+ }
177
+ ]
178
+ };
179
+ }
180
+ catch (error) {
181
+ console.error(`Error searching posts in blog ${args.blogId}:`, error);
182
+ return {
183
+ content: [
184
+ {
185
+ type: 'text',
186
+ text: `Error searching posts: ${error}`
187
+ }
188
+ ],
189
+ isError: true
190
+ };
191
+ }
192
+ }
193
+ },
194
+ {
195
+ name: 'get_post',
196
+ description: 'Retrieves details of a specific post',
197
+ args: zod_1.z.object({
198
+ blogId: zod_1.z.string().describe('Blog ID'),
199
+ postId: zod_1.z.string().describe('Post ID')
200
+ }),
201
+ handler: async (args, _extra) => {
202
+ try {
203
+ const post = await bloggerService.getPost(args.blogId, args.postId);
204
+ return {
205
+ content: [
206
+ {
207
+ type: 'text',
208
+ text: JSON.stringify({ post }, null, 2)
209
+ }
210
+ ]
211
+ };
212
+ }
213
+ catch (error) {
214
+ console.error(`Error fetching post ${args.postId}:`, error);
215
+ return {
216
+ content: [
217
+ {
218
+ type: 'text',
219
+ text: `Error fetching post: ${error}`
220
+ }
221
+ ],
222
+ isError: true
223
+ };
224
+ }
225
+ }
226
+ },
227
+ {
228
+ name: 'create_post',
229
+ description: 'Creates a new post in a blog',
230
+ args: zod_1.z.object({
231
+ blogId: zod_1.z.string().describe('Blog ID'),
232
+ title: zod_1.z.string().describe('Post title'),
233
+ content: zod_1.z.string().describe('Post content'),
234
+ labels: zod_1.z.array(zod_1.z.string()).optional().describe('Labels to associate with the post')
235
+ }),
236
+ handler: async (args, _extra) => {
237
+ try {
238
+ const post = await bloggerService.createPost(args.blogId, {
239
+ title: args.title,
240
+ content: args.content,
241
+ labels: args.labels
242
+ });
243
+ return {
244
+ content: [
245
+ {
246
+ type: 'text',
247
+ text: JSON.stringify({ post }, null, 2)
248
+ }
249
+ ]
250
+ };
251
+ }
252
+ catch (error) {
253
+ console.error(`Error creating post in blog ${args.blogId}:`, error);
254
+ return {
255
+ content: [
256
+ {
257
+ type: 'text',
258
+ text: `Error creating post: ${error}`
259
+ }
260
+ ],
261
+ isError: true
262
+ };
263
+ }
264
+ }
265
+ },
266
+ {
267
+ name: 'update_post',
268
+ description: 'Updates an existing post',
269
+ args: zod_1.z.object({
270
+ blogId: zod_1.z.string().describe('Blog ID'),
271
+ postId: zod_1.z.string().describe('Post ID'),
272
+ title: zod_1.z.string().optional().describe('New post title'),
273
+ content: zod_1.z.string().optional().describe('New post content'),
274
+ labels: zod_1.z.array(zod_1.z.string()).optional().describe('New labels to associate with the post')
275
+ }),
276
+ handler: async (args, _extra) => {
277
+ try {
278
+ const post = await bloggerService.updatePost(args.blogId, args.postId, {
279
+ title: args.title,
280
+ content: args.content,
281
+ labels: args.labels
282
+ });
283
+ return {
284
+ content: [
285
+ {
286
+ type: 'text',
287
+ text: JSON.stringify({ post }, null, 2)
288
+ }
289
+ ]
290
+ };
291
+ }
292
+ catch (error) {
293
+ console.error(`Error updating post ${args.postId}:`, error);
294
+ return {
295
+ content: [
296
+ {
297
+ type: 'text',
298
+ text: `Error updating post: ${error}`
299
+ }
300
+ ],
301
+ isError: true
302
+ };
303
+ }
304
+ }
305
+ },
306
+ {
307
+ name: 'delete_post',
308
+ description: 'Deletes a post',
309
+ args: zod_1.z.object({
310
+ blogId: zod_1.z.string().describe('Blog ID'),
311
+ postId: zod_1.z.string().describe('Post ID')
312
+ }),
313
+ handler: async (args, _extra) => {
314
+ try {
315
+ await bloggerService.deletePost(args.blogId, args.postId);
316
+ return {
317
+ content: [
318
+ {
319
+ type: 'text',
320
+ text: JSON.stringify({ success: true }, null, 2)
321
+ }
322
+ ]
323
+ };
324
+ }
325
+ catch (error) {
326
+ console.error(`Error deleting post ${args.postId}:`, error);
327
+ return {
328
+ content: [
329
+ {
330
+ type: 'text',
331
+ text: `Error deleting post: ${error}`
332
+ }
333
+ ],
334
+ isError: true
335
+ };
336
+ }
337
+ }
338
+ },
339
+ {
340
+ name: 'list_labels',
341
+ description: 'Lists all labels from a blog',
342
+ args: zod_1.z.object({
343
+ blogId: zod_1.z.string().describe('Blog ID')
344
+ }),
345
+ handler: async (args, _extra) => {
346
+ try {
347
+ const labels = await bloggerService.listLabels(args.blogId);
348
+ return {
349
+ content: [
350
+ {
351
+ type: 'text',
352
+ text: JSON.stringify({ labels }, null, 2)
353
+ }
354
+ ]
355
+ };
356
+ }
357
+ catch (error) {
358
+ console.error(`Error fetching labels for blog ${args.blogId}:`, error);
359
+ return {
360
+ content: [
361
+ {
362
+ type: 'text',
363
+ text: `Error fetching labels: ${error}`
364
+ }
365
+ ],
366
+ isError: true
367
+ };
368
+ }
369
+ }
370
+ },
371
+ {
372
+ name: 'get_label',
373
+ description: 'Retrieves details of a specific label',
374
+ args: zod_1.z.object({
375
+ blogId: zod_1.z.string().describe('Blog ID'),
376
+ labelName: zod_1.z.string().describe('Label name')
377
+ }),
378
+ handler: async (args, _extra) => {
379
+ try {
380
+ const label = await bloggerService.getLabel(args.blogId, args.labelName);
381
+ return {
382
+ content: [
383
+ {
384
+ type: 'text',
385
+ text: JSON.stringify({ label }, null, 2)
386
+ }
387
+ ]
388
+ };
389
+ }
390
+ catch (error) {
391
+ console.error(`Error fetching label ${args.labelName}:`, error);
392
+ return {
393
+ content: [
394
+ {
395
+ type: 'text',
396
+ text: `Error fetching label: ${error}`
397
+ }
398
+ ],
399
+ isError: true
400
+ };
401
+ }
402
+ }
403
+ }
404
+ ];
405
+ }
406
+ /**
407
+ * Initializes the MCP server with all Blogger tools
408
+ * @param bloggerService Blogger service to interact with the API
409
+ * @param config Server configuration
410
+ * @returns MCP server instance
411
+ */
412
+ function initMCPServer(bloggerService, config) {
413
+ // Create a new MCP server instance with server information
414
+ const server = new mcp_js_1.McpServer({
415
+ name: "Blogger MCP Server",
416
+ version: "1.0.4",
417
+ vendor: "mcproadev"
418
+ });
419
+ // Get all tool definitions
420
+ const toolDefinitions = createToolDefinitions(bloggerService);
421
+ // Register each tool with the MCP server
422
+ for (const tool of toolDefinitions) {
423
+ // We can't directly pass the schema object if it's already a Zod object in our definition,
424
+ // The MCP SDK expects the shape, not the Zod object itself for the 'args' parameter in server.tool()
425
+ // However, looking at the previous code:
426
+ // server.tool('name', 'desc', { param: z.string() }, handler)
427
+ // The previous code passed an object with Zod schemas as values.
428
+ // Our ToolDefinition.args is a z.ZodType<any>, which is likely a z.object({...}).
429
+ // We need to extract the shape from the z.object to pass it to server.tool if we want to match the signature.
430
+ // Actually, looking at the SDK, server.tool takes:
431
+ // name: string, description: string, args: ToolArgs, handler: ToolCallback
432
+ // where ToolArgs is Record<string, ZodType<any>>
433
+ // So my ToolDefinition.args should probably be Record<string, ZodType<any>> instead of z.ZodType<any>
434
+ // to make it easier to spread.
435
+ // Let's adjust the implementation in the loop.
436
+ // Since I defined args as z.ZodType<any> (which is z.object({...})), I can cast it or access .shape if it's a ZodObject.
437
+ if (tool.args instanceof zod_1.z.ZodObject) {
438
+ server.tool(tool.name, tool.description, tool.args.shape, tool.handler);
439
+ }
440
+ else {
441
+ // Fallback for empty objects or other schemas if we had them (list_blogs has empty object)
442
+ // If it's not a ZodObject, we might have issues if the SDK expects a shape map.
443
+ // list_blogs used {} which is compatible with Record<string, ZodType>
444
+ server.tool(tool.name, tool.description, {}, tool.handler);
445
+ }
446
+ }
447
+ return server;
448
+ }
@@ -0,0 +1,94 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Types used in the MCP server for Blogger
4
+ */
5
+ export interface ToolDefinition {
6
+ name: string;
7
+ description: string;
8
+ args: z.ZodType<any>;
9
+ handler: (args: any, extra?: any) => Promise<any>;
10
+ }
11
+ export interface BloggerBlog {
12
+ id: string;
13
+ name: string;
14
+ description?: string;
15
+ url: string;
16
+ status?: string;
17
+ posts?: BloggerPost[];
18
+ labels?: BloggerLabel[];
19
+ }
20
+ export interface BloggerPost {
21
+ id: string;
22
+ blogId: string;
23
+ title: string;
24
+ content: string;
25
+ url?: string;
26
+ published?: string;
27
+ updated?: string;
28
+ author?: {
29
+ id: string;
30
+ displayName: string;
31
+ url: string;
32
+ image?: {
33
+ url: string;
34
+ };
35
+ };
36
+ labels?: string[];
37
+ }
38
+ export interface BloggerLabel {
39
+ id?: string;
40
+ name: string;
41
+ }
42
+ export interface SearchParams {
43
+ query: string;
44
+ maxResults?: number;
45
+ }
46
+ export interface PaginationParams {
47
+ pageToken?: string;
48
+ maxResults?: number;
49
+ }
50
+ export type ServerMode = {
51
+ type: 'stdio';
52
+ } | {
53
+ type: 'http';
54
+ host: string;
55
+ port: number;
56
+ };
57
+ export interface OAuth2Config {
58
+ clientId?: string;
59
+ clientSecret?: string;
60
+ refreshToken?: string;
61
+ }
62
+ export interface ServerConfig {
63
+ mode: ServerMode;
64
+ blogger: {
65
+ apiKey?: string;
66
+ maxResults: number;
67
+ timeout: number;
68
+ };
69
+ oauth2: OAuth2Config;
70
+ logging: {
71
+ level: string;
72
+ };
73
+ }
74
+ export interface ServerStatus {
75
+ running: boolean;
76
+ mode: string;
77
+ startTime?: Date;
78
+ connections: number;
79
+ tools: string[];
80
+ }
81
+ export interface ClientConnection {
82
+ id: string;
83
+ ip?: string;
84
+ connectedAt: Date;
85
+ lastActivity: Date;
86
+ requestCount: number;
87
+ }
88
+ export interface ServerStats {
89
+ totalRequests: number;
90
+ successfulRequests: number;
91
+ failedRequests: number;
92
+ averageResponseTime: number;
93
+ toolUsage: Record<string, number>;
94
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,22 @@
1
+ import { ServerStatus, ClientConnection, ServerStats } from './types';
2
+ export interface UIManager {
3
+ start(port: number): Promise<void>;
4
+ stop(): Promise<void>;
5
+ updateStatus(status: ServerStatus): void;
6
+ updateConnections(connections: ClientConnection[]): void;
7
+ updateStats(stats: ServerStats): void;
8
+ }
9
+ export declare class WebUIManager implements UIManager {
10
+ private app;
11
+ private server;
12
+ private io;
13
+ private status;
14
+ private connections;
15
+ private stats;
16
+ constructor();
17
+ start(port: number): Promise<void>;
18
+ stop(): Promise<void>;
19
+ updateStatus(status: ServerStatus): void;
20
+ updateConnections(connections: ClientConnection[]): void;
21
+ updateStats(stats: ServerStats): void;
22
+ }
@@ -0,0 +1,110 @@
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.WebUIManager = void 0;
7
+ const express_1 = __importDefault(require("express"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const http_1 = require("http");
10
+ const socket_io_1 = require("socket.io");
11
+ // UI manager implementation
12
+ class WebUIManager {
13
+ constructor() {
14
+ this.server = null;
15
+ this.io = null;
16
+ this.status = {
17
+ running: false,
18
+ mode: 'stopped',
19
+ connections: 0,
20
+ tools: []
21
+ };
22
+ this.connections = [];
23
+ this.stats = {
24
+ totalRequests: 0,
25
+ successfulRequests: 0,
26
+ failedRequests: 0,
27
+ averageResponseTime: 0,
28
+ toolUsage: {}
29
+ };
30
+ this.app = (0, express_1.default)();
31
+ // Express configuration
32
+ this.app.use(express_1.default.json());
33
+ this.app.use(express_1.default.static(path_1.default.join(__dirname, '../public')));
34
+ // API routes
35
+ this.app.get('/api/status', (req, res) => {
36
+ res.json(this.status);
37
+ });
38
+ this.app.get('/api/connections', (req, res) => {
39
+ res.json(this.connections);
40
+ });
41
+ this.app.get('/api/stats', (req, res) => {
42
+ res.json(this.stats);
43
+ });
44
+ // Main route for the UI - wildcard route fix
45
+ this.app.get('/', (req, res) => {
46
+ res.sendFile(path_1.default.join(__dirname, '../public/index.html'));
47
+ });
48
+ }
49
+ async start(port) {
50
+ return new Promise((resolve) => {
51
+ this.server = new http_1.Server(this.app);
52
+ this.io = new socket_io_1.Server(this.server);
53
+ // Socket.IO configuration
54
+ this.io.on('connection', (socket) => {
55
+ console.log('New UI connection:', socket.id);
56
+ // Send initial data
57
+ socket.emit('status', this.status);
58
+ socket.emit('connections', this.connections);
59
+ socket.emit('stats', this.stats);
60
+ // Handle user actions
61
+ socket.on('restart-server', () => {
62
+ console.log('Server restart request received');
63
+ // Restart logic to be implemented
64
+ });
65
+ });
66
+ this.server.listen(port, () => {
67
+ console.log(`Web UI started on port ${port}`);
68
+ resolve();
69
+ });
70
+ });
71
+ }
72
+ async stop() {
73
+ return new Promise((resolve, reject) => {
74
+ if (this.server) {
75
+ this.server.close((err) => {
76
+ if (err) {
77
+ reject(err);
78
+ }
79
+ else {
80
+ this.server = null;
81
+ this.io = null;
82
+ resolve();
83
+ }
84
+ });
85
+ }
86
+ else {
87
+ resolve();
88
+ }
89
+ });
90
+ }
91
+ updateStatus(status) {
92
+ this.status = status;
93
+ if (this.io) {
94
+ this.io.emit('status', status);
95
+ }
96
+ }
97
+ updateConnections(connections) {
98
+ this.connections = connections;
99
+ if (this.io) {
100
+ this.io.emit('connections', connections);
101
+ }
102
+ }
103
+ updateStats(stats) {
104
+ this.stats = stats;
105
+ if (this.io) {
106
+ this.io.emit('stats', stats);
107
+ }
108
+ }
109
+ }
110
+ exports.WebUIManager = WebUIManager;
package/jest.config.js ADDED
@@ -0,0 +1,7 @@
1
+ /** @type {import('ts-jest').JestConfigWithTsJest} */
2
+ module.exports = {
3
+ preset: 'ts-jest',
4
+ testEnvironment: 'node',
5
+ roots: ['<rootDir>/src'],
6
+ testMatch: ['**/*.test.ts'],
7
+ };