ai-cost-analyzer 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.
Potentially problematic release.
This version of ai-cost-analyzer might be problematic. Click here for more details.
- package/README.md +228 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +219 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/analyze.d.ts +6 -0
- package/dist/tools/analyze.d.ts.map +1 -0
- package/dist/tools/analyze.js +210 -0
- package/dist/tools/analyze.js.map +1 -0
- package/dist/tools/compare.d.ts +6 -0
- package/dist/tools/compare.d.ts.map +1 -0
- package/dist/tools/compare.js +212 -0
- package/dist/tools/compare.js.map +1 -0
- package/dist/tools/optimize.d.ts +6 -0
- package/dist/tools/optimize.d.ts.map +1 -0
- package/dist/tools/optimize.js +204 -0
- package/dist/tools/optimize.js.map +1 -0
- package/dist/tools/pricing.d.ts +16 -0
- package/dist/tools/pricing.d.ts.map +1 -0
- package/dist/tools/pricing.js +162 -0
- package/dist/tools/pricing.js.map +1 -0
- package/dist/tools/savings.d.ts +6 -0
- package/dist/tools/savings.d.ts.map +1 -0
- package/dist/tools/savings.js +193 -0
- package/dist/tools/savings.js.map +1 -0
- package/dist/types.d.ts +144 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +41 -0
- package/src/index.ts +291 -0
- package/src/tools/analyze.ts +260 -0
- package/src/tools/compare.ts +282 -0
- package/src/tools/optimize.ts +291 -0
- package/src/tools/pricing.ts +197 -0
- package/src/tools/savings.ts +238 -0
- package/src/types.ts +183 -0
- package/tsconfig.json +19 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the AI Cost Analyzer MCP server.
|
|
3
|
+
*/
|
|
4
|
+
export interface ModelPricing {
|
|
5
|
+
/** Human-readable model name */
|
|
6
|
+
name: string;
|
|
7
|
+
/** Provider (e.g. "Anthropic", "OpenAI", "Google") */
|
|
8
|
+
provider: string;
|
|
9
|
+
/** Cost per 1M input tokens in USD */
|
|
10
|
+
inputPerMillion: number;
|
|
11
|
+
/** Cost per 1M output tokens in USD */
|
|
12
|
+
outputPerMillion: number;
|
|
13
|
+
/** Cost per 1M cached input tokens in USD (if supported) */
|
|
14
|
+
cachedInputPerMillion?: number;
|
|
15
|
+
/** Max context window in tokens */
|
|
16
|
+
contextWindow: number;
|
|
17
|
+
/** Max output tokens */
|
|
18
|
+
maxOutput: number;
|
|
19
|
+
/** Relative quality tier: 1 = frontier, 2 = strong, 3 = fast/cheap */
|
|
20
|
+
qualityTier: 1 | 2 | 3;
|
|
21
|
+
}
|
|
22
|
+
export type ModelId = "claude-opus" | "claude-sonnet" | "claude-haiku" | "gpt-4o" | "gpt-4o-mini" | "gemini-2.0-flash";
|
|
23
|
+
export interface UsageRecord {
|
|
24
|
+
/** Model identifier */
|
|
25
|
+
model: string;
|
|
26
|
+
/** Number of input tokens */
|
|
27
|
+
input_tokens: number;
|
|
28
|
+
/** Number of output tokens */
|
|
29
|
+
output_tokens: number;
|
|
30
|
+
/** Number of cached input tokens (subset of input_tokens) */
|
|
31
|
+
cached_tokens?: number;
|
|
32
|
+
/** ISO-8601 timestamp */
|
|
33
|
+
timestamp: string;
|
|
34
|
+
/** Optional: number of tool definitions sent in the request */
|
|
35
|
+
tool_definitions?: number;
|
|
36
|
+
/** Optional: system prompt token count */
|
|
37
|
+
system_prompt_tokens?: number;
|
|
38
|
+
}
|
|
39
|
+
export interface ModelCostBreakdown {
|
|
40
|
+
model: string;
|
|
41
|
+
requestCount: number;
|
|
42
|
+
totalInputTokens: number;
|
|
43
|
+
totalOutputTokens: number;
|
|
44
|
+
totalCachedTokens: number;
|
|
45
|
+
inputCost: number;
|
|
46
|
+
outputCost: number;
|
|
47
|
+
cacheSavings: number;
|
|
48
|
+
totalCost: number;
|
|
49
|
+
}
|
|
50
|
+
export interface TrendDataPoint {
|
|
51
|
+
period: string;
|
|
52
|
+
requestCount: number;
|
|
53
|
+
totalCost: number;
|
|
54
|
+
avgCostPerRequest: number;
|
|
55
|
+
}
|
|
56
|
+
export interface UsageAnalysis {
|
|
57
|
+
summary: {
|
|
58
|
+
totalRequests: number;
|
|
59
|
+
totalCost: number;
|
|
60
|
+
avgCostPerRequest: number;
|
|
61
|
+
totalInputTokens: number;
|
|
62
|
+
totalOutputTokens: number;
|
|
63
|
+
totalCachedTokens: number;
|
|
64
|
+
};
|
|
65
|
+
costByModel: ModelCostBreakdown[];
|
|
66
|
+
wasteEstimation: {
|
|
67
|
+
unusedToolDefinitionTokens: number;
|
|
68
|
+
oversizedSystemPromptTokens: number;
|
|
69
|
+
estimatedWasteCost: number;
|
|
70
|
+
wastePercentage: number;
|
|
71
|
+
};
|
|
72
|
+
trends: {
|
|
73
|
+
daily: TrendDataPoint[];
|
|
74
|
+
weekly: TrendDataPoint[];
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export interface SavingsEstimate {
|
|
78
|
+
currentMonthlyCost: number;
|
|
79
|
+
optimizedMonthlyCost: number;
|
|
80
|
+
totalMonthlySavings: number;
|
|
81
|
+
savingsPercentage: number;
|
|
82
|
+
breakdown: {
|
|
83
|
+
promptCaching: {
|
|
84
|
+
description: string;
|
|
85
|
+
currentCost: number;
|
|
86
|
+
optimizedCost: number;
|
|
87
|
+
monthlySavings: number;
|
|
88
|
+
};
|
|
89
|
+
modelRouting: {
|
|
90
|
+
description: string;
|
|
91
|
+
currentCost: number;
|
|
92
|
+
optimizedCost: number;
|
|
93
|
+
monthlySavings: number;
|
|
94
|
+
};
|
|
95
|
+
contextPruning: {
|
|
96
|
+
description: string;
|
|
97
|
+
currentCost: number;
|
|
98
|
+
optimizedCost: number;
|
|
99
|
+
monthlySavings: number;
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
recommendations: string[];
|
|
103
|
+
}
|
|
104
|
+
export interface OptimizationResult {
|
|
105
|
+
original: {
|
|
106
|
+
systemPrompt: string;
|
|
107
|
+
systemPromptTokens: number;
|
|
108
|
+
toolDefinitions: string[];
|
|
109
|
+
toolDefinitionTokens: number;
|
|
110
|
+
totalTokens: number;
|
|
111
|
+
};
|
|
112
|
+
optimized: {
|
|
113
|
+
systemPrompt: string;
|
|
114
|
+
systemPromptTokens: number;
|
|
115
|
+
toolDefinitions: string[];
|
|
116
|
+
toolDefinitionTokens: number;
|
|
117
|
+
totalTokens: number;
|
|
118
|
+
};
|
|
119
|
+
savings: {
|
|
120
|
+
tokensReduced: number;
|
|
121
|
+
percentageReduction: number;
|
|
122
|
+
estimatedMonthlySavings: number;
|
|
123
|
+
};
|
|
124
|
+
recommendations: string[];
|
|
125
|
+
}
|
|
126
|
+
export interface ModelComparisonEntry {
|
|
127
|
+
model: string;
|
|
128
|
+
provider: string;
|
|
129
|
+
qualityTier: string;
|
|
130
|
+
estimatedInputCost: number;
|
|
131
|
+
estimatedOutputCost: number;
|
|
132
|
+
estimatedTotalCost: number;
|
|
133
|
+
costPer1000Requests: number;
|
|
134
|
+
recommendation: string;
|
|
135
|
+
bestFor: string[];
|
|
136
|
+
}
|
|
137
|
+
export interface ModelComparison {
|
|
138
|
+
taskDescription: string;
|
|
139
|
+
estimatedInputTokens: number;
|
|
140
|
+
estimatedOutputTokens: number;
|
|
141
|
+
models: ModelComparisonEntry[];
|
|
142
|
+
recommendation: string;
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ai-cost-analyzer",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server that helps developers understand and reduce their AI API costs through usage analysis, savings estimation, prompt optimization, and model comparison.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ai-cost-analyzer": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"start:sse": "node dist/index.js --sse",
|
|
14
|
+
"dev": "tsc --watch",
|
|
15
|
+
"prepublishOnly": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mcp",
|
|
19
|
+
"ai",
|
|
20
|
+
"cost",
|
|
21
|
+
"analyzer",
|
|
22
|
+
"llm",
|
|
23
|
+
"tokens",
|
|
24
|
+
"optimization",
|
|
25
|
+
"claude",
|
|
26
|
+
"openai",
|
|
27
|
+
"gemini"
|
|
28
|
+
],
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
32
|
+
"zod": "^3.25.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^22.0.0",
|
|
36
|
+
"typescript": "^5.7.0"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18.0.0"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AI Cost Analyzer -- MCP Server
|
|
5
|
+
*
|
|
6
|
+
* Helps developers understand and reduce their AI API costs through
|
|
7
|
+
* usage analysis, savings estimation, prompt optimization, and model comparison.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
11
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
12
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
import http from "node:http";
|
|
15
|
+
|
|
16
|
+
import { handleAnalyzeUsage } from "./tools/analyze.js";
|
|
17
|
+
import { handleEstimateSavings } from "./tools/savings.js";
|
|
18
|
+
import { handleOptimizePrompt } from "./tools/optimize.js";
|
|
19
|
+
import { handleCompareModels } from "./tools/compare.js";
|
|
20
|
+
import { handleGetPricing } from "./tools/pricing.js";
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Zod schemas for tool inputs
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
const UsageRecordSchema = z.object({
|
|
27
|
+
model: z.string().describe("Model identifier (e.g. 'claude-sonnet', 'gpt-4o')"),
|
|
28
|
+
input_tokens: z.number().int().min(0).describe("Number of input tokens"),
|
|
29
|
+
output_tokens: z.number().int().min(0).describe("Number of output tokens"),
|
|
30
|
+
cached_tokens: z.number().int().min(0).optional().describe("Number of cached input tokens"),
|
|
31
|
+
timestamp: z.string().describe("ISO-8601 timestamp of the request"),
|
|
32
|
+
tool_definitions: z.number().int().min(0).optional().describe("Number of tool definitions sent"),
|
|
33
|
+
system_prompt_tokens: z.number().int().min(0).optional().describe("Token count of the system prompt"),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Server setup
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
const server = new McpServer({
|
|
41
|
+
name: "ai-cost-analyzer",
|
|
42
|
+
version: "1.0.0",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Tool: analyze_usage
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
server.tool(
|
|
50
|
+
"analyze_usage",
|
|
51
|
+
"Analyze AI API usage data and return cost breakdowns by model, average cost per request, token waste estimation, and daily/weekly trends.",
|
|
52
|
+
{
|
|
53
|
+
usage_data: z.array(UsageRecordSchema).describe(
|
|
54
|
+
"Array of API usage records with model, tokens, and timestamps",
|
|
55
|
+
),
|
|
56
|
+
},
|
|
57
|
+
async ({ usage_data }) => {
|
|
58
|
+
try {
|
|
59
|
+
const result = handleAnalyzeUsage(usage_data);
|
|
60
|
+
return {
|
|
61
|
+
content: [{ type: "text" as const, text: result }],
|
|
62
|
+
};
|
|
63
|
+
} catch (err) {
|
|
64
|
+
return {
|
|
65
|
+
content: [{
|
|
66
|
+
type: "text" as const,
|
|
67
|
+
text: `Error analyzing usage: ${err instanceof Error ? err.message : String(err)}`,
|
|
68
|
+
}],
|
|
69
|
+
isError: true,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// Tool: estimate_savings
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
server.tool(
|
|
80
|
+
"estimate_savings",
|
|
81
|
+
"Estimate potential monthly savings from prompt caching, model routing, and context pruning based on current usage patterns.",
|
|
82
|
+
{
|
|
83
|
+
usage_data: z.array(UsageRecordSchema).describe(
|
|
84
|
+
"Array of API usage records representing current usage patterns",
|
|
85
|
+
),
|
|
86
|
+
monthly_multiplier: z.number().min(1).optional().describe(
|
|
87
|
+
"If the usage_data is a sample, multiply costs by this factor to project monthly totals (default: 1)",
|
|
88
|
+
),
|
|
89
|
+
},
|
|
90
|
+
async ({ usage_data, monthly_multiplier }) => {
|
|
91
|
+
try {
|
|
92
|
+
const result = handleEstimateSavings(usage_data, monthly_multiplier);
|
|
93
|
+
return {
|
|
94
|
+
content: [{ type: "text" as const, text: result }],
|
|
95
|
+
};
|
|
96
|
+
} catch (err) {
|
|
97
|
+
return {
|
|
98
|
+
content: [{
|
|
99
|
+
type: "text" as const,
|
|
100
|
+
text: `Error estimating savings: ${err instanceof Error ? err.message : String(err)}`,
|
|
101
|
+
}],
|
|
102
|
+
isError: true,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// Tool: optimize_prompt
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
server.tool(
|
|
113
|
+
"optimize_prompt",
|
|
114
|
+
"Analyze a system prompt and tool definitions, return an optimized version with redundant content removed, token counts before/after, and cost reduction estimates.",
|
|
115
|
+
{
|
|
116
|
+
system_prompt: z.string().describe("The system prompt text to optimize"),
|
|
117
|
+
tool_definitions: z.array(z.string()).describe(
|
|
118
|
+
"Array of tool definition strings (JSON or plain text)",
|
|
119
|
+
),
|
|
120
|
+
requests_per_month: z.number().int().min(1).optional().describe(
|
|
121
|
+
"Estimated requests per month for cost projection (default: 10,000)",
|
|
122
|
+
),
|
|
123
|
+
model: z.string().optional().describe(
|
|
124
|
+
"Model name for pricing calculation (default: claude-sonnet)",
|
|
125
|
+
),
|
|
126
|
+
},
|
|
127
|
+
async ({ system_prompt, tool_definitions, requests_per_month, model }) => {
|
|
128
|
+
try {
|
|
129
|
+
const result = handleOptimizePrompt(system_prompt, tool_definitions, requests_per_month, model);
|
|
130
|
+
return {
|
|
131
|
+
content: [{ type: "text" as const, text: result }],
|
|
132
|
+
};
|
|
133
|
+
} catch (err) {
|
|
134
|
+
return {
|
|
135
|
+
content: [{
|
|
136
|
+
type: "text" as const,
|
|
137
|
+
text: `Error optimizing prompt: ${err instanceof Error ? err.message : String(err)}`,
|
|
138
|
+
}],
|
|
139
|
+
isError: true,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// Tool: compare_models
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
server.tool(
|
|
150
|
+
"compare_models",
|
|
151
|
+
"Compare cost and quality tradeoffs across Claude, GPT-4o, GPT-4o-mini, and Gemini models for a given task. Returns cost per request, monthly projections, and recommendations.",
|
|
152
|
+
{
|
|
153
|
+
task_description: z.string().describe(
|
|
154
|
+
"Description of the task to compare models for",
|
|
155
|
+
),
|
|
156
|
+
estimated_input_tokens: z.number().int().min(1).optional().describe(
|
|
157
|
+
"Override estimated input tokens per request",
|
|
158
|
+
),
|
|
159
|
+
estimated_output_tokens: z.number().int().min(1).optional().describe(
|
|
160
|
+
"Override estimated output tokens per request",
|
|
161
|
+
),
|
|
162
|
+
},
|
|
163
|
+
async ({ task_description, estimated_input_tokens, estimated_output_tokens }) => {
|
|
164
|
+
try {
|
|
165
|
+
const result = handleCompareModels(
|
|
166
|
+
task_description,
|
|
167
|
+
estimated_input_tokens,
|
|
168
|
+
estimated_output_tokens,
|
|
169
|
+
);
|
|
170
|
+
return {
|
|
171
|
+
content: [{ type: "text" as const, text: result }],
|
|
172
|
+
};
|
|
173
|
+
} catch (err) {
|
|
174
|
+
return {
|
|
175
|
+
content: [{
|
|
176
|
+
type: "text" as const,
|
|
177
|
+
text: `Error comparing models: ${err instanceof Error ? err.message : String(err)}`,
|
|
178
|
+
}],
|
|
179
|
+
isError: true,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
// Tool: get_pricing
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
server.tool(
|
|
190
|
+
"get_pricing",
|
|
191
|
+
"Return current pricing for all major LLM APIs (Claude, OpenAI, Gemini) with input, output, and cached token rates per million tokens.",
|
|
192
|
+
{},
|
|
193
|
+
async () => {
|
|
194
|
+
try {
|
|
195
|
+
const result = handleGetPricing();
|
|
196
|
+
return {
|
|
197
|
+
content: [{ type: "text" as const, text: result }],
|
|
198
|
+
};
|
|
199
|
+
} catch (err) {
|
|
200
|
+
return {
|
|
201
|
+
content: [{
|
|
202
|
+
type: "text" as const,
|
|
203
|
+
text: `Error fetching pricing: ${err instanceof Error ? err.message : String(err)}`,
|
|
204
|
+
}],
|
|
205
|
+
isError: true,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
// Transport: stdio or SSE
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
|
|
215
|
+
async function main(): Promise<void> {
|
|
216
|
+
const useSSE = process.argv.includes("--sse");
|
|
217
|
+
|
|
218
|
+
if (useSSE) {
|
|
219
|
+
const port = parseInt(process.env.PORT ?? "3000", 10);
|
|
220
|
+
|
|
221
|
+
// Track active transports for cleanup
|
|
222
|
+
const transports = new Map<string, SSEServerTransport>();
|
|
223
|
+
|
|
224
|
+
const httpServer = http.createServer(async (req, res) => {
|
|
225
|
+
// CORS headers
|
|
226
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
227
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
228
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
229
|
+
|
|
230
|
+
if (req.method === "OPTIONS") {
|
|
231
|
+
res.writeHead(204);
|
|
232
|
+
res.end();
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const url = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
237
|
+
|
|
238
|
+
if (url.pathname === "/sse" && req.method === "GET") {
|
|
239
|
+
// Create a new SSE transport for this connection
|
|
240
|
+
const transport = new SSEServerTransport("/messages", res);
|
|
241
|
+
const sessionId = transport.sessionId;
|
|
242
|
+
transports.set(sessionId, transport);
|
|
243
|
+
|
|
244
|
+
res.on("close", () => {
|
|
245
|
+
transports.delete(sessionId);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
await server.connect(transport);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (url.pathname === "/messages" && req.method === "POST") {
|
|
253
|
+
const sessionId = url.searchParams.get("sessionId");
|
|
254
|
+
if (!sessionId || !transports.has(sessionId)) {
|
|
255
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
256
|
+
res.end(JSON.stringify({ error: "Invalid or missing sessionId" }));
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const transport = transports.get(sessionId)!;
|
|
260
|
+
await transport.handlePostMessage(req, res);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Health check
|
|
265
|
+
if (url.pathname === "/health") {
|
|
266
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
267
|
+
res.end(JSON.stringify({ status: "ok", server: "ai-cost-analyzer", version: "1.0.0" }));
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
272
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
httpServer.listen(port, () => {
|
|
276
|
+
console.error(`AI Cost Analyzer MCP server (SSE) listening on port ${port}`);
|
|
277
|
+
console.error(` SSE endpoint: http://localhost:${port}/sse`);
|
|
278
|
+
console.error(` Messages endpoint: http://localhost:${port}/messages`);
|
|
279
|
+
console.error(` Health check: http://localhost:${port}/health`);
|
|
280
|
+
});
|
|
281
|
+
} else {
|
|
282
|
+
const transport = new StdioServerTransport();
|
|
283
|
+
await server.connect(transport);
|
|
284
|
+
console.error("AI Cost Analyzer MCP server running on stdio");
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
main().catch((err) => {
|
|
289
|
+
console.error("Fatal error:", err);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
});
|