@satiyap/confluence-reader-mcp 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 satiyap
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,214 @@
1
+ # Confluence Reader MCP Server
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@satiyap/confluence-reader-mcp.svg)](https://www.npmjs.com/package/@satiyap/confluence-reader-mcp)
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
+
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.
9
+
10
+ ## Features
11
+
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
18
+
19
+ ## Quick Start
20
+
21
+ ### 1. Set Environment Variables
22
+
23
+ Get your scoped API token from: https://support.atlassian.com/confluence/kb/scoped-api-tokens-in-confluence-cloud/
24
+
25
+ ```bash
26
+ export CONFLUENCE_TOKEN="your_scoped_token_here"
27
+ export CONFLUENCE_CLOUD_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
28
+ ```
29
+
30
+ Or copy `.env.example` to `.env` and fill in your values.
31
+
32
+ ### 2. Install Dependencies & Build
33
+
34
+ ```bash
35
+ npm install
36
+ npm run build
37
+ ```
38
+
39
+ ### 3. Configure MCP
40
+
41
+ Add to your MCP settings configuration file (e.g., `mcp.json` or similar):
42
+
43
+ ```json
44
+ {
45
+ "mcpServers": {
46
+ "confluence-reader": {
47
+ "command": "npx",
48
+ "args": ["@satiyap/confluence-reader-mcp"],
49
+ "env": {
50
+ "CONFLUENCE_TOKEN": "${env:CONFLUENCE_TOKEN}",
51
+ "CONFLUENCE_CLOUD_ID": "${env:CONFLUENCE_CLOUD_ID}"
52
+ }
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
58
+ ### 4. Restart Your MCP Host
59
+
60
+ Restart your MCP-compatible application to load the server.
61
+
62
+ ## Environment Variables
63
+
64
+ | Variable | Required | Description |
65
+ |----------|----------|-------------|
66
+ | `CONFLUENCE_TOKEN` | Yes | [Scoped API token](https://support.atlassian.com/confluence/kb/scoped-api-tokens-in-confluence-cloud/) (Bearer auth only) |
67
+ | `CONFLUENCE_CLOUD_ID` | Recommended | Atlassian Cloud ID for api.atlassian.com routing |
68
+ | `CONFLUENCE_BASE_URL` | Optional | Fallback: `https://yourtenant.atlassian.net` |
69
+
70
+ **Authentication:**
71
+ - Only supports scoped API tokens with Bearer authentication
72
+ - No email/password or Basic auth support
73
+
74
+ **Routing:**
75
+ - If `CONFLUENCE_CLOUD_ID` is set, uses `https://api.atlassian.com/ex/confluence/{cloudId}`
76
+ - Otherwise uses `CONFLUENCE_BASE_URL`
77
+
78
+ ## Available Tools
79
+
80
+ ### `confluence.fetch_doc`
81
+
82
+ Fetch a Confluence Cloud page by URL, returning clean text for analysis.
83
+
84
+ **Parameters:**
85
+ - `url` (string, required): Confluence page URL
86
+ - Supports: `/wiki/spaces/KEY/pages/123456789/Title`
87
+ - Supports: `/wiki/pages/viewpage.action?pageId=123456789`
88
+ - `includeStorageHtml` (boolean, optional): If true, also returns original storage HTML
89
+
90
+ **Returns:**
91
+ ```json
92
+ {
93
+ "pageId": "123456789",
94
+ "title": "Page Title",
95
+ "status": "current",
96
+ "version": 42,
97
+ "webui": "/wiki/spaces/...",
98
+ "extractedText": "Clean text content...",
99
+ "storageHtml": "..." // if includeStorageHtml=true
100
+ }
101
+ ```
102
+
103
+ ### `docs.build_comparison_bundle`
104
+
105
+ Build a git-style unified diff comparing local documentation against Confluence content.
106
+
107
+ **Parameters:**
108
+ - `confluenceText` (string, required): Text from `confluence.fetch_doc.extractedText`
109
+ - `prd` (string, optional): Local document text (e.g., PRD, requirements)
110
+ - `systemOverview` (string, optional): Local document text (e.g., architecture overview)
111
+ - `systemDesign` (string, optional): Local document text (e.g., technical design)
112
+ - `lld` (string, optional): Local document text (e.g., detailed design, implementation notes)
113
+
114
+ **Note:** Parameter names are flexible - use them for any type of documentation you want to compare.
115
+
116
+ **Returns:**
117
+ ```json
118
+ {
119
+ "totalComparisons": 2,
120
+ "diffs": [
121
+ {
122
+ "document": "PRD",
123
+ "additions": 15,
124
+ "deletions": 8,
125
+ "totalChanges": 23,
126
+ "diff": "--- a/confluence\n+++ b/prd\n@@ -1,5 +1,5 @@\n context line\n-removed line\n+added line\n context line"
127
+ },
128
+ {
129
+ "document": "System Design",
130
+ "additions": 42,
131
+ "deletions": 12,
132
+ "totalChanges": 54,
133
+ "diff": "..."
134
+ }
135
+ ]
136
+ }
137
+ ```
138
+
139
+ ## Usage Example
140
+
141
+ When a user provides a Confluence URL in their prompt:
142
+
143
+ 1. AI assistant detects the URL
144
+ 2. Calls `confluence.fetch_doc` with the URL
145
+ 3. Calls `docs.build_comparison_bundle` with:
146
+ - `confluenceText` from step 2
147
+ - Local documentation content from filesystem
148
+ 4. AI assistant analyzes the structured comparison and reports differences
149
+
150
+ ## Supported Confluence URL Formats
151
+
152
+ - `/wiki/spaces/SPACEKEY/pages/123456789/Page+Title`
153
+ - `/wiki/pages/viewpage.action?pageId=123456789`
154
+ - Any URL containing `/pages/<numeric-id>/`
155
+
156
+ ## Project Structure
157
+
158
+ ```
159
+ confluence-reader-mcp/
160
+ ├── src/
161
+ │ ├── index.ts # MCP server + tool registrations
162
+ │ ├── confluence/
163
+ │ │ ├── client.ts # HTTP client with scoped token auth
164
+ │ │ ├── url.ts # URL → pageId parser
165
+ │ │ ├── types.ts # API response types
166
+ │ │ └── transform.ts # Storage HTML → text converter
167
+ │ └── compare/
168
+ │ └── diff.ts # Git-style unified diff generator
169
+ ├── dist/ # Compiled output
170
+ ├── package.json # Binary: confluence-reader-mcp
171
+ ├── tsconfig.json
172
+ ├── .env.example
173
+ ├── .gitignore
174
+ └── README.md
175
+ ```
176
+
177
+ ## Development
178
+
179
+ ```bash
180
+ npm run dev # Run with tsx (no build needed)
181
+ npm run build # Compile TypeScript
182
+ npm start # Run compiled server
183
+ ```
184
+
185
+ ## Security Notes
186
+
187
+ - Never commit tokens to git (see `.gitignore`)
188
+ - Use scoped API tokens with minimal permissions
189
+ - Tokens are read from OS environment only
190
+ - No tokens in config files
191
+
192
+ ## Publishing to npm (Optional)
193
+
194
+ Once ready for public use:
195
+
196
+ 1. Build the package:
197
+ ```bash
198
+ npm run build
199
+ ```
200
+ 2. Publish to npm:
201
+ ```bash
202
+ npm publish --access public
203
+ ```
204
+
205
+ ## API References
206
+
207
+ - [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
208
+ - [Model Context Protocol](https://modelcontextprotocol.io/)
209
+ - [Confluence REST API v2](https://developer.atlassian.com/cloud/confluence/rest/v2/)
210
+ - [Atlassian Scoped API Tokens](https://support.atlassian.com/confluence/kb/scoped-api-tokens-in-confluence-cloud/)
211
+
212
+ ## License
213
+
214
+ MIT
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Generate a git-style unified diff between two texts
3
+ * This is a simple line-based diff implementation
4
+ */
5
+ function longestCommonSubsequence(a, b) {
6
+ const m = a.length;
7
+ const n = b.length;
8
+ const dp = Array(m + 1).fill(0).map(() => Array(n + 1).fill(0));
9
+ for (let i = 1; i <= m; i++) {
10
+ for (let j = 1; j <= n; j++) {
11
+ if (a[i - 1] === b[j - 1]) {
12
+ dp[i][j] = dp[i - 1][j - 1] + 1;
13
+ }
14
+ else {
15
+ dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
16
+ }
17
+ }
18
+ }
19
+ return dp;
20
+ }
21
+ function buildDiff(a, b, dp) {
22
+ const result = [];
23
+ let i = a.length;
24
+ let j = b.length;
25
+ while (i > 0 || j > 0) {
26
+ if (i > 0 && j > 0 && a[i - 1] === b[j - 1]) {
27
+ result.unshift({ type: 'context', line: a[i - 1], oldLineNum: i, newLineNum: j });
28
+ i--;
29
+ j--;
30
+ }
31
+ else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
32
+ result.unshift({ type: 'add', line: b[j - 1], newLineNum: j });
33
+ j--;
34
+ }
35
+ else if (i > 0) {
36
+ result.unshift({ type: 'remove', line: a[i - 1], oldLineNum: i });
37
+ i--;
38
+ }
39
+ }
40
+ return result;
41
+ }
42
+ function formatUnifiedDiff(oldLabel, newLabel, diffLines, contextLines = 3) {
43
+ if (diffLines.length === 0) {
44
+ return `--- ${oldLabel}\n+++ ${newLabel}\n(no differences)\n`;
45
+ }
46
+ const output = [];
47
+ output.push(`--- ${oldLabel}`);
48
+ output.push(`+++ ${newLabel}`);
49
+ // Group changes into hunks
50
+ const hunks = [];
51
+ let currentHunk = [];
52
+ let lastChangeIndex = -1;
53
+ diffLines.forEach((line, idx) => {
54
+ const isChange = line.type !== 'context';
55
+ if (isChange) {
56
+ // Include context before and after
57
+ const start = Math.max(0, lastChangeIndex + 1, idx - contextLines);
58
+ const contextBefore = diffLines.slice(start, idx).filter(l => !currentHunk.includes(l));
59
+ currentHunk.push(...contextBefore, line);
60
+ lastChangeIndex = idx;
61
+ }
62
+ else if (lastChangeIndex >= 0 && idx - lastChangeIndex <= contextLines) {
63
+ // Context after a change
64
+ currentHunk.push(line);
65
+ }
66
+ else if (lastChangeIndex >= 0 && idx - lastChangeIndex > contextLines) {
67
+ // End current hunk
68
+ if (currentHunk.length > 0) {
69
+ hunks.push(currentHunk);
70
+ currentHunk = [];
71
+ }
72
+ lastChangeIndex = -1;
73
+ }
74
+ });
75
+ if (currentHunk.length > 0) {
76
+ hunks.push(currentHunk);
77
+ }
78
+ // Format each hunk
79
+ hunks.forEach(hunk => {
80
+ const firstLine = hunk[0];
81
+ const lastLine = hunk[hunk.length - 1];
82
+ const oldStart = firstLine.oldLineNum || 1;
83
+ const newStart = firstLine.newLineNum || 1;
84
+ const oldCount = hunk.filter(l => l.type !== 'add').length;
85
+ const newCount = hunk.filter(l => l.type !== 'remove').length;
86
+ output.push(`@@ -${oldStart},${oldCount} +${newStart},${newCount} @@`);
87
+ hunk.forEach(line => {
88
+ switch (line.type) {
89
+ case 'context':
90
+ output.push(` ${line.line}`);
91
+ break;
92
+ case 'add':
93
+ output.push(`+${line.line}`);
94
+ break;
95
+ case 'remove':
96
+ output.push(`-${line.line}`);
97
+ break;
98
+ }
99
+ });
100
+ });
101
+ return output.join('\n');
102
+ }
103
+ export function generateUnifiedDiff(oldText, newText, oldLabel = 'a/original', newLabel = 'b/modified') {
104
+ const oldLines = oldText.split('\n');
105
+ const newLines = newText.split('\n');
106
+ const dp = longestCommonSubsequence(oldLines, newLines);
107
+ const diffLines = buildDiff(oldLines, newLines, dp);
108
+ return formatUnifiedDiff(oldLabel, newLabel, diffLines);
109
+ }
110
+ export function generateDiffStats(oldText, newText) {
111
+ const oldLines = oldText.split('\n');
112
+ const newLines = newText.split('\n');
113
+ const dp = longestCommonSubsequence(oldLines, newLines);
114
+ const diffLines = buildDiff(oldLines, newLines, dp);
115
+ const additions = diffLines.filter(l => l.type === 'add').length;
116
+ const deletions = diffLines.filter(l => l.type === 'remove').length;
117
+ return {
118
+ additions,
119
+ deletions,
120
+ changes: additions + deletions
121
+ };
122
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Build authorization headers for Confluence API requests
3
+ * Only supports scoped API tokens with Bearer authentication
4
+ *
5
+ * @see https://support.atlassian.com/confluence/kb/scoped-api-tokens-in-confluence-cloud/
6
+ */
7
+ function buildAuthHeaders(cfg) {
8
+ return { Authorization: `Bearer ${cfg.token}` };
9
+ }
10
+ /**
11
+ * Build base URL for Confluence API requests
12
+ * Prefers cloudId routing over direct baseUrl
13
+ */
14
+ function buildBase(cfg) {
15
+ // Prefer cloudId routing (works well with scoped token access patterns)
16
+ if (cfg.cloudId)
17
+ return `https://api.atlassian.com/ex/confluence/${cfg.cloudId}`;
18
+ if (cfg.baseUrl)
19
+ return cfg.baseUrl;
20
+ throw new Error("Set CONFLUENCE_CLOUD_ID or CONFLUENCE_BASE_URL.");
21
+ }
22
+ /**
23
+ * Fetch a Confluence page by ID using the v2 REST API
24
+ *
25
+ * @param cfg - Client configuration with token and routing info
26
+ * @param pageId - Numeric page ID
27
+ * @returns Page data including title, content, and metadata
28
+ * @throws Error if API request fails
29
+ */
30
+ export async function fetchPageById(cfg, pageId) {
31
+ const base = buildBase(cfg);
32
+ // v2 endpoint with body-format=storage to get HTML content
33
+ const url = new URL(`${base}/wiki/api/v2/pages/${pageId}`);
34
+ url.searchParams.set("body-format", "storage");
35
+ const res = await fetch(url.toString(), {
36
+ method: "GET",
37
+ headers: {
38
+ ...buildAuthHeaders(cfg),
39
+ Accept: "application/json"
40
+ }
41
+ });
42
+ if (!res.ok) {
43
+ const text = await res.text().catch(() => "");
44
+ throw new Error(`Confluence API error ${res.status}: ${text.slice(0, 500)}`);
45
+ }
46
+ return (await res.json());
47
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Convert Confluence storage HTML to plain text
3
+ *
4
+ * This is a lightweight HTML-to-text converter that:
5
+ * - Strips HTML tags
6
+ * - Preserves paragraph and heading breaks
7
+ * - Decodes common HTML entities
8
+ *
9
+ * Note: Not a perfect HTML→Markdown converter; intentionally simple for MCP use.
10
+ *
11
+ * @param storageHtml - Confluence storage format HTML
12
+ * @returns Plain text representation
13
+ */
14
+ export function storageToText(storageHtml) {
15
+ // Minimal, safe-ish conversion:
16
+ // - strip tags
17
+ // - preserve headings/paragraph-ish breaks
18
+ // Not a perfect HTML->MD converter; intentionally lightweight for an MCP tool.
19
+ const withBreaks = storageHtml
20
+ .replace(/<\/(p|h1|h2|h3|h4|li|tr|div)>/gi, "\n")
21
+ .replace(/<br\s*\/?>/gi, "\n");
22
+ const stripped = withBreaks.replace(/<[^>]+>/g, "");
23
+ const decoded = stripped
24
+ .replace(/&nbsp;/g, " ")
25
+ .replace(/&amp;/g, "&")
26
+ .replace(/&lt;/g, "<")
27
+ .replace(/&gt;/g, ">")
28
+ .replace(/&quot;/g, "\"")
29
+ .replace(/&#39;/g, "'");
30
+ return decoded
31
+ .split("\n")
32
+ .map((l) => l.trim())
33
+ .filter(Boolean)
34
+ .join("\n");
35
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Extract Confluence page ID from various URL formats
3
+ *
4
+ * Supported formats:
5
+ * - /wiki/spaces/KEY/pages/123456789/Title
6
+ * - /wiki/pages/viewpage.action?pageId=123456789
7
+ *
8
+ * @param url - Full Confluence page URL
9
+ * @returns Page ID as string
10
+ * @throws Error if URL format is not recognized
11
+ */
12
+ export function extractConfluencePageId(url) {
13
+ // Common Confluence Cloud patterns:
14
+ // 1) /wiki/spaces/KEY/pages/123456789/Title
15
+ // 2) /wiki/pages/viewpage.action?pageId=123456789
16
+ // 3) Some short links redirect, but the final URL usually matches one of the above.
17
+ try {
18
+ const u = new URL(url);
19
+ // Pattern 2: viewpage.action?pageId=...
20
+ const pageId = u.searchParams.get("pageId");
21
+ if (pageId && /^\d+$/.test(pageId))
22
+ return pageId;
23
+ // Pattern 1: .../pages/<id>/...
24
+ const m = u.pathname.match(/\/pages\/(\d+)(\/|$)/);
25
+ if (m?.[1])
26
+ return m[1];
27
+ throw new Error("Unsupported Confluence URL format (no pageId found).");
28
+ }
29
+ catch (e) {
30
+ throw new Error(`Invalid URL: ${e.message}`);
31
+ }
32
+ }
package/dist/index.js ADDED
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ import { z } from "zod";
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { extractConfluencePageId } from "./confluence/url.js";
6
+ import { fetchPageById } from "./confluence/client.js";
7
+ import { storageToText } from "./confluence/transform.js";
8
+ import { generateUnifiedDiff, generateDiffStats } from "./compare/diff.js";
9
+ const server = new McpServer({
10
+ name: "confluence-reader-mcp",
11
+ version: "0.1.0"
12
+ });
13
+ function getEnv(name) {
14
+ const v = process.env[name];
15
+ return v && v.trim().length > 0 ? v.trim() : undefined;
16
+ }
17
+ server.tool("confluence.fetch_doc", "Fetch a Confluence Cloud page by URL using env-scoped credentials, returning clean text for analysis.", {
18
+ url: z.string().describe("Confluence page URL (e.g. /wiki/spaces/.../pages/<id>/...)"),
19
+ includeStorageHtml: z.boolean().optional().describe("If true, also return original storage HTML")
20
+ }, async ({ url, includeStorageHtml }) => {
21
+ const token = getEnv("CONFLUENCE_TOKEN");
22
+ const cloudId = getEnv("CONFLUENCE_CLOUD_ID");
23
+ const baseUrl = getEnv("CONFLUENCE_BASE_URL");
24
+ if (!token)
25
+ throw new Error("Missing CONFLUENCE_TOKEN env var (scoped API token required).");
26
+ const pageId = extractConfluencePageId(url);
27
+ const page = await fetchPageById({ token, cloudId, baseUrl }, pageId);
28
+ const storage = page.body?.storage?.value ?? "";
29
+ const text = storage ? storageToText(storage) : "";
30
+ const payload = {
31
+ pageId: page.id,
32
+ title: page.title,
33
+ status: page.status,
34
+ version: page.version?.number,
35
+ webui: page._links?.webui,
36
+ extractedText: text,
37
+ ...(includeStorageHtml ? { storageHtml: storage } : {})
38
+ };
39
+ return {
40
+ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
41
+ };
42
+ });
43
+ // Helper tool to generate git-style diffs between Confluence and local docs
44
+ server.tool("docs.build_comparison_bundle", "Build a git-style unified diff comparing PRD/System Overview/System Design/LLD against a Confluence page text.", {
45
+ confluenceText: z.string().describe("Text extracted from Confluence (output of confluence.fetch_doc.extractedText)"),
46
+ prd: z.string().optional().describe("Local PRD text"),
47
+ systemOverview: z.string().optional().describe("Local System Overview text"),
48
+ systemDesign: z.string().optional().describe("Local System Design text"),
49
+ lld: z.string().optional().describe("Local LLD text")
50
+ }, async (args) => {
51
+ const diffs = [];
52
+ const sections = [
53
+ ["PRD", args.prd],
54
+ ["System Overview", args.systemOverview],
55
+ ["System Design", args.systemDesign],
56
+ ["LLD", args.lld]
57
+ ].filter(([, v]) => !!v && v.trim().length > 0);
58
+ for (const [name, localText] of sections) {
59
+ const docName = name;
60
+ const diff = generateUnifiedDiff(args.confluenceText.trim(), localText.trim(), `a/confluence`, `b/${docName.toLowerCase().replace(/\s+/g, '-')}`);
61
+ const stats = generateDiffStats(args.confluenceText.trim(), localText.trim());
62
+ diffs.push({ name: docName, diff, stats });
63
+ }
64
+ const summary = {
65
+ totalComparisons: diffs.length,
66
+ diffs: diffs.map(d => ({
67
+ document: d.name,
68
+ additions: d.stats.additions,
69
+ deletions: d.stats.deletions,
70
+ totalChanges: d.stats.changes,
71
+ diff: d.diff
72
+ }))
73
+ };
74
+ return {
75
+ content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
76
+ };
77
+ });
78
+ async function main() {
79
+ const transport = new StdioServerTransport();
80
+ await server.connect(transport);
81
+ }
82
+ main().catch((err) => {
83
+ console.error("MCP server failed:", err);
84
+ process.exit(1);
85
+ });
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@satiyap/confluence-reader-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for fetching and comparing Confluence documentation with local files",
5
+ "author": "satiyap",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "mcp",
9
+ "confluence",
10
+ "documentation",
11
+ "model-context-protocol"
12
+ ],
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/satiyap/confluence-reader-mcp.git"
16
+ },
17
+ "type": "module",
18
+ "main": "dist/index.js",
19
+ "bin": {
20
+ "confluence-reader-mcp": "dist/index.js"
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "README.md",
25
+ "LICENSE"
26
+ ],
27
+ "scripts": {
28
+ "dev": "tsx src/index.ts",
29
+ "build": "tsc",
30
+ "start": "node dist/index.js",
31
+ "prepublishOnly": "npm run build"
32
+ },
33
+ "dependencies": {
34
+ "@modelcontextprotocol/sdk": "^1.0.0",
35
+ "zod": "^3.25.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^22.0.0",
39
+ "tsx": "^4.0.0",
40
+ "typescript": "^5.0.0"
41
+ },
42
+ "engines": {
43
+ "node": ">=18.0.0"
44
+ }
45
+ }