@ltorrey/beforethechart-mcp 1.0.0
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/README.md +32 -0
- package/dist/auth.js +24 -0
- package/dist/index.js +69 -0
- package/package.json +20 -0
- package/smithery.yaml +18 -0
- package/src/auth.ts +32 -0
- package/src/index.ts +94 -0
- package/tsconfig.json +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# BeforeTheChart MCP Server
|
|
2
|
+
|
|
3
|
+
BeforeTheChart MCP provides pre-trade market intelligence tools over the Model Context Protocol (MCP).
|
|
4
|
+
|
|
5
|
+
## Run
|
|
6
|
+
|
|
7
|
+
Set the Gumroad license key issued with your purchase, then launch the server:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
GUMROAD_LICENSE_KEY="YOUR_GUMROAD_LICENSE_KEY" npx -y @ltorrey/beforethechart-mcp
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Claude Desktop
|
|
14
|
+
|
|
15
|
+
Add this server to the `mcpServers` object in `claude_desktop_config.json`:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"BeforeTheChart-MCP": {
|
|
20
|
+
"command": "npx",
|
|
21
|
+
"args": ["-y", "@ltorrey/beforethechart-mcp"],
|
|
22
|
+
"env": {
|
|
23
|
+
"GUMROAD_LICENSE_KEY": "YOUR_GUMROAD_LICENSE_KEY"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Tools
|
|
30
|
+
|
|
31
|
+
- `get_market_sentiment` returns recent sentiment data for a stock or cryptocurrency ticker.
|
|
32
|
+
- `calculate_position_size` calculates position sizing from portfolio balance, risk percentage, entry price, and stop loss.
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export async function verifyGumroadLicense(licenseKey, productId) {
|
|
2
|
+
try {
|
|
3
|
+
const requestBody = new URLSearchParams();
|
|
4
|
+
requestBody.append("product_id", productId);
|
|
5
|
+
requestBody.append("license_key", licenseKey);
|
|
6
|
+
requestBody.append("increment_uses_count", "false"); // Don't burn a use just for booting the server
|
|
7
|
+
const response = await fetch("https://api.gumroad.com/v2/licenses/verify", {
|
|
8
|
+
method: "POST",
|
|
9
|
+
body: requestBody,
|
|
10
|
+
});
|
|
11
|
+
const data = await response.json();
|
|
12
|
+
// Check if valid and not refunded
|
|
13
|
+
if (data.success &&
|
|
14
|
+
!data.purchase.refunded &&
|
|
15
|
+
!data.purchase.chargebacked) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
console.error("License verification failed.");
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { verifyGumroadLicense } from "./auth.js";
|
|
6
|
+
const GUMROAD_PRODUCT_ID = "wavYh-9BsIk_fYxLpyoh0w==";
|
|
7
|
+
async function main() {
|
|
8
|
+
const licenseKey = process.env.GUMROAD_LICENSE_KEY;
|
|
9
|
+
if (!licenseKey) {
|
|
10
|
+
console.error("Fatal: GUMROAD_LICENSE_KEY environment variable is missing.");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const isValid = await verifyGumroadLicense(licenseKey, GUMROAD_PRODUCT_ID);
|
|
14
|
+
if (!isValid) {
|
|
15
|
+
console.error("Fatal: Invalid or refunded Gumroad License Key.");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
// 1. Initialize Server
|
|
19
|
+
const server = new McpServer({
|
|
20
|
+
name: "BeforeTheChart-MCP",
|
|
21
|
+
version: "1.0.0",
|
|
22
|
+
});
|
|
23
|
+
// 2. Register Tool: Sentiment Aggregator
|
|
24
|
+
server.tool("get_market_sentiment", "Fetches recent news sentiment for a specific ticker symbol", {
|
|
25
|
+
ticker: z
|
|
26
|
+
.string()
|
|
27
|
+
.describe("The stock or crypto ticker symbol (e.g. AAPL, BTC)"),
|
|
28
|
+
}, async ({ ticker }) => {
|
|
29
|
+
// Stub: Wire to Finnhub or X API in production
|
|
30
|
+
const mockSentiment = {
|
|
31
|
+
asset: ticker,
|
|
32
|
+
bullish_signals: 14,
|
|
33
|
+
bearish_signals: 2,
|
|
34
|
+
trend: "Strong Buy",
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
content: [
|
|
38
|
+
{
|
|
39
|
+
type: "text",
|
|
40
|
+
text: JSON.stringify(mockSentiment, null, 2),
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
// 3. Register Tool: Risk Sandbox
|
|
46
|
+
server.tool("calculate_position_size", "Calculates optimal position sizing based on portfolio risk tolerance", {
|
|
47
|
+
portfolio_balance: z.number(),
|
|
48
|
+
risk_percentage: z.number().max(10),
|
|
49
|
+
entry_price: z.number(),
|
|
50
|
+
stop_loss: z.number(),
|
|
51
|
+
}, async ({ portfolio_balance, risk_percentage, entry_price, stop_loss }) => {
|
|
52
|
+
const riskAmount = portfolio_balance * (risk_percentage / 100);
|
|
53
|
+
const priceRisk = Math.abs(entry_price - stop_loss);
|
|
54
|
+
const positionSize = riskAmount / priceRisk;
|
|
55
|
+
return {
|
|
56
|
+
content: [
|
|
57
|
+
{
|
|
58
|
+
type: "text",
|
|
59
|
+
text: `Suggested Position Size: ${positionSize.toFixed(4)} units. Total Capital at Risk: $${riskAmount.toFixed(2)}`,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
// 4. Connect Transport Layer
|
|
65
|
+
const transport = new StdioServerTransport();
|
|
66
|
+
await server.connect(transport);
|
|
67
|
+
console.error("BeforeTheChart MCP Server running safely on stdio.");
|
|
68
|
+
}
|
|
69
|
+
main().catch(console.error);
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ltorrey/beforethechart-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"pretrade-mcp": "dist/index.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
13
|
+
"zod": "^4.6.2"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/node": "^22.20.2",
|
|
17
|
+
"tsx": "^4.23.13",
|
|
18
|
+
"typescript": "^7.0.2"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/smithery.yaml
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
startCommand:
|
|
2
|
+
type: stdio
|
|
3
|
+
configSchema:
|
|
4
|
+
type: object
|
|
5
|
+
required:
|
|
6
|
+
- gumroadLicenseKey
|
|
7
|
+
properties:
|
|
8
|
+
gumroadLicenseKey:
|
|
9
|
+
type: string
|
|
10
|
+
description: Your BeforeTheChart Gumroad license key.
|
|
11
|
+
commandFunction: |-
|
|
12
|
+
(config) => ({
|
|
13
|
+
command: 'npx',
|
|
14
|
+
args: ['-y', '@ltorrey/beforethechart-mcp'],
|
|
15
|
+
env: { GUMROAD_LICENSE_KEY: config.gumroadLicenseKey }
|
|
16
|
+
})
|
|
17
|
+
exampleConfig:
|
|
18
|
+
gumroadLicenseKey: YOUR_GUMROAD_LICENSE_KEY
|
package/src/auth.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export async function verifyGumroadLicense(
|
|
2
|
+
licenseKey: string,
|
|
3
|
+
productId: string,
|
|
4
|
+
): Promise<boolean> {
|
|
5
|
+
try {
|
|
6
|
+
const requestBody = new URLSearchParams();
|
|
7
|
+
requestBody.append("product_id", productId);
|
|
8
|
+
requestBody.append("license_key", licenseKey);
|
|
9
|
+
requestBody.append("increment_uses_count", "false"); // Don't burn a use just for booting the server
|
|
10
|
+
|
|
11
|
+
const response = await fetch("https://api.gumroad.com/v2/licenses/verify", {
|
|
12
|
+
method: "POST",
|
|
13
|
+
body: requestBody,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const data = await response.json();
|
|
17
|
+
|
|
18
|
+
// Check if valid and not refunded
|
|
19
|
+
if (
|
|
20
|
+
data.success &&
|
|
21
|
+
!data.purchase.refunded &&
|
|
22
|
+
!data.purchase.chargebacked
|
|
23
|
+
) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return false;
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error("License verification failed.");
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
import { verifyGumroadLicense } from "./auth.js";
|
|
8
|
+
|
|
9
|
+
const GUMROAD_PRODUCT_ID = "wavYh-9BsIk_fYxLpyoh0w==";
|
|
10
|
+
|
|
11
|
+
async function main() {
|
|
12
|
+
const licenseKey = process.env.GUMROAD_LICENSE_KEY;
|
|
13
|
+
|
|
14
|
+
if (!licenseKey) {
|
|
15
|
+
console.error(
|
|
16
|
+
"Fatal: GUMROAD_LICENSE_KEY environment variable is missing.",
|
|
17
|
+
);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const isValid = await verifyGumroadLicense(licenseKey, GUMROAD_PRODUCT_ID);
|
|
22
|
+
if (!isValid) {
|
|
23
|
+
console.error("Fatal: Invalid or refunded Gumroad License Key.");
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 1. Initialize Server
|
|
28
|
+
const server = new McpServer({
|
|
29
|
+
name: "BeforeTheChart-MCP",
|
|
30
|
+
version: "1.0.0",
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// 2. Register Tool: Sentiment Aggregator
|
|
34
|
+
server.tool(
|
|
35
|
+
"get_market_sentiment",
|
|
36
|
+
"Fetches recent news sentiment for a specific ticker symbol",
|
|
37
|
+
{
|
|
38
|
+
ticker: z
|
|
39
|
+
.string()
|
|
40
|
+
.describe("The stock or crypto ticker symbol (e.g. AAPL, BTC)"),
|
|
41
|
+
},
|
|
42
|
+
async ({ ticker }) => {
|
|
43
|
+
// Stub: Wire to Finnhub or X API in production
|
|
44
|
+
const mockSentiment = {
|
|
45
|
+
asset: ticker,
|
|
46
|
+
bullish_signals: 14,
|
|
47
|
+
bearish_signals: 2,
|
|
48
|
+
trend: "Strong Buy",
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: JSON.stringify(mockSentiment, null, 2),
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// 3. Register Tool: Risk Sandbox
|
|
63
|
+
server.tool(
|
|
64
|
+
"calculate_position_size",
|
|
65
|
+
"Calculates optimal position sizing based on portfolio risk tolerance",
|
|
66
|
+
{
|
|
67
|
+
portfolio_balance: z.number(),
|
|
68
|
+
risk_percentage: z.number().max(10),
|
|
69
|
+
entry_price: z.number(),
|
|
70
|
+
stop_loss: z.number(),
|
|
71
|
+
},
|
|
72
|
+
async ({ portfolio_balance, risk_percentage, entry_price, stop_loss }) => {
|
|
73
|
+
const riskAmount = portfolio_balance * (risk_percentage / 100);
|
|
74
|
+
const priceRisk = Math.abs(entry_price - stop_loss);
|
|
75
|
+
const positionSize = riskAmount / priceRisk;
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
content: [
|
|
79
|
+
{
|
|
80
|
+
type: "text",
|
|
81
|
+
text: `Suggested Position Size: ${positionSize.toFixed(4)} units. Total Capital at Risk: $${riskAmount.toFixed(2)}`,
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// 4. Connect Transport Layer
|
|
89
|
+
const transport = new StdioServerTransport();
|
|
90
|
+
await server.connect(transport);
|
|
91
|
+
console.error("BeforeTheChart MCP Server running safely on stdio.");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
main().catch(console.error);
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"rootDir": "src",
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"types": ["node"]
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*.ts"],
|
|
15
|
+
"exclude": ["node_modules", "dist"]
|
|
16
|
+
}
|