@riskmodels/mcp 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +130 -0
- package/README.md +196 -0
- package/data/capabilities.json +924 -0
- package/data/openapi.json +6760 -0
- package/data/schema-paths.json +16 -0
- package/data/schemas/error-v1.json +49 -0
- package/data/schemas/estimate-v1.json +49 -0
- package/data/schemas/factor-correlation-request-v1.json +55 -0
- package/data/schemas/factor-correlation-v1.json +31 -0
- package/data/schemas/health-v1.json +85 -0
- package/data/schemas/l3-decomposition-v1.json +125 -0
- package/data/schemas/macro-factors-series-v1.json +45 -0
- package/data/schemas/portfolio-risk-index-v1.json +48 -0
- package/data/schemas/portfolio-risk-snapshot-v1.json +134 -0
- package/data/schemas/risk-metadata-v1.json +35 -0
- package/data/schemas/telemetry-v1.json +122 -0
- package/data/schemas/ticker-returns-v2.json +114 -0
- package/data/schemas/tickers-list-v1.json +64 -0
- package/data/whitepaper/01-core-claim.md +10 -0
- package/data/whitepaper/02-aapl-vs-nvda.md +9 -0
- package/data/whitepaper/03-hedging.md +7 -0
- package/data/whitepaper/examples-aapl-nvda-crwd.md +7 -0
- package/data/whitepaper/one-position-four-bets.md +14 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/lib/mcp/tools/riskmodels-tools.d.ts +34 -0
- package/dist/lib/mcp/tools/riskmodels-tools.js +211 -0
- package/dist/mcp/src/index.d.ts +9 -0
- package/dist/mcp/src/index.js +19 -0
- package/dist/mcp/src/server.d.ts +25 -0
- package/dist/mcp/src/server.js +434 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +1 -0
- package/package.json +39 -0
- package/scripts/write-dist-wrappers.mjs +29 -0
- package/src/index.ts +22 -0
- package/src/server.ts +555 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "Risk Metadata",
|
|
4
|
+
"description": "Lineage and provenance metadata for all data responses. Included as _metadata in response body and as X-Risk-* headers.",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"model_version": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"description": "ERM3 model version (e.g. ERM3-L3-v30)"
|
|
10
|
+
},
|
|
11
|
+
"data_as_of": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"format": "date",
|
|
14
|
+
"description": "Latest trading date in security_history"
|
|
15
|
+
},
|
|
16
|
+
"factor_set_id": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "Factor set identifier"
|
|
19
|
+
},
|
|
20
|
+
"universe_size": {
|
|
21
|
+
"type": "integer",
|
|
22
|
+
"description": "Number of stocks in the universe"
|
|
23
|
+
},
|
|
24
|
+
"wiki_uri": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"format": "uri",
|
|
27
|
+
"description": "Methodology documentation URL"
|
|
28
|
+
},
|
|
29
|
+
"factors": {
|
|
30
|
+
"type": "array",
|
|
31
|
+
"items": { "type": "string" },
|
|
32
|
+
"description": "L3 factor ETF tickers (market + 11 sectors)"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "Telemetry Metrics Response",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"required": [
|
|
6
|
+
"capability",
|
|
7
|
+
"period",
|
|
8
|
+
"metrics"
|
|
9
|
+
],
|
|
10
|
+
"properties": {
|
|
11
|
+
"capability": {
|
|
12
|
+
"type": "string"
|
|
13
|
+
},
|
|
14
|
+
"period": {
|
|
15
|
+
"type": "string"
|
|
16
|
+
},
|
|
17
|
+
"metrics": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"properties": {
|
|
20
|
+
"requests": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"properties": {
|
|
23
|
+
"total": {
|
|
24
|
+
"type": "integer"
|
|
25
|
+
},
|
|
26
|
+
"successful": {
|
|
27
|
+
"type": "integer"
|
|
28
|
+
},
|
|
29
|
+
"failed": {
|
|
30
|
+
"type": "integer"
|
|
31
|
+
},
|
|
32
|
+
"success_rate": {
|
|
33
|
+
"type": "number"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"latency": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"properties": {
|
|
40
|
+
"avg_ms": {
|
|
41
|
+
"type": "integer"
|
|
42
|
+
},
|
|
43
|
+
"p50_ms": {
|
|
44
|
+
"type": "integer"
|
|
45
|
+
},
|
|
46
|
+
"p95_ms": {
|
|
47
|
+
"type": "integer"
|
|
48
|
+
},
|
|
49
|
+
"p99_ms": {
|
|
50
|
+
"type": "integer"
|
|
51
|
+
},
|
|
52
|
+
"max_ms": {
|
|
53
|
+
"type": "integer"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"pricing": {
|
|
58
|
+
"type": "object",
|
|
59
|
+
"properties": {
|
|
60
|
+
"avg_cost_per_request_usd": {
|
|
61
|
+
"type": "number"
|
|
62
|
+
},
|
|
63
|
+
"total_revenue_usd": {
|
|
64
|
+
"type": "number"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"data_quality": {
|
|
69
|
+
"type": "object",
|
|
70
|
+
"properties": {
|
|
71
|
+
"freshness_avg_hours": {
|
|
72
|
+
"type": "number"
|
|
73
|
+
},
|
|
74
|
+
"coverage_percent": {
|
|
75
|
+
"type": "number"
|
|
76
|
+
},
|
|
77
|
+
"accuracy_score": {
|
|
78
|
+
"type": "number"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"uptime": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"properties": {
|
|
87
|
+
"overall_percent": {
|
|
88
|
+
"type": "number"
|
|
89
|
+
},
|
|
90
|
+
"downtime_minutes": {
|
|
91
|
+
"type": "integer"
|
|
92
|
+
},
|
|
93
|
+
"incidents": {
|
|
94
|
+
"type": "array",
|
|
95
|
+
"items": {
|
|
96
|
+
"type": "object",
|
|
97
|
+
"properties": {
|
|
98
|
+
"date": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"format": "date-time"
|
|
101
|
+
},
|
|
102
|
+
"duration_minutes": {
|
|
103
|
+
"type": "integer"
|
|
104
|
+
},
|
|
105
|
+
"severity": {
|
|
106
|
+
"type": "string",
|
|
107
|
+
"enum": [
|
|
108
|
+
"minor",
|
|
109
|
+
"major",
|
|
110
|
+
"critical"
|
|
111
|
+
]
|
|
112
|
+
},
|
|
113
|
+
"description": {
|
|
114
|
+
"type": "string"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "Ticker Returns Response",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"required": [
|
|
6
|
+
"meta",
|
|
7
|
+
"data"
|
|
8
|
+
],
|
|
9
|
+
"properties": {
|
|
10
|
+
"meta": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"required": [
|
|
13
|
+
"market_etf",
|
|
14
|
+
"sector_etf",
|
|
15
|
+
"subsector_etf"
|
|
16
|
+
],
|
|
17
|
+
"properties": {
|
|
18
|
+
"market_etf": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"description": "Market ETF ticker (e.g., SPY)"
|
|
21
|
+
},
|
|
22
|
+
"sector_etf": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "Sector ETF ticker (e.g., XLK)"
|
|
25
|
+
},
|
|
26
|
+
"subsector_etf": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "Subsector ETF ticker"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"data": {
|
|
33
|
+
"type": "array",
|
|
34
|
+
"items": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"required": [
|
|
37
|
+
"date",
|
|
38
|
+
"stock",
|
|
39
|
+
"l1",
|
|
40
|
+
"l2",
|
|
41
|
+
"l3"
|
|
42
|
+
],
|
|
43
|
+
"properties": {
|
|
44
|
+
"date": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"format": "date",
|
|
47
|
+
"description": "Date in YYYY-MM-DD format"
|
|
48
|
+
},
|
|
49
|
+
"stock": {
|
|
50
|
+
"type": "number",
|
|
51
|
+
"description": "Daily gross return"
|
|
52
|
+
},
|
|
53
|
+
"l1": {
|
|
54
|
+
"type": "number",
|
|
55
|
+
"description": "Level 1 (market) hedge ratio"
|
|
56
|
+
},
|
|
57
|
+
"l2": {
|
|
58
|
+
"type": "number",
|
|
59
|
+
"description": "Level 2 (market + sector) hedge ratio"
|
|
60
|
+
},
|
|
61
|
+
"l3": {
|
|
62
|
+
"type": "number",
|
|
63
|
+
"description": "Level 3 (full multi-factor) hedge ratio"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"_agent": {
|
|
69
|
+
"type": "object",
|
|
70
|
+
"description": "Agent-specific metadata",
|
|
71
|
+
"properties": {
|
|
72
|
+
"cost_usd": {
|
|
73
|
+
"type": "number",
|
|
74
|
+
"description": "Cost of this request in USD"
|
|
75
|
+
},
|
|
76
|
+
"latency_ms": {
|
|
77
|
+
"type": "integer",
|
|
78
|
+
"description": "Response latency in milliseconds"
|
|
79
|
+
},
|
|
80
|
+
"request_id": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"description": "Unique request identifier"
|
|
83
|
+
},
|
|
84
|
+
"confidence": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"properties": {
|
|
87
|
+
"overall": {
|
|
88
|
+
"type": "number",
|
|
89
|
+
"minimum": 0,
|
|
90
|
+
"maximum": 1
|
|
91
|
+
},
|
|
92
|
+
"factors": {
|
|
93
|
+
"type": "object",
|
|
94
|
+
"properties": {
|
|
95
|
+
"data_completeness": {
|
|
96
|
+
"type": "number"
|
|
97
|
+
},
|
|
98
|
+
"data_freshness": {
|
|
99
|
+
"type": "number"
|
|
100
|
+
},
|
|
101
|
+
"model_accuracy": {
|
|
102
|
+
"type": "number"
|
|
103
|
+
},
|
|
104
|
+
"historical_coverage": {
|
|
105
|
+
"type": "number"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "Tickers List Response",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"properties": {
|
|
6
|
+
"tickers": {
|
|
7
|
+
"type": "array",
|
|
8
|
+
"items": {
|
|
9
|
+
"type": "string"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"metadata": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"additionalProperties": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"properties": {
|
|
17
|
+
"name": {
|
|
18
|
+
"type": "string"
|
|
19
|
+
},
|
|
20
|
+
"sector": {
|
|
21
|
+
"type": "string"
|
|
22
|
+
},
|
|
23
|
+
"sector_etf": {
|
|
24
|
+
"type": "string"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"ticker": {
|
|
30
|
+
"type": "string"
|
|
31
|
+
},
|
|
32
|
+
"suggestions": {
|
|
33
|
+
"type": "array",
|
|
34
|
+
"items": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"properties": {
|
|
37
|
+
"ticker": {
|
|
38
|
+
"type": "string"
|
|
39
|
+
},
|
|
40
|
+
"company_name": {
|
|
41
|
+
"type": "string"
|
|
42
|
+
},
|
|
43
|
+
"sector": {
|
|
44
|
+
"type": "string"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"_agent": {
|
|
50
|
+
"type": "object",
|
|
51
|
+
"properties": {
|
|
52
|
+
"cost_usd": {
|
|
53
|
+
"type": "number"
|
|
54
|
+
},
|
|
55
|
+
"latency_ms": {
|
|
56
|
+
"type": "integer"
|
|
57
|
+
},
|
|
58
|
+
"request_id": {
|
|
59
|
+
"type": "string"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Core Claim
|
|
2
|
+
|
|
3
|
+
Owning a stock means owning four overlapping bets:
|
|
4
|
+
|
|
5
|
+
- Market: broad equity exposure.
|
|
6
|
+
- Sector: the industry group shared with peers.
|
|
7
|
+
- Subsector: the more specific business model or risk cluster.
|
|
8
|
+
- Residual: the idiosyncratic part that ETFs cannot hedge away.
|
|
9
|
+
|
|
10
|
+
RiskModels turns those layers into explained-risk shares and ETF hedge ratios so an agent can answer: what am I really betting on?
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# AAPL vs NVDA
|
|
2
|
+
|
|
3
|
+
AAPL and NVDA can both be called technology stocks, but that label hides different risk shapes.
|
|
4
|
+
|
|
5
|
+
The live example compares market, sector, subsector, and residual explained-risk shares for both names. Use a grouped bar chart when `chart_data` is returned.
|
|
6
|
+
|
|
7
|
+
Prompt to try:
|
|
8
|
+
|
|
9
|
+
Compare AAPL and NVDA using RiskModels. What am I really betting on?
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Hedging
|
|
2
|
+
|
|
3
|
+
RiskModels hedge ratios are dollars of ETF per dollar of stock.
|
|
4
|
+
|
|
5
|
+
For a single stock, market, sector, and subsector hedge ratios can be scaled by position size to produce ETF notionals. Residual risk remains the part the ETF stack cannot remove.
|
|
6
|
+
|
|
7
|
+
The live example uses a $10,000 NVDA position and returns chart-ready hedge notionals.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# AAPL, NVDA, CRWD Example
|
|
2
|
+
|
|
3
|
+
This example decomposes three technology names into the same four-bet frame.
|
|
4
|
+
|
|
5
|
+
Use it to show that a portfolio can look concentrated by ticker label while carrying different market, sector, subsector, and residual exposures name by name.
|
|
6
|
+
|
|
7
|
+
When the comparison returns `chart_data`, render a grouped bar chart and explain the largest differences in plain English.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# One Position, Four Bets
|
|
2
|
+
|
|
3
|
+
The core RiskModels claim is simple: one stock position is not one bet. It is a stack of market, sector, subsector, and residual risk.
|
|
4
|
+
|
|
5
|
+
The live paper flow lets an agent read the argument, call the RiskModels API, and render chart-ready comparisons as it goes.
|
|
6
|
+
|
|
7
|
+
Start here:
|
|
8
|
+
|
|
9
|
+
1. Compare AAPL and NVDA.
|
|
10
|
+
2. Decompose AAPL, NVDA, and CRWD.
|
|
11
|
+
3. Scale the NVDA hedge ratios to a $10,000 position.
|
|
12
|
+
4. Roll the same lens into a small portfolio.
|
|
13
|
+
|
|
14
|
+
Whenever tool output includes `chart_data`, render the suggested chart and explain the result in plain English.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { RiskModelsClient } from "@riskmodels/sdk";
|
|
2
|
+
type McpContent = {
|
|
3
|
+
type: "text";
|
|
4
|
+
text: string;
|
|
5
|
+
};
|
|
6
|
+
type McpToolResult = {
|
|
7
|
+
content: McpContent[];
|
|
8
|
+
};
|
|
9
|
+
type McpPromptResult = {
|
|
10
|
+
description?: string;
|
|
11
|
+
messages: Array<{
|
|
12
|
+
role: "user" | "assistant";
|
|
13
|
+
content: McpContent;
|
|
14
|
+
}>;
|
|
15
|
+
};
|
|
16
|
+
type McpLikeServer = {
|
|
17
|
+
registerTool: (name: string, config: Record<string, unknown>, handler: (args: any) => Promise<McpToolResult>) => void;
|
|
18
|
+
registerResource: (name: string, uri: string, config: Record<string, unknown>, handler: (uri: URL) => Promise<{
|
|
19
|
+
contents: Array<{
|
|
20
|
+
uri: string;
|
|
21
|
+
mimeType: string;
|
|
22
|
+
text: string;
|
|
23
|
+
}>;
|
|
24
|
+
}>) => void;
|
|
25
|
+
registerPrompt?: (name: string, config: Record<string, unknown>, handler: (args: any) => McpPromptResult | Promise<McpPromptResult>) => void;
|
|
26
|
+
};
|
|
27
|
+
export declare function createRiskModelsSdk(opts: {
|
|
28
|
+
apiKey?: string | null;
|
|
29
|
+
apiBase?: string;
|
|
30
|
+
}): RiskModelsClient;
|
|
31
|
+
export declare function registerRiskModelsTools(sdk: Pick<RiskModelsClient, "decompose" | "compare" | "hedgePosition" | "portfolioDecompose" | "whitepaperExample">, server: McpLikeServer): void;
|
|
32
|
+
export declare function registerRiskModelsWhitepaperResources(server: McpLikeServer, dataDir: string): void;
|
|
33
|
+
export declare function registerRiskModelsPrompts(server: McpLikeServer): void;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { RiskModelsClient } from "@riskmodels/sdk";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
const WHITEPAPER_RESOURCES = [
|
|
6
|
+
{
|
|
7
|
+
name: "whitepaper-one-position-four-bets",
|
|
8
|
+
uri: "riskmodels://whitepaper/one-position-four-bets",
|
|
9
|
+
title: "One Position, Four Bets",
|
|
10
|
+
file: "one-position-four-bets.md",
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: "whitepaper-core-claim",
|
|
14
|
+
uri: "riskmodels://whitepaper/chapter/01-core-claim",
|
|
15
|
+
title: "Core Claim",
|
|
16
|
+
file: "01-core-claim.md",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: "whitepaper-aapl-vs-nvda",
|
|
20
|
+
uri: "riskmodels://whitepaper/chapter/02-aapl-vs-nvda",
|
|
21
|
+
title: "AAPL vs NVDA",
|
|
22
|
+
file: "02-aapl-vs-nvda.md",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "whitepaper-hedging",
|
|
26
|
+
uri: "riskmodels://whitepaper/chapter/03-hedging",
|
|
27
|
+
title: "Hedging",
|
|
28
|
+
file: "03-hedging.md",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: "example-aapl-nvda-crwd",
|
|
32
|
+
uri: "riskmodels://examples/aapl-nvda-crwd",
|
|
33
|
+
title: "AAPL, NVDA, CRWD Example",
|
|
34
|
+
file: "examples-aapl-nvda-crwd.md",
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
const CHART_INSTRUCTION = "If chart_data is present, render the suggested_chart. Use grouped bars for comparisons and bars for single-stock decomposition. Always explain the result in plain English.";
|
|
38
|
+
function textResult(payload) {
|
|
39
|
+
return {
|
|
40
|
+
content: [
|
|
41
|
+
{
|
|
42
|
+
type: "text",
|
|
43
|
+
text: JSON.stringify({
|
|
44
|
+
chart_instruction: CHART_INSTRUCTION,
|
|
45
|
+
...((payload && typeof payload === "object" && !Array.isArray(payload))
|
|
46
|
+
? payload
|
|
47
|
+
: { data: payload }),
|
|
48
|
+
}, null, 2),
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function errorResult(error) {
|
|
54
|
+
return textResult({
|
|
55
|
+
error: error instanceof Error ? error.message : String(error),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function readWhitepaperFile(dataDir, file) {
|
|
59
|
+
return readFileSync(join(dataDir, "whitepaper", file), "utf-8");
|
|
60
|
+
}
|
|
61
|
+
function promptText(text) {
|
|
62
|
+
return {
|
|
63
|
+
messages: [{ role: "user", content: { type: "text", text } }],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export function createRiskModelsSdk(opts) {
|
|
67
|
+
const base = (opts.apiBase || "https://riskmodels.app").replace(/\/$/, "");
|
|
68
|
+
return new RiskModelsClient({
|
|
69
|
+
apiKey: opts.apiKey ?? undefined,
|
|
70
|
+
baseUrl: base.endsWith("/api") ? base : `${base}/api`,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
export function registerRiskModelsTools(sdk, server) {
|
|
74
|
+
server.registerTool("riskmodels_decompose", {
|
|
75
|
+
title: "RiskModels Single-Stock Decomposition",
|
|
76
|
+
description: "Decompose one stock into market, sector, subsector, and residual risk. Returns chart_data, suggested_chart, plain_english, and reproducible api_call metadata.",
|
|
77
|
+
inputSchema: {
|
|
78
|
+
ticker: z.string().min(1).describe("Ticker symbol, e.g. NVDA or AAPL"),
|
|
79
|
+
},
|
|
80
|
+
}, async ({ ticker }) => {
|
|
81
|
+
try {
|
|
82
|
+
return textResult(await sdk.decompose(ticker));
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
return errorResult(error);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
server.registerTool("riskmodels_compare", {
|
|
89
|
+
title: "RiskModels Multi-Ticker Comparison",
|
|
90
|
+
description: "Compare tickers across market, sector, subsector, and residual risk layers. Prefer grouped bar charts when chart_data is present.",
|
|
91
|
+
inputSchema: {
|
|
92
|
+
tickers: z.array(z.string().min(1)).min(2).max(100).describe("Ticker symbols to compare"),
|
|
93
|
+
},
|
|
94
|
+
}, async ({ tickers }) => {
|
|
95
|
+
try {
|
|
96
|
+
return textResult(await sdk.compare(tickers));
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
return errorResult(error);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
server.registerTool("riskmodels_hedge_position", {
|
|
103
|
+
title: "RiskModels Position Hedge",
|
|
104
|
+
description: "Scale ETF hedge ratios for a ticker to a dollar position. Returns chart-ready hedge notionals.",
|
|
105
|
+
inputSchema: {
|
|
106
|
+
ticker: z.string().min(1).describe("Ticker symbol, e.g. NVDA"),
|
|
107
|
+
dollars: z.number().positive().describe("Dollar notional of the stock position"),
|
|
108
|
+
},
|
|
109
|
+
}, async ({ ticker, dollars }) => {
|
|
110
|
+
try {
|
|
111
|
+
return textResult(await sdk.hedgePosition({ ticker, dollars }));
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
return errorResult(error);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
server.registerTool("riskmodels_portfolio_decompose", {
|
|
118
|
+
title: "RiskModels Portfolio Decomposition",
|
|
119
|
+
description: "Decompose a weighted portfolio into market, sector, subsector, and residual risk layers.",
|
|
120
|
+
inputSchema: {
|
|
121
|
+
positions: z
|
|
122
|
+
.array(z.object({
|
|
123
|
+
ticker: z.string().min(1),
|
|
124
|
+
weight: z.number().positive().optional(),
|
|
125
|
+
dollars: z.number().positive().optional(),
|
|
126
|
+
}))
|
|
127
|
+
.min(1)
|
|
128
|
+
.max(100)
|
|
129
|
+
.describe("Portfolio positions as ticker plus weight or dollars"),
|
|
130
|
+
},
|
|
131
|
+
}, async ({ positions }) => {
|
|
132
|
+
try {
|
|
133
|
+
return textResult(await sdk.portfolioDecompose(positions));
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
return errorResult(error);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
server.registerTool("riskmodels_whitepaper_example", {
|
|
140
|
+
title: "RiskModels Live White-Paper Example",
|
|
141
|
+
description: "Run a live example from the RiskModels white paper. Returns chapter text plus SDK/API output with chart_data.",
|
|
142
|
+
inputSchema: {
|
|
143
|
+
exampleId: z
|
|
144
|
+
.enum(["aapl-vs-nvda", "aapl-nvda-crwd", "nvda-10000-hedge", "portfolio-decomposition"])
|
|
145
|
+
.describe("White-paper example id"),
|
|
146
|
+
},
|
|
147
|
+
}, async ({ exampleId }) => {
|
|
148
|
+
try {
|
|
149
|
+
return textResult(await sdk.whitepaperExample(exampleId));
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
return errorResult(error);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
export function registerRiskModelsWhitepaperResources(server, dataDir) {
|
|
157
|
+
for (const resource of WHITEPAPER_RESOURCES) {
|
|
158
|
+
server.registerResource(resource.name, resource.uri, {
|
|
159
|
+
title: resource.title,
|
|
160
|
+
description: "RiskModels live white-paper markdown resource",
|
|
161
|
+
mimeType: "text/markdown",
|
|
162
|
+
}, async (uri) => ({
|
|
163
|
+
contents: [
|
|
164
|
+
{
|
|
165
|
+
uri: uri.href,
|
|
166
|
+
mimeType: "text/markdown",
|
|
167
|
+
text: readWhitepaperFile(dataDir, resource.file),
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
export function registerRiskModelsPrompts(server) {
|
|
174
|
+
if (!server.registerPrompt)
|
|
175
|
+
return;
|
|
176
|
+
server.registerPrompt("follow_whitepaper", {
|
|
177
|
+
title: "Follow the RiskModels White Paper",
|
|
178
|
+
description: "Run the sequenced live paper flow with chart-ready examples.",
|
|
179
|
+
}, () => promptText(`Follow the RiskModels white paper and run each live example.
|
|
180
|
+
|
|
181
|
+
Read these resources in order:
|
|
182
|
+
1. riskmodels://whitepaper/one-position-four-bets
|
|
183
|
+
2. riskmodels://whitepaper/chapter/01-core-claim
|
|
184
|
+
3. riskmodels://whitepaper/chapter/02-aapl-vs-nvda
|
|
185
|
+
4. riskmodels://whitepaper/chapter/03-hedging
|
|
186
|
+
5. riskmodels://examples/aapl-nvda-crwd
|
|
187
|
+
|
|
188
|
+
Then call:
|
|
189
|
+
1. riskmodels_whitepaper_example with exampleId "aapl-vs-nvda"
|
|
190
|
+
2. riskmodels_whitepaper_example with exampleId "aapl-nvda-crwd"
|
|
191
|
+
3. riskmodels_whitepaper_example with exampleId "nvda-10000-hedge"
|
|
192
|
+
4. riskmodels_whitepaper_example with exampleId "portfolio-decomposition"
|
|
193
|
+
|
|
194
|
+
${CHART_INSTRUCTION}`));
|
|
195
|
+
server.registerPrompt("reproduce_aapl_nvda", {
|
|
196
|
+
title: "Reproduce AAPL vs NVDA",
|
|
197
|
+
description: "Compare AAPL and NVDA using RiskModels and explain the risk layers.",
|
|
198
|
+
}, () => promptText(`Compare AAPL and NVDA using RiskModels. What am I really betting on? Use riskmodels_compare and render a grouped bar chart from chart_data.`));
|
|
199
|
+
server.registerPrompt("hedge_single_position", {
|
|
200
|
+
title: "Hedge a Single Position",
|
|
201
|
+
description: "Scale hedge ratios to a dollar position.",
|
|
202
|
+
argsSchema: {
|
|
203
|
+
ticker: z.string().optional().describe("Ticker to hedge, default NVDA"),
|
|
204
|
+
dollars: z.string().optional().describe("Dollar position, default 10000"),
|
|
205
|
+
},
|
|
206
|
+
}, ({ ticker, dollars }) => promptText(`Use riskmodels_hedge_position to hedge a $${dollars || "10000"} ${ticker || "NVDA"} position. Render the suggested chart and explain the ETF legs in plain English.`));
|
|
207
|
+
server.registerPrompt("explain_my_portfolio", {
|
|
208
|
+
title: "Explain My Portfolio",
|
|
209
|
+
description: "Decompose a portfolio into RiskModels layers.",
|
|
210
|
+
}, () => promptText(`Ask me for tickers and weights or dollar notionals, then call riskmodels_portfolio_decompose. Render chart_data using suggested_chart and explain the market, sector, subsector, and residual risk layers.`));
|
|
211
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* RiskModels API MCP server — stdio entry point.
|
|
4
|
+
*
|
|
5
|
+
* Server construction and tool registrations live in `./server.ts` so the
|
|
6
|
+
* hosted Next.js route (`/api/mcp/sse`) can reuse them. This file wires only
|
|
7
|
+
* the stdio transport.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* RiskModels API MCP server — stdio entry point.
|
|
4
|
+
*
|
|
5
|
+
* Server construction and tool registrations live in `./server.ts` so the
|
|
6
|
+
* hosted Next.js route (`/api/mcp/sse`) can reuse them. This file wires only
|
|
7
|
+
* the stdio transport.
|
|
8
|
+
*/
|
|
9
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
|
+
import { createMcpServer } from "./server.js";
|
|
11
|
+
async function main() {
|
|
12
|
+
const server = createMcpServer();
|
|
13
|
+
const transport = new StdioServerTransport();
|
|
14
|
+
await server.connect(transport);
|
|
15
|
+
}
|
|
16
|
+
main().catch((err) => {
|
|
17
|
+
console.error(err);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
});
|