@atlassian-dc-mcp/confluence 0.12.2 → 0.13.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +41 -2
  3. package/build/__tests__/config.test.d.ts +2 -0
  4. package/build/__tests__/config.test.d.ts.map +1 -0
  5. package/build/__tests__/config.test.js +49 -0
  6. package/build/__tests__/config.test.js.map +1 -0
  7. package/build/__tests__/confluence-response-mapper.test.d.ts +2 -0
  8. package/build/__tests__/confluence-response-mapper.test.d.ts.map +1 -0
  9. package/build/__tests__/confluence-response-mapper.test.js +24 -0
  10. package/build/__tests__/confluence-response-mapper.test.js.map +1 -0
  11. package/build/__tests__/confluence-service.test.js +125 -5
  12. package/build/__tests__/confluence-service.test.js.map +1 -1
  13. package/build/config.d.ts +4 -0
  14. package/build/config.d.ts.map +1 -0
  15. package/build/config.js +11 -0
  16. package/build/config.js.map +1 -0
  17. package/build/confluence-response-mapper.d.ts +13 -0
  18. package/build/confluence-response-mapper.d.ts.map +1 -0
  19. package/build/confluence-response-mapper.js +87 -0
  20. package/build/confluence-response-mapper.js.map +1 -0
  21. package/build/confluence-service.d.ts +18 -4
  22. package/build/confluence-service.d.ts.map +1 -1
  23. package/build/confluence-service.js +42 -22
  24. package/build/confluence-service.js.map +1 -1
  25. package/build/index.js +27 -14
  26. package/build/index.js.map +1 -1
  27. package/package.json +3 -3
  28. package/server.json +12 -5
  29. package/src/__tests__/config.test.ts +64 -0
  30. package/src/__tests__/confluence-response-mapper.test.ts +26 -0
  31. package/src/__tests__/confluence-service.test.ts +161 -5
  32. package/src/config.ts +13 -0
  33. package/src/confluence-response-mapper.ts +104 -0
  34. package/src/confluence-service.ts +69 -26
  35. package/src/index.ts +31 -16
  36. package/tsconfig.tsbuildinfo +1 -1
@@ -1,6 +1,8 @@
1
1
  import { z } from 'zod';
2
2
  import { ContentResourceService, OpenAPI, SearchService } from './confluence-client/index.js';
3
3
  import { handleApiOperation } from '@atlassian-dc-mcp/common';
4
+ import { getDefaultPageSize, getMissingConfig } from './config.js';
5
+ import { shapeConfluenceContent } from './confluence-response-mapper.js';
4
6
  /**
5
7
  * Escapes user input for safe use inside a CQL quoted string.
6
8
  * Escapes backslash first, then double quote, so that neither can break out of the phrase.
@@ -9,14 +11,24 @@ import { handleApiOperation } from '@atlassian-dc-mcp/common';
9
11
  export function escapeSearchTextForCql(searchText) {
10
12
  return searchText.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
11
13
  }
14
+ function resolveToken(token, missingTokenMessage) {
15
+ return async () => {
16
+ const resolvedToken = typeof token === 'function' ? token() : token;
17
+ if (!resolvedToken) {
18
+ throw new Error(missingTokenMessage);
19
+ }
20
+ return resolvedToken;
21
+ };
22
+ }
12
23
  export class ConfluenceService {
24
+ getPageSize;
13
25
  /**
14
26
  * Creates a new ConfluenceService instance
15
27
  * @param host The hostname of the Confluence server (e.g., "host.com")
16
28
  * @param token The API token for authentication
17
29
  * @param fullApiUrl Optional full API URL (e.g., "https://host.com/wiki/"). If provided, host and apiBasePath are ignored.
18
30
  */
