feat(mcp): add MCP Server Integration — Property Search, Analytics, Valuation

Implement 3 MCP servers in libs/mcp-servers/ using @modelcontextprotocol/sdk:

- Property Search: NL search via Typesense, property comparison, detail lookup
- Market Analytics: market reports, price trends, district comparison
- Valuation: AVM integration with Python AI service, feature extraction, batch valuation

Includes NestJS integration module with SSE transport for in-process hosting.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-08 03:22:27 +07:00
parent efa49e225e
commit cb00b12d7b
17 changed files with 1077 additions and 41 deletions

View File

@@ -0,0 +1,63 @@
import { Injectable, Inject, type OnModuleInit } from '@nestjs/common';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { MCP_MODULE_OPTIONS, type McpModuleOptions } from './mcp.module';
@Injectable()
export class McpRegistryService implements OnModuleInit {
private readonly servers = new Map<string, McpServer>();
private typesenseClient: import('typesense').Client | null = null;
constructor(
@Inject(MCP_MODULE_OPTIONS) private readonly options: McpModuleOptions,
) {}
async onModuleInit(): Promise<void> {
// Lazy import to avoid hard dependency at module load time
const { createPropertySearchServer } = await import('../property-search/property-search.server');
const { createMarketAnalyticsServer } = await import('../market-analytics/market-analytics.server');
const { createValuationServer } = await import('../valuation/valuation.server');
// Typesense client is injected from the host app via setTypesenseClient
// If not set by the time servers are needed, tools that require it will fail gracefully
if (this.typesenseClient) {
this.servers.set(
'property-search',
createPropertySearchServer({
typesenseClient: this.typesenseClient,
collectionName: this.options.typesenseCollectionName,
}),
);
this.servers.set(
'market-analytics',
createMarketAnalyticsServer({
typesenseClient: this.typesenseClient,
collectionName: this.options.typesenseCollectionName,
}),
);
}
this.servers.set(
'valuation',
createValuationServer({
aiServiceBaseUrl: this.options.aiServiceBaseUrl,
}),
);
}
setTypesenseClient(client: import('typesense').Client): void {
this.typesenseClient = client;
}
getServer(name: string): McpServer | undefined {
return this.servers.get(name);
}
getServerNames(): string[] {
return Array.from(this.servers.keys());
}
getAllServers(): Map<string, McpServer> {
return this.servers;
}
}