@warmio/mcp 3.0.2 → 4.1.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 +237 -35
- package/dist/http.d.ts +9 -0
- package/dist/http.js +117 -0
- package/dist/index.js +140 -7
- package/dist/install.d.ts +5 -1
- package/dist/install.js +95 -35
- package/dist/schemas.d.ts +225 -0
- package/dist/schemas.js +186 -0
- package/dist/server.d.ts +5 -7
- package/dist/server.js +9 -311
- package/dist/types.d.ts +157 -0
- package/dist/types.js +6 -0
- package/dist/warm-server.d.ts +14 -0
- package/dist/warm-server.js +358 -0
- package/package.json +25 -2
package/README.md
CHANGED
|
@@ -1,60 +1,262 @@
|
|
|
1
|
-
# Warm MCP
|
|
1
|
+
# Warm MCP
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Read-only MCP server for Warm financial data.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Warm supports two transport shapes from this repo:
|
|
6
|
+
|
|
7
|
+
- Local `stdio` via the npm package `@warmio/mcp`
|
|
8
|
+
- Self-hosted Streamable HTTP via `warm-mcp http`
|
|
9
|
+
|
|
10
|
+
Warm does not currently publish a Warm-hosted Streamable HTTP MCP endpoint from this repo.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
6
13
|
|
|
7
14
|
```bash
|
|
8
15
|
npx @warmio/mcp
|
|
9
16
|
```
|
|
10
17
|
|
|
11
|
-
|
|
12
|
-
|
|
18
|
+
The installer detects supported MCP clients, prompts for your Warm API key, and writes the local
|
|
19
|
+
`stdio` server config automatically.
|
|
13
20
|
|
|
14
|
-
|
|
15
|
-
- "What's my net worth?"
|
|
16
|
-
- "How much did I spend on restaurants last month?"
|
|
17
|
-
- "Show me my subscriptions"
|
|
21
|
+
## Requirements
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
- Warm Pro
|
|
24
|
+
- A Warm API key from [Settings -> API Keys](https://warm.io/settings)
|
|
25
|
+
- Node.js 18+
|
|
20
26
|
|
|
21
|
-
|
|
22
|
-
|---------|-------------|
|
|
23
|
-
| `npx @warmio/mcp` | Run the installer / configurator |
|
|
24
|
-
| `npx @warmio/mcp --force` | Re-run installer (updates API key in all configs) |
|
|
25
|
-
| `npx @warmio/mcp --server` | Start the MCP server (used internally by clients) |
|
|
27
|
+
## Manual `stdio` Config
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
Use this shape when configuring a local MCP client manually:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"mcpServers": {
|
|
34
|
+
"warm": {
|
|
35
|
+
"command": "npx",
|
|
36
|
+
"args": ["-y", "@warmio/mcp", "--server"],
|
|
37
|
+
"env": {
|
|
38
|
+
"WARM_API_KEY": "your_warm_api_key"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Self-hosted Streamable HTTP
|
|
46
|
+
|
|
47
|
+
Run the HTTP server locally or behind your own reverse proxy:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx @warmio/mcp http --host 0.0.0.0 --port 3000 --path /mcp
|
|
51
|
+
```
|
|
28
52
|
|
|
29
|
-
|
|
30
|
-
- **API key** — Generate in [Settings → API Keys](https://warm.io/settings)
|
|
31
|
-
- **Node.js 18+** — For running the MCP server
|
|
53
|
+
Environment overrides:
|
|
32
54
|
|
|
33
|
-
|
|
55
|
+
- `WARM_MCP_HTTP_HOST`
|
|
56
|
+
- `WARM_MCP_HTTP_PORT`
|
|
57
|
+
- `WARM_MCP_HTTP_PATH`
|
|
58
|
+
- `WARM_MCP_ALLOWED_HOSTS`
|
|
34
59
|
|
|
35
|
-
|
|
36
|
-
2. It detects which MCP clients you have installed
|
|
37
|
-
3. Prompts for your Warm API key
|
|
38
|
-
4. Writes the server config into each client's settings
|
|
39
|
-
5. Each client is configured to run `npx -y @warmio/mcp --server` on demand
|
|
60
|
+
On Windows, prefer:
|
|
40
61
|
|
|
41
|
-
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"mcpServers": {
|
|
65
|
+
"warm": {
|
|
66
|
+
"command": "cmd",
|
|
67
|
+
"args": ["/c", "npx", "-y", "@warmio/mcp", "--server"],
|
|
68
|
+
"env": {
|
|
69
|
+
"WARM_API_KEY": "your_warm_api_key"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Core Tools
|
|
42
77
|
|
|
43
|
-
|
|
78
|
+
Warm's published/documented MCP surface is the following four-tool core:
|
|
44
79
|
|
|
45
80
|
| Tool | Description |
|
|
46
81
|
|------|-------------|
|
|
47
|
-
| `get_accounts` | List
|
|
48
|
-
| `get_transactions` |
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
51
|
-
|
|
82
|
+
| `get_accounts` | List connected accounts with current balances |
|
|
83
|
+
| `get_transactions` | Page through transactions with an opaque cursor |
|
|
84
|
+
| `get_financial_state` | Return the current typed financial state bundle |
|
|
85
|
+
| `verify_key` | Validate the configured API key |
|
|
86
|
+
|
|
87
|
+
## Strict Contract
|
|
88
|
+
|
|
89
|
+
- Every tool takes a JSON object input and returns a JSON object output.
|
|
90
|
+
- Treat the contracts as closed and typed. Do not depend on undocumented fields.
|
|
91
|
+
- Calendar dates use `YYYY-MM-DD`. Incremental sync timestamps use ISO 8601 datetimes.
|
|
92
|
+
- Amounts are numbers, never formatted strings.
|
|
93
|
+
- Transaction amounts follow the Plaid sign convention:
|
|
94
|
+
positive = expense/debit, negative = income/credit.
|
|
95
|
+
- Pagination cursors are opaque strings. Do not parse them or mix them with changed filters.
|
|
96
|
+
|
|
97
|
+
### `get_accounts`
|
|
98
|
+
|
|
99
|
+
Input:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"accounts": [
|
|
110
|
+
{
|
|
111
|
+
"name": "Primary Checking",
|
|
112
|
+
"type": "depository",
|
|
113
|
+
"subtype": "checking",
|
|
114
|
+
"balance": 2450.12,
|
|
115
|
+
"institution": "Chase",
|
|
116
|
+
"mask": "1234"
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### `get_transactions`
|
|
123
|
+
|
|
124
|
+
Input:
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"limit": 100,
|
|
129
|
+
"cursor": "opaque-cursor-from-a-prior-page",
|
|
130
|
+
"last_knowledge": "2026-03-11T00:00:00.000Z"
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"generated_at": "2026-03-11T12:00:00.000Z",
|
|
139
|
+
"next_knowledge": "2026-03-11T12:00:00.000Z",
|
|
140
|
+
"txns": [
|
|
141
|
+
{
|
|
142
|
+
"id": "txn_123",
|
|
143
|
+
"date": "2026-01-15",
|
|
144
|
+
"amount": 12.34,
|
|
145
|
+
"merchant": "Coffee Shop",
|
|
146
|
+
"description": "COFFEE SHOP",
|
|
147
|
+
"category": "FOOD_AND_DRINK",
|
|
148
|
+
"detailed_category": "FOOD_AND_DRINK_COFFEE"
|
|
149
|
+
}
|
|
150
|
+
],
|
|
151
|
+
"pagination": {
|
|
152
|
+
"limit": 100,
|
|
153
|
+
"next_cursor": "opaque-next-cursor",
|
|
154
|
+
"has_more": true
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Cursor model:
|
|
160
|
+
|
|
161
|
+
1. Omit `cursor` on the first call.
|
|
162
|
+
2. Keep `limit` fixed while following a cursor chain.
|
|
163
|
+
3. If `pagination.next_cursor` is non-null, pass it unchanged to fetch the next page.
|
|
164
|
+
4. Stop when `next_cursor` is `null`.
|
|
165
|
+
5. Do not combine `cursor` with `last_knowledge`.
|
|
166
|
+
|
|
167
|
+
### `get_financial_state`
|
|
168
|
+
|
|
169
|
+
Input:
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
|
|
177
|
+
```json
|
|
178
|
+
{
|
|
179
|
+
"generated_at": "2026-03-11T12:00:00.000Z",
|
|
180
|
+
"snapshots": [
|
|
181
|
+
{
|
|
182
|
+
"date": "2026-03-11",
|
|
183
|
+
"net_worth": 125430.55,
|
|
184
|
+
"total_assets": 168210.77,
|
|
185
|
+
"total_liabilities": 42780.22
|
|
186
|
+
}
|
|
187
|
+
],
|
|
188
|
+
"recurring": [
|
|
189
|
+
{
|
|
190
|
+
"merchant": "Netflix",
|
|
191
|
+
"amount": 15.49,
|
|
192
|
+
"frequency": "MONTHLY",
|
|
193
|
+
"next_date": "2026-03-18",
|
|
194
|
+
"type": "subscription",
|
|
195
|
+
"active": true
|
|
196
|
+
}
|
|
197
|
+
],
|
|
198
|
+
"budgets": [
|
|
199
|
+
{
|
|
200
|
+
"name": "Dining Out",
|
|
201
|
+
"amount": 400,
|
|
202
|
+
"spent": 182.55,
|
|
203
|
+
"remaining": 217.45,
|
|
204
|
+
"percent_used": 45.64,
|
|
205
|
+
"period": "monthly",
|
|
206
|
+
"status": "on_track"
|
|
207
|
+
}
|
|
208
|
+
],
|
|
209
|
+
"goals": [
|
|
210
|
+
{
|
|
211
|
+
"name": "Emergency Fund",
|
|
212
|
+
"target": 10000,
|
|
213
|
+
"current": 4200,
|
|
214
|
+
"progress_percent": 42,
|
|
215
|
+
"target_date": null,
|
|
216
|
+
"status": "active",
|
|
217
|
+
"category": "safety",
|
|
218
|
+
"monthly_contribution_needed": 400
|
|
219
|
+
}
|
|
220
|
+
],
|
|
221
|
+
"health": {
|
|
222
|
+
"score": 78,
|
|
223
|
+
"label": "Good",
|
|
224
|
+
"data_completeness": 94,
|
|
225
|
+
"pillars": {
|
|
226
|
+
"spend": 20,
|
|
227
|
+
"save": 23,
|
|
228
|
+
"borrow": 15,
|
|
229
|
+
"build": 20
|
|
230
|
+
},
|
|
231
|
+
"message": null
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
If Warm does not have enough state data yet, nullable fields remain `null`.
|
|
237
|
+
|
|
238
|
+
### `verify_key`
|
|
239
|
+
|
|
240
|
+
Input:
|
|
241
|
+
|
|
242
|
+
```json
|
|
243
|
+
{}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
|
|
248
|
+
```json
|
|
249
|
+
{
|
|
250
|
+
"valid": true,
|
|
251
|
+
"status": "ok"
|
|
252
|
+
}
|
|
253
|
+
```
|
|
52
254
|
|
|
53
255
|
## Security
|
|
54
256
|
|
|
55
|
-
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
257
|
+
- Read-only: no write, delete, transfer, or mutation tools
|
|
258
|
+
- Scoped: the key only reads the owner's Warm data
|
|
259
|
+
- Revocable: delete the key in Settings to revoke access immediately
|
|
58
260
|
|
|
59
261
|
## Development
|
|
60
262
|
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Server as HttpServer } from 'http';
|
|
2
|
+
export interface WarmHttpServerOptions {
|
|
3
|
+
host?: string;
|
|
4
|
+
port?: number;
|
|
5
|
+
path?: string;
|
|
6
|
+
allowedHosts?: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare function resolveHttpServerOptions(overrides?: WarmHttpServerOptions): Required<WarmHttpServerOptions>;
|
|
9
|
+
export declare function startHttpServer(options?: WarmHttpServerOptions): Promise<HttpServer>;
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js';
|
|
2
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
3
|
+
import { createWarmServer } from './server.js';
|
|
4
|
+
const DEFAULT_HTTP_HOST = '0.0.0.0';
|
|
5
|
+
const DEFAULT_HTTP_PORT = 3000;
|
|
6
|
+
const DEFAULT_HTTP_PATH = '/mcp';
|
|
7
|
+
function parsePort(value, fallback) {
|
|
8
|
+
const parsed = Number(value);
|
|
9
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
10
|
+
}
|
|
11
|
+
function normalizePath(value) {
|
|
12
|
+
if (!value) {
|
|
13
|
+
return DEFAULT_HTTP_PATH;
|
|
14
|
+
}
|
|
15
|
+
return value.startsWith('/') ? value : `/${value}`;
|
|
16
|
+
}
|
|
17
|
+
function parseAllowedHosts(value) {
|
|
18
|
+
if (!value) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
return value
|
|
22
|
+
.split(',')
|
|
23
|
+
.map((host) => host.trim())
|
|
24
|
+
.filter(Boolean);
|
|
25
|
+
}
|
|
26
|
+
function jsonRpcError(message) {
|
|
27
|
+
return {
|
|
28
|
+
jsonrpc: '2.0',
|
|
29
|
+
error: {
|
|
30
|
+
code: -32000,
|
|
31
|
+
message,
|
|
32
|
+
},
|
|
33
|
+
id: null,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function sendMethodNotAllowed(res) {
|
|
37
|
+
res.status(405).set('Allow', 'POST').json(jsonRpcError('Method not allowed.'));
|
|
38
|
+
}
|
|
39
|
+
export function resolveHttpServerOptions(overrides = {}) {
|
|
40
|
+
return {
|
|
41
|
+
host: overrides.host ||
|
|
42
|
+
process.env.WARM_MCP_HTTP_HOST ||
|
|
43
|
+
process.env.MCP_HOST ||
|
|
44
|
+
process.env.HOST ||
|
|
45
|
+
DEFAULT_HTTP_HOST,
|
|
46
|
+
port: overrides.port ??
|
|
47
|
+
parsePort(process.env.WARM_MCP_HTTP_PORT || process.env.MCP_PORT || process.env.PORT, DEFAULT_HTTP_PORT),
|
|
48
|
+
path: normalizePath(overrides.path || process.env.WARM_MCP_HTTP_PATH || process.env.MCP_PATH || DEFAULT_HTTP_PATH),
|
|
49
|
+
allowedHosts: overrides.allowedHosts ??
|
|
50
|
+
parseAllowedHosts(process.env.WARM_MCP_ALLOWED_HOSTS || process.env.MCP_ALLOWED_HOSTS),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export async function startHttpServer(options = {}) {
|
|
54
|
+
const resolved = resolveHttpServerOptions(options);
|
|
55
|
+
const app = createMcpExpressApp({
|
|
56
|
+
host: resolved.host,
|
|
57
|
+
...(resolved.allowedHosts.length > 0 ? { allowedHosts: resolved.allowedHosts } : {}),
|
|
58
|
+
});
|
|
59
|
+
app.post(resolved.path, async (req, res) => {
|
|
60
|
+
const server = createWarmServer();
|
|
61
|
+
const transport = new StreamableHTTPServerTransport({
|
|
62
|
+
sessionIdGenerator: undefined,
|
|
63
|
+
});
|
|
64
|
+
let cleanedUp = false;
|
|
65
|
+
const cleanup = async () => {
|
|
66
|
+
if (cleanedUp) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
cleanedUp = true;
|
|
70
|
+
await Promise.allSettled([transport.close(), server.close()]);
|
|
71
|
+
};
|
|
72
|
+
res.on('close', () => {
|
|
73
|
+
void cleanup();
|
|
74
|
+
});
|
|
75
|
+
try {
|
|
76
|
+
await server.connect(transport);
|
|
77
|
+
await transport.handleRequest(req, res, req.body);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
console.error('Error handling Warm MCP HTTP request:', error);
|
|
81
|
+
if (!res.headersSent) {
|
|
82
|
+
res.status(500).json(jsonRpcError('Internal server error'));
|
|
83
|
+
}
|
|
84
|
+
void cleanup();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
app.get(resolved.path, (_req, res) => {
|
|
88
|
+
sendMethodNotAllowed(res);
|
|
89
|
+
});
|
|
90
|
+
app.delete(resolved.path, (_req, res) => {
|
|
91
|
+
sendMethodNotAllowed(res);
|
|
92
|
+
});
|
|
93
|
+
const listener = await new Promise((resolve, reject) => {
|
|
94
|
+
const httpServer = app.listen(resolved.port, resolved.host, () => {
|
|
95
|
+
resolve(httpServer);
|
|
96
|
+
});
|
|
97
|
+
httpServer.once('error', reject);
|
|
98
|
+
});
|
|
99
|
+
let shuttingDown = false;
|
|
100
|
+
const shutdown = (signal) => {
|
|
101
|
+
if (shuttingDown) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
shuttingDown = true;
|
|
105
|
+
console.log(`Received ${signal}, shutting down Warm MCP HTTP server...`);
|
|
106
|
+
listener.close((error) => {
|
|
107
|
+
if (error) {
|
|
108
|
+
console.error('Failed to close Warm MCP HTTP server:', error);
|
|
109
|
+
process.exitCode = 1;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
process.once('SIGINT', () => shutdown('SIGINT'));
|
|
114
|
+
process.once('SIGTERM', () => shutdown('SIGTERM'));
|
|
115
|
+
console.log(`Warm MCP Streamable HTTP server listening on http://${resolved.host}:${resolved.port}${resolved.path}`);
|
|
116
|
+
return listener;
|
|
117
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,143 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { startHttpServer } from './http.js';
|
|
3
|
+
import { install } from './install.js';
|
|
4
|
+
import { startStdioServer } from './server.js';
|
|
5
|
+
function printUsage() {
|
|
6
|
+
console.log('');
|
|
7
|
+
console.log(' Warm MCP');
|
|
8
|
+
console.log(' --------');
|
|
9
|
+
console.log('');
|
|
10
|
+
console.log(' warm-mcp [install] [--force] [--no-validate]');
|
|
11
|
+
console.log(' warm-mcp stdio');
|
|
12
|
+
console.log(' warm-mcp http [--host 0.0.0.0] [--port 3000] [--path /mcp]');
|
|
13
|
+
console.log(' [--allowed-hosts host1,host2]');
|
|
14
|
+
console.log('');
|
|
15
|
+
console.log(' Aliases:');
|
|
16
|
+
console.log(' --server, --stdio Start stdio mode');
|
|
17
|
+
console.log(' --http Start HTTP mode');
|
|
18
|
+
console.log('');
|
|
5
19
|
}
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
20
|
+
function parsePort(value) {
|
|
21
|
+
const parsed = Number(value);
|
|
22
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
23
|
+
throw new Error(`Invalid port: ${value}`);
|
|
24
|
+
}
|
|
25
|
+
return parsed;
|
|
26
|
+
}
|
|
27
|
+
function parseList(value) {
|
|
28
|
+
return value
|
|
29
|
+
.split(',')
|
|
30
|
+
.map((item) => item.trim())
|
|
31
|
+
.filter(Boolean);
|
|
32
|
+
}
|
|
33
|
+
function readOption(args, index, flag) {
|
|
34
|
+
const arg = args[index];
|
|
35
|
+
if (arg.startsWith(`${flag}=`)) {
|
|
36
|
+
return {
|
|
37
|
+
nextIndex: index,
|
|
38
|
+
value: arg.slice(flag.length + 1),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const value = args[index + 1];
|
|
42
|
+
if (!value) {
|
|
43
|
+
throw new Error(`Missing value for ${flag}`);
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
nextIndex: index + 1,
|
|
47
|
+
value,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function parseCliArgs(args) {
|
|
51
|
+
const options = {
|
|
52
|
+
command: 'install',
|
|
53
|
+
force: false,
|
|
54
|
+
validateApiKey: true,
|
|
55
|
+
};
|
|
56
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
57
|
+
const arg = args[index];
|
|
58
|
+
if (arg === 'help' || arg === '--help' || arg === '-h') {
|
|
59
|
+
options.command = 'help';
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (arg === 'install' || arg === '--install') {
|
|
63
|
+
options.command = 'install';
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (arg === 'stdio' || arg === 'server' || arg === '--stdio' || arg === '--server') {
|
|
67
|
+
options.command = 'stdio';
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (arg === 'http' || arg === '--http') {
|
|
71
|
+
options.command = 'http';
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (arg === '--force') {
|
|
75
|
+
options.force = true;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (arg === '--no-validate') {
|
|
79
|
+
options.validateApiKey = false;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (arg === '--host' || arg.startsWith('--host=')) {
|
|
83
|
+
const { nextIndex, value } = readOption(args, index, '--host');
|
|
84
|
+
options.host = value;
|
|
85
|
+
index = nextIndex;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (arg === '--port' || arg.startsWith('--port=')) {
|
|
89
|
+
const { nextIndex, value } = readOption(args, index, '--port');
|
|
90
|
+
options.port = parsePort(value);
|
|
91
|
+
index = nextIndex;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (arg === '--path' || arg.startsWith('--path=')) {
|
|
95
|
+
const { nextIndex, value } = readOption(args, index, '--path');
|
|
96
|
+
options.path = value;
|
|
97
|
+
index = nextIndex;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (arg === '--allowed-hosts' || arg.startsWith('--allowed-hosts=')) {
|
|
101
|
+
const { nextIndex, value } = readOption(args, index, '--allowed-hosts');
|
|
102
|
+
options.allowedHosts = parseList(value);
|
|
103
|
+
index = nextIndex;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
107
|
+
}
|
|
108
|
+
return options;
|
|
109
|
+
}
|
|
110
|
+
async function main() {
|
|
111
|
+
const options = parseCliArgs(process.argv.slice(2));
|
|
112
|
+
switch (options.command) {
|
|
113
|
+
case 'help':
|
|
114
|
+
printUsage();
|
|
115
|
+
return;
|
|
116
|
+
case 'http':
|
|
117
|
+
await startHttpServer({
|
|
118
|
+
allowedHosts: options.allowedHosts,
|
|
119
|
+
host: options.host,
|
|
120
|
+
path: options.path,
|
|
121
|
+
port: options.port,
|
|
122
|
+
});
|
|
123
|
+
return;
|
|
124
|
+
case 'stdio':
|
|
125
|
+
await startStdioServer();
|
|
126
|
+
return;
|
|
127
|
+
case 'install':
|
|
128
|
+
await install({
|
|
129
|
+
force: options.force,
|
|
130
|
+
validateApiKey: options.validateApiKey,
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
await main();
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
140
|
+
console.error(message);
|
|
141
|
+
console.error('Run "warm-mcp --help" for usage.');
|
|
142
|
+
process.exit(1);
|
|
9
143
|
}
|
|
10
|
-
export {};
|
package/dist/install.d.ts
CHANGED