@warmio/mcp 10.0.0 → 11.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 +8 -318
- package/dist/config-paths.d.ts +2 -2
- package/dist/config-paths.d.ts.map +1 -0
- package/dist/config-paths.js +12 -16
- package/dist/config-paths.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -206
- package/dist/index.js.map +1 -0
- package/dist/install.d.ts +17 -3
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +262 -492
- package/dist/install.js.map +1 -0
- package/dist/load-env.d.ts +1 -0
- package/dist/load-env.d.ts.map +1 -0
- package/dist/load-env.js +6 -23
- package/dist/load-env.js.map +1 -0
- package/dist/server.d.ts +10 -8
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +76 -105
- package/dist/server.js.map +1 -0
- package/dist/warm-api-client.d.ts +17 -30
- package/dist/warm-api-client.d.ts.map +1 -0
- package/dist/warm-api-client.js +118 -180
- package/dist/warm-api-client.js.map +1 -0
- package/package.json +9 -16
- package/dist/http.d.ts +0 -14
- package/dist/http.js +0 -154
- package/dist/schemas.d.ts +0 -2
- package/dist/schemas.js +0 -2
- package/dist/stdio.d.ts +0 -29
- package/dist/stdio.js +0 -200
- package/dist/types.d.ts +0 -8
- package/dist/types.js +0 -8
- package/dist/warm-server.d.ts +0 -2
- package/dist/warm-server.js +0 -2
package/dist/warm-api-client.js
CHANGED
|
@@ -1,206 +1,144 @@
|
|
|
1
|
+
import { API_ORIGIN, apiEndpointManifest } from '@warmio/contracts/api';
|
|
2
|
+
import { WARM_MCP_CREDENTIALS, WARM_MCP_INSTALLER_COMMAND } from '@warmio/contracts/mcp';
|
|
1
3
|
import { getWarmApiKeyPath, readConfigFile } from './config-paths.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const endpoint = endpointByOperationId[operationId];
|
|
13
|
-
if (endpoint.audience !== audience) {
|
|
14
|
-
throw new Error(`API operation ${operationId} is not available to ${audience}.`);
|
|
15
|
-
}
|
|
16
|
-
return endpoint.path;
|
|
17
|
-
}
|
|
18
|
-
function getRequestSignal(timeoutMs) {
|
|
19
|
-
if (typeof AbortSignal.timeout === 'function') {
|
|
20
|
-
return AbortSignal.timeout(timeoutMs);
|
|
21
|
-
}
|
|
22
|
-
const controller = new AbortController();
|
|
23
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
24
|
-
timer.unref?.();
|
|
25
|
-
return controller.signal;
|
|
26
|
-
}
|
|
27
|
-
function createWarmApiClientConfig(options) {
|
|
28
|
-
return {
|
|
29
|
-
audience: options.audience || 'context',
|
|
30
|
-
apiKeyResolver: options.apiKeyResolver,
|
|
31
|
-
apiUrl: options.apiUrl || DEFAULT_API_URL,
|
|
32
|
-
fetchImplementation: options.fetchImplementation || fetch,
|
|
33
|
-
requestTimeoutMs: options.requestTimeoutMs || DEFAULT_REQUEST_TIMEOUT_MS,
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
export function getConfiguredApiKey(audience = 'context') {
|
|
37
|
-
if (cachedApiKeys.has(audience)) {
|
|
38
|
-
return cachedApiKeys.get(audience) ?? null;
|
|
39
|
-
}
|
|
40
|
-
const environmentKey = WARM_MCP_CREDENTIALS[audience].apiKeyEnv;
|
|
41
|
-
const apiKey = process.env[environmentKey]?.trim() || readConfigFile(getWarmApiKeyPath(audience));
|
|
42
|
-
cachedApiKeys.set(audience, apiKey);
|
|
43
|
-
return apiKey;
|
|
4
|
+
export const API_URL = process.env.WARM_API_URL || API_ORIGIN;
|
|
5
|
+
const DEFAULT_TIMEOUT_MS = Number(process.env.WARM_API_TIMEOUT_MS || 10_000) || 10_000;
|
|
6
|
+
const endpointByOperationId = Object.fromEntries(apiEndpointManifest.map((endpoint) => [endpoint.operation_id, endpoint.path]));
|
|
7
|
+
let cachedApiKey;
|
|
8
|
+
export function getConfiguredApiKey() {
|
|
9
|
+
if (cachedApiKey !== undefined)
|
|
10
|
+
return cachedApiKey;
|
|
11
|
+
cachedApiKey =
|
|
12
|
+
process.env[WARM_MCP_CREDENTIALS.apiKeyEnv]?.trim() || readConfigFile(getWarmApiKeyPath());
|
|
13
|
+
return cachedApiKey || null;
|
|
44
14
|
}
|
|
45
15
|
export function resetConfiguredApiKeyCache() {
|
|
46
|
-
|
|
16
|
+
cachedApiKey = undefined;
|
|
47
17
|
}
|
|
48
18
|
export class WarmApiError extends Error {
|
|
49
|
-
|
|
50
|
-
headers;
|
|
51
|
-
status;
|
|
19
|
+
response;
|
|
52
20
|
constructor(message, response) {
|
|
53
21
|
super(message);
|
|
22
|
+
this.response = response;
|
|
54
23
|
this.name = 'WarmApiError';
|
|
55
|
-
this.body = response.body;
|
|
56
|
-
this.headers = response.headers;
|
|
57
|
-
this.status = response.status;
|
|
58
24
|
}
|
|
25
|
+
get body() {
|
|
26
|
+
return this.response.body;
|
|
27
|
+
}
|
|
28
|
+
get headers() {
|
|
29
|
+
return this.response.headers;
|
|
30
|
+
}
|
|
31
|
+
get status() {
|
|
32
|
+
return this.response.status;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function requestSignal(timeoutMs) {
|
|
36
|
+
return typeof AbortSignal.timeout === 'function'
|
|
37
|
+
? AbortSignal.timeout(timeoutMs)
|
|
38
|
+
: new AbortController().signal;
|
|
59
39
|
}
|
|
60
|
-
function
|
|
61
|
-
const
|
|
62
|
-
401: 'Invalid
|
|
63
|
-
403: 'API access is available on paid plans only.
|
|
40
|
+
function errorFromResponse(response) {
|
|
41
|
+
const knownMessages = {
|
|
42
|
+
401: 'Invalid API key. Regenerate it in Warm Settings.',
|
|
43
|
+
403: 'API access is available on paid plans only.',
|
|
64
44
|
429: 'Rate limit exceeded. Try again in a few minutes.',
|
|
65
45
|
};
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
?
|
|
71
|
-
:
|
|
72
|
-
return new WarmApiError(
|
|
46
|
+
const bodyError = typeof response.body === 'object' &&
|
|
47
|
+
response.body !== null &&
|
|
48
|
+
'error' in response.body &&
|
|
49
|
+
typeof response.body.error === 'string'
|
|
50
|
+
? response.body.error
|
|
51
|
+
: undefined;
|
|
52
|
+
return new WarmApiError(knownMessages[response.status] || bodyError || `Request failed with status ${response.status}`, response);
|
|
73
53
|
}
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
response = await requestOptions.fetchImplementation(url.toString(), {
|
|
89
|
-
...(request.method && request.method !== 'GET' ? { method: request.method } : {}),
|
|
54
|
+
function createRequest(authorizationResolver, options) {
|
|
55
|
+
const apiUrl = options.apiUrl || API_URL;
|
|
56
|
+
const fetchImplementation = options.fetchImplementation || fetch;
|
|
57
|
+
const requestTimeoutMs = options.requestTimeoutMs || DEFAULT_TIMEOUT_MS;
|
|
58
|
+
return async (input) => {
|
|
59
|
+
const token = authorizationResolver();
|
|
60
|
+
if (!token)
|
|
61
|
+
throw new Error(`No Warm API key configured. Run \"${WARM_MCP_INSTALLER_COMMAND}\" or set ${WARM_MCP_CREDENTIALS.apiKeyEnv}.`);
|
|
62
|
+
const url = new URL(input.endpoint, apiUrl);
|
|
63
|
+
for (const [key, value] of Object.entries(input.query || {}))
|
|
64
|
+
if (value)
|
|
65
|
+
url.searchParams.set(key, value);
|
|
66
|
+
const response = await fetchImplementation(url, {
|
|
67
|
+
...(input.method ? { method: input.method } : {}),
|
|
90
68
|
headers: {
|
|
91
69
|
Accept: 'application/json',
|
|
92
|
-
Authorization: `Bearer ${
|
|
93
|
-
...(
|
|
70
|
+
Authorization: `Bearer ${token}`,
|
|
71
|
+
...(input.body ? { 'Content-Type': 'application/json' } : {}),
|
|
94
72
|
},
|
|
95
|
-
...(
|
|
96
|
-
signal:
|
|
73
|
+
...(input.body ? { body: JSON.stringify(input.body) } : {}),
|
|
74
|
+
signal: requestSignal(requestTimeoutMs),
|
|
97
75
|
});
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
const text = await response.text();
|
|
109
|
-
let body = null;
|
|
110
|
-
if (text) {
|
|
111
|
-
try {
|
|
112
|
-
body = JSON.parse(text);
|
|
113
|
-
}
|
|
114
|
-
catch {
|
|
115
|
-
body = text;
|
|
76
|
+
const text = await response.text();
|
|
77
|
+
let body = null;
|
|
78
|
+
if (text) {
|
|
79
|
+
try {
|
|
80
|
+
body = JSON.parse(text);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
body = text;
|
|
84
|
+
}
|
|
116
85
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
86
|
+
const apiResponse = {
|
|
87
|
+
body,
|
|
88
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
89
|
+
status: response.status,
|
|
90
|
+
};
|
|
91
|
+
if (!response.ok)
|
|
92
|
+
throw errorFromResponse(apiResponse);
|
|
93
|
+
return body;
|
|
122
94
|
};
|
|
123
95
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (typeof rawInput.month === 'string' && rawInput.latest !== undefined) {
|
|
137
|
-
throw new Error('`month` and `latest` are mutually exclusive.');
|
|
138
|
-
}
|
|
139
|
-
if (typeof rawInput.month === 'string') {
|
|
140
|
-
return await requestSuccessfulJson({
|
|
141
|
-
endpoint: getEndpointPath('getFinancialContextTransactions', 'context'),
|
|
142
|
-
query: { month: rawInput.month },
|
|
143
|
-
}, options);
|
|
144
|
-
}
|
|
145
|
-
if (rawInput.latest === true) {
|
|
146
|
-
return await requestSuccessfulJson({
|
|
147
|
-
endpoint: getEndpointPath('getFinancialContextTransactions', 'context'),
|
|
96
|
+
function createReadOnlyClient(request) {
|
|
97
|
+
return {
|
|
98
|
+
getFinancialContext: () => request({ endpoint: endpointByOperationId.getFinancialContext }),
|
|
99
|
+
getFinancialContextMeta: () => request({ endpoint: endpointByOperationId.getFinancialContextMeta }),
|
|
100
|
+
getTransactions: (input) => {
|
|
101
|
+
if ('month' in input)
|
|
102
|
+
return request({
|
|
103
|
+
endpoint: endpointByOperationId.getFinancialContextTransactions,
|
|
104
|
+
query: { month: input.month },
|
|
105
|
+
});
|
|
106
|
+
return request({
|
|
107
|
+
endpoint: endpointByOperationId.getFinancialContextTransactions,
|
|
148
108
|
query: { latest: '1' },
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
throw new Error('Call getTransactions with `month` in YYYY-MM format or `latest: true`.');
|
|
152
|
-
};
|
|
153
|
-
const verifyKey = async () => {
|
|
154
|
-
const response = await requestSuccessfulJson({ endpoint: getEndpointPath('verifyApiAccess', 'context') }, options);
|
|
155
|
-
return {
|
|
156
|
-
audience: response.audience,
|
|
157
|
-
status: response.status || (response.valid ? 'ok' : 'invalid'),
|
|
158
|
-
valid: response.valid === true,
|
|
159
|
-
};
|
|
160
|
-
};
|
|
161
|
-
const validateAudience = async () => {
|
|
162
|
-
const result = await verifyKey();
|
|
163
|
-
const expectedAudience = createWarmApiClientConfig(options).audience;
|
|
164
|
-
const hasCompatibleAudience = result.audience === expectedAudience || result.audience === 'oauth';
|
|
165
|
-
if (!result.valid || !hasCompatibleAudience) {
|
|
166
|
-
const article = expectedAudience === 'automation' ? 'an' : 'a';
|
|
167
|
-
throw new Error(`This MCP mode requires ${article} ${expectedAudience} API key. Run "${WARM_MCP_INSTALLER_COMMANDS[expectedAudience]}" to configure a separate key.`);
|
|
168
|
-
}
|
|
169
|
-
return result;
|
|
170
|
-
};
|
|
171
|
-
const searchOperations = async (query) => await requestSuccessfulJson({
|
|
172
|
-
body: query ? { query } : {},
|
|
173
|
-
endpoint: getEndpointPath('searchOperations', 'automation'),
|
|
174
|
-
method: 'POST',
|
|
175
|
-
}, options);
|
|
176
|
-
const describeOperation = async (operationId, input) => await requestSuccessfulJson({
|
|
177
|
-
body: { operation_id: operationId, ...(input ? { input } : {}) },
|
|
178
|
-
endpoint: getEndpointPath('describeOperation', 'automation'),
|
|
179
|
-
method: 'POST',
|
|
180
|
-
}, options);
|
|
181
|
-
const invokeOperation = async (operationId, input, approvalId) => await requestSuccessfulJson({
|
|
182
|
-
body: {
|
|
183
|
-
operation_id: operationId,
|
|
184
|
-
input: input ?? {},
|
|
185
|
-
...(approvalId ? { approval_id: approvalId } : {}),
|
|
109
|
+
});
|
|
186
110
|
},
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function createClient(authorizationResolver, options) {
|
|
114
|
+
const request = createRequest(authorizationResolver, options);
|
|
190
115
|
return {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
116
|
+
...createReadOnlyClient(request),
|
|
117
|
+
searchOperations: (query) => request({
|
|
118
|
+
endpoint: endpointByOperationId.searchOperations,
|
|
119
|
+
method: 'POST',
|
|
120
|
+
body: query ? { query } : {},
|
|
121
|
+
}),
|
|
122
|
+
describeOperation: (operation_id, input) => request({
|
|
123
|
+
endpoint: endpointByOperationId.describeOperation,
|
|
124
|
+
method: 'POST',
|
|
125
|
+
body: { operation_id, ...(input ? { input } : {}) },
|
|
126
|
+
}),
|
|
127
|
+
invokeOperation: (operation_id, input, approval_id) => request({
|
|
128
|
+
endpoint: endpointByOperationId.invokeOperation,
|
|
129
|
+
method: 'POST',
|
|
130
|
+
body: {
|
|
131
|
+
operation_id,
|
|
132
|
+
input: input ?? {},
|
|
133
|
+
...(approval_id ? { approval_id } : {}),
|
|
134
|
+
},
|
|
135
|
+
}),
|
|
199
136
|
};
|
|
200
137
|
}
|
|
201
|
-
export
|
|
202
|
-
return
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
138
|
+
export function createWarmApiClient(options = {}) {
|
|
139
|
+
return createClient(options.apiKeyResolver || getConfiguredApiKey, options);
|
|
140
|
+
}
|
|
141
|
+
export function createReadOnlyWarmApiClient(options) {
|
|
142
|
+
return createReadOnlyClient(createRequest(options.accessTokenResolver, options));
|
|
206
143
|
}
|
|
144
|
+
//# sourceMappingURL=warm-api-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"warm-api-client.js","sourceRoot":"","sources":["../src/warm-api-client.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@warmio/mcp",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"description": "Warm CLI and compact context or automation MCP server",
|
|
3
|
+
"version": "11.0.0",
|
|
4
|
+
"description": "Warm local stdio MCP server",
|
|
6
5
|
"type": "module",
|
|
7
6
|
"engines": {
|
|
8
7
|
"node": ">=18"
|
|
@@ -37,36 +36,30 @@
|
|
|
37
36
|
"dist"
|
|
38
37
|
],
|
|
39
38
|
"scripts": {
|
|
40
|
-
"build": "
|
|
41
|
-
"test": "
|
|
42
|
-
"
|
|
43
|
-
"start:http": "node dist/index.js http",
|
|
44
|
-
"start:install": "node dist/index.js install",
|
|
45
|
-
"prepublishOnly": "npm run build",
|
|
46
|
-
"start:mcp": "node dist/index.js mcp"
|
|
39
|
+
"build": "tsc -p tsconfig.json",
|
|
40
|
+
"test": "pnpm build && node --test test/*.mjs",
|
|
41
|
+
"pack:check": "pnpm pack --dry-run"
|
|
47
42
|
},
|
|
48
43
|
"keywords": [
|
|
49
44
|
"mcp",
|
|
50
45
|
"warm",
|
|
51
46
|
"warmio",
|
|
52
47
|
"financial",
|
|
53
|
-
"api"
|
|
54
|
-
"claude",
|
|
55
|
-
"chatgpt"
|
|
48
|
+
"api"
|
|
56
49
|
],
|
|
57
50
|
"license": "MIT",
|
|
58
51
|
"repository": {
|
|
59
52
|
"type": "git",
|
|
60
|
-
"url": "
|
|
53
|
+
"url": "https://github.com/jasonstockman/warm-mcp"
|
|
61
54
|
},
|
|
62
55
|
"dependencies": {
|
|
63
56
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
64
|
-
"@warmio/contracts": "
|
|
57
|
+
"@warmio/contracts": "3.0.0",
|
|
65
58
|
"dotenv": "^17.2.3",
|
|
66
59
|
"zod": "^3.25.76"
|
|
67
60
|
},
|
|
68
61
|
"devDependencies": {
|
|
69
62
|
"@types/node": "^24.10.9",
|
|
70
|
-
"typescript": "
|
|
63
|
+
"typescript": "7.0.2"
|
|
71
64
|
}
|
|
72
65
|
}
|
package/dist/http.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { type WarmServerMode } from './server.js';
|
|
2
|
-
export interface HttpServerOptions {
|
|
3
|
-
allowedHosts?: string[];
|
|
4
|
-
authToken?: string;
|
|
5
|
-
host?: string;
|
|
6
|
-
mode?: WarmServerMode;
|
|
7
|
-
path?: string;
|
|
8
|
-
port?: number;
|
|
9
|
-
}
|
|
10
|
-
export declare function isLoopbackHost(host: string): boolean;
|
|
11
|
-
export declare function validateHttpSecurity(options: Pick<HttpServerOptions, 'authToken' | 'host' | 'mode'>): void;
|
|
12
|
-
export declare function hasValidTransportBearerToken(authorization: string | undefined, authToken: string): boolean;
|
|
13
|
-
export declare function resolveHttpServerOptions(overrides?: HttpServerOptions): Required<HttpServerOptions>;
|
|
14
|
-
export declare function startHttpServer(options?: HttpServerOptions): Promise<any>;
|
package/dist/http.js
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js';
|
|
2
|
-
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
3
|
-
import { timingSafeEqual } from 'node:crypto';
|
|
4
|
-
import { createWarmServer } from './server.js';
|
|
5
|
-
const DEFAULT_HTTP_HOST = '127.0.0.1';
|
|
6
|
-
const DEFAULT_HTTP_PORT = 3000;
|
|
7
|
-
const DEFAULT_HTTP_PATH = '/mcp';
|
|
8
|
-
export function isLoopbackHost(host) {
|
|
9
|
-
const normalized = host.trim().toLowerCase().replace(/^\[|\]$/g, '');
|
|
10
|
-
return normalized === 'localhost' || normalized === '127.0.0.1' || normalized === '::1';
|
|
11
|
-
}
|
|
12
|
-
export function validateHttpSecurity(options) {
|
|
13
|
-
const mode = options.mode || 'context';
|
|
14
|
-
const hasAuthToken = Boolean(options.authToken?.trim());
|
|
15
|
-
if (mode === 'automation' && !hasAuthToken) {
|
|
16
|
-
throw new Error('Automation MCP HTTP requires WARM_MCP_AUTH_TOKEN.');
|
|
17
|
-
}
|
|
18
|
-
if (!isLoopbackHost(options.host || DEFAULT_HTTP_HOST) && !hasAuthToken) {
|
|
19
|
-
throw new Error('Non-loopback MCP HTTP requires WARM_MCP_AUTH_TOKEN.');
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
export function hasValidTransportBearerToken(authorization, authToken) {
|
|
23
|
-
const expected = `Bearer ${authToken}`;
|
|
24
|
-
if (!authorization) {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
const actualBuffer = Buffer.from(authorization);
|
|
28
|
-
const expectedBuffer = Buffer.from(expected);
|
|
29
|
-
if (actualBuffer.length !== expectedBuffer.length) {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
return timingSafeEqual(actualBuffer, expectedBuffer);
|
|
33
|
-
}
|
|
34
|
-
function parsePort(value, fallback) {
|
|
35
|
-
const parsed = Number(value);
|
|
36
|
-
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
37
|
-
}
|
|
38
|
-
function normalizePath(value) {
|
|
39
|
-
if (!value) {
|
|
40
|
-
return DEFAULT_HTTP_PATH;
|
|
41
|
-
}
|
|
42
|
-
return value.startsWith('/') ? value : `/${value}`;
|
|
43
|
-
}
|
|
44
|
-
function parseAllowedHosts(value) {
|
|
45
|
-
if (!value) {
|
|
46
|
-
return [];
|
|
47
|
-
}
|
|
48
|
-
return value
|
|
49
|
-
.split(',')
|
|
50
|
-
.map((host) => host.trim())
|
|
51
|
-
.filter(Boolean);
|
|
52
|
-
}
|
|
53
|
-
function jsonRpcError(message) {
|
|
54
|
-
return {
|
|
55
|
-
error: {
|
|
56
|
-
code: -32000,
|
|
57
|
-
message,
|
|
58
|
-
},
|
|
59
|
-
id: null,
|
|
60
|
-
jsonrpc: '2.0',
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
export function resolveHttpServerOptions(overrides = {}) {
|
|
64
|
-
return {
|
|
65
|
-
allowedHosts: overrides.allowedHosts ??
|
|
66
|
-
parseAllowedHosts(process.env.WARM_MCP_ALLOWED_HOSTS || process.env.MCP_ALLOWED_HOSTS),
|
|
67
|
-
authToken: overrides.authToken ?? process.env.WARM_MCP_AUTH_TOKEN?.trim() ?? '',
|
|
68
|
-
host: overrides.host ||
|
|
69
|
-
process.env.WARM_MCP_HTTP_HOST ||
|
|
70
|
-
process.env.MCP_HOST ||
|
|
71
|
-
process.env.HOST ||
|
|
72
|
-
DEFAULT_HTTP_HOST,
|
|
73
|
-
mode: overrides.mode ?? 'context',
|
|
74
|
-
path: normalizePath(overrides.path || process.env.WARM_MCP_HTTP_PATH || process.env.MCP_PATH || DEFAULT_HTTP_PATH),
|
|
75
|
-
port: overrides.port ??
|
|
76
|
-
parsePort(process.env.WARM_MCP_HTTP_PORT || process.env.MCP_PORT || process.env.PORT, DEFAULT_HTTP_PORT),
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
function sendJsonMethodNotAllowed(res) {
|
|
80
|
-
res.status(405);
|
|
81
|
-
res.set('Allow', 'POST');
|
|
82
|
-
res.json(jsonRpcError('Method not allowed.'));
|
|
83
|
-
}
|
|
84
|
-
export async function startHttpServer(options = {}) {
|
|
85
|
-
const resolved = resolveHttpServerOptions(options);
|
|
86
|
-
validateHttpSecurity(resolved);
|
|
87
|
-
const app = createMcpExpressApp({
|
|
88
|
-
...(resolved.allowedHosts.length > 0 ? { allowedHosts: resolved.allowedHosts } : {}),
|
|
89
|
-
host: resolved.host,
|
|
90
|
-
});
|
|
91
|
-
app.post(resolved.path, async (req, res) => {
|
|
92
|
-
if (resolved.authToken &&
|
|
93
|
-
!hasValidTransportBearerToken(req.get('authorization'), resolved.authToken)) {
|
|
94
|
-
res.status(401).json(jsonRpcError('Unauthorized MCP transport request.'));
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
const server = createWarmServer({ mode: resolved.mode });
|
|
98
|
-
const transport = new StreamableHTTPServerTransport({
|
|
99
|
-
sessionIdGenerator: undefined,
|
|
100
|
-
});
|
|
101
|
-
let cleanedUp = false;
|
|
102
|
-
const cleanup = async () => {
|
|
103
|
-
if (cleanedUp) {
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
cleanedUp = true;
|
|
107
|
-
await Promise.allSettled([transport.close(), server.close()]);
|
|
108
|
-
};
|
|
109
|
-
res.on('close', () => {
|
|
110
|
-
void cleanup();
|
|
111
|
-
});
|
|
112
|
-
try {
|
|
113
|
-
await server.connect(transport);
|
|
114
|
-
await transport.handleRequest(req, res, req.body);
|
|
115
|
-
}
|
|
116
|
-
catch (error) {
|
|
117
|
-
console.error('Error handling Warm MCP HTTP request:', error);
|
|
118
|
-
if (!res.headersSent) {
|
|
119
|
-
res.status(500).json(jsonRpcError('Internal server error'));
|
|
120
|
-
}
|
|
121
|
-
void cleanup();
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
app.get(resolved.path, (_req, res) => {
|
|
125
|
-
sendJsonMethodNotAllowed(res);
|
|
126
|
-
});
|
|
127
|
-
app.delete(resolved.path, (_req, res) => {
|
|
128
|
-
sendJsonMethodNotAllowed(res);
|
|
129
|
-
});
|
|
130
|
-
const listener = await new Promise((resolve, reject) => {
|
|
131
|
-
const httpServer = app.listen(resolved.port, resolved.host, () => {
|
|
132
|
-
resolve(httpServer);
|
|
133
|
-
});
|
|
134
|
-
httpServer.once('error', reject);
|
|
135
|
-
});
|
|
136
|
-
let shuttingDown = false;
|
|
137
|
-
const shutdown = (signal) => {
|
|
138
|
-
if (shuttingDown) {
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
shuttingDown = true;
|
|
142
|
-
console.log(`Received ${signal}, shutting down Warm MCP HTTP server...`);
|
|
143
|
-
listener.close((error) => {
|
|
144
|
-
if (error) {
|
|
145
|
-
console.error('Failed to close Warm MCP HTTP server:', error);
|
|
146
|
-
process.exitCode = 1;
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
};
|
|
150
|
-
process.once('SIGINT', () => shutdown('SIGINT'));
|
|
151
|
-
process.once('SIGTERM', () => shutdown('SIGTERM'));
|
|
152
|
-
console.log(`Warm MCP Streamable HTTP server listening on http://${resolved.host}:${resolved.port}${resolved.path}`);
|
|
153
|
-
return listener;
|
|
154
|
-
}
|
package/dist/schemas.d.ts
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
export { automationInputSchema, financialContextAccountSchema as accountSchema, financialContextMetaSchema, financialContextSchema, financialContextTransactionSchema as transactionSchema, latestTransactionsSchema, transactionMonthSchema, } from '@warmio/contracts/schemas';
|
|
2
|
-
export { describeOperationMcpInputShape as describeOperationInputSchema, emptyMcpInputShape as emptyInputSchema, getTransactionsMcpInputShape as getTransactionsInputSchema, invokeOperationMcpInputShape as invokeOperationInputSchema, searchOperationsMcpInputShape as searchOperationsInputSchema, } from '@warmio/contracts/mcp';
|
package/dist/schemas.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
export { automationInputSchema, financialContextAccountSchema as accountSchema, financialContextMetaSchema, financialContextSchema, financialContextTransactionSchema as transactionSchema, latestTransactionsSchema, transactionMonthSchema, } from '@warmio/contracts/schemas';
|
|
2
|
-
export { describeOperationMcpInputShape as describeOperationInputSchema, emptyMcpInputShape as emptyInputSchema, getTransactionsMcpInputShape as getTransactionsInputSchema, invokeOperationMcpInputShape as invokeOperationInputSchema, searchOperationsMcpInputShape as searchOperationsInputSchema, } from '@warmio/contracts/mcp';
|
package/dist/stdio.d.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { Readable, Writable } from 'node:stream';
|
|
2
|
-
import type { Transport, TransportSendOptions } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
3
|
-
import type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
-
export declare class AdaptiveStdioTransport implements Transport {
|
|
5
|
-
private readonly stdin;
|
|
6
|
-
private readonly stdout;
|
|
7
|
-
onclose?: () => void;
|
|
8
|
-
onerror?: (error: Error) => void;
|
|
9
|
-
onmessage?: <T extends JSONRPCMessage>(message: T) => void;
|
|
10
|
-
private buffer;
|
|
11
|
-
private clientFraming;
|
|
12
|
-
private closed;
|
|
13
|
-
private started;
|
|
14
|
-
constructor(stdin?: Readable, stdout?: Writable);
|
|
15
|
-
start(): Promise<void>;
|
|
16
|
-
close(): Promise<void>;
|
|
17
|
-
send(message: JSONRPCMessage, _options?: TransportSendOptions): Promise<void>;
|
|
18
|
-
private readonly handleData;
|
|
19
|
-
private readonly handleError;
|
|
20
|
-
private readonly handleEnd;
|
|
21
|
-
private processBuffer;
|
|
22
|
-
private trimLeadingNewlines;
|
|
23
|
-
private readMessage;
|
|
24
|
-
private detectFraming;
|
|
25
|
-
private readJsonLineMessage;
|
|
26
|
-
private readContentLengthMessage;
|
|
27
|
-
private findHeaderBoundary;
|
|
28
|
-
private parseContentLength;
|
|
29
|
-
}
|