Skip to content

Commit 940d8bd

Browse files
Merge pull request #20 from CASParser/release-please--branches--main--changes--next--components--cas-parser-node
release: 1.10.2
2 parents fed9ed4 + 19d3ff1 commit 940d8bd

File tree

14 files changed

+204
-395
lines changed

14 files changed

+204
-395
lines changed

.github/workflows/ci.yml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
name: CI
22
on:
33
push:
4-
branches-ignore:
5-
- 'generated'
6-
- 'codegen/**'
7-
- 'integrated/**'
8-
- 'stl-preview-head/**'
9-
- 'stl-preview-base/**'
4+
branches:
5+
- '**'
6+
- '!integrated/**'
7+
- '!stl-preview-head/**'
8+
- '!stl-preview-base/**'
9+
- '!generated'
10+
- '!codegen/**'
11+
- 'codegen/stl/**'
1012
pull_request:
1113
branches-ignore:
1214
- 'stl-preview-head/**'

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "1.10.1"
2+
".": "1.10.2"
33
}

CHANGELOG.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,34 @@
11
# Changelog
22

3+
## 1.10.2 (2026-03-17)
4+
5+
Full Changelog: [v1.10.1...v1.10.2](https://github.com/CASParser/cas-parser-node/compare/v1.10.1...v1.10.2)
6+
7+
### Chores
8+
9+
* **internal:** codegen related update ([42785e5](https://github.com/CASParser/cas-parser-node/commit/42785e5ad046d551e6c851d7521203a176354433))
10+
* **internal:** codegen related update ([4515206](https://github.com/CASParser/cas-parser-node/commit/4515206424c06f436dc61bea1994c56cb687c394))
11+
* **internal:** codegen related update ([f91eb0e](https://github.com/CASParser/cas-parser-node/commit/f91eb0e9800b5d0f970f31e97d06355aa6d3df77))
12+
* **internal:** codegen related update ([c38d136](https://github.com/CASParser/cas-parser-node/commit/c38d136006bcbf2f05e1642b1098c6d785c32af2))
13+
* **internal:** codegen related update ([44b93cc](https://github.com/CASParser/cas-parser-node/commit/44b93cccf487ebf941d1c947e4d3320338e22f77))
14+
* **internal:** codegen related update ([4005e2f](https://github.com/CASParser/cas-parser-node/commit/4005e2f3a16bba4dccfdfad92e3ff03696d67d10))
15+
* **internal:** codegen related update ([a9f558a](https://github.com/CASParser/cas-parser-node/commit/a9f558abca126a4b575f9015dd6550706c60783a))
16+
* **internal:** codegen related update ([da227d9](https://github.com/CASParser/cas-parser-node/commit/da227d988d02d7a168bc84b5d1e7ae5094390747))
17+
* **internal:** codegen related update ([57415da](https://github.com/CASParser/cas-parser-node/commit/57415dabd939521c07a8fcf745624f790dcbdf3e))
18+
* **internal:** codegen related update ([5040a3f](https://github.com/CASParser/cas-parser-node/commit/5040a3f99ad8354df704b5fd383f503f2d704e8a))
19+
* **internal:** make generated MCP servers compatible with Cloudflare worker environments ([d44814b](https://github.com/CASParser/cas-parser-node/commit/d44814bb29c75f80ea39a7e51b8c0ec495672e0f))
20+
* **internal:** support x-stainless-mcp-client-envs header in MCP servers ([a97ef55](https://github.com/CASParser/cas-parser-node/commit/a97ef55a49629a248550f498201d884891b41aec))
21+
* **internal:** support x-stainless-mcp-client-permissions headers in MCP servers ([c7cc5dc](https://github.com/CASParser/cas-parser-node/commit/c7cc5dc4378b6b76af659ca87b91bedeee47b76c))
22+
* **internal:** tweak CI branches ([5a31470](https://github.com/CASParser/cas-parser-node/commit/5a31470b7bbf42e03a4ca9a8948c5bf6212ec749))
23+
* **internal:** update dependencies to address dependabot vulnerabilities ([67cd264](https://github.com/CASParser/cas-parser-node/commit/67cd26484b9ab683bf8101210ac9ae285c86eab2))
24+
* **internal:** update lock file ([e95138b](https://github.com/CASParser/cas-parser-node/commit/e95138b5d2cdf73a4474d11d52beb2ad8a966c89))
25+
* **internal:** update lockfile ([9f834de](https://github.com/CASParser/cas-parser-node/commit/9f834de4889c6439090b17132ab987376603740c))
26+
27+
28+
### Refactors
29+
30+
* update sdk ([b972fe0](https://github.com/CASParser/cas-parser-node/commit/b972fe0ffc1dc4b3ab81afb47c86a0e306f06120))
31+
332
## 1.10.1 (2026-03-07)
433

534
Full Changelog: [v1.10.0...v1.10.1](https://github.com/CASParser/cas-parser-node/compare/v1.10.0...v1.10.1)

package.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cas-parser-node",
3-
"version": "1.10.1",
3+
"version": "1.10.2",
44
"description": "The official TypeScript library for the Cas Parser API",
55
"author": "Cas Parser <sameer@casparser.in>",
66
"types": "dist/index.d.ts",
@@ -50,6 +50,17 @@
5050
"typescript": "5.8.3",
5151
"typescript-eslint": "8.31.1"
5252
},
53+
"overrides": {
54+
"minimatch": "^9.0.5"
55+
},
56+
"pnpm": {
57+
"overrides": {
58+
"minimatch": "^9.0.5"
59+
}
60+
},
61+
"resolutions": {
62+
"minimatch": "^9.0.5"
63+
},
5364
"exports": {
5465
".": {
5566
"import": "./dist/index.mjs",

packages/mcp-server/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"dxt_version": "0.2",
33
"name": "cas-parser-node-mcp",
4-
"version": "1.10.1",
4+
"version": "1.10.2",
55
"description": "The official MCP Server for the Cas Parser API",
66
"author": {
77
"name": "Cas Parser",

packages/mcp-server/package.json

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cas-parser-node-mcp",
3-
"version": "1.10.1",
3+
"version": "1.10.2",
44
"description": "The official MCP Server for the Cas Parser API",
55
"author": "Cas Parser <sameer@casparser.in>",
66
"types": "dist/index.d.ts",
@@ -25,13 +25,16 @@
2525
"prepublishOnly": "echo 'to publish, run pnpm build && (cd dist; pnpm publish)' && exit 1",
2626
"format": "prettier --write --cache --cache-strategy metadata . !dist",
2727
"tsn": "ts-node -r tsconfig-paths/register",
28-
"lint": "eslint --ext ts,js .",
29-
"fix": "eslint --fix --ext ts,js ."
28+
"lint": "eslint .",
29+
"fix": "eslint --fix ."
3030
},
3131
"dependencies": {
3232
"cas-parser-node": "workspace:*",
33+
"ajv": "^8.18.0",
3334
"@cloudflare/cabidela": "^0.2.4",
34-
"@modelcontextprotocol/sdk": "^1.26.0",
35+
"@hono/node-server": "^1.19.10",
36+
"@modelcontextprotocol/sdk": "^1.27.1",
37+
"hono": "^4.12.4",
3538
"@valtown/deno-http-worker": "^0.0.21",
3639
"cookie-parser": "^1.4.6",
3740
"cors": "^2.8.5",
@@ -61,9 +64,9 @@
6164
"@types/yargs": "^17.0.8",
6265
"@typescript-eslint/eslint-plugin": "8.31.1",
6366
"@typescript-eslint/parser": "8.31.1",
64-
"eslint": "^8.49.0",
65-
"eslint-plugin-prettier": "^5.0.1",
66-
"eslint-plugin-unused-imports": "^3.0.0",
67+
"eslint": "^9.39.1",
68+
"eslint-plugin-prettier": "^5.4.1",
69+
"eslint-plugin-unused-imports": "^4.1.4",
6770
"jest": "^29.4.0",
6871
"prettier": "^3.0.0",
6972
"ts-jest": "^29.1.0",
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
22

3-
export const workerPath = require.resolve('./code-tool-worker.mjs');
3+
export function getWorkerPath(): string {
4+
return require.resolve('./code-tool-worker.mjs');
5+
}

packages/mcp-server/src/code-tool.ts

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
22

3-
import fs from 'node:fs';
4-
import path from 'node:path';
5-
import url from 'node:url';
6-
import { newDenoHTTPWorker } from '@valtown/deno-http-worker';
7-
import { workerPath } from './code-tool-paths.cjs';
83
import {
94
ContentBlock,
105
McpRequestContext,
@@ -149,19 +144,23 @@ const remoteStainlessHandler = async ({
149144

150145
const codeModeEndpoint = readEnv('CODE_MODE_ENDPOINT_URL') ?? 'https://api.stainless.com/api/ai/code-tool';
151146

147+
const localClientEnvs = {
148+
CAS_PARSER_API_KEY: requireValue(
149+
readEnv('CAS_PARSER_API_KEY') ?? client.apiKey,
150+
'set CAS_PARSER_API_KEY environment variable or provide apiKey client option',
151+
),
152+
CAS_PARSER_BASE_URL: readEnv('CAS_PARSER_BASE_URL') ?? client.baseURL ?? undefined,
153+
};
154+
// Merge any upstream client envs from the request header, with upstream values taking precedence.
155+
const mergedClientEnvs = { ...localClientEnvs, ...reqContext.upstreamClientEnvs };
156+
152157
// Setting a Stainless API key authenticates requests to the code tool endpoint.
153158
const res = await fetch(codeModeEndpoint, {
154159
method: 'POST',
155160
headers: {
156161
...(reqContext.stainlessApiKey && { Authorization: reqContext.stainlessApiKey }),
157162
'Content-Type': 'application/json',
158-
'x-stainless-mcp-client-envs': JSON.stringify({
159-
CAS_PARSER_API_KEY: requireValue(
160-
readEnv('CAS_PARSER_API_KEY') ?? client.apiKey,
161-
'set CAS_PARSER_API_KEY environment variable or provide apiKey client option',
162-
),
163-
CAS_PARSER_BASE_URL: readEnv('CAS_PARSER_BASE_URL') ?? client.baseURL ?? undefined,
164-
}),
163+
'x-stainless-mcp-client-envs': JSON.stringify(mergedClientEnvs),
165164
},
166165
body: JSON.stringify({
167166
project_name: 'cas-parser',
@@ -204,6 +203,13 @@ const localDenoHandler = async ({
204203
reqContext: McpRequestContext;
205204
args: unknown;
206205
}): Promise<ToolCallResult> => {
206+
const fs = await import('node:fs');
207+
const path = await import('node:path');
208+
const url = await import('node:url');
209+
const { newDenoHTTPWorker } = await import('@valtown/deno-http-worker');
210+
const { getWorkerPath } = await import('./code-tool-paths.cjs');
211+
const workerPath = getWorkerPath();
212+
207213
const client = reqContext.client;
208214
const baseURLHostname = new URL(client.baseURL).hostname;
209215
const { code } = args as { code: string };
@@ -265,6 +271,9 @@ const localDenoHandler = async ({
265271
printOutput: true,
266272
spawnOptions: {
267273
cwd: path.dirname(workerPath),
274+
// Merge any upstream client envs into the Deno subprocess environment,
275+
// with the upstream env vars taking precedence.
276+
env: { ...process.env, ...reqContext.upstreamClientEnvs },
268277
},
269278
});
270279

@@ -274,13 +283,17 @@ const localDenoHandler = async ({
274283
reject(new Error(`Worker exited with code ${exitCode}`));
275284
});
276285

277-
const opts: ClientOptions = {
278-
baseURL: client.baseURL,
279-
apiKey: client.apiKey,
280-
defaultHeaders: {
281-
'X-Stainless-MCP': 'true',
282-
},
283-
};
286+
// Strip null/undefined values so that the worker SDK client can fall back to
287+
// reading from environment variables (including any upstreamClientEnvs).
288+
const opts: ClientOptions = Object.fromEntries(
289+
Object.entries({
290+
baseURL: client.baseURL,
291+
apiKey: client.apiKey,
292+
defaultHeaders: {
293+
'X-Stainless-MCP': 'true',
294+
},
295+
}).filter(([_, v]) => v != null),
296+
) as ClientOptions;
284297

285298
const req = worker.request(
286299
'http://localhost',

packages/mcp-server/src/http.ts

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,56 @@ const newServer = async ({
2727

2828
const authOptions = parseClientAuthHeaders(req, false);
2929

30+
let upstreamClientEnvs: Record<string, string> | undefined;
31+
const clientEnvsHeader = req.headers['x-stainless-mcp-client-envs'];
32+
if (typeof clientEnvsHeader === 'string') {
33+
try {
34+
const parsed = JSON.parse(clientEnvsHeader);
35+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
36+
upstreamClientEnvs = parsed;
37+
}
38+
} catch {
39+
// Ignore malformed header
40+
}
41+
}
42+
43+
// Parse x-stainless-mcp-client-permissions header to override permission options
44+
//
45+
// Note: Permissions are best-effort and intended to prevent clients from doing unexpected things;
46+
// they're not a hard security boundary, so we allow arbitrary, client-driven overrides.
47+
//
48+
// See the Stainless MCP documentation for more details.
49+
let effectiveMcpOptions = mcpOptions;
50+
const clientPermissionsHeader = req.headers['x-stainless-mcp-client-permissions'];
51+
if (typeof clientPermissionsHeader === 'string') {
52+
try {
53+
const parsed = JSON.parse(clientPermissionsHeader);
54+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
55+
effectiveMcpOptions = {
56+
...mcpOptions,
57+
...(typeof parsed.allow_http_gets === 'boolean' && { codeAllowHttpGets: parsed.allow_http_gets }),
58+
...(Array.isArray(parsed.allowed_methods) && { codeAllowedMethods: parsed.allowed_methods }),
59+
...(Array.isArray(parsed.blocked_methods) && { codeBlockedMethods: parsed.blocked_methods }),
60+
};
61+
getLogger().info(
62+
{ clientPermissions: parsed },
63+
'Overriding code execution permissions from x-stainless-mcp-client-permissions header',
64+
);
65+
}
66+
} catch (error) {
67+
getLogger().warn({ error }, 'Failed to parse x-stainless-mcp-client-permissions header');
68+
}
69+
}
70+
3071
await initMcpServer({
3172
server: server,
32-
mcpOptions: mcpOptions,
73+
mcpOptions: effectiveMcpOptions,
3374
clientOptions: {
3475
...clientOptions,
3576
...authOptions,
3677
},
3778
stainlessApiKey: stainlessApiKey,
79+
upstreamClientEnvs,
3880
});
3981

4082
return server;
@@ -72,7 +114,7 @@ const del = async (req: express.Request, res: express.Response) => {
72114
};
73115

74116
const redactHeaders = (headers: Record<string, any>) => {
75-
const hiddenHeaders = /auth|cookie|key|token/i;
117+
const hiddenHeaders = /auth|cookie|key|token|x-stainless-mcp-client-envs/i;
76118
const filtered = { ...headers };
77119
Object.keys(filtered).forEach((key) => {
78120
if (hiddenHeaders.test(key)) {

packages/mcp-server/src/instructions.ts

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,24 @@ interface InstructionsCacheEntry {
1212

1313
const instructionsCache = new Map<string, InstructionsCacheEntry>();
1414

15-
// Periodically evict stale entries so the cache doesn't grow unboundedly.
16-
const _cacheCleanupInterval = setInterval(() => {
17-
const now = Date.now();
18-
for (const [key, entry] of instructionsCache) {
19-
if (now - entry.fetchedAt > INSTRUCTIONS_CACHE_TTL_MS) {
20-
instructionsCache.delete(key);
21-
}
22-
}
23-
}, INSTRUCTIONS_CACHE_TTL_MS);
24-
25-
// Don't keep the process alive just for cleanup.
26-
_cacheCleanupInterval.unref();
27-
2815
export async function getInstructions(stainlessApiKey: string | undefined): Promise<string> {
16+
const now = Date.now();
2917
const cacheKey = stainlessApiKey ?? '';
3018
const cached = instructionsCache.get(cacheKey);
3119

32-
if (cached && Date.now() - cached.fetchedAt <= INSTRUCTIONS_CACHE_TTL_MS) {
20+
if (cached && now - cached.fetchedAt <= INSTRUCTIONS_CACHE_TTL_MS) {
3321
return cached.fetchedInstructions;
3422
}
3523

24+
// Evict stale entries so the cache doesn't grow unboundedly.
25+
for (const [key, entry] of instructionsCache) {
26+
if (now - entry.fetchedAt > INSTRUCTIONS_CACHE_TTL_MS) {
27+
instructionsCache.delete(key);
28+
}
29+
}
30+
3631
const fetchedInstructions = await fetchLatestInstructions(stainlessApiKey);
37-
instructionsCache.set(cacheKey, { fetchedInstructions, fetchedAt: Date.now() });
32+
instructionsCache.set(cacheKey, { fetchedInstructions, fetchedAt: now });
3833
return fetchedInstructions;
3934
}
4035

0 commit comments

Comments
 (0)