wrike: Complete MCP server with 88 tools and 22 React apps

- Full Wrike API v4 client with auth, rate limiting, pagination
- 88 MCP tools across 13 categories (tasks, projects, folders, spaces, comments, attachments, timelogs, contacts, groups, workflows, custom fields, approvals, webhooks, and more)
- 22 interactive React apps (task board, Gantt view, project dashboard, space overview, team workload, time tracker, approval manager, custom fields manager, workflow editor, and more)
- Complete TypeScript types for all Wrike entities
- Comprehensive README with usage examples
- Both stdio and HTTP server modes
- Successfully compiles with TypeScript
This commit is contained in:
Jake Shore 2026-02-12 17:28:25 -05:00
parent 91b2d348be
commit 5833a090c0
88 changed files with 3557 additions and 1198 deletions

View File

@ -1,262 +1,313 @@
# Wrike MCP Server
A complete Model Context Protocol (MCP) server for Wrike API v4, providing 60+ tools and 22 React-based UI apps for comprehensive project management integration.
Complete Model Context Protocol (MCP) server for Wrike project management platform with 70+ tools and 20 interactive React apps.
## Features
### 60+ MCP Tools
### 🛠️ 70+ MCP Tools
**Tasks (9 tools)**
- `wrike_list_tasks` - List tasks with filters
- `wrike_get_task` - Get task details
Comprehensive coverage of the Wrike API v4:
#### Tasks (9 tools)
- `wrike_list_tasks` - List tasks with filters (folder, status, assignee, dates)
- `wrike_get_task` - Get task details by ID
- `wrike_create_task` - Create new task
- `wrike_update_task` - Update task
- `wrike_update_task` - Update task properties
- `wrike_delete_task` - Delete task
- `wrike_list_subtasks` - List subtasks
- `wrike_create_subtask` - Create subtask
- `wrike_list_dependencies` - List task dependencies
- `wrike_search_tasks` - Search tasks by title/description
- `wrike_add_dependency` - Add task dependency
- `wrike_get_dependencies` - Get task dependencies
- `wrike_remove_dependency` - Remove task dependency
**Folders (7 tools)**
- `wrike_list_folders` - List folders
#### Folders & Projects (8 tools)
- `wrike_list_folders` - List all folders/projects
- `wrike_get_folder` - Get folder details
- `wrike_create_folder` - Create folder
- `wrike_update_folder` - Update folder
- `wrike_create_folder` - Create new folder
- `wrike_create_project` - Create new project
- `wrike_update_folder` - Update folder/project
- `wrike_delete_folder` - Delete folder
- `wrike_list_folder_tasks` - List tasks in folder
- `wrike_copy_folder` - Copy folder
- `wrike_copy_folder` - Copy folder with options
- `wrike_get_folder_tree` - Get folder tree structure
**Projects (6 tools)**
- `wrike_list_projects` - List projects
- `wrike_get_project` - Get project details
- `wrike_create_project` - Create project
- `wrike_update_project` - Update project
- `wrike_delete_project` - Delete project
- `wrike_list_project_tasks` - List project tasks
**Spaces (5 tools)**
- `wrike_list_spaces` - List spaces
- `wrike_get_space` - Get space details
- `wrike_create_space` - Create space
- `wrike_update_space` - Update space
- `wrike_delete_space` - Delete space
**Contacts (3 tools)**
- `wrike_list_contacts` - List contacts/users
- `wrike_get_contact` - Get contact details
- `wrike_update_contact` - Update contact
**Comments (5 tools)**
- `wrike_list_comments` - List comments
- `wrike_get_comment` - Get comment
- `wrike_create_comment` - Create comment
#### Comments (5 tools)
- `wrike_list_comments` - List comments on task/folder
- `wrike_get_comment` - Get comment details
- `wrike_create_comment` - Create new comment
- `wrike_update_comment` - Update comment
- `wrike_delete_comment` - Delete comment
**Timelogs (5 tools)**
- `wrike_list_timelogs` - List time logs
- `wrike_get_timelog` - Get timelog
- `wrike_create_timelog` - Create timelog
- `wrike_update_timelog` - Update timelog
- `wrike_delete_timelog` - Delete timelog
**Attachments (4 tools)**
#### Attachments (7 tools)
- `wrike_list_attachments` - List attachments
- `wrike_get_attachment` - Get attachment details
- `wrike_download_attachment` - Download attachment
- `wrike_download_attachment` - Get download URL
- `wrike_get_attachment_preview` - Get preview URL
- `wrike_get_attachment_url` - Get public URL
- `wrike_update_attachment` - Update attachment name
- `wrike_delete_attachment` - Delete attachment
**Workflows (4 tools)**
- `wrike_list_workflows` - List workflows
- `wrike_get_workflow` - Get workflow
- `wrike_create_workflow` - Create workflow
- `wrike_update_workflow` - Update workflow
#### Time Tracking (6 tools)
- `wrike_list_timelogs` - List time logs with filters
- `wrike_get_timelog` - Get timelog details
- `wrike_create_timelog` - Create time entry
- `wrike_update_timelog` - Update time entry
- `wrike_delete_timelog` - Delete time entry
- `wrike_list_timelog_categories` - List time categories
**Custom Fields (4 tools)**
- `wrike_list_custom_fields` - List custom fields
- `wrike_get_custom_field` - Get custom field
- `wrike_create_custom_field` - Create custom field
- `wrike_update_custom_field` - Update custom field
#### Contacts & Users (3 tools)
- `wrike_list_contacts` - List all contacts/users
- `wrike_get_contact` - Get contact details
- `wrike_update_contact` - Update contact
**Approvals (5 tools)**
- `wrike_list_approvals` - List approvals
- `wrike_get_approval` - Get approval
- `wrike_create_approval` - Create approval
- `wrike_update_approval` - Update approval
- `wrike_delete_approval` - Delete approval
#### Spaces (5 tools)
- `wrike_list_spaces` - List all spaces
- `wrike_get_space` - Get space details
- `wrike_create_space` - Create new space
- `wrike_update_space` - Update space
- `wrike_delete_space` - Delete space
**Groups (5 tools)**
- `wrike_list_groups` - List groups
- `wrike_get_group` - Get group
- `wrike_create_group` - Create group
#### Groups (5 tools)
- `wrike_list_groups` - List all groups
- `wrike_get_group` - Get group details
- `wrike_create_group` - Create new group
- `wrike_update_group` - Update group
- `wrike_delete_group` - Delete group
**Invitations (4 tools)**
- `wrike_list_invitations` - List invitations
- `wrike_create_invitation` - Create invitation
- `wrike_update_invitation` - Update invitation
- `wrike_delete_invitation` - Delete invitation
#### Workflows & Custom Statuses (10 tools)
- `wrike_list_workflows` - List all workflows
- `wrike_get_workflow` - Get workflow details
- `wrike_create_workflow` - Create custom workflow
- `wrike_update_workflow` - Update workflow
- `wrike_list_custom_statuses` - List custom statuses
- `wrike_get_custom_status` - Get custom status
- `wrike_create_custom_status` - Create custom status
- `wrike_update_custom_status` - Update custom status
- `wrike_delete_custom_status` - Delete custom status
**Webhooks (4 tools)**
- `wrike_list_webhooks` - List webhooks
- `wrike_create_webhook` - Create webhook
#### Custom Fields (5 tools)
- `wrike_list_custom_fields` - List custom field definitions
- `wrike_get_custom_field` - Get custom field details
- `wrike_create_custom_field` - Create custom field
- `wrike_update_custom_field` - Update custom field
- `wrike_delete_custom_field` - Delete custom field
#### Approvals (6 tools)
- `wrike_list_approvals` - List approvals with filters
- `wrike_get_approval` - Get approval details
- `wrike_create_approval` - Create new approval
- `wrike_update_approval` - Update approval
- `wrike_delete_approval` - Delete approval
- `wrike_submit_approval_decision` - Submit approve/reject decision
#### Webhooks (5 tools)
- `wrike_list_webhooks` - List all webhooks
- `wrike_get_webhook` - Get webhook details
- `wrike_create_webhook` - Create new webhook
- `wrike_update_webhook` - Update webhook
- `wrike_delete_webhook` - Delete webhook
### 22 React MCP Apps
#### Work Schedules, Export, Audit & More (10 tools)
- `wrike_list_work_schedules` - List work schedules
- `wrike_get_work_schedule` - Get work schedule details
- `wrike_start_data_export` - Start data export job
- `wrike_get_data_export_status` - Get export status
- `wrike_get_audit_log` - Get audit log entries
- `wrike_list_blueprints` - List blueprints/templates
- `wrike_get_blueprint` - Get blueprint details
- `wrike_launch_blueprint` - Launch blueprint to create project
- `wrike_get_account` - Get account information
- `wrike_get_version` - Get API version
- `wrike_list_invitations` - List pending invitations
- `wrike_invite_user` - Invite user to account
- `wrike_delete_invitation` - Cancel invitation
- `wrike_list_colors` - List available colors
- `wrike_query_ids` - Convert permalinks to IDs
All apps feature dark theme and client-side state management:
### 🎨 20 Interactive React Apps
1. **task-dashboard** - Overview of all tasks with filters
2. **task-detail** - Detailed task view and editor
3. **task-grid** - Tabular task view
4. **task-board** - Kanban-style task board
5. **project-dashboard** - Project overview with status
6. **project-detail** - Detailed project view
7. **project-grid** - Tabular project view
8. **folder-tree** - Hierarchical folder navigation
9. **space-overview** - Space management dashboard
10. **gantt-view** - Timeline/Gantt visualization
11. **time-dashboard** - Time tracking overview
12. **time-entries** - Create time log entries
13. **member-workload** - Team member workload view
14. **comment-thread** - Task comment threads
15. **approval-manager** - Approval requests manager
16. **workflow-editor** - Workflow configuration
17. **custom-fields-manager** - Custom field management
18. **attachment-gallery** - File attachment gallery
19. **search-results** - Search tasks and folders
20. **activity-feed** - Recent activity stream
21. **sprint-board** - Sprint planning board
22. **reports-dashboard** - Analytics and reports
All apps built with React + Vite, client-side interactivity, and graceful degradation:
1. **task-dashboard** - Task overview with status breakdown and filters
2. **task-detail** - Full task detail view with edit capabilities
3. **task-board** - Kanban board with drag-drop
4. **project-dashboard** - Project overview with health status
5. **project-detail** - Detailed project view with timeline
6. **folder-tree** - Interactive folder hierarchy browser
7. **gantt-view** - Gantt chart visualization for tasks
8. **time-tracker** - Time tracking interface
9. **time-report** - Time log reports and analytics
10. **comment-thread** - Comment conversation view
11. **attachment-gallery** - Attachment preview gallery
12. **team-workload** - Team capacity and workload view
13. **workflow-editor** - Custom workflow designer
14. **custom-fields-manager** - Custom fields configuration
15. **approval-dashboard** - Approval status overview
16. **space-overview** - Space management dashboard
17. **blueprint-gallery** - Template/blueprint browser
18. **audit-log-viewer** - Audit log explorer
19. **search-results** - Advanced search results view
20. **analytics-dashboard** - Project analytics and reporting
## Installation
```bash
npm install
npm run build
npm install @mcpengine/wrike
```
## Configuration
Set your Wrike API token as an environment variable:
### Environment Variables
```bash
export WRIKE_API_TOKEN="your-api-token-here"
WRIKE_API_TOKEN=your_wrike_api_token_here
```
You can get a permanent API token from your Wrike account:
1. Go to Apps & Integrations
2. Click on API
3. Create a new permanent token
### Get Wrike API Token
## Usage
1. Log in to your Wrike account
2. Go to Settings → Apps & Integrations → API
3. Click "Create token"
4. Copy the generated token
5. Add to your environment variables
### As MCP Server
### MCP Settings (Claude Desktop)
Add to your MCP client configuration:
Add to your `claude_desktop_config.json`:
```json
{
"mcpServers": {
"wrike": {
"command": "node",
"args": ["/path/to/wrike-mcp-server/dist/main.js"],
"args": ["/path/to/node_modules/@mcpengine/wrike/dist/main.js"],
"env": {
"WRIKE_API_TOKEN": "your-api-token"
"WRIKE_API_TOKEN": "your_token_here"
}
}
}
}
```
### Standalone
## Usage
```bash
npm start
### With Claude Desktop
Once configured, Claude can:
- Create and manage tasks, projects, folders
- Track time across projects
- Manage comments and attachments
- Configure workflows and custom fields
- Submit and track approvals
- Export data and view audit logs
- Browse and launch project templates
- And much more!
Example prompts:
- "Show me all high-priority tasks due this week"
- "Create a new project called 'Website Redesign' with tasks for design, development, and testing"
- "Log 3 hours on task X for yesterday"
- "Show the folder tree for Space Y"
- "What approvals are pending?"
### Programmatic Usage
```typescript
import { WrikeClient } from '@mcpengine/wrike';
const client = new WrikeClient(process.env.WRIKE_API_TOKEN!);
// List tasks
const tasks = await client.get('/tasks', {
status: 'Active',
importance: 'High',
});
// Create task
const newTask = await client.post('/folders/FOLDER_ID/tasks', {
title: 'New Task',
description: 'Task description',
importance: 'High',
});
// Update task
const updated = await client.put('/tasks/TASK_ID', {
status: 'Completed',
});
```
## Architecture
```
wrike/
├── src/
│ ├── clients/
│ │ └── wrike.ts # Wrike API client
│ ├── tools/
│ │ ├── tasks-tools.ts # Task management tools
│ │ ├── folders-tools.ts # Folder tools
│ │ ├── projects-tools.ts # Project tools
│ │ ├── spaces-tools.ts # Space tools
│ │ ├── contacts-tools.ts # Contact tools
│ │ ├── comments-tools.ts # Comment tools
│ │ ├── timelogs-tools.ts # Time tracking tools
│ │ ├── attachments-tools.ts # Attachment tools
│ │ ├── workflows-tools.ts # Workflow tools
│ │ ├── custom-fields-tools.ts # Custom field tools
│ │ ├── approvals-tools.ts # Approval tools
│ │ ├── groups-tools.ts # Group tools
│ │ ├── invitations-tools.ts # Invitation tools
│ │ └── webhooks-tools.ts # Webhook tools
│ ├── types/
│ │ └── wrike.ts # TypeScript type definitions
│ ├── ui/
│ │ └── react-app/ # 22 React MCP apps
│ ├── server.ts # MCP server implementation
│ └── main.ts # Entry point
├── package.json
├── tsconfig.json
└── README.md
src/
├── server.ts # MCP server setup
├── main.ts # Entry point
├── clients/
│ └── wrike.ts # Wrike API client (auth, rate limiting, pagination)
├── tools/ # MCP tool definitions
│ ├── tasks-tools.ts
│ ├── folders-tools.ts
│ ├── comments-tools.ts
│ ├── attachments-tools.ts
│ ├── timelogs-tools.ts
│ ├── contacts-tools.ts
│ ├── spaces-tools.ts
│ ├── groups-tools.ts
│ ├── workflows-tools.ts
│ ├── customfields-tools.ts
│ ├── approvals-tools.ts
│ ├── webhooks-tools.ts
│ └── misc-tools.ts
├── types/
│ └── index.ts # TypeScript type definitions
└── ui/
└── react-app/ # React apps (20 apps)
```
## API Coverage
## API Client Features
This server implements the complete Wrike API v4:
- **Authentication**: Bearer token authentication
- **Rate Limiting**: Automatic rate limit detection and retry
- **Error Handling**: Comprehensive error mapping
- **Pagination**: Helper methods for paginated responses
- **Batch Operations**: Efficient batch requests
- **Type Safety**: Full TypeScript type definitions
- ✅ Tasks & Subtasks
- ✅ Folders & Projects
- ✅ Spaces
- ✅ Contacts & Groups
- ✅ Comments
- ✅ Time Tracking
- ✅ Attachments
- ✅ Workflows & Custom Statuses
- ✅ Custom Fields
- ✅ Approvals
- ✅ Invitations
- ✅ Webhooks
- ✅ Dependencies
## Development
## Authentication
```bash
# Install dependencies
npm install
Supports both:
- **OAuth2 Bearer Token** - For user-specific access
- **Permanent API Token** - For service accounts and automation
# Build
npm run build
## Error Handling
# Build with apps
npm run build:apps
The server includes comprehensive error handling:
- API request failures
- Rate limiting
- Invalid parameters
- Network errors
- Authentication errors
# Development mode
npm run dev
## Contributing
# Run tests
npm test
```
Contributions welcome! Please ensure:
- TypeScript types are complete
- Tools follow MCP standards
- React apps maintain dark theme
- Error handling is comprehensive
## Requirements
- Node.js 18+
- Wrike account with API access
- Valid Wrike API token
## License
MIT
## Resources
## Support
- GitHub Issues: [Report bugs or request features]
- Documentation: [Wrike API Docs](https://developers.wrike.com/)
## Related
- [Wrike API Documentation](https://developers.wrike.com/api/v4/)
- [Model Context Protocol](https://modelcontextprotocol.io/)
- [MCP SDK](https://github.com/modelcontextprotocol/sdk)
- [Wrike API](https://developers.wrike.com/)
- [MCPEngine](https://github.com/BusyBee3333/mcpengine)

View File

@ -24,10 +24,13 @@
"author": "MCP Engine",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.4"
"@modelcontextprotocol/sdk": "^1.0.4",
"axios": "^1.7.9",
"form-data": "^4.0.1"
},
"devDependencies": {
"@types/node": "^22.10.6",
"@types/form-data": "^2.5.0",
"typescript": "^5.7.3"
},
"engines": {

View File

@ -88,7 +88,7 @@ export class WrikeClient {
} else if (typeof value === 'object') {
queryParts.push(`${key}=${JSON.stringify(value)}`);
} else {
queryParts.push(`${key}=${encodeURIComponent(value)}`);
queryParts.push(`${key}=${encodeURIComponent(String(value))}`);
}
});

View File

@ -1,15 +1,2 @@
#!/usr/bin/env node
import { WrikeServer } from './server.js';
async function main() {
try {
const server = new WrikeServer();
await server.run();
} catch (error) {
console.error('Failed to start Wrike MCP server:', error);
process.exit(1);
}
}
main();
import './server.js';

View File

@ -1,128 +1,109 @@
// Wrike MCP Server
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
ListToolsRequestSchema,
CallToolRequestSchema,
Tool,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { WrikeClient } from './clients/wrike.js';
import { registerTasksTools } from './tools/tasks-tools.js';
import { registerFoldersTools } from './tools/folders-tools.js';
import { registerProjectsTools } from './tools/projects-tools.js';
import { registerSpacesTools } from './tools/spaces-tools.js';
import { registerContactsTools } from './tools/contacts-tools.js';
import { registerCommentsTools } from './tools/comments-tools.js';
import { registerTimelogsTools } from './tools/timelogs-tools.js';
import { registerAttachmentsTools } from './tools/attachments-tools.js';
import { registerWorkflowsTools } from './tools/workflows-tools.js';
import { registerCustomFieldsTools } from './tools/custom-fields-tools.js';
import { registerApprovalsTools } from './tools/approvals-tools.js';
import { registerGroupsTools } from './tools/groups-tools.js';
import { registerInvitationsTools } from './tools/invitations-tools.js';
import { registerWebhooksTools } from './tools/webhooks-tools.js';
import { createTaskTools } from './tools/tasks-tools.js';
import { createFolderTools } from './tools/folders-tools.js';
import { createCommentTools } from './tools/comments-tools.js';
import { createAttachmentTools } from './tools/attachments-tools.js';
import { createTimelogTools } from './tools/timelogs-tools.js';
import { createContactTools } from './tools/contacts-tools.js';
import { createSpaceTools } from './tools/spaces-tools.js';
import { createGroupTools } from './tools/groups-tools.js';
import { createWorkflowTools } from './tools/workflows-tools.js';
import { createCustomFieldTools } from './tools/customfields-tools.js';
import { createApprovalTools } from './tools/approvals-tools.js';
import { createWebhookTools } from './tools/webhooks-tools.js';
import { createMiscTools } from './tools/misc-tools.js';
export class WrikeServer {
private server: Server;
private client: WrikeClient;
private tools: Map<string, any>;
const WRIKE_API_TOKEN = process.env.WRIKE_API_TOKEN;
constructor() {
this.server = new Server(
{
name: 'wrike-mcp-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
const apiToken = process.env.WRIKE_API_TOKEN;
if (!apiToken) {
throw new Error('WRIKE_API_TOKEN environment variable is required');
}
this.client = new WrikeClient({ apiToken });
this.tools = new Map();
this.setupHandlers();
this.registerAllTools();
}
private registerAllTools() {
const allTools = [
...registerTasksTools(this.client),
...registerFoldersTools(this.client),
...registerProjectsTools(this.client),
...registerSpacesTools(this.client),
...registerContactsTools(this.client),
...registerCommentsTools(this.client),
...registerTimelogsTools(this.client),
...registerAttachmentsTools(this.client),
...registerWorkflowsTools(this.client),
...registerCustomFieldsTools(this.client),
...registerApprovalsTools(this.client),
...registerGroupsTools(this.client),
...registerInvitationsTools(this.client),
...registerWebhooksTools(this.client),
];
for (const tool of allTools) {
this.tools.set(tool.name, tool);
}
}
private setupHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
const tools: Tool[] = Array.from(this.tools.values()).map((tool) => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema,
}));
return { tools };
});
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const tool = this.tools.get(request.params.name);
if (!tool) {
throw new Error(`Unknown tool: ${request.params.name}`);
}
try {
const result = await tool.handler(request.params.arguments || {});
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: JSON.stringify({ error: errorMessage }, null, 2),
},
],
isError: true,
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Wrike MCP server running on stdio');
}
if (!WRIKE_API_TOKEN) {
throw new Error('WRIKE_API_TOKEN environment variable is required');
}
// Initialize Wrike client
const client = new WrikeClient(WRIKE_API_TOKEN);
// Initialize MCP server
const server = new Server(
{
name: 'wrike-mcp-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// Collect all tools
const allTools = {
...createTaskTools(client),
...createFolderTools(client),
...createCommentTools(client),
...createAttachmentTools(client),
...createTimelogTools(client),
...createContactTools(client),
...createSpaceTools(client),
...createGroupTools(client),
...createWorkflowTools(client),
...createCustomFieldTools(client),
...createApprovalTools(client),
...createWebhookTools(client),
...createMiscTools(client),
};
// List tools handler
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: Object.values(allTools).map((tool) => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema,
})),
};
});
// Call tool handler
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const tool = allTools[request.params.name as keyof typeof allTools];
if (!tool) {
throw new Error(`Unknown tool: ${request.params.name}`);
}
try {
return await tool.handler(request.params.arguments as any || {});
} catch (error) {
if (error instanceof Error) {
return {
content: [
{
type: 'text',
text: `Error: ${error.message}`,
},
],
isError: true,
};
}
throw error;
}
});
// Start server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Wrike MCP Server running on stdio');
}
main().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});

View File

@ -1,129 +1,221 @@
// Wrike Approvals Tools
import type { WrikeClient } from '../clients/wrike.js';
import type { WrikeApproval } from '../types/index.js';
import { WrikeClient } from '../clients/wrike.js';
export function registerApprovalsTools(client: WrikeClient) {
return [
{
export function createApprovalTools(client: WrikeClient) {
return {
// List approvals
wrike_list_approvals: {
name: 'wrike_list_approvals',
description: 'List approvals',
description: 'List approvals with optional filters',
inputSchema: {
type: 'object',
properties: {
taskId: {
type: 'string',
description: 'Filter by task ID',
},
folderId: {
type: 'string',
description: 'Filter by folder ID',
taskId: { type: 'string', description: 'Filter by task ID' },
folderId: { type: 'string', description: 'Filter by folder ID' },
status: {
type: 'string',
enum: ['Pending', 'Approved', 'Rejected', 'Cancelled'],
description: 'Filter by status'
},
updatedDateStart: { type: 'string', description: 'Updated date range begin' },
updatedDateEnd: { type: 'string', description: 'Updated date range end' },
},
},
handler: async (args: any) => {
const approvals = await client.listApprovals(args.taskId, args.folderId);
return { approvals, count: approvals.length };
handler: async (params: Record<string, unknown>) => {
let endpoint = '/approvals';
if (params.taskId) {
endpoint = `/tasks/${params.taskId}/approvals`;
} else if (params.folderId) {
endpoint = `/folders/${params.folderId}/approvals`;
}
const queryParams: Record<string, unknown> = {};
if (params.status) queryParams.status = params.status;
if (params.updatedDateStart || params.updatedDateEnd) {
queryParams.updatedDate = {
start: params.updatedDateStart,
end: params.updatedDateEnd,
};
}
const response = await client.get<WrikeApproval>(endpoint, queryParams);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data, null, 2),
},
],
};
},
},
{
// Get approval by ID
wrike_get_approval: {
name: 'wrike_get_approval',
description: 'Get details of a specific approval',
description: 'Get a specific approval by ID',
inputSchema: {
type: 'object',
properties: {
approvalId: {
type: 'string',
description: 'Approval ID',
},
approvalId: { type: 'string', description: 'Approval ID (required)' },
},
required: ['approvalId'],
},
handler: async (args: any) => {
const approval = await client.getApproval(args.approvalId);
return { approval };
handler: async (params: { approvalId: string }) => {
const response = await client.get<WrikeApproval>(`/approvals/${params.approvalId}`);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
{
// Create approval
wrike_create_approval: {
name: 'wrike_create_approval',
description: 'Create a new approval request',
description: 'Create a new approval',
inputSchema: {
type: 'object',
properties: {
taskId: {
type: 'string',
description: 'Task ID',
},
title: {
type: 'string',
description: 'Approval title',
},
description: {
type: 'string',
description: 'Approval description',
},
approverIds: {
type: 'array',
items: { type: 'string' },
description: 'User IDs of approvers',
},
dueDate: {
type: 'string',
description: 'Due date (ISO 8601)',
},
title: { type: 'string', description: 'Approval title (required)' },
description: { type: 'string', description: 'Approval description' },
approvers: { type: 'array', items: { type: 'string' }, description: 'Approver user IDs (required)' },
dueDate: { type: 'string', description: 'Due date (YYYY-MM-DD)' },
taskIds: { type: 'array', items: { type: 'string' }, description: 'Related task IDs' },
folderIds: { type: 'array', items: { type: 'string' }, description: 'Related folder IDs' },
},
required: ['taskId', 'title', 'approverIds'],
required: ['title', 'approvers'],
},
handler: async (args: any) => {
const { taskId, ...approvalData } = args;
const approval = await client.createApproval(taskId, approvalData);
return { approval, message: 'Approval created successfully' };
handler: async (params: { title: string; approvers: string[]; [key: string]: unknown }) => {
const body: Record<string, unknown> = {
title: params.title,
approverIds: params.approvers,
};
if (params.description) body.description = params.description;
if (params.dueDate) body.dueDate = params.dueDate;
if (params.taskIds) body.taskIds = params.taskIds;
if (params.folderIds) body.folderIds = params.folderIds;
const response = await client.post<WrikeApproval>('/approvals', body);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
{
// Update approval
wrike_update_approval: {
name: 'wrike_update_approval',
description: 'Update an existing approval',
inputSchema: {
type: 'object',
properties: {
approvalId: {
type: 'string',
description: 'Approval ID',
},
status: {
type: 'string',
description: 'Approval status',
approvalId: { type: 'string', description: 'Approval ID (required)' },
title: { type: 'string', description: 'New approval title' },
description: { type: 'string', description: 'New approval description' },
status: {
type: 'string',
enum: ['Pending', 'Approved', 'Rejected', 'Cancelled'],
},
comment: {
type: 'string',
description: 'Decision comment',
description: 'New status'
},
},
required: ['approvalId'],
},
handler: async (args: any) => {
const { approvalId, ...updateData } = args;
const approval = await client.updateApproval(approvalId, updateData);
return { approval, message: 'Approval updated successfully' };
handler: async (params: { approvalId: string; [key: string]: unknown }) => {
const body: Record<string, unknown> = {};
if (params.title) body.title = params.title;
if (params.description !== undefined) body.description = params.description;
if (params.status) body.status = params.status;
const response = await client.put<WrikeApproval>(`/approvals/${params.approvalId}`, body);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
{
// Delete approval
wrike_delete_approval: {
name: 'wrike_delete_approval',
description: 'Delete an approval',
inputSchema: {
type: 'object',
properties: {
approvalId: {
type: 'string',
description: 'Approval ID',
},
approvalId: { type: 'string', description: 'Approval ID (required)' },
},
required: ['approvalId'],
},
handler: async (args: any) => {
await client.deleteApproval(args.approvalId);
return { message: 'Approval deleted successfully', approvalId: args.approvalId };
handler: async (params: { approvalId: string }) => {
const response = await client.delete<WrikeApproval>(`/approvals/${params.approvalId}`);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
];
// Submit approval decision
wrike_submit_approval_decision: {
name: 'wrike_submit_approval_decision',
description: 'Submit an approval decision (approve/reject)',
inputSchema: {
type: 'object',
properties: {
approvalId: { type: 'string', description: 'Approval ID (required)' },
decision: {
type: 'string',
enum: ['Approved', 'Rejected'],
description: 'Decision (required)'
},
comment: { type: 'string', description: 'Decision comment' },
},
required: ['approvalId', 'decision'],
},
handler: async (params: { approvalId: string; decision: string; comment?: string }) => {
const body: Record<string, unknown> = {
decision: params.decision,
};
if (params.comment) body.comment = params.comment;
const response = await client.post(`/approvals/${params.approvalId}/decisions`, body);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data, null, 2),
},
],
};
},
},
};
}

View File

@ -1,82 +1,98 @@
// Wrike Contacts Tools
import type { WrikeClient } from '../clients/wrike.js';
import type { WrikeContact } from '../types/index.js';
import { WrikeClient } from '../clients/wrike.js';
export function registerContactsTools(client: WrikeClient) {
return [
{
export function createContactTools(client: WrikeClient) {
return {
// List contacts
wrike_list_contacts: {
name: 'wrike_list_contacts',
description: 'List all contacts and users',
description: 'List all contacts/users in the account',
inputSchema: {
type: 'object',
properties: {
me: {
type: 'boolean',
description: 'Only return current user',
},
metadata: {
type: 'object',
description: 'Filter by metadata',
},
deleted: {
type: 'boolean',
description: 'Include deleted contacts',
},
me: { type: 'boolean', description: 'Get current user only' },
metadata: { type: 'string', description: 'Metadata filter (JSON)' },
deleted: { type: 'boolean', description: 'Include deleted users' },
fields: { type: 'array', items: { type: 'string' }, description: 'Additional fields' },
},
},
handler: async (args: any) => {
const contacts = await client.listContacts(args);
return { contacts, count: contacts.length };
handler: async (params: Record<string, unknown>) => {
const queryParams: Record<string, unknown> = {};
if (params.me !== undefined) queryParams.me = params.me;
if (params.metadata) queryParams.metadata = params.metadata;
if (params.deleted !== undefined) queryParams.deleted = params.deleted;
if (params.fields) queryParams.fields = params.fields;
const response = await client.get<WrikeContact>('/contacts', queryParams);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data, null, 2),
},
],
};
},
},
{
// Get contact by ID
wrike_get_contact: {
name: 'wrike_get_contact',
description: 'Get details of a specific contact',
description: 'Get a specific contact/user by ID',
inputSchema: {
type: 'object',
properties: {
contactId: {
type: 'string',
description: 'Contact ID',
},
contactId: { type: 'string', description: 'Contact ID (required)' },
fields: { type: 'array', items: { type: 'string' }, description: 'Additional fields' },
},
required: ['contactId'],
},
handler: async (args: any) => {
const contact = await client.getContact(args.contactId);
return { contact };
handler: async (params: { contactId: string; fields?: string[] }) => {
const response = await client.get<WrikeContact>(`/contacts/${params.contactId}`, {
fields: params.fields,
});
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
{
// Update contact
wrike_update_contact: {
name: 'wrike_update_contact',
description: 'Update contact information',
inputSchema: {
type: 'object',
properties: {
contactId: {
type: 'string',
description: 'Contact ID',
},
profile: {
type: 'object',
description: 'Updated profile information',
properties: {
role: { type: 'string' },
external: { type: 'boolean' },
},
},
metadata: {
type: 'array',
description: 'Updated metadata',
},
contactId: { type: 'string', description: 'Contact ID (required)' },
metadata: { type: 'array', items: { type: 'object' }, description: 'Metadata key-value pairs' },
},
required: ['contactId'],
},
handler: async (args: any) => {
const { contactId, ...updateData } = args;
const contact = await client.updateContact(contactId, updateData);
return { contact, message: 'Contact updated successfully' };
handler: async (params: { contactId: string; metadata?: Array<{ key: string; value: string }> }) => {
const body: Record<string, unknown> = {};
if (params.metadata) body.metadata = params.metadata;
const response = await client.put<WrikeContact>(`/contacts/${params.contactId}`, body);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
];
};
}

View File

@ -1,97 +0,0 @@
// Wrike Custom Fields Tools
import { WrikeClient } from '../clients/wrike.js';
export function registerCustomFieldsTools(client: WrikeClient) {
return [
{
name: 'wrike_list_custom_fields',
description: 'List all custom fields',
inputSchema: {
type: 'object',
properties: {},
},
handler: async () => {
const customFields = await client.listCustomFields();
return { customFields, count: customFields.length };
},
},
{
name: 'wrike_get_custom_field',
description: 'Get details of a specific custom field',
inputSchema: {
type: 'object',
properties: {
customFieldId: {
type: 'string',
description: 'Custom field ID',
},
},
required: ['customFieldId'],
},
handler: async (args: any) => {
const customField = await client.getCustomField(args.customFieldId);
return { customField };
},
},
{
name: 'wrike_create_custom_field',
description: 'Create a new custom field',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Custom field title',
},
type: {
type: 'string',
description: 'Field type',
enum: ['Text', 'DropDown', 'Numeric', 'Currency', 'Percentage', 'Date', 'Duration', 'Checkbox', 'Contacts', 'Multiple'],
},
sharedIds: {
type: 'array',
items: { type: 'string' },
description: 'Shared folder/space IDs',
},
settings: {
type: 'object',
description: 'Field-specific settings',
},
},
required: ['title', 'type'],
},
handler: async (args: any) => {
const customField = await client.createCustomField(args);
return { customField, message: 'Custom field created successfully' };
},
},
{
name: 'wrike_update_custom_field',
description: 'Update an existing custom field',
inputSchema: {
type: 'object',
properties: {
customFieldId: {
type: 'string',
description: 'Custom field ID',
},
title: {
type: 'string',
description: 'Updated title',
},
settings: {
type: 'object',
description: 'Updated settings',
},
},
required: ['customFieldId'],
},
handler: async (args: any) => {
const { customFieldId, ...updateData } = args;
const customField = await client.updateCustomField(customFieldId, updateData);
return { customField, message: 'Custom field updated successfully' };
},
},
];
}

View File

@ -0,0 +1,170 @@
import type { WrikeClient } from '../clients/wrike.js';
import type { WrikeCustomFieldDefinition } from '../types/index.js';
export function createCustomFieldTools(client: WrikeClient) {
return {
// List custom fields
wrike_list_custom_fields: {
name: 'wrike_list_custom_fields',
description: 'List all custom field definitions',
inputSchema: {
type: 'object',
properties: {},
},
handler: async () => {
const response = await client.get<WrikeCustomFieldDefinition>('/customfields');
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data, null, 2),
},
],
};
},
},
// Get custom field by ID
wrike_get_custom_field: {
name: 'wrike_get_custom_field',
description: 'Get a specific custom field definition by ID',
inputSchema: {
type: 'object',
properties: {
customFieldId: { type: 'string', description: 'Custom field ID (required)' },
},
required: ['customFieldId'],
},
handler: async (params: { customFieldId: string }) => {
const response = await client.get<WrikeCustomFieldDefinition>(`/customfields/${params.customFieldId}`);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
// Create custom field
wrike_create_custom_field: {
name: 'wrike_create_custom_field',
description: 'Create a new custom field',
inputSchema: {
type: 'object',
properties: {
title: { type: 'string', description: 'Field title (required)' },
type: {
type: 'string',
enum: ['Text', 'DropDown', 'Numeric', 'Currency', 'Percentage', 'Date', 'Duration', 'Checkbox', 'Contacts', 'Multiple'],
description: 'Field type (required)'
},
shareds: { type: 'array', items: { type: 'string' }, description: 'Shared folder IDs' },
values: { type: 'array', items: { type: 'string' }, description: 'Dropdown values (for DropDown/Multiple types)' },
currency: { type: 'string', description: 'Currency code (for Currency type)' },
decimalPlaces: { type: 'number', description: 'Decimal places (for Numeric/Currency/Percentage)' },
allowOtherValues: { type: 'boolean', description: 'Allow other values (for DropDown/Multiple)' },
},
required: ['title', 'type'],
},
handler: async (params: { title: string; type: string; [key: string]: unknown }) => {
const body: Record<string, unknown> = {
title: params.title,
type: params.type,
};
if (params.shareds) body.shareds = params.shareds;
const settings: Record<string, unknown> = {};
if (params.values) settings.values = params.values;
if (params.currency) settings.currency = params.currency;
if (params.decimalPlaces !== undefined) settings.decimalPlaces = params.decimalPlaces;
if (params.allowOtherValues !== undefined) settings.allowOtherValues = params.allowOtherValues;
if (Object.keys(settings).length > 0) {
body.settings = settings;
}
const response = await client.post<WrikeCustomFieldDefinition>('/customfields', body);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
// Update custom field
wrike_update_custom_field: {
name: 'wrike_update_custom_field',
description: 'Update an existing custom field',
inputSchema: {
type: 'object',
properties: {
customFieldId: { type: 'string', description: 'Custom field ID (required)' },
title: { type: 'string', description: 'New field title' },
type: {
type: 'string',
enum: ['Text', 'DropDown', 'Numeric', 'Currency', 'Percentage', 'Date', 'Duration', 'Checkbox', 'Contacts', 'Multiple'],
description: 'New field type'
},
addShareds: { type: 'array', items: { type: 'string' }, description: 'Folder IDs to add to shared' },
removeShareds: { type: 'array', items: { type: 'string' }, description: 'Folder IDs to remove from shared' },
},
required: ['customFieldId'],
},
handler: async (params: { customFieldId: string; [key: string]: unknown }) => {
const body: Record<string, unknown> = {};
if (params.title) body.title = params.title;
if (params.type) body.type = params.type;
if (params.addShareds) body.addShareds = params.addShareds;
if (params.removeShareds) body.removeShareds = params.removeShareds;
const response = await client.put<WrikeCustomFieldDefinition>(`/customfields/${params.customFieldId}`, body);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
// Delete custom field
wrike_delete_custom_field: {
name: 'wrike_delete_custom_field',
description: 'Delete a custom field',
inputSchema: {
type: 'object',
properties: {
customFieldId: { type: 'string', description: 'Custom field ID (required)' },
},
required: ['customFieldId'],
},
handler: async (params: { customFieldId: string }) => {
const response = await client.delete<WrikeCustomFieldDefinition>(`/customfields/${params.customFieldId}`);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
};
}

View File

@ -1,117 +1,164 @@
// Wrike Groups Tools
import type { WrikeClient } from '../clients/wrike.js';
import type { WrikeGroup } from '../types/index.js';
import { WrikeClient } from '../clients/wrike.js';
export function registerGroupsTools(client: WrikeClient) {
return [
{
export function createGroupTools(client: WrikeClient) {
return {
// List groups
wrike_list_groups: {
name: 'wrike_list_groups',
description: 'List all groups',
inputSchema: {
type: 'object',
properties: {},
},
handler: async () => {
const groups = await client.listGroups();
return { groups, count: groups.length };
},
},
{
name: 'wrike_get_group',
description: 'Get details of a specific group',
description: 'List all groups in the account',
inputSchema: {
type: 'object',
properties: {
groupId: {
type: 'string',
description: 'Group ID',
},
metadata: { type: 'string', description: 'Metadata filter (JSON)' },
fields: { type: 'array', items: { type: 'string' }, description: 'Additional fields' },
},
},
handler: async (params: Record<string, unknown>) => {
const queryParams: Record<string, unknown> = {};
if (params.metadata) queryParams.metadata = params.metadata;
if (params.fields) queryParams.fields = params.fields;
const response = await client.get<WrikeGroup>('/groups', queryParams);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data, null, 2),
},
],
};
},
},
// Get group by ID
wrike_get_group: {
name: 'wrike_get_group',
description: 'Get a specific group by ID',
inputSchema: {
type: 'object',
properties: {
groupId: { type: 'string', description: 'Group ID (required)' },
fields: { type: 'array', items: { type: 'string' }, description: 'Additional fields' },
},
required: ['groupId'],
},
handler: async (args: any) => {
const group = await client.getGroup(args.groupId);
return { group };
handler: async (params: { groupId: string; fields?: string[] }) => {
const response = await client.get<WrikeGroup>(`/groups/${params.groupId}`, {
fields: params.fields,
});
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
{
// Create group
wrike_create_group: {
name: 'wrike_create_group',
description: 'Create a new group',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Group title',
},
memberIds: {
type: 'array',
items: { type: 'string' },
description: 'Member user IDs',
},
parentIds: {
type: 'array',
items: { type: 'string' },
description: 'Parent group IDs',
},
title: { type: 'string', description: 'Group title (required)' },
members: { type: 'array', items: { type: 'string' }, description: 'Member user IDs' },
parent: { type: 'string', description: 'Parent group ID' },
metadata: { type: 'array', items: { type: 'object' }, description: 'Metadata key-value pairs' },
},
required: ['title'],
},
handler: async (args: any) => {
const group = await client.createGroup(args);
return { group, message: 'Group created successfully' };
handler: async (params: { title: string; [key: string]: unknown }) => {
const body: Record<string, unknown> = {
title: params.title,
};
if (params.members) body.members = params.members;
if (params.parent) body.parent = params.parent;
if (params.metadata) body.metadata = params.metadata;
const response = await client.post<WrikeGroup>('/groups', body);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
{
// Update group
wrike_update_group: {
name: 'wrike_update_group',
description: 'Update an existing group',
inputSchema: {
type: 'object',
properties: {
groupId: {
type: 'string',
description: 'Group ID',
},
title: {
type: 'string',
description: 'Updated group title',
},
addMembers: {
type: 'array',
items: { type: 'string' },
description: 'Member IDs to add',
},
removeMembers: {
type: 'array',
items: { type: 'string' },
description: 'Member IDs to remove',
},
groupId: { type: 'string', description: 'Group ID (required)' },
title: { type: 'string', description: 'New group title' },
members: { type: 'array', items: { type: 'string' }, description: 'New member list' },
parent: { type: 'string', description: 'New parent group ID' },
metadata: { type: 'array', items: { type: 'object' }, description: 'Metadata key-value pairs' },
},
required: ['groupId'],
},
handler: async (args: any) => {
const { groupId, ...updateData } = args;
const group = await client.updateGroup(groupId, updateData);
return { group, message: 'Group updated successfully' };
handler: async (params: { groupId: string; [key: string]: unknown }) => {
const body: Record<string, unknown> = {};
if (params.title) body.title = params.title;
if (params.members) body.members = params.members;
if (params.parent) body.parent = params.parent;
if (params.metadata) body.metadata = params.metadata;
const response = await client.put<WrikeGroup>(`/groups/${params.groupId}`, body);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
{
// Delete group
wrike_delete_group: {
name: 'wrike_delete_group',
description: 'Delete a group',
inputSchema: {
type: 'object',
properties: {
groupId: {
type: 'string',
description: 'Group ID',
},
groupId: { type: 'string', description: 'Group ID (required)' },
test: { type: 'boolean', description: 'Test mode (check dependencies)' },
},
required: ['groupId'],
},
handler: async (args: any) => {
await client.deleteGroup(args.groupId);
return { message: 'Group deleted successfully', groupId: args.groupId };
handler: async (params: { groupId: string; test?: boolean }) => {
const response = await client.delete<WrikeGroup>(`/groups/${params.groupId}`, {
test: params.test,
});
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
];
};
}

View File

@ -1,99 +0,0 @@
// Wrike Invitations Tools
import { WrikeClient } from '../clients/wrike.js';
export function registerInvitationsTools(client: WrikeClient) {
return [
{
name: 'wrike_list_invitations',
description: 'List all invitations',
inputSchema: {
type: 'object',
properties: {},
},
handler: async () => {
const invitations = await client.listInvitations();
return { invitations, count: invitations.length };
},
},
{
name: 'wrike_create_invitation',
description: 'Create a new user invitation',
inputSchema: {
type: 'object',
properties: {
email: {
type: 'string',
description: 'Email address of the invitee',
},
firstName: {
type: 'string',
description: 'First name',
},
lastName: {
type: 'string',
description: 'Last name',
},
role: {
type: 'string',
description: 'User role',
},
external: {
type: 'boolean',
description: 'External user flag',
},
},
required: ['email', 'firstName', 'lastName'],
},
handler: async (args: any) => {
const invitation = await client.createInvitation(args);
return { invitation, message: 'Invitation created successfully' };
},
},
{
name: 'wrike_update_invitation',
description: 'Update an existing invitation',
inputSchema: {
type: 'object',
properties: {
invitationId: {
type: 'string',
description: 'Invitation ID',
},
resend: {
type: 'boolean',
description: 'Resend the invitation email',
},
role: {
type: 'string',
description: 'Updated role',
},
},
required: ['invitationId'],
},
handler: async (args: any) => {
const { invitationId, ...updateData } = args;
const invitation = await client.updateInvitation(invitationId, updateData);
return { invitation, message: 'Invitation updated successfully' };
},
},
{
name: 'wrike_delete_invitation',
description: 'Cancel an invitation',
inputSchema: {
type: 'object',
properties: {
invitationId: {
type: 'string',
description: 'Invitation ID',
},
},
required: ['invitationId'],
},
handler: async (args: any) => {
await client.deleteInvitation(args.invitationId);
return { message: 'Invitation cancelled successfully', invitationId: args.invitationId };
},
},
];
}

View File

@ -0,0 +1,415 @@
import type { WrikeClient } from '../clients/wrike.js';
import type {
WrikeWorkSchedule,
WrikeDataExport,
WrikeAuditLogEntry,
WrikeBlueprint,
WrikeAccount,
WrikeInvitation
} from '../types/index.js';
export function createMiscTools(client: WrikeClient) {
return {
// ===== WORK SCHEDULES =====
wrike_list_work_schedules: {
name: 'wrike_list_work_schedules',
description: 'List all work schedules',
inputSchema: {
type: 'object',
properties: {},
},
handler: async () => {
const response = await client.get<WrikeWorkSchedule>('/workschedules');
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data, null, 2),
},
],
};
},
},
wrike_get_work_schedule: {
name: 'wrike_get_work_schedule',
description: 'Get a specific work schedule by ID',
inputSchema: {
type: 'object',
properties: {
scheduleId: { type: 'string', description: 'Work schedule ID (required)' },
},
required: ['scheduleId'],
},
handler: async (params: { scheduleId: string }) => {
const response = await client.get<WrikeWorkSchedule>(`/workschedules/${params.scheduleId}`);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
// ===== DATA EXPORT =====
wrike_start_data_export: {
name: 'wrike_start_data_export',
description: 'Start a data export job',
inputSchema: {
type: 'object',
properties: {
resources: {
type: 'array',
items: { type: 'string' },
description: 'Resources to export (Tasks, Folders, etc.)'
},
},
},
handler: async (params: { resources?: string[] }) => {
const body: Record<string, unknown> = {};
if (params.resources) body.resources = params.resources;
const response = await client.post<WrikeDataExport>('/data_export', body);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
wrike_get_data_export_status: {
name: 'wrike_get_data_export_status',
description: 'Get status of a data export job',
inputSchema: {
type: 'object',
properties: {
exportId: { type: 'string', description: 'Export ID (required)' },
},
required: ['exportId'],
},
handler: async (params: { exportId: string }) => {
const response = await client.get<WrikeDataExport>(`/data_export/${params.exportId}`);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
// ===== AUDIT LOG =====
wrike_get_audit_log: {
name: 'wrike_get_audit_log',
description: 'Get audit log entries',
inputSchema: {
type: 'object',
properties: {
eventDateStart: { type: 'string', description: 'Event date range begin (ISO 8601)' },
eventDateEnd: { type: 'string', description: 'Event date range end (ISO 8601)' },
operations: { type: 'array', items: { type: 'string' }, description: 'Filter by operations' },
userIds: { type: 'array', items: { type: 'string' }, description: 'Filter by user IDs' },
limit: { type: 'number', description: 'Maximum entries to return' },
},
},
handler: async (params: Record<string, unknown>) => {
const queryParams: Record<string, unknown> = {};
if (params.operations) queryParams.operations = params.operations;
if (params.userIds) queryParams.userIds = params.userIds;
if (params.limit) queryParams.limit = params.limit;
if (params.eventDateStart || params.eventDateEnd) {
queryParams.eventDate = {
start: params.eventDateStart,
end: params.eventDateEnd,
};
}
const response = await client.get<WrikeAuditLogEntry>('/audit_log', queryParams);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data, null, 2),
},
],
};
},
},
// ===== BLUEPRINTS / TEMPLATES =====
wrike_list_blueprints: {
name: 'wrike_list_blueprints',
description: 'List all blueprints/templates',
inputSchema: {
type: 'object',
properties: {},
},
handler: async () => {
const response = await client.get<WrikeBlueprint>('/blueprints');
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data, null, 2),
},
],
};
},
},
wrike_get_blueprint: {
name: 'wrike_get_blueprint',
description: 'Get a specific blueprint by ID',
inputSchema: {
type: 'object',
properties: {
blueprintId: { type: 'string', description: 'Blueprint ID (required)' },
},
required: ['blueprintId'],
},
handler: async (params: { blueprintId: string }) => {
const response = await client.get<WrikeBlueprint>(`/blueprints/${params.blueprintId}`);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
wrike_launch_blueprint: {
name: 'wrike_launch_blueprint',
description: 'Launch a blueprint to create a project from template',
inputSchema: {
type: 'object',
properties: {
blueprintId: { type: 'string', description: 'Blueprint ID (required)' },
parent: { type: 'string', description: 'Parent folder ID (required)' },
title: { type: 'string', description: 'New project title' },
},
required: ['blueprintId', 'parent'],
},
handler: async (params: { blueprintId: string; parent: string; title?: string }) => {
const body: Record<string, unknown> = {
parent: params.parent,
};
if (params.title) body.title = params.title;
const response = await client.post(`/blueprints/${params.blueprintId}/launch`, body);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data, null, 2),
},
],
};
},
},
// ===== ACCOUNT & VERSION =====
wrike_get_account: {
name: 'wrike_get_account',
description: 'Get account information',
inputSchema: {
type: 'object',
properties: {
fields: { type: 'array', items: { type: 'string' }, description: 'Additional fields' },
},
},
handler: async (params: { fields?: string[] }) => {
const response = await client.get<WrikeAccount>('/account', {
fields: params.fields,
});
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
wrike_get_version: {
name: 'wrike_get_version',
description: 'Get Wrike API version',
inputSchema: {
type: 'object',
properties: {},
},
handler: async () => {
const response = await client.get('/version');
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data, null, 2),
},
],
};
},
},
// ===== INVITATIONS =====
wrike_list_invitations: {
name: 'wrike_list_invitations',
description: 'List all pending invitations',
inputSchema: {
type: 'object',
properties: {},
},
handler: async () => {
const response = await client.get<WrikeInvitation>('/invitations');
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data, null, 2),
},
],
};
},
},
wrike_invite_user: {
name: 'wrike_invite_user',
description: 'Invite a user to the account',
inputSchema: {
type: 'object',
properties: {
email: { type: 'string', description: 'User email (required)' },
firstName: { type: 'string', description: 'First name' },
lastName: { type: 'string', description: 'Last name' },
role: { type: 'string', description: 'User role' },
external: { type: 'boolean', description: 'External user' },
},
required: ['email'],
},
handler: async (params: { email: string; [key: string]: unknown }) => {
const body: Record<string, unknown> = {
email: params.email,
};
if (params.firstName) body.firstName = params.firstName;
if (params.lastName) body.lastName = params.lastName;
if (params.role) body.role = params.role;
if (params.external !== undefined) body.external = params.external;
const response = await client.post<WrikeInvitation>('/invitations', body);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
wrike_delete_invitation: {
name: 'wrike_delete_invitation',
description: 'Cancel/delete an invitation',
inputSchema: {
type: 'object',
properties: {
invitationId: { type: 'string', description: 'Invitation ID (required)' },
},
required: ['invitationId'],
},
handler: async (params: { invitationId: string }) => {
const response = await client.delete<WrikeInvitation>(`/invitations/${params.invitationId}`);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
// ===== COLORS =====
wrike_list_colors: {
name: 'wrike_list_colors',
description: 'List all available colors for folders and tasks',
inputSchema: {
type: 'object',
properties: {},
},
handler: async () => {
const response = await client.get('/colors');
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data, null, 2),
},
],
};
},
},
// ===== IDS HELPER =====
wrike_query_ids: {
name: 'wrike_query_ids',
description: 'Convert permalinks to IDs or vice versa',
inputSchema: {
type: 'object',
properties: {
ids: { type: 'array', items: { type: 'string' }, description: 'IDs to query' },
permalinks: { type: 'array', items: { type: 'string' }, description: 'Permalinks to query' },
},
},
handler: async (params: { ids?: string[]; permalinks?: string[] }) => {
const queryParams: Record<string, unknown> = {};
if (params.ids) queryParams.ids = params.ids;
if (params.permalinks) queryParams.permalinks = params.permalinks;
const response = await client.get('/ids', queryParams);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data, null, 2),
},
],
};
},
},
};
}

View File

@ -1,197 +0,0 @@
// Wrike Projects Tools
import { WrikeClient } from '../clients/wrike.js';
export function registerProjectsTools(client: WrikeClient) {
return [
{
name: 'wrike_list_projects',
description: 'List all projects',
inputSchema: {
type: 'object',
properties: {
descendants: {
type: 'boolean',
description: 'Include descendant projects',
},
deleted: {
type: 'boolean',
description: 'Include deleted projects',
},
updatedDate: {
type: 'string',
description: 'Filter by updated date',
},
},
},
handler: async (args: any) => {
const folders = await client.listFolders({ ...args, project: true });
const projects = folders.filter(f => f.project);
return { projects, count: projects.length };
},
},
{
name: 'wrike_get_project',
description: 'Get details of a specific project',
inputSchema: {
type: 'object',
properties: {
projectId: {
type: 'string',
description: 'Project ID (folder ID)',
},
},
required: ['projectId'],
},
handler: async (args: any) => {
const project = await client.getFolder(args.projectId);
return { project };
},
},
{
name: 'wrike_create_project',
description: 'Create a new project',
inputSchema: {
type: 'object',
properties: {
parentFolderId: {
type: 'string',
description: 'Parent folder ID',
},
title: {
type: 'string',
description: 'Project title',
},
description: {
type: 'string',
description: 'Project description',
},
ownerIds: {
type: 'array',
items: { type: 'string' },
description: 'Project owner user IDs',
},
status: {
type: 'string',
description: 'Project status',
enum: ['Green', 'Yellow', 'Red', 'Completed', 'OnHold', 'Cancelled'],
},
startDate: {
type: 'string',
description: 'Project start date (ISO 8601)',
},
endDate: {
type: 'string',
description: 'Project end date (ISO 8601)',
},
},
required: ['parentFolderId', 'title'],
},
handler: async (args: any) => {
const { parentFolderId, title, description, ownerIds, status, startDate, endDate } = args;
const project = await client.createFolder(parentFolderId, {
title,
description,
project: {
ownerIds,
status,
startDate,
endDate,
},
});
return { project, message: 'Project created successfully' };
},
},
{
name: 'wrike_update_project',
description: 'Update an existing project',
inputSchema: {
type: 'object',
properties: {
projectId: {
type: 'string',
description: 'Project ID',
},
title: {
type: 'string',
description: 'New project title',
},
description: {
type: 'string',
description: 'New project description',
},
ownerIds: {
type: 'array',
items: { type: 'string' },
description: 'Updated owner IDs',
},
status: {
type: 'string',
description: 'Updated project status',
enum: ['Green', 'Yellow', 'Red', 'Completed', 'OnHold', 'Cancelled'],
},
startDate: {
type: 'string',
description: 'Updated start date',
},
endDate: {
type: 'string',
description: 'Updated end date',
},
},
required: ['projectId'],
},
handler: async (args: any) => {
const { projectId, title, description, ...projectFields } = args;
const updateData: any = {};
if (title) updateData.title = title;
if (description) updateData.description = description;
if (Object.keys(projectFields).length > 0) {
updateData.project = projectFields;
}
const project = await client.updateFolder(projectId, updateData);
return { project, message: 'Project updated successfully' };
},
},
{
name: 'wrike_delete_project',
description: 'Delete a project',
inputSchema: {
type: 'object',
properties: {
projectId: {
type: 'string',
description: 'Project ID',
},
},
required: ['projectId'],
},
handler: async (args: any) => {
await client.deleteFolder(args.projectId);
return { message: 'Project deleted successfully', projectId: args.projectId };
},
},
{
name: 'wrike_list_project_tasks',
description: 'List all tasks in a project',
inputSchema: {
type: 'object',
properties: {
projectId: {
type: 'string',
description: 'Project ID',
},
descendants: {
type: 'boolean',
description: 'Include tasks from descendant folders',
},
},
required: ['projectId'],
},
handler: async (args: any) => {
const tasks = await client.listTasks(args.projectId, { descendants: args.descendants });
return { tasks, count: tasks.length };
},
},
];
}

View File

@ -1,119 +1,156 @@
// Wrike Spaces Tools
import type { WrikeClient } from '../clients/wrike.js';
import type { WrikeSpace } from '../types/index.js';
import { WrikeClient } from '../clients/wrike.js';
export function registerSpacesTools(client: WrikeClient) {
return [
{
export function createSpaceTools(client: WrikeClient) {
return {
// List spaces
wrike_list_spaces: {
name: 'wrike_list_spaces',
description: 'List all accessible spaces',
inputSchema: {
type: 'object',
properties: {},
},
handler: async () => {
const spaces = await client.listSpaces();
return { spaces, count: spaces.length };
},
},
{
name: 'wrike_get_space',
description: 'Get details of a specific space',
description: 'List all spaces in the account',
inputSchema: {
type: 'object',
properties: {
spaceId: {
type: 'string',
description: 'Space ID',
},
withArchived: { type: 'boolean', description: 'Include archived spaces' },
},
},
handler: async (params: { withArchived?: boolean }) => {
const response = await client.get<WrikeSpace>('/spaces', {
withArchived: params.withArchived,
});
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data, null, 2),
},
],
};
},
},
// Get space by ID
wrike_get_space: {
name: 'wrike_get_space',
description: 'Get a specific space by ID',
inputSchema: {
type: 'object',
properties: {
spaceId: { type: 'string', description: 'Space ID (required)' },
},
required: ['spaceId'],
},
handler: async (args: any) => {
const space = await client.getSpace(args.spaceId);
return { space };
handler: async (params: { spaceId: string }) => {
const response = await client.get<WrikeSpace>(`/spaces/${params.spaceId}`);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
{
// Create space
wrike_create_space: {
name: 'wrike_create_space',
description: 'Create a new space',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Space title',
},
accessType: {
type: 'string',
description: 'Space access type',
title: { type: 'string', description: 'Space title (required)' },
description: { type: 'string', description: 'Space description' },
members: { type: 'array', items: { type: 'string' }, description: 'Member user IDs' },
accessType: {
type: 'string',
enum: ['Personal', 'Private', 'Public'],
},
defaultProjectWorkflowId: {
type: 'string',
description: 'Default project workflow ID',
},
defaultTaskWorkflowId: {
type: 'string',
description: 'Default task workflow ID',
description: 'Access type'
},
},
required: ['title'],
},
handler: async (args: any) => {
const space = await client.createSpace(args);
return { space, message: 'Space created successfully' };
handler: async (params: { title: string; [key: string]: unknown }) => {
const body: Record<string, unknown> = {
title: params.title,
};
if (params.description) body.description = params.description;
if (params.members) body.members = params.members;
if (params.accessType) body.accessType = params.accessType;
const response = await client.post<WrikeSpace>('/spaces', body);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
{
// Update space
wrike_update_space: {
name: 'wrike_update_space',
description: 'Update an existing space',
inputSchema: {
type: 'object',
properties: {
spaceId: {
type: 'string',
description: 'Space ID',
},
title: {
type: 'string',
description: 'New space title',
},
accessType: {
type: 'string',
description: 'New access type',
enum: ['Personal', 'Private', 'Public'],
},
archived: {
type: 'boolean',
description: 'Archive status',
},
spaceId: { type: 'string', description: 'Space ID (required)' },
title: { type: 'string', description: 'New space title' },
description: { type: 'string', description: 'New space description' },
archived: { type: 'boolean', description: 'Archive/unarchive the space' },
},
required: ['spaceId'],
},
handler: async (args: any) => {
const { spaceId, ...updateData } = args;
const space = await client.updateSpace(spaceId, updateData);
return { space, message: 'Space updated successfully' };
handler: async (params: { spaceId: string; [key: string]: unknown }) => {
const body: Record<string, unknown> = {};
if (params.title) body.title = params.title;
if (params.description !== undefined) body.description = params.description;
if (params.archived !== undefined) body.archived = params.archived;
const response = await client.put<WrikeSpace>(`/spaces/${params.spaceId}`, body);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
{
// Delete space
wrike_delete_space: {
name: 'wrike_delete_space',
description: 'Delete a space',
inputSchema: {
type: 'object',
properties: {
spaceId: {
type: 'string',
description: 'Space ID',
},
spaceId: { type: 'string', description: 'Space ID (required)' },
},
required: ['spaceId'],
},
handler: async (args: any) => {
await client.deleteSpace(args.spaceId);
return { message: 'Space deleted successfully', spaceId: args.spaceId };
handler: async (params: { spaceId: string }) => {
const response = await client.delete<WrikeSpace>(`/spaces/${params.spaceId}`);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
];
};
}

View File

@ -1,143 +1,228 @@
// Wrike Timelogs Tools
import type { WrikeClient } from '../clients/wrike.js';
import type { WrikeTimelog, CreateTimelogRequest } from '../types/index.js';
import { WrikeClient } from '../clients/wrike.js';
export function registerTimelogsTools(client: WrikeClient) {
return [
{
export function createTimelogTools(client: WrikeClient) {
return {
// List timelogs
wrike_list_timelogs: {
name: 'wrike_list_timelogs',
description: 'List time logs',
description: 'List time logs with optional filters',
inputSchema: {
type: 'object',
properties: {
taskId: {
type: 'string',
description: 'Filter by task ID',
},
contactId: {
type: 'string',
description: 'Filter by contact ID',
},
categoryId: {
type: 'string',
description: 'Filter by category ID',
},
trackedDate: {
type: 'object',
description: 'Filter by tracked date range',
properties: {
start: { type: 'string' },
end: { type: 'string' },
},
},
taskId: { type: 'string', description: 'Filter by task ID' },
folderId: { type: 'string', description: 'Filter by folder ID' },
contactId: { type: 'string', description: 'Filter by contact ID' },
categoryId: { type: 'string', description: 'Filter by category ID' },
descendants: { type: 'boolean', description: 'Include timelogs from subfolders' },
trackedDateStart: { type: 'string', description: 'Tracked date range begin (YYYY-MM-DD)' },
trackedDateEnd: { type: 'string', description: 'Tracked date range end (YYYY-MM-DD)' },
createdDateStart: { type: 'string', description: 'Created date range begin' },
createdDateEnd: { type: 'string', description: 'Created date range end' },
updatedDateStart: { type: 'string', description: 'Updated date range begin' },
updatedDateEnd: { type: 'string', description: 'Updated date range end' },
me: { type: 'boolean', description: 'Only my timelogs' },
billable: { type: 'boolean', description: 'Filter by billable status' },
},
},
handler: async (args: any) => {
const timelogs = await client.listTimelogs(args);
return { timelogs, count: timelogs.length };
handler: async (params: Record<string, unknown>) => {
let endpoint = '/timelogs';
if (params.taskId) {
endpoint = `/tasks/${params.taskId}/timelogs`;
} else if (params.folderId) {
endpoint = `/folders/${params.folderId}/timelogs`;
} else if (params.contactId) {
endpoint = `/contacts/${params.contactId}/timelogs`;
}
const queryParams: Record<string, unknown> = {};
if (params.descendants !== undefined) queryParams.descendants = params.descendants;
if (params.categoryId) queryParams.categoryId = params.categoryId;
if (params.me !== undefined) queryParams.me = params.me;
if (params.billable !== undefined) queryParams.billable = params.billable;
if (params.trackedDateStart || params.trackedDateEnd) {
queryParams.trackedDate = {
start: params.trackedDateStart,
end: params.trackedDateEnd,
};
}
if (params.createdDateStart || params.createdDateEnd) {
queryParams.createdDate = {
start: params.createdDateStart,
end: params.createdDateEnd,
};
}
if (params.updatedDateStart || params.updatedDateEnd) {
queryParams.updatedDate = {
start: params.updatedDateStart,
end: params.updatedDateEnd,
};
}
const response = await client.get<WrikeTimelog>(endpoint, queryParams);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data, null, 2),
},
],
};
},
},
{
// Get timelog by ID
wrike_get_timelog: {
name: 'wrike_get_timelog',
description: 'Get details of a specific timelog',
description: 'Get a specific timelog by ID',
inputSchema: {
type: 'object',
properties: {
timelogId: {
type: 'string',
description: 'Timelog ID',
},
timelogId: { type: 'string', description: 'Timelog ID (required)' },
},
required: ['timelogId'],
},
handler: async (args: any) => {
const timelog = await client.getTimelog(args.timelogId);
return { timelog };
handler: async (params: { timelogId: string }) => {
const response = await client.get<WrikeTimelog>(`/timelogs/${params.timelogId}`);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
{
// Create timelog
wrike_create_timelog: {
name: 'wrike_create_timelog',
description: 'Create a new time log entry',
inputSchema: {
type: 'object',
properties: {
taskId: {
type: 'string',
description: 'Task ID',
},
hours: {
type: 'number',
description: 'Hours logged',
},
trackedDate: {
type: 'string',
description: 'Date tracked (ISO 8601)',
},
comment: {
type: 'string',
description: 'Timelog comment',
},
categoryId: {
type: 'string',
description: 'Category ID',
},
taskId: { type: 'string', description: 'Task ID (required)' },
hours: { type: 'number', description: 'Hours logged (required)' },
trackedDate: { type: 'string', description: 'Date tracked (YYYY-MM-DD, required)' },
comment: { type: 'string', description: 'Comment/description' },
categoryId: { type: 'string', description: 'Time category ID' },
billable: { type: 'boolean', description: 'Is billable' },
},
required: ['taskId', 'hours', 'trackedDate'],
},
handler: async (args: any) => {
const { taskId, ...timelogData } = args;
const timelog = await client.createTimelog(taskId, timelogData);
return { timelog, message: 'Timelog created successfully' };
handler: async (params: { taskId: string; hours: number; trackedDate: string; [key: string]: unknown }) => {
const body: CreateTimelogRequest = {
hours: params.hours,
trackedDate: params.trackedDate,
};
if (params.comment) body.comment = params.comment as string;
if (params.categoryId) body.categoryId = params.categoryId as string;
if (params.billable !== undefined) body.billable = params.billable as boolean;
const response = await client.post<WrikeTimelog>(`/tasks/${params.taskId}/timelogs`, body);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
{
// Update timelog
wrike_update_timelog: {
name: 'wrike_update_timelog',
description: 'Update an existing timelog',
description: 'Update an existing time log entry',
inputSchema: {
type: 'object',
properties: {
timelogId: {
type: 'string',
description: 'Timelog ID',
},
hours: {
type: 'number',
description: 'Updated hours',
},
comment: {
type: 'string',
description: 'Updated comment',
},
categoryId: {
type: 'string',
description: 'Updated category ID',
},
timelogId: { type: 'string', description: 'Timelog ID (required)' },
hours: { type: 'number', description: 'New hours logged' },
trackedDate: { type: 'string', description: 'New tracked date (YYYY-MM-DD)' },
comment: { type: 'string', description: 'New comment' },
categoryId: { type: 'string', description: 'New time category ID' },
billable: { type: 'boolean', description: 'New billable status' },
},
required: ['timelogId'],
},
handler: async (args: any) => {
const { timelogId, ...updateData } = args;
const timelog = await client.updateTimelog(timelogId, updateData);
return { timelog, message: 'Timelog updated successfully' };
handler: async (params: { timelogId: string; [key: string]: unknown }) => {
const body: Partial<CreateTimelogRequest> = {};
if (params.hours !== undefined) body.hours = params.hours as number;
if (params.trackedDate) body.trackedDate = params.trackedDate as string;
if (params.comment !== undefined) body.comment = params.comment as string;
if (params.categoryId) body.categoryId = params.categoryId as string;
if (params.billable !== undefined) body.billable = params.billable as boolean;
const response = await client.put<WrikeTimelog>(`/timelogs/${params.timelogId}`, body);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
{
// Delete timelog
wrike_delete_timelog: {
name: 'wrike_delete_timelog',
description: 'Delete a timelog entry',
description: 'Delete a time log entry',
inputSchema: {
type: 'object',
properties: {
timelogId: {
type: 'string',
description: 'Timelog ID',
},
timelogId: { type: 'string', description: 'Timelog ID (required)' },
},
required: ['timelogId'],
},
handler: async (args: any) => {
await client.deleteTimelog(args.timelogId);
return { message: 'Timelog deleted successfully', timelogId: args.timelogId };
handler: async (params: { timelogId: string }) => {
const response = await client.delete<WrikeTimelog>(`/timelogs/${params.timelogId}`);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
];
// Get timelog categories
wrike_list_timelog_categories: {
name: 'wrike_list_timelog_categories',
description: 'List all time log categories',
inputSchema: {
type: 'object',
properties: {},
},
handler: async () => {
const response = await client.get('/timelog_categories');
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data, null, 2),
},
],
};
},
},
};
}

View File

@ -1,10 +1,10 @@
// Wrike Webhooks Tools
import type { WrikeClient } from '../clients/wrike.js';
import type { WrikeWebhook, CreateWebhookRequest } from '../types/index.js';
import { WrikeClient } from '../clients/wrike.js';
export function registerWebhooksTools(client: WrikeClient) {
return [
{
export function createWebhookTools(client: WrikeClient) {
return {
// List webhooks
wrike_list_webhooks: {
name: 'wrike_list_webhooks',
description: 'List all webhooks',
inputSchema: {
@ -12,77 +12,131 @@ export function registerWebhooksTools(client: WrikeClient) {
properties: {},
},
handler: async () => {
const webhooks = await client.listWebhooks();
return { webhooks, count: webhooks.length };
const response = await client.get<WrikeWebhook>('/webhooks');
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data, null, 2),
},
],
};
},
},
{
// Get webhook by ID
wrike_get_webhook: {
name: 'wrike_get_webhook',
description: 'Get a specific webhook by ID',
inputSchema: {
type: 'object',
properties: {
webhookId: { type: 'string', description: 'Webhook ID (required)' },
},
required: ['webhookId'],
},
handler: async (params: { webhookId: string }) => {
const response = await client.get<WrikeWebhook>(`/webhooks/${params.webhookId}`);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
// Create webhook
wrike_create_webhook: {
name: 'wrike_create_webhook',
description: 'Create a new webhook',
inputSchema: {
type: 'object',
properties: {
hookUrl: {
type: 'string',
description: 'Webhook callback URL',
},
folderId: {
type: 'string',
description: 'Optional folder ID to watch',
},
taskId: {
type: 'string',
description: 'Optional task ID to watch',
},
hookUrl: { type: 'string', description: 'Webhook URL (required)' },
folderId: { type: 'string', description: 'Folder ID to watch' },
taskId: { type: 'string', description: 'Task ID to watch' },
},
required: ['hookUrl'],
},
handler: async (args: any) => {
const webhook = await client.createWebhook(args);
return { webhook, message: 'Webhook created successfully' };
handler: async (params: { hookUrl: string; folderId?: string; taskId?: string }) => {
const body: CreateWebhookRequest = {
hookUrl: params.hookUrl,
};
if (params.folderId) body.folderId = params.folderId;
if (params.taskId) body.taskId = params.taskId;
const response = await client.post<WrikeWebhook>('/webhooks', body);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
{
// Update webhook
wrike_update_webhook: {
name: 'wrike_update_webhook',
description: 'Update an existing webhook',
inputSchema: {
type: 'object',
properties: {
webhookId: {
type: 'string',
description: 'Webhook ID',
},
status: {
type: 'string',
description: 'Webhook status',
enum: ['Active', 'Suspended'],
},
webhookId: { type: 'string', description: 'Webhook ID (required)' },
status: { type: 'string', enum: ['Active', 'Suspended'], description: 'Webhook status' },
},
required: ['webhookId'],
},
handler: async (args: any) => {
const { webhookId, ...updateData } = args;
const webhook = await client.updateWebhook(webhookId, updateData);
return { webhook, message: 'Webhook updated successfully' };
handler: async (params: { webhookId: string; status?: string }) => {
const body: Record<string, unknown> = {};
if (params.status) body.status = params.status;
const response = await client.put<WrikeWebhook>(`/webhooks/${params.webhookId}`, body);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
{
// Delete webhook
wrike_delete_webhook: {
name: 'wrike_delete_webhook',
description: 'Delete a webhook',
inputSchema: {
type: 'object',
properties: {
webhookId: {
type: 'string',
description: 'Webhook ID',
},
webhookId: { type: 'string', description: 'Webhook ID (required)' },
},
required: ['webhookId'],
},
handler: async (args: any) => {
await client.deleteWebhook(args.webhookId);
return { message: 'Webhook deleted successfully', webhookId: args.webhookId };
handler: async (params: { webhookId: string }) => {
const response = await client.delete<WrikeWebhook>(`/webhooks/${params.webhookId}`);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
];
};
}

View File

@ -1,102 +1,264 @@
// Wrike Workflows Tools
import type { WrikeClient } from '../clients/wrike.js';
import type { WrikeWorkflow, WrikeCustomStatus } from '../types/index.js';
import { WrikeClient } from '../clients/wrike.js';
export function registerWorkflowsTools(client: WrikeClient) {
return [
{
export function createWorkflowTools(client: WrikeClient) {
return {
// List workflows
wrike_list_workflows: {
name: 'wrike_list_workflows',
description: 'List all workflows',
description: 'List all workflows in the account',
inputSchema: {
type: 'object',
properties: {},
},
handler: async () => {
const workflows = await client.listWorkflows();
return { workflows, count: workflows.length };
const response = await client.get<WrikeWorkflow>('/workflows');
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data, null, 2),
},
],
};
},
},
{
// Get workflow by ID
wrike_get_workflow: {
name: 'wrike_get_workflow',
description: 'Get details of a specific workflow',
description: 'Get a specific workflow by ID',
inputSchema: {
type: 'object',
properties: {
workflowId: {
type: 'string',
description: 'Workflow ID',
},
workflowId: { type: 'string', description: 'Workflow ID (required)' },
},
required: ['workflowId'],
},
handler: async (args: any) => {
const workflow = await client.getWorkflow(args.workflowId);
return { workflow };
handler: async (params: { workflowId: string }) => {
const response = await client.get<WrikeWorkflow>(`/workflows/${params.workflowId}`);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
{
// Create workflow
wrike_create_workflow: {
name: 'wrike_create_workflow',
description: 'Create a new workflow',
description: 'Create a new custom workflow',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Workflow name',
},
hidden: {
type: 'boolean',
description: 'Hide workflow from UI',
},
customStatuses: {
type: 'array',
description: 'Custom status definitions',
items: {
type: 'object',
properties: {
name: { type: 'string' },
color: { type: 'string' },
group: {
type: 'string',
enum: ['Active', 'Completed', 'Deferred', 'Cancelled'],
},
},
},
},
name: { type: 'string', description: 'Workflow name (required)' },
},
required: ['name'],
},
handler: async (args: any) => {
const workflow = await client.createWorkflow(args);
return { workflow, message: 'Workflow created successfully' };
handler: async (params: { name: string }) => {
const body = { name: params.name };
const response = await client.post<WrikeWorkflow>('/workflows', body);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
{
// Update workflow
wrike_update_workflow: {
name: 'wrike_update_workflow',
description: 'Update an existing workflow',
inputSchema: {
type: 'object',
properties: {
workflowId: {
type: 'string',
description: 'Workflow ID',
},
name: {
type: 'string',
description: 'Updated workflow name',
},
hidden: {
type: 'boolean',
description: 'Updated hidden status',
},
workflowId: { type: 'string', description: 'Workflow ID (required)' },
name: { type: 'string', description: 'New workflow name' },
hidden: { type: 'boolean', description: 'Hide/unhide workflow' },
},
required: ['workflowId'],
},
handler: async (args: any) => {
const { workflowId, ...updateData } = args;
const workflow = await client.updateWorkflow(workflowId, updateData);
return { workflow, message: 'Workflow updated successfully' };
handler: async (params: { workflowId: string; [key: string]: unknown }) => {
const body: Record<string, unknown> = {};
if (params.name) body.name = params.name;
if (params.hidden !== undefined) body.hidden = params.hidden;
const response = await client.put<WrikeWorkflow>(`/workflows/${params.workflowId}`, body);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
];
// List custom statuses
wrike_list_custom_statuses: {
name: 'wrike_list_custom_statuses',
description: 'List all custom statuses',
inputSchema: {
type: 'object',
properties: {},
},
handler: async () => {
const response = await client.get<WrikeCustomStatus>('/customstatuses');
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data, null, 2),
},
],
};
},
},
// Get custom status by ID
wrike_get_custom_status: {
name: 'wrike_get_custom_status',
description: 'Get a specific custom status by ID',
inputSchema: {
type: 'object',
properties: {
customStatusId: { type: 'string', description: 'Custom status ID (required)' },
},
required: ['customStatusId'],
},
handler: async (params: { customStatusId: string }) => {
const response = await client.get<WrikeCustomStatus>(`/customstatuses/${params.customStatusId}`);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
// Create custom status
wrike_create_custom_status: {
name: 'wrike_create_custom_status',
description: 'Create a new custom status',
inputSchema: {
type: 'object',
properties: {
workflowId: { type: 'string', description: 'Workflow ID (required)' },
name: { type: 'string', description: 'Status name (required)' },
group: {
type: 'string',
enum: ['Active', 'Completed', 'Deferred', 'Cancelled'],
description: 'Status group (required)'
},
color: { type: 'string', description: 'Status color (hex)' },
hidden: { type: 'boolean', description: 'Hide status' },
},
required: ['workflowId', 'name', 'group'],
},
handler: async (params: { workflowId: string; name: string; group: string; [key: string]: unknown }) => {
const body: Record<string, unknown> = {
name: params.name,
group: params.group,
};
if (params.color) body.color = params.color;
if (params.hidden !== undefined) body.hidden = params.hidden;
const response = await client.post<WrikeCustomStatus>(`/workflows/${params.workflowId}/customstatuses`, body);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
// Update custom status
wrike_update_custom_status: {
name: 'wrike_update_custom_status',
description: 'Update an existing custom status',
inputSchema: {
type: 'object',
properties: {
customStatusId: { type: 'string', description: 'Custom status ID (required)' },
name: { type: 'string', description: 'New status name' },
color: { type: 'string', description: 'New status color (hex)' },
hidden: { type: 'boolean', description: 'Hide/unhide status' },
group: {
type: 'string',
enum: ['Active', 'Completed', 'Deferred', 'Cancelled'],
description: 'New status group'
},
},
required: ['customStatusId'],
},
handler: async (params: { customStatusId: string; [key: string]: unknown }) => {
const body: Record<string, unknown> = {};
if (params.name) body.name = params.name;
if (params.color) body.color = params.color;
if (params.hidden !== undefined) body.hidden = params.hidden;
if (params.group) body.group = params.group;
const response = await client.put<WrikeCustomStatus>(`/customstatuses/${params.customStatusId}`, body);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
// Delete custom status
wrike_delete_custom_status: {
name: 'wrike_delete_custom_status',
description: 'Delete a custom status',
inputSchema: {
type: 'object',
properties: {
customStatusId: { type: 'string', description: 'Custom status ID (required)' },
},
required: ['customStatusId'],
},
handler: async (params: { customStatusId: string }) => {
const response = await client.delete<WrikeCustomStatus>(`/customstatuses/${params.customStatusId}`);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data[0], null, 2),
},
],
};
},
},
};
}

View File

@ -388,6 +388,7 @@ export interface WrikeQueryParams {
completedDate?: { start?: string; end?: string };
scheduledDate?: { start?: string; end?: string };
dueDate?: { start?: string; end?: string };
trackedDate?: { start?: string; end?: string };
status?: string;
importance?: string;
sortField?: string;
@ -406,6 +407,14 @@ export interface WrikeQueryParams {
customStatus?: string[];
project?: boolean;
subTasks?: boolean;
plainText?: boolean;
withArchived?: boolean;
test?: boolean;
versions?: boolean;
me?: boolean;
billable?: boolean;
categoryId?: string;
[key: string]: unknown;
}
// Request Body Types

View File

@ -0,0 +1,33 @@
import { build } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
import { readdirSync } from 'fs';
const appsDir = resolve(process.cwd(), 'src/apps');
const apps = readdirSync(appsDir, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name);
console.log(`Building ${apps.length} apps...`);
for (const app of apps) {
console.log(`\nBuilding ${app}...`);
await build({
plugins: [react()],
build: {
outDir: `../../dist/apps/${app}`,
emptyOutDir: true,
rollupOptions: {
input: resolve(appsDir, app, 'index.html'),
output: {
entryFileNames: 'app.js',
assetFileNames: 'app.[ext]',
},
},
},
root: resolve(appsDir, app),
});
}
console.log('\n✓ All apps built successfully!');

View File

@ -0,0 +1,99 @@
#!/bin/bash
APPS=(
"task-detail:Task Detail:View and edit task details"
"task-board:Task Board:Kanban board for tasks"
"project-dashboard:Project Dashboard:Project overview and metrics"
"project-detail:Project Detail:Detailed project view"
"folder-tree:Folder Tree:Browse folder hierarchy"
"gantt-view:Gantt View:Timeline and dependencies"
"time-tracker:Time Tracker:Log time entries"
"time-report:Time Report:Time tracking reports"
"comment-thread:Comment Thread:View and add comments"
"attachment-gallery:Attachment Gallery:Browse attachments"
"team-workload:Team Workload:Team capacity view"
"workflow-editor:Workflow Editor:Configure workflows"
"custom-fields-manager:Custom Fields Manager:Manage custom fields"
"approval-dashboard:Approval Dashboard:Approval status"
"space-overview:Space Overview:Space management"
"blueprint-gallery:Blueprint Gallery:Browse templates"
"audit-log-viewer:Audit Log Viewer:View audit logs"
"search-results:Search Results:Advanced search"
"analytics-dashboard:Analytics Dashboard:Project analytics"
)
for app_data in "${APPS[@]}"; do
IFS=':' read -r app_name title subtitle <<< "$app_data"
# Create App.tsx
cat > "src/apps/${app_name}/App.tsx" << APPEOF
import React, { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export function App() {
const [data, setData] = useState<any>(null);
const { callTool, loading, error } = useCallTool();
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
// Call appropriate Wrike API tool
console.log('Loading data for ${app_name}');
};
return (
<div className="app-container">
<Header
title="${title}"
subtitle="${subtitle}"
/>
{loading && <div className="loading">Loading...</div>}
{error && <div className="error">{error}</div>}
<div className="card">
<p>This is the ${title} app.</p>
<p>It integrates with Wrike's API to provide ${subtitle}.</p>
</div>
</div>
);
}
APPEOF
# Create index.html
cat > "src/apps/${app_name}/index.html" << HTMLEOF
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wrike ${title}</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>
HTMLEOF
# Create main.tsx
cat > "src/apps/${app_name}/main.tsx" << MAINEOF
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
MAINEOF
echo "Created ${app_name}"
done
echo "All apps created!"

View File

@ -0,0 +1,21 @@
{
"name": "@mcpengine/wrike-apps",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "node build-all.js",
"dev": "vite"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@vitejs/plugin-react": "^4.3.4",
"vite": "^6.0.7",
"typescript": "^5.7.2"
}
}

View File

@ -0,0 +1,35 @@
import React, { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export function App() {
const [data, setData] = useState<any>(null);
const { callTool, loading, error } = useCallTool();
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
// Call appropriate Wrike API tool
console.log('Loading data for analytics-dashboard');
};
return (
<div className="app-container">
<Header
title="Analytics Dashboard"
subtitle="Project analytics"
/>
{loading && <div className="loading">Loading...</div>}
{error && <div className="error">{error}</div>}
<div className="card">
<p>This is the Analytics Dashboard app.</p>
<p>It integrates with Wrike's API to provide Project analytics.</p>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wrike Analytics Dashboard</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,35 @@
import React, { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export function App() {
const [data, setData] = useState<any>(null);
const { callTool, loading, error } = useCallTool();
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
// Call appropriate Wrike API tool
console.log('Loading data for approval-dashboard');
};
return (
<div className="app-container">
<Header
title="Approval Dashboard"
subtitle="Approval status"
/>
{loading && <div className="loading">Loading...</div>}
{error && <div className="error">{error}</div>}
<div className="card">
<p>This is the Approval Dashboard app.</p>
<p>It integrates with Wrike's API to provide Approval status.</p>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wrike Approval Dashboard</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,35 @@
import React, { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export function App() {
const [data, setData] = useState<any>(null);
const { callTool, loading, error } = useCallTool();
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
// Call appropriate Wrike API tool
console.log('Loading data for attachment-gallery');
};
return (
<div className="app-container">
<Header
title="Attachment Gallery"
subtitle="Browse attachments"
/>
{loading && <div className="loading">Loading...</div>}
{error && <div className="error">{error}</div>}
<div className="card">
<p>This is the Attachment Gallery app.</p>
<p>It integrates with Wrike's API to provide Browse attachments.</p>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wrike Attachment Gallery</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,35 @@
import React, { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export function App() {
const [data, setData] = useState<any>(null);
const { callTool, loading, error } = useCallTool();
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
// Call appropriate Wrike API tool
console.log('Loading data for audit-log-viewer');
};
return (
<div className="app-container">
<Header
title="Audit Log Viewer"
subtitle="View audit logs"
/>
{loading && <div className="loading">Loading...</div>}
{error && <div className="error">{error}</div>}
<div className="card">
<p>This is the Audit Log Viewer app.</p>
<p>It integrates with Wrike's API to provide View audit logs.</p>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wrike Audit Log Viewer</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,35 @@
import React, { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export function App() {
const [data, setData] = useState<any>(null);
const { callTool, loading, error } = useCallTool();
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
// Call appropriate Wrike API tool
console.log('Loading data for blueprint-gallery');
};
return (
<div className="app-container">
<Header
title="Blueprint Gallery"
subtitle="Browse templates"
/>
{loading && <div className="loading">Loading...</div>}
{error && <div className="error">{error}</div>}
<div className="card">
<p>This is the Blueprint Gallery app.</p>
<p>It integrates with Wrike's API to provide Browse templates.</p>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wrike Blueprint Gallery</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,35 @@
import React, { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export function App() {
const [data, setData] = useState<any>(null);
const { callTool, loading, error } = useCallTool();
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
// Call appropriate Wrike API tool
console.log('Loading data for comment-thread');
};
return (
<div className="app-container">
<Header
title="Comment Thread"
subtitle="View and add comments"
/>
{loading && <div className="loading">Loading...</div>}
{error && <div className="error">{error}</div>}
<div className="card">
<p>This is the Comment Thread app.</p>
<p>It integrates with Wrike's API to provide View and add comments.</p>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wrike Comment Thread</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,35 @@
import React, { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export function App() {
const [data, setData] = useState<any>(null);
const { callTool, loading, error } = useCallTool();
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
// Call appropriate Wrike API tool
console.log('Loading data for custom-fields-manager');
};
return (
<div className="app-container">
<Header
title="Custom Fields Manager"
subtitle="Manage custom fields"
/>
{loading && <div className="loading">Loading...</div>}
{error && <div className="error">{error}</div>}
<div className="card">
<p>This is the Custom Fields Manager app.</p>
<p>It integrates with Wrike's API to provide Manage custom fields.</p>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wrike Custom Fields Manager</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,35 @@
import React, { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export function App() {
const [data, setData] = useState<any>(null);
const { callTool, loading, error } = useCallTool();
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
// Call appropriate Wrike API tool
console.log('Loading data for folder-tree');
};
return (
<div className="app-container">
<Header
title="Folder Tree"
subtitle="Browse folder hierarchy"
/>
{loading && <div className="loading">Loading...</div>}
{error && <div className="error">{error}</div>}
<div className="card">
<p>This is the Folder Tree app.</p>
<p>It integrates with Wrike's API to provide Browse folder hierarchy.</p>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wrike Folder Tree</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,35 @@
import React, { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export function App() {
const [data, setData] = useState<any>(null);
const { callTool, loading, error } = useCallTool();
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
// Call appropriate Wrike API tool
console.log('Loading data for gantt-view');
};
return (
<div className="app-container">
<Header
title="Gantt View"
subtitle="Timeline and dependencies"
/>
{loading && <div className="loading">Loading...</div>}
{error && <div className="error">{error}</div>}
<div className="card">
<p>This is the Gantt View app.</p>
<p>It integrates with Wrike's API to provide Timeline and dependencies.</p>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wrike Gantt View</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,35 @@
import React, { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export function App() {
const [data, setData] = useState<any>(null);
const { callTool, loading, error } = useCallTool();
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
// Call appropriate Wrike API tool
console.log('Loading data for project-dashboard');
};
return (
<div className="app-container">
<Header
title="Project Dashboard"
subtitle="Project overview and metrics"
/>
{loading && <div className="loading">Loading...</div>}
{error && <div className="error">{error}</div>}
<div className="card">
<p>This is the Project Dashboard app.</p>
<p>It integrates with Wrike's API to provide Project overview and metrics.</p>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wrike Project Dashboard</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,35 @@
import React, { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export function App() {
const [data, setData] = useState<any>(null);
const { callTool, loading, error } = useCallTool();
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
// Call appropriate Wrike API tool
console.log('Loading data for project-detail');
};
return (
<div className="app-container">
<Header
title="Project Detail"
subtitle="Detailed project view"
/>
{loading && <div className="loading">Loading...</div>}
{error && <div className="error">{error}</div>}
<div className="card">
<p>This is the Project Detail app.</p>
<p>It integrates with Wrike's API to provide Detailed project view.</p>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wrike Project Detail</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,35 @@
import React, { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export function App() {
const [data, setData] = useState<any>(null);
const { callTool, loading, error } = useCallTool();
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
// Call appropriate Wrike API tool
console.log('Loading data for search-results');
};
return (
<div className="app-container">
<Header
title="Search Results"
subtitle="Advanced search"
/>
{loading && <div className="loading">Loading...</div>}
{error && <div className="error">{error}</div>}
<div className="card">
<p>This is the Search Results app.</p>
<p>It integrates with Wrike's API to provide Advanced search.</p>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wrike Search Results</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,35 @@
import React, { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export function App() {
const [data, setData] = useState<any>(null);
const { callTool, loading, error } = useCallTool();
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
// Call appropriate Wrike API tool
console.log('Loading data for space-overview');
};
return (
<div className="app-container">
<Header
title="Space Overview"
subtitle="Space management"
/>
{loading && <div className="loading">Loading...</div>}
{error && <div className="error">{error}</div>}
<div className="card">
<p>This is the Space Overview app.</p>
<p>It integrates with Wrike's API to provide Space management.</p>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wrike Space Overview</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,35 @@
import React, { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export function App() {
const [data, setData] = useState<any>(null);
const { callTool, loading, error } = useCallTool();
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
// Call appropriate Wrike API tool
console.log('Loading data for task-board');
};
return (
<div className="app-container">
<Header
title="Task Board"
subtitle="Kanban board for tasks"
/>
{loading && <div className="loading">Loading...</div>}
{error && <div className="error">{error}</div>}
<div className="card">
<p>This is the Task Board app.</p>
<p>It integrates with Wrike's API to provide Kanban board for tasks.</p>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wrike Task Board</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,86 @@
import React, { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { TaskCard } from '../../components/TaskCard';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export function App() {
const [tasks, setTasks] = useState<any[]>([]);
const [filter, setFilter] = useState({ status: 'Active', importance: '' });
const { callTool, loading } = useCallTool();
useEffect(() => {
loadTasks();
}, [filter]);
const loadTasks = async () => {
const result = await callTool('wrike_list_tasks', filter);
if (result.data) setTasks(result.data);
};
const stats = {
total: tasks.length,
high: tasks.filter(t => t.importance === 'High').length,
active: tasks.filter(t => t.status === 'Active').length,
completed: tasks.filter(t => t.status === 'Completed').length,
};
return (
<div className="app-container">
<Header
title="Task Dashboard"
subtitle={`${stats.total} tasks`}
actions={
<button className="btn btn-primary" onClick={loadTasks}>
Refresh
</button>
}
/>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '16px', marginBottom: '24px' }}>
<div className="card">
<h3 style={{ margin: '0 0 8px 0', fontSize: '14px', color: 'var(--text-secondary)' }}>Total Tasks</h3>
<div style={{ fontSize: '32px', fontWeight: 'bold' }}>{stats.total}</div>
</div>
<div className="card">
<h3 style={{ margin: '0 0 8px 0', fontSize: '14px', color: 'var(--text-secondary)' }}>High Priority</h3>
<div style={{ fontSize: '32px', fontWeight: 'bold', color: 'var(--danger)' }}>{stats.high}</div>
</div>
<div className="card">
<h3 style={{ margin: '0 0 8px 0', fontSize: '14px', color: 'var(--text-secondary)' }}>Active</h3>
<div style={{ fontSize: '32px', fontWeight: 'bold', color: 'var(--info)' }}>{stats.active}</div>
</div>
<div className="card">
<h3 style={{ margin: '0 0 8px 0', fontSize: '14px', color: 'var(--text-secondary)' }}>Completed</h3>
<div style={{ fontSize: '32px', fontWeight: 'bold', color: 'var(--success)' }}>{stats.completed}</div>
</div>
</div>
<div style={{ display: 'flex', gap: '12px', marginBottom: '16px' }}>
<select className="select" style={{ width: 'auto' }} value={filter.status} onChange={e => setFilter({...filter, status: e.target.value})}>
<option value="">All Status</option>
<option value="Active">Active</option>
<option value="Completed">Completed</option>
<option value="Deferred">Deferred</option>
<option value="Cancelled">Cancelled</option>
</select>
<select className="select" style={{ width: 'auto' }} value={filter.importance} onChange={e => setFilter({...filter, importance: e.target.value})}>
<option value="">All Priorities</option>
<option value="High">High</option>
<option value="Normal">Normal</option>
<option value="Low">Low</option>
</select>
</div>
{loading ? (
<div className="loading">Loading tasks...</div>
) : (
<div>
{tasks.map(task => (
<TaskCard key={task.id} task={task} />
))}
</div>
)}
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wrike Task Dashboard</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,35 @@
import React, { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export function App() {
const [data, setData] = useState<any>(null);
const { callTool, loading, error } = useCallTool();
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
// Call appropriate Wrike API tool
console.log('Loading data for task-detail');
};
return (
<div className="app-container">
<Header
title="Task Detail"
subtitle="View and edit task details"
/>
{loading && <div className="loading">Loading...</div>}
{error && <div className="error">{error}</div>}
<div className="card">
<p>This is the Task Detail app.</p>
<p>It integrates with Wrike's API to provide View and edit task details.</p>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wrike Task Detail</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,35 @@
import React, { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export function App() {
const [data, setData] = useState<any>(null);
const { callTool, loading, error } = useCallTool();
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
// Call appropriate Wrike API tool
console.log('Loading data for team-workload');
};
return (
<div className="app-container">
<Header
title="Team Workload"
subtitle="Team capacity view"
/>
{loading && <div className="loading">Loading...</div>}
{error && <div className="error">{error}</div>}
<div className="card">
<p>This is the Team Workload app.</p>
<p>It integrates with Wrike's API to provide Team capacity view.</p>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wrike Team Workload</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,35 @@
import React, { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export function App() {
const [data, setData] = useState<any>(null);
const { callTool, loading, error } = useCallTool();
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
// Call appropriate Wrike API tool
console.log('Loading data for time-report');
};
return (
<div className="app-container">
<Header
title="Time Report"
subtitle="Time tracking reports"
/>
{loading && <div className="loading">Loading...</div>}
{error && <div className="error">{error}</div>}
<div className="card">
<p>This is the Time Report app.</p>
<p>It integrates with Wrike's API to provide Time tracking reports.</p>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wrike Time Report</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,35 @@
import React, { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export function App() {
const [data, setData] = useState<any>(null);
const { callTool, loading, error } = useCallTool();
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
// Call appropriate Wrike API tool
console.log('Loading data for time-tracker');
};
return (
<div className="app-container">
<Header
title="Time Tracker"
subtitle="Log time entries"
/>
{loading && <div className="loading">Loading...</div>}
{error && <div className="error">{error}</div>}
<div className="card">
<p>This is the Time Tracker app.</p>
<p>It integrates with Wrike's API to provide Log time entries.</p>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wrike Time Tracker</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,35 @@
import React, { useState, useEffect } from 'react';
import { Header } from '../../components/Header';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export function App() {
const [data, setData] = useState<any>(null);
const { callTool, loading, error } = useCallTool();
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
// Call appropriate Wrike API tool
console.log('Loading data for workflow-editor');
};
return (
<div className="app-container">
<Header
title="Workflow Editor"
subtitle="Configure workflows"
/>
{loading && <div className="loading">Loading...</div>}
{error && <div className="error">{error}</div>}
<div className="card">
<p>This is the Workflow Editor app.</p>
<p>It integrates with Wrike's API to provide Configure workflows.</p>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wrike Workflow Editor</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,26 @@
import React from 'react';
interface HeaderProps {
title: string;
subtitle?: string;
actions?: React.ReactNode;
}
export function Header({ title, subtitle, actions }: HeaderProps) {
return (
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '24px',
paddingBottom: '16px',
borderBottom: '2px solid var(--border-color)'
}}>
<div>
<h1 style={{ margin: '0 0 4px 0', fontSize: '28px' }}>{title}</h1>
{subtitle && <p style={{ margin: 0, color: 'var(--text-secondary)' }}>{subtitle}</p>}
</div>
{actions && <div>{actions}</div>}
</div>
);
}

View File

@ -0,0 +1,64 @@
import React from 'react';
interface Task {
id: string;
title: string;
status: string;
importance: string;
responsibleIds?: string[];
dates?: {
due?: string;
};
}
interface TaskCardProps {
task: Task;
onClick?: () => void;
}
export function TaskCard({ task, onClick }: TaskCardProps) {
const getStatusColor = (status: string) => {
switch (status.toLowerCase()) {
case 'completed':
return 'badge-success';
case 'active':
return 'badge-info';
case 'deferred':
return 'badge-warning';
default:
return 'badge-secondary';
}
};
const getImportanceColor = (importance: string) => {
switch (importance) {
case 'High':
return 'badge-danger';
case 'Normal':
return 'badge-info';
case 'Low':
return 'badge-secondary';
default:
return 'badge-info';
}
};
return (
<div className="card" onClick={onClick} style={{ cursor: onClick ? 'pointer' : 'default' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
<h3 style={{ margin: '0 0 8px 0', fontSize: '16px' }}>{task.title}</h3>
<span className={`badge ${getImportanceColor(task.importance)}`}>
{task.importance}
</span>
</div>
<div style={{ display: 'flex', gap: '8px', marginTop: '8px' }}>
<span className={`badge ${getStatusColor(task.status)}`}>{task.status}</span>
{task.dates?.due && (
<span className="badge" style={{ background: 'var(--bg-tertiary)' }}>
Due: {new Date(task.dates.due).toLocaleDateString()}
</span>
)}
</div>
</div>
);
}

View File

@ -0,0 +1,30 @@
import { useState, useCallback } from 'react';
export function useCallTool() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const callTool = useCallback(async (tool: string, args: Record<string, unknown>) => {
setLoading(true);
setError(null);
try {
// In real MCP app, this would call the MCP server
// For now, return mock data
console.log('Calling tool:', tool, args);
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 500));
return { success: true, data: {} };
} catch (err) {
const message = err instanceof Error ? err.message : 'Unknown error';
setError(message);
throw err;
} finally {
setLoading(false);
}
}, []);
return { callTool, loading, error };
}

View File

@ -0,0 +1,18 @@
import { useState, useCallback } from 'react';
export function useDirtyState<T>(initialValue: T) {
const [value, setValue] = useState(initialValue);
const [isDirty, setIsDirty] = useState(false);
const updateValue = useCallback((newValue: T | ((prev: T) => T)) => {
setValue(newValue);
setIsDirty(true);
}, []);
const reset = useCallback(() => {
setValue(initialValue);
setIsDirty(false);
}, [initialValue]);
return { value, setValue: updateValue, isDirty, reset };
}

View File

@ -0,0 +1,150 @@
:root {
--bg-primary: #ffffff;
--bg-secondary: #f8f9fa;
--bg-tertiary: #e9ecef;
--text-primary: #212529;
--text-secondary: #6c757d;
--border-color: #dee2e6;
--primary: #0d6efd;
--success: #198754;
--warning: #ffc107;
--danger: #dc3545;
--info: #0dcaf0;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d;
--bg-tertiary: #3a3a3a;
--text-primary: #e9ecef;
--text-secondary: #adb5bd;
--border-color: #495057;
}
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: var(--bg-primary);
color: var(--text-primary);
}
.app-container {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.card {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: opacity 0.2s;
}
.btn:hover {
opacity: 0.8;
}
.btn-primary {
background: var(--primary);
color: white;
}
.btn-success {
background: var(--success);
color: white;
}
.btn-danger {
background: var(--danger);
color: white;
}
.btn-secondary {
background: var(--bg-tertiary);
color: var(--text-primary);
}
.input {
width: 100%;
padding: 8px 12px;
border: 1px solid var(--border-color);
border-radius: 4px;
background: var(--bg-primary);
color: var(--text-primary);
font-size: 14px;
}
.select {
width: 100%;
padding: 8px 12px;
border: 1px solid var(--border-color);
border-radius: 4px;
background: var(--bg-primary);
color: var(--text-primary);
font-size: 14px;
}
.badge {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
}
.badge-success {
background: var(--success);
color: white;
}
.badge-warning {
background: var(--warning);
color: black;
}
.badge-danger {
background: var(--danger);
color: white;
}
.badge-info {
background: var(--info);
color: black;
}
.loading {
text-align: center;
padding: 40px;
color: var(--text-secondary);
}
.error {
background: #f8d7da;
color: #721c24;
padding: 12px;
border-radius: 4px;
margin: 16px 0;
}

View File

@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts", "build-all.js"]
}