Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -225,4 +225,7 @@ doc/api/.manifest
# Coverage
CoverageReport
coverage.xml
xunit.xml
xunit.xml

# MCP Server configuration with secrets
src/Dapplo.Confluence.McpServer/appsettings.json
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ This is a simple REST based Confluence client, written for Greenshot, by using D
- Coverage Status: [![Coverage Status](https://coveralls.io/repos/github/dapplo/Dapplo.Confluence/badge.svg?branch=master)](https://coveralls.io/github/dapplo/Dapplo.Confluence?branch=master)
- NuGet package: [![NuGet package](https://badge.fury.io/nu/Dapplo.Confluence.svg)](https://badge.fury.io/nu/Dapplo.Confluence)

## 🆕 MCP Server for AI Integration

**NEW**: Integrate Confluence with AI assistants like Claude Desktop and Microsoft 365 Copilot!

The [Dapplo.Confluence.McpServer](src/Dapplo.Confluence.McpServer) provides a Model Context Protocol (MCP) server that exposes Confluence functionality to AI assistants.

**Quick start:**
```bash
cd src/Dapplo.Confluence.McpServer
cp appsettings.example.json appsettings.json
# Edit appsettings.json with your Confluence URL and API token
dotnet run
```

See the [Quick Start Guide](src/Dapplo.Confluence.McpServer/QUICKSTART.md) and [full documentation](src/Dapplo.Confluence.McpServer/README.md) for details.

## Confluence Client Library

The Confluence client supports most REST methods, and has a fluent API for building a CQL (Confluence Query Language) string to search with.

An example on how to use this Confluence client:
Expand Down
32 changes: 32 additions & 0 deletions src/Dapplo.Confluence.McpServer/ConfluenceSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace Dapplo.Confluence.McpServer;

/// <summary>
/// Configuration settings for the Confluence MCP server
/// </summary>
public class ConfluenceSettings
{
/// <summary>
/// The Confluence server URL (e.g., https://yourcompany.atlassian.net)
/// </summary>
public string ConfluenceUrl { get; set; } = string.Empty;

/// <summary>
/// Authentication method: "basic" or "bearer"
/// </summary>
public string AuthType { get; set; } = "bearer";

/// <summary>
/// Username for basic authentication
/// </summary>
public string? Username { get; set; }

/// <summary>
/// Password or API token for basic authentication
/// </summary>
public string? Password { get; set; }

/// <summary>
/// Bearer token for token-based authentication
/// </summary>
public string? BearerToken { get; set; }
}
31 changes: 31 additions & 0 deletions src/Dapplo.Confluence.McpServer/Dapplo.Confluence.McpServer.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Description>MCP (Model Context Protocol) server for Confluence API integration</Description>
<PackageTags>mcp;confluence;atlassian;dapplo;copilot</PackageTags>
<IsPackable>false</IsPackable>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Dapplo.Confluence\Dapplo.Confluence.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.0" />
</ItemGroup>

<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
114 changes: 114 additions & 0 deletions src/Dapplo.Confluence.McpServer/McpServerHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using Dapplo.Confluence.McpServer.Models;
using Dapplo.Confluence.McpServer.Tools;
using System.Text.Json;
using System.Text.Json.Nodes;

namespace Dapplo.Confluence.McpServer;

/// <summary>
/// MCP server handler that processes JSON-RPC requests
/// </summary>
public class McpServerHandler
{
private readonly ToolRegistry _toolRegistry;
private const string McpVersion = "2024-11-05";

public McpServerHandler(IConfluenceClient confluenceClient)
{
_toolRegistry = new ToolRegistry(confluenceClient);
}

/// <summary>
/// Process an MCP request and return a response
/// </summary>
public async Task<McpResponse> ProcessRequestAsync(McpRequest request)
{
try
{
var result = request.Method switch
{
"initialize" => HandleInitialize(request.Params),
"tools/list" => HandleToolsList(),
"tools/call" => await HandleToolsCallAsync(request.Params),
"ping" => new { },
_ => throw new InvalidOperationException($"Unknown method: {request.Method}")
};

return new McpResponse
{
Id = request.Id,
Result = result
};
}
catch (Exception ex)
{
return new McpResponse
{
Id = request.Id,
Error = new McpError
{
Code = -32603,
Message = ex.Message,
Data = new { exception = ex.GetType().Name }
}
};
}
}

private object HandleInitialize(object? parameters)
{
return new
{
protocolVersion = McpVersion,
capabilities = new
{
tools = new { }
},
serverInfo = new
{
name = "confluence-mcp-server",
version = "1.0.0"
}
};
}

private object HandleToolsList()
{
var tools = _toolRegistry.GetToolDefinitions();
return new { tools };
}

private async Task<object> HandleToolsCallAsync(object? parameters)
{
if (parameters == null)
throw new ArgumentException("Parameters required for tools/call");

var jsonElement = JsonSerializer.SerializeToElement(parameters);

var toolName = jsonElement.GetProperty("name").GetString()
?? throw new ArgumentException("Tool name is required");

JsonElement? toolParams = null;
if (jsonElement.TryGetProperty("arguments", out var argsElement))
{
toolParams = argsElement;
}

var result = await _toolRegistry.ExecuteToolAsync(toolName, toolParams);

return new
{
content = new[]
{
new
{
type = "text",
text = JsonSerializer.Serialize(result, new JsonSerializerOptions
{
WriteIndented = true
})
}
}
};
}
}
21 changes: 21 additions & 0 deletions src/Dapplo.Confluence.McpServer/Models/McpRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Text.Json.Serialization;

namespace Dapplo.Confluence.McpServer.Models;

/// <summary>
/// Represents a JSON-RPC 2.0 request for MCP
/// </summary>
public class McpRequest
{
[JsonPropertyName("jsonrpc")]
public string JsonRpc { get; set; } = "2.0";

[JsonPropertyName("id")]
public object? Id { get; set; }

[JsonPropertyName("method")]
public string Method { get; set; } = string.Empty;

[JsonPropertyName("params")]
public object? Params { get; set; }
}
36 changes: 36 additions & 0 deletions src/Dapplo.Confluence.McpServer/Models/McpResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Text.Json.Serialization;

namespace Dapplo.Confluence.McpServer.Models;

/// <summary>
/// Represents a JSON-RPC 2.0 response for MCP
/// </summary>
public class McpResponse
{
[JsonPropertyName("jsonrpc")]
public string JsonRpc { get; set; } = "2.0";

[JsonPropertyName("id")]
public object? Id { get; set; }

[JsonPropertyName("result")]
public object? Result { get; set; }

[JsonPropertyName("error")]
public McpError? Error { get; set; }
}

/// <summary>
/// Represents a JSON-RPC 2.0 error
/// </summary>
public class McpError
{
[JsonPropertyName("code")]
public int Code { get; set; }

[JsonPropertyName("message")]
public string Message { get; set; } = string.Empty;

[JsonPropertyName("data")]
public object? Data { get; set; }
}
Loading