@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 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
+ }