19
- constructor(host, token, fullApiUrl) {
31
+ constructor(host, token, fullApiUrl, getPageSize = getDefaultPageSize) {
20
32
  if (fullApiUrl) {
21
33
  OpenAPI.BASE = fullApiUrl;
22
34
  }
@@ -26,21 +38,32 @@ export class ConfluenceService {
26
38
  else {
27
39
  throw new Error('Either host or fullApiUrl must be provided');
28
40
  }
29
- OpenAPI.TOKEN = token;
41
+ OpenAPI.TOKEN = resolveToken(token, 'Missing required environment variable: CONFLUENCE_API_TOKEN');
30
42
  OpenAPI.VERSION = '1.0';
43
+ this.getPageSize = getPageSize;
31
44
  }
32
45
  /**
33
46
  * Get a Confluence page by ID
34
47
  * @param contentId The ID of the page to retrieve
35
48
  * @param expand Optional comma-separated list of properties to expand
36
49
  */
37
- async getContent(contentId, expand) {
50
+ async getContentRaw(contentId, expand) {
38
51
  const expandValue = expand || 'body.storage';
39
52
  const finalExpand = expand && !expand.includes('body.storage')
40
53
  ? `${expand},body.storage`
41
54
  : expandValue;
42
55
  return handleApiOperation(() => ContentResourceService.getContentById(contentId, finalExpand), 'Error getting content');
43
56
  }
57
+ async getContent(contentId, expand, bodyMode = 'storage', maxBodyChars) {
58
+ const result = await this.getContentRaw(contentId, expand);
59
+ if (result.success && result.data) {
60
+ return {
61
+ ...result,
62
+ data: shapeConfluenceContent(result.data, bodyMode, maxBodyChars),
63
+ };
64
+ }
65
+ return result;
66
+ }
44
67
  /**
45
68
  * Search for content in Confluence using CQL
46
69
  * @param cql Confluence Query Language string
@@ -48,8 +71,8 @@ export class ConfluenceService {
48
71
  * @param start Start index for pagination
49
72
  * @param expand Optional comma-separated list of properties to expand
50
73
  */
51
- async searchContent(cql, limit, start, expand) {
52
- return handleApiOperation(() => SearchService.search1(undefined, expand, undefined, limit?.toString(), start?.toString(), undefined, cql), 'Error searching for content');
74
+ async searchContent(cql, limit, start, expand, excerpt = 'none') {
75
+ return handleApiOperation(() => SearchService.search1(undefined, expand, undefined, (limit ?? this.getPageSize()).toString(), start?.toString(), excerpt, cql), 'Error searching for content');
53
76
  }
54
77
  /**
55
78
  * Create a new page in Confluence
@@ -73,56 +96,53 @@ export class ConfluenceService {
73
96
  * @param start Start index for pagination
74
97
  * @param expand Optional comma-separated list of properties to expand
75
98
  */
76
- async searchSpaces(searchText, limit, start, expand) {
99
+ async searchSpaces(searchText, limit, start, expand, excerpt = 'none') {
77
100
  // Create a CQL query that searches for spaces
78
101
  // The correct syntax for space search is: type=space AND title ~ "searchText"
79
102
  const escapedSearchText = escapeSearchTextForCql(searchText);
80
103
  const cql = `type=space AND title ~ "${escapedSearchText}"`;
81
- return handleApiOperation(() => SearchService.search1(undefined, expand, undefined, limit?.toString(), start?.toString(), undefined, cql), 'Error searching for spaces');
104
+ return handleApiOperation(() => SearchService.search1(undefined, expand, undefined, (limit ?? this.getPageSize()).toString(), start?.toString(), excerpt, cql), 'Error searching for spaces');
82
105
  }
83
106
  static validateConfig() {
84
- const missingVars = [];
85
- // API token is always required
86
- if (!process.env.CONFLUENCE_API_TOKEN) {
87
- missingVars.push('CONFLUENCE_API_TOKEN');
88
- }
89
- // Either CONFLUENCE_HOST or CONFLUENCE_API_BASE_PATH must be set
90
- if (!process.env.CONFLUENCE_HOST && !process.env.CONFLUENCE_API_BASE_PATH) {
91
- missingVars.push('CONFLUENCE_HOST or CONFLUENCE_API_BASE_PATH');
92
- }
93
- return missingVars;
107
+ return getMissingConfig();
94
108
  }
95
109
  }
96
110
  export const confluenceToolSchemas = {
97
111
  getContent: {
98
112
  contentId: z.string().describe("Confluence Data Center content ID"),
99
- expand: z.string().optional().describe("Comma-separated list of properties to expand")
113
+ expand: z.string().optional().describe("Comma-separated list of properties to expand"),
114
+ bodyMode: z.enum(['storage', 'text', 'none']).optional().describe("How to return the page body. Defaults to storage for backward compatibility."),
115
+ maxBodyChars: z.number().optional().describe("Maximum number of characters to keep when bodyMode is text")
100
116
  },
101
117
  searchContent: {
102
118
  cql: z.string().describe("Confluence Query Language (CQL) search string for Confluence Data Center"),
103
119
  limit: z.number().optional().describe("Maximum number of results to return"),
104
120
  start: z.number().optional().describe("Start index for pagination"),
105
- expand: z.string().optional().describe("Comma-separated list of properties to expand")
121
+ expand: z.string().optional().describe("Comma-separated list of properties to expand"),
122
+ excerpt: z.enum(['none', 'highlight']).optional().describe("Excerpt mode for search results. Defaults to none.")
106
123
  },
107
124
  createContent: {
108
125
  title: z.string().describe("Title of the content"),
109
126
  spaceKey: z.string().describe("Space key where content will be created"),
110
127
  type: z.string().default("page").describe("Content type (page, blogpost, etc)"),
111
128
  content: z.string().describe("Content body in Confluence Data Center \"storage\" format (confluence XML)"),
112
- parentId: z.string().optional().describe("ID of the parent page (if creating a child page)")
129
+ parentId: z.string().optional().describe("ID of the parent page (if creating a child page)"),
130
+ output: z.enum(['ack', 'full']).optional().describe("Return a compact acknowledgement or the full API response. Defaults to ack.")
113
131
  },
114
132
  updateContent: {
115
133
  contentId: z.string().describe("ID of the content to update"),
116
134
  title: z.string().optional().describe("New title of the content"),
117
135
  content: z.string().optional().describe("New content body in Confluence Data Center storage format (XML-based)"),
118
136
  version: z.number().describe("New version number (must be incremented)"),
119
- versionComment: z.string().optional().describe("Comment for this version")
137
+ versionComment: z.string().optional().describe("Comment for this version"),
138
+ output: z.enum(['ack', 'full']).optional().describe("Return a compact acknowledgement or the full API response. Defaults to ack.")
120
139
  },
121
140
  searchSpaces: {
122
141
  searchText: z.string().describe("Text to search for in Confluence Data Center space names or descriptions. Quotes and backslashes are escaped for CQL; pass the literal search phrase only (do not pre-escape)."),
123
142
  limit: z.number().optional().describe("Maximum number of results to return"),
124
143
  start: z.number().optional().describe("Start index for pagination"),
125
- expand: z.string().optional().describe("Comma-separated list of properties to expand")
144
+ expand: z.string().optional().describe("Comma-separated list of properties to expand"),
145
+ excerpt: z.enum(['none', 'highlight']).optional().describe("Excerpt mode for search results. Defaults to none.")
126
146
  }
127
147
  };
128
148
  //# sourceMappingURL=confluence-service.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"confluence-service.js","sourceRoot":"","sources":["../src/confluence-service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"confluence-service.js","sourceRoot":"","sources":["../src/confluence-service.ts"],"names":[],"mappings":"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"}
package/build/index.js CHANGED
@@ -1,15 +1,16 @@
1
- import { connectServer, createMcpServer, formatToolResponse } from '@atlassian-dc-mcp/common';
1
+ import { connectServer, createMcpServer, formatToolResponse, initializeRuntimeConfig } from '@atlassian-dc-mcp/common';
2
2
  import { ConfluenceService, confluenceToolSchemas } from './confluence-service.js';
3
- import dotenv from 'dotenv';
4
- // Load environment variables
5
- dotenv.config();
3
+ import { shapeConfluenceMutationAck } from './confluence-response-mapper.js';
4
+ import { getConfluenceRuntimeConfig, getDefaultPageSize } from './config.js';
5
+ initializeRuntimeConfig();
6
6
  // Validate required environment variables
7
7
  const missingEnvVars = ConfluenceService.validateConfig();
8
8
  if (missingEnvVars.length > 0) {
9
9
  throw new Error(`Missing required environment variables: ${missingEnvVars.join(', ')}`);
10
10
  }
11
11
  // Initialize Confluence service
12
- const confluenceService = new ConfluenceService(process.env.CONFLUENCE_API_BASE_PATH ? undefined : process.env.CONFLUENCE_HOST, process.env.CONFLUENCE_API_TOKEN, process.env.CONFLUENCE_API_BASE_PATH);
12
+ const confluenceConfig = getConfluenceRuntimeConfig();
13
+ const confluenceService = new ConfluenceService(confluenceConfig.host, () => getConfluenceRuntimeConfig().token, confluenceConfig.apiBasePath, getDefaultPageSize);
13
14
  // Define Confluence instance type
14
15
  const confluenceInstanceType = "Confluence Data Center edition instance";
15
16
  // Initialize MCP server
@@ -18,15 +19,15 @@ const server = createMcpServer({
18
19
  version: "1.0.0"
19
20
  });
20
21
  // Add Confluence content tools
21
- server.tool("confluence_getContent", `Get Confluence content by ID from the ${confluenceInstanceType}`, confluenceToolSchemas.getContent, async ({ contentId, expand }) => {
22
- const result = await confluenceService.getContent(contentId, expand);
22
+ server.tool("confluence_getContent", `Get Confluence content by ID from the ${confluenceInstanceType}`, confluenceToolSchemas.getContent, async ({ contentId, expand, bodyMode, maxBodyChars }) => {
23
+ const result = await confluenceService.getContent(contentId, expand, bodyMode, maxBodyChars);
23
24
  return formatToolResponse(result);
24
25
  });
25
- server.tool("confluence_searchContent", `Search for content in ${confluenceInstanceType} using CQL`, confluenceToolSchemas.searchContent, async ({ cql, limit, start, expand }) => {
26
- const result = await confluenceService.searchContent(cql, limit, start, expand);
26
+ server.tool("confluence_searchContent", `Search for content in ${confluenceInstanceType} using CQL`, confluenceToolSchemas.searchContent, async ({ cql, limit, start, expand, excerpt }) => {
27
+ const result = await confluenceService.searchContent(cql, limit, start, expand, excerpt);
27
28
  return formatToolResponse(result);
28
29
  });
29
- server.tool("confluence_createContent", `Create new content in ${confluenceInstanceType}`, confluenceToolSchemas.createContent, async ({ title, spaceKey, type, content, parentId }) => {
30
+ server.tool("confluence_createContent", `Create new content in ${confluenceInstanceType}`, confluenceToolSchemas.createContent, async ({ title, spaceKey, type, content, parentId, output }) => {
30
31
  const contentObj = {
31
32
  type: type || 'page',
32
33
  title,
@@ -43,11 +44,17 @@ server.tool("confluence_createContent", `Create new content in ${confluenceInsta
43
44
  contentObj.ancestors = [{ id: parentId }];
44
45
  }
45
46
  const result = await confluenceService.createContent(contentObj);
47
+ if (result.success && result.data && output !== 'full') {
48
+ return formatToolResponse({
49
+ ...result,
50
+ data: shapeConfluenceMutationAck(result.data),
51
+ });
52
+ }
46
53
  return formatToolResponse(result);
47
54
  });
48
- server.tool("confluence_updateContent", `Update existing content in ${confluenceInstanceType}`, confluenceToolSchemas.updateContent, async ({ contentId, title, content, version, versionComment }) => {
55
+ server.tool("confluence_updateContent", `Update existing content in ${confluenceInstanceType}`, confluenceToolSchemas.updateContent, async ({ contentId, title, content, version, versionComment, output }) => {
49
56
  // First get the current content to build upon
50
- const currentContent = await confluenceService.getContent(contentId);
57
+ const currentContent = await confluenceService.getContentRaw(contentId);
51
58
  if (!currentContent.success || !currentContent.data) {
52
59
  return formatToolResponse({
53
60
  success: false,
@@ -76,10 +83,16 @@ server.tool("confluence_updateContent", `Update existing content in ${confluence
76
83
  };
77
84
  }
78
85
  const result = await confluenceService.updateContent(contentId, updateObj);
86
+ if (result.success && result.data && output !== 'full') {
87
+ return formatToolResponse({
88
+ ...result,
89
+ data: shapeConfluenceMutationAck(result.data),
90
+ });
91
+ }
79
92
  return formatToolResponse(result);
80
93
  });
81
- server.tool('confluence_searchSpace', `Search for spaces in ${confluenceInstanceType}`, confluenceToolSchemas.searchSpaces, async ({ searchText, limit, start, expand }) => {
82
- const result = await confluenceService.searchSpaces(searchText, limit, start, expand);
94
+ server.tool('confluence_searchSpace', `Search for spaces in ${confluenceInstanceType}`, confluenceToolSchemas.searchSpaces, async ({ searchText, limit, start, expand, excerpt }) => {
95
+ const result = await confluenceService.searchSpaces(searchText, limit, start, expand, excerpt);
83
96
  return formatToolResponse(result);
84
97
  });
85
98
  await connectServer(server);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AACvH,OAAO,EAAE,iBAAiB,EAAqB,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AACtG,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAE7E,uBAAuB,EAAE,CAAC;AAE1B,0CAA0C;AAC1C,MAAM,cAAc,GAAG,iBAAiB,CAAC,cAAc,EAAE,CAAC;AAC1D,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;IAC9B,MAAM,IAAI,KAAK,CAAC,2CAA2C,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC1F,CAAC;AAED,gCAAgC;AAChC,MAAM,gBAAgB,GAAG,0BAA0B,EAAE,CAAC;AACtD,MAAM,iBAAiB,GAAG,IAAI,iBAAiB,CAC7C,gBAAgB,CAAC,IAAI,EACrB,GAAG,EAAE,CAAC,0BAA0B,EAAE,CAAC,KAAK,EACxC,gBAAgB,CAAC,WAAW,EAC5B,kBAAkB,CACnB,CAAC;AAEF,kCAAkC;AAClC,MAAM,sBAAsB,GAAG,yCAAyC,CAAC;AAEzE,wBAAwB;AACxB,MAAM,MAAM,GAAG,eAAe,CAAC;IAC7B,IAAI,EAAE,0BAA0B;IAChC,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,+BAA+B;AAC/B,MAAM,CAAC,IAAI,CACT,uBAAuB,EACvB,yCAAyC,sBAAsB,EAAE,EACjE,qBAAqB,CAAC,UAAU,EAChC,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,EAAE;IACtD,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC;IAC7F,OAAO,kBAAkB,CAAC,MAAM,CAAC,CAAC;AACpC,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,IAAI,CACT,0BAA0B,EAC1B,yBAAyB,sBAAsB,YAAY,EAC3D,qBAAqB,CAAC,aAAa,EACnC,KAAK,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE;IAC/C,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,aAAa,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IACzF,OAAO,kBAAkB,CAAC,MAAM,CAAC,CAAC;AACpC,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,IAAI,CACT,0BAA0B,EAC1B,yBAAyB,sBAAsB,EAAE,EACjD,qBAAqB,CAAC,aAAa,EACnC,KAAK,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE;IAC7D,MAAM,UAAU,GAAsB;QACpC,IAAI,EAAE,IAAI,IAAI,MAAM;QACpB,KAAK;QACL,KAAK,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE;QACxB,IAAI,EAAE;YACJ,OAAO,EAAE;gBACP,KAAK,EAAE,OAAO;gBACd,cAAc,EAAE,SAAS;aAC1B;SACF;KACF,CAAC;IAEF,2CAA2C;IAC3C,IAAI,QAAQ,EAAE,CAAC;QACb,UAAU,CAAC,SAAS,GAAG,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;IACjE,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,IAAI,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACvD,OAAO,kBAAkB,CAAC;YACxB,GAAG,MAAM;YACT,IAAI,EAAE,0BAA0B,CAAC,MAAM,CAAC,IAAI,CAAC;SAC9C,CAAC,CAAC;IACL,CAAC;IACD,OAAO,kBAAkB,CAAC,MAAM,CAAC,CAAC;AACpC,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,IAAI,CACT,0BAA0B,EAC1B,8BAA8B,sBAAsB,EAAE,EACtD,qBAAqB,CAAC,aAAa,EACnC,KAAK,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE;IACvE,8CAA8C;IAC9C,MAAM,cAAc,GAAG,MAAM,iBAAiB,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;IAExE,IAAI,CAAC,cAAc,CAAC,OAAO,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;QACpD,OAAO,kBAAkB,CAAC;YACxB,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,sCAAsC,SAAS,KAAK,cAAc,CAAC,KAAK,IAAI,eAAe,EAAE;SACrG,CAAC,CAAC;IACL,CAAC;IAED,6DAA6D;IAC7D,MAAM,WAAW,GAAG,cAAc,CAAC,IAIlC,CAAC;IAEF,MAAM,SAAS,GAAsB;QACnC,EAAE,EAAE,SAAS;QACb,IAAI,EAAE,WAAW,CAAC,IAAI;QACtB,KAAK,EAAE,KAAK,IAAI,WAAW,CAAC,KAAK;QACjC,KAAK,EAAE,WAAW,CAAC,KAAK;QACxB,OAAO,EAAE;YACP,MAAM,EAAE,OAAO;YACf,OAAO,EAAE,cAAc;SACxB;KACF,CAAC;IAEF,0CAA0C;IAC1C,IAAI,OAAO,EAAE,CAAC;QACZ,SAAS,CAAC,IAAI,GAAG;YACf,OAAO,EAAE;gBACP,KAAK,EAAE,OAAO;gBACd,cAAc,EAAE,SAAS;aAC1B;SACF,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,aAAa,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAC3E,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,IAAI,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACvD,OAAO,kBAAkB,CAAC;YACxB,GAAG,MAAM;YACT,IAAI,EAAE,0BAA0B,CAAC,MAAM,CAAC,IAAI,CAAC;SAC9C,CAAC,CAAC;IACL,CAAC;IACD,OAAO,kBAAkB,CAAC,MAAM,CAAC,CAAC;AACpC,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAClC,wBAAwB,sBAAsB,EAAE,EAChD,qBAAqB,CAAC,YAAY,EAClC,KAAK,EAAE,EACE,UAAU,EACV,KAAK,EACL,KAAK,EACL,MAAM,EACN,OAAO,EACR,EAAE,EAAE;IACV,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,YAAY,CAAC,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/F,OAAO,kBAAkB,CAAC,MAAM,CAAC,CAAC;AACpC,CAAC,CAAC,CAAC;AAEL,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlassian-dc-mcp/confluence",
3
- "version": "0.12.2",
3
+ "version": "0.13.0",
4
4
  "main": "build/index.js",
5
5
  "type": "module",
6
6
  "bin": "./bin/run.js",
@@ -14,7 +14,7 @@
14
14
  "test": "jest --passWithNoTests"
15
15
  },
16
16
  "dependencies": {
17
- "@atlassian-dc-mcp/common": "^0.12.2",
17
+ "@atlassian-dc-mcp/common": "^0.13.0",
18
18
  "@modelcontextprotocol/sdk": "^1.27.1",
19
19
  "dotenv": "^16.4.7",
20
20
  "node-fetch": "^3.3.2",
@@ -29,5 +29,5 @@
29
29
  "publishConfig": {
30
30
  "access": "public"
31
31
  },
32
- "gitHead": "6488590c1832714d510cc4a4f5f3d9e8e38f3b09"
32
+ "gitHead": "2f5845bf1c5837d547bcc957dcf01c4f9865c83a"
33
33
  }
package/server.json CHANGED
@@ -18,22 +18,29 @@
18
18
  },
19
19
  "environmentVariables": [
20
20
  {
21
- "description": "Confluence host domain (e.g. your-instance.atlassian.net)",
21
+ "description": "Absolute path to a shared dotenv-style config file. When set, values are read from this file before direct environment variable overrides are applied.",
22
+ "isRequired": false,
23
+ "format": "string",
24
+ "isSecret": false,
25
+ "name": "ATLASSIAN_DC_MCP_CONFIG_FILE"
26
+ },
27
+ {
28
+ "description": "Confluence host domain (e.g. your-instance.atlassian.net). Required unless provided through ATLASSIAN_DC_MCP_CONFIG_FILE or CONFLUENCE_API_BASE_PATH.",
22
29
  "isRequired": false,
23
30
  "format": "string",
24
31
  "isSecret": false,
25
32
  "name": "CONFLUENCE_HOST"
26
33
  },
27
34
  {
28
- "description": "Confluence API base path (alternative to CONFLUENCE_HOST)",
35
+ "description": "Confluence API base path (alternative to CONFLUENCE_HOST). Required unless provided through ATLASSIAN_DC_MCP_CONFIG_FILE or CONFLUENCE_HOST.",
29
36
  "isRequired": false,
30
37
  "format": "string",
31
38
  "isSecret": false,
32
39
  "name": "CONFLUENCE_API_BASE_PATH"
33
40
  },
34
41
  {
35
- "description": "Confluence Personal Access Token or API token",
36
- "isRequired": true,
42
+ "description": "Confluence Personal Access Token or API token. Required unless provided through ATLASSIAN_DC_MCP_CONFIG_FILE.",
43
+ "isRequired": false,
37
44
  "format": "string",
38
45
  "isSecret": true,
39
46
  "name": "CONFLUENCE_API_TOKEN"
@@ -41,4 +48,4 @@
41
48
  ]
42
49
  }
43
50
  ]
44
- }
51
+ }
@@ -0,0 +1,64 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ describe('Confluence config', () => {
6
+ const originalEnv = process.env;
7
+ const originalCwd = process.cwd();
8
+ let tempDir: string;
9
+
10
+ beforeEach(() => {
11
+ jest.resetModules();
12
+ process.env = { ...originalEnv };
13
+ delete process.env.ATLASSIAN_DC_MCP_CONFIG_FILE;
14
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'confluence-config-'));
15
+ process.chdir(tempDir);
16
+ });
17
+
18
+ afterEach(() => {
19
+ fs.rmSync(tempDir, { recursive: true, force: true });
20
+ process.chdir(originalCwd);
21
+ });
22
+
23
+ afterAll(() => {
24
+ process.env = originalEnv;
25
+ });
26
+
27
+ it('uses the configured page size when the env var is a positive integer', async () => {
28
+ process.env.CONFLUENCE_DEFAULT_PAGE_SIZE = '40';
29
+
30
+ const { getDefaultPageSize } = await import('../config.js');
31
+
32
+ expect(getDefaultPageSize()).toBe(40);
33
+ });
34
+
35
+ it('falls back to 25 when the env var is invalid', async () => {
36
+ process.env.CONFLUENCE_DEFAULT_PAGE_SIZE = '0';
37
+
38
+ const { getDefaultPageSize } = await import('../config.js');
39
+
40
+ expect(getDefaultPageSize()).toBe(25);
41
+ });
42
+
43
+ it('reads the page size from the shared config file', async () => {
44
+ const sharedConfigPath = path.join(tempDir, 'shared.env');
45
+ fs.writeFileSync(sharedConfigPath, 'CONFLUENCE_HOST=file-host\nCONFLUENCE_API_TOKEN=file-token\nCONFLUENCE_DEFAULT_PAGE_SIZE=35\n');
46
+ process.env.ATLASSIAN_DC_MCP_CONFIG_FILE = sharedConfigPath;
47
+
48
+ const { getConfluenceRuntimeConfig, getDefaultPageSize } = await import('../config.js');
49
+
50
+ expect(getDefaultPageSize()).toBe(35);
51
+ expect(getConfluenceRuntimeConfig().token).toBe('file-token');
52
+ });
53
+
54
+ it('keeps env values higher priority than the shared config file', async () => {
55
+ const sharedConfigPath = path.join(tempDir, 'shared.env');
56
+ fs.writeFileSync(sharedConfigPath, 'CONFLUENCE_HOST=file-host\nCONFLUENCE_API_TOKEN=file-token\nCONFLUENCE_DEFAULT_PAGE_SIZE=35\n');
57
+ process.env.ATLASSIAN_DC_MCP_CONFIG_FILE = sharedConfigPath;
58
+ process.env.CONFLUENCE_DEFAULT_PAGE_SIZE = '45';
59
+
60
+ const { getDefaultPageSize } = await import('../config.js');
61
+
62
+ expect(getDefaultPageSize()).toBe(45);
63
+ });
64
+ });
@@ -0,0 +1,26 @@
1
+ import { shapeConfluenceMutationAck } from '../confluence-response-mapper.js';
2
+
3
+ describe('shapeConfluenceMutationAck', () => {
4
+ it('returns a compact acknowledgement with a resolved content URL', () => {
5
+ expect(
6
+ shapeConfluenceMutationAck({
7
+ id: '123',
8
+ type: 'page',
9
+ title: 'Test page',
10
+ space: { key: 'DOCS' },
11
+ version: { number: 7 },
12
+ _links: {
13
+ base: 'https://confluence.example.com',
14
+ webui: '/pages/viewpage.action?pageId=123',
15
+ },
16
+ })
17
+ ).toEqual({
18
+ id: '123',
19
+ type: 'page',
20
+ title: 'Test page',
21
+ spaceKey: 'DOCS',
22
+ version: 7,
23
+ url: 'https://confluence.example.com/pages/viewpage.action?pageId=123',
24
+ });
25
+ });
26
+ });
@@ -1,8 +1,12 @@
1
1
  import { ConfluenceService, escapeSearchTextForCql } from '../confluence-service.js';
2
- import { SearchService } from '../confluence-client/index.js';
2
+ import { ContentResourceService, SearchService } from '../confluence-client/index.js';
3
3
 
4
4
  jest.mock('../confluence-client/index.js', () => ({
5
- ContentResourceService: {},
5
+ ContentResourceService: {
6
+ getContentById: jest.fn(),
7
+ createContent: jest.fn(),
8
+ update2: jest.fn(),
9
+ },
6
10
  SearchService: {
7
11
  search1: jest.fn(),
8
12
  },
@@ -75,7 +79,7 @@ describe('ConfluenceService.searchSpaces', () => {
75
79
  undefined,
76
80
  '10',
77
81
  '0',
78
- undefined,
82
+ 'none',
79
83
  'type=space AND title ~ "my space"'
80
84
  );
81
85
  });
@@ -91,7 +95,7 @@ describe('ConfluenceService.searchSpaces', () => {
91
95
  undefined,
92
96
  '5',
93
97
  undefined,
94
- undefined,
98
+ 'none',
95
99
  'type=space AND title ~ "say \\"hello\\""'
96
100
  );
97
101
  });
@@ -107,7 +111,7 @@ describe('ConfluenceService.searchSpaces', () => {
107
111
  undefined,
108
112
  '5',
109
113
  undefined,
110
- undefined,
114
+ 'none',
111
115
  'type=space AND title ~ "path\\\\to\\\\space"'
112
116
  );
113
117
  });
@@ -122,3 +126,155 @@ describe('ConfluenceService.searchSpaces', () => {
122
126
  expect(result.error).toBeDefined();
123
127
  });
124
128
  });
129
+
130
+ describe('ConfluenceService token optimization paths', () => {
131
+ let service: ConfluenceService;
132
+
133
+ beforeEach(() => {
134
+ service = new ConfluenceService('test-host', 'test-token');
135
+ jest.clearAllMocks();
136
+ });
137
+
138
+ it('keeps storage mode as the default body shape', async () => {
139
+ const mockContent = {
140
+ id: '123',
141
+ type: 'page',
142
+ title: 'Test page',
143
+ body: {
144
+ storage: {
145
+ value: '<p>Hello</p>',
146
+ representation: 'storage',
147
+ },
148
+ },
149
+ };
150
+ (ContentResourceService.getContentById as jest.Mock).mockResolvedValue(mockContent);
151
+
152
+ const result = await service.getContent('123');
153
+
154
+ expect(result.success).toBe(true);
155
+ expect(result.data).toBe(mockContent);
156
+ expect(ContentResourceService.getContentById).toHaveBeenCalledWith('123', 'body.storage');
157
+ });
158
+
159
+ it('converts storage XML to text when bodyMode is text', async () => {
160
+ (ContentResourceService.getContentById as jest.Mock).mockResolvedValue({
161
+ id: '123',
162
+ type: 'page',
163
+ title: 'Test page',
164
+ body: {
165
+ storage: {
166
+ value: '<p>Hello &amp; <strong>world</strong></p><ul><li>One</li><li>Two</li></ul>',
167
+ representation: 'storage',
168
+ },
169
+ },
170
+ version: { number: 3 },
171
+ });
172
+
173
+ const result = await service.getContent('123', 'version', 'text');
174
+
175
+ expect(result.success).toBe(true);
176
+ expect(ContentResourceService.getContentById).toHaveBeenCalledWith('123', 'version,body.storage');
177
+ expect(result.data).toMatchObject({
178
+ id: '123',
179
+ type: 'page',
180
+ title: 'Test page',
181
+ version: { number: 3 },
182
+ body: {
183
+ text: {
184
+ representation: 'text',
185
+ },
186
+ },
187
+ });
188
+ expect((result.data as any).body.text.value).toContain('Hello & world');
189
+ expect((result.data as any).body.text.value).toContain('- One');
190
+ expect((result.data as any).body.text.value).toContain('- Two');
191
+ });
192
+
193
+ it('truncates text bodies when maxBodyChars is provided', async () => {
194
+ (ContentResourceService.getContentById as jest.Mock).mockResolvedValue({
195
+ id: '123',
196
+ type: 'page',
197
+ title: 'Test page',
198
+ body: {
199
+ storage: {
200
+ value: '<p>Hello world</p>',
201
+ representation: 'storage',
202
+ },
203
+ },
204
+ });
205
+
206
+ const result = await service.getContent('123', undefined, 'text', 5);
207
+
208
+ expect(result.success).toBe(true);
209
+ expect(result.data).toEqual({
210
+ id: '123',
211
+ type: 'page',
212
+ title: 'Test page',
213
+ body: {
214
+ text: {
215
+ value: 'Hello',
216
+ representation: 'text',
217
+ truncated: true,
218
+ originalLength: 11,
219
+ },
220
+ },
221
+ });
222
+ });
223
+
224
+ it('omits the body when bodyMode is none', async () => {
225
+ (ContentResourceService.getContentById as jest.Mock).mockResolvedValue({
226
+ id: '123',
227
+ type: 'page',
228
+ title: 'Test page',
229
+ body: {
230
+ storage: {
231
+ value: '<p>Hello</p>',
232
+ representation: 'storage',
233
+ },
234
+ },
235
+ version: { number: 1 },
236
+ });
237
+
238
+ const result = await service.getContent('123', undefined, 'none');
239
+
240
+ expect(result.success).toBe(true);
241
+ expect(result.data).toEqual({
242
+ id: '123',
243
+ type: 'page',
244
+ title: 'Test page',
245
+ version: { number: 1 },
246
+ });
247
+ });
248
+
249
+ it('uses the package default limit and no excerpt for content search', async () => {
250
+ (SearchService.search1 as jest.Mock).mockResolvedValue({ results: [] });
251
+
252
+ await service.searchContent('type=page');
253
+
254
+ expect(SearchService.search1).toHaveBeenCalledWith(
255
+ undefined,
256
+ undefined,
257
+ undefined,
258
+ '25',
259
+ undefined,
260
+ 'none',
261
+ 'type=page'
262
+ );
263
+ });
264
+
265
+ it('forwards explicit excerpt for space search', async () => {
266
+ (SearchService.search1 as jest.Mock).mockResolvedValue({ results: [] });
267
+
268
+ await service.searchSpaces('docs', 5, 10, 'space.icon', 'highlight');
269
+
270
+ expect(SearchService.search1).toHaveBeenCalledWith(
271
+ undefined,
272
+ 'space.icon',
273
+ undefined,
274
+ '5',
275
+ '10',
276
+ 'highlight',
277
+ 'type=space AND title ~ "docs"'
278
+ );
279
+ });
280
+ });
package/src/config.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { getProductRuntimeConfig, validateProductRuntimeConfig } from '@atlassian-dc-mcp/common';
2
+
3
+ export function getConfluenceRuntimeConfig() {
4
+ return getProductRuntimeConfig('confluence');
5
+ }
6
+
7
+ export function getDefaultPageSize() {
8
+ return getConfluenceRuntimeConfig().defaultPageSize;
9
+ }
10
+
11
+ export function getMissingConfig() {
12
+ return validateProductRuntimeConfig('confluence');
13
+ }