MCP Server - Reference Implementation in Fund Accounting
Building an MCP Server for Mutual Fund and ETF Accounting Static Data: A Complete Guide for Investment Management
Introduction
In investment management firms, mutual fund and ETF accounting systems rely heavily on accurate, consistent static data. This includes fund specifications, portfolio classifications, benchmark indices, pricing sources, regulatory mappings, and operational parameters. The Model Context Protocol (MCP) provides an excellent framework for exposing this critical data to AI agents in a structured, standardized way.
This article demonstrates how to build a robust MCP server that serves mutual fund and ETF accounting static data, enabling AI agents to process fund accounting, performance analysis, and regulatory reporting intelligently while maintaining data consistency across the organization.
Understanding the Model Context Protocol (MCP)
What is MCP?
The Model Context Protocol (MCP) is an open standard that enables seamless integration between AI agents and data sources, tools, and services. Unlike traditional APIs that require custom integration work for each AI system, MCP provides a standardized way for AI agents to discover, access, and interact with external resources.
MCP was designed specifically to address the challenges of building AI systems that need to work with diverse, dynamic data sources while maintaining consistency, security, and scalability.
Key MCP Concepts
Resources: Static or dynamic data that AI agents can read and reference. In our investment management context, this includes fund master data, security reference information, benchmark details, and regulatory classifications.
Tools: Functions that AI agents can invoke to perform operations or calculations. Examples include trade validation, NAV calculation, performance attribution, and risk analytics.
Prompts: Reusable templates that help AI agents interact with the data and tools in consistent ways.
Sampling: The ability for AI agents to explore and understand the structure of data before making requests.
MCP Architecture Benefits
1graph TB
2 subgraph "AI Agents"
3 A1[Portfolio Management Agent]
4 A2[Risk Analytics Agent]
5 A3[Compliance Agent]
6 A4[Performance Agent]
7 end
8
9 subgraph "MCP Layer"
10 MCP[MCP Server<br/>Fund Accounting Data]
11 end
12
13 subgraph "Data Sources"
14 DB[Fund Database]
15 PS[Pricing Services]
16 RS[Risk Systems]
17 CS[Compliance Systems]
18 end
19
20 A1 --> MCP
21 A2 --> MCP
22 A3 --> MCP
23 A4 --> MCP
24
25 MCP --> DB
26 MCP --> PS
27 MCP --> RS
28 MCP --> CS
Why Traditional APIs Are Insufficient for AI-Driven Investment Management
Limitations of Standard REST/GraphQL APIs
#### 1. Lack of Semantic Understanding
Traditional APIs expose data through predefined endpoints but don't provide semantic context about what the data means or how it should be used.
1# Traditional API response - lacks context
2{
3 "fund_id": "FUND001",
4 "expense_ratio": 0.0075,
5 "benchmark_id": "BENCH001"
6}
7
8# MCP provides rich context and validation
9{
10 "fund": {
11 "fund_id": "FUND001",
12 "fund_name": "Global Equity Growth Fund",
13 "expense_ratio": 0.0075,
14 "fund_type": "MUTUAL_FUND",
15 "asset_class": "EQUITY",
16 "regulatory_classification": "40_ACT",
17 "investment_restrictions": {
18 "max_single_security_weight": 5.0,
19 "allowed_asset_classes": ["EQUITY"]
20 }
21 },
22 "validation_rules": ["check_concentration_limits", "verify_asset_class_compliance"],
23 "related_resources": ["benchmark_data", "share_classes", "pricing_rules"]
24}
#### 2. Static Schema Limitations
REST APIs typically have fixed schemas that don't adapt to the dynamic needs of AI agents. Investment management data often requires flexible access patterns.
1# Traditional API - Fixed endpoints
2GET /api/funds/{id}
3GET /api/securities/{id}
4GET /api/benchmarks/{id}
5
6# MCP - Dynamic resource discovery
7resources://fund-master # Discovers all fund data
8resources://security-master # Finds security classifications
9tools://validate_fund_trade # Discovers validation capabilities
10tools://calculate_performance # Finds analytical tools
#### 3. No Built-in Tool Discovery
Traditional APIs don't provide mechanisms for AI agents to discover what operations are available or how to use them effectively.
1# Traditional API - AI agent must be pre-programmed
2def validate_trade(fund_id, security_id, quantity):
3 # Agent needs to know exact endpoint and parameters
4 response = requests.post("/api/validate", {
5 "fund_id": fund_id,
6 "security_id": security_id,
7 "quantity": quantity
8 })
9 # Agent must handle response format manually
10 return response.json()
11
12# MCP - AI agent discovers capabilities dynamically
13async def validate_trade_with_mcp(fund_id, security_id, quantity):
14 # Agent discovers available tools
15 tools = await mcp_client.list_tools()
16
17 # Agent finds the right tool with description
18 validation_tool = find_tool(tools, "validate_fund_trade")
19
20 # Agent understands parameters from schema
21 result = await mcp_client.call_tool(
22 validation_tool.name,
23 {
24 "fund_id": fund_id,
25 "security_id": security_id,
26 "quantity": quantity,
27 "trade_type": "BUY" # Tool schema guides parameter inclusion
28 }
29 )
30
31 # Structured response with validation results
32 return result
#### 4. Insufficient Context for Decision Making
Investment management requires understanding relationships between data elements, regulatory constraints, and business rules that traditional APIs don't capture.
1# Traditional API - Fragmented data
2fund_data = get_fund(fund_id)
3security_data = get_security(security_id)
4restrictions = get_restrictions(fund_id)
5benchmark_data = get_benchmark(fund_data['benchmark_id'])
6
7# AI agent must manually correlate and validate relationships
8
9# MCP - Contextual, relationship-aware responses
10validation_result = await mcp_client.call_tool("validate_fund_trade", {
11 "fund_id": fund_id,
12 "security_id": security_id,
13 "quantity": 1000,
14 "trade_type": "BUY"
15})
16
17# Returns comprehensive context
18{
19 "valid": True,
20 "fund": {...}, # Complete fund context
21 "security": {...}, # Security details with classifications
22 "compliance_checks": {
23 "concentration_limit": "PASS",
24 "asset_class_match": "PASS",
25 "regulatory_compliance": "PASS"
26 },
27 "risk_impact": {
28 "portfolio_weight_change": 0.02,
29 "tracking_error_impact": 0.001
30 },
31 "benchmark_alignment": {
32 "security_in_benchmark": True,
33 "benchmark_weight": 0.015
34 }
35}
#### 5. No Standard Error Handling for AI Context
Traditional APIs return HTTP status codes and error messages designed for human developers, not AI agents that need to understand and react to different types of failures.
1# Traditional API error - Not AI-friendly
2{
3 "status": 400,
4 "error": "Bad Request",
5 "message": "Invalid fund ID"
6}
7
8# MCP error handling - AI-actionable
9{
10 "valid": False,
11 "error": "Fund FUND999 not found",
12 "error_type": "RESOURCE_NOT_FOUND",
13 "suggestions": [
14 {
15 "fund_id": "FUND001",
16 "fund_name": "Global Equity Growth Fund",
17 "similarity_score": 0.8
18 }
19 ],
20 "corrective_actions": [
21 "Verify fund ID spelling",
22 "Check if fund has been deactivated",
23 "Use fund search tool to find similar funds"
24 ]
25}
MCP's Advantages for Investment Management AI
#### 1. Intelligent Resource Discovery
1# AI agent can discover what data is available
2resources = await mcp_client.list_resources()
3for resource in resources:
4 print(f"Available: {resource.name} - {resource.description}")
5
6# Output:
7# Available: Fund Master - Complete fund data with specifications
8# Available: Security Master - Security reference with identifiers
9# Available: Benchmark Indices - Performance benchmarks and constituents
10# Available: Pricing Rules - Security pricing methodology and validation
#### 2. Self-Documenting Tools
1# AI agent understands tool capabilities from schema
2tools = await mcp_client.list_tools()
3trade_validation_tool = find_tool(tools, "validate_fund_trade")
4
5print(trade_validation_tool.description)
6# "Validate if a trade complies with fund restrictions and parameters"
7
8print(trade_validation_tool.inputSchema)
9# Shows required parameters, types, and constraints
#### 3. Contextual Data Relationships
MCP responses include related information that AI agents need for decision-making, reducing the number of API calls and providing richer context.
1# Single MCP call provides comprehensive context
2result = await mcp_client.call_tool("calculate_fund_nav", {
3 "fund_id": "FUND001",
4 "nav_date": "2024-01-31"
5})
6
7# Response includes:
8# - Fund specifications
9# - All holdings with current positions
10# - Pricing sources and validation status
11# - Expense accruals
12# - Share class information
13# - Historical NAV trends
#### 4. Built-in Validation and Business Rules
MCP tools can encapsulate complex business logic that AI agents can invoke without understanding the underlying complexity.
1# AI agent doesn't need to understand investment restrictions
2# MCP tool handles all validation logic
3compliance_check = await mcp_client.call_tool("validate_investment_compliance", {
4 "fund_id": "FUND001",
5 "proposed_trade": {
6 "security_id": "SEC001",
7 "quantity": 10000,
8 "trade_type": "BUY"
9 }
10})
11
12# Returns comprehensive compliance analysis
13# - Concentration limits
14# - Asset class restrictions
15# - Regulatory requirements
16# - Risk limit impacts
Integration Complexity Comparison
#### Traditional API Integration
1class InvestmentDataService:
2 def __init__(self):
3 self.fund_api = FundAPI(base_url="https://funds.company.com/api")
4 self.security_api = SecurityAPI(base_url="https://securities.company.com/api")
5 self.pricing_api = PricingAPI(base_url="https://pricing.company.com/api")
6 self.risk_api = RiskAPI(base_url="https://risk.company.com/api")
7
8 async def validate_trade(self, fund_id, security_id, quantity):
9 # Multiple API calls with manual error handling
10 try:
11 fund = await self.fund_api.get_fund(fund_id)
12 security = await self.security_api.get_security(security_id)
13 restrictions = await self.fund_api.get_restrictions(fund_id)
14
15 # Manual validation logic
16 if security.asset_class not in fund.allowed_asset_classes:
17 return {"valid": False, "reason": "Asset class mismatch"}
18
19 # More validation logic...
20
21 except FundNotFoundError:
22 return {"valid": False, "reason": "Fund not found"}
23 except SecurityNotFoundError:
24 return {"valid": False, "reason": "Security not found"}
25 except Exception as e:
26 return {"valid": False, "reason": f"Validation failed: {str(e)}"}
#### MCP Integration
1class InvestmentMCPService:
2 def __init__(self):
3 self.mcp_client = MCPClient("fund-accounting-static-data")
4
5 async def validate_trade(self, fund_id, security_id, quantity):
6 # Single MCP tool call with comprehensive validation
7 return await self.mcp_client.call_tool("validate_fund_trade", {
8 "fund_id": fund_id,
9 "security_id": security_id,
10 "quantity": quantity,
11 "trade_type": "BUY"
12 })
13 # MCP handles all error cases, validation logic, and relationships
The MCP approach reduces integration complexity from hundreds of lines of code to just a few lines, while providing richer functionality and better error handling.
Use Case: Investment Management Fund Accounting
The Challenge
Investment management firms face several challenges when managing fund accounting data:
- Data Consistency: Multiple systems need access to the same fund reference data across portfolio management, accounting, and reporting
- Real-time Validation: Fund transactions must be validated against current fund parameters, restrictions, and regulatory requirements
- Complex Classifications: Securities need proper classification for risk management, regulatory reporting, and performance attribution
- Benchmark Tracking: Accurate benchmark and index data for performance measurement and risk analytics
- Regulatory Compliance: Proper fund categorization, risk metrics, and reporting requirements compliance
- Agent Integration: AI agents need structured access to this data for automated fund accounting, compliance monitoring, and reporting
The Solution: MCP Server Architecture
An MCP server provides a standardized interface that multiple AI agents can use to access mutual fund and ETF accounting static data. This ensures consistency, reduces duplication, and enables sophisticated fund accounting workflows.
Core Data Models
Fund and Security Structure
1from dataclasses import dataclass
2from typing import List, Optional, Dict
3from enum import Enum
4from decimal import Decimal
5from datetime import date
6
7class FundType(Enum):
8 MUTUAL_FUND = "MUTUAL_FUND"
9 ETF = "ETF"
10 CLOSED_END_FUND = "CLOSED_END_FUND"
11 HEDGE_FUND = "HEDGE_FUND"
12 MONEY_MARKET = "MONEY_MARKET"
13
14class AssetClass(Enum):
15 EQUITY = "EQUITY"
16 FIXED_INCOME = "FIXED_INCOME"
17 ALTERNATIVE = "ALTERNATIVE"
18 CASH = "CASH"
19 COMMODITY = "COMMODITY"
20 REAL_ESTATE = "REAL_ESTATE"
21
22class PricingSource(Enum):
23 BLOOMBERG = "BLOOMBERG"
24 REUTERS = "REUTERS"
25 FACTSET = "FACTSET"
26 MSCI = "MSCI"
27 VENDOR_SPECIFIC = "VENDOR_SPECIFIC"
28
29@dataclass
30class Fund:
31 fund_id: str
32 fund_name: str
33 fund_type: FundType
34 asset_class: AssetClass
35 inception_date: date
36 base_currency: str
37 expense_ratio: Decimal
38 management_fee: Decimal
39 benchmark_id: str
40 nav_frequency: str # DAILY, WEEKLY, MONTHLY
41 is_active: bool
42 regulatory_classification: str # 40 Act, UCITS, etc.
43 investment_objective: str
44 minimum_investment: Decimal
45 share_classes: List[str]
46
47@dataclass
48class ShareClass:
49 share_class_id: str
50 fund_id: str
51 class_name: str
52 ticker_symbol: Optional[str]
53 isin: Optional[str]
54 cusip: Optional[str]
55 expense_ratio: Decimal
56 management_fee: Decimal
57 distribution_fee: Decimal
58 load_front: Decimal
59 load_back: Decimal
60 minimum_investment: Decimal
61 currency: str
62 is_active: bool
63
64@dataclass
65class Benchmark:
66 benchmark_id: str
67 benchmark_name: str
68 provider: str
69 asset_class: AssetClass
70 currency: str
71 pricing_source: PricingSource
72 calculation_method: str
73 is_active: bool
74 constituent_count: Optional[int]
75
76@dataclass
77class SecurityMaster:
78 security_id: str
79 security_name: str
80 asset_class: AssetClass
81 security_type: str # STOCK, BOND, OPTION, FUTURE, etc.
82 currency: str
83 country_of_risk: str
84 sector: Optional[str]
85 industry: Optional[str]
86 pricing_source: PricingSource
87 exchange: Optional[str]
88 is_active: bool
89 identifiers: Dict[str, str] # ISIN, CUSIP, SEDOL, etc.
90
91@dataclass
92class PricingRule:
93 rule_id: str
94 security_id: str
95 pricing_source: PricingSource
96 pricing_method: str # MARKET, MODEL, BROKER, MATRIX
97 validation_rules: List[str]
98 fallback_sources: List[str]
99 stale_price_threshold_hours: int
100 is_active: bool
Python Implementation
MCP Server Setup
1import asyncio
2import json
3from typing import Any, Dict, List
4from decimal import Decimal
5from datetime import date, datetime
6from mcp.server import Server
7from mcp.server.stdio import stdio_server
8from mcp.types import (
9 Resource,
10 Tool,
11 TextContent,
12 GetResourceRequest,
13 CallToolRequest,
14 ListResourcesRequest,
15 ListToolsRequest
16)
17
18class FundAccountingMCPServer:
19 def __init__(self):
20 self.server = Server("fund-accounting-static-data")
21 self._setup_handlers()
22
23 # Sample data - in production, this would come from database
24 self.funds = self._load_funds()
25 self.share_classes = self._load_share_classes()
26 self.benchmarks = self._load_benchmarks()
27 self.security_master = self._load_security_master()
28 self.pricing_rules = self._load_pricing_rules()
29 self.fund_restrictions = self._load_fund_restrictions()
30
31 def _setup_handlers(self):
32 """Set up MCP protocol handlers"""
33
34 @self.server.list_resources()
35 async def list_resources() -> List[Resource]:
36 return [
37 Resource(
38 uri="funds://fund-master",
39 name="Fund Master",
40 mimeType="application/json",
41 description="Complete fund master data with specifications and parameters"
42 ),
43 Resource(
44 uri="funds://share-classes",
45 name="Share Classes",
46 mimeType="application/json",
47 description="Share class details with fees and minimums"
48 ),
49 Resource(
50 uri="funds://benchmarks",
51 name="Benchmark Indices",
52 mimeType="application/json",
53 description="Benchmark and index reference data"
54 ),
55 Resource(
56 uri="funds://security-master",
57 name="Security Master",
58 mimeType="application/json",
59 description="Security reference data with classifications"
60 ),
61 Resource(
62 uri="funds://pricing-rules",
63 name="Pricing Rules",
64 mimeType="application/json",
65 description="Security pricing methodology and validation rules"
66 ),
67 Resource(
68 uri="funds://fund-restrictions",
69 name="Fund Restrictions",
70 mimeType="application/json",
71 description="Investment restrictions and compliance rules by fund"
72 )
73 ]
74
75 @self.server.get_resource()
76 async def get_resource(request: GetResourceRequest) -> str:
77 if request.uri == "funds://fund-master":
78 return self._serialize_data(self.funds)
79 elif request.uri == "funds://share-classes":
80 return self._serialize_data(self.share_classes)
81 elif request.uri == "funds://benchmarks":
82 return self._serialize_data(self.benchmarks)
83 elif request.uri == "funds://security-master":
84 return self._serialize_data(self.security_master)
85 elif request.uri == "funds://pricing-rules":
86 return self._serialize_data(self.pricing_rules)
87 elif request.uri == "funds://fund-restrictions":
88 return self._serialize_data(self.fund_restrictions)
89 else:
90 raise ValueError(f"Unknown resource: {request.uri}")
91
92 @self.server.list_tools()
93 async def list_tools() -> List[Tool]:
94 return [
95 Tool(
96 name="validate_fund_trade",
97 description="Validate if a trade complies with fund restrictions and parameters",
98 inputSchema={
99 "type": "object",
100 "properties": {
101 "fund_id": {"type": "string"},
102 "security_id": {"type": "string"},
103 "quantity": {"type": "number"},
104 "trade_type": {"type": "string", "enum": ["BUY", "SELL"]},
105 "trade_amount": {"type": "number"}
106 },
107 "required": ["fund_id", "security_id", "quantity", "trade_type"]
108 }
109 ),
110 Tool(
111 name="get_fund_benchmark",
112 description="Get benchmark information for a specific fund",
113 inputSchema={
114 "type": "object",
115 "properties": {
116 "fund_id": {"type": "string"}
117 },
118 "required": ["fund_id"]
119 }
120 ),
121 Tool(
122 name="lookup_security_details",
123 description="Get detailed security information including identifiers and classifications",
124 inputSchema={
125 "type": "object",
126 "properties": {
127 "identifier": {"type": "string"},
128 "identifier_type": {"type": "string", "enum": ["ISIN", "CUSIP", "SEDOL", "TICKER", "INTERNAL_ID"]}
129 },
130 "required": ["identifier", "identifier_type"]
131 }
132 ),
133 Tool(
134 name="calculate_fund_metrics",
135 description="Calculate key fund metrics like expense ratios, turnover, etc.",
136 inputSchema={
137 "type": "object",
138 "properties": {
139 "fund_id": {"type": "string"},
140 "calculation_date": {"type": "string", "format": "date"},
141 "metrics": {"type": "array", "items": {"type": "string"}}
142 },
143 "required": ["fund_id", "calculation_date"]
144 }
145 ),
146 Tool(
147 name="get_pricing_source",
148 description="Get pricing source and methodology for a security",
149 inputSchema={
150 "type": "object",
151 "properties": {
152 "security_id": {"type": "string"},
153 "pricing_date": {"type": "string", "format": "date"}
154 },
155 "required": ["security_id"]
156 }
157 ),
158 Tool(
159 name="validate_fund_classification",
160 description="Validate fund classification for regulatory reporting",
161 inputSchema={
162 "type": "object",
163 "properties": {
164 "fund_id": {"type": "string"},
165 "classification_type": {"type": "string", "enum": ["GICS", "MORNINGSTAR", "REGULATORY", "CUSTOM"]}
166 },
167 "required": ["fund_id", "classification_type"]
168 }
169 )
170 ]
171
172 @self.server.call_tool()
173 async def call_tool(request: CallToolRequest) -> List[TextContent]:
174 tool_handlers = {
175 "validate_fund_trade": self._validate_fund_trade,
176 "get_fund_benchmark": self._get_fund_benchmark,
177 "lookup_security_details": self._lookup_security_details,
178 "calculate_fund_metrics": self._calculate_fund_metrics,
179 "get_pricing_source": self._get_pricing_source,
180 "validate_fund_classification": self._validate_fund_classification
181 }
182
183 if request.name in tool_handlers:
184 return await tool_handlers[request.name](request.arguments)
185 else:
186 raise ValueError(f"Unknown tool: {request.name}")
187
188 async def _validate_fund_trade(self, args: Dict[str, Any]) -> List[TextContent]:
189 """Validate fund trade against restrictions and parameters"""
190 fund_id = args["fund_id"]
191 security_id = args["security_id"]
192 quantity = args["quantity"]
193 trade_type = args["trade_type"]
194 trade_amount = args.get("trade_amount", 0)
195
196 # Find fund
197 fund = next((f for f in self.funds if f.fund_id == fund_id), None)
198 if not fund:
199 return self._create_response({
200 "valid": False,
201 "error": f"Fund {fund_id} not found"
202 })
203
204 # Find security
205 security = next((s for s in self.security_master if s.security_id == security_id), None)
206 if not security:
207 return self._create_response({
208 "valid": False,
209 "error": f"Security {security_id} not found"
210 })
211
212 # Get fund restrictions
213 restrictions = next((r for r in self.fund_restrictions if r["fund_id"] == fund_id), None)
214
215 validation_result = {
216 "valid": True,
217 "fund": self._dataclass_to_dict(fund),
218 "security": self._dataclass_to_dict(security),
219 "validations": [],
220 "warnings": []
221 }
222
223 # Validate asset class restrictions
224 if restrictions and "allowed_asset_classes" in restrictions:
225 if security.asset_class.value not in restrictions["allowed_asset_classes"]:
226 validation_result["valid"] = False
227 validation_result["validations"].append(
228 f"Asset class {security.asset_class.value} not allowed for fund {fund_id}"
229 )
230
231 # Validate concentration limits
232 if restrictions and "max_single_security_weight" in restrictions:
233 max_weight = restrictions["max_single_security_weight"]
234 validation_result["warnings"].append(
235 f"Monitor concentration limit: max {max_weight}% per security"
236 )
237
238 # Currency validation
239 if security.currency != fund.base_currency:
240 validation_result["warnings"].append(
241 f"Currency mismatch: Security in {security.currency}, Fund base currency {fund.base_currency}"
242 )
243
244 return self._create_response(validation_result)
245
246 async def _get_fund_benchmark(self, args: Dict[str, Any]) -> List[TextContent]:
247 """Get benchmark information for a fund"""
248 fund_id = args["fund_id"]
249
250 fund = next((f for f in self.funds if f.fund_id == fund_id), None)
251 if not fund:
252 return self._create_response({
253 "error": f"Fund {fund_id} not found"
254 })
255
256 benchmark = next((b for b in self.benchmarks if b.benchmark_id == fund.benchmark_id), None)
257 if not benchmark:
258 return self._create_response({
259 "error": f"Benchmark {fund.benchmark_id} not found for fund {fund_id}"
260 })
261
262 return self._create_response({
263 "fund_id": fund_id,
264 "fund_name": fund.fund_name,
265 "benchmark": self._dataclass_to_dict(benchmark)
266 })
267
268 async def _lookup_security_details(self, args: Dict[str, Any]) -> List[TextContent]:
269 """Lookup security by identifier"""
270 identifier = args["identifier"]
271 identifier_type = args["identifier_type"]
272
273 security = None
274
275 if identifier_type == "INTERNAL_ID":
276 security = next((s for s in self.security_master if s.security_id == identifier), None)
277 else:
278 security = next((s for s in self.security_master
279 if identifier in s.identifiers.values()), None)
280
281 if not security:
282 return self._create_response({
283 "found": False,
284 "message": f"Security not found for {identifier_type}: {identifier}"
285 })
286
287 # Get pricing rules for this security
288 pricing_rule = next((pr for pr in self.pricing_rules
289 if pr.security_id == security.security_id), None)
290
291 result = {
292 "found": True,
293 "security": self._dataclass_to_dict(security),
294 "pricing_rule": self._dataclass_to_dict(pricing_rule) if pricing_rule else None
295 }
296
297 return self._create_response(result)
298
299 async def _calculate_fund_metrics(self, args: Dict[str, Any]) -> List[TextContent]:
300 """Calculate fund metrics"""
301 fund_id = args["fund_id"]
302 calculation_date = args["calculation_date"]
303 requested_metrics = args.get("metrics", ["expense_ratio", "management_fee"])
304
305 fund = next((f for f in self.funds if f.fund_id == fund_id), None)
306 if not fund:
307 return self._create_response({
308 "error": f"Fund {fund_id} not found"
309 })
310
311 metrics = {}
312
313 if "expense_ratio" in requested_metrics:
314 metrics["expense_ratio"] = float(fund.expense_ratio)
315
316 if "management_fee" in requested_metrics:
317 metrics["management_fee"] = float(fund.management_fee)
318
319 if "total_net_assets" in requested_metrics:
320 # In production, this would be calculated from positions
321 metrics["total_net_assets"] = 1000000.0 # Sample value
322
323 if "number_of_holdings" in requested_metrics:
324 # In production, this would be calculated from positions
325 metrics["number_of_holdings"] = 150 # Sample value
326
327 return self._create_response({
328 "fund_id": fund_id,
329 "calculation_date": calculation_date,
330 "metrics": metrics
331 })
332
333 def _serialize_data(self, data):
334 """Serialize dataclass objects to JSON"""
335 if isinstance(data, list):
336 return json.dumps([self._dataclass_to_dict(item) for item in data], indent=2, default=str)
337 else:
338 return json.dumps(self._dataclass_to_dict(data), indent=2, default=str)
339
340 def _dataclass_to_dict(self, obj):
341 """Convert dataclass to dictionary"""
342 if hasattr(obj, '__dict__'):
343 result = {}
344 for key, value in obj.__dict__.items():
345 if isinstance(value, Enum):
346 result[key] = value.value
347 elif isinstance(value, (date, datetime)):
348 result[key] = value.isoformat()
349 elif isinstance(value, Decimal):
350 result[key] = float(value)
351 else:
352 result[key] = value
353 return result
354 return obj
355
356 def _create_response(self, data):
357 """Create standardized response"""
358 return [TextContent(
359 type="text",
360 text=json.dumps(data, indent=2, default=str)
361 )]
362
363 def _load_funds(self) -> List[Fund]:
364 """Load fund master data"""
365 return [
366 Fund(
367 fund_id="FUND001",
368 fund_name="Global Equity Growth Fund",
369 fund_type=FundType.MUTUAL_FUND,
370 asset_class=AssetClass.EQUITY,
371 inception_date=date(2010, 1, 15),
372 base_currency="USD",
373 expense_ratio=Decimal("0.0075"),
374 management_fee=Decimal("0.0050"),
375 benchmark_id="BENCH001",
376 nav_frequency="DAILY",
377 is_active=True,
378 regulatory_classification="40_ACT",
379 investment_objective="Long-term capital growth through global equity investments",
380 minimum_investment=Decimal("10000"),
381 share_classes=["A", "I", "C"]
382 ),
383 Fund(
384 fund_id="ETF001",
385 fund_name="Technology Sector ETF",
386 fund_type=FundType.ETF,
387 asset_class=AssetClass.EQUITY,
388 inception_date=date(2015, 6, 1),
389 base_currency="USD",
390 expense_ratio=Decimal("0.0025"),
391 management_fee=Decimal("0.0020"),
392 benchmark_id="BENCH002",
393 nav_frequency="DAILY",
394 is_active=True,
395 regulatory_classification="40_ACT",
396 investment_objective="Track the performance of technology sector stocks",
397 minimum_investment=Decimal("1"),
398 share_classes=["ETF"]
399 ),
400 Fund(
401 fund_id="FUND002",
402 fund_name="International Bond Fund",
403 fund_type=FundType.MUTUAL_FUND,
404 asset_class=AssetClass.FIXED_INCOME,
405 inception_date=date(2012, 3, 20),
406 base_currency="USD",
407 expense_ratio=Decimal("0.0060"),
408 management_fee=Decimal("0.0040"),
409 benchmark_id="BENCH003",
410 nav_frequency="DAILY",
411 is_active=True,
412 regulatory_classification="40_ACT",
413 investment_objective="Income generation through international fixed income securities",
414 minimum_investment=Decimal("5000"),
415 share_classes=["A", "I"]
416 )
417 ]
418
419 def _load_share_classes(self) -> List[ShareClass]:
420 """Load share class data"""
421 return [
422 ShareClass(
423 share_class_id="FUND001_A",
424 fund_id="FUND001",
425 class_name="Class A",
426 ticker_symbol="GEGRX",
427 isin="US12345678901",
428 cusip="123456789",
429 expense_ratio=Decimal("0.0100"),
430 management_fee=Decimal("0.0050"),
431 distribution_fee=Decimal("0.0025"),
432 load_front=Decimal("0.0575"),
433 load_back=Decimal("0.0000"),
434 minimum_investment=Decimal("10000"),
435 currency="USD",
436 is_active=True
437 ),
438 ShareClass(
439 share_class_id="FUND001_I",
440 fund_id="FUND001",
441 class_name="Institutional",
442 ticker_symbol="GEGIX",
443 isin="US12345678902",
444 cusip="123456790",
445 expense_ratio=Decimal("0.0075"),
446 management_fee=Decimal("0.0050"),
447 distribution_fee=Decimal("0.0000"),
448 load_front=Decimal("0.0000"),
449 load_back=Decimal("0.0000"),
450 minimum_investment=Decimal("1000000"),
451 currency="USD",
452 is_active=True
453 ),
454 ShareClass(
455 share_class_id="ETF001_ETF",
456 fund_id="ETF001",
457 class_name="ETF Shares",
458 ticker_symbol="TECH",
459 isin="US12345678903",
460 cusip="123456791",
461 expense_ratio=Decimal("0.0025"),
462 management_fee=Decimal("0.0020"),
463 distribution_fee=Decimal("0.0000"),
464 load_front=Decimal("0.0000"),
465 load_back=Decimal("0.0000"),
466 minimum_investment=Decimal("1"),
467 currency="USD",
468 is_active=True
469 )
470 ]
471
472 def _load_benchmarks(self) -> List[Benchmark]:
473 """Load benchmark data"""
474 return [
475 Benchmark(
476 benchmark_id="BENCH001",
477 benchmark_name="MSCI World Index",
478 provider="MSCI",
479 asset_class=AssetClass.EQUITY,
480 currency="USD",
481 pricing_source=PricingSource.MSCI,
482 calculation_method="MARKET_CAP_WEIGHTED",
483 is_active=True,
484 constituent_count=1500
485 ),
486 Benchmark(
487 benchmark_id="BENCH002",
488 benchmark_name="NASDAQ Technology Index",
489 provider="NASDAQ",
490 asset_class=AssetClass.EQUITY,
491 currency="USD",
492 pricing_source=PricingSource.BLOOMBERG,
493 calculation_method="MARKET_CAP_WEIGHTED",
494 is_active=True,
495 constituent_count=100
496 ),
497 Benchmark(
498 benchmark_id="BENCH003",
499 benchmark_name="Bloomberg Global Aggregate Bond Index",
500 provider="Bloomberg",
501 asset_class=AssetClass.FIXED_INCOME,
502 currency="USD",
503 pricing_source=PricingSource.BLOOMBERG,
504 calculation_method="MARKET_VALUE_WEIGHTED",
505 is_active=True,
506 constituent_count=25000
507 )
508 ]
509
510 def _load_security_master(self) -> List[SecurityMaster]:
511 """Load security master data"""
512 return [
513 SecurityMaster(
514 security_id="SEC001",
515 security_name="Apple Inc.",
516 asset_class=AssetClass.EQUITY,
517 security_type="COMMON_STOCK",
518 currency="USD",
519 country_of_risk="US",
520 sector="Technology",
521 industry="Technology Hardware",
522 pricing_source=PricingSource.BLOOMBERG,
523 exchange="NASDAQ",
524 is_active=True,
525 identifiers={
526 "ISIN": "US0378331005",
527 "CUSIP": "037833100",
528 "TICKER": "AAPL",
529 "SEDOL": "2046251"
530 }
531 ),
532 SecurityMaster(
533 security_id="SEC002",
534 security_name="US Treasury 2.5% 2030",
535 asset_class=AssetClass.FIXED_INCOME,
536 security_type="GOVERNMENT_BOND",
537 currency="USD",
538 country_of_risk="US",
539 sector="Government",
540 industry="Treasury",
541 pricing_source=PricingSource.BLOOMBERG,
542 exchange=None,
543 is_active=True,
544 identifiers={
545 "ISIN": "US912828XG55",
546 "CUSIP": "912828XG5",
547 "TICKER": "T 2.5 08/15/30"
548 }
549 ),
550 SecurityMaster(
551 security_id="SEC003",
552 security_name="Microsoft Corporation",
553 asset_class=AssetClass.EQUITY,
554 security_type="COMMON_STOCK",
555 currency="USD",
556 country_of_risk="US",
557 sector="Technology",
558 industry="Software",
559 pricing_source=PricingSource.BLOOMBERG,
560 exchange="NASDAQ",
561 is_active=True,
562 identifiers={
563 "ISIN": "US5949181045",
564 "CUSIP": "594918104",
565 "TICKER": "MSFT",
566 "SEDOL": "2588173"
567 }
568 )
569 ]
570
571 def _load_pricing_rules(self) -> List[PricingRule]:
572 """Load pricing rules"""
573 return [
574 PricingRule(
575 rule_id="RULE001",
576 security_id="SEC001",
577 pricing_source=PricingSource.BLOOMBERG,
578 pricing_method="MARKET",
579 validation_rules=["STALE_PRICE_CHECK", "PRICE_VARIANCE_CHECK"],
580 fallback_sources=["REUTERS", "FACTSET"],
581 stale_price_threshold_hours=24,
582 is_active=True
583 ),
584 PricingRule(
585 rule_id="RULE002",
586 security_id="SEC002",
587 pricing_source=PricingSource.BLOOMBERG,
588 pricing_method="MATRIX",
589 validation_rules=["YIELD_CURVE_CHECK", "CREDIT_SPREAD_CHECK"],
590 fallback_sources=["REUTERS"],
591 stale_price_threshold_hours=24,
592 is_active=True
593 ),
594 PricingRule(
595 rule_id="RULE003",
596 security_id="SEC003",
597 pricing_source=PricingSource.BLOOMBERG,
598 pricing_method="MARKET",
599 validation_rules=["STALE_PRICE_CHECK", "VOLUME_CHECK"],
600 fallback_sources=["REUTERS", "FACTSET"],
601 stale_price_threshold_hours=24,
602 is_active=True
603 )
604 ]
605
606 def _load_fund_restrictions(self) -> List[Dict]:
607 """Load fund investment restrictions"""
608 return [
609 {
610 "fund_id": "FUND001",
611 "allowed_asset_classes": ["EQUITY"],
612 "max_single_security_weight": 5.0,
613 "max_sector_weight": 25.0,
614 "max_country_weight": 30.0,
615 "min_holdings": 50,
616 "max_holdings": 200,
617 "liquidity_requirements": {
618 "min_daily_liquidity_pct": 10.0,
619 "max_illiquid_securities_pct": 15.0
620 }
621 },
622 {
623 "fund_id": "ETF001",
624 "allowed_asset_classes": ["EQUITY"],
625 "max_single_security_weight": 25.0,
626 "max_sector_weight": 100.0,
627 "sector_focus": "Technology",
628 "tracking_error_limit": 0.50,
629 "replication_method": "FULL"
630 },
631 {
632 "fund_id": "FUND002",
633 "allowed_asset_classes": ["FIXED_INCOME"],
634 "max_single_security_weight": 5.0,
635 "min_credit_quality": "BBB",
636 "max_duration": 10.0,
637 "currency_hedging": True,
638 "geographic_focus": "INTERNATIONAL_EX_US"
639 }
640 ]
641
642# Server startup
643async def main():
644 server_instance = FundAccountingMCPServer()
645
646 async with stdio_server() as (read_stream, write_stream):
647 await server_instance.server.run(
648 read_stream,
649 write_stream,
650 server_instance.server.create_initialization_options()
651 )
652
653if __name__ == "__main__":
654 asyncio.run(main())
Java Implementation
MCP Server in Java
1package com.investment.fund.mcp;
2
3import com.fasterxml.jackson.databind.ObjectMapper;
4import com.fasterxml.jackson.databind.node.ArrayNode;
5import com.fasterxml.jackson.databind.node.ObjectNode;
6import java.math.BigDecimal;
7import java.time.LocalDate;
8import java.util.*;
9import java.util.stream.Collectors;
10
11public class FundAccountingMCPServer {
12
13 private final ObjectMapper objectMapper = new ObjectMapper();
14 private final List<Fund> funds;
15 private final List<ShareClass> shareClasses;
16 private final List<Benchmark> benchmarks;
17 private final List<SecurityMaster> securityMaster;
18 private final List<PricingRule> pricingRules;
19
20 public FundAccountingMCPServer() {
21 this.funds = loadFunds();
22 this.shareClasses = loadShareClasses();
23 this.benchmarks = loadBenchmarks();
24 this.securityMaster = loadSecurityMaster();
25 this.pricingRules = loadPricingRules();
26 }
27
28 // Data Models
29 public static class Fund {
30 public String fundId;
31 public String fundName;
32 public FundType fundType;
33 public AssetClass assetClass;
34 public LocalDate inceptionDate;
35 public String baseCurrency;
36 public BigDecimal expenseRatio;
37 public BigDecimal managementFee;
38 public String benchmarkId;
39 public String navFrequency;
40 public boolean isActive;
41 public String regulatoryClassification;
42 public String investmentObjective;
43 public BigDecimal minimumInvestment;
44 public List<String> shareClasses;
45
46 public Fund(String fundId, String fundName, FundType fundType, AssetClass assetClass,
47 LocalDate inceptionDate, String baseCurrency, BigDecimal expenseRatio,
48 BigDecimal managementFee, String benchmarkId, String navFrequency,
49 boolean isActive, String regulatoryClassification, String investmentObjective,
50 BigDecimal minimumInvestment, List<String> shareClasses) {
51 this.fundId = fundId;
52 this.fundName = fundName;
53 this.fundType = fundType;
54 this.assetClass = assetClass;
55 this.inceptionDate = inceptionDate;
56 this.baseCurrency = baseCurrency;
57 this.expenseRatio = expenseRatio;
58 this.managementFee = managementFee;
59 this.benchmarkId = benchmarkId;
60 this.navFrequency = navFrequency;
61 this.isActive = isActive;
62 this.regulatoryClassification = regulatoryClassification;
63 this.investmentObjective = investmentObjective;
64 this.minimumInvestment = minimumInvestment;
65 this.shareClasses = shareClasses;
66 }
67 }
68
69 public static class ShareClass {
70 public String shareClassId;
71 public String fundId;
72 public String className;
73 public String tickerSymbol;
74 public String isin;
75 public String cusip;
76 public BigDecimal expenseRatio;
77 public BigDecimal managementFee;
78 public BigDecimal distributionFee;
79 public BigDecimal loadFront;
80 public BigDecimal loadBack;
81 public BigDecimal minimumInvestment;
82 public String currency;
83 public boolean isActive;
84
85 public ShareClass(String shareClassId, String fundId, String className, String tickerSymbol,
86 String isin, String cusip, BigDecimal expenseRatio, BigDecimal managementFee,
87 BigDecimal distributionFee, BigDecimal loadFront, BigDecimal loadBack,
88 BigDecimal minimumInvestment, String currency, boolean isActive) {
89 this.shareClassId = shareClassId;
90 this.fundId = fundId;
91 this.className = className;
92 this.tickerSymbol = tickerSymbol;
93 this.isin = isin;
94 this.cusip = cusip;
95 this.expenseRatio = expenseRatio;
96 this.managementFee = managementFee;
97 this.distributionFee = distributionFee;
98 this.loadFront = loadFront;
99 this.loadBack = loadBack;
100 this.minimumInvestment = minimumInvestment;
101 this.currency = currency;
102 this.isActive = isActive;
103 }
104 }
105
106 public static class Benchmark {
107 public String benchmarkId;
108 public String benchmarkName;
109 public String provider;
110 public AssetClass assetClass;
111 public String currency;
112 public PricingSource pricingSource;
113 public String calculationMethod;
114 public boolean isActive;
115 public Integer constituentCount;
116
117 public Benchmark(String benchmarkId, String benchmarkName, String provider,
118 AssetClass assetClass, String currency, PricingSource pricingSource,
119 String calculationMethod, boolean isActive, Integer constituentCount) {
120 this.benchmarkId = benchmarkId;
121 this.benchmarkName = benchmarkName;
122 this.provider = provider;
123 this.assetClass = assetClass;
124 this.currency = currency;
125 this.pricingSource = pricingSource;
126 this.calculationMethod = calculationMethod;
127 this.isActive = isActive;
128 this.constituentCount = constituentCount;
129 }
130 }
131
132 public static class SecurityMaster {
133 public String securityId;
134 public String securityName;
135 public AssetClass assetClass;
136 public String securityType;
137 public String currency;
138 public String countryOfRisk;
139 public String sector;
140 public String industry;
141 public PricingSource pricingSource;
142 public String exchange;
143 public boolean isActive;
144 public Map<String, String> identifiers;
145
146 public SecurityMaster(String securityId, String securityName, AssetClass assetClass,
147 String securityType, String currency, String countryOfRisk,
148 String sector, String industry, PricingSource pricingSource,
149 String exchange, boolean isActive, Map<String, String> identifiers) {
150 this.securityId = securityId;
151 this.securityName = securityName;
152 this.assetClass = assetClass;
153 this.securityType = securityType;
154 this.currency = currency;
155 this.countryOfRisk = countryOfRisk;
156 this.sector = sector;
157 this.industry = industry;
158 this.pricingSource = pricingSource;
159 this.exchange = exchange;
160 this.isActive = isActive;
161 this.identifiers = identifiers;
162 }
163 }
164
165 public static class PricingRule {
166 public String ruleId;
167 public String securityId;
168 public PricingSource pricingSource;
169 public String pricingMethod;
170 public List<String> validationRules;
171 public List<String> fallbackSources;
172 public int stalePriceThresholdHours;
173 public boolean isActive;
174
175 public PricingRule(String ruleId, String securityId, PricingSource pricingSource,
176 String pricingMethod, List<String> validationRules,
177 List<String> fallbackSources, int stalePriceThresholdHours,
178 boolean isActive) {
179 this.ruleId = ruleId;
180 this.securityId = securityId;
181 this.pricingSource = pricingSource;
182 this.pricingMethod = pricingMethod;
183 this.validationRules = validationRules;
184 this.fallbackSources = fallbackSources;
185 this.stalePriceThresholdHours = stalePriceThresholdHours;
186 this.isActive = isActive;
187 }
188 }
189
190 public enum FundType {
191 MUTUAL_FUND, ETF, CLOSED_END_FUND, HEDGE_FUND, MONEY_MARKET
192 }
193
194 public enum AssetClass {
195 EQUITY, FIXED_INCOME, ALTERNATIVE, CASH, COMMODITY, REAL_ESTATE
196 }
197
198 public enum PricingSource {
199 BLOOMBERG, REUTERS, FACTSET, MSCI, VENDOR_SPECIFIC
200 }
201
202 // MCP Protocol Handlers
203 public Object handleListResources() {
204 ObjectNode response = objectMapper.createObjectNode();
205 ArrayNode resources = objectMapper.createArrayNode();
206
207 resources.add(createResource(
208 "funds://fund-master",
209 "Fund Master",
210 "application/json",
211 "Complete fund master data with specifications and parameters"
212 ));
213
214 resources.add(createResource(
215 "funds://share-classes",
216 "Share Classes",
217 "application/json",
218 "Share class details with fees and minimums"
219 ));
220
221 resources.add(createResource(
222 "funds://benchmarks",
223 "Benchmark Indices",
224 "application/json",
225 "Benchmark and index reference data"
226 ));
227
228 resources.add(createResource(
229 "funds://security-master",
230 "Security Master",
231 "application/json",
232 "Security reference data with classifications"
233 ));
234
235 resources.add(createResource(
236 "funds://pricing-rules",
237 "Pricing Rules",
238 "application/json",
239 "Security pricing methodology and validation rules"
240 ));
241
242 response.set("resources", resources);
243 return response;
244 }
245
246 public Object handleGetResource(String uri) throws Exception {
247 switch (uri) {
248 case "funds://fund-master":
249 return objectMapper.writeValueAsString(funds);
250 case "funds://share-classes":
251 return objectMapper.writeValueAsString(shareClasses);
252 case "funds://benchmarks":
253 return objectMapper.writeValueAsString(benchmarks);
254 case "funds://security-master":
255 return objectMapper.writeValueAsString(securityMaster);
256 case "funds://pricing-rules":
257 return objectMapper.writeValueAsString(pricingRules);
258 default:
259 throw new IllegalArgumentException("Unknown resource: " + uri);
260 }
261 }
262
263 public Object handleListTools() {
264 ObjectNode response = objectMapper.createObjectNode();
265 ArrayNode tools = objectMapper.createArrayNode();
266
267 // Validate Fund Trade Tool
268 ObjectNode validateTradeTool = objectMapper.createObjectNode();
269 validateTradeTool.put("name", "validate_fund_trade");
270 validateTradeTool.put("description", "Validate if a trade complies with fund restrictions and parameters");
271
272 ObjectNode validateTradeSchema = objectMapper.createObjectNode();
273 validateTradeSchema.put("type", "object");
274 ObjectNode validateTradeProps = objectMapper.createObjectNode();
275 validateTradeProps.set("fund_id", objectMapper.createObjectNode().put("type", "string"));
276 validateTradeProps.set("security_id", objectMapper.createObjectNode().put("type", "string"));
277 validateTradeProps.set("quantity", objectMapper.createObjectNode().put("type", "number"));
278
279 ObjectNode tradeTypeEnum = objectMapper.createObjectNode();
280 tradeTypeEnum.put("type", "string");
281 ArrayNode tradeTypeOptions = objectMapper.createArrayNode();
282 tradeTypeOptions.add("BUY");
283 tradeTypeOptions.add("SELL");
284 tradeTypeEnum.set("enum", tradeTypeOptions);
285 validateTradeProps.set("trade_type", tradeTypeEnum);
286
287 validateTradeProps.set("trade_amount", objectMapper.createObjectNode().put("type", "number"));
288 validateTradeSchema.set("properties", validateTradeProps);
289
290 ArrayNode validateTradeRequired = objectMapper.createArrayNode();
291 validateTradeRequired.add("fund_id");
292 validateTradeRequired.add("security_id");
293 validateTradeRequired.add("quantity");
294 validateTradeRequired.add("trade_type");
295 validateTradeSchema.set("required", validateTradeRequired);
296 validateTradeTool.set("inputSchema", validateTradeSchema);
297
298 tools.add(validateTradeTool);
299
300 // Get Fund Benchmark Tool
301 ObjectNode benchmarkTool = objectMapper.createObjectNode();
302 benchmarkTool.put("name", "get_fund_benchmark");
303 benchmarkTool.put("description", "Get benchmark information for a specific fund");
304
305 ObjectNode benchmarkSchema = objectMapper.createObjectNode();
306 benchmarkSchema.put("type", "object");
307 ObjectNode benchmarkProps = objectMapper.createObjectNode();
308 benchmarkProps.set("fund_id", objectMapper.createObjectNode().put("type", "string"));
309 benchmarkSchema.set("properties", benchmarkProps);
310
311 ArrayNode benchmarkRequired = objectMapper.createArrayNode();
312 benchmarkRequired.add("fund_id");
313 benchmarkSchema.set("required", benchmarkRequired);
314 benchmarkTool.set("inputSchema", benchmarkSchema);
315
316 tools.add(benchmarkTool);
317
318 // Lookup Security Details Tool
319 ObjectNode securityTool = objectMapper.createObjectNode();
320 securityTool.put("name", "lookup_security_details");
321 securityTool.put("description", "Get detailed security information including identifiers and classifications");
322
323 ObjectNode securitySchema = objectMapper.createObjectNode();
324 securitySchema.put("type", "object");
325 ObjectNode securityProps = objectMapper.createObjectNode();
326 securityProps.set("identifier", objectMapper.createObjectNode().put("type", "string"));
327
328 ObjectNode identifierTypeEnum = objectMapper.createObjectNode();
329 identifierTypeEnum.put("type", "string");
330 ArrayNode identifierTypeOptions = objectMapper.createArrayNode();
331 identifierTypeOptions.add("ISIN");
332 identifierTypeOptions.add("CUSIP");
333 identifierTypeOptions.add("SEDOL");
334 identifierTypeOptions.add("TICKER");
335 identifierTypeOptions.add("INTERNAL_ID");
336 identifierTypeEnum.set("enum", identifierTypeOptions);
337 securityProps.set("identifier_type", identifierTypeEnum);
338
339 securitySchema.set("properties", securityProps);
340
341 ArrayNode securityRequired = objectMapper.createArrayNode();
342 securityRequired.add("identifier");
343 securityRequired.add("identifier_type");
344 securitySchema.set("required", securityRequired);
345 securityTool.set("inputSchema", securitySchema);
346
347 tools.add(securityTool);
348
349 response.set("tools", tools);
350 return response;
351 }
352
353 public Object handleCallTool(String toolName, Map<String, Object> arguments) throws Exception {
354 switch (toolName) {
355 case "validate_fund_trade":
356 return validateFundTrade(arguments);
357 case "get_fund_benchmark":
358 return getFundBenchmark(arguments);
359 case "lookup_security_details":
360 return lookupSecurityDetails(arguments);
361 default:
362 throw new IllegalArgumentException("Unknown tool: " + toolName);
363 }
364 }
365
366 // Tool Implementations
367 private Object validateFundTrade(Map<String, Object> args) {
368 String fundId = (String) args.get("fund_id");
369 String securityId = (String) args.get("security_id");
370 Double quantity = (Double) args.get("quantity");
371 String tradeType = (String) args.get("trade_type");
372
373 ObjectNode result = objectMapper.createObjectNode();
374
375 // Find fund
376 Fund fund = funds.stream()
377 .filter(f -> f.fundId.equals(fundId))
378 .findFirst()
379 .orElse(null);
380
381 if (fund == null) {
382 result.put("valid", false);
383 result.put("error", "Fund " + fundId + " not found");
384 return result;
385 }
386
387 // Find security
388 SecurityMaster security = securityMaster.stream()
389 .filter(s -> s.securityId.equals(securityId))
390 .findFirst()
391 .orElse(null);
392
393 if (security == null) {
394 result.put("valid", false);
395 result.put("error", "Security " + securityId + " not found");
396 return result;
397 }
398
399 result.put("valid", true);
400 result.set("fund", objectMapper.valueToTree(fund));
401 result.set("security", objectMapper.valueToTree(security));
402
403 ArrayNode validations = objectMapper.createArrayNode();
404 ArrayNode warnings = objectMapper.createArrayNode();
405
406 // Asset class validation
407 if (!security.assetClass.equals(fund.assetClass)) {
408 warnings.add("Asset class mismatch: Security is " + security.assetClass +
409 ", Fund focuses on " + fund.assetClass);
410 }
411
412 // Currency validation
413 if (!security.currency.equals(fund.baseCurrency)) {
414 warnings.add("Currency mismatch: Security in " + security.currency +
415 ", Fund base currency " + fund.baseCurrency);
416 }
417
418 result.set("validations", validations);
419 result.set("warnings", warnings);
420
421 return result;
422 }
423
424 private Object getFundBenchmark(Map<String, Object> args) {
425 String fundId = (String) args.get("fund_id");
426
427 Fund fund = funds.stream()
428 .filter(f -> f.fundId.equals(fundId))
429 .findFirst()
430 .orElse(null);
431
432 if (fund == null) {
433 ObjectNode error = objectMapper.createObjectNode();
434 error.put("error", "Fund " + fundId + " not found");
435 return error;
436 }
437
438 Benchmark benchmark = benchmarks.stream()
439 .filter(b -> b.benchmarkId.equals(fund.benchmarkId))
440 .findFirst()
441 .orElse(null);
442
443 if (benchmark == null) {
444 ObjectNode error = objectMapper.createObjectNode();
445 error.put("error", "Benchmark " + fund.benchmarkId + " not found for fund " + fundId);
446 return error;
447 }
448
449 ObjectNode result = objectMapper.createObjectNode();
450 result.put("fund_id", fundId);
451 result.put("fund_name", fund.fundName);
452 result.set("benchmark", objectMapper.valueToTree(benchmark));
453
454 return result;
455 }
456
457 private Object lookupSecurityDetails(Map<String, Object> args) {
458 String identifier = (String) args.get("identifier");
459 String identifierType = (String) args.get("identifier_type");
460
461 SecurityMaster security = null;
462
463 if ("INTERNAL_ID".equals(identifierType)) {
464 security = securityMaster.stream()
465 .filter(s -> s.securityId.equals(identifier))
466 .findFirst()
467 .orElse(null);
468 } else {
469 security = securityMaster.stream()
470 .filter(s -> s.identifiers.containsValue(identifier))
471 .findFirst()
472 .orElse(null);
473 }
474
475 ObjectNode result = objectMapper.createObjectNode();
476
477 if (security == null) {
478 result.put("found", false);
479 result.put("message", "Security not found for " + identifierType + ": " + identifier);
480 return result;
481 }
482
483 // Find pricing rule
484 PricingRule pricingRule = pricingRules.stream()
485 .filter(pr -> pr.securityId.equals(security.securityId))
486 .findFirst()
487 .orElse(null);
488
489 result.put("found", true);
490 result.set("security", objectMapper.valueToTree(security));
491 if (pricingRule != null) {
492 result.set("pricing_rule", objectMapper.valueToTree(pricingRule));
493 }
494
495 return result;
496 }
497
498 // Helper Methods
499 private ObjectNode createResource(String uri, String name, String mimeType, String description) {
500 ObjectNode resource = objectMapper.createObjectNode();
501 resource.put("uri", uri);
502 resource.put("name", name);
503 resource.put("mimeType", mimeType);
504 resource.put("description", description);
505 return resource;
506 }
507
508 // Data Loading Methods
509 private List<Fund> loadFunds() {
510 return Arrays.asList(
511 new Fund("FUND001", "Global Equity Growth Fund", FundType.MUTUAL_FUND, AssetClass.EQUITY,
512 LocalDate.of(2010, 1, 15), "USD", new BigDecimal("0.0075"),
513 new BigDecimal("0.0050"), "BENCH001", "DAILY", true, "40_ACT",
514 "Long-term capital growth through global equity investments",
515 new BigDecimal("10000"), Arrays.asList("A", "I", "C")),
516
517 new Fund("ETF001", "Technology Sector ETF", FundType.ETF, AssetClass.EQUITY,
518 LocalDate.of(2015, 6, 1), "USD", new BigDecimal("0.0025"),
519 new BigDecimal("0.0020"), "BENCH002", "DAILY", true, "40_ACT",
520 "Track the performance of technology sector stocks",
521 new BigDecimal("1"), Arrays.asList("ETF")),
522
523 new Fund("FUND002", "International Bond Fund", FundType.MUTUAL_FUND, AssetClass.FIXED_INCOME,
524 LocalDate.of(2012, 3, 20), "USD", new BigDecimal("0.0060"),
525 new BigDecimal("0.0040"), "BENCH003", "DAILY", true, "40_ACT",
526 "Income generation through international fixed income securities",
527 new BigDecimal("5000"), Arrays.asList("A", "I"))
528 );
529 }
530
531 private List<ShareClass> loadShareClasses() {
532 return Arrays.asList(
533 new ShareClass("FUND001_A", "FUND001", "Class A", "GEGRX", "US12345678901", "123456789",
534 new BigDecimal("0.0100"), new BigDecimal("0.0050"), new BigDecimal("0.0025"),
535 new BigDecimal("0.0575"), new BigDecimal("0.0000"), new BigDecimal("10000"),
536 "USD", true),
537
538 new ShareClass("FUND001_I", "FUND001", "Institutional", "GEGIX", "US12345678902", "123456790",
539 new BigDecimal("0.0075"), new BigDecimal("0.0050"), new BigDecimal("0.0000"),
540 new BigDecimal("0.0000"), new BigDecimal("0.0000"), new BigDecimal("1000000"),
541 "USD", true),
542
543 new ShareClass("ETF001_ETF", "ETF001", "ETF Shares", "TECH", "US12345678903", "123456791",
544 new BigDecimal("0.0025"), new BigDecimal("0.0020"), new BigDecimal("0.0000"),
545 new BigDecimal("0.0000"), new BigDecimal("0.0000"), new BigDecimal("1"),
546 "USD", true)
547 );
548 }
549
550 private List<Benchmark> loadBenchmarks() {
551 return Arrays.asList(
552 new Benchmark("BENCH001", "MSCI World Index", "MSCI", AssetClass.EQUITY, "USD",
553 PricingSource.MSCI, "MARKET_CAP_WEIGHTED", true, 1500),
554
555 new Benchmark("BENCH002", "NASDAQ Technology Index", "NASDAQ", AssetClass.EQUITY, "USD",
556 PricingSource.BLOOMBERG, "MARKET_CAP_WEIGHTED", true, 100),
557
558 new Benchmark("BENCH003", "Bloomberg Global Aggregate Bond Index", "Bloomberg",
559 AssetClass.FIXED_INCOME, "USD", PricingSource.BLOOMBERG,
560 "MARKET_VALUE_WEIGHTED", true, 25000)
561 );
562 }
563
564 private List<SecurityMaster> loadSecurityMaster() {
565 return Arrays.asList(
566 new SecurityMaster("SEC001", "Apple Inc.", AssetClass.EQUITY, "COMMON_STOCK", "USD", "US",
567 "Technology", "Technology Hardware", PricingSource.BLOOMBERG, "NASDAQ",
568 true, Map.of("ISIN", "US0378331005", "CUSIP", "037833100",
569 "TICKER", "AAPL", "SEDOL", "2046251")),
570
571 new SecurityMaster("SEC002", "US Treasury 2.5% 2030", AssetClass.FIXED_INCOME, "GOVERNMENT_BOND",
572 "USD", "US", "Government", "Treasury", PricingSource.BLOOMBERG, null,
573 true, Map.of("ISIN", "US912828XG55", "CUSIP", "912828XG5",
574 "TICKER", "T 2.5 08/15/30")),
575
576 new SecurityMaster("SEC003", "Microsoft Corporation", AssetClass.EQUITY, "COMMON_STOCK",
577 "USD", "US", "Technology", "Software", PricingSource.BLOOMBERG, "NASDAQ",
578 true, Map.of("ISIN", "US5949181045", "CUSIP", "594918104",
579 "TICKER", "MSFT", "SEDOL", "2588173"))
580 );
581 }
582
583 private List<PricingRule> loadPricingRules() {
584 return Arrays.asList(
585 new PricingRule("RULE001", "SEC001", PricingSource.BLOOMBERG, "MARKET",
586 Arrays.asList("STALE_PRICE_CHECK", "PRICE_VARIANCE_CHECK"),
587 Arrays.asList("REUTERS", "FACTSET"), 24, true),
588
589 new PricingRule("RULE002", "SEC002", PricingSource.BLOOMBERG, "MATRIX",
590 Arrays.asList("YIELD_CURVE_CHECK", "CREDIT_SPREAD_CHECK"),
591 Arrays.asList("REUTERS"), 24, true),
592
593 new PricingRule("RULE003", "SEC003", PricingSource.BLOOMBERG, "MARKET",
594 Arrays.asList("STALE_PRICE_CHECK", "VOLUME_CHECK"),
595 Arrays.asList("REUTERS", "FACTSET"), 24, true)
596 );
597 }
598}
Usage Examples for AI Agents
Fund Accounting Agent
1# Example of how an AI agent would use the MCP server for fund accounting
2import mcp_client
3
4async def process_fund_trade(trade_data):
5 mcp_client = MCPClient("fund-accounting-static-data")
6
7 # Validate fund trade
8 validation_result = await mcp_client.call_tool(
9 "validate_fund_trade",
10 {
11 "fund_id": trade_data["fund_id"],
12 "security_id": trade_data["security_id"],
13 "quantity": trade_data["quantity"],
14 "trade_type": trade_data["trade_type"],
15 "trade_amount": trade_data["trade_amount"]
16 }
17 )
18
19 if not validation_result["valid"]:
20 return {
21 "status": "error",
22 "message": validation_result["error"]
23 }
24
25 # Get benchmark for performance attribution
26 benchmark_result = await mcp_client.call_tool(
27 "get_fund_benchmark",
28 {"fund_id": trade_data["fund_id"]}
29 )
30
31 # Get detailed security information
32 security_details = await mcp_client.call_tool(
33 "lookup_security_details",
34 {
35 "identifier": trade_data["security_id"],
36 "identifier_type": "INTERNAL_ID"
37 }
38 )
39
40 return {
41 "status": "validated",
42 "fund_info": validation_result["fund"],
43 "security_info": security_details["security"],
44 "benchmark": benchmark_result["benchmark"],
45 "warnings": validation_result.get("warnings", []),
46 "pricing_rule": security_details.get("pricing_rule")
47 }
48
49async def calculate_fund_performance(fund_id, start_date, end_date):
50 mcp_client = MCPClient("fund-accounting-static-data")
51
52 # Get fund benchmark
53 benchmark_result = await mcp_client.call_tool(
54 "get_fund_benchmark",
55 {"fund_id": fund_id}
56 )
57
58 # Calculate fund metrics
59 metrics_result = await mcp_client.call_tool(
60 "calculate_fund_metrics",
61 {
62 "fund_id": fund_id,
63 "calculation_date": end_date,
64 "metrics": ["expense_ratio", "total_net_assets", "number_of_holdings"]
65 }
66 )
67
68 return {
69 "fund_id": fund_id,
70 "period": f"{start_date} to {end_date}",
71 "benchmark": benchmark_result["benchmark"],
72 "fund_metrics": metrics_result["metrics"]
73 }
Production Considerations
Database Integration for Investment Data
1class ProductionFundAccountingMCPServer(FundAccountingMCPServer):
2 def __init__(self, db_connection):
3 self.db = db_connection
4 super().__init__()
5
6 def _load_funds(self):
7 """Load from investment management database"""
8 query = """
9 SELECT fund_id, fund_name, fund_type, asset_class, inception_date,
10 base_currency, expense_ratio, management_fee, benchmark_id,
11 nav_frequency, is_active, regulatory_classification,
12 investment_objective, minimum_investment
13 FROM fund_master
14 WHERE is_active = true
15 ORDER BY fund_id
16 """
17 funds = []
18 for row in self.db.execute(query):
19 # Load share classes for this fund
20 share_classes = self._load_fund_share_classes(row['fund_id'])
21 funds.append(Fund(
22 fund_id=row['fund_id'],
23 fund_name=row['fund_name'],
24 fund_type=FundType(row['fund_type']),
25 asset_class=AssetClass(row['asset_class']),
26 inception_date=row['inception_date'],
27 base_currency=row['base_currency'],
28 expense_ratio=row['expense_ratio'],
29 management_fee=row['management_fee'],
30 benchmark_id=row['benchmark_id'],
31 nav_frequency=row['nav_frequency'],
32 is_active=row['is_active'],
33 regulatory_classification=row['regulatory_classification'],
34 investment_objective=row['investment_objective'],
35 minimum_investment=row['minimum_investment'],
36 share_classes=[sc.class_name for sc in share_classes]
37 ))
38 return funds
39
40 async def _validate_fund_trade(self, args):
41 """Enhanced validation with real-time portfolio data"""
42 validation_result = await super()._validate_fund_trade(args)
43
44 if validation_result["valid"]:
45 # Check current portfolio position
46 current_position = await self._get_current_position(
47 args["fund_id"], args["security_id"]
48 )
49
50 # Check fund restrictions in real-time
51 restriction_check = await self._check_investment_restrictions(
52 args["fund_id"], args["security_id"], args["quantity"]
53 )
54
55 validation_result["current_position"] = current_position
56 validation_result["restriction_compliance"] = restriction_check
57
58 return validation_result
59
60 async def _get_current_position(self, fund_id, security_id):
61 """Get current portfolio position"""
62 query = """
63 SELECT quantity, market_value, weight_percent
64 FROM portfolio_positions
65 WHERE fund_id = %s AND security_id = %s AND position_date = CURRENT_DATE
66 """
67 result = await self.db.fetch_one(query, fund_id, security_id)
68 return result if result else {"quantity": 0, "market_value": 0, "weight_percent": 0}
Risk Management Integration
1class RiskAwareFundAccountingMCPServer(ProductionFundAccountingMCPServer):
2 def __init__(self, db_connection, risk_service):
3 self.risk_service = risk_service
4 super().__init__(db_connection)
5
6 async def _validate_fund_trade(self, args):
7 """Enhanced validation with risk analytics"""
8 validation_result = await super()._validate_fund_trade(args)
9
10 if validation_result["valid"]:
11 # Calculate risk metrics impact
12 risk_impact = await self._calculate_risk_impact(
13 args["fund_id"], args["security_id"], args["quantity"], args["trade_type"]
14 )
15
16 # Check VaR limits
17 var_check = await self._check_var_limits(args["fund_id"], risk_impact)
18
19 # Check tracking error
20 tracking_error_check = await self._check_tracking_error(
21 args["fund_id"], risk_impact
22 )
23
24 validation_result["risk_impact"] = risk_impact
25 validation_result["var_compliance"] = var_check
26 validation_result["tracking_error_compliance"] = tracking_error_check
27
28 return validation_result
29
30 async def _calculate_risk_impact(self, fund_id, security_id, quantity, trade_type):
31 """Calculate risk impact of proposed trade"""
32 return await self.risk_service.calculate_trade_impact(
33 fund_id=fund_id,
34 security_id=security_id,
35 quantity=quantity,
36 trade_type=trade_type,
37 metrics=["VAR_1D", "VAR_10D", "TRACKING_ERROR", "BETA"]
38 )
Security and Compliance
1class ComplianceFundAccountingMCPServer(RiskAwareFundAccountingMCPServer):
2 def __init__(self, db_connection, risk_service, compliance_service):
3 self.compliance_service = compliance_service
4 super().__init__(db_connection, risk_service)
5
6 async def authenticate_request(self, request_context):
7 """Validate API keys and fund access permissions"""
8 api_key = request_context.get("api_key")
9 if not self.compliance_service.validate_api_key(api_key):
10 raise PermissionError("Invalid API key")
11
12 permissions = self.compliance_service.get_permissions(api_key)
13 return permissions
14
15 async def authorize_fund_access(self, fund_id, permissions):
16 """Check if client has access to specific fund"""
17 allowed_funds = permissions.get("allowed_funds", [])
18 if fund_id not in allowed_funds and "*" not in allowed_funds:
19 raise PermissionError(f"Access denied to fund {fund_id}")
20
21 async def _validate_fund_trade(self, args):
22 """Enhanced validation with regulatory compliance"""
23 validation_result = await super()._validate_fund_trade(args)
24
25 if validation_result["valid"]:
26 # Check regulatory limits (diversification, concentration, etc.)
27 regulatory_check = await self._check_regulatory_compliance(
28 args["fund_id"], args["security_id"], args["quantity"]
29 )
30
31 # Check investment restrictions (prohibited securities, etc.)
32 restriction_check = await self._check_investment_restrictions(
33 args["fund_id"], args["security_id"]
34 )
35
36 validation_result["regulatory_compliance"] = regulatory_check
37 validation_result["investment_restrictions"] = restriction_check
38
39 return validation_result
Advanced Features
Real-time NAV Calculation
1from decimal import Decimal
2import asyncio
3from typing import Dict, List
4
5class NAVCalculationMCPServer(ComplianceFundAccountingMCPServer):
6 def __init__(self, db_connection, risk_service, compliance_service, pricing_service):
7 self.pricing_service = pricing_service
8 super().__init__(db_connection, risk_service, compliance_service)
9
10 # Add NAV calculation tools
11 self._setup_nav_tools()
12
13 def _setup_nav_tools(self):
14 """Add NAV-specific tools"""
15
16 @self.server.list_tools()
17 async def enhanced_list_tools():
18 base_tools = await super().list_tools()
19 nav_tools = [
20 Tool(
21 name="calculate_fund_nav",
22 description="Calculate Net Asset Value for a fund",
23 inputSchema={
24 "type": "object",
25 "properties": {
26 "fund_id": {"type": "string"},
27 "nav_date": {"type": "string", "format": "date"},
28 "share_class": {"type": "string"}
29 },
30 "required": ["fund_id", "nav_date"]
31 }
32 ),
33 Tool(
34 name="get_fund_holdings",
35 description="Get current fund holdings with market values",
36 inputSchema={
37 "type": "object",
38 "properties": {
39 "fund_id": {"type": "string"},
40 "as_of_date": {"type": "string", "format": "date"}
41 },
42 "required": ["fund_id", "as_of_date"]
43 }
44 ),
45 Tool(
46 name="validate_pricing",
47 description="Validate security pricing for NAV calculation",
48 inputSchema={
49 "type": "object",
50 "properties": {
51 "security_ids": {"type": "array", "items": {"type": "string"}},
52 "pricing_date": {"type": "string", "format": "date"}
53 },
54 "required": ["security_ids", "pricing_date"]
55 }
56 )
57 ]
58 return base_tools + nav_tools
59
60 async def _calculate_fund_nav(self, args: Dict[str, Any]) -> List[TextContent]:
61 """Calculate fund NAV"""
62 fund_id = args["fund_id"]
63 nav_date = args["nav_date"]
64 share_class = args.get("share_class")
65
66 try:
67 # Get fund information
68 fund = next((f for f in self.funds if f.fund_id == fund_id), None)
69 if not fund:
70 return self._create_response({
71 "error": f"Fund {fund_id} not found"
72 })
73
74 # Get fund holdings
75 holdings = await self._get_fund_holdings_data(fund_id, nav_date)
76
77 # Get security prices
78 security_ids = [h["security_id"] for h in holdings]
79 prices = await self.pricing_service.get_prices(security_ids, nav_date)
80
81 # Calculate portfolio value
82 total_market_value = Decimal("0")
83 holding_details = []
84
85 for holding in holdings:
86 security_id = holding["security_id"]
87 quantity = Decimal(str(holding["quantity"]))
88 price = prices.get(security_id, {}).get("price", Decimal("0"))
89
90 market_value = quantity * price
91 total_market_value += market_value
92
93 holding_details.append({
94 "security_id": security_id,
95 "quantity": float(quantity),
96 "price": float(price),
97 "market_value": float(market_value),
98 "weight": 0 # Will calculate after total is known
99 })
100
101 # Calculate weights
102 for holding in holding_details:
103 if total_market_value > 0:
104 holding["weight"] = holding["market_value"] / float(total_market_value)
105
106 # Get fund expenses and liabilities
107 expenses = await self._get_fund_expenses(fund_id, nav_date)
108 liabilities = await self._get_fund_liabilities(fund_id, nav_date)
109
110 # Calculate NAV
111 net_assets = total_market_value - expenses - liabilities
112
113 # Get shares outstanding
114 if share_class:
115 shares_outstanding = await self._get_shares_outstanding(fund_id, share_class, nav_date)
116 nav_per_share = net_assets / shares_outstanding if shares_outstanding > 0 else Decimal("0")
117
118 nav_result = {
119 "fund_id": fund_id,
120 "share_class": share_class,
121 "nav_date": nav_date,
122 "total_market_value": float(total_market_value),
123 "expenses": float(expenses),
124 "liabilities": float(liabilities),
125 "net_assets": float(net_assets),
126 "shares_outstanding": float(shares_outstanding),
127 "nav_per_share": float(nav_per_share),
128 "holdings": holding_details
129 }
130 else:
131 # Calculate for all share classes
132 share_classes = await self._get_fund_share_classes(fund_id)
133 nav_by_class = {}
134
135 for sc in share_classes:
136 shares = await self._get_shares_outstanding(fund_id, sc.class_name, nav_date)
137 nav_per_share = net_assets / shares if shares > 0 else Decimal("0")
138
139 nav_by_class[sc.class_name] = {
140 "shares_outstanding": float(shares),
141 "nav_per_share": float(nav_per_share)
142 }
143
144 nav_result = {
145 "fund_id": fund_id,
146 "nav_date": nav_date,
147 "total_market_value": float(total_market_value),
148 "expenses": float(expenses),
149 "liabilities": float(liabilities),
150 "net_assets": float(net_assets),
151 "nav_by_share_class": nav_by_class,
152 "holdings": holding_details
153 }
154
155 return self._create_response(nav_result)
156
157 except Exception as e:
158 return self._create_response({
159 "error": f"NAV calculation failed: {str(e)}"
160 })
161
162 async def _get_fund_holdings_data(self, fund_id: str, as_of_date: str) -> List[Dict]:
163 """Get fund holdings from database"""
164 query = """
165 SELECT security_id, quantity, original_cost
166 FROM portfolio_positions
167 WHERE fund_id = %s AND position_date = %s AND quantity > 0
168 """
169 return await self.db.fetch_all(query, fund_id, as_of_date)
Performance Attribution
1class PerformanceAttributionMCPServer(NAVCalculationMCPServer):
2 def __init__(self, db_connection, risk_service, compliance_service, pricing_service, attribution_service):
3 self.attribution_service = attribution_service
4 super().__init__(db_connection, risk_service, compliance_service, pricing_service)
5 self._setup_attribution_tools()
6
7 def _setup_attribution_tools(self):
8 """Add performance attribution tools"""
9
10 @self.server.list_tools()
11 async def enhanced_list_tools():
12 base_tools = await super().list_tools()
13 attribution_tools = [
14 Tool(
15 name="calculate_performance_attribution",
16 description="Calculate performance attribution vs benchmark",
17 inputSchema={
18 "type": "object",
19 "properties": {
20 "fund_id": {"type": "string"},
21 "start_date": {"type": "string", "format": "date"},
22 "end_date": {"type": "string", "format": "date"},
23 "attribution_method": {"type": "string", "enum": ["BRINSON", "FAMA_FRENCH", "CUSTOM"]}
24 },
25 "required": ["fund_id", "start_date", "end_date"]
26 }
27 ),
28 Tool(
29 name="calculate_fund_returns",
30 description="Calculate fund returns over specified period",
31 inputSchema={
32 "type": "object",
33 "properties": {
34 "fund_id": {"type": "string"},
35 "start_date": {"type": "string", "format": "date"},
36 "end_date": {"type": "string", "format": "date"},
37 "return_frequency": {"type": "string", "enum": ["DAILY", "MONTHLY", "QUARTERLY"]}
38 },
39 "required": ["fund_id", "start_date", "end_date"]
40 }
41 )
42 ]
43 return base_tools + attribution_tools
44
45 async def _calculate_performance_attribution(self, args: Dict[str, Any]) -> List[TextContent]:
46 """Calculate performance attribution analysis"""
47 fund_id = args["fund_id"]
48 start_date = args["start_date"]
49 end_date = args["end_date"]
50 attribution_method = args.get("attribution_method", "BRINSON")
51
52 try:
53 # Get fund and benchmark information
54 fund = next((f for f in self.funds if f.fund_id == fund_id), None)
55 if not fund:
56 return self._create_response({"error": f"Fund {fund_id} not found"})
57
58 benchmark = next((b for b in self.benchmarks if b.benchmark_id == fund.benchmark_id), None)
59 if not benchmark:
60 return self._create_response({"error": f"Benchmark {fund.benchmark_id} not found"})
61
62 # Calculate fund returns
63 fund_returns = await self._calculate_period_returns(fund_id, start_date, end_date)
64
65 # Get benchmark returns
66 benchmark_returns = await self._get_benchmark_returns(fund.benchmark_id, start_date, end_date)
67
68 # Calculate attribution components
69 attribution_result = await self.attribution_service.calculate_attribution(
70 fund_id=fund_id,
71 benchmark_id=fund.benchmark_id,
72 start_date=start_date,
73 end_date=end_date,
74 method=attribution_method
75 )
76
77 result = {
78 "fund_id": fund_id,
79 "benchmark_id": fund.benchmark_id,
80 "period": f"{start_date} to {end_date}",
81 "fund_return": fund_returns["total_return"],
82 "benchmark_return": benchmark_returns["total_return"],
83 "excess_return": fund_returns["total_return"] - benchmark_returns["total_return"],
84 "attribution_method": attribution_method,
85 "attribution_analysis": attribution_result
86 }
87
88 return self._create_response(result)
89
90 except Exception as e:
91 return self._create_response({
92 "error": f"Performance attribution calculation failed: {str(e)}"
93 })
Testing Strategy
Unit Tests for Fund Accounting
1import pytest
2import asyncio
3from decimal import Decimal
4from datetime import date
5from unittest.mock import Mock, patch
6from fund_accounting_mcp_server import FundAccountingMCPServer, Fund, FundType, AssetClass
7
8class TestFundAccountingMCPServer:
9 @pytest.fixture
10 def server(self):
11 return FundAccountingMCPServer()
12
13 @pytest.fixture
14 def sample_fund(self):
15 return Fund(
16 fund_id="TEST001",
17 fund_name="Test Fund",
18 fund_type=FundType.MUTUAL_FUND,
19 asset_class=AssetClass.EQUITY,
20 inception_date=date(2020, 1, 1),
21 base_currency="USD",
22 expense_ratio=Decimal("0.0075"),
23 management_fee=Decimal("0.0050"),
24 benchmark_id="BENCH001",
25 nav_frequency="DAILY",
26 is_active=True,
27 regulatory_classification="40_ACT",
28 investment_objective="Test objective",
29 minimum_investment=Decimal("10000"),
30 share_classes=["A", "I"]
31 )
32
33 @pytest.mark.asyncio
34 async def test_validate_fund_trade_valid(self, server):
35 """Test validation of a valid fund trade"""
36 args = {
37 "fund_id": "FUND001",
38 "security_id": "SEC001",
39 "quantity": 1000,
40 "trade_type": "BUY",
41 "trade_amount": 150000.0
42 }
43
44 result = await server._validate_fund_trade(args)
45 result_data = json.loads(result[0].text)
46
47 assert result_data["valid"] is True
48 assert "fund" in result_data
49 assert "security" in result_data
50 assert result_data["fund"]["fund_id"] == "FUND001"
51
52 @pytest.mark.asyncio
53 async def test_validate_fund_trade_invalid_fund(self, server):
54 """Test validation with invalid fund ID"""
55 args = {
56 "fund_id": "INVALID",
57 "security_id": "SEC001",
58 "quantity": 1000,
59 "trade_type": "BUY"
60 }
61
62 result = await server._validate_fund_trade(args)
63 result_data = json.loads(result[0].text)
64
65 assert result_data["valid"] is False
66 assert "not found" in result_data["error"]
67
68 @pytest.mark.asyncio
69 async def test_get_fund_benchmark(self, server):
70 """Test benchmark retrieval for fund"""
71 args = {"fund_id": "FUND001"}
72
73 result = await server._get_fund_benchmark(args)
74 result_data = json.loads(result[0].text)
75
76 assert "benchmark" in result_data
77 assert result_data["fund_id"] == "FUND001"
78 assert result_data["benchmark"]["benchmark_id"] == "BENCH001"
79
80 @pytest.mark.asyncio
81 async def test_lookup_security_by_isin(self, server):
82 """Test security lookup by ISIN"""
83 args = {
84 "identifier": "US0378331005",
85 "identifier_type": "ISIN"
86 }
87
88 result = await server._lookup_security_details(args)
89 result_data = json.loads(result[0].text)
90
91 assert result_data["found"] is True
92 assert result_data["security"]["security_name"] == "Apple Inc."
93 assert result_data["security"]["identifiers"]["ISIN"] == "US0378331005"
94
95 @pytest.mark.asyncio
96 async def test_calculate_fund_metrics(self, server):
97 """Test fund metrics calculation"""
98 args = {
99 "fund_id": "FUND001",
100 "calculation_date": "2023-12-31",
101 "metrics": ["expense_ratio", "management_fee"]
102 }
103
104 result = await server._calculate_fund_metrics(args)
105 result_data = json.loads(result[0].text)
106
107 assert "metrics" in result_data
108 assert "expense_ratio" in result_data["metrics"]
109 assert "management_fee" in result_data["metrics"]
110 assert result_data["metrics"]["expense_ratio"] == 0.0075
Integration Tests for Fund Operations
1import pytest
2import asyncio
3from mcp_test_client import MCPTestClient
4
5class TestFundAccountingMCPIntegration:
6 @pytest.fixture
7 def mcp_client(self):
8 return MCPTestClient("fund-accounting-static-data")
9
10 @pytest.mark.asyncio
11 async def test_complete_fund_trade_workflow(self, mcp_client):
12 """Test complete fund trade validation workflow"""
13
14 # Step 1: Validate the trade
15 validation_result = await mcp_client.call_tool(
16 "validate_fund_trade",
17 {
18 "fund_id": "FUND001",
19 "security_id": "SEC001",
20 "quantity": 500,
21 "trade_type": "BUY",
22 "trade_amount": 75000.0
23 }
24 )
25
26 assert validation_result["valid"] is True
27 assert validation_result["fund"]["asset_class"] == "EQUITY"
28
29 # Step 2: Get benchmark information
30 benchmark_result = await mcp_client.call_tool(
31 "get_fund_benchmark",
32 {"fund_id": "FUND001"}
33 )
34
35 assert "benchmark" in benchmark_result
36 assert benchmark_result["benchmark"]["asset_class"] == "EQUITY"
37
38 # Step 3: Get detailed security information
39 security_result = await mcp_client.call_tool(
40 "lookup_security_details",
41 {
42 "identifier": "SEC001",
43 "identifier_type": "INTERNAL_ID"
44 }
45 )
46
47 assert security_result["found"] is True
48 assert security_result["security"]["asset_class"] == "EQUITY"
49 assert "pricing_rule" in security_result
50
51 @pytest.mark.asyncio
52 async def test_etf_vs_mutual_fund_characteristics(self, mcp_client):
53 """Test differences between ETF and mutual fund handling"""
54
55 # Test mutual fund
56 mf_validation = await mcp_client.call_tool(
57 "validate_fund_trade",
58 {
59 "fund_id": "FUND001", # Mutual Fund
60 "security_id": "SEC001",
61 "quantity": 100,
62 "trade_type": "BUY"
63 }
64 )
65
66 # Test ETF
67 etf_validation = await mcp_client.call_tool(
68 "validate_fund_trade",
69 {
70 "fund_id": "ETF001", # ETF
71 "security_id": "SEC003",
72 "quantity": 100,
73 "trade_type": "BUY"
74 }
75 )
76
77 assert mf_validation["fund"]["fund_type"] == "MUTUAL_FUND"
78 assert etf_validation["fund"]["fund_type"] == "ETF"
79
80 # ETF should have lower expense ratio
81 assert etf_validation["fund"]["expense_ratio"] < mf_validation["fund"]["expense_ratio"]
Performance Tests
1import asyncio
2import time
3import statistics
4from concurrent.futures import ThreadPoolExecutor
5
6class FundAccountingLoadTester:
7 def __init__(self, mcp_client, num_concurrent_requests=100):
8 self.mcp_client = mcp_client
9 self.num_concurrent_requests = num_concurrent_requests
10 self.results = []
11
12 async def single_fund_lookup(self):
13 """Simulate a single fund lookup request"""
14 start_time = time.time()
15
16 try:
17 result = await self.mcp_client.call_tool(
18 "lookup_security_details",
19 {
20 "identifier": "US0378331005",
21 "identifier_type": "ISIN"
22 }
23 )
24 duration = time.time() - start_time
25 self.results.append({
26 "success": True,
27 "duration": duration,
28 "response_size": len(str(result))
29 })
30 except Exception as e:
31 duration = time.time() - start_time
32 self.results.append({
33 "success": False,
34 "duration": duration,
35 "error": str(e)
36 })
37
38 async def single_trade_validation(self):
39 """Simulate a single trade validation request"""
40 start_time = time.time()
41
42 try:
43 result = await self.mcp_client.call_tool(
44 "validate_fund_trade",
45 {
46 "fund_id": "FUND001",
47 "security_id": "SEC001",
48 "quantity": 100,
49 "trade_type": "BUY"
50 }
51 )
52 duration = time.time() - start_time
53 self.results.append({
54 "success": True,
55 "duration": duration,
56 "operation": "trade_validation"
57 })
58 except Exception as e:
59 duration = time.time() - start_time
60 self.results.append({
61 "success": False,
62 "duration": duration,
63 "error": str(e),
64 "operation": "trade_validation"
65 })
66
67 async def run_mixed_load_test(self, duration_seconds=300):
68 """Run mixed load test with various operations"""
69 end_time = time.time() + duration_seconds
70
71 while time.time() < end_time:
72 # Mix of different operations
73 tasks = []
74
75 # 50% trade validations
76 for _ in range(self.num_concurrent_requests // 2):
77 tasks.append(self.single_trade_validation())
78
79 # 30% security lookups
80 for _ in range(int(self.num_concurrent_requests * 0.3)):
81 tasks.append(self.single_fund_lookup())
82
83 # 20% benchmark lookups
84 for _ in range(int(self.num_concurrent_requests * 0.2)):
85 tasks.append(self.single_benchmark_lookup())
86
87 await asyncio.gather(*tasks)
88 await asyncio.sleep(0.1) # Brief pause between batches
89
90 self.print_detailed_results()
91
92 def print_detailed_results(self):
93 """Print detailed load test results"""
94 successful_requests = [r for r in self.results if r["success"]]
95 failed_requests = [r for r in self.results if not r["success"]]
96
97 if successful_requests:
98 durations = [r["duration"] for r in successful_requests]
99
100 print(f"Fund Accounting MCP Load Test Results:")
101 print(f" Total Requests: {len(self.results)}")
102 print(f" Successful: {len(successful_requests)}")
103 print(f" Failed: {len(failed_requests)}")
104 print(f" Success Rate: {len(successful_requests)/len(self.results)*100:.2f}%")
105 print(f" Average Response Time: {statistics.mean(durations):.3f}s")
106 print(f" Median Response Time: {statistics.median(durations):.3f}s")
107 print(f" 95th Percentile: {statistics.quantiles(durations, n=20)[18]:.3f}s")
108 print(f" 99th Percentile: {statistics.quantiles(durations, n=100)[98]:.3f}s")
109 print(f" Requests/Second: {len(self.results)/300:.2f}")
110
111 # Break down by operation type
112 operations = {}
113 for result in successful_requests:
114 op_type = result.get("operation", "unknown")
115 if op_type not in operations:
116 operations[op_type] = []
117 operations[op_type].append(result["duration"])
118
119 print(f"\n Performance by Operation Type:")
120 for op_type, times in operations.items():
121 print(f" {op_type}: avg {statistics.mean(times):.3f}s, "
122 f"95th {statistics.quantiles(times, n=20)[18]:.3f}s")
Deployment
Docker Configuration for Fund Accounting
1# Dockerfile
2FROM python:3.11-slim
3
4WORKDIR /app
5
6# Install system dependencies for financial calculations
7RUN apt-get update && apt-get install -y \
8 gcc \
9 g++ \
10 gfortran \
11 libopenblas-dev \
12 liblapack-dev \
13 && rm -rf /var/lib/apt/lists/*
14
15# Copy requirements and install Python dependencies
16COPY requirements.txt .
17RUN pip install --no-cache-dir -r requirements.txt
18
19# Copy application code
20COPY . .
21
22# Create non-root user
23RUN useradd --create-home --shell /bin/bash fundmcp
24RUN chown -R fundmcp:fundmcp /app
25USER fundmcp
26
27# Health check
28HEALTHCHECK \
29 CMD python -c "import requests; requests.get('http://localhost:8080/health')"
30
31# Expose port
32EXPOSE 8080
33
34# Run the application
35CMD ["python", "-m", "uvicorn", "fund_accounting_mcp_server:app", "--host", "0.0.0.0", "--port", "8080"]
Docker Compose for Investment Management Stack
1# docker-compose.yml
2version: '3.8'
3
4services:
5 fund-accounting-mcp-server:
6 build: .
7 ports:
8 - "8080:8080"
9 environment:
10 - DATABASE_URL=postgresql://funduser:fundpass@postgres:5432/investment_db
11 - REDIS_URL=redis://redis:6379
12 - PRICING_SERVICE_URL=http://pricing-service:8081
13 - RISK_SERVICE_URL=http://risk-service:8082
14 - LOG_LEVEL=INFO
15 - ENVIRONMENT=production
16 depends_on:
17 - postgres
18 - redis
19 - pricing-service
20 - risk-service
21 volumes:
22 - ./config.yaml:/app/config.yaml:ro
23 restart: unless-stopped
24 networks:
25 - fund-network
26
27 postgres:
28 image: postgres:15
29 environment:
30 - POSTGRES_DB=investment_db
31 - POSTGRES_USER=funduser
32 - POSTGRES_PASSWORD=fundpass
33 volumes:
34 - postgres_data:/var/lib/postgresql/data
35 - ./sql/init_investment_db.sql:/docker-entrypoint-initdb.d/init.sql
36 networks:
37 - fund-network
38
39 redis:
40 image: redis:7-alpine
41 command: redis-server --appendonly yes --maxmemory 512mb --maxmemory-policy allkeys-lru
42 volumes:
43 - redis_data:/data
44 networks:
45 - fund-network
46
47 pricing-service:
48 image: company/pricing-service:latest
49 ports:
50 - "8081:8081"
51 environment:
52 - BLOOMBERG_API_KEY=${BLOOMBERG_API_KEY}
53 - REUTERS_API_KEY=${REUTERS_API_KEY}
54 networks:
55 - fund-network
56
57 risk-service:
58 image: company/risk-analytics:latest
59 ports:
60 - "8082:8082"
61 environment:
62 - RISK_MODEL_PATH=/models
63 volumes:
64 - ./risk_models:/models:ro
65 networks:
66 - fund-network
67
68 prometheus:
69 image: prom/prometheus
70 ports:
71 - "9090:9090"
72 volumes:
73 - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
74 networks:
75 - fund-network
76
77 grafana:
78 image: grafana/grafana
79 ports:
80 - "3000:3000"
81 environment:
82 - GF_SECURITY_ADMIN_PASSWORD=admin
83 volumes:
84 - grafana_data:/var/lib/grafana
85 - ./monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards
86 networks:
87 - fund-network
88
89volumes:
90 postgres_data:
91 redis_data:
92 grafana_data:
93
94networks:
95 fund-network:
96 driver: bridge
Production Database Schema for Investment Management
1-- sql/init_investment_db.sql
2-- Fund Master Tables
3CREATE TABLE fund_master (
4 fund_id VARCHAR(20) PRIMARY KEY,
5 fund_name VARCHAR(255) NOT NULL,
6 fund_type VARCHAR(20) NOT NULL,
7 asset_class VARCHAR(30) NOT NULL,
8 inception_date DATE NOT NULL,
9 base_currency CHAR(3) NOT NULL,
10 expense_ratio DECIMAL(8,6) DEFAULT 0,
11 management_fee DECIMAL(8,6) DEFAULT 0,
12 benchmark_id VARCHAR(20),
13 nav_frequency VARCHAR(10) DEFAULT 'DAILY',
14 is_active BOOLEAN DEFAULT true,
15 regulatory_classification VARCHAR(50),
16 investment_objective TEXT,
17 minimum_investment DECIMAL(15,2) DEFAULT 0,
18 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
19 updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
20);
21
22CREATE TABLE share_classes (
23 share_class_id VARCHAR(30) PRIMARY KEY,
24 fund_id VARCHAR(20) NOT NULL REFERENCES fund_master(fund_id),
25 class_name VARCHAR(50) NOT NULL,
26 ticker_symbol VARCHAR(10),
27 isin VARCHAR(12),
28 cusip VARCHAR(9),
29 expense_ratio DECIMAL(8,6) DEFAULT 0,
30 management_fee DECIMAL(8,6) DEFAULT 0,
31 distribution_fee DECIMAL(8,6) DEFAULT 0,
32 load_front DECIMAL(6,4) DEFAULT 0,
33 load_back DECIMAL(6,4) DEFAULT 0,
34 minimum_investment DECIMAL(15,2) DEFAULT 0,
35 currency CHAR(3) NOT NULL,
36 is_active BOOLEAN DEFAULT true,
37 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
38 updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
39);
40
41CREATE TABLE benchmarks (
42 benchmark_id VARCHAR(20) PRIMARY KEY,
43 benchmark_name VARCHAR(255) NOT NULL,
44 provider VARCHAR(100),
45 asset_class VARCHAR(30) NOT NULL,
46 currency CHAR(3) NOT NULL,
47 pricing_source VARCHAR(50),
48 calculation_method VARCHAR(100),
49 is_active BOOLEAN DEFAULT true,
50 constituent_count INTEGER,
51 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
52 updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
53);
54
55CREATE TABLE security_master (
56 security_id VARCHAR(20) PRIMARY KEY,
57 security_name VARCHAR(255) NOT NULL,
58 asset_class VARCHAR(30) NOT NULL,
59 security_type VARCHAR(50) NOT NULL,
60 currency CHAR(3) NOT NULL,
61 country_of_risk CHAR(2),
62 sector VARCHAR(100),
63 industry VARCHAR(100),
64 pricing_source VARCHAR(50),
65 exchange VARCHAR(50),
66 is_active BOOLEAN DEFAULT true,
67 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
68 updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
69);
70
71CREATE TABLE security_identifiers (
72 security_id VARCHAR(20) NOT NULL REFERENCES security_master(security_id),
73 identifier_type VARCHAR(20) NOT NULL,
74 identifier_value VARCHAR(50) NOT NULL,
75 is_primary BOOLEAN DEFAULT false,
76 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
77 PRIMARY KEY (security_id, identifier_type)
78);
79
80CREATE TABLE pricing_rules (
81 rule_id VARCHAR(20) PRIMARY KEY,
82 security_id VARCHAR(20) NOT NULL REFERENCES security_master(security_id),
83 pricing_source VARCHAR(50) NOT NULL,
84 pricing_method VARCHAR(50) NOT NULL,
85 stale_price_threshold_hours INTEGER DEFAULT 24,
86 is_active BOOLEAN DEFAULT true,
87 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
88 updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
89);
90
91CREATE TABLE pricing_validation_rules (
92 rule_id VARCHAR(20) NOT NULL REFERENCES pricing_rules(rule_id),
93 validation_type VARCHAR(50) NOT NULL,
94 validation_parameter JSONB,
95 is_active BOOLEAN DEFAULT true,
96 PRIMARY KEY (rule_id, validation_type)
97);
98
99CREATE TABLE pricing_fallback_sources (
100 rule_id VARCHAR(20) NOT NULL REFERENCES pricing_rules(rule_id),
101 fallback_source VARCHAR(50) NOT NULL,
102 priority_order INTEGER NOT NULL,
103 PRIMARY KEY (rule_id, fallback_source)
104);
105
106-- Portfolio Holdings and NAV Tables
107CREATE TABLE portfolio_positions (
108 position_id SERIAL PRIMARY KEY,
109 fund_id VARCHAR(20) NOT NULL REFERENCES fund_master(fund_id),
110 security_id VARCHAR(20) NOT NULL REFERENCES security_master(security_id),
111 position_date DATE NOT NULL,
112 quantity DECIMAL(18,6) NOT NULL,
113 original_cost DECIMAL(18,4),
114 market_value DECIMAL(18,4),
115 weight_percent DECIMAL(8,6),
116 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
117 UNIQUE(fund_id, security_id, position_date)
118);
119
120CREATE TABLE fund_nav (
121 nav_id SERIAL PRIMARY KEY,
122 fund_id VARCHAR(20) NOT NULL REFERENCES fund_master(fund_id),
123 share_class_id VARCHAR(30) REFERENCES share_classes(share_class_id),
124 nav_date DATE NOT NULL,
125 total_assets DECIMAL(18,4) NOT NULL,
126 total_liabilities DECIMAL(18,4) DEFAULT 0,
127 net_assets DECIMAL(18,4) NOT NULL,
128 shares_outstanding DECIMAL(18,6) NOT NULL,
129 nav_per_share DECIMAL(12,6) NOT NULL,
130 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
131 UNIQUE(fund_id, share_class_id, nav_date)
132);
133
134CREATE TABLE fund_expenses (
135 expense_id SERIAL PRIMARY KEY,
136 fund_id VARCHAR(20) NOT NULL REFERENCES fund_master(fund_id),
137 expense_date DATE NOT NULL,
138 expense_type VARCHAR(50) NOT NULL,
139 expense_amount DECIMAL(12,4) NOT NULL,
140 description TEXT,
141 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
142);
143
144-- Investment Restrictions Tables
145CREATE TABLE fund_restrictions (
146 restriction_id SERIAL PRIMARY KEY,
147 fund_id VARCHAR(20) NOT NULL REFERENCES fund_master(fund_id),
148 restriction_type VARCHAR(50) NOT NULL,
149 restriction_value DECIMAL(8,4),
150 restriction_config JSONB,
151 is_active BOOLEAN DEFAULT true,
152 effective_date DATE NOT NULL,
153 expiration_date DATE,
154 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
155);
156
157-- Performance and Attribution Tables
158CREATE TABLE fund_returns (
159 return_id SERIAL PRIMARY KEY,
160 fund_id VARCHAR(20) NOT NULL REFERENCES fund_master(fund_id),
161 share_class_id VARCHAR(30) REFERENCES share_classes(share_class_id),
162 return_date DATE NOT NULL,
163 return_1d DECIMAL(10,8),
164 return_mtd DECIMAL(10,8),
165 return_qtd DECIMAL(10,8),
166 return_ytd DECIMAL(10,8),
167 return_1y DECIMAL(10,8),
168 return_3y DECIMAL(10,8),
169 return_5y DECIMAL(10,8),
170 return_inception DECIMAL(10,8),
171 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
172 UNIQUE(fund_id, share_class_id, return_date)
173);
174
175CREATE TABLE benchmark_returns (
176 return_id SERIAL PRIMARY KEY,
177 benchmark_id VARCHAR(20) NOT NULL REFERENCES benchmarks(benchmark_id),
178 return_date DATE NOT NULL,
179 return_1d DECIMAL(10,8),
180 return_mtd DECIMAL(10,8),
181 return_qtd DECIMAL(10,8),
182 return_ytd DECIMAL(10,8),
183 return_1y DECIMAL(10,8),
184 return_3y DECIMAL(10,8),
185 return_5y DECIMAL(10,8),
186 return_inception DECIMAL(10,8),
187 index_level DECIMAL(18,6),
188 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
189 UNIQUE(benchmark_id, return_date)
190);
191
192-- Risk Analytics Tables
193CREATE TABLE fund_risk_metrics (
194 metric_id SERIAL PRIMARY KEY,
195 fund_id VARCHAR(20) NOT NULL REFERENCES fund_master(fund_id),
196 calculation_date DATE NOT NULL,
197 var_1d DECIMAL(12,8),
198 var_10d DECIMAL(12,8),
199 tracking_error DECIMAL(10,8),
200 beta DECIMAL(8,6),
201 alpha DECIMAL(10,8),
202 sharpe_ratio DECIMAL(8,6),
203 information_ratio DECIMAL(8,6),
204 max_drawdown DECIMAL(8,6),
205 volatility DECIMAL(10,8),
206 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
207 UNIQUE(fund_id, calculation_date)
208);
209
210-- Security Prices Table
211CREATE TABLE security_prices (
212 price_id SERIAL PRIMARY KEY,
213 security_id VARCHAR(20) NOT NULL REFERENCES security_master(security_id),
214 price_date DATE NOT NULL,
215 price DECIMAL(18,8) NOT NULL,
216 currency CHAR(3) NOT NULL,
217 pricing_source VARCHAR(50) NOT NULL,
218 price_type VARCHAR(20) DEFAULT 'CLOSE',
219 volume BIGINT,
220 is_validated BOOLEAN DEFAULT false,
221 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
222 UNIQUE(security_id, price_date, pricing_source, price_type)
223);
224
225-- Indexes for Performance
226CREATE INDEX idx_portfolio_positions_fund_date ON portfolio_positions(fund_id, position_date);
227CREATE INDEX idx_portfolio_positions_security_date ON portfolio_positions(security_id, position_date);
228CREATE INDEX idx_fund_nav_fund_date ON fund_nav(fund_id, nav_date);
229CREATE INDEX idx_security_prices_security_date ON security_prices(security_id, price_date);
230CREATE INDEX idx_fund_returns_fund_date ON fund_returns(fund_id, return_date);
231CREATE INDEX idx_benchmark_returns_bench_date ON benchmark_returns(benchmark_id, return_date);
232
233-- Sample Data
234INSERT INTO fund_master VALUES
235('FUND001', 'Global Equity Growth Fund', 'MUTUAL_FUND', 'EQUITY', '2010-01-15', 'USD',
236 0.007500, 0.005000, 'BENCH001', 'DAILY', true, '40_ACT',
237 'Long-term capital growth through global equity investments', 10000.00),
238('ETF001', 'Technology Sector ETF', 'ETF', 'EQUITY', '2015-06-01', 'USD',
239 0.002500, 0.002000, 'BENCH002', 'DAILY', true, '40_ACT',
240 'Track the performance of technology sector stocks', 1.00),
241('FUND002', 'International Bond Fund', 'MUTUAL_FUND', 'FIXED_INCOME', '2012-03-20', 'USD',
242 0.006000, 0.004000, 'BENCH003', 'DAILY', true, '40_ACT',
243 'Income generation through international fixed income securities', 5000.00);
244
245INSERT INTO share_classes VALUES
246('FUND001_A', 'FUND001', 'Class A', 'GEGRX', 'US12345678901', '123456789',
247 0.010000, 0.005000, 0.002500, 0.0575, 0.0000, 10000.00, 'USD', true),
248('FUND001_I', 'FUND001', 'Institutional', 'GEGIX', 'US12345678902', '123456790',
249 0.007500, 0.005000, 0.000000, 0.0000, 0.0000, 1000000.00, 'USD', true),
250('ETF001_ETF', 'ETF001', 'ETF Shares', 'TECH', 'US12345678903', '123456791',
251 0.002500, 0.002000, 0.000000, 0.0000, 0.0000, 1.00, 'USD', true);
252
253INSERT INTO benchmarks VALUES
254('BENCH001', 'MSCI World Index', 'MSCI', 'EQUITY', 'USD', 'MSCI', 'MARKET_CAP_WEIGHTED', true, 1500),
255('BENCH002', 'NASDAQ Technology Index', 'NASDAQ', 'EQUITY', 'USD', 'BLOOMBERG', 'MARKET_CAP_WEIGHTED', true, 100),
256('BENCH003', 'Bloomberg Global Aggregate Bond Index', 'Bloomberg', 'FIXED_INCOME', 'USD', 'BLOOMBERG', 'MARKET_VALUE_WEIGHTED', true, 25000);
257
258INSERT INTO security_master VALUES
259('SEC001', 'Apple Inc.', 'EQUITY', 'COMMON_STOCK', 'USD', 'US', 'Technology', 'Technology Hardware', 'BLOOMBERG', 'NASDAQ', true),
260('SEC002', 'US Treasury 2.5% 2030', 'FIXED_INCOME', 'GOVERNMENT_BOND', 'USD', 'US', 'Government', 'Treasury', 'BLOOMBERG', null, true),
261('SEC003', 'Microsoft Corporation', 'EQUITY', 'COMMON_STOCK', 'USD', 'US', 'Technology', 'Software', 'BLOOMBERG', 'NASDAQ', true);
262
263INSERT INTO security_identifiers VALUES
264('SEC001', 'ISIN', 'US0378331005', true),
265('SEC001', 'CUSIP', '037833100', false),
266('SEC001', 'TICKER', 'AAPL', false),
267('SEC001', 'SEDOL', '2046251', false),
268('SEC002', 'ISIN', 'US912828XG55', true),
269('SEC002', 'CUSIP', '912828XG5', false),
270('SEC002', 'TICKER', 'T 2.5 08/15/30', false),
271('SEC003', 'ISIN', 'US5949181045', true),
272('SEC003', 'CUSIP', '594918104', false),
273('SEC003', 'TICKER', 'MSFT', false),
274('SEC003', 'SEDOL', '2588173', false);
275
276INSERT INTO pricing_rules VALUES
277('RULE001', 'SEC001', 'BLOOMBERG', 'MARKET', 24, true),
278('RULE002', 'SEC002', 'BLOOMBERG', 'MATRIX', 24, true),
279('RULE003', 'SEC003', 'BLOOMBERG', 'MARKET', 24, true);
280
281INSERT INTO pricing_validation_rules VALUES
282('RULE001', 'STALE_PRICE_CHECK', '{"max_hours": 24}', true),
283('RULE001', 'PRICE_VARIANCE_CHECK', '{"max_variance_pct": 10.0}', true),
284('RULE002', 'YIELD_CURVE_CHECK', '{"tolerance_bps": 5}', true),
285('RULE002', 'CREDIT_SPREAD_CHECK', '{"tolerance_bps": 10}', true),
286('RULE003', 'STALE_PRICE_CHECK', '{"max_hours": 24}', true),
287('RULE003', 'VOLUME_CHECK', '{"min_volume": 10000}', true);
288
289INSERT INTO pricing_fallback_sources VALUES
290('RULE001', 'REUTERS', 1),
291('RULE001', 'FACTSET', 2),
292('RULE002', 'REUTERS', 1),
293('RULE003', 'REUTERS', 1),
294('RULE003', 'FACTSET', 2);
295
296-- Sample fund restrictions
297INSERT INTO fund_restrictions VALUES
298(1, 'FUND001', 'MAX_SINGLE_SECURITY_WEIGHT', 5.0, null, true, '2010-01-15', null),
299(2, 'FUND001', 'MAX_SECTOR_WEIGHT', 25.0, null, true, '2010-01-15', null),
300(3, 'FUND001', 'MAX_COUNTRY_WEIGHT', 30.0, null, true, '2010-01-15', null),
301(4, 'FUND001', 'MIN_HOLDINGS', 50, null, true, '2010-01-15', null),
302(5, 'ETF001', 'MAX_SINGLE_SECURITY_WEIGHT', 25.0, null, true, '2015-06-01', null),
303(6, 'ETF001', 'TRACKING_ERROR_LIMIT', 0.5, null, true, '2015-06-01', null),
304(7, 'FUND002', 'MIN_CREDIT_QUALITY', null, '{"min_rating": "BBB"}', true, '2012-03-20', null),
305(8, 'FUND002', 'MAX_DURATION', 10.0, null, true, '2012-03-20', null);
Kubernetes Deployment for Fund Accounting
1# k8s-fund-accounting.yaml
2apiVersion: apps/v1
3kind: Deployment
4metadata:
5 name: fund-accounting-mcp-server
6 labels:
7 app: fund-accounting-mcp-server
8 tier: api
9spec:
10 replicas: 3
11 selector:
12 matchLabels:
13 app: fund-accounting-mcp-server
14 template:
15 metadata:
16 labels:
17 app: fund-accounting-mcp-server
18 spec:
19 containers:
20 - name: fund-accounting-mcp-server
21 image: company/fund-accounting-mcp:latest
22 ports:
23 - containerPort: 8080
24 env:
25 - name: DATABASE_URL
26 valueFrom:
27 secretKeyRef:
28 name: fund-mcp-secrets
29 key: database-url
30 - name: REDIS_URL
31 valueFrom:
32 secretKeyRef:
33 name: fund-mcp-secrets
34 key: redis-url
35 - name: PRICING_SERVICE_URL
36 value: "http://pricing-service:8081"
37 - name: RISK_SERVICE_URL
38 value: "http://risk-service:8082"
39 resources:
40 requests:
41 memory: "512Mi"
42 cpu: "500m"
43 limits:
44 memory: "1Gi"
45 cpu: "1000m"
46 livenessProbe:
47 httpGet:
48 path: /health
49 port: 8080
50 initialDelaySeconds: 60
51 periodSeconds: 30
52 readinessProbe:
53 httpGet:
54 path: /ready
55 port: 8080
56 initialDelaySeconds: 10
57 periodSeconds: 5
58 volumeMounts:
59 - name: config-volume
60 mountPath: /app/config.yaml
61 subPath: config.yaml
62 volumes:
63 - name: config-volume
64 configMap:
65 name: fund-mcp-config
66---
67apiVersion: v1
68kind: Service
69metadata:
70 name: fund-accounting-mcp-service
71spec:
72 selector:
73 app: fund-accounting-mcp-server
74 ports:
75 - protocol: TCP
76 port: 80
77 targetPort: 8080
78 type: ClusterIP
79---
80apiVersion: v1
81kind: ConfigMap
82metadata:
83 name: fund-mcp-config
84data:
85 config.yaml: |
86 server:
87 name: "fund-accounting-static-data"
88 version: "1.0.0"
89 environment: "production"
90 port: 8080
91
92 database:
93 pool_size: 20
94 timeout: 30
95 retry_attempts: 3
96
97 cache:
98 ttl_minutes: 15
99 max_connections: 20
100
101 security:
102 require_api_key: true
103 rate_limit_per_minute: 2000
104 allowed_origins:
105 - "https://portfolio-management.company.com"
106 - "https://fund-accounting.company.com"
107 - "https://risk-analytics.company.com"
108
109 features:
110 enable_real_time_pricing: true
111 enable_risk_analytics: true
112 enable_compliance_checking: true
113 enable_performance_attribution: true
114
115 monitoring:
116 metrics_enabled: true
117 log_level: "INFO"
118 health_check_interval: 30
119---
120apiVersion: v1
121kind: Secret
122metadata:
123 name: fund-mcp-secrets
124type: Opaque
125stringData:
126 database-url: "postgresql://funduser:fundpass@postgres-service:5432/investment_db"
127 redis-url: "redis://redis-service:6379"
Requirements File for Investment Management
1# requirements.txt
2# MCP and Core Framework
3mcp>=1.0.0
4asyncio-mqtt>=0.16.0
5fastapi>=0.104.0
6uvicorn>=0.24.0
7pydantic>=2.4.0
8
9# Database and Caching
10asyncpg>=0.29.0
11SQLAlchemy>=2.0.0
12alembic>=1.12.0
13aioredis>=2.0.0
14
15# Financial and Mathematical Libraries
16numpy>=1.24.0
17pandas>=2.1.0
18scipy>=1.11.0
19pyquantlib>=0.2.0
20
21# Data Validation and Serialization
22pydantic>=2.4.0
23marshmallow>=3.20.0
24
25# API and HTTP
26aiohttp>=3.8.0
27httpx>=0.24.0
28requests>=2.31.0
29
30# Configuration and Environment
31pyyaml>=6.0
32python-dotenv>=1.0.0
33
34# Monitoring and Logging
35prometheus-client>=0.17.0
36structlog>=23.1.0
37
38# Testing
39pytest>=7.4.0
40pytest-asyncio>=0.21.0
41pytest-mock>=3.11.0
42factory-boy>=3.3.0
43
44# Financial Data Sources (Optional - based on vendors used)
45# bloomberg-api>=1.0.0
46# refinitiv-dataplatform>=1.0.0
47# alpha-vantage>=2.3.0
48
49# Development Tools
50black>=23.7.0
51flake8>=6.0.0
52mypy>=1.5.0
Monitoring and Alerting
Grafana Dashboard Configuration
1{
2 "dashboard": {
3 "title": "Fund Accounting MCP Server",
4 "panels": [
5 {
6 "title": "Fund Trade Validations",
7 "type": "graph",
8 "targets": [
9 {
10 "expr": "rate(mcp_fund_trade_validations_total[5m])",
11 "legendFormat": "{{status}}"
12 }
13 ]
14 },
15 {
16 "title": "NAV Calculations",
17 "type": "graph",
18 "targets": [
19 {
20 "expr": "rate(mcp_nav_calculations_total[5m])",
21 "legendFormat": "{{fund_type}}"
22 }
23 ]
24 },
25 {
26 "title": "Security Lookups",
27 "type": "graph",
28 "targets": [
29 {
30 "expr": "histogram_quantile(0.95, rate(mcp_security_lookup_duration_seconds_bucket[5m]))",
31 "legendFormat": "95th Percentile"
32 }
33 ]
34 },
35 {
36 "title": "Fund Performance Attribution",
37 "type": "graph",
38 "targets": [
39 {
40 "expr": "rate(mcp_performance_attribution_requests_total[5m])",
41 "legendFormat": "{{attribution_method}}"
42 }
43 ]
44 },
45 {
46 "title": "Database Connection Pool",
47 "type": "graph",
48 "targets": [
49 {
50 "expr": "mcp_db_connections_active",
51 "legendFormat": "Active Connections"
52 }
53 ]
54 }
55 ]
56 }
57}
Conclusion
This comprehensive guide demonstrates how to build a production-ready MCP server specifically designed for mutual fund and ETF accounting static data. The implementation provides:
Key Benefits for Investment Management
- Comprehensive Fund Data: Complete fund specifications, share classes, benchmarks, and security master data
- Real-time Validation: Trade validation against fund restrictions, asset allocation limits, and regulatory requirements
- Performance Analytics: NAV calculation, performance attribution, and risk analytics integration
- Regulatory Compliance: Built-in compliance checking for investment restrictions and regulatory requirements
- Scalable Architecture: Designed to handle high-frequency trading systems and large fund complexes
Production Features for Investment Firms
- Multi-vendor Pricing: Support for Bloomberg, Reuters, FactSet, and custom pricing sources
- Risk Integration: Real-time risk analytics including VaR, tracking error, and attribution analysis
- Fund Accounting Workflows: Complete NAV calculation, expense allocation, and performance measurement
- Security Master Management: Comprehensive security identifiers, classifications, and pricing rules
- Investment Restrictions: Flexible rule engine for fund-specific investment guidelines and limits
Advanced Investment Management Capabilities
- Real-time Portfolio Monitoring: Live position tracking and compliance monitoring
- Performance Attribution: Multi-factor performance analysis and benchmark comparison
- Risk Analytics: Integration with risk management systems for VaR and stress testing
- Regulatory Reporting: Support for various regulatory frameworks (40 Act, UCITS, etc.)
- Multi-currency Support: Global fund management with currency hedging capabilities
For Investment Management AI Agents
The MCP server provides a robust foundation for building sophisticated investment management agents that can:
- Validate trades against complex fund restrictions and regulatory requirements
- Calculate NAV and performance metrics in real-time
- Perform risk analysis and attribution analysis
- Monitor compliance with investment guidelines
- Generate regulatory reports and client communications
This architecture enables investment management firms to build an ecosystem of AI agents that work together seamlessly, all backed by consistent, reliable fund accounting and investment data services. The system is designed to scale from small asset managers to large global investment firms with complex multi-fund, multi-currency operations.
References
Technical Standards and Protocols
1. Model Context Protocol (MCP) Specification
- Anthropic. (2024). Model Context Protocol Documentation. https://docs.anthropic.com/en/api/mcp
- GitHub Repository: https://github.com/modelcontextprotocol/specification
2. Financial Industry Standards
- ISO 20022: Universal Financial Industry Message Scheme. International Organization for Standardization. https://www.iso20022.org/
- FIX Protocol: Financial Information eXchange Protocol for Electronic Trading. FIX Trading Community. https://www.fixtrading.org/
- SWIFT Standards: Society for Worldwide Interbank Financial Telecommunication. https://www.swift.com/standards
Investment Management Frameworks
3. Fund Accounting Standards
- Investment Company Institute. (2023). Mutual Fund Fact Book. https://www.ici.org/system/files/2023-05/2023_factbook.pdf
- CFA Institute. (2020). Global Investment Performance Standards (GIPS®). https://www.cfainstitute.org/en/ethics-standards/codes/gips-standards
4. Regulatory Guidelines
- U.S. Securities and Exchange Commission. Investment Company Act of 1940. https://www.sec.gov/investment/laws-regulations
- European Securities and Markets Authority. UCITS Directive. https://www.esma.europa.eu/regulation/fund-management/ucits
Data Standards and Identifiers
5. Security Identification Standards
- ISIN: International Securities Identification Number. ISO 6166:2021. https://www.isin.org/
- CUSIP: Committee on Uniform Securities Identification Procedures. https://www.cusip.com/
- SEDOL: Stock Exchange Daily Official List. London Stock Exchange Group. https://www.londonstockexchange.com/
6. Classification Systems
- GICS: Global Industry Classification Standard. MSCI and S&P Dow Jones Indices. https://www.msci.com/gics
- ICB: Industry Classification Benchmark. FTSE Russell. https://www.ftserussell.com/data/industry-classification-benchmark-icb
Technology and Architecture References
7. Database and Performance
- PostgreSQL Global Development Group. (2023). PostgreSQL Documentation. https://www.postgresql.org/docs/
- Redis Labs. (2023). Redis Documentation. https://redis.io/documentation
8. Python Financial Libraries
- McKinney, W. (2022). Python for Data Analysis, 3rd Edition. O'Reilly Media.
- Hilpisch, Y. (2020). Python for Finance: Mastering Data-Driven Finance, 2nd Edition. O'Reilly Media.
Risk Management and Performance Attribution
9. Risk Analytics
- Jorion, P. (2020). Value at Risk: The New Benchmark for Managing Financial Risk, 4th Edition. McGraw-Hill Education.
- Litterman, R. (1996). "Hot Spots and Hedges." Journal of Portfolio Management, 23(2), 52-75.
10. Performance Attribution
- Brinson, G. P., Hood, L. R., & Beebower, G. L. (1986). "Determinants of Portfolio Performance." Financial Analysts Journal, 42(4), 39-44.
- Fama, E. F., & French, K. R. (1993). "Common Risk Factors in the Returns on Stocks and Bonds." Journal of Financial Economics, 33(1), 3-56.
API Design and Microservices
11. REST API Design
- Fielding, R. T. (2000). Architectural Styles and the Design of Network-based Software Architectures. Doctoral dissertation, University of California, Irvine.
- Richardson, L., & Ruby, S. (2007). RESTful Web Services. O'Reilly Media.
12. Microservices Architecture
- Newman, S. (2021). Building Microservices: Designing Fine-Grained Systems, 2nd Edition. O'Reilly Media.
- Fowler, M. (2014). "Microservices." https://martinfowler.com/articles/microservices.html
Containerization and Deployment
13. Docker and Kubernetes
- Burns, B., & Beda, J. (2019). Kubernetes: Up and Running, 2nd Edition. O'Reilly Media.
- Docker Inc. (2023). Docker Documentation. https://docs.docker.com/
Monitoring and Observability
14. Prometheus and Grafana
- Prometheus Authors. (2023). Prometheus Documentation. https://prometheus.io/docs/
- Grafana Labs. (2023). Grafana Documentation. https://grafana.com/docs/
Testing and Quality Assurance
15. Testing Frameworks
- Percival, H., & Gregory, B. (2020). Architecture Patterns with Python. O'Reilly Media.
- pytest Development Team. (2023). pytest Documentation. https://docs.pytest.org/
Financial Data Vendors
16. Market Data Providers
- Bloomberg Terminal: Bloomberg L.P. https://www.bloomberg.com/professional/
- Refinitiv (formerly Thomson Reuters): https://www.refinitiv.com/
- FactSet: https://www.factset.com/
- MSCI: https://www.msci.com/
Compliance and Regulatory Technology
17. RegTech and Compliance
- Arner, D. W., Barberis, J., & Buckey, R. P. (2017). "FinTech, RegTech, and the Reconceptualization of Financial Regulation." Northwestern Journal of International Law & Business, 37(3), 371-413.
- Financial Conduct Authority. (2023). RegTech. https://www.fca.org.uk/firms/innovation/regtech
Academic and Research Papers
18. Fund Performance and Efficiency
- Sharpe, W. F. (1966). "Mutual Fund Performance." Journal of Business, 39(1), 119-138.
- Jensen, M. C. (1968). "The Performance of Mutual Funds in the Period 1945-1964." Journal of Finance, 23(2), 389-416.
19. Modern Portfolio Theory
- Markowitz, H. (1952). "Portfolio Selection." Journal of Finance, 7(1), 77-91.
- Sharpe, W. F. (1964). "Capital Asset Prices: A Theory of Market Equilibrium Under Conditions of Risk." Journal of Finance, 19(3), 425-442.
Industry Reports and Whitepapers
20. Investment Management Technology
- PwC. (2023). Asset & Wealth Management Revolution: Embracing Exponential Change. https://www.pwc.com/gx/en/industries/financial-services/publications/asset-wealth-management-revolution.html
- Deloitte. (2023). Investment Management Outlook 2023. https://www2.deloitte.com/content/dam/Deloitte/us/Documents/financial-services/us-fsi-dcfs-investment-management-outlook-2023.pdf
21. FinTech and AI in Investment Management
- McKinsey & Company. (2023). The Future of AI in Financial Services. https://www.mckinsey.com/industries/financial-services/our-insights/the-future-of-ai-in-financial-services
- EY. (2023). Global FinTech Adoption Index 2023. https://www.ey.com/en_gl/financial-services-emeia/how-fintech-is-evolving-to-meet-changing-demands
Open Source Projects and Libraries
22. Python Financial Ecosystem
- QuantLib: https://www.quantlib.org/
- PyPortfolioOpt: https://github.com/robertmartin8/PyPortfolioOpt
- zipline: https://github.com/quantopian/zipline
- pandas: https://pandas.pydata.org/
23. Java Financial Libraries
- Strata: OpenGamma's market risk and pricing library. https://github.com/OpenGamma/Strata
- JQuantLib: Java port of QuantLib. http://www.jquantlib.org/
Security and Best Practices
24. API Security
- OWASP. (2023). API Security Top 10 2023. https://owasp.org/API-Security/editions/2023/en/0x00-toc/
- National Institute of Standards and Technology. (2020). NIST Cybersecurity Framework. https://www.nist.gov/cyberframework
Data Privacy and Governance
25. Data Protection
- European Union. (2018). General Data Protection Regulation (GDPR). https://eur-lex.europa.eu/eli/reg/2016/679/oj
- California Consumer Privacy Act. (2020). https://oag.ca.gov/privacy/ccpa
---
Note: URLs and publication dates are current as of the time of writing (2024). Some links may require institutional access or subscriptions. For academic papers, consider accessing through institutional libraries or academic databases such as JSTOR, SSRN, or Google Scholar.
Disclaimer: This article is for educational and informational purposes only and does not constitute financial, investment, or professional advice. Implementation of any system described should be done in consultation with qualified professionals and in compliance with applicable regulations and standards.
Share this article
Article: MCP Server - Reference Implementation in Fund Accounting
URL: /blog/mcp-server-reference-implementation-in-fund-accounting