Initial commit
This commit is contained in:
153
skills/mxcp-expert/assets/project-templates/confluence/README.md
Normal file
153
skills/mxcp-expert/assets/project-templates/confluence/README.md
Normal 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.
|
||||
@@ -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"
|
||||
@@ -0,0 +1,7 @@
|
||||
mxcp: 1
|
||||
project: confluence-demo
|
||||
profile: dev
|
||||
plugin:
|
||||
- name: confluence
|
||||
module: mxcp_plugin_confluence
|
||||
config: confluence
|
||||
@@ -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)
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Example CQL query endpoint
|
||||
SELECT cql_query_confluence($cql, $space_key, $limit) as result;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Show metadata about a Confluence page
|
||||
SELECT describe_page_confluence($page_id) as result;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- List direct children of a Confluence page
|
||||
SELECT get_children_confluence($page_id) as result;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Get Confluence page content
|
||||
SELECT get_page_confluence($page_id) as result;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- List all accessible Confluence spaces
|
||||
SELECT list_spaces_confluence() as result;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Search Confluence pages by keyword
|
||||
SELECT search_pages_confluence($query, $limit) as result;
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user