@satiyap/confluence-reader-mcp 0.1.1 → 0.1.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/README.md CHANGED
@@ -2,39 +2,35 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@satiyap/confluence-reader-mcp.svg)](https://www.npmjs.com/package/@satiyap/confluence-reader-mcp)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.0-blue.svg)](https://www.typescriptlang.org/)
6
- [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18-green.svg)](https://nodejs.org/)
7
5
 
8
- MCP server for fetching and comparing Confluence documentation with local files. Enables AI assistants to read Confluence pages and generate git-style diffs against local documentation.
6
+ An MCP server that lets AI assistants read Confluence pages, walk page trees, and diff Confluence content against local documentation.
9
7
 
10
- ## Features
8
+ ## Setup
11
9
 
12
- - **URL-based fetching**: Pass any Confluence page URL, automatically extracts page ID
13
- - **Clean text extraction**: Converts Confluence storage HTML to readable text/markdown
14
- - **Git-style diffs**: Generate unified diffs comparing Confluence docs with local documentation
15
- - **Flexible auth**: Supports scoped API tokens with Bearer authentication
16
- - **Dual routing**: Works with cloudId routing or direct baseUrl
17
- - **Zero install**: Use via `npx` for frictionless setup
10
+ ### 1. Get a Confluence API Token
18
11
 
19
- ## Quick Start
12
+ Create a scoped API token at: https://support.atlassian.com/confluence/kb/scoped-api-tokens-in-confluence-cloud/
20
13
 
21
- ### 1. Set Environment Variables
14
+ ### 2. Set Environment Variables
22
15
 
23
- Add these to your shell profile (`~/.zshrc`, `~/.bashrc`, etc.):
16
+ Add to your shell profile (`~/.zshrc`, `~/.bashrc`, etc.):
24
17
 
25
18
  ```bash
26
- export CONFLUENCE_TOKEN="your_scoped_token_here"
27
- export CONFLUENCE_EMAIL="your.email@company.com"
19
+ export CONFLUENCE_TOKEN="your_scoped_token"
20
+ export CONFLUENCE_EMAIL="you@company.com"
28
21
  export CONFLUENCE_CLOUD_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
29
22
  ```
30
23
 
31
- Then reload your shell (`source ~/.zshrc`) or open a new terminal.
24
+ Reload your shell or open a new terminal.
32
25
 
33
- Get your scoped API token from: https://support.atlassian.com/confluence/kb/scoped-api-tokens-in-confluence-cloud/
34
-
35
- ### 2. Configure MCP
26
+ | Variable | Required | Description |
27
+ |----------|----------|-------------|
28
+ | `CONFLUENCE_TOKEN` | Yes | Scoped API token |
29
+ | `CONFLUENCE_EMAIL` | Yes | Email tied to your Atlassian account |
30
+ | `CONFLUENCE_CLOUD_ID` | One of these | Cloud ID — routes via `api.atlassian.com` |
31
+ | `CONFLUENCE_BASE_URL` | is required | Direct tenant URL, e.g. `https://yourteam.atlassian.net` |
36
32
 
37
- Add to your MCP settings (`mcp.json`). No `env` block needed — credentials come from `.env`:
33
+ ### 3. Add to MCP Config
38
34
 
39
35
  ```json
40
36
  {
@@ -47,173 +43,47 @@ Add to your MCP settings (`mcp.json`). No `env` block needed — credentials com
47
43
  }
48
44
  ```
49
45
 
50
- ### 3. Restart Your MCP Host
46
+ Restart the MCP host to pick up the new server.
51
47
 
52
- Restart your MCP-compatible application to load the server.
53
-
54
- ## Environment Variables
55
-
56
- | Variable | Required | Description |
57
- |----------|----------|-------------|
58
- | `CONFLUENCE_TOKEN` | ✅ Yes | [Scoped API token](https://support.atlassian.com/confluence/kb/scoped-api-tokens-in-confluence-cloud/) |
59
- | `CONFLUENCE_EMAIL` | ✅ Yes | Email address associated with your Atlassian account |
60
- | `CONFLUENCE_CLOUD_ID` | Recommended | Atlassian Cloud ID for api.atlassian.com routing |
61
- | `CONFLUENCE_BASE_URL` | Optional | Fallback: `https://yourtenant.atlassian.net` |
48
+ ## Tools
62
49
 
63
- **Authentication:**
64
- - Uses scoped API tokens with Basic authentication (email:token)
65
- - Scoped tokens provide granular access control and better security than legacy API tokens
50
+ ### `confluence.fetch_page`
66
51
 
67
- **Routing:**
68
- - If `CONFLUENCE_CLOUD_ID` is set → Uses `https://api.atlassian.com/ex/confluence/{cloudId}`
69
- - Otherwise uses `CONFLUENCE_BASE_URL`
52
+ Fetches a single Confluence page by URL and returns its content as text. Also lists any direct child pages at the bottom of the response.
70
53
 
71
- ## Available Tools
54
+ | Parameter | Type | Description |
55
+ |-----------|------|-------------|
56
+ | `url` | string | Confluence page URL |
72
57
 
73
- ### `confluence.fetch_doc`
58
+ ### `confluence.fetch_page_tree`
74
59
 
75
- Fetch a Confluence Cloud page by URL, returning clean text for analysis.
60
+ Fetches a page and all its descendants recursively, up to a given depth. Returns a single markdown document with nested headings.
76
61
 
77
- **Parameters:**
78
- - `url` (string, required): Confluence page URL
79
- - Supports: `/wiki/spaces/KEY/pages/123456789/Title`
80
- - Supports: `/wiki/pages/viewpage.action?pageId=123456789`
81
- - `includeStorageHtml` (boolean, optional): If true, also returns original storage HTML
62
+ | Parameter | Type | Default | Description |
63
+ |-----------|------|---------|-------------|
64
+ | `url` | string | — | Confluence page URL |
65
+ | `depth` | number | 1 | How many levels of children to fetch |
82
66
 
83
- **Returns:**
84
- ```json
85
- {
86
- "pageId": "123456789",
87
- "title": "Page Title",
88
- "status": "current",
89
- "version": 42,
90
- "webui": "/wiki/spaces/...",
91
- "extractedText": "Clean text content...",
92
- "storageHtml": "..." // if includeStorageHtml=true
93
- }
94
- ```
67
+ ### `confluence.compare`
95
68
 
96
- ### `docs.build_comparison_bundle`
69
+ Generates a git-style unified diff between a Confluence page and a local markdown string.
97
70
 
98
- Build a git-style unified diff comparing local documentation against Confluence content.
71
+ | Parameter | Type | Description |
72
+ |-----------|------|-------------|
73
+ | `url` | string | Confluence page URL |
74
+ | `localContent` | string | Local markdown to compare against |
99
75
 
100
- **Parameters:**
101
- - `confluenceText` (string, required): Text from `confluence.fetch_doc.extractedText`
102
- - `prd` (string, optional): Local document text (e.g., PRD, requirements)
103
- - `systemOverview` (string, optional): Local document text (e.g., architecture overview)
104
- - `systemDesign` (string, optional): Local document text (e.g., technical design)
105
- - `lld` (string, optional): Local document text (e.g., detailed design, implementation notes)
76
+ Returns a JSON object with `additions`, `deletions`, `totalChanges`, and the full `diff`.
106
77
 
107
- **Note:** Parameter names are flexible - use them for any type of documentation you want to compare.
108
-
109
- **Returns:**
110
- ```json
111
- {
112
- "totalComparisons": 2,
113
- "diffs": [
114
- {
115
- "document": "PRD",
116
- "additions": 15,
117
- "deletions": 8,
118
- "totalChanges": 23,
119
- "diff": "--- a/confluence\n+++ b/prd\n@@ -1,5 +1,5 @@\n context line\n-removed line\n+added line\n context line"
120
- },
121
- {
122
- "document": "System Design",
123
- "additions": 42,
124
- "deletions": 12,
125
- "totalChanges": 54,
126
- "diff": "..."
127
- }
128
- ]
129
- }
130
- ```
131
-
132
- ## Usage Example
133
-
134
- When a user provides a Confluence URL in their prompt:
135
-
136
- 1. AI assistant detects the URL
137
- 2. Calls `confluence.fetch_doc` with the URL
138
- 3. Calls `docs.build_comparison_bundle` with:
139
- - `confluenceText` from step 2
140
- - Local documentation content from filesystem
141
- 4. AI assistant analyzes the structured comparison and reports differences
142
-
143
- ## Supported Confluence URL Formats
78
+ ## Supported URL Formats
144
79
 
145
80
  - `/wiki/spaces/SPACEKEY/pages/123456789/Page+Title`
146
81
  - `/wiki/pages/viewpage.action?pageId=123456789`
147
- - Any URL containing `/pages/<numeric-id>/`
148
-
149
- ## Project Structure
150
-
151
- ```
152
- confluence-reader-mcp/
153
- ├── src/
154
- │ ├── index.ts # MCP server + tool registrations
155
- │ ├── confluence/
156
- │ │ ├── client.ts # HTTP client with scoped token auth
157
- │ │ ├── url.ts # URL → pageId parser
158
- │ │ ├── types.ts # API response types
159
- │ │ └── transform.ts # Storage HTML → text converter
160
- │ └── compare/
161
- │ └── diff.ts # Git-style unified diff generator
162
- ├── dist/ # Compiled output
163
- ├── package.json # Binary: confluence-reader-mcp
164
- ├── tsconfig.json
165
- ├── .gitignore
166
- └── README.md
167
- ```
168
-
169
- ## Development
170
-
171
- ```bash
172
- npm run dev # Run with tsx (no build needed)
173
- npm run build # Compile TypeScript
174
- npm start # Run compiled server
175
- ```
176
-
177
- ## Security Notes
178
-
179
- - ✅ Credentials read from OS environment variables only — never in config files
180
- - ✅ Never commit tokens to git
181
- - ✅ Use scoped API tokens with minimal permissions
182
-
183
- ## Publishing to npm (Optional)
184
-
185
- Once ready for public use:
186
-
187
- 1. Update `package.json` with your repository URL and author info
188
- 2. Build the package:
189
- ```bash
190
- npm run build
191
- ```
192
- 3. Publish to npm:
193
- ```bash
194
- npm publish --access public
195
- ```
196
-
197
- Then users can use this minimal config (no env block needed):
198
- ```json
199
- {
200
- "mcpServers": {
201
- "confluence-reader": {
202
- "command": "npx",
203
- "args": ["@satiyap/confluence-reader-mcp"]
204
- }
205
- }
206
- }
207
- ```
208
-
209
- **Note:** Users must set `CONFLUENCE_TOKEN`, `CONFLUENCE_EMAIL`, and `CONFLUENCE_CLOUD_ID` as OS environment variables.
210
82
 
211
- ## API References
83
+ ## Security
212
84
 
213
- - [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
214
- - [Model Context Protocol](https://modelcontextprotocol.io/)
215
- - [Confluence REST API v2](https://developer.atlassian.com/cloud/confluence/rest/v2/)
216
- - [Atlassian Scoped API Tokens](https://support.atlassian.com/confluence/kb/scoped-api-tokens-in-confluence-cloud/)
85
+ - Credentials are read from environment variables only — never passed in config files.
86
+ - Use scoped tokens with the minimum permissions needed.
217
87
 
218
88
  ## License
219
89
 
@@ -51,3 +51,39 @@ export async function fetchPageById(cfg, pageId) {
51
51
  }
52
52
  return (await res.json());
53
53
  }
54
+ /**
55
+ * Fetch direct child pages of a Confluence page using the v2 REST API.
56
+ * Returns all children (paginates automatically).
57
+ */
58
+ export async function fetchChildPages(cfg, pageId) {
59
+ const base = buildBase(cfg);
60
+ const all = [];
61
+ let cursor;
62
+ while (true) {
63
+ const url = new URL(`${base}/wiki/api/v2/pages/${pageId}/children`);
64
+ url.searchParams.set("limit", "50");
65
+ if (cursor)
66
+ url.searchParams.set("cursor", cursor);
67
+ const res = await fetch(url.toString(), {
68
+ method: "GET",
69
+ headers: {
70
+ ...buildAuthHeaders(cfg),
71
+ Accept: "application/json",
72
+ },
73
+ });
74
+ if (!res.ok) {
75
+ const text = await res.text().catch(() => "");
76
+ throw new Error(`Confluence API error ${res.status}: ${text.slice(0, 500)}`);
77
+ }
78
+ const data = (await res.json());
79
+ all.push(...data.results);
80
+ if (!data._links?.next)
81
+ break;
82
+ // The next link contains the cursor parameter
83
+ const nextUrl = new URL(data._links.next, base);
84
+ cursor = nextUrl.searchParams.get("cursor") ?? undefined;
85
+ if (!cursor)
86
+ break;
87
+ }
88
+ return all;
89
+ }
package/dist/index.js CHANGED
@@ -3,12 +3,12 @@ import { z } from "zod";
3
3
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
5
  import { extractConfluencePageId } from "./confluence/url.js";
6
- import { fetchPageById, buildAuthHeaders, buildBase } from "./confluence/client.js";
6
+ import { fetchPageById, fetchChildPages, buildAuthHeaders, buildBase } from "./confluence/client.js";
7
7
  import { storageToText } from "./confluence/transform.js";
8
8
  import { generateUnifiedDiff, generateDiffStats } from "./compare/diff.js";
9
9
  const server = new McpServer({
10
10
  name: "confluence-reader-mcp",
11
- version: "0.1.1"
11
+ version: "0.1.2"
12
12
  });
13
13
  function getEnv(name) {
14
14
  const v = process.env[name];
@@ -46,20 +46,55 @@ server.tool("confluence.fetch_page", "Fetch a Confluence page and return it as m
46
46
  const email = getEnv("CONFLUENCE_EMAIL");
47
47
  const cloudId = getEnv("CONFLUENCE_CLOUD_ID");
48
48
  const baseUrl = getEnv("CONFLUENCE_BASE_URL");
49
+ const cfg = { token, email, cloudId, baseUrl };
49
50
  const pageId = extractConfluencePageId(url);
50
- const page = await fetchPageById({ token, email, cloudId, baseUrl }, pageId);
51
+ const page = await fetchPageById(cfg, pageId);
52
+ const children = await fetchChildPages(cfg, pageId);
51
53
  const storage = page.body?.storage?.value ?? "";
52
54
  const markdown = storage ? storageToText(storage) : "";
55
+ const childLinks = children.map(c => `- [${c.title}] (id: ${c.id})`).join("\n");
56
+ const body = childLinks
57
+ ? `${markdown}\n\n## Child Pages\n${childLinks}`
58
+ : markdown;
53
59
  return {
54
- content: [{ type: "text", text: markdown }]
60
+ content: [{ type: "text", text: body }]
55
61
  };
56
62
  });
57
63
  server.tool("confluence.fetch_page_tree", "Fetch a Confluence page and all its child pages recursively up to a specified depth.", {
58
64
  url: z.string().describe("Confluence page URL"),
59
65
  depth: z.number().optional().default(1).describe("How many levels deep to fetch child pages (default: 1)")
60
66
  }, async ({ url, depth }) => {
61
- // TODO: Implement recursive child page fetching
62
- throw new Error("Not yet implemented - coming soon!");
67
+ const token = getEnv("CONFLUENCE_TOKEN");
68
+ const email = getEnv("CONFLUENCE_EMAIL");
69
+ const cloudId = getEnv("CONFLUENCE_CLOUD_ID");
70
+ const baseUrl = getEnv("CONFLUENCE_BASE_URL");
71
+ const cfg = { token, email, cloudId, baseUrl };
72
+ const pageId = extractConfluencePageId(url);
73
+ async function buildTree(id, remaining) {
74
+ const page = await fetchPageById(cfg, id);
75
+ const storage = page.body?.storage?.value ?? "";
76
+ const content = storage ? storageToText(storage) : "";
77
+ const children = [];
78
+ if (remaining > 0) {
79
+ const childPages = await fetchChildPages(cfg, id);
80
+ for (const child of childPages) {
81
+ children.push(await buildTree(child.id, remaining - 1));
82
+ }
83
+ }
84
+ return { id: page.id, title: page.title, content, children };
85
+ }
86
+ const tree = await buildTree(pageId, depth);
87
+ function renderTree(node, level) {
88
+ const heading = "#".repeat(Math.min(level + 1, 6));
89
+ const parts = [`${heading} ${node.title}`, node.content];
90
+ for (const child of node.children) {
91
+ parts.push(renderTree(child, level + 1));
92
+ }
93
+ return parts.join("\n\n");
94
+ }
95
+ return {
96
+ content: [{ type: "text", text: renderTree(tree, 0) }]
97
+ };
63
98
  });
64
99
  server.tool("confluence.compare", "Compare a local markdown file or string with a Confluence page and show the differences.", {
65
100
  url: z.string().describe("Confluence page URL"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@satiyap/confluence-reader-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "MCP server for fetching and comparing Confluence documentation with local files",
5
5
  "author": "satiyap",
6
6
  "license": "MIT",