@warmio/mcp 11.0.7 → 11.0.10
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 +94 -11
- package/dist/hosted-http.d.ts +9 -0
- package/dist/hosted-http.d.ts.map +1 -0
- package/dist/hosted-http.js +238 -0
- package/dist/hosted-http.js.map +1 -0
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +109 -23
- package/dist/install.js.map +1 -1
- package/dist/load-env.d.ts +1 -1
- package/dist/load-env.d.ts.map +1 -1
- package/dist/load-env.js +3 -2
- package/dist/load-env.js.map +1 -1
- package/dist/public-server.d.ts +20 -0
- package/dist/public-server.d.ts.map +1 -0
- package/dist/public-server.js +112 -0
- package/dist/public-server.js.map +1 -0
- package/dist/server.d.ts +41 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +31 -31
- package/dist/server.js.map +1 -1
- package/dist/warm-api-client.d.ts +6 -2
- package/dist/warm-api-client.d.ts.map +1 -1
- package/dist/warm-api-client.js +25 -5
- package/dist/warm-api-client.js.map +1 -1
- package/package.json +13 -8
package/README.md
CHANGED
|
@@ -1,19 +1,100 @@
|
|
|
1
1
|
# @warmio/mcp
|
|
2
2
|
|
|
3
|
-
`@warmio/mcp` is Warm's local stdio MCP server. Run the interactive installer to validate and store one full-access `WARM_API_KEY`, then configure detected supported clients:
|
|
3
|
+
`@warmio/mcp` is Warm's local stdio MCP server. It also documents the hosted HTTP endpoint at `https://app.warm.io/mcp`. Run the interactive installer to validate and store one full-access `WARM_API_KEY`, then configure detected supported clients:
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
npx -y @warmio/mcp@
|
|
6
|
+
npx -y @warmio/mcp@latest install
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Each client starts the server with the
|
|
9
|
+
Each client starts the server with the canonical latest launcher spec from `@warmio/contracts/mcp`:
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
|
-
npx -y @warmio/mcp@
|
|
12
|
+
npx -y @warmio/mcp@latest mcp
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
The server exposes financial-context read tools plus the automation operation catalog. The installer stores the local credential with owner-only file permissions. Importing `@warmio/mcp` exposes the library API without starting the CLI.
|
|
16
16
|
|
|
17
|
+
## Hosted HTTP
|
|
18
|
+
|
|
19
|
+
Claude Code, Cursor, and VS Code can use the hosted endpoint instead of the local process. The URL is `https://app.warm.io/mcp`. Send the existing Warm API key in the `Authorization` header as `Bearer` plus the key. Do not put the key in the URL.
|
|
20
|
+
|
|
21
|
+
The hosted server lists only `get_financial_context` and `get_transactions`. The local installer and its five tools stay the same. Claude Desktop, claude.ai, and ChatGPT are not supported on this endpoint. A missing, invalid, or revoked key returns HTTP 401 with `WWW-Authenticate: Bearer realm="Warm MCP"`.
|
|
22
|
+
|
|
23
|
+
Claude Code:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mcpServers": {
|
|
28
|
+
"warm": {
|
|
29
|
+
"type": "http",
|
|
30
|
+
"url": "https://app.warm.io/mcp",
|
|
31
|
+
"headers": {
|
|
32
|
+
"Authorization": "Bearer ${WARM_API_KEY}"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Cursor:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"mcpServers": {
|
|
44
|
+
"warm": {
|
|
45
|
+
"url": "https://app.warm.io/mcp",
|
|
46
|
+
"headers": {
|
|
47
|
+
"Authorization": "Bearer ${env:WARM_API_KEY}"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
VS Code (`.vscode/mcp.json`):
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"servers": {
|
|
59
|
+
"warm": {
|
|
60
|
+
"type": "http",
|
|
61
|
+
"url": "https://app.warm.io/mcp",
|
|
62
|
+
"headers": {
|
|
63
|
+
"Authorization": "Bearer ${input:warm-api-key}"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"inputs": [
|
|
68
|
+
{
|
|
69
|
+
"id": "warm-api-key",
|
|
70
|
+
"type": "promptString",
|
|
71
|
+
"description": "Warm API key",
|
|
72
|
+
"password": true
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## OpenCode 2
|
|
79
|
+
|
|
80
|
+
The installer configures OpenCode 2 using `mcp.servers.warm`. It uses an existing
|
|
81
|
+
`~/.config/opencode/opencode.jsonc` file when present, otherwise `opencode.json`.
|
|
82
|
+
Comments and unrelated settings remain in place.
|
|
83
|
+
|
|
84
|
+
When reinstalling, the installer moves existing OpenCode 1 MCP server entries under
|
|
85
|
+
`mcp.servers`. It converts `enabled`, numeric timeouts, and OAuth field names to their
|
|
86
|
+
OpenCode 2 equivalents. Conflicting old and new entries for another server stop the
|
|
87
|
+
installation without writing files. The installer replaces Warm's own entry after
|
|
88
|
+
the existing reinstall confirmation, or when `--force` is supplied.
|
|
89
|
+
|
|
90
|
+
The installer migrates MCP settings only. Migrate the rest of an OpenCode 1 configuration
|
|
91
|
+
using OpenCode's [migration guide](https://opencode.ai/v2/docs/migrate-v1/) before using
|
|
92
|
+
OpenCode 2. New installer output targets OpenCode 2 rather than OpenCode 1.
|
|
93
|
+
|
|
94
|
+
No plugin is required. OpenCode 2 discovers Warm's tools through MCP and enables Code
|
|
95
|
+
Mode by default. Warm still requires Plus or Pro and staying within the plan's
|
|
96
|
+
bank-login limit.
|
|
97
|
+
|
|
17
98
|
## Environment precedence
|
|
18
99
|
|
|
19
100
|
Credential and config resolution is fixed and never loaded from ancestor or project `.env` files by default:
|
|
@@ -28,22 +109,24 @@ Optional local development: set `WARM_LOAD_PROJECT_ENV=1` before starting the CL
|
|
|
28
109
|
|
|
29
110
|
## Launcher integrity contract
|
|
30
111
|
|
|
31
|
-
|
|
112
|
+
Setup commands and generated launcher configs use the approved `@warmio/mcp@latest` tag. The manifest separately retains the exact release `packageSpec` and `packageVersion` for provenance. Its `packageIntegrity` contract applies to that exact release. The `.github/workflows/publish-mcp.yml` workflow requires an exact merged source SHA and npm registry readback for that version. A registry integrity value does not attest which release the floating `latest` tag resolves to. Until that gate passes, treat `registryIntegrity: null` as an external deployment requirement rather than a local hash to copy by hand.
|
|
32
113
|
|
|
33
114
|
Set `WARM_MCP_REQUIRE_REGISTRY_INTEGRITY=1` to require an exact registry readback. When the manifest still has `registryIntegrity: null`, set `WARM_MCP_REGISTRY_INTEGRITY` to the trusted workflow's `dist.integrity` value. The gate rejects missing or malformed values. Default installs and launcher generation remain allowed until this opt-in gate is enabled.
|
|
34
115
|
|
|
35
116
|
## Claude Desktop Extension
|
|
36
117
|
|
|
37
|
-
For Claude Desktop users,
|
|
118
|
+
For Claude Desktop users, the fixed-tree extension is produced only by the guarded, context-aware flow:
|
|
38
119
|
|
|
39
120
|
```sh
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
121
|
+
/absolute/worktree/tools/tasks/run-plan-gate.mjs \
|
|
122
|
+
--target-id P0_MCPB_FIXED_TREE \
|
|
123
|
+
--argv-json '[\"/usr/bin/node\",\"/absolute/worktree/libraries/mcp/mcpb/build.js\",\"--contracts-tar\",\"/absolute/worktree/libraries/mcp/mcpb/.inputs/contracts.tgz\",\"--mcp-tar\",\"/absolute/worktree/libraries/mcp/mcpb/.inputs/mcp.tgz\"]' \
|
|
124
|
+
--context /absolute/context.json
|
|
43
125
|
```
|
|
44
126
|
|
|
45
|
-
|
|
46
|
-
|
|
127
|
+
The flow consumes only the exact local closure tarballs, materializes and packs offline, then
|
|
128
|
+
securely extracts and smokes the unpacked MCPB. It produces `warm.mcpb`; registry and network
|
|
129
|
+
fallbacks are forbidden.
|
|
47
130
|
|
|
48
131
|
## Cursor Plugin
|
|
49
132
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const HOSTED_MCP_MAX_REQUEST_BYTES = 65536;
|
|
2
|
+
type HostedDispatch = (request: Request) => Promise<Response>;
|
|
3
|
+
export interface HostedReadOnlyMcpDependencies {
|
|
4
|
+
concurrencyKey: string;
|
|
5
|
+
dispatchApi: HostedDispatch;
|
|
6
|
+
}
|
|
7
|
+
export declare function handleHostedReadOnlyMcpRequest(request: Request, dependencies: HostedReadOnlyMcpDependencies): Promise<Response>;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=hosted-http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hosted-http.d.ts","sourceRoot":"","sources":["../src/hosted-http.ts"],"names":[],"mappings":"AAuBA,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAWnD,KAAK,cAAc,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAE9D,MAAM,WAAW,6BAA6B;IAC5C,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,cAAc,CAAC;CAC7B;AAkMD,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,6BAA6B,GAC1C,OAAO,CAAC,QAAQ,CAAC,CA2CnB"}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/sdk/validation/cfworker';
|
|
2
|
+
import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
3
|
+
import { apiEndpointManifest } from '@warmio/contracts/api';
|
|
4
|
+
import { transactionPeriodsMcpOutputSchema, WARM_MCP_PACKAGE_VERSION, } from '@warmio/contracts/mcp';
|
|
5
|
+
import { financialContextSchema, latestTransactionsSchema, transactionMonthSchema, } from '@warmio/contracts/schemas';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { createPublicWarmServer, HostedApiResultError, HostedMcpLimitError, } from './public-server.js';
|
|
8
|
+
const HOSTED_MCP_MAX_IN_FLIGHT = 2;
|
|
9
|
+
export const HOSTED_MCP_MAX_REQUEST_BYTES = 65_536;
|
|
10
|
+
const HOSTED_MCP_MAX_RESPONSE_BYTES = 16_777_216;
|
|
11
|
+
const HOSTED_MCP_LIMIT_MESSAGE = 'The response is larger than 16777216 bytes.';
|
|
12
|
+
const HOSTED_MCP_MAX_ERROR_MESSAGE_LENGTH = 500;
|
|
13
|
+
const apiErrorSchema = z.object({ error: z.string().min(1) }).passthrough();
|
|
14
|
+
const rpcIdSchema = z
|
|
15
|
+
.object({ id: z.union([z.string(), z.number(), z.null()]).optional() })
|
|
16
|
+
.passthrough();
|
|
17
|
+
const inFlightToolCalls = new Map();
|
|
18
|
+
function authorizeHostedTool(permission) {
|
|
19
|
+
switch (permission) {
|
|
20
|
+
case 'financial_context:read':
|
|
21
|
+
case 'transactions:read':
|
|
22
|
+
return true;
|
|
23
|
+
default:
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function apiPath(operationId) {
|
|
28
|
+
const endpoint = apiEndpointManifest.find((item) => item.operation_id === operationId);
|
|
29
|
+
if (!endpoint)
|
|
30
|
+
throw new Error(`Unknown Warm API operation: ${operationId}`);
|
|
31
|
+
return endpoint.path;
|
|
32
|
+
}
|
|
33
|
+
function forwardedHeaders(request) {
|
|
34
|
+
const headers = new Headers();
|
|
35
|
+
const authorization = request.headers.get('authorization');
|
|
36
|
+
const accept = request.headers.get('accept');
|
|
37
|
+
if (authorization !== null)
|
|
38
|
+
headers.set('authorization', authorization);
|
|
39
|
+
if (accept !== null)
|
|
40
|
+
headers.set('accept', accept);
|
|
41
|
+
return headers;
|
|
42
|
+
}
|
|
43
|
+
function financialContextRequest(origin, headers) {
|
|
44
|
+
return new Request(new URL(apiPath('getFinancialContext'), origin), {
|
|
45
|
+
headers,
|
|
46
|
+
method: 'GET',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function transactionsRequest(origin, headers, input) {
|
|
50
|
+
const url = new URL(apiPath('getFinancialContextTransactions'), origin);
|
|
51
|
+
if ('month' in input)
|
|
52
|
+
url.searchParams.set('month', input.month);
|
|
53
|
+
else
|
|
54
|
+
url.searchParams.set('latest', '1');
|
|
55
|
+
return new Request(url, { headers, method: 'GET' });
|
|
56
|
+
}
|
|
57
|
+
async function readResponseText(response) {
|
|
58
|
+
const reader = response.body?.getReader();
|
|
59
|
+
if (!reader)
|
|
60
|
+
return '';
|
|
61
|
+
const chunks = [];
|
|
62
|
+
let total = 0;
|
|
63
|
+
try {
|
|
64
|
+
while (true) {
|
|
65
|
+
const next = await reader.read();
|
|
66
|
+
if (next.done)
|
|
67
|
+
break;
|
|
68
|
+
chunks.push(next.value);
|
|
69
|
+
total += next.value.byteLength;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
await reader.cancel().catch(() => undefined);
|
|
74
|
+
}
|
|
75
|
+
const bytes = new Uint8Array(total);
|
|
76
|
+
let offset = 0;
|
|
77
|
+
for (const chunk of chunks) {
|
|
78
|
+
bytes.set(chunk, offset);
|
|
79
|
+
offset += chunk.byteLength;
|
|
80
|
+
}
|
|
81
|
+
return new TextDecoder().decode(bytes);
|
|
82
|
+
}
|
|
83
|
+
async function readRpcId(request) {
|
|
84
|
+
try {
|
|
85
|
+
const parsed = rpcIdSchema.safeParse(JSON.parse(await request.text()));
|
|
86
|
+
if (!parsed.success || parsed.data.id === undefined)
|
|
87
|
+
return null;
|
|
88
|
+
return parsed.data.id;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function oversizedToolResponse(id) {
|
|
95
|
+
const message = {
|
|
96
|
+
result: {
|
|
97
|
+
content: [
|
|
98
|
+
{
|
|
99
|
+
type: 'text',
|
|
100
|
+
text: JSON.stringify({ error: HOSTED_MCP_LIMIT_MESSAGE }),
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
isError: true,
|
|
104
|
+
},
|
|
105
|
+
jsonrpc: '2.0',
|
|
106
|
+
id,
|
|
107
|
+
};
|
|
108
|
+
return new Response(JSON.stringify(message), {
|
|
109
|
+
status: 200,
|
|
110
|
+
headers: { 'Content-Type': 'application/json' },
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
async function enforceSerializedResponseCap(response, id) {
|
|
114
|
+
const reader = response.body?.getReader();
|
|
115
|
+
if (!reader)
|
|
116
|
+
return response;
|
|
117
|
+
const chunks = [];
|
|
118
|
+
let total = 0;
|
|
119
|
+
let overflow = false;
|
|
120
|
+
try {
|
|
121
|
+
while (true) {
|
|
122
|
+
const next = await reader.read();
|
|
123
|
+
if (next.done)
|
|
124
|
+
break;
|
|
125
|
+
total += next.value.byteLength;
|
|
126
|
+
if (total > HOSTED_MCP_MAX_RESPONSE_BYTES) {
|
|
127
|
+
overflow = true;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
chunks.push(next.value);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
await reader.cancel().catch(() => undefined);
|
|
135
|
+
}
|
|
136
|
+
if (overflow)
|
|
137
|
+
return oversizedToolResponse(id);
|
|
138
|
+
const body = new ArrayBuffer(total);
|
|
139
|
+
const bytes = new Uint8Array(body);
|
|
140
|
+
let offset = 0;
|
|
141
|
+
for (const chunk of chunks) {
|
|
142
|
+
bytes.set(chunk, offset);
|
|
143
|
+
offset += chunk.byteLength;
|
|
144
|
+
}
|
|
145
|
+
const headers = new Headers(response.headers);
|
|
146
|
+
headers.delete('content-length');
|
|
147
|
+
return new Response(body, {
|
|
148
|
+
status: response.status,
|
|
149
|
+
statusText: response.statusText,
|
|
150
|
+
headers,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
function parseFinancialContext(text) {
|
|
154
|
+
return financialContextSchema.parse(JSON.parse(text));
|
|
155
|
+
}
|
|
156
|
+
function parseTransactions(text) {
|
|
157
|
+
const value = JSON.parse(text);
|
|
158
|
+
const periods = transactionPeriodsMcpOutputSchema.safeParse(value);
|
|
159
|
+
if (periods.success)
|
|
160
|
+
return periods.data;
|
|
161
|
+
const month = transactionMonthSchema.safeParse(value);
|
|
162
|
+
if (month.success)
|
|
163
|
+
return month.data;
|
|
164
|
+
return latestTransactionsSchema.parse(value);
|
|
165
|
+
}
|
|
166
|
+
function parseTransactionMonth(text) {
|
|
167
|
+
return transactionMonthSchema.parse(JSON.parse(text));
|
|
168
|
+
}
|
|
169
|
+
async function readApiErrorMessage(response) {
|
|
170
|
+
if (response.status >= 500) {
|
|
171
|
+
await response.body?.cancel();
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
const parsed = apiErrorSchema.safeParse(JSON.parse(await readResponseText(response)));
|
|
176
|
+
if (!parsed.success)
|
|
177
|
+
return null;
|
|
178
|
+
return parsed.data.error.slice(0, HOSTED_MCP_MAX_ERROR_MESSAGE_LENGTH);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async function readDispatch(dependencies, request, parse) {
|
|
185
|
+
const key = dependencies.concurrencyKey;
|
|
186
|
+
const inFlight = inFlightToolCalls.get(key) ?? 0;
|
|
187
|
+
if (inFlight >= HOSTED_MCP_MAX_IN_FLIGHT) {
|
|
188
|
+
throw new HostedMcpLimitError('Too many Warm MCP calls are in progress. Retry.');
|
|
189
|
+
}
|
|
190
|
+
inFlightToolCalls.set(key, inFlight + 1);
|
|
191
|
+
try {
|
|
192
|
+
const response = await dependencies.dispatchApi(request);
|
|
193
|
+
if (!response.ok) {
|
|
194
|
+
throw new HostedApiResultError(response.status, await readApiErrorMessage(response));
|
|
195
|
+
}
|
|
196
|
+
return parse(await readResponseText(response));
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
const remaining = (inFlightToolCalls.get(key) ?? 1) - 1;
|
|
200
|
+
if (remaining > 0)
|
|
201
|
+
inFlightToolCalls.set(key, remaining);
|
|
202
|
+
else
|
|
203
|
+
inFlightToolCalls.delete(key);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
export async function handleHostedReadOnlyMcpRequest(request, dependencies) {
|
|
207
|
+
const headers = forwardedHeaders(request);
|
|
208
|
+
const origin = new URL(request.url).origin;
|
|
209
|
+
const server = createPublicWarmServer({
|
|
210
|
+
authorize: authorizeHostedTool,
|
|
211
|
+
jsonSchemaValidator: new CfWorkerJsonSchemaValidator(),
|
|
212
|
+
readClient: {
|
|
213
|
+
getFinancialContext: () => readDispatch(dependencies, financialContextRequest(origin, headers), parseFinancialContext),
|
|
214
|
+
getTransactions: async (input) => {
|
|
215
|
+
if ('periods' in input) {
|
|
216
|
+
const periods = await Promise.all(input.periods.map((month) => readDispatch(dependencies, transactionsRequest(origin, headers, { month }), parseTransactionMonth)));
|
|
217
|
+
return transactionPeriodsMcpOutputSchema.parse({ periods });
|
|
218
|
+
}
|
|
219
|
+
return readDispatch(dependencies, transactionsRequest(origin, headers, input), parseTransactions);
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
serverInfo: { name: 'warm', version: WARM_MCP_PACKAGE_VERSION },
|
|
223
|
+
});
|
|
224
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
225
|
+
enableJsonResponse: true,
|
|
226
|
+
maxRequestBodySize: HOSTED_MCP_MAX_REQUEST_BYTES,
|
|
227
|
+
});
|
|
228
|
+
const rpcId = await readRpcId(request.clone());
|
|
229
|
+
try {
|
|
230
|
+
await server.connect(transport);
|
|
231
|
+
return await enforceSerializedResponseCap(await transport.handleRequest(request), rpcId);
|
|
232
|
+
}
|
|
233
|
+
finally {
|
|
234
|
+
await server.close();
|
|
235
|
+
await transport.close();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=hosted-http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hosted-http.js","sourceRoot":"","sources":["../src/hosted-http.ts"],"names":[],"mappings":"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"}
|
package/dist/install.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"AA4CA,KAAK,aAAa,GAAG;IACnB,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAcF,MAAM,WAAW,cAAc;IAC7B,gGAAgG;IAChG,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,gEAAgE;IAChE,mBAAmB,CAAC,EAAE,OAAO,KAAK,CAAC;IACnC,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACvE,sEAAsE;IACtE,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACjD,0EAA0E;IAC1E,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iFAAiF;IACjF,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7E;AAscD,2EAA2E;AAC3E,wBAAsB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwCzE"}
|
package/dist/install.js
CHANGED
|
@@ -3,9 +3,12 @@ import * as fs from 'node:fs';
|
|
|
3
3
|
import * as os from 'node:os';
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import { createInterface } from 'node:readline';
|
|
6
|
-
import {
|
|
6
|
+
import { applyEdits, modify, parse } from 'jsonc-parser';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { API_ORIGIN, isWarmApiKey } from '@warmio/contracts/api';
|
|
7
9
|
import { assertWarmMcpLauncherContract, maybeRequireWarmMcpRegistryIntegrity, WARM_MCP_CLIENTS, WARM_MCP_MANIFEST, WARM_MCP_PROJECT_CONFIGS, WARM_MCP_SERVER_CONFIG, } from '@warmio/contracts/mcp';
|
|
8
10
|
import { getWarmApiKeyPath } from './config-paths.js';
|
|
11
|
+
import { BANK_LOGIN_CAPACITY_EXCEEDED_CODE } from './warm-api-client.js';
|
|
9
12
|
function hasReadlineWriter(readline) {
|
|
10
13
|
return '_writeToOutput' in readline && readline._writeToOutput instanceof Function;
|
|
11
14
|
}
|
|
@@ -15,7 +18,6 @@ function isRecord(value) {
|
|
|
15
18
|
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- object detection is the filesystem JSON boundary check.
|
|
16
19
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
17
20
|
}
|
|
18
|
-
const API_KEY_PATTERN = new RegExp(`^${WARM_API_KEY_PREFIX}[A-Za-z0-9_-]{43}$`);
|
|
19
21
|
function configPath(config, homeDir) {
|
|
20
22
|
const root = config.root === 'appData'
|
|
21
23
|
? process.env['APPDATA']?.trim() || path.join(homeDir, 'AppData', 'Roaming')
|
|
@@ -50,7 +52,14 @@ function supportedClients(homeDir, workingDirectory) {
|
|
|
50
52
|
return clients;
|
|
51
53
|
}, []);
|
|
52
54
|
const configPaths = new Set();
|
|
53
|
-
return [...globalClients, ...projectClients]
|
|
55
|
+
return [...globalClients, ...projectClients]
|
|
56
|
+
.map((client) => {
|
|
57
|
+
if (client.configDialect !== 'openCode')
|
|
58
|
+
return client;
|
|
59
|
+
const jsoncPath = `${client.configPath}c`;
|
|
60
|
+
return fs.existsSync(jsoncPath) ? { ...client, configPath: jsoncPath } : client;
|
|
61
|
+
})
|
|
62
|
+
.filter((client) => {
|
|
54
63
|
if (configPaths.has(client.configPath))
|
|
55
64
|
return false;
|
|
56
65
|
configPaths.add(client.configPath);
|
|
@@ -79,7 +88,12 @@ function isCurrentWarmConfig(client, server) {
|
|
|
79
88
|
function parseJsonConfig(client, content) {
|
|
80
89
|
if (!content)
|
|
81
90
|
return {};
|
|
82
|
-
const
|
|
91
|
+
const errors = [];
|
|
92
|
+
const parsed = client.configDialect === 'openCode'
|
|
93
|
+
? parse(content, errors, { allowTrailingComma: true })
|
|
94
|
+
: JSON.parse(content);
|
|
95
|
+
if (errors.length > 0)
|
|
96
|
+
throw new Error(`${client.name} config is not valid JSONC.`);
|
|
83
97
|
if (!isRecord(parsed))
|
|
84
98
|
throw new Error(`${client.name} config is not a JSON object.`);
|
|
85
99
|
return parsed;
|
|
@@ -87,6 +101,17 @@ function parseJsonConfig(client, content) {
|
|
|
87
101
|
function readJsonConfig(client) {
|
|
88
102
|
return parseJsonConfig(client, fs.existsSync(client.configPath) ? fs.readFileSync(client.configPath, 'utf8') : '');
|
|
89
103
|
}
|
|
104
|
+
function openCodeMcpConfig(config) {
|
|
105
|
+
if (config['mcp'] === undefined)
|
|
106
|
+
return {};
|
|
107
|
+
if (!isRecord(config['mcp']))
|
|
108
|
+
throw new Error('OpenCode mcp config must be an object.');
|
|
109
|
+
const mcp = config['mcp'];
|
|
110
|
+
if (mcp['servers'] !== undefined && !isRecord(mcp['servers'])) {
|
|
111
|
+
throw new Error('OpenCode mcp.servers config must be an object.');
|
|
112
|
+
}
|
|
113
|
+
return mcp;
|
|
114
|
+
}
|
|
90
115
|
const WARM_TOML_BLOCK = /\n?\[mcp_servers\.warm\][\s\S]*?(?=\n\[[^\n]+\]|\s*$)/g;
|
|
91
116
|
const WARM_TOML_ENV_BLOCK = /\n?\[mcp_servers\.warm\.env\][\s\S]*?(?=\n\[[^\n]+\]|\s*$)/g;
|
|
92
117
|
function existingWarmConfig(client) {
|
|
@@ -109,14 +134,16 @@ function existingWarmConfig(client) {
|
|
|
109
134
|
}
|
|
110
135
|
const config = readJsonConfig(client);
|
|
111
136
|
if (client.configDialect === 'openCode') {
|
|
112
|
-
const
|
|
137
|
+
const mcp = openCodeMcpConfig(config);
|
|
138
|
+
const servers = isRecord(mcp['servers']) ? mcp['servers'] : {};
|
|
113
139
|
const legacyServers = isRecord(config['mcpServers']) ? config['mcpServers'] : {};
|
|
114
140
|
const hasLegacyWarmConfig = 'warm' in legacyServers;
|
|
115
141
|
return {
|
|
116
142
|
current: isRecord(servers['warm']) &&
|
|
117
143
|
isCurrentWarmConfig(client, servers['warm']) &&
|
|
144
|
+
!('warm' in mcp) &&
|
|
118
145
|
!hasLegacyWarmConfig,
|
|
119
|
-
exists: 'warm' in servers || hasLegacyWarmConfig,
|
|
146
|
+
exists: 'warm' in servers || 'warm' in mcp || hasLegacyWarmConfig,
|
|
120
147
|
};
|
|
121
148
|
}
|
|
122
149
|
const servers = isRecord(config['mcpServers']) ? config['mcpServers'] : {};
|
|
@@ -125,21 +152,69 @@ function existingWarmConfig(client) {
|
|
|
125
152
|
exists: 'warm' in servers,
|
|
126
153
|
};
|
|
127
154
|
}
|
|
155
|
+
function openCodeServerConfig(server) {
|
|
156
|
+
const migrated = { ...server };
|
|
157
|
+
if (server['enabled'] === true || server['enabled'] === false) {
|
|
158
|
+
migrated['disabled'] = !server['enabled'];
|
|
159
|
+
delete migrated['enabled'];
|
|
160
|
+
}
|
|
161
|
+
const timeout = z.number().safeParse(server['timeout']);
|
|
162
|
+
if (timeout.success)
|
|
163
|
+
migrated['timeout'] = { catalog: timeout.data, execution: timeout.data };
|
|
164
|
+
if (isRecord(server['oauth'])) {
|
|
165
|
+
const oauth = { ...server['oauth'] };
|
|
166
|
+
for (const [oldName, newName] of [
|
|
167
|
+
['clientId', 'client_id'],
|
|
168
|
+
['clientSecret', 'client_secret'],
|
|
169
|
+
['callbackPort', 'callback_port'],
|
|
170
|
+
['redirectUri', 'redirect_uri'],
|
|
171
|
+
]) {
|
|
172
|
+
if (!(oldName in oauth))
|
|
173
|
+
continue;
|
|
174
|
+
if (!(newName in oauth))
|
|
175
|
+
oauth[newName] = oauth[oldName];
|
|
176
|
+
delete oauth[oldName];
|
|
177
|
+
}
|
|
178
|
+
migrated['oauth'] = oauth;
|
|
179
|
+
}
|
|
180
|
+
return migrated;
|
|
181
|
+
}
|
|
182
|
+
function prepareOpenCodeConfig(config, current) {
|
|
183
|
+
const mcp = openCodeMcpConfig(config);
|
|
184
|
+
const servers = isRecord(mcp['servers']) ? mcp['servers'] : {};
|
|
185
|
+
let content = current || '{}';
|
|
186
|
+
const edit = (location, value) => {
|
|
187
|
+
content = applyEdits(content, modify(content, location, value, {
|
|
188
|
+
formattingOptions: { insertSpaces: true, tabSize: 2, eol: '\n' },
|
|
189
|
+
}));
|
|
190
|
+
};
|
|
191
|
+
for (const [name, server] of Object.entries(mcp)) {
|
|
192
|
+
if (name === 'warm' ||
|
|
193
|
+
!isRecord(server) ||
|
|
194
|
+
(server['type'] !== 'local' && server['type'] !== 'remote'))
|
|
195
|
+
continue;
|
|
196
|
+
if (name in servers)
|
|
197
|
+
throw new Error(`OpenCode config has conflicting MCP entries for ${name}.`);
|
|
198
|
+
edit(['mcp', 'servers', name], openCodeServerConfig(server));
|
|
199
|
+
edit(['mcp', name], undefined);
|
|
200
|
+
}
|
|
201
|
+
edit(['mcp', 'servers', 'warm'], {
|
|
202
|
+
type: 'local',
|
|
203
|
+
command: [WARM_MCP_SERVER_CONFIG.command, ...WARM_MCP_SERVER_CONFIG.args],
|
|
204
|
+
});
|
|
205
|
+
if ('warm' in mcp)
|
|
206
|
+
edit(['mcp', 'warm'], undefined);
|
|
207
|
+
if (isRecord(config['mcpServers']) && 'warm' in config['mcpServers']) {
|
|
208
|
+
edit(['mcpServers', 'warm'], undefined);
|
|
209
|
+
if (Object.keys(config['mcpServers']).length === 1)
|
|
210
|
+
edit(['mcpServers'], undefined);
|
|
211
|
+
}
|
|
212
|
+
return `${content.trimEnd()}\n`;
|
|
213
|
+
}
|
|
128
214
|
function prepareJsonConfig(client, current) {
|
|
129
215
|
const config = parseJsonConfig(client, current);
|
|
130
216
|
if (client.configDialect === 'openCode') {
|
|
131
|
-
|
|
132
|
-
servers['warm'] = {
|
|
133
|
-
type: 'local',
|
|
134
|
-
command: [WARM_MCP_SERVER_CONFIG.command, ...WARM_MCP_SERVER_CONFIG.args],
|
|
135
|
-
};
|
|
136
|
-
config['mcp'] = servers;
|
|
137
|
-
if (isRecord(config['mcpServers']) && 'warm' in config['mcpServers']) {
|
|
138
|
-
delete config['mcpServers']['warm'];
|
|
139
|
-
if (Object.keys(config['mcpServers']).length === 0)
|
|
140
|
-
delete config['mcpServers'];
|
|
141
|
-
}
|
|
142
|
-
return `${JSON.stringify(config, null, 2)}\n`;
|
|
217
|
+
return prepareOpenCodeConfig(config, current);
|
|
143
218
|
}
|
|
144
219
|
const servers = isRecord(config['mcpServers']) ? config['mcpServers'] : {};
|
|
145
220
|
servers['warm'] = {
|
|
@@ -171,6 +246,7 @@ function snapshotFile(target) {
|
|
|
171
246
|
};
|
|
172
247
|
}
|
|
173
248
|
catch (error) {
|
|
249
|
+
// oxlint-disable-next-line typescript/consistent-type-assertions -- [typescript/consistent-type-assertions] Pre-existing production TypeScript assertion surfaced by the corrected assertion-expression family classifier.
|
|
174
250
|
if (error.code !== 'ENOENT')
|
|
175
251
|
throw error;
|
|
176
252
|
return { content: '', exists: false, mode: 0o666 };
|
|
@@ -281,9 +357,13 @@ function readlinePrompt(question, options) {
|
|
|
281
357
|
});
|
|
282
358
|
});
|
|
283
359
|
}
|
|
360
|
+
const API_KEY_VALIDATION_FAILED_MESSAGE = 'Warm API key validation failed. Check Warm Settings and try again.';
|
|
361
|
+
const BANK_LOGIN_CAPACITY_RESOLUTION_MESSAGE = 'This Warm API key is valid, but your account holds more bank logins than your plan includes. Remove a bank login or change your plan in Warm, then run the installer again.';
|
|
362
|
+
class WarmApiCapacityError extends Error {
|
|
363
|
+
}
|
|
284
364
|
async function validateApiKey(apiKey, options) {
|
|
285
|
-
if (!
|
|
286
|
-
throw new Error(
|
|
365
|
+
if (!isWarmApiKey(apiKey)) {
|
|
366
|
+
throw new Error(API_KEY_VALIDATION_FAILED_MESSAGE);
|
|
287
367
|
}
|
|
288
368
|
const response = await (options.fetchImplementation || fetch)(new URL('/api/verify', options.apiUrl || API_ORIGIN), {
|
|
289
369
|
headers: {
|
|
@@ -293,9 +373,13 @@ async function validateApiKey(apiKey, options) {
|
|
|
293
373
|
});
|
|
294
374
|
const text = await response.text();
|
|
295
375
|
const body = parseJsonResponse(text);
|
|
296
|
-
if (
|
|
297
|
-
|
|
376
|
+
if (response.ok && isRecord(body) && body['valid'] === true && Object.keys(body).length === 1) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
if (!response.ok && isRecord(body) && body['code'] === BANK_LOGIN_CAPACITY_EXCEEDED_CODE) {
|
|
380
|
+
throw new WarmApiCapacityError(BANK_LOGIN_CAPACITY_RESOLUTION_MESSAGE);
|
|
298
381
|
}
|
|
382
|
+
throw new Error(API_KEY_VALIDATION_FAILED_MESSAGE);
|
|
299
383
|
}
|
|
300
384
|
function parseJsonResponse(text) {
|
|
301
385
|
try {
|
|
@@ -327,7 +411,9 @@ async function resolveApiKey(target, ask, options) {
|
|
|
327
411
|
await validateApiKey(storedApiKey, options);
|
|
328
412
|
return { apiKey: storedApiKey, replaceStoredKey: false };
|
|
329
413
|
}
|
|
330
|
-
catch {
|
|
414
|
+
catch (error) {
|
|
415
|
+
if (error instanceof WarmApiCapacityError)
|
|
416
|
+
throw error;
|
|
331
417
|
return {
|
|
332
418
|
apiKey: await requestedApiKey(ask, options, INVALID_STORED_API_KEY_PASTE_PROMPT),
|
|
333
419
|
replaceStoredKey: true,
|