Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
67cd264
chore(internal): update dependencies to address dependabot vulnerabil…
stainless-app[bot] Mar 10, 2026
5040a3f
chore(internal): codegen related update
stainless-app[bot] Mar 10, 2026
57415da
chore(internal): codegen related update
stainless-app[bot] Mar 11, 2026
da227d9
chore(internal): codegen related update
stainless-app[bot] Mar 11, 2026
a9f558a
chore(internal): codegen related update
stainless-app[bot] Mar 11, 2026
b972fe0
refactor: update sdk
stainless-app[bot] Mar 11, 2026
4005e2f
chore(internal): codegen related update
stainless-app[bot] Mar 11, 2026
44b93cc
chore(internal): codegen related update
stainless-app[bot] Mar 11, 2026
c38d136
chore(internal): codegen related update
stainless-app[bot] Mar 11, 2026
f91eb0e
chore(internal): codegen related update
stainless-app[bot] Mar 11, 2026
4515206
chore(internal): codegen related update
stainless-app[bot] Mar 11, 2026
42785e5
chore(internal): codegen related update
stainless-app[bot] Mar 12, 2026
e95138b
chore(internal): update lock file
stainless-app[bot] Mar 12, 2026
9f834de
chore(internal): update lockfile
stainless-app[bot] Mar 14, 2026
d44814b
chore(internal): make generated MCP servers compatible with Cloudflar…
stainless-app[bot] Mar 14, 2026
a97ef55
chore(internal): support x-stainless-mcp-client-envs header in MCP se…
stainless-app[bot] Mar 14, 2026
5a31470
chore(internal): tweak CI branches
stainless-app[bot] Mar 17, 2026
c7cc5dc
chore(internal): support x-stainless-mcp-client-permissions headers i…
stainless-app[bot] Mar 17, 2026
19d3ff1
release: 1.10.2
stainless-app[bot] Mar 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
name: CI
on:
push:
branches-ignore:
- 'generated'
- 'codegen/**'
- 'integrated/**'
- 'stl-preview-head/**'
- 'stl-preview-base/**'
branches:
- '**'
- '!integrated/**'
- '!stl-preview-head/**'
- '!stl-preview-base/**'
- '!generated'
- '!codegen/**'
- 'codegen/stl/**'
pull_request:
branches-ignore:
- 'stl-preview-head/**'
Expand Down
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "1.10.1"
".": "1.10.2"
}
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
# Changelog

## 1.10.2 (2026-03-17)

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

### Chores

