@warmio/mcp 4.2.2 → 4.3.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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Read-only MCP server for Warm financial data.
4
4
 
5
- This package supports local `stdio` transport only.
5
+ This package starts in local `stdio` mode by default and also supports optional Streamable HTTP.
6
6
 
7
7
  ## Install
8
8
 
@@ -58,6 +58,21 @@ On Windows, prefer:
58
58
  }
59
59
  ```
60
60
 
61
+ ## Optional Streamable HTTP
62
+
63
+ If your MCP client supports Streamable HTTP, you can run:
64
+
65
+ ```bash
66
+ npx @warmio/mcp http --host 127.0.0.1 --port 3000 --path /mcp
67
+ ```
68
+
69
+ Relevant optional environment variables:
70
+
71
+ - `WARM_MCP_HTTP_HOST`
72
+ - `WARM_MCP_HTTP_PORT`
73
+ - `WARM_MCP_HTTP_PATH`
74
+ - `WARM_MCP_ALLOWED_HOSTS`
75
+
61
76
  ## Core Tools
62
77
 
63
78
  Warm's published/documented MCP surface is the following four-tool core:
@@ -1,2 +1,3 @@
1
1
  export declare function getWarmConfigDir(): string;
2
2
  export declare function getWarmApiKeyPath(): string;
3
+ export declare function readConfigFile(configPath: string): string | null;
@@ -1,3 +1,4 @@
1
+ import * as fs from 'node:fs';
1
2
  import * as os from 'node:os';
2
3
  import * as path from 'node:path';
3
4
  export function getWarmConfigDir() {
@@ -21,3 +22,11 @@ export function getWarmApiKeyPath() {
21
22
  }
22
23
  return path.join(getWarmConfigDir(), 'api_key');
23
24
  }
25
+ export function readConfigFile(configPath) {
26
+ try {
27
+ return fs.readFileSync(configPath, 'utf-8').trim() || null;
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ }
package/dist/http.d.ts CHANGED
@@ -1,9 +1,8 @@
1
- import type { Server as HttpServer } from 'http';
2
- export interface WarmHttpServerOptions {
1
+ export interface HttpServerOptions {
2
+ allowedHosts?: string[];
3
3
  host?: string;
4
- port?: number;
5
4
  path?: string;
6
- allowedHosts?: string[];
5
+ port?: number;
7
6
  }
8
- export declare function resolveHttpServerOptions(overrides?: WarmHttpServerOptions): Required<WarmHttpServerOptions>;
9
- export declare function startHttpServer(options?: WarmHttpServerOptions): Promise<HttpServer>;
7
+ export declare function resolveHttpServerOptions(overrides?: HttpServerOptions): Required<HttpServerOptions>;
8
+ export declare function startHttpServer(options?: HttpServerOptions): Promise<any>;
package/dist/http.js CHANGED
@@ -25,36 +25,38 @@ function parseAllowedHosts(value) {
25
25
  }
26
26
  function jsonRpcError(message) {
27
27
  return {
28
- jsonrpc: '2.0',
29
28
  error: {
30
29
  code: -32000,
31
30
  message,
32
31
  },
33
32
  id: null,
33
+ jsonrpc: '2.0',
34
34
  };
35
35
  }
36
- function sendMethodNotAllowed(res) {
37
- res.status(405).set('Allow', 'POST').json(jsonRpcError('Method not allowed.'));
38
- }
39
36
  export function resolveHttpServerOptions(overrides = {}) {
40
37
  return {
38
+ allowedHosts: overrides.allowedHosts ??
39
+ parseAllowedHosts(process.env.WARM_MCP_ALLOWED_HOSTS || process.env.MCP_ALLOWED_HOSTS),
41
40
  host: overrides.host ||
42
41
  process.env.WARM_MCP_HTTP_HOST ||
43
42
  process.env.MCP_HOST ||
44
43
  process.env.HOST ||
45
44
  DEFAULT_HTTP_HOST,
45
+ path: normalizePath(overrides.path || process.env.WARM_MCP_HTTP_PATH || process.env.MCP_PATH || DEFAULT_HTTP_PATH),
46
46
  port: overrides.port ??
47
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
48
  };
52
49
  }
50
+ function sendJsonMethodNotAllowed(res) {
51
+ res.status(405);
52
+ res.set('Allow', 'POST');
53
+ res.json(jsonRpcError('Method not allowed.'));
54
+ }
53
55
  export async function startHttpServer(options = {}) {
54
56
  const resolved = resolveHttpServerOptions(options);
55
57
  const app = createMcpExpressApp({
56
- host: resolved.host,
57
58
  ...(resolved.allowedHosts.length > 0 ? { allowedHosts: resolved.allowedHosts } : {}),
59
+ host: resolved.host,
58
60
  });
59
61
  app.post(resolved.path, async (req, res) => {
60
62
  const server = createWarmServer();
@@ -85,10 +87,10 @@ export async function startHttpServer(options = {}) {
85
87
  }
86
88
  });
87
89
  app.get(resolved.path, (_req, res) => {
88
- sendMethodNotAllowed(res);
90
+ sendJsonMethodNotAllowed(res);
89
91
  });
90
92
  app.delete(resolved.path, (_req, res) => {
91
- sendMethodNotAllowed(res);
93
+ sendJsonMethodNotAllowed(res);
92
94
  });
93
95
  const listener = await new Promise((resolve, reject) => {
94
96
  const httpServer = app.listen(resolved.port, resolved.host, () => {
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ export declare function startStdioServer(): Promise<import("@modelcontextprotocol/sdk/server/mcp").McpServer>;
package/dist/index.js CHANGED
@@ -1,6 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { install } from './install.js';
3
- import { startStdioServer } from './server.js';
3
+ import { startHttpServer } from './http.js';
4
+ import { loadDotEnv } from './load-env.js';
5
+ import { createWarmServer } from './server.js';
6
+ import { AdaptiveStdioTransport } from './stdio.js';
4
7
  function isInteractiveSession() {
5
8
  return process.stdin.isTTY === true && process.stdout.isTTY === true;
6
9
  }
@@ -15,6 +18,7 @@ function printUsage() {
15
18
  console.log('');
16
19
  console.log(' warm-mcp install [--force] [--no-validate]');
17
20
  console.log(' warm-mcp stdio');
21
+ console.log(' warm-mcp http [--host 127.0.0.1] [--port 3000] [--path /mcp]');
18
22
  console.log('');
19
23
  console.log(' Aliases:');
20
24
  console.log(' --server, --stdio Start stdio mode');
@@ -40,6 +44,33 @@ function parseCliArgs(args) {
40
44
  options.command = 'stdio';
41
45
  continue;
42
46
  }
47
+ if (arg === 'http') {
48
+ options.command = 'http';
49
+ continue;
50
+ }
51
+ if (arg === '--host') {
52
+ options.httpHost = args[index + 1];
53
+ index += 1;
54
+ continue;
55
+ }
56
+ if (arg === '--path') {
57
+ options.httpPath = args[index + 1];
58
+ index += 1;
59
+ continue;
60
+ }
61
+ if (arg === '--port') {
62
+ const nextArg = args[index + 1];
63
+ if (!nextArg) {
64
+ throw new Error('Missing value for --port');
65
+ }
66
+ const port = Number.parseInt(nextArg, 10);
67
+ if (!Number.isInteger(port) || port <= 0) {
68
+ throw new Error(`Invalid value for --port: ${nextArg}`);
69
+ }
70
+ options.httpPort = port;
71
+ index += 1;
72
+ continue;
73
+ }
43
74
  if (arg === '--force') {
44
75
  options.force = true;
45
76
  continue;
@@ -52,7 +83,14 @@ function parseCliArgs(args) {
52
83
  }
53
84
  return options;
54
85
  }
86
+ export async function startStdioServer() {
87
+ const server = createWarmServer();
88
+ const transport = new AdaptiveStdioTransport();
89
+ await server.connect(transport);
90
+ return server;
91
+ }
55
92
  async function main() {
93
+ loadDotEnv();
56
94
  const options = parseCliArgs(process.argv.slice(2));
57
95
  switch (options.command) {
58
96
  case 'help':
@@ -61,6 +99,13 @@ async function main() {
61
99
  case 'stdio':
62
100
  await startStdioServer();
63
101
  return;
102
+ case 'http':
103
+ await startHttpServer({
104
+ host: options.httpHost,
105
+ path: options.httpPath,
106
+ port: options.httpPort,
107
+ });
108
+ return;
64
109
  case 'install':
65
110
  await install({
66
111
  force: options.force,
@@ -73,8 +118,7 @@ try {
73
118
  await main();
74
119
  }
75
120
  catch (error) {
76
- const message = error instanceof Error ? error.message : String(error);
121
+ const message = error instanceof Error ? error.stack || error.message : String(error);
77
122
  console.error(message);
78
- console.error('Run "warm-mcp --help" for usage.');
79
123
  process.exit(1);
80
124
  }
@@ -0,0 +1 @@
1
+ export declare function loadDotEnv(): void;
@@ -0,0 +1,36 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { config as loadEnvFile } from 'dotenv';
5
+ let loaded = false;
6
+ export function loadDotEnv() {
7
+ if (loaded) {
8
+ return;
9
+ }
10
+ loaded = true;
11
+ const candidates = new Set();
12
+ let currentDir = process.cwd();
13
+ while (true) {
14
+ candidates.add(path.join(currentDir, '.env'));
15
+ const parentDir = path.dirname(currentDir);
16
+ if (parentDir === currentDir) {
17
+ break;
18
+ }
19
+ currentDir = parentDir;
20
+ }
21
+ let moduleDir = path.dirname(fileURLToPath(import.meta.url));
22
+ while (true) {
23
+ candidates.add(path.join(moduleDir, '.env'));
24
+ const parentDir = path.dirname(moduleDir);
25
+ if (parentDir === moduleDir) {
26
+ break;
27
+ }
28
+ moduleDir = parentDir;
29
+ }
30
+ for (const candidate of candidates) {
31
+ if (fs.existsSync(candidate)) {
32
+ loadEnvFile({ path: candidate, override: false });
33
+ return;
34
+ }
35
+ }
36
+ }