feat: Add 52 new tools bringing total to 160
New modules (11): - teams.ts (5 tools): Team/workspace management - integrations.ts (6 tools): External integrations (Slack, embeds) - notifications.ts (4 tools): User notification management - subscriptions.ts (4 tools): Document subscription management - templates.ts (5 tools): Document template management - imports-tools.ts (4 tools): Import job management - emojis.ts (3 tools): Custom emoji management - user-permissions.ts (3 tools): Permission management - bulk-operations.ts (6 tools): Batch operations - advanced-search.ts (6 tools): Faceted search, recent, orphaned, duplicates - analytics.ts (6 tools): Usage statistics and insights Updated: - src/index.ts: Import and register all new tools - src/tools/index.ts: Export all new modules - CHANGELOG.md: Version 1.2.0 entry - CLAUDE.md: Updated tool count to 160 - CONTINUE.md: Updated state documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
179
src/tools/imports-tools.ts
Normal file
179
src/tools/imports-tools.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* MCP Outline PostgreSQL - Imports Tools
|
||||
* @author Descomplicar® | @link descomplicar.pt | @copyright 2026
|
||||
*/
|
||||
|
||||
import { Pool } from 'pg';
|
||||
import { BaseTool, ToolResponse, PaginationArgs } from '../types/tools.js';
|
||||
import { validatePagination, isValidUUID } from '../utils/security.js';
|
||||
|
||||
interface ImportListArgs extends PaginationArgs {
|
||||
team_id?: string;
|
||||
state?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* imports.list - List imports
|
||||
*/
|
||||
const listImports: BaseTool<ImportListArgs> = {
|
||||
name: 'outline_list_imports',
|
||||
description: 'List document import jobs.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
team_id: { type: 'string', description: 'Filter by team ID (UUID)' },
|
||||
state: { type: 'string', description: 'Filter by state (pending, processing, completed, failed)' },
|
||||
limit: { type: 'number', description: 'Max results (default: 25)' },
|
||||
offset: { type: 'number', description: 'Skip results (default: 0)' },
|
||||
},
|
||||
},
|
||||
handler: async (args, pgClient): Promise<ToolResponse> => {
|
||||
const { limit, offset } = validatePagination(args.limit, args.offset);
|
||||
const conditions: string[] = [];
|
||||
const params: any[] = [];
|
||||
let idx = 1;
|
||||
|
||||
if (args.team_id) {
|
||||
if (!isValidUUID(args.team_id)) throw new Error('Invalid team_id');
|
||||
conditions.push(`i."teamId" = $${idx++}`);
|
||||
params.push(args.team_id);
|
||||
}
|
||||
if (args.state) {
|
||||
conditions.push(`i.state = $${idx++}`);
|
||||
params.push(args.state);
|
||||
}
|
||||
|
||||
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
||||
|
||||
const result = await pgClient.query(`
|
||||
SELECT
|
||||
i.id, i.state, i.type, i."documentCount", i."fileCount",
|
||||
i."teamId", i."createdById", i."integrationId",
|
||||
i."createdAt", i."updatedAt",
|
||||
u.name as "createdByName",
|
||||
t.name as "teamName"
|
||||
FROM imports i
|
||||
LEFT JOIN users u ON i."createdById" = u.id
|
||||
LEFT JOIN teams t ON i."teamId" = t.id
|
||||
${whereClause}
|
||||
ORDER BY i."createdAt" DESC
|
||||
LIMIT $${idx++} OFFSET $${idx}
|
||||
`, [...params, limit, offset]);
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify({ data: result.rows, pagination: { limit, offset, total: result.rows.length } }, null, 2) }],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* imports.status - Get import status
|
||||
*/
|
||||
const getImportStatus: BaseTool<{ id: string }> = {
|
||||
name: 'outline_get_import_status',
|
||||
description: 'Get detailed status of an import job.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Import ID (UUID)' },
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
handler: async (args, pgClient): Promise<ToolResponse> => {
|
||||
if (!isValidUUID(args.id)) throw new Error('Invalid import ID');
|
||||
|
||||
const result = await pgClient.query(`
|
||||
SELECT i.*, u.name as "createdByName"
|
||||
FROM imports i
|
||||
LEFT JOIN users u ON i."createdById" = u.id
|
||||
WHERE i.id = $1
|
||||
`, [args.id]);
|
||||
|
||||
if (result.rows.length === 0) throw new Error('Import not found');
|
||||
|
||||
// Get import tasks
|
||||
const tasks = await pgClient.query(`
|
||||
SELECT id, state, "documentId", "createdAt"
|
||||
FROM import_tasks
|
||||
WHERE "importId" = $1
|
||||
ORDER BY "createdAt" DESC
|
||||
LIMIT 50
|
||||
`, [args.id]);
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify({
|
||||
import: result.rows[0],
|
||||
tasks: tasks.rows,
|
||||
taskCount: tasks.rows.length,
|
||||
}, null, 2) }],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* imports.create - Create import job
|
||||
*/
|
||||
const createImport: BaseTool<{ type: string; collection_id?: string }> = {
|
||||
name: 'outline_create_import',
|
||||
description: 'Create a new import job. Note: This creates the job record, actual file upload handled separately.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
type: { type: 'string', description: 'Import type (e.g., "notion", "confluence", "markdown")' },
|
||||
collection_id: { type: 'string', description: 'Target collection ID (UUID, optional)' },
|
||||
},
|
||||
required: ['type'],
|
||||
},
|
||||
handler: async (args, pgClient): Promise<ToolResponse> => {
|
||||
if (args.collection_id && !isValidUUID(args.collection_id)) throw new Error('Invalid collection_id');
|
||||
|
||||
const team = await pgClient.query(`SELECT id FROM teams WHERE "deletedAt" IS NULL LIMIT 1`);
|
||||
if (team.rows.length === 0) throw new Error('No team found');
|
||||
|
||||
const user = await pgClient.query(`SELECT id FROM users WHERE role = 'admin' AND "deletedAt" IS NULL LIMIT 1`);
|
||||
if (user.rows.length === 0) throw new Error('No admin user found');
|
||||
|
||||
const result = await pgClient.query(`
|
||||
INSERT INTO imports (id, type, state, "teamId", "createdById", "documentCount", "fileCount", "createdAt", "updatedAt")
|
||||
VALUES (gen_random_uuid(), $1, 'pending', $2, $3, 0, 0, NOW(), NOW())
|
||||
RETURNING *
|
||||
`, [args.type, team.rows[0].id, user.rows[0].id]);
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify({ data: result.rows[0], message: 'Import job created' }, null, 2) }],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* imports.cancel - Cancel import job
|
||||
*/
|
||||
const cancelImport: BaseTool<{ id: string }> = {
|
||||
name: 'outline_cancel_import',
|
||||
description: 'Cancel a pending or processing import job.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Import ID (UUID)' },
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
handler: async (args, pgClient): Promise<ToolResponse> => {
|
||||
if (!isValidUUID(args.id)) throw new Error('Invalid import ID');
|
||||
|
||||
const result = await pgClient.query(`
|
||||
UPDATE imports
|
||||
SET state = 'cancelled', "updatedAt" = NOW()
|
||||
WHERE id = $1 AND state IN ('pending', 'processing')
|
||||
RETURNING id, state, type
|
||||
`, [args.id]);
|
||||
|
||||
if (result.rows.length === 0) throw new Error('Import not found or cannot be cancelled');
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify({ data: result.rows[0], message: 'Import cancelled' }, null, 2) }],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const importsTools: BaseTool<any>[] = [listImports, getImportStatus, createImport, cancelImport];
|
||||
Reference in New Issue
Block a user