* **internal:** codegen related update ([42785e5](https://github.com/CASParser/cas-parser-node/commit/42785e5ad046d551e6c851d7521203a176354433))
* **internal:** codegen related update ([4515206](https://github.com/CASParser/cas-parser-node/commit/4515206424c06f436dc61bea1994c56cb687c394))
* **internal:** codegen related update ([f91eb0e](https://github.com/CASParser/cas-parser-node/commit/f91eb0e9800b5d0f970f31e97d06355aa6d3df77))
* **internal:** codegen related update ([c38d136](https://github.com/CASParser/cas-parser-node/commit/c38d136006bcbf2f05e1642b1098c6d785c32af2))
* **internal:** codegen related update ([44b93cc](https://github.com/CASParser/cas-parser-node/commit/44b93cccf487ebf941d1c947e4d3320338e22f77))
* **internal:** codegen related update ([4005e2f](https://github.com/CASParser/cas-parser-node/commit/4005e2f3a16bba4dccfdfad92e3ff03696d67d10))
* **internal:** codegen related update ([a9f558a](https://github.com/CASParser/cas-parser-node/commit/a9f558abca126a4b575f9015dd6550706c60783a))
* **internal:** codegen related update ([da227d9](https://github.com/CASParser/cas-parser-node/commit/da227d988d02d7a168bc84b5d1e7ae5094390747))
* **internal:** codegen related update ([57415da](https://github.com/CASParser/cas-parser-node/commit/57415dabd939521c07a8fcf745624f790dcbdf3e))
* **internal:** codegen related update ([5040a3f](https://github.com/CASParser/cas-parser-node/commit/5040a3f99ad8354df704b5fd383f503f2d704e8a))
* **internal:** make generated MCP servers compatible with Cloudflare worker environments ([d44814b](https://github.com/CASParser/cas-parser-node/commit/d44814bb29c75f80ea39a7e51b8c0ec495672e0f))
* **internal:** support x-stainless-mcp-client-envs header in MCP servers ([a97ef55](https://github.com/CASParser/cas-parser-node/commit/a97ef55a49629a248550f498201d884891b41aec))
* **internal:** support x-stainless-mcp-client-permissions headers in MCP servers ([c7cc5dc](https://github.com/CASParser/cas-parser-node/commit/c7cc5dc4378b6b76af659ca87b91bedeee47b76c))
* **internal:** tweak CI branches ([5a31470](https://github.com/CASParser/cas-parser-node/commit/5a31470b7bbf42e03a4ca9a8948c5bf6212ec749))
* **internal:** update dependencies to address dependabot vulnerabilities ([67cd264](https://github.com/CASParser/cas-parser-node/commit/67cd26484b9ab683bf8101210ac9ae285c86eab2))
* **internal:** update lock file ([e95138b](https://github.com/CASParser/cas-parser-node/commit/e95138b5d2cdf73a4474d11d52beb2ad8a966c89))
* **internal:** update lockfile ([9f834de](https://github.com/CASParser/cas-parser-node/commit/9f834de4889c6439090b17132ab987376603740c))


### Refactors

* update sdk ([b972fe0](https://github.com/CASParser/cas-parser-node/commit/b972fe0ffc1dc4b3ab81afb47c86a0e306f06120))

## 1.10.1 (2026-03-07)

Full Changelog: [v1.10.0...v1.10.1](https://github.com/CASParser/cas-parser-node/compare/v1.10.0...v1.10.1)
Expand Down
13 changes: 12 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cas-parser-node",
"version": "1.10.1",
"version": "1.10.2",
"description": "The official TypeScript library for the Cas Parser API",
"author": "Cas Parser <sameer@casparser.in>",
"types": "dist/index.d.ts",
Expand Down Expand Up @@ -50,6 +50,17 @@
"typescript": "5.8.3",
"typescript-eslint": "8.31.1"
},
"overrides": {
"minimatch": "^9.0.5"
},
"pnpm": {
"overrides": {
"minimatch": "^9.0.5"
}
},
"resolutions": {
"minimatch": "^9.0.5"
},
"exports": {
".": {
"import": "./dist/index.mjs",
Expand Down
2 changes: 1 addition & 1 deletion packages/mcp-server/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"dxt_version": "0.2",
"name": "cas-parser-node-mcp",
"version": "1.10.1",
"version": "1.10.2",
"description": "The official MCP Server for the Cas Parser API",
"author": {
"name": "Cas Parser",
Expand Down
17 changes: 10 additions & 7 deletions packages/mcp-server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cas-parser-node-mcp",
"version": "1.10.1",
"version": "1.10.2",
"description": "The official MCP Server for the Cas Parser API",
"author": "Cas Parser <sameer@casparser.in>",
"types": "dist/index.d.ts",
Expand All @@ -25,13 +25,16 @@
"prepublishOnly": "echo 'to publish, run pnpm build && (cd dist; pnpm publish)' && exit 1",
"format": "prettier --write --cache --cache-strategy metadata . !dist",
"tsn": "ts-node -r tsconfig-paths/register",
"lint": "eslint --ext ts,js .",
"fix": "eslint --fix --ext ts,js ."
"lint": "eslint .",
"fix": "eslint --fix ."
},
"dependencies": {
"cas-parser-node": "workspace:*",
"ajv": "^8.18.0",
"@cloudflare/cabidela": "^0.2.4",
"@modelcontextprotocol/sdk": "^1.26.0",
"@hono/node-server": "^1.19.10",
"@modelcontextprotocol/sdk": "^1.27.1",
"hono": "^4.12.4",
"@valtown/deno-http-worker": "^0.0.21",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
Expand Down Expand Up @@ -61,9 +64,9 @@
"@types/yargs": "^17.0.8",
"@typescript-eslint/eslint-plugin": "8.31.1",
"@typescript-eslint/parser": "8.31.1",
"eslint": "^8.49.0",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-unused-imports": "^3.0.0",
"eslint": "^9.39.1",
"eslint-plugin-prettier": "^5.4.1",
"eslint-plugin-unused-imports": "^4.1.4",
"jest": "^29.4.0",
"prettier": "^3.0.0",
"ts-jest": "^29.1.0",
Expand Down
4 changes: 3 additions & 1 deletion packages/mcp-server/src/code-tool-paths.cts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

export const workerPath = require.resolve('./code-tool-worker.mjs');
export function getWorkerPath(): string {
return require.resolve('./code-tool-worker.mjs');
}
51 changes: 32 additions & 19 deletions packages/mcp-server/src/code-tool.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import fs from 'node:fs';
import path from 'node:path';
import url from 'node:url';
import { newDenoHTTPWorker } from '@valtown/deno-http-worker';
import { workerPath } from './code-tool-paths.cjs';
import {
ContentBlock,
McpRequestContext,
Expand Down Expand Up @@ -149,19 +144,23 @@ const remoteStainlessHandler = async ({

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

const localClientEnvs = {
CAS_PARSER_API_KEY: requireValue(
readEnv('CAS_PARSER_API_KEY') ?? client.apiKey,
'set CAS_PARSER_API_KEY environment variable or provide apiKey client option',
),
CAS_PARSER_BASE_URL: readEnv('CAS_PARSER_BASE_URL') ?? client.baseURL ?? undefined,
};
// Merge any upstream client envs from the request header, with upstream values taking precedence.
const mergedClientEnvs = { ...localClientEnvs, ...reqContext.upstreamClientEnvs };

// Setting a Stainless API key authenticates requests to the code tool endpoint.
const res = await fetch(codeModeEndpoint, {
method: 'POST',
headers: {
...(reqContext.stainlessApiKey && { Authorization: reqContext.stainlessApiKey }),
'Content-Type': 'application/json',
'x-stainless-mcp-client-envs': JSON.stringify({
CAS_PARSER_API_KEY: requireValue(
readEnv('CAS_PARSER_API_KEY') ?? client.apiKey,
'set CAS_PARSER_API_KEY environment variable or provide apiKey client option',
),
CAS_PARSER_BASE_URL: readEnv('CAS_PARSER_BASE_URL') ?? client.baseURL ?? undefined,
}),
'x-stainless-mcp-client-envs': JSON.stringify(mergedClientEnvs),
},
body: JSON.stringify({
project_name: 'cas-parser',
Expand Down Expand Up @@ -204,6 +203,13 @@ const localDenoHandler = async ({
reqContext: McpRequestContext;
args: unknown;
}): Promise<ToolCallResult> => {
const fs = await import('node:fs');
const path = await import('node:path');
const url = await import('node:url');
const { newDenoHTTPWorker } = await import('@valtown/deno-http-worker');
const { getWorkerPath } = await import('./code-tool-paths.cjs');
const workerPath = getWorkerPath();

const client = reqContext.client;
const baseURLHostname = new URL(client.baseURL).hostname;
const { code } = args as { code: string };
Expand Down Expand Up @@ -265,6 +271,9 @@ const localDenoHandler = async ({
printOutput: true,
spawnOptions: {
cwd: path.dirname(workerPath),
// Merge any upstream client envs into the Deno subprocess environment,
// with the upstream env vars taking precedence.
env: { ...process.env, ...reqContext.upstreamClientEnvs },
},
});

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

const opts: ClientOptions = {
baseURL: client.baseURL,
apiKey: client.apiKey,
defaultHeaders: {
'X-Stainless-MCP': 'true',
},
};
// Strip null/undefined values so that the worker SDK client can fall back to
// reading from environment variables (including any upstreamClientEnvs).
const opts: ClientOptions = Object.fromEntries(
Object.entries({
baseURL: client.baseURL,
apiKey: client.apiKey,
defaultHeaders: {
'X-Stainless-MCP': 'true',
},
}).filter(([_, v]) => v != null),
) as ClientOptions;

const req = worker.request(
'http://localhost',
Expand Down
46 changes: 44 additions & 2 deletions packages/mcp-server/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,56 @@ const newServer = async ({

const authOptions = parseClientAuthHeaders(req, false);

let upstreamClientEnvs: Record<string, string> | undefined;
const clientEnvsHeader = req.headers['x-stainless-mcp-client-envs'];
if (typeof clientEnvsHeader === 'string') {
try {
const parsed = JSON.parse(clientEnvsHeader);
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
upstreamClientEnvs = parsed;
}
} catch {
// Ignore malformed header
}
}

// Parse x-stainless-mcp-client-permissions header to override permission options
//
// Note: Permissions are best-effort and intended to prevent clients from doing unexpected things;
// they're not a hard security boundary, so we allow arbitrary, client-driven overrides.
//
// See the Stainless MCP documentation for more details.
let effectiveMcpOptions = mcpOptions;
const clientPermissionsHeader = req.headers['x-stainless-mcp-client-permissions'];
if (typeof clientPermissionsHeader === 'string') {
try {
const parsed = JSON.parse(clientPermissionsHeader);
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
effectiveMcpOptions = {
...mcpOptions,
...(typeof parsed.allow_http_gets === 'boolean' && { codeAllowHttpGets: parsed.allow_http_gets }),
...(Array.isArray(parsed.allowed_methods) && { codeAllowedMethods: parsed.allowed_methods }),
...(Array.isArray(parsed.blocked_methods) && { codeBlockedMethods: parsed.blocked_methods }),
};
getLogger().info(
{ clientPermissions: parsed },
'Overriding code execution permissions from x-stainless-mcp-client-permissions header',
);
}
} catch (error) {
getLogger().warn({ error }, 'Failed to parse x-stainless-mcp-client-permissions header');
}
}

await initMcpServer({
server: server,
mcpOptions: mcpOptions,
mcpOptions: effectiveMcpOptions,
clientOptions: {
...clientOptions,
...authOptions,
},
stainlessApiKey: stainlessApiKey,
upstreamClientEnvs,
});

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

const redactHeaders = (headers: Record<string, any>) => {
const hiddenHeaders = /auth|cookie|key|token/i;
const hiddenHeaders = /auth|cookie|key|token|x-stainless-mcp-client-envs/i;
const filtered = { ...headers };
Object.keys(filtered).forEach((key) => {
if (hiddenHeaders.test(key)) {
Expand Down
25 changes: 10 additions & 15 deletions packages/mcp-server/src/instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,24 @@ interface InstructionsCacheEntry {

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

// Periodically evict stale entries so the cache doesn't grow unboundedly.
const _cacheCleanupInterval = setInterval(() => {
const now = Date.now();
for (const [key, entry] of instructionsCache) {
if (now - entry.fetchedAt > INSTRUCTIONS_CACHE_TTL_MS) {
instructionsCache.delete(key);
}
}
}, INSTRUCTIONS_CACHE_TTL_MS);

// Don't keep the process alive just for cleanup.
_cacheCleanupInterval.unref();

export async function getInstructions(stainlessApiKey: string | undefined): Promise<string> {
const now = Date.now();
const cacheKey = stainlessApiKey ?? '';
const cached = instructionsCache.get(cacheKey);

if (cached && Date.now() - cached.fetchedAt <= INSTRUCTIONS_CACHE_TTL_MS) {
if (cached && now - cached.fetchedAt <= INSTRUCTIONS_CACHE_TTL_MS) {
return cached.fetchedInstructions;
}

// Evict stale entries so the cache doesn't grow unboundedly.
for (const [key, entry] of instructionsCache) {
if (now - entry.fetchedAt > INSTRUCTIONS_CACHE_TTL_MS) {
instructionsCache.delete(key);
}
}

const fetchedInstructions = await fetchLatestInstructions(stainlessApiKey);
instructionsCache.set(cacheKey, { fetchedInstructions, fetchedAt: Date.now() });
instructionsCache.set(cacheKey, { fetchedInstructions, fetchedAt: now });
return fetchedInstructions;
}

Expand Down
4 changes: 3 additions & 1 deletion packages/mcp-server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const newMcpServer = async (stainlessApiKey: string | undefined) =>
new McpServer(
{
name: 'cas_parser_node_api',
version: '1.10.1',
version: '1.10.2',
},
{
instructions: await getInstructions(stainlessApiKey),
Expand All @@ -37,6 +37,7 @@ export async function initMcpServer(params: {
clientOptions?: ClientOptions;
mcpOptions?: McpOptions;
stainlessApiKey?: string | undefined;
upstreamClientEnvs?: Record<string, string> | undefined;
}) {
const server = params.server instanceof McpServer ? params.server.server : params.server;

Expand Down Expand Up @@ -118,6 +119,7 @@ export async function initMcpServer(params: {
reqContext: {
client,
stainlessApiKey: params.stainlessApiKey ?? params.mcpOptions?.stainlessApiKey,
upstreamClientEnvs: params.upstreamClientEnvs,
},
args,
});
Expand Down
1 change: 1 addition & 0 deletions packages/mcp-server/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export type ToolCallResult = {
export type McpRequestContext = {
client: CasParser;
stainlessApiKey?: string | undefined;
upstreamClientEnvs?: Record<string, string> | undefined;
};

export type HandlerFunction = ({
Expand Down
Loading
Loading