Merge pull request #4 from barryw/fix/tool-names-replace-dots-with-underscores
fix: replace dots with underscores in tool names
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using FluentAssertions;
|
||||
using ModelContextProtocol.Server;
|
||||
using Xunit;
|
||||
|
||||
namespace PaperlessMCP.Tests.Tools;
|
||||
|
||||
public class ToolNamingTests
|
||||
{
|
||||
private static readonly Regex AnthropicToolNamePattern = new("^[a-zA-Z0-9_-]{1,64}$");
|
||||
|
||||
[Fact]
|
||||
public void AllToolNames_ShouldMatchAnthropicApiNamingRules()
|
||||
{
|
||||
var toolAssembly = typeof(PaperlessMCP.Tools.HealthTools).Assembly;
|
||||
|
||||
var toolNames = toolAssembly.GetTypes()
|
||||
.SelectMany(t => t.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance))
|
||||
.SelectMany(m => m.GetCustomAttributes<McpServerToolAttribute>())
|
||||
.Where(a => a.Name is not null)
|
||||
.Select(a => a.Name!)
|
||||
.ToList();
|
||||
|
||||
toolNames.Should().NotBeEmpty("expected to find at least one McpServerTool attribute");
|
||||
|
||||
var violations = toolNames
|
||||
.Where(name => !AnthropicToolNamePattern.IsMatch(name))
|
||||
.ToList();
|
||||
|
||||
violations.Should().BeEmpty(
|
||||
"tool names must match ^[a-zA-Z0-9_-]{{1,64}}$ per Anthropic API rules, " +
|
||||
$"but found: {string.Join(", ", violations)}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllToolNames_ShouldNotContainDots()
|
||||
{
|
||||
var toolAssembly = typeof(PaperlessMCP.Tools.HealthTools).Assembly;
|
||||
|
||||
var toolNames = toolAssembly.GetTypes()
|
||||
.SelectMany(t => t.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance))
|
||||
.SelectMany(m => m.GetCustomAttributes<McpServerToolAttribute>())
|
||||
.Where(a => a.Name is not null)
|
||||
.Select(a => a.Name!)
|
||||
.ToList();
|
||||
|
||||
var dotNames = toolNames.Where(name => name.Contains('.')).ToList();
|
||||
|
||||
dotNames.Should().BeEmpty(
|
||||
$"dots are not allowed in tool names, but found: {string.Join(", ", dotNames)}");
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace PaperlessMCP.Tools;
|
||||
[McpServerToolType]
|
||||
public static class CorrespondentTools
|
||||
{
|
||||
[McpServerTool(Name = "paperless.correspondents.list")]
|
||||
[McpServerTool(Name = "paperless_correspondents_list")]
|
||||
[Description("List all correspondents with pagination.")]
|
||||
public static async Task<string> List(
|
||||
PaperlessClient client,
|
||||
@@ -38,7 +38,7 @@ public static class CorrespondentTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.correspondents.get")]
|
||||
[McpServerTool(Name = "paperless_correspondents_get")]
|
||||
[Description("Get a correspondent by its ID.")]
|
||||
public static async Task<string> Get(
|
||||
PaperlessClient client,
|
||||
@@ -63,7 +63,7 @@ public static class CorrespondentTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.correspondents.create")]
|
||||
[McpServerTool(Name = "paperless_correspondents_create")]
|
||||
[Description("Create a new correspondent.")]
|
||||
public static async Task<string> Create(
|
||||
PaperlessClient client,
|
||||
@@ -97,7 +97,7 @@ public static class CorrespondentTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.correspondents.update")]
|
||||
[McpServerTool(Name = "paperless_correspondents_update")]
|
||||
[Description("Update an existing correspondent.")]
|
||||
public static async Task<string> Update(
|
||||
PaperlessClient client,
|
||||
@@ -132,7 +132,7 @@ public static class CorrespondentTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.correspondents.delete")]
|
||||
[McpServerTool(Name = "paperless_correspondents_delete")]
|
||||
[Description("Delete a correspondent. Requires explicit confirmation.")]
|
||||
public static async Task<string> Delete(
|
||||
PaperlessClient client,
|
||||
@@ -181,7 +181,7 @@ public static class CorrespondentTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.correspondents.bulk_delete")]
|
||||
[McpServerTool(Name = "paperless_correspondents_bulk_delete")]
|
||||
[Description("Delete multiple correspondents. Supports dry run mode.")]
|
||||
public static async Task<string> BulkDelete(
|
||||
PaperlessClient client,
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace PaperlessMCP.Tools;
|
||||
[McpServerToolType]
|
||||
public static class CustomFieldTools
|
||||
{
|
||||
[McpServerTool(Name = "paperless.custom_fields.list")]
|
||||
[McpServerTool(Name = "paperless_custom_fields_list")]
|
||||
[Description("List all custom field definitions with pagination.")]
|
||||
public static async Task<string> List(
|
||||
PaperlessClient client,
|
||||
@@ -37,7 +37,7 @@ public static class CustomFieldTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.custom_fields.get")]
|
||||
[McpServerTool(Name = "paperless_custom_fields_get")]
|
||||
[Description("Get a custom field definition by its ID.")]
|
||||
public static async Task<string> Get(
|
||||
PaperlessClient client,
|
||||
@@ -62,7 +62,7 @@ public static class CustomFieldTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.custom_fields.create")]
|
||||
[McpServerTool(Name = "paperless_custom_fields_create")]
|
||||
[Description("Create a new custom field definition.")]
|
||||
public static async Task<string> Create(
|
||||
PaperlessClient client,
|
||||
@@ -114,7 +114,7 @@ public static class CustomFieldTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.custom_fields.update")]
|
||||
[McpServerTool(Name = "paperless_custom_fields_update")]
|
||||
[Description("Update an existing custom field definition.")]
|
||||
public static async Task<string> Update(
|
||||
PaperlessClient client,
|
||||
@@ -165,7 +165,7 @@ public static class CustomFieldTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.custom_fields.delete")]
|
||||
[McpServerTool(Name = "paperless_custom_fields_delete")]
|
||||
[Description("Delete a custom field definition. Requires explicit confirmation.")]
|
||||
public static async Task<string> Delete(
|
||||
PaperlessClient client,
|
||||
@@ -214,7 +214,7 @@ public static class CustomFieldTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.custom_fields.assign")]
|
||||
[McpServerTool(Name = "paperless_custom_fields_assign")]
|
||||
[Description("Assign a custom field value to a document.")]
|
||||
public static async Task<string> Assign(
|
||||
PaperlessClient client,
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace PaperlessMCP.Tools;
|
||||
[McpServerToolType]
|
||||
public static class DocumentTools
|
||||
{
|
||||
[McpServerTool(Name = "paperless.documents.search")]
|
||||
[McpServerTool(Name = "paperless_documents_search")]
|
||||
[Description("Search for documents with full-text search and filters. Supports pagination.")]
|
||||
public static async Task<string> Search(
|
||||
PaperlessClient client,
|
||||
@@ -82,7 +82,7 @@ public static class DocumentTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.documents.get")]
|
||||
[McpServerTool(Name = "paperless_documents_get")]
|
||||
[Description("Get a document by its ID.")]
|
||||
public static async Task<string> Get(
|
||||
PaperlessClient client,
|
||||
@@ -107,7 +107,7 @@ public static class DocumentTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.documents.download")]
|
||||
[McpServerTool(Name = "paperless_documents_download")]
|
||||
[Description("Get download URLs for a document's original file, preview, and thumbnail.")]
|
||||
public static async Task<string> Download(
|
||||
PaperlessClient client,
|
||||
@@ -134,7 +134,7 @@ public static class DocumentTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.documents.preview")]
|
||||
[McpServerTool(Name = "paperless_documents_preview")]
|
||||
[Description("Get the preview URL for a document.")]
|
||||
public static async Task<string> Preview(
|
||||
PaperlessClient client,
|
||||
@@ -161,7 +161,7 @@ public static class DocumentTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.documents.thumbnail")]
|
||||
[McpServerTool(Name = "paperless_documents_thumbnail")]
|
||||
[Description("Get the thumbnail URL for a document.")]
|
||||
public static async Task<string> Thumbnail(
|
||||
PaperlessClient client,
|
||||
@@ -188,7 +188,7 @@ public static class DocumentTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.documents.upload")]
|
||||
[McpServerTool(Name = "paperless_documents_upload")]
|
||||
[Description("Upload a new document to Paperless-ngx. Provide file content as base64. For large files, use paperless.documents.upload_from_path instead.")]
|
||||
public static async Task<string> Upload(
|
||||
PaperlessClient client,
|
||||
@@ -247,7 +247,7 @@ public static class DocumentTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.documents.upload_from_path")]
|
||||
[McpServerTool(Name = "paperless_documents_upload_from_path")]
|
||||
[Description("Upload a document from a local file path. More reliable than base64 upload for large files. Includes automatic retries.")]
|
||||
public static async Task<string> UploadFromPath(
|
||||
PaperlessClient client,
|
||||
@@ -326,7 +326,7 @@ public static class DocumentTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.documents.update")]
|
||||
[McpServerTool(Name = "paperless_documents_update")]
|
||||
[Description("Update document metadata (title, correspondent, type, tags, etc.).")]
|
||||
public static async Task<string> Update(
|
||||
PaperlessClient client,
|
||||
@@ -371,7 +371,7 @@ public static class DocumentTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.documents.delete")]
|
||||
[McpServerTool(Name = "paperless_documents_delete")]
|
||||
[Description("Delete a document. Requires explicit confirmation.")]
|
||||
public static async Task<string> Delete(
|
||||
PaperlessClient client,
|
||||
@@ -427,7 +427,7 @@ public static class DocumentTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.documents.bulk_update")]
|
||||
[McpServerTool(Name = "paperless_documents_bulk_update")]
|
||||
[Description("Perform bulk operations on multiple documents. Supports dry run mode.")]
|
||||
public static async Task<string> BulkUpdate(
|
||||
PaperlessClient client,
|
||||
@@ -513,7 +513,7 @@ public static class DocumentTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.documents.reprocess")]
|
||||
[McpServerTool(Name = "paperless_documents_reprocess")]
|
||||
[Description("Reprocess a document's OCR and content extraction.")]
|
||||
public static async Task<string> Reprocess(
|
||||
PaperlessClient client,
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace PaperlessMCP.Tools;
|
||||
[McpServerToolType]
|
||||
public static class DocumentTypeTools
|
||||
{
|
||||
[McpServerTool(Name = "paperless.document_types.list")]
|
||||
[McpServerTool(Name = "paperless_document_types_list")]
|
||||
[Description("List all document types with pagination.")]
|
||||
public static async Task<string> List(
|
||||
PaperlessClient client,
|
||||
@@ -38,7 +38,7 @@ public static class DocumentTypeTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.document_types.get")]
|
||||
[McpServerTool(Name = "paperless_document_types_get")]
|
||||
[Description("Get a document type by its ID.")]
|
||||
public static async Task<string> Get(
|
||||
PaperlessClient client,
|
||||
@@ -63,7 +63,7 @@ public static class DocumentTypeTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.document_types.create")]
|
||||
[McpServerTool(Name = "paperless_document_types_create")]
|
||||
[Description("Create a new document type.")]
|
||||
public static async Task<string> Create(
|
||||
PaperlessClient client,
|
||||
@@ -97,7 +97,7 @@ public static class DocumentTypeTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.document_types.update")]
|
||||
[McpServerTool(Name = "paperless_document_types_update")]
|
||||
[Description("Update an existing document type.")]
|
||||
public static async Task<string> Update(
|
||||
PaperlessClient client,
|
||||
@@ -132,7 +132,7 @@ public static class DocumentTypeTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.document_types.delete")]
|
||||
[McpServerTool(Name = "paperless_document_types_delete")]
|
||||
[Description("Delete a document type. Requires explicit confirmation.")]
|
||||
public static async Task<string> Delete(
|
||||
PaperlessClient client,
|
||||
@@ -181,7 +181,7 @@ public static class DocumentTypeTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.document_types.bulk_delete")]
|
||||
[McpServerTool(Name = "paperless_document_types_bulk_delete")]
|
||||
[Description("Delete multiple document types. Supports dry run mode.")]
|
||||
public static async Task<string> BulkDelete(
|
||||
PaperlessClient client,
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace PaperlessMCP.Tools;
|
||||
[McpServerToolType]
|
||||
public static class HealthTools
|
||||
{
|
||||
[McpServerTool(Name = "paperless.ping")]
|
||||
[McpServerTool(Name = "paperless_ping")]
|
||||
[Description("Verify connectivity and authentication with the Paperless-ngx instance. Returns server version if available.")]
|
||||
public static async Task<string> Ping(PaperlessClient client)
|
||||
{
|
||||
@@ -35,7 +35,7 @@ public static class HealthTools
|
||||
return JsonSerializer.Serialize(errorResponse);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.capabilities")]
|
||||
[McpServerTool(Name = "paperless_capabilities")]
|
||||
[Description("Return supported API endpoints and detected Paperless-ngx version information.")]
|
||||
public static async Task<string> GetCapabilities(PaperlessClient client)
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace PaperlessMCP.Tools;
|
||||
[McpServerToolType]
|
||||
public static class StoragePathTools
|
||||
{
|
||||
[McpServerTool(Name = "paperless.storage_paths.list")]
|
||||
[McpServerTool(Name = "paperless_storage_paths_list")]
|
||||
[Description("List all storage paths with pagination.")]
|
||||
public static async Task<string> List(
|
||||
PaperlessClient client,
|
||||
@@ -38,7 +38,7 @@ public static class StoragePathTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.storage_paths.get")]
|
||||
[McpServerTool(Name = "paperless_storage_paths_get")]
|
||||
[Description("Get a storage path by its ID.")]
|
||||
public static async Task<string> Get(
|
||||
PaperlessClient client,
|
||||
@@ -63,7 +63,7 @@ public static class StoragePathTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.storage_paths.create")]
|
||||
[McpServerTool(Name = "paperless_storage_paths_create")]
|
||||
[Description("Create a new storage path.")]
|
||||
public static async Task<string> Create(
|
||||
PaperlessClient client,
|
||||
@@ -99,7 +99,7 @@ public static class StoragePathTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.storage_paths.update")]
|
||||
[McpServerTool(Name = "paperless_storage_paths_update")]
|
||||
[Description("Update an existing storage path.")]
|
||||
public static async Task<string> Update(
|
||||
PaperlessClient client,
|
||||
@@ -136,7 +136,7 @@ public static class StoragePathTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.storage_paths.delete")]
|
||||
[McpServerTool(Name = "paperless_storage_paths_delete")]
|
||||
[Description("Delete a storage path. Requires explicit confirmation.")]
|
||||
public static async Task<string> Delete(
|
||||
PaperlessClient client,
|
||||
@@ -185,7 +185,7 @@ public static class StoragePathTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.storage_paths.bulk_delete")]
|
||||
[McpServerTool(Name = "paperless_storage_paths_bulk_delete")]
|
||||
[Description("Delete multiple storage paths. Supports dry run mode.")]
|
||||
public static async Task<string> BulkDelete(
|
||||
PaperlessClient client,
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace PaperlessMCP.Tools;
|
||||
[McpServerToolType]
|
||||
public static class TagTools
|
||||
{
|
||||
[McpServerTool(Name = "paperless.tags.list")]
|
||||
[McpServerTool(Name = "paperless_tags_list")]
|
||||
[Description("List all tags with pagination.")]
|
||||
public static async Task<string> List(
|
||||
PaperlessClient client,
|
||||
@@ -38,7 +38,7 @@ public static class TagTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.tags.get")]
|
||||
[McpServerTool(Name = "paperless_tags_get")]
|
||||
[Description("Get a tag by its ID.")]
|
||||
public static async Task<string> Get(
|
||||
PaperlessClient client,
|
||||
@@ -63,7 +63,7 @@ public static class TagTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.tags.create")]
|
||||
[McpServerTool(Name = "paperless_tags_create")]
|
||||
[Description("Create a new tag.")]
|
||||
public static async Task<string> Create(
|
||||
PaperlessClient client,
|
||||
@@ -103,7 +103,7 @@ public static class TagTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.tags.update")]
|
||||
[McpServerTool(Name = "paperless_tags_update")]
|
||||
[Description("Update an existing tag.")]
|
||||
public static async Task<string> Update(
|
||||
PaperlessClient client,
|
||||
@@ -142,7 +142,7 @@ public static class TagTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.tags.delete")]
|
||||
[McpServerTool(Name = "paperless_tags_delete")]
|
||||
[Description("Delete a tag. Requires explicit confirmation.")]
|
||||
public static async Task<string> Delete(
|
||||
PaperlessClient client,
|
||||
@@ -191,7 +191,7 @@ public static class TagTools
|
||||
return JsonSerializer.Serialize(response);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "paperless.tags.bulk_delete")]
|
||||
[McpServerTool(Name = "paperless_tags_bulk_delete")]
|
||||
[Description("Delete multiple tags. Supports dry run mode.")]
|
||||
public static async Task<string> BulkDelete(
|
||||
PaperlessClient client,
|
||||
|
||||
Reference in New Issue
Block a user