Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:49:50 +08:00
commit adc4b2be25
147 changed files with 24716 additions and 0 deletions

View File

@@ -0,0 +1,153 @@
# MXCP Confluence Plugin Example
This example demonstrates how to use MXCP with Confluence data. It shows how to:
- Create and use a custom MXCP plugin for Confluence integration
- Query Confluence content using SQL
- Combine Confluence data with other data sources
## Overview
The plugin provides several UDFs that allow you to:
- Search pages using keywords and CQL queries
- Fetch page content and metadata
- List child pages and spaces
- Navigate the Confluence content hierarchy
## Configuration
### 1. Creating an Atlassian API Token
**Important:** This plugin currently only supports API tokens **without scopes**. While Atlassian has introduced scoped API tokens, there are known compatibility issues when using scoped tokens with basic authentication that this plugin relies on.
To create an API token without scopes:
1. **Log in to your Atlassian account** at [https://id.atlassian.com/manage-profile/security/api-tokens](https://id.atlassian.com/manage-profile/security/api-tokens)
2. **Verify your identity** (if prompted):
- Atlassian may ask you to verify your identity before creating API tokens
- Check your email for a one-time passcode and enter it when prompted
3. **Create the API token**:
- Click **"Create API token"** (not "Create API token with scopes")
- Enter a descriptive name for your token (e.g., "MXCP Confluence Integration")
- Select an expiration date (tokens can last from 1 day to 1 year)
- Click **"Create"**
4. **Copy and save your token**:
- Click **"Copy to clipboard"** to copy the token
- **Important:** Save this token securely (like in a password manager) as you won't be able to view it again
- This token will be used as your "password" in the configuration below
### 2. User Configuration
Add the following to your MXCP user config (`~/.mxcp/config.yml`). You can use the example `config.yml` in this directory as a template:
```yaml
mxcp: 1
projects:
confluence-demo:
profiles:
dev:
plugin:
config:
confluence:
url: "https://your-domain.atlassian.net/wiki"
username: "your-email@example.com"
password: "your-api-token" # Use the API token you created above
```
**Configuration Notes:**
- Replace `your-domain` with your actual Atlassian domain
- Replace `your-email@example.com` with the email address of your Atlassian account
- Replace `your-api-token` with the API token you created in step 1
- The `password` field should contain your API token, not your actual Atlassian password
### 2. Site Configuration
Create an `mxcp-site.yml` file:
```yaml
mxcp: 1
project: confluence-demo
profile: dev
plugin:
- name: confluence
module: mxcp_plugin_confluence
config: confluence
```
## Available Tools
### Search Pages
```sql
-- Search for pages containing specific text
SELECT search_pages_confluence($query, $limit) as result;
```
### Get Page
```sql
-- Fetch a page's content
SELECT get_page_confluence($page_id) as result;
```
### Get Children
```sql
-- List direct children of a page
SELECT get_children_confluence($page_id) as result;
```
### List Spaces
```sql
-- List all accessible spaces
SELECT list_spaces_confluence() as result;
```
### Describe Page
```sql
-- Show metadata about a page
SELECT describe_page_confluence($page_id) as result;
```
## Example Queries
1. Search and analyze page content:
```sql
WITH pages AS (
SELECT * FROM search_pages_confluence('important documentation', 50)
)
SELECT
p.title as page_title,
p.space.name as space_name,
p.version.number as version,
p.metadata.created as created_date
FROM pages p
ORDER BY p.metadata.created DESC;
```
## Plugin Development
The `mxcp_plugin_confluence` directory contains a complete MXCP plugin implementation that you can use as a reference for creating your own plugins. It demonstrates:
- Plugin class structure
- Type conversion
- UDF implementation
- Configuration handling
## Running the Example
1. Set the `MXCP_CONFIG` environment variable to point to your config file:
```bash
export MXCP_CONFIG=/path/to/examples/confluence/config.yml
```
2. Start the MXCP server:
```bash
mxcp serve
```
## Notes
- Make sure to keep your API token secure and never commit it to version control.
- The plugin requires proper authentication and API permissions to work with your Confluence instance.
- All functions return JSON strings containing the requested data.

View File

@@ -0,0 +1,12 @@
mxcp: 1
projects:
confluence-demo:
profiles:
dev:
plugin:
config:
confluence:
url: "https://your-domain.atlassian.net/wiki"
username: "your-email@example.com"
password: "your-api-token"

View File

@@ -0,0 +1,7 @@
mxcp: 1
project: confluence-demo
profile: dev
plugin:
- name: confluence
module: mxcp_plugin_confluence
config: confluence

View File

@@ -0,0 +1,172 @@
"""
Confluence Plugin Implementation
This module provides UDFs for interacting with Atlassian Confluence.
"""
import json
import logging
from typing import Any, Dict, List, Optional
from atlassian import Confluence
from mxcp.plugins import MXCPBasePlugin, udf
logger = logging.getLogger(__name__)
class MXCPPlugin(MXCPBasePlugin):
"""Confluence plugin that provides content query functionality."""
def __init__(self, config: Dict[str, Any]):
"""Initialize the Confluence plugin.
Args:
config: Plugin configuration containing Confluence API credentials
Required keys:
- url: The base URL of your Confluence instance
- username: Your Atlassian username/email
- password: Your Atlassian API token
"""
super().__init__(config)
self.url = config.get("url", "")
self.username = config.get("username", "")
self.password = config.get("password", "")
if not all([self.url, self.username, self.password]):
raise ValueError(
"Confluence plugin requires url, username, and password in configuration"
)
# Initialize Confluence client
self.confluence = Confluence(
url=self.url, username=self.username, password=self.password, cloud=True
)
@udf
def cql_query(
self, query: str, space_key: Optional[str] = None, max_results: Optional[int] = 50
) -> str:
"""Execute a CQL query against Confluence.
Args:
query: The CQL query string
space_key: Optional space key to limit the search
max_results: Maximum number of results to return (default: 50)
Returns:
JSON string containing matching pages
"""
logger.info(
"Executing CQL query: %s in space=%s with max_results=%s", query, space_key, max_results
)
# Build the CQL query
cql = query
if space_key:
cql = f'space = "{space_key}" AND {cql}'
# Execute the CQL query
results = self.confluence.cql(cql=cql, limit=max_results, expand="version,metadata.labels")
# Transform the response to match our schema
transformed_results = [
{
"id": page["content"]["id"],
"title": page["content"]["title"],
"space_key": page["content"]["space"]["key"],
"url": f"{self.url}/wiki/spaces/{page['content']['space']['key']}/pages/{page['content']['id']}",
"version": {
"number": page["content"]["version"]["number"],
"when": page["content"]["version"]["when"],
},
"last_modified": page["content"]["version"]["when"],
"author": page["content"]["version"]["by"]["email"],
"labels": [
label["name"] for label in page["content"]["metadata"]["labels"]["results"]
],
}
for page in results["results"]
]
return json.dumps(transformed_results)
@udf
def search_pages(self, query: str, limit: Optional[int] = 10) -> str:
"""Search pages by keyword.
Args:
query: Search string, e.g., 'onboarding guide'
limit: Maximum number of results to return (default: 10)
Returns:
JSON string containing matching pages
"""
logger.info("Searching pages with query: %s, limit: %s", query, limit)
results = self.confluence.cql(cql=f'text ~ "{query}"', limit=limit, expand="version,space")
return json.dumps(results)
@udf
def get_page(self, page_id: str) -> str:
"""Fetch page content (storage format or rendered HTML).
Args:
page_id: Confluence page ID
Returns:
JSON string containing page content
"""
logger.info("Getting page content for ID: %s", page_id)
page = self.confluence.get_page_by_id(page_id=page_id, expand="body.storage,body.view")
return json.dumps(page)
@udf
def get_children(self, page_id: str) -> str:
"""List direct children of a page.
Args:
page_id: Confluence page ID
Returns:
JSON string containing child pages
"""
logger.info("Getting children for page ID: %s", page_id)
children = self.confluence.get_child_pages(page_id=page_id, expand="version,space")
return json.dumps(children)
@udf
def list_spaces(self) -> str:
"""Return all accessible spaces (by key and name).
Returns:
JSON string containing list of spaces
"""
logger.info("Listing all spaces")
spaces = self.confluence.get_all_spaces(expand="description,metadata.labels")
return json.dumps(spaces)
@udf
def describe_page(self, page_id: str) -> str:
"""Show metadata about a page (title, author, updated, labels, etc).
Args:
page_id: Confluence page ID
Returns:
JSON string containing page metadata
"""
logger.info("Getting metadata for page ID: %s", page_id)
page = self.confluence.get_page_by_id(
page_id=page_id, expand="version,space,metadata.labels"
)
return json.dumps(page)

View File

@@ -0,0 +1,2 @@
-- Example CQL query endpoint
SELECT cql_query_confluence($cql, $space_key, $limit) as result;

View File

@@ -0,0 +1,2 @@
-- Show metadata about a Confluence page
SELECT describe_page_confluence($page_id) as result;

View File

@@ -0,0 +1,2 @@
-- List direct children of a Confluence page
SELECT get_children_confluence($page_id) as result;

View File

@@ -0,0 +1,2 @@
-- Get Confluence page content
SELECT get_page_confluence($page_id) as result;

View File

@@ -0,0 +1,2 @@
-- List all accessible Confluence spaces
SELECT list_spaces_confluence() as result;

View File

@@ -0,0 +1,2 @@
-- Search Confluence pages by keyword
SELECT search_pages_confluence($query, $limit) as result;

View File

@@ -0,0 +1,66 @@
mxcp: 1
tool:
name: cql_query
description: "Execute a CQL query against Confluence"
parameters:
- name: cql
type: string
description: |
The CQL query string to execute.
Example: 'text ~ "important documentation"'
examples: [
'text ~ "important documentation"',
'type = page AND space = "TEAM"',
'label = "documentation"'
]
- name: space_key
type: string
description: |
The space key to search in.
Example: 'TEAM'
examples: ["TEAM", "DOCS", "PROD"]
- name: limit
type: integer
description: |
Maximum number of results to return.
Defaults to 10 if not specified.
examples: [10, 20, 50]
return:
type: array
items:
type: object
properties:
id:
type: string
description: "Page ID"
title:
type: string
description: "Page title"
space_key:
type: string
description: "Space key"
url:
type: string
description: "Page URL"
version:
type: object
properties:
number:
type: integer
description: "Version number"
when:
type: string
description: "Version timestamp"
last_modified:
type: string
description: "Last modification timestamp"
author:
type: string
description: "Page author"
labels:
type: array
items:
type: string
description: "Page labels"
source:
file: "../sql/cql_query.sql"

View File

@@ -0,0 +1,28 @@
mxcp: 1
tool:
name: describe_page
description: |
Show metadata about a Confluence page.
Returns a JSON string containing page details like title, author, update date, and labels.
type: tool
annotations:
title: Describe Page
readOnlyHint: true
destructiveHint: false
idempotentHint: true
openWorldHint: true
parameters:
- name: page_id
type: string
description: |
The ID of the page to describe.
This is typically a numeric ID found in the page URL.
examples: ["123456", "789012"]
return:
type: string
description: |
A JSON string containing the page metadata.
language: "sql"
source:
file: "../sql/describe_page.sql"

View File

@@ -0,0 +1,28 @@
mxcp: 1
tool:
name: get_children
description: |
List direct children of a Confluence page.
Returns a JSON string containing the child pages.
type: tool
annotations:
title: Get Children
readOnlyHint: true
destructiveHint: false
idempotentHint: true
openWorldHint: true
parameters:
- name: page_id
type: string
description: |
The ID of the parent page.
This is typically a numeric ID found in the page URL.
examples: ["123456", "789012"]
return:
type: string
description: |
A JSON string containing an array of child pages.
language: "sql"
source:
file: "../sql/get_children.sql"

View File

@@ -0,0 +1,28 @@
mxcp: 1
tool:
name: get_page
description: |
Fetch a Confluence page's content.
Returns a JSON string containing the page content in both storage format and rendered HTML.
type: tool
annotations:
title: Get Page
readOnlyHint: true
destructiveHint: false
idempotentHint: true
openWorldHint: true
parameters:
- name: page_id
type: string
description: |
The ID of the page to fetch.
This is typically a numeric ID found in the page URL.
examples: ["123456", "789012"]
return:
type: string
description: |
A JSON string containing the page content.
language: "sql"
source:
file: "../sql/get_page.sql"

View File

@@ -0,0 +1,21 @@
mxcp: 1
tool:
name: list_spaces
description: |
List all accessible Confluence spaces.
Returns a JSON string containing space keys and names.
type: tool
annotations:
title: List Spaces
readOnlyHint: true
destructiveHint: false
idempotentHint: true
openWorldHint: true
return:
type: string
description: |
A JSON string containing an array of spaces.
language: "sql"
source:
file: "../sql/list_spaces.sql"

View File

@@ -0,0 +1,38 @@
mxcp: 1
tool:
name: search_pages
description: |
Search Confluence pages by keyword.
Returns a JSON string containing matching pages with their details.
type: tool
annotations:
title: Search Pages
readOnlyHint: true
destructiveHint: false
idempotentHint: true
openWorldHint: true
parameters:
- name: query
type: string
description: |
The search string to find in page content.
This will search through page titles and content.
examples: [
"onboarding guide",
"release notes",
"API documentation"
]
- name: limit
type: integer
description: |
Maximum number of results to return.
Defaults to 10 if not specified.
examples: [10, 20, 50]
return:
type: string
description: |
A JSON string containing an array of matching pages.
language: "sql"
source:
file: "../sql/search_pages.sql"