@dalcontak/blogger-mcp-server 1.0.0 → 1.0.2
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/.github/workflows/publish.yml +3 -0
- package/AGENTS.md +2 -2
- package/README.md +201 -100
- package/RELEASE.md +64 -32
- package/dist/bloggerService.d.ts +7 -100
- package/dist/bloggerService.js +17 -146
- package/dist/config.d.ts +3 -0
- package/dist/config.js +12 -12
- package/dist/index.js +80 -154
- package/dist/server.d.ts +0 -11
- package/dist/server.js +59 -339
- package/dist/types.d.ts +15 -44
- package/dist/ui-manager.js +8 -16
- package/package.json +5 -1
- package/src/bloggerService.test.ts +5 -1
- package/src/bloggerService.ts +26 -161
- package/src/config.test.ts +34 -20
- package/src/config.ts +17 -16
- package/src/index.ts +115 -194
- package/src/server.test.ts +128 -0
- package/src/server.ts +63 -332
- package/src/types.ts +12 -60
- package/src/ui-manager.ts +17 -26
- package/Dockerfile +0 -64
- package/dist/mcp-sdk-mock.d.ts +0 -57
- package/dist/mcp-sdk-mock.js +0 -227
package/dist/bloggerService.d.ts
CHANGED
|
@@ -1,121 +1,28 @@
|
|
|
1
1
|
import { blogger_v3 } from 'googleapis';
|
|
2
|
-
import {
|
|
3
|
-
/**
|
|
4
|
-
* Custom types to compensate for Blogger API limitations
|
|
5
|
-
*/
|
|
2
|
+
import { BloggerPost } from './types';
|
|
6
3
|
interface BloggerLabelList {
|
|
7
4
|
kind?: string;
|
|
8
|
-
items?:
|
|
5
|
+
items?: Array<{
|
|
6
|
+
name: string;
|
|
7
|
+
}>;
|
|
9
8
|
}
|
|
10
|
-
/**
|
|
11
|
-
* Google Blogger API interaction service
|
|
12
|
-
*
|
|
13
|
-
* Supports two authentication modes:
|
|
14
|
-
* - OAuth2 (GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET + GOOGLE_REFRESH_TOKEN):
|
|
15
|
-
* full access (read + write). Required for listBlogs, createPost, updatePost, deletePost.
|
|
16
|
-
* - API Key (BLOGGER_API_KEY): read-only access to public blogs.
|
|
17
|
-
* Works for getBlog, listPosts, getPost, searchPosts, listLabels, getLabel.
|
|
18
|
-
*
|
|
19
|
-
* If both are configured, OAuth2 is used (it covers all operations).
|
|
20
|
-
*/
|
|
21
9
|
export declare class BloggerService {
|
|
22
10
|
private blogger;
|
|
23
11
|
private readonly isOAuth2;
|
|
24
|
-
/**
|
|
25
|
-
* Initializes the Blogger service with OAuth2 or API key
|
|
26
|
-
*/
|
|
27
12
|
constructor();
|
|
28
|
-
/**
|
|
29
|
-
* Checks that OAuth2 authentication is available.
|
|
30
|
-
* Throws an explicit error if the operation requires OAuth2 and we are in API key mode.
|
|
31
|
-
*/
|
|
32
13
|
private requireOAuth2;
|
|
33
|
-
/**
|
|
34
|
-
* Lists all blogs for the authenticated user.
|
|
35
|
-
* Requires OAuth2 (blogs.listByUser with userId: 'self').
|
|
36
|
-
* @returns Blog list
|
|
37
|
-
*/
|
|
38
14
|
listBlogs(): Promise<blogger_v3.Schema$BlogList>;
|
|
39
|
-
/**
|
|
40
|
-
* Retrieves details of a specific blog
|
|
41
|
-
* @param blogId ID of the blog to retrieve
|
|
42
|
-
* @returns Blog details
|
|
43
|
-
*/
|
|
44
15
|
getBlog(blogId: string): Promise<blogger_v3.Schema$Blog>;
|
|
45
|
-
/**
|
|
46
|
-
* Retrieves a blog by its URL
|
|
47
|
-
* @param url Blog URL
|
|
48
|
-
* @returns Blog details
|
|
49
|
-
*/
|
|
50
16
|
getBlogByUrl(url: string): Promise<blogger_v3.Schema$Blog>;
|
|
51
|
-
/**
|
|
52
|
-
* Simulates blog creation.
|
|
53
|
-
* Note: The Blogger API does not actually allow creating a blog via API.
|
|
54
|
-
* This method simulates the functionality and returns an explanatory error message.
|
|
55
|
-
*
|
|
56
|
-
* @param blogData Blog data to create
|
|
57
|
-
* @returns Explanatory error message
|
|
58
|
-
*/
|
|
59
|
-
createBlog(blogData: Partial<BloggerBlog>): Promise<any>;
|
|
60
|
-
/**
|
|
61
|
-
* Lists posts from a blog
|
|
62
|
-
* @param blogId Blog ID
|
|
63
|
-
* @param maxResults Maximum number of results to return
|
|
64
|
-
* @returns Post list
|
|
65
|
-
*/
|
|
66
17
|
listPosts(blogId: string, maxResults?: number): Promise<blogger_v3.Schema$PostList>;
|
|
67
|
-
/**
|
|
68
|
-
* Searches posts in a blog using the native posts.search endpoint of the Blogger API
|
|
69
|
-
* @param blogId Blog ID
|
|
70
|
-
* @param query Search term
|
|
71
|
-
* @param maxResults Maximum number of results to return
|
|
72
|
-
* @returns List of matching posts
|
|
73
|
-
*/
|
|
74
18
|
searchPosts(blogId: string, query: string, maxResults?: number): Promise<blogger_v3.Schema$PostList>;
|
|
75
|
-
/**
|
|
76
|
-
* Retrieves a specific post
|
|
77
|
-
* @param blogId Blog ID
|
|
78
|
-
* @param postId Post ID
|
|
79
|
-
* @returns Post details
|
|
80
|
-
*/
|
|
81
19
|
getPost(blogId: string, postId: string): Promise<blogger_v3.Schema$Post>;
|
|
82
|
-
/**
|
|
83
|
-
* Creates a new post in a blog.
|
|
84
|
-
* Requires OAuth2.
|
|
85
|
-
* @param blogId Blog ID
|
|
86
|
-
* @param postData Post data to create
|
|
87
|
-
* @returns Created post
|
|
88
|
-
*/
|
|
89
20
|
createPost(blogId: string, postData: Partial<BloggerPost>): Promise<blogger_v3.Schema$Post>;
|
|
90
|
-
/**
|
|
91
|
-
* Updates an existing post.
|
|
92
|
-
* Requires OAuth2.
|
|
93
|
-
* @param blogId Blog ID
|
|
94
|
-
* @param postId Post ID
|
|
95
|
-
* @param postData Post data to update
|
|
96
|
-
* @returns Updated post
|
|
97
|
-
*/
|
|
98
21
|
updatePost(blogId: string, postId: string, postData: Partial<BloggerPost>): Promise<blogger_v3.Schema$Post>;
|
|
99
|
-
/**
|
|
100
|
-
* Deletes a post.
|
|
101
|
-
* Requires OAuth2.
|
|
102
|
-
* @param blogId Blog ID
|
|
103
|
-
* @param postId Post ID
|
|
104
|
-
* @returns Deletion status
|
|
105
|
-
*/
|
|
106
22
|
deletePost(blogId: string, postId: string): Promise<void>;
|
|
107
|
-
/**
|
|
108
|
-
* Lists labels from a blog
|
|
109
|
-
* @param blogId Blog ID
|
|
110
|
-
* @returns Label list
|
|
111
|
-
*/
|
|
112
23
|
listLabels(blogId: string): Promise<BloggerLabelList>;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
* @param labelName Label name
|
|
117
|
-
* @returns Label details
|
|
118
|
-
*/
|
|
119
|
-
getLabel(blogId: string, labelName: string): Promise<BloggerLabel>;
|
|
24
|
+
getLabel(blogId: string, labelName: string): Promise<{
|
|
25
|
+
name: string;
|
|
26
|
+
}>;
|
|
120
27
|
}
|
|
121
28
|
export {};
|
package/dist/bloggerService.js
CHANGED
|
@@ -3,21 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.BloggerService = void 0;
|
|
4
4
|
const googleapis_1 = require("googleapis");
|
|
5
5
|
const config_1 = require("./config");
|
|
6
|
-
/**
|
|
7
|
-
* Google Blogger API interaction service
|
|
8
|
-
*
|
|
9
|
-
* Supports two authentication modes:
|
|
10
|
-
* - OAuth2 (GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET + GOOGLE_REFRESH_TOKEN):
|
|
11
|
-
* full access (read + write). Required for listBlogs, createPost, updatePost, deletePost.
|
|
12
|
-
* - API Key (BLOGGER_API_KEY): read-only access to public blogs.
|
|
13
|
-
* Works for getBlog, listPosts, getPost, searchPosts, listLabels, getLabel.
|
|
14
|
-
*
|
|
15
|
-
* If both are configured, OAuth2 is used (it covers all operations).
|
|
16
|
-
*/
|
|
17
6
|
class BloggerService {
|
|
18
|
-
/**
|
|
19
|
-
* Initializes the Blogger service with OAuth2 or API key
|
|
20
|
-
*/
|
|
21
7
|
constructor() {
|
|
22
8
|
const { oauth2 } = config_1.config;
|
|
23
9
|
const hasOAuth2 = !!(oauth2.clientId && oauth2.clientSecret && oauth2.refreshToken);
|
|
@@ -47,10 +33,6 @@ class BloggerService {
|
|
|
47
33
|
'GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET + GOOGLE_REFRESH_TOKEN (full access).');
|
|
48
34
|
}
|
|
49
35
|
}
|
|
50
|
-
/**
|
|
51
|
-
* Checks that OAuth2 authentication is available.
|
|
52
|
-
* Throws an explicit error if the operation requires OAuth2 and we are in API key mode.
|
|
53
|
-
*/
|
|
54
36
|
requireOAuth2(operation) {
|
|
55
37
|
if (!this.isOAuth2) {
|
|
56
38
|
throw new Error(`Operation "${operation}" requires OAuth2 authentication. ` +
|
|
@@ -58,11 +40,6 @@ class BloggerService {
|
|
|
58
40
|
'Configure GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET and GOOGLE_REFRESH_TOKEN.');
|
|
59
41
|
}
|
|
60
42
|
}
|
|
61
|
-
/**
|
|
62
|
-
* Lists all blogs for the authenticated user.
|
|
63
|
-
* Requires OAuth2 (blogs.listByUser with userId: 'self').
|
|
64
|
-
* @returns Blog list
|
|
65
|
-
*/
|
|
66
43
|
async listBlogs() {
|
|
67
44
|
this.requireOAuth2('list_blogs');
|
|
68
45
|
try {
|
|
@@ -76,16 +53,9 @@ class BloggerService {
|
|
|
76
53
|
throw error;
|
|
77
54
|
}
|
|
78
55
|
}
|
|
79
|
-
/**
|
|
80
|
-
* Retrieves details of a specific blog
|
|
81
|
-
* @param blogId ID of the blog to retrieve
|
|
82
|
-
* @returns Blog details
|
|
83
|
-
*/
|
|
84
56
|
async getBlog(blogId) {
|
|
85
57
|
try {
|
|
86
|
-
const response = await this.blogger.blogs.get({
|
|
87
|
-
blogId
|
|
88
|
-
});
|
|
58
|
+
const response = await this.blogger.blogs.get({ blogId });
|
|
89
59
|
return response.data;
|
|
90
60
|
}
|
|
91
61
|
catch (error) {
|
|
@@ -93,16 +63,9 @@ class BloggerService {
|
|
|
93
63
|
throw error;
|
|
94
64
|
}
|
|
95
65
|
}
|
|
96
|
-
/**
|
|
97
|
-
* Retrieves a blog by its URL
|
|
98
|
-
* @param url Blog URL
|
|
99
|
-
* @returns Blog details
|
|
100
|
-
*/
|
|
101
66
|
async getBlogByUrl(url) {
|
|
102
67
|
try {
|
|
103
|
-
const response = await this.blogger.blogs.getByUrl({
|
|
104
|
-
url
|
|
105
|
-
});
|
|
68
|
+
const response = await this.blogger.blogs.getByUrl({ url });
|
|
106
69
|
return response.data;
|
|
107
70
|
}
|
|
108
71
|
catch (error) {
|
|
@@ -110,31 +73,6 @@ class BloggerService {
|
|
|
110
73
|
throw error;
|
|
111
74
|
}
|
|
112
75
|
}
|
|
113
|
-
/**
|
|
114
|
-
* Simulates blog creation.
|
|
115
|
-
* Note: The Blogger API does not actually allow creating a blog via API.
|
|
116
|
-
* This method simulates the functionality and returns an explanatory error message.
|
|
117
|
-
*
|
|
118
|
-
* @param blogData Blog data to create
|
|
119
|
-
* @returns Explanatory error message
|
|
120
|
-
*/
|
|
121
|
-
async createBlog(blogData) {
|
|
122
|
-
// Simulate a delay to make the response more realistic
|
|
123
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
124
|
-
// Return an explanatory error message
|
|
125
|
-
return {
|
|
126
|
-
error: true,
|
|
127
|
-
message: "The Google Blogger API does not allow creating a new blog via API. Please create a blog manually on blogger.com.",
|
|
128
|
-
details: "This limitation is documented by Google. Blogs must be created via the Blogger web interface.",
|
|
129
|
-
suggestedAction: "Create a blog at https://www.blogger.com, then use its ID with this MCP server."
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Lists posts from a blog
|
|
134
|
-
* @param blogId Blog ID
|
|
135
|
-
* @param maxResults Maximum number of results to return
|
|
136
|
-
* @returns Post list
|
|
137
|
-
*/
|
|
138
76
|
async listPosts(blogId, maxResults) {
|
|
139
77
|
try {
|
|
140
78
|
const response = await this.blogger.posts.list({
|
|
@@ -148,13 +86,6 @@ class BloggerService {
|
|
|
148
86
|
throw error;
|
|
149
87
|
}
|
|
150
88
|
}
|
|
151
|
-
/**
|
|
152
|
-
* Searches posts in a blog using the native posts.search endpoint of the Blogger API
|
|
153
|
-
* @param blogId Blog ID
|
|
154
|
-
* @param query Search term
|
|
155
|
-
* @param maxResults Maximum number of results to return
|
|
156
|
-
* @returns List of matching posts
|
|
157
|
-
*/
|
|
158
89
|
async searchPosts(blogId, query, maxResults) {
|
|
159
90
|
try {
|
|
160
91
|
const response = await this.blogger.posts.search({
|
|
@@ -162,8 +93,6 @@ class BloggerService {
|
|
|
162
93
|
q: query,
|
|
163
94
|
fetchBodies: true
|
|
164
95
|
});
|
|
165
|
-
// The search endpoint does not support maxResults directly,
|
|
166
|
-
// so we truncate client-side if needed
|
|
167
96
|
const items = response.data.items || [];
|
|
168
97
|
const limit = maxResults || config_1.config.blogger.maxResults;
|
|
169
98
|
return {
|
|
@@ -176,18 +105,9 @@ class BloggerService {
|
|
|
176
105
|
throw error;
|
|
177
106
|
}
|
|
178
107
|
}
|
|
179
|
-
/**
|
|
180
|
-
* Retrieves a specific post
|
|
181
|
-
* @param blogId Blog ID
|
|
182
|
-
* @param postId Post ID
|
|
183
|
-
* @returns Post details
|
|
184
|
-
*/
|
|
185
108
|
async getPost(blogId, postId) {
|
|
186
109
|
try {
|
|
187
|
-
const response = await this.blogger.posts.get({
|
|
188
|
-
blogId,
|
|
189
|
-
postId
|
|
190
|
-
});
|
|
110
|
+
const response = await this.blogger.posts.get({ blogId, postId });
|
|
191
111
|
return response.data;
|
|
192
112
|
}
|
|
193
113
|
catch (error) {
|
|
@@ -195,20 +115,15 @@ class BloggerService {
|
|
|
195
115
|
throw error;
|
|
196
116
|
}
|
|
197
117
|
}
|
|
198
|
-
/**
|
|
199
|
-
* Creates a new post in a blog.
|
|
200
|
-
* Requires OAuth2.
|
|
201
|
-
* @param blogId Blog ID
|
|
202
|
-
* @param postData Post data to create
|
|
203
|
-
* @returns Created post
|
|
204
|
-
*/
|
|
205
118
|
async createPost(blogId, postData) {
|
|
206
119
|
this.requireOAuth2('create_post');
|
|
207
120
|
try {
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
121
|
+
const requestBody = {
|
|
122
|
+
title: postData.title ?? undefined,
|
|
123
|
+
content: postData.content ?? undefined,
|
|
124
|
+
labels: postData.labels ?? undefined
|
|
125
|
+
};
|
|
126
|
+
const response = await this.blogger.posts.insert({ blogId, requestBody });
|
|
212
127
|
return response.data;
|
|
213
128
|
}
|
|
214
129
|
catch (error) {
|
|
@@ -216,28 +131,15 @@ class BloggerService {
|
|
|
216
131
|
throw error;
|
|
217
132
|
}
|
|
218
133
|
}
|
|
219
|
-
/**
|
|
220
|
-
* Updates an existing post.
|
|
221
|
-
* Requires OAuth2.
|
|
222
|
-
* @param blogId Blog ID
|
|
223
|
-
* @param postId Post ID
|
|
224
|
-
* @param postData Post data to update
|
|
225
|
-
* @returns Updated post
|
|
226
|
-
*/
|
|
227
134
|
async updatePost(blogId, postId, postData) {
|
|
228
135
|
this.requireOAuth2('update_post');
|
|
229
136
|
try {
|
|
230
|
-
// Convert types to avoid compilation errors
|
|
231
137
|
const requestBody = {
|
|
232
|
-
title: postData.title,
|
|
233
|
-
content: postData.content,
|
|
234
|
-
labels: postData.labels
|
|
138
|
+
title: postData.title ?? undefined,
|
|
139
|
+
content: postData.content ?? undefined,
|
|
140
|
+
labels: postData.labels ?? undefined
|
|
235
141
|
};
|
|
236
|
-
const response = await this.blogger.posts.update({
|
|
237
|
-
blogId,
|
|
238
|
-
postId,
|
|
239
|
-
requestBody
|
|
240
|
-
});
|
|
142
|
+
const response = await this.blogger.posts.update({ blogId, postId, requestBody });
|
|
241
143
|
return response.data;
|
|
242
144
|
}
|
|
243
145
|
catch (error) {
|
|
@@ -245,68 +147,37 @@ class BloggerService {
|
|
|
245
147
|
throw error;
|
|
246
148
|
}
|
|
247
149
|
}
|
|
248
|
-
/**
|
|
249
|
-
* Deletes a post.
|
|
250
|
-
* Requires OAuth2.
|
|
251
|
-
* @param blogId Blog ID
|
|
252
|
-
* @param postId Post ID
|
|
253
|
-
* @returns Deletion status
|
|
254
|
-
*/
|
|
255
150
|
async deletePost(blogId, postId) {
|
|
256
151
|
this.requireOAuth2('delete_post');
|
|
257
152
|
try {
|
|
258
|
-
await this.blogger.posts.delete({
|
|
259
|
-
blogId,
|
|
260
|
-
postId
|
|
261
|
-
});
|
|
153
|
+
await this.blogger.posts.delete({ blogId, postId });
|
|
262
154
|
}
|
|
263
155
|
catch (error) {
|
|
264
156
|
console.error(`Error deleting post ${postId}:`, error);
|
|
265
157
|
throw error;
|
|
266
158
|
}
|
|
267
159
|
}
|
|
268
|
-
/**
|
|
269
|
-
* Lists labels from a blog
|
|
270
|
-
* @param blogId Blog ID
|
|
271
|
-
* @returns Label list
|
|
272
|
-
*/
|
|
273
160
|
async listLabels(blogId) {
|
|
274
161
|
try {
|
|
275
|
-
// The Blogger API does not provide a direct endpoint to list labels
|
|
276
|
-
// We fetch all posts and extract unique labels
|
|
277
162
|
const response = await this.blogger.posts.list({
|
|
278
163
|
blogId,
|
|
279
|
-
maxResults: 50
|
|
164
|
+
maxResults: 50
|
|
280
165
|
});
|
|
281
166
|
const posts = response.data.items || [];
|
|
282
167
|
const labelSet = new Set();
|
|
283
|
-
// Extract all unique labels from posts
|
|
284
168
|
posts.forEach(post => {
|
|
285
|
-
|
|
286
|
-
postLabels.forEach(label => labelSet.add(label));
|
|
169
|
+
(post.labels || []).forEach(label => labelSet.add(label));
|
|
287
170
|
});
|
|
288
|
-
// Convert to expected format
|
|
289
171
|
const labels = Array.from(labelSet).map(name => ({ name }));
|
|
290
|
-
return {
|
|
291
|
-
kind: 'blogger#labelList',
|
|
292
|
-
items: labels
|
|
293
|
-
};
|
|
172
|
+
return { kind: 'blogger#labelList', items: labels };
|
|
294
173
|
}
|
|
295
174
|
catch (error) {
|
|
296
175
|
console.error(`Error fetching labels for blog ${blogId}:`, error);
|
|
297
176
|
throw error;
|
|
298
177
|
}
|
|
299
178
|
}
|
|
300
|
-
/**
|
|
301
|
-
* Retrieves a specific label
|
|
302
|
-
* @param blogId Blog ID
|
|
303
|
-
* @param labelName Label name
|
|
304
|
-
* @returns Label details
|
|
305
|
-
*/
|
|
306
179
|
async getLabel(blogId, labelName) {
|
|
307
180
|
try {
|
|
308
|
-
// The Blogger API does not provide a direct endpoint to retrieve a label
|
|
309
|
-
// We check if the label exists by listing all labels
|
|
310
181
|
const labels = await this.listLabels(blogId);
|
|
311
182
|
const label = labels.items?.find(l => l.name === labelName);
|
|
312
183
|
if (!label) {
|
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.config = void 0;
|
|
4
|
-
|
|
4
|
+
function safeInt(value, defaultValue) {
|
|
5
|
+
if (!value)
|
|
6
|
+
return defaultValue;
|
|
7
|
+
const parsed = parseInt(value, 10);
|
|
8
|
+
return Number.isNaN(parsed) ? defaultValue : parsed;
|
|
9
|
+
}
|
|
5
10
|
exports.config = {
|
|
6
|
-
// Server operating mode (stdio or http)
|
|
7
11
|
mode: process.env.MCP_MODE || 'stdio',
|
|
8
|
-
// HTTP mode configuration (if used)
|
|
9
12
|
http: {
|
|
10
13
|
host: process.env.MCP_HTTP_HOST || '0.0.0.0',
|
|
11
|
-
port:
|
|
14
|
+
port: safeInt(process.env.MCP_HTTP_PORT, 3000)
|
|
12
15
|
},
|
|
13
|
-
// Blogger API configuration
|
|
14
16
|
blogger: {
|
|
15
17
|
apiKey: process.env.BLOGGER_API_KEY,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
// API request timeout in milliseconds
|
|
19
|
-
timeout: parseInt(process.env.BLOGGER_API_TIMEOUT || '30000', 10)
|
|
18
|
+
maxResults: safeInt(process.env.BLOGGER_MAX_RESULTS, 10),
|
|
19
|
+
timeout: safeInt(process.env.BLOGGER_API_TIMEOUT, 30000)
|
|
20
20
|
},
|
|
21
|
-
// OAuth2 configuration for authenticated operations (create, update, delete)
|
|
22
|
-
// If these variables are not set, the server runs in read-only mode (API key)
|
|
23
21
|
oauth2: {
|
|
24
22
|
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
25
23
|
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
26
24
|
refreshToken: process.env.GOOGLE_REFRESH_TOKEN
|
|
27
25
|
},
|
|
28
|
-
// Logging configuration
|
|
29
26
|
logging: {
|
|
30
27
|
level: process.env.LOG_LEVEL || 'info'
|
|
28
|
+
},
|
|
29
|
+
ui: {
|
|
30
|
+
port: safeInt(process.env.UI_PORT, 0)
|
|
31
31
|
}
|
|
32
32
|
};
|