feat: v1.3.1 - Multi-transport + Production deployment

- Add HTTP transport (StreamableHTTPServerTransport)
- Add shared server module (src/server/)
- Configure production for hub.descomplicar.pt
- Add SSH tunnel script (start-tunnel.sh)
- Fix connection leak in pg-client.ts
- Fix atomicity bug in comments deletion
- Update docs with test plan for 164 tools

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-31 17:06:30 +00:00
parent 0329a1179a
commit 5f49cb63e8
15 changed files with 1381 additions and 464 deletions

180
src/server/create-server.ts Normal file
View File

@@ -0,0 +1,180 @@
/**
* MCP Outline PostgreSQL - Server Factory
* Creates configured MCP server instances for different transports
* @author Descomplicar® | @link descomplicar.pt | @copyright 2026
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { Pool } from 'pg';
import { registerHandlers } from './register-handlers.js';
import { BaseTool } from '../types/tools.js';
// Import ALL tools
import {
documentsTools,
collectionsTools,
usersTools,
groupsTools,
commentsTools,
sharesTools,
revisionsTools,
eventsTools,
attachmentsTools,
fileOperationsTools,
oauthTools,
authTools,
starsTools,
pinsTools,
viewsTools,
reactionsTools,
apiKeysTools,
webhooksTools,
backlinksTools,
searchQueriesTools,
teamsTools,
integrationsTools,
notificationsTools,
subscriptionsTools,
templatesTools,
importsTools,
emojisTools,
userPermissionsTools,
bulkOperationsTools,
advancedSearchTools,
analyticsTools,
exportImportTools,
deskSyncTools
} from '../tools/index.js';
export interface ServerConfig {
name?: string;
version?: string;
}
// Combine ALL tools into single array
export const allTools: BaseTool[] = [
// Core functionality
...documentsTools,
...collectionsTools,
...usersTools,
...groupsTools,
// Collaboration
...commentsTools,
...sharesTools,
...revisionsTools,
// System
...eventsTools,
...attachmentsTools,
...fileOperationsTools,
// Authentication
...oauthTools,
...authTools,
// User engagement
...starsTools,
...pinsTools,
...viewsTools,
...reactionsTools,
// API & Integration
...apiKeysTools,
...webhooksTools,
...integrationsTools,
// Analytics & Search
...backlinksTools,
...searchQueriesTools,
...advancedSearchTools,
...analyticsTools,
// Teams & Workspace
...teamsTools,
// Notifications & Subscriptions
...notificationsTools,
...subscriptionsTools,
// Templates & Imports
...templatesTools,
...importsTools,
// Custom content
...emojisTools,
// Permissions & Bulk operations
...userPermissionsTools,
...bulkOperationsTools,
// Export/Import & External Sync
...exportImportTools,
...deskSyncTools
];
/**
* Create a configured MCP server instance
*/
export function createMcpServer(
pgPool: Pool,
config: ServerConfig = {}
): Server {
const server = new Server({
name: config.name || 'mcp-outline-postgresql',
version: config.version || '1.3.1'
});
// Set capabilities (required for MCP v2.2+)
(server as any)._capabilities = {
tools: {},
resources: {},
prompts: {}
};
// Register all handlers
registerHandlers(server, pgPool, allTools);
return server;
}
/**
* Get tool counts by module for debugging
*/
export function getToolCounts(): Record<string, number> {
return {
documents: documentsTools.length,
collections: collectionsTools.length,
users: usersTools.length,
groups: groupsTools.length,
comments: commentsTools.length,
shares: sharesTools.length,
revisions: revisionsTools.length,
events: eventsTools.length,
attachments: attachmentsTools.length,
fileOperations: fileOperationsTools.length,
oauth: oauthTools.length,
auth: authTools.length,
stars: starsTools.length,
pins: pinsTools.length,
views: viewsTools.length,
reactions: reactionsTools.length,
apiKeys: apiKeysTools.length,
webhooks: webhooksTools.length,
backlinks: backlinksTools.length,
searchQueries: searchQueriesTools.length,
teams: teamsTools.length,
integrations: integrationsTools.length,
notifications: notificationsTools.length,
subscriptions: subscriptionsTools.length,
templates: templatesTools.length,
imports: importsTools.length,
emojis: emojisTools.length,
userPermissions: userPermissionsTools.length,
bulkOperations: bulkOperationsTools.length,
advancedSearch: advancedSearchTools.length,
analytics: analyticsTools.length,
exportImport: exportImportTools.length,
deskSync: deskSyncTools.length
};
}

7
src/server/index.ts Normal file
View File

@@ -0,0 +1,7 @@
/**
* MCP Outline PostgreSQL - Server Module
* @author Descomplicar® | @link descomplicar.pt | @copyright 2026
*/
export { createMcpServer, allTools, getToolCounts, type ServerConfig } from './create-server.js';
export { registerHandlers } from './register-handlers.js';

View File

@@ -0,0 +1,92 @@
/**
* MCP Outline PostgreSQL - Register Handlers
* Shared handler registration for all transport types
* @author Descomplicar® | @link descomplicar.pt | @copyright 2026
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import {
ListToolsRequestSchema,
CallToolRequestSchema,
ListResourcesRequestSchema,
ListPromptsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import { Pool } from 'pg';
import { BaseTool } from '../types/tools.js';
import { checkRateLimit } from '../utils/security.js';
import { logger } from '../utils/logger.js';
/**
* Register all MCP handlers on a server instance
*/
export function registerHandlers(
server: Server,
pgPool: Pool,
tools: BaseTool[]
): void {
// Register tools list handler
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: tools.map((tool) => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema
}))
}));
// Register resources handler (required even if empty)
server.setRequestHandler(ListResourcesRequestSchema, async () => {
logger.debug('Resources list requested');
return { resources: [] };
});
// Register prompts handler (required even if empty)
server.setRequestHandler(ListPromptsRequestSchema, async () => {
logger.debug('Prompts list requested');
return { prompts: [] };
});
// Register tool call handler
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
// Rate limiting (using 'default' as clientId for now)
const clientId = process.env.CLIENT_ID || 'default';
if (!checkRateLimit('api', clientId)) {
return {
content: [
{ type: 'text', text: 'Too Many Requests: rate limit exceeded. Try again later.' }
]
};
}
// Find the tool handler
const tool = tools.find((t) => t.name === name);
if (!tool) {
return {
content: [
{
type: 'text',
text: `Tool '${name}' not found`
}
]
};
}
try {
return await tool.handler(args as Record<string, unknown>, pgPool);
} catch (error) {
logger.error(`Error in tool ${name}:`, {
error: error instanceof Error ? error.message : String(error)
});
return {
content: [
{
type: 'text',
text: `Error in tool ${name}: ${error instanceof Error ? error.message : String(error)}`
}
]
};
}
});
}