关于 Vikunja MCP Server
Vikunja MCP Server 是面向 Vikunja 任务管理实例的 MCP 服务,让 AI 助手能够读取和操作任务、项目、标签、团队、用户及 Webhook。项目采用 TypeScript 编写,并使用 Zod 进行输入校验。
主要功能
- 01按子命令组织的工具
- 02基于会话的身份验证与令牌管理
- 03项目、标签与团队管理
- 04Webhook 管理
- 05从 CSV 或 JSON 文件批量导入任务
- 06基于 opossum 的断路器重试机制
适用场景
- 01让 AI 助手查询和创建 Vikunja 任务
- 02通过 Webhook 为项目配置自动化
- 03批量导入任务数据
MCP 相关
该项目是一个 MCP 服务,向 MCP 客户端暴露 Vikunja 的任务、项目、标签等操作,工具以 vikunja_tasks、vikunja_projects 等子命令形式提供。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
9收录的工具列表 · 更新于 3 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| subcommand | enum | 是 | connect | status | refresh | disconnect |
| apiUrl | string | 否 | |
| apiToken | string | 否 |
详细介绍
Vikunja MCP 服务
一个模型上下文协议(MCP)服务,可让 AI 助手与 Vikunja 任务管理实例交互。
功能
- 基于子命令的工具,让 AI 交互更直观
- 基于会话的身份验证,自动管理令牌
- 已实现完整的任务管理操作
- 完整的项目管理,支持 CRUD 操作
- 标签管理,用于整理任务
- 团队操作,支持协作(受 API 限制,仅支持获取、更新和成员管理)
- 用户管理,支持设置和搜索
- Webhook 管理,用于项目自动化
- 批量导入,可从 CSV 或 JSON 文件导入任务
- 输入验证,适用于日期、ID 和十六进制颜色
- 高效的差异式更新,用于受指派人
- 采用严格模式的 TypeScript,确保类型安全
- 全面的错误处理,包含类型化错误和集中式工具
- 适用于生产环境的重试逻辑,使用 opossum 熔断器增强可靠性
- 增强的安全性,采用基于 Zod 的输入验证并防范 DoS
- 速率限制保护,通过可配置限制抵御 DoS 攻击
- 内存保护,包含分页限制和使用情况监控
- 简化的架构,代码量减少 90%,更易维护
🚀 重大架构改进(v0.2.0)
此版本进行了大幅架构简化,消除技术债务,同时提升安全性和可靠性:
存储架构重构(代码量减少 90%)
- 之前:33 个文件,9,803 行过度设计的存储系统代码
- 之后:4 个文件,仅保留必要功能
- 已移除:复杂的协调器、健康监控器、统计跟踪和迁移系统
- 结果:外部 API 保持不变,可维护性大幅提升
基于 Zod 的筛选器系统(移除 850 多行代码)
- 之前:自定义分词器、解析器和验证器,存在安全漏洞
- 之后:通过安全的 Zod 模式验证实现适用于生产环境的解析
- 增强:DoS 防护、输入清理和全面的错误处理
- 结果:解析速度更快、安全性更高,并达到企业级可靠性
适用于生产环境的重试系统(替换 580 多行代码)
- 之前:自定义重试逻辑,维护成本高
- 之后:经过实战验证的 opossum 熔断器库
- 功能:共享熔断器状态、自动恢复和全面监控
- 结果:采用经过实战验证的模式,增强生产环境的可靠性
零破坏性变更
所有改进在增强可靠性和安全性的同时,均与现有实现保持100% 向后兼容。
要求
- Node.js 20+(仅限 LTS 版本)
- 具有 API 访问权限的 Vikunja 实例
- 用于身份验证的 API 令牌(以
tk_开头)或 JWT 令牌
安装
选项 1:从 NPM 安装(推荐)
使用 vikunja-mcp 最简单的方式是在 Claude Desktop 或其他兼容 MCP 的客户端配置中通过 npx 调用:
{
"vikunja": {
"command": "npx",
"args": ["-y", "@democratize-technology/vikunja-mcp"],
"env": {
"VIKUNJA_URL": "https://your-vikunja-instance.com/api/v1",
"VIKUNJA_API_TOKEN": "your-api-token"
}
}
}
选项 2:本地开发
用于开发或自定义:
git clone https://github.com/democratize-technology/vikunja-mcp.git
cd vikunja-mcp
npm install
npm run build
然后配置 MCP 客户端:
{
"vikunja": {
"command": "node",
"args": ["/path/to/vikunja-mcp/dist/index.js"],
"env": {
"VIKUNJA_URL": "https://your-vikunja-instance.com/api/v1",
"VIKUNJA_API_TOKEN": "your-api-token"
}
}
}
配置
日志配置
服务包含结构化日志系统。通过环境变量进行配置:
# Enable debug logging (default: false)
DEBUG=true
# Set specific log level (error, warn, info, debug)
# If not set, defaults to 'info' (or 'debug' if DEBUG=true)
LOG_LEVEL=debug
日志输出包含时间戳和日志级别:
[2025-05-25T17:00:00.000Z] [INFO] Vikunja MCP server started
[2025-05-25T17:00:00.100Z] [DEBUG] Executing tasks tool { subcommand: 'list', args: {...} }
所有日志均写入 stderr,以便将 stdout 保留用于 MCP 协议通信。
身份验证方式
Vikunja MCP 服务支持两种身份验证方式,各自具备不同的能力:
API 令牌身份验证(默认)
API 令牌是 Vikunja 的标准身份验证方式:
- 获取方式: 前往 Vikunja「设置」→「API 令牌」→「创建新令牌」
- 令牌格式: 以
tk_开头(例如,tk_abc123def456) - 功能: 可完整访问任务、项目、标签、团队和 Webhook
- 限制: 无法访问用户专属端点(用户个人资料、设置、导出)
- 适用场景: 自动化、CI/CD 和常规任务管理
JWT 身份验证(高级)
JWT(JSON Web Token)身份验证可完整访问 Vikunja 的所有端点:
- 获取方式: 从浏览器会话中提取(见下方说明)
- 令牌格式: 以
eyJ开头的长字符串(标准 JWT 格式) - 功能: 可完整访问所有端点,包括用户管理和导出
- 限制: 令牌会过期(通常在 24 小时后)
- 适用场景: 用户管理、数据导出以及需要用户上下文的操作
如何提取 JWT 令牌
- 在网页浏览器中登录 Vikunja
- 打开开发者工具(F12 或右键单击 → 检查)
- 前往 Application/Storage 选项卡
- 找到 JWT 令牌:
- 在 Local Storage → 你的 Vikunja 域名下查找
- 找到名为
token或类似名称的键 - 其值就是你的 JWT 令牌
- 复制完整的令牌值(令牌会很长)
使用 JWT 身份验证
// Connect with JWT token - automatically detected!
vikunja_auth.connect({
apiUrl: "https://your-vikunja-instance.com/api/v1",
apiToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
})
重要说明:
- JWT 令牌会过期;过期后需要重新提取
- 系统会根据格式自动检测令牌类型(无需设置标志)
- 部分工具(用户、导出)仅支持 JWT 身份验证
快速入门
-
设置身份验证(如果不使用环境变量):
vikunja_auth.connect({ apiUrl: "https://your-vikunja-instance.com/api/v1", apiToken: "your-api-token" }) -
创建你的第一个任务:
vikunja_tasks.create({ projectId: 1, title: "My first task via MCP!" }) -
列出你的所有任务:
vikunja_tasks.list({ allProjects: true })
用法
MCP 服务通过子命令提供工具。所有操作都需要先进行身份验证(通过环境变量或手动连接)。
身份验证
// Connect with API token (automatically detected)
vikunja_auth.connect({
apiUrl: "https://your-vikunja-instance.com/api/v1",
apiToken: "tk_your-api-token"
})
// Connect with JWT token (automatically detected, enables additional tools: users, export)
vikunja_auth.connect({
apiUrl: "https://your-vikunja-instance.com/api/v1",
apiToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
})
// Check authentication status
vikunja_auth.status()
// Disconnect and clean up resources
vikunja_auth.disconnect()
任务管理示例
// List all tasks across all projects
vikunja_tasks.list({ allProjects: true })
// List tasks for a specific project with pagination
vikunja_tasks.list({
projectId: 1,
page: 1,
perPage: 20,
sort: "due_date"
})
// List tasks with filters (high priority, not done)
vikunja_tasks.list({
filter: "(priority >= 4 && done = false)"
})
// List tasks with simple filter
vikunja_tasks.list({
filter: "priority >= 3"
})
// List tasks with complex filter conditions
vikunja_tasks.list({
filter: "(priority >= 3 && priority <= 5) || (done = true && updated > '2024-01-01')"
})
// Combine filter with search
vikunja_tasks.list({
filter: "priority >= 4",
search: "urgent"
})
// Create a new task with labels and assignees
vikunja_tasks.create({
projectId: 1,
title: "Complete documentation",
description: "Update README with examples",
dueDate: "2024-12-31T23:59:59Z",
priority: 3,
labels: [1, 2], // Label IDs
assignees: [1, 3] // User IDs
})
// Create a recurring task (repeats every week)
vikunja_tasks.create({
projectId: 1,
title: "Weekly team meeting",
description: "Sync up with the team",
dueDate: "2024-12-01T10:00:00Z",
repeatAfter: 7, // Number of units
repeatMode: "day" // Unit: "day", "week", "month", or "year"
})
// Create a monthly recurring task
vikunja_tasks.create({
projectId: 1,
title: "Monthly report",
repeatAfter: 1,
repeatMode: "month"
})
// Get detailed information about a task
vikunja_tasks.get({ id: 123 })
// Update a task (partial updates supported)
vikunja_tasks.update({
id: 123,
done: true,
priority: 5
})
// Update recurring settings on an existing task
vikunja_tasks.update({
id: 123,
repeatAfter: 14, // Change to bi-weekly
repeatMode: "day"
})
// Update task assignees (uses efficient diff-based approach)
vikunja_tasks.update({
id: 123,
assignees: [1, 2, 4] // Only adds/removes differences
})
// Delete a task
vikunja_tasks.delete({ id: 123 })
// Bulk assign users to a task
vikunja_tasks.assign({
id: 123,
assignees: [2, 3, 4]
})
// Remove users from a task
vikunja_tasks.unassign({
id: 123,
assignees: [2, 4] // Removes only these users
})
// List all assignees for a task
vikunja_tasks.list-assignees({ id: 123 })
// Add a comment to a task
vikunja_tasks.comment({
id: 123,
comment: "This task is now complete!"
})
// List all comments on a task
vikunja_tasks.comment({ id: 123 })
// Create a task relation (e.g., subtask, blocking, related)
vikunja_tasks.relate({
id: 123,
otherTaskId: 124,
relationKind: "subtask" // 124 is a subtask of 123
})
// Available relation kinds:
// - subtask: Other task is a subtask of this task
// - parenttask: Other task is the parent of this task
// - related: Tasks are related
// - duplicateof: This task is a duplicate of the other
// - duplicates: Other task is a duplicate of this one
// - blocking: This task blocks the other
// - blocked: This task is blocked by the other
// - precedes: This task precedes the other
// - follows: This task follows the other
// - copiedfrom: This task was copied from the other
// - copiedto: Other task was copied from this one
// Remove a task relation
vikunja_tasks.unrelate({
id: 123,
otherTaskId: 124,
relationKind: "subtask"
})
// Get all relations for a task
vikunja_tasks.relations({ id: 123 })
// Add a reminder to a task
vikunja_tasks.add-reminder({
id: 123,
reminderDate: "2024-12-25T10:00:00Z"
})
// List all reminders for a task
vikunja_tasks.list-reminders({ id: 123 })
// Remove a specific reminder from a task
vikunja_tasks.remove-reminder({
id: 123,
reminderId: 1
})
// Bulk create multiple tasks at once (max 100)
vikunja_tasks.bulk-create({
projectId: 1,
tasks: [
{
title: "Task 1",
description: "First task",
priority: 3,
labels: [1, 2]
},
{
title: "Task 2",
dueDate: "2024-12-31T23:59:59Z",
assignees: [1]
},
{
title: "Weekly standup",
repeatAfter: 7,
repeatMode: "day"
}
]
})
// Bulk update multiple tasks with the same field value
vikunja_tasks.bulk-update({
taskIds: [123, 124, 125],
field: "done", // Field to update
value: true // New value for all tasks
})
// Other bulk update examples
vikunja_tasks.bulk-update({
taskIds: [123, 124, 125],
field: "priority",
value: 5
})
vikunja_tasks.bulk-update({
taskIds: [123, 124],
field: "project_id",
value: 2 // Move tasks to different project
})
vikunja_tasks.bulk-update({
taskIds: [123, 124, 125],
field: "labels",
value: [1, 3, 5] // Set same labels on all tasks
})
// Bulk delete multiple tasks (max 100)
vikunja_tasks.bulk-delete({
taskIds: [123, 124, 125]
})
// Batch import tasks from CSV or JSON
vikunja_batch_import({
projectId: 1,
format: "json",
data: JSON.stringify([
{
title: "Task 1",
description: "First imported task",
priority: 3,
dueDate: "2024-12-31T23:59:59Z"
},
{
title: "Task 2",
labels: ["bug", "urgent"], // Will look up label IDs by name
assignees: ["john.doe"] // Will look up user IDs by username
}
])
})
// Import from CSV with headers
vikunja_batch_import({
projectId: 1,
format: "csv",
data: `title,description,priority,dueDate,labels,assignees
"Task 1","Description with, comma",3,2024-12-31T23:59:59Z,"bug;feature","john.doe"
"Task 2","Another task",5,,"urgent","john.doe;jane.smith"`
})
// Dry run to validate without creating tasks
vikunja_batch_import({
projectId: 1,
format: "json",
data: JSON.stringify([...]),
dryRun: true // Only validates, doesn't create tasks
})
// Continue on errors instead of stopping
vikunja_batch_import({
projectId: 1,
format: "csv",
data: csvData,
skipErrors: true // Skip invalid tasks and continue with valid ones
})
数据导出示例
// Export a project with all its data
vikunja_export_project({
projectId: 1,
includeChildren: false // Only export the specified project
})
// Export a project including all child projects
vikunja_export_project({
projectId: 1,
includeChildren: true // Recursively export child projects
})
// The export returns JSON data with the following structure:
// {
// project: { ... }, // Project details
// tasks: [ ... ], // All tasks in the project
// labels: [ ... ], // All labels used in tasks
// child_projects: [ ... ], // Nested child project exports (if includeChildren: true)
// exported_at: "...", // ISO timestamp of export
// version: "1.0.0" // Export format version
// }
// Request a full user data export (sent via email)
vikunja_request_user_export({
password: "your-password" // Required for security
})
// Download a previously requested user data export
vikunja_download_user_export({
password: "your-password" // Required for security
})
项目管理示例
// List all projects
vikunja_projects.list()
// List projects with search and pagination
vikunja_projects.list({
search: "frontend",
page: 1,
perPage: 10,
isArchived: false
})
// Get a specific project
vikunja_projects.get({ id: 1 })
// Create a new project
vikunja_projects.create({
title: "New Frontend Project",
description: "React-based web application",
hexColor: "#4287f5"
})
// Update a project
vikunja_projects.update({
id: 1,
title: "Updated Project Name",
isArchived: true
})
// Archive a project
vikunja_projects.archive({ id: 1 })
// Unarchive a project
vikunja_projects.unarchive({ id: 1 })
// Delete a project
vikunja_projects.delete({ id: 1 })
// --- Project Hierarchy Management ---
// Create a child project
vikunja_projects.create({
title: "Frontend Module",
description: "React components",
parentProjectId: 1, // Will be a child of project 1
hexColor: "#3498db"
})
// Get all direct children of a project
vikunja_projects.get-children({ id: 1 })
// Returns: Array of projects that have parentProjectId = 1
// Get complete project hierarchy as a tree
vikunja_projects.get-tree({ id: 1 })
// Returns: Project with nested children structure
// {
// id: 1,
// title: "Main Project",
// children: [
// {
// id: 2,
// title: "Frontend Module",
// children: [
// { id: 4, title: "Components", children: [] },
// { id: 5, title: "Styles", children: [] }
// ]
// },
// {
// id: 3,
// title: "Backend Module",
// children: []
// }
// ]
// }
// Get breadcrumb path from root to a project
vikunja_projects.get-breadcrumb({ id: 5 })
// Returns: Array of projects from root to target
// [
// { id: 1, title: "Main Project" },
// { id: 2, title: "Frontend Module" },
// { id: 5, title: "Styles" }
// ]
// Also includes a formatted path: "Main Project > Frontend Module > Styles"
// Move a project to a new parent
vikunja_projects.move({
id: 5, // Project to move
parentProjectId: 3 // New parent
})
// Validates against circular references and depth limits
// Move a project to root level (no parent)
vikunja_projects.move({
id: 5,
parentProjectId: undefined
})
// --- Project Sharing ---
// Create a read-only share link
vikunja_projects.create-share({
id: 1,
right: 0, // 0=Read, 1=Write, 2=Admin
label: "Public read-only access"
})
// Create a password-protected share with write access
vikunja_projects.create-share({
id: 1,
right: 1,
password: "securepassword123",
label: "Team collaboration link"
})
// Create an expiring share link
vikunja_projects.create-share({
id: 1,
right: 0,
expires: "2025-12-31T23:59:59Z",
label: "Temporary access until year end"
})
// List all shares for a project
vikunja_projects.list-shares({ id: 1 })
// Get details of a specific share
vikunja_projects.get-share({
id: 1,
shareId: 123
})
// Delete a share link
vikunja_projects.delete-share({
id: 1,
shareId: 123
})
// Authenticate to access a shared project
vikunja_projects.auth-share({
shareHash: "abc123def456"
})
// Authenticate to a password-protected share
vikunja_projects.auth-share({
shareHash: "abc123def456",
password: "securepassword123"
})
标签管理示例
// List all labels
vikunja_labels.list()
// Search for labels
vikunja_labels.list({
search: "bug",
page: 1,
perPage: 20
})
// Get a specific label
vikunja_labels.get({ id: 1 })
// Create a new label
vikunja_labels.create({
title: "Critical",
description: "Critical priority issues",
hexColor: "#ff0000"
})
// Update a label
vikunja_labels.update({
id: 1,
title: "High Priority",
hexColor: "#ff6600"
})
// Delete a label
vikunja_labels.delete({ id: 1 })
// --- Label Assignment to Tasks ---
// Apply multiple labels to a task
vikunja_tasks.apply-label({
id: 123,
labels: [1, 2, 3] // Apply labels with IDs 1, 2, and 3
})
// Apply a single label
vikunja_tasks.apply-label({
id: 123,
labels: [1] // Apply just the "research" label
})
// Remove specific labels from a task
vikunja_tasks.remove-label({
id: 123,
labels: [2, 3] // Remove labels 2 and 3, keep others
})
// List all labels on a task
vikunja_tasks.list-labels({ id: 123 })
// Returns: Task info with detailed label data including colors and descriptions
团队管理示例
// List all teams
vikunja_teams.list()
// Search for teams
vikunja_teams.list({
search: "frontend",
page: 1,
perPage: 10
})
// Create a new team
vikunja_teams.create({
name: "Frontend Team",
description: "Responsible for UI/UX development"
})
// Delete a team
vikunja_teams.delete({ id: 1 })
// Note: get, update, and members operations are not yet
// implemented in the node-vikunja library
用户管理示例
⚠️ 已知问题:即使使用有效令牌,用户端点也可能因身份验证错误而失败。这是 Vikunja API 的已知问题。发生这种情况时,MCP 服务会提供有帮助的错误消息。如果遇到此问题,请联系你的 Vikunja 服务管理员。
// Get current user information
vikunja_users.current()
// Search for users
vikunja_users.search({
search: "john"
})
// Get current user settings
vikunja_users.settings()
// Update user settings
vikunja_users.update-settings({
name: "John Doe",
language: "en",
timezone: "America/New_York",
weekStart: 1 // Monday
})
// Update notification preferences
vikunja_users.update-settings({
emailRemindersEnabled: true, // Enable email reminders for tasks
overdueTasksRemindersEnabled: true, // Enable daily overdue task emails
overdueTasksRemindersTime: "09:00" // Time to send overdue task summary
})
Webhook 管理示例
// List all available webhook events
vikunja_webhooks.list-events()
// Returns: ["task.created", "task.updated", "task.deleted", "task.assigned", ...]
// List webhooks for a project
vikunja_webhooks.list({ projectId: 1 })
// Create a webhook for task events
vikunja_webhooks.create({
projectId: 1,
targetUrl: "https://example.com/webhook",
events: ["task.created", "task.updated"],
secret: "my-secret-key" // Optional, for HMAC signing
})
// Create a webhook without secret
vikunja_webhooks.create({
projectId: 1,
targetUrl: "https://example.com/notifications",
events: ["task.assigned", "task.comment.created"]
})
// Get a specific webhook
vikunja_webhooks.get({
projectId: 1,
webhookId: 123
})
// Update webhook events
vikunja_webhooks.update({
projectId: 1,
webhookId: 123,
events: ["task.created", "task.updated", "task.deleted"]
})
// Delete a webhook
vikunja_webhooks.delete({
projectId: 1,
webhookId: 123
})
高级筛选示例
// Create a saved filter for high priority tasks
vikunja_filters.create({
name: "High Priority Tasks",
description: "All undone tasks with priority 4 or 5",
filter: "done = false && priority >= 4",
isGlobal: true
})
// Alternative format using title and filters object
vikunja_filters.create({
title: "🔥 High Priority Tasks",
description: "All tasks with priority 4 or 5 that are not completed",
filters: {
filter_by: ["priority"],
filter_value: ["5"],
filter_comparator: [">="],
filter_concat: ""
},
is_favorite: true
})
// Create a filter with multiple conditions
vikunja_filters.create({
title: "Urgent & Incomplete",
filters: {
filter_by: ["priority", "done"],
filter_value: ["3", "false"],
filter_comparator: [">=", "="],
filter_concat: "&&"
}
})
// Create a project-specific filter
vikunja_filters.create({
name: "This Week's Tasks",
filter: "dueDate >= now && dueDate < now+7d",
projectId: 1,
isGlobal: false
})
// List all saved filters
vikunja_filters.list()
// List filters for a specific project
vikunja_filters.list({ projectId: 1 })
// Apply a saved filter to task listing
vikunja_tasks.list({
filterId: "550e8400-e29b-41d4-a716-446655440000"
})
// Build a filter programmatically
vikunja_filters.build({
conditions: [
{ field: "done", operator: "=", value: false },
{ field: "priority", operator: ">=", value: 3 },
{ field: "assignees", operator: "in", value: ["user1", "user2"] }
],
groupOperator: "&&"
})
// Returns: { filter: "(done = false && priority >= 3 && assignees in user1, user2)", valid: true }
// Update an existing filter
vikunja_filters.update({
id: "550e8400-e29b-41d4-a716-446655440000",
name: "Critical Tasks",
filter: "done = false && priority = 5"
})
// Validate a filter string
vikunja_filters.validate({
filter: "done = false && priority >= 3"
})
// Returns: { valid: true, errors: [] }
筛选语法参考
Vikunja 筛选语法支持类似 SQL 的查询,包含以下内容:
字段:
done- 任务完成状态(布尔值)priority- 任务优先级(1-5)percentDone- 完成百分比(0-100)dueDate- 任务截止日期assignees- 任务受分配人labels- 关联的标签created- 任务创建时间updated- 任务更新时间
运算符:
- 比较:
=,!=,>,>=,<,<= - 模式匹配:
like(使用%通配符) - 列表匹配:
in,not in
日期计算:
now- 当前时间now+24h- 24 小时后now-7d- 7 天前now/d- 当天开始时间- 支持:s(秒)、m(分钟)、h(小时)、d(天)、w(周)、M(月)、y(年)
示例:
priority = 4dueDate < nowdone = false && priority >= 3assignees in user1, user2dueDate >= now && dueDate < now+7dtitle like "%urgent%"
智能混合筛选: 此 MCP 服务采用智能混合筛选方式,结合服务端和客户端筛选,以实现最佳性能和可靠性:
- 首选:优先尝试服务端筛选,以获得最高性能
- 回退:如果服务端筛选失败或不可用,则回退到客户端筛选
- 透明:无论使用哪种方式,筛选语法都相同
- 优化:通过分页限制提供内存保护,避免无界加载
- 元数据:响应包含所用的筛选方式(
serverSideFiltering或clientSideFiltering) - 性能:服务端筛选可显著减少网络流量和处理时间
响应格式
Vikunja MCP 服务中的所有操作均遵循标准化的响应格式,以确保一致性和可预测性:
interface StandardResponse {
success: boolean;
operation: string; // The operation performed (e.g., 'create', 'update', 'list')
message?: string; // Human-readable description of the result
data?: any; // The primary data returned (task, project, label, etc.)
metadata?: {
timestamp: string; // ISO 8601 timestamp of the operation
[key: string]: any; // Additional operation-specific metadata
};
}
响应示例
成功:
{
"success": true,
"operation": "create",
"message": "Task created successfully",
"data": { "id": 123, "title": "Complete documentation" },
"metadata": { "timestamp": "2025-05-25T12:00:00Z" }
}
错误:
{
"success": false,
"operation": "update",
"message": "Task not found",
"error": { "code": "TASK_NOT_FOUND", "details": "No task exists with ID 999" }
}
此标准化格式可确保:
- 一致性:所有工具均以相同结构返回响应
- 可预测性:客户端始终清楚会有哪些字段
- 调试:元数据提供故障排查所需的上下文
- 错误处理:提供包含代码和消息的清晰错误信息
可用工具
身份验证
vikunja_auth- 身份验证管理connect- 使用 API 令牌初始化连接status- 检查身份验证状态refresh- 刷新身份验证令牌
任务管理 ✅
vikunja_tasks- 任务操作(已完整实现)list- 按筛选条件列出任务- 按项目筛选或获取所有任务
- 支持分页、搜索和排序
- 按完成状态筛选
- 使用
filterId参数应用已保存的筛选条件
create- 创建新任务- 必填:title、projectId
- 可选:description、dueDate、priority、labels、assignees
- 验证日期格式(ISO 8601)和 ID
get- 按 ID 获取任务详情update- 更新现有任务- 支持部分更新
- 可更新 title、description、dueDate、priority 和 done 状态
- 可更新标签和受分配人(使用高效的差异比对方式)
delete- 按 ID 删除任务assign- 批量将用户分配给任务unassign- 从任务中移除用户comment- 列出或添加任务评论bulk-update- 一次更新多个任务- 必填:taskIds 数组、字段名称、值
- 支持的字段:done、priority、due_date、project_id、assignees、labels
- 验证字段类型和值
- ⚠️ 性能:会调用 API 获取每个已更新的任务
bulk-delete- 一次删除多个任务- 必填:taskIds 数组
- 返回已删除任务的详情以供确认
- 妥善处理部分失败
- ⚠️ 性能:会为每个任务单独调用删除接口
- 建议:每批处理 20 个或更少的任务
attach- 尚未实现(MCP 中不提供文件处理功能)
批量导入 ✅
vikunja_batch_import- 从 CSV 或 JSON 导入多个任务(已完整实现)- 必填:projectId、format('csv' 或 'json')、data
- 可选:skipErrors(出错时继续)、dryRun(仅验证)
- 批量大小限制:每次导入最多 100 个任务
- CSV 格式:
- 需要包含字段名称的标题行
- 支持带引号的值和转义引号
- 字段:title、description、priority、dueDate、labels、assignees
- labels 和 assignees 使用分号分隔值(使用分号以避免与 CSV 逗号冲突)
- JSON 格式:
- 任务对象数组
- 字段与 CSV 相同,并直接支持数组
- 功能:
- 按名称自动查找标签
- 按用户名自动查找用户
- 创建前进行验证
- 详细的错误报告
- 用于测试的试运行模式
- 支持跳过错误,以便部分导入
项目管理 ✅
vikunja_projects- 项目操作(已完整实现)list- 列出所有项目并支持筛选- 支持分页和搜索
- 按归档状态筛选
get- 按 ID 获取项目详情create- 创建新项目- 必填:title
- 可选:description、parentProjectId、isArchived、hexColor(格式:#RRGGBB)
- 验证父项目层级深度(最多 10 层)
update- 更新现有项目- 支持部分更新
- 可更新所有项目字段,包括 hexColor(格式:#RRGGBB)
- 更改父项目时验证父项目层级深度
delete- 按 ID 删除项目archive- 归档项目unarchive- 取消归档项目- 层级管理(新增!)
get-children- 列出项目的直接子项目get-tree- 以树形结构获取完整的项目层级get-breadcrumb- 获取从根项目到指定项目的路径move- 将项目移至新的父项目下- 验证是否存在循环引用
- 强制执行最多 10 层的深度限制
- 项目共享
create-share- 创建具有相应权限的共享链接list-shares- 列出项目的所有共享get-share- 获取共享详情delete-share- 移除共享链接auth-share- 验证身份以访问共享项目
标签管理 ✅
vikunja_labels- 标签操作(已完整实现)list- 列出所有标签并支持筛选- 支持分页和搜索
get- 按 ID 获取标签详情create- 创建新标签- 必填:title
- 可选:description、hexColor(格式:#RRGGBB)
update- 更新现有标签- 支持部分更新
- 可更新 title、description、hexColor
delete- 按 ID 删除标签apply-label- 将一个或多个标签应用到任务- 必填:task id、labels 数组
- 支持批量应用标签
remove-label- 从任务中移除一个或多个标签- 必填:task id、labels 数组
- 支持批量移除标签
list-labels- 列出分配给任务的所有标签- 必填:task id
- 返回详细的标签信息
项目模板 ✅
vikunja_templates- 模板操作(已完整实现)create- 根据现有项目创建模板- 必填:projectId、name
- 可选:description、tags
- 捕获所有项目设置和任务
list- 列出所有可用模板- 显示模板名称、标签和作者
get- 根据 ID 获取模板详情update- 更新模板元数据- 可更新 name、description、tags
delete- 删除模板instantiate- 根据模板创建新项目- 必填:id(模板 ID)、projectName
- 可选:parentProjectId、variables
- 支持变量替换:
{{PROJECT_NAME}}- 新项目名称{{TODAY}}- 当前日期(YYYY-MM-DD){{NOW}}- 当前时间戳- 通过 variables 参数传入自定义变量
- 使用模板中的标签创建所有任务
团队管理 ✅
vikunja_teams- 团队操作(部分实现)list- 使用筛选条件列出所有团队- 支持分页和搜索
create- 创建新团队- 必填:name
- 可选:description
delete- 根据 ID 删除团队(支持备用 API)get- 尚未在 node-vikunja 中实现update- 尚未在 node-vikunja 中实现members- 尚未在 node-vikunja 中实现
用户管理 ✅
vikunja_users- 用户操作(已完整实现)[需要 JWT 身份验证]current- 获取当前已通过身份验证的用户信息search- 搜索用户- 可选:搜索查询、分页
settings- 获取当前用户设置update-settings- 更新用户设置- 可选:name、language、timezone、weekStart、frontendSettings
- 注意: 用户操作需要 JWT 身份验证。使用 API token 身份验证时,无法使用这些工具。
Webhook 管理 ✅
-
vikunja_webhooks- 用于项目自动化的 Webhook 操作(已完整实现)list-events- 获取所有可用的 Webhook 事件类型list- 列出项目的 Webhook- 必填:projectId
get- 获取指定的 Webhook- 必填:projectId、webhookId
create- 创建新的 Webhook- 必填:projectId、targetUrl、events(数组)
- 可选:secret(用于 HMAC 签名)
- 注意:会根据可用事件类型验证事件
update- 更新 Webhook 事件- 必填:projectId、webhookId、events(数组)
- 注意:会根据可用事件类型验证事件
delete- 删除 Webhook- 必填:projectId、webhookId
事件验证:创建或更新 Webhook 时,系统会自动根据 API 提供的可用事件列表验证所提供的事件。无效事件会导致清晰的错误消息,其中会指出哪些事件无效,并列出所有有效选项。有效事件会缓存 5 分钟,以提升性能。
筛选器管理 ✅
-
vikunja_filters- 任务高级筛选(已完整实现)list- 列出已保存的筛选器- 可选:projectId(用于项目专属筛选器)、global 标记
get- 根据 ID 获取指定的筛选器create- 创建新的已保存筛选器- 必填:name、filter(查询字符串)
- 可选:description、projectId、isGlobal
update- 更新现有筛选器- 必填:id
- 可选:name、description、filter、projectId、isGlobal
delete- 删除已保存的筛选器build- 根据条件构建筛选器字符串- 必填:conditions 数组
- 可选:groupOperator(&&、||)
validate- 验证筛选器字符串
注意: 已保存的筛选器目前存储在内存中,MCP 服务重启时会丢失。用于生产环境时,请考虑实现持久化存储。
数据导出 ✅
⚠️ 警告:内存使用
导出操作会将整个项目层级结构加载到内存中。对于包含数千个任务或嵌套结构很深的大型项目,这可能会占用大量内存。建议逐个导出较小的项目。
-
vikunja_export_project- 导出项目数据 [需要 JWT 身份验证]- 参数:
projectId(必需)- 要导出的项目 IDincludeChildren(可选)- 递归包含子项目(默认值:false)
- **返回:**完整的项目数据,包括任务、标签和元数据
- 功能:
- 导出所有任务及其完整详细信息
- 包含项目中使用的所有标签
- 可选包含完整的子项目层级结构
- 检测嵌套项目中的循环引用
- 导出元数据包含时间戳和版本
- **注意:**导出操作需要 JWT 身份验证。使用 API 令牌身份验证时,此工具不可用。
- 参数:
-
vikunja_request_user_export- 请求导出完整的用户数据- 参数:
password(必需)- 用于安全验证的用户密码
- **返回:**已请求导出的确认信息
- **注意:**导出准备就绪后,您会收到一封电子邮件
- 参数:
-
vikunja_download_user_export- 下载之前请求的用户数据导出文件- 参数:
password(必需)- 用于安全验证的用户密码
- **返回:**完整的用户数据导出文件
- **注意:**必须先通过
vikunja_request_user_export请求导出
- 参数:
已知限制
- 文件附件:由于 MCP 协议限制,尚未实现
attach子命令 - 团队操作:由于 node-vikunja API 支持不完整,功能有限:
- 无法按 ID 获取团队
- 无法更新团队信息
- 无法删除团队
- 无法管理团队成员
- 分页:由于 API 限制,某些端点可能无法完全支持分页参数
- 身份验证问题:部分 Vikunja API 端点存在已知的身份验证问题:
- 用户端点:即使令牌有效,也可能因令牌错误而失败(Vikunja API 的已知限制)
- 批量操作:某些 Vikunja API 版本可能存在身份验证问题
- 标签操作:某些服务器配置可能导致身份验证错误
- 受派人操作:创建或更新包含受派人的任务时,可能出现身份验证错误
- 出现这些问题时,服务会提供详细的错误消息,并建议解决方法
安全与性能功能
安全增强
- Zod 模式验证:采用全面的类型检查,提供企业级输入验证
- DoS 防护:输入清理、长度限制和字符允许列表
- 凭据保护:自动屏蔽日志和错误消息中的敏感令牌与 URL
- 实体解析服务:针对格式错误的 API 响应,提供稳健的标签和用户映射及防御性错误处理
- 速率限制:可配置请求速率限制和负载大小限制,以防止 DoS 攻击
- 内存保护:分页限制和内存使用监控,防止资源耗尽
- 错误处理:采用结构化错误响应,避免泄露敏感系统信息
性能优化
- 混合筛选:智能服务端筛选,并在必要时回退到客户端筛选,以实现最佳性能
- 连接池:通过自动缓存客户端,高效管理会话
- 请求批处理:通过基于差异的高效更新,优化批量操作
- 内存管理:通过自动清理和分页,安全处理大型数据集
- 线程安全的客户端管理:仅支持异步的 ClientContext API,可消除并发场景中的竞态条件
- Opossum 熔断器:生产就绪的重试逻辑,支持自动检测故障并恢复
- 简化存储:使用内存中的筛选器存储,复杂度和开销降低 90%
配置
环境变量
此服务支持通过环境变量配置多种选项:
基本配置
# Vikunja instance URL (required)
VIKUNJA_URL=https://your-vikunja-instance.com/api/v1
# Authentication token (required)
VIKUNJA_API_TOKEN=your-api-token
# Enable debug logging (default: false)
DEBUG=true
# Set log level (error, warn, info, debug)
LOG_LEVEL=debug
安全与性能配置
# Rate limiting (default: enabled)
RATE_LIMIT_ENABLED=true
RATE_LIMIT_PER_MINUTE=60 # Requests per minute (default: 60)
RATE_LIMIT_PER_HOUR=1000 # Requests per hour (default: 1000)
# Request size limits (default: 1MB)
MAX_REQUEST_SIZE=1048576 # Maximum request payload size in bytes
MAX_RESPONSE_SIZE=10485760 # Maximum response size in bytes (default: 10MB)
# Execution timeout (default: 30 seconds)
EXECUTION_TIMEOUT=30000 # Tool execution timeout in milliseconds
# Memory protection (default: enabled)
MEMORY_PROTECTION_ENABLED=true
MAX_TASKS_PER_REQUEST=1000 # Maximum tasks to load per request
# Circuit breaker configuration (opossum)
CIRCUIT_BREAKER_ENABLED=true # Enable circuit breaker for API calls
CIRCUIT_BREAKER_TIMEOUT=60000 # Circuit breaker timeout in milliseconds (default: 60s)
CIRCUIT_BREAKER_ERRORS_THROTTLE=10 # Errors before opening circuit (default: 10)
CIRCUIT_BREAKER_RESET_TIMEOUT=30000 # Time to wait before trying half-open state (default: 30s)
# Filter security (Zod validation)
FILTER_MAX_LENGTH=1000 # Maximum filter string length (default: 1000)
FILTER_MAX_VALUE_LENGTH=200 # Maximum individual value length (default: 200)
详细的速率限制配置请参阅 docs/RATE_LIMITING.md。
路线图
- ✅ 安全强化 - 已实现全面的漏洞修复
- ✅ 性能优化 - 混合筛选和内存保护
- ✅ 错误处理 - 集中式错误工具和结构化响应
- ✅ 测试覆盖率 - 函数覆盖率达到 98.91%
- ✅ 架构简化 - 代码量减少 90%,可维护性增强
- ✅ 生产就绪的弹性 - Opossum 熔断器和 Zod 验证
- 添加 Webhook 订阅以支持实时更新
- 为频繁访问的数据添加缓存
- 添加与真实 Vikunja 实例的集成测试
- 为已保存的筛选器实现持久化存储(可选 - 内存存储效果良好)
贡献
开发指南和工作流请参阅 CONTRIBUTING.md。
许可证
MIT
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。