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
954b27e
feat(api): add override_company_name parameter to payments ACH
stainless-app[bot] Mar 24, 2026
10dfba6
chore(ci): skip lint on metadata-only changes
stainless-app[bot] Mar 24, 2026
4962972
codegen metadata
stainless-app[bot] Mar 24, 2026
c36c997
docs(api): clarify nature_of_business and qr_code_url field constraints
stainless-app[bot] Mar 25, 2026
21854f4
chore(internal): support custom-instructions-path flag in MCP servers
stainless-app[bot] Mar 26, 2026
827a434
chore(internal): codegen related update
stainless-app[bot] Mar 27, 2026
c7f9b6a
chore(internal): support local docs search in MCP servers
stainless-app[bot] Mar 27, 2026
699d85e
chore(ci): escape input path in publish-npm workflow
stainless-app[bot] Mar 27, 2026
9f3f1cb
chore(mcp-server): add support for session id, forward client info
stainless-app[bot] Mar 30, 2026
231f6a0
chore(internal): improve local docs search for MCP servers
stainless-app[bot] Mar 30, 2026
f3658af
chore(internal): improve local docs search for MCP servers
stainless-app[bot] Mar 31, 2026
08c0b61
fix(internal): gitignore generated `oidc` dir
stainless-app[bot] Mar 31, 2026
d004ede
feat(api): add decline count attributes to auth-rules v2
stainless-app[bot] Mar 31, 2026
4f94af4
chore(internal): support type annotations when running MCP in local e…
stainless-app[bot] Apr 1, 2026
2e1174a
chore(mcp-server): log client info
stainless-app[bot] Apr 2, 2026
e3813f7
feat(api): add statement_totals field to Statement response
stainless-app[bot] Apr 2, 2026
9c8e9af
chore(internal): use link instead of file in MCP server package.json …
stainless-app[bot] Apr 2, 2026
cd92ad7
chore(internal): fix MCP docker image builds in yarn projects
stainless-app[bot] Apr 2, 2026
9cb60de
release: 0.136.0
stainless-app[bot] Apr 2, 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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
timeout-minutes: 10
name: lint
runs-on: ${{ github.repository == 'stainless-sdks/lithic-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
steps:
- uses: actions/checkout@v6

Expand All @@ -38,7 +38,7 @@ jobs:
timeout-minutes: 5
name: build
runs-on: ${{ github.repository == 'stainless-sdks/lithic-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
permissions:
contents: read
id-token: write
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/publish-npm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@ jobs:

- name: Publish to NPM
run: |
if [ -n "${{ github.event.inputs.path }}" ]; then
PATHS_RELEASED='[\"${{ github.event.inputs.path }}\"]'
if [ -n "$INPUT_PATH" ]; then
PATHS_RELEASED="[\"$INPUT_PATH\"]"
else
PATHS_RELEASED='[\".\", \"packages/mcp-server\"]'
fi
yarn tsn scripts/publish-packages.ts "{ \"paths_released\": \"$PATHS_RELEASED\" }"
env:
INPUT_PATH: ${{ github.event.inputs.path }}

- name: Upload MCP Server DXT GitHub release asset
run: |
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ dist-deno
.eslintcache
dist-bundle
*.mcpb
oidc
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.135.0"
".": "0.136.0"
}
4 changes: 2 additions & 2 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 190
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-17c04dd1b0508b380c21e3acc3d4cd1e86b590f81d14fa26d1973b236f660e38.yml
openapi_spec_hash: f8ddee07358d2c938450a6889fbf7940
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-bf54d7063b11e1b1656e00d02438f34f87938a9fdbdd5b980fce3cfae3dabffa.yml
openapi_spec_hash: c6efbc9d3105fa48f76ebb095b887e08
config_hash: edbdfefeb0d3d927c2f9fe3402793215
36 changes: 36 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,41 @@
# Changelog

## 0.136.0 (2026-04-02)

Full Changelog: [v0.135.0...v0.136.0](https://github.com/lithic-com/lithic-node/compare/v0.135.0...v0.136.0)

### Features

* **api:** add decline count attributes to auth-rules v2 ([d004ede](https://github.com/lithic-com/lithic-node/commit/d004ede97fda3a5840c8843a648fe5a3977d2d30))
* **api:** add override_company_name parameter to payments ACH ([954b27e](https://github.com/lithic-com/lithic-node/commit/954b27e3fb8dd7b8c55ea57b42d878094efbe3d8))
* **api:** add statement_totals field to Statement response ([e3813f7](https://github.com/lithic-com/lithic-node/commit/e3813f70cf8e408de65ec058a39b137cd74c2dfe))


### Bug Fixes

* **internal:** gitignore generated `oidc` dir ([08c0b61](https://github.com/lithic-com/lithic-node/commit/08c0b61957f3be0c9c52196cf5af017b136d64ee))


### Chores

* **ci:** escape input path in publish-npm workflow ([699d85e](https://github.com/lithic-com/lithic-node/commit/699d85e576bdb3d6c25f868c5b6bba2e3772598b))
* **ci:** skip lint on metadata-only changes ([10dfba6](https://github.com/lithic-com/lithic-node/commit/10dfba6a8f96b1df7d76885f0a762f50b2ada322))
* **internal:** codegen related update ([827a434](https://github.com/lithic-com/lithic-node/commit/827a4344ef5a4e6a47a58564a69373d1135dedcf))
* **internal:** fix MCP docker image builds in yarn projects ([cd92ad7](https://github.com/lithic-com/lithic-node/commit/cd92ad7fd5d497ae09f9c90fb26171053f89a903))
* **internal:** improve local docs search for MCP servers ([f3658af](https://github.com/lithic-com/lithic-node/commit/f3658af5dfea2fa5b42c9471054945481153ef99))
* **internal:** improve local docs search for MCP servers ([231f6a0](https://github.com/lithic-com/lithic-node/commit/231f6a037beea43f90512f3eb6bee560a0233a45))
* **internal:** support custom-instructions-path flag in MCP servers ([21854f4](https://github.com/lithic-com/lithic-node/commit/21854f4bfde44d17f4f22a0f25913f7e91d87c21))
* **internal:** support local docs search in MCP servers ([c7f9b6a](https://github.com/lithic-com/lithic-node/commit/c7f9b6a3ef5ebd16763c7f17b9c93b407e32a4b2))
* **internal:** support type annotations when running MCP in local execution mode ([4f94af4](https://github.com/lithic-com/lithic-node/commit/4f94af4683c5c6116e230e95388685066063483b))
* **internal:** use link instead of file in MCP server package.json files ([9c8e9af](https://github.com/lithic-com/lithic-node/commit/9c8e9af13c7acbda7f78121bd6097e8d0665e1e5))
* **mcp-server:** add support for session id, forward client info ([9f3f1cb](https://github.com/lithic-com/lithic-node/commit/9f3f1cbbb34756d4d92ef000e9554a7580a775d8))
* **mcp-server:** log client info ([2e1174a](https://github.com/lithic-com/lithic-node/commit/2e1174ac9aa88945719d45758ea713ebc5c8eaa8))


### Documentation

* **api:** clarify nature_of_business and qr_code_url field constraints ([c36c997](https://github.com/lithic-com/lithic-node/commit/c36c99779ede1d40967ed1763e5124e2f38c3db1))

## 0.135.0 (2026-03-23)

Full Changelog: [v0.134.0...v0.135.0](https://github.com/lithic-com/lithic-node/compare/v0.134.0...v0.135.0)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lithic",
"version": "0.135.0",
"version": "0.136.0",
"description": "The official TypeScript library for the Lithic API",
"author": "Lithic <sdk-feedback@lithic.com>",
"types": "dist/index.d.ts",
Expand Down
4 changes: 3 additions & 1 deletion packages/mcp-server/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ COPY . .

# Install all dependencies and build everything
RUN yarn install --frozen-lockfile && \
yarn build
yarn build && \
# Remove the symlink to the SDK so it doesn't interfere with the explicit COPY below
rm -Rf packages/mcp-server/node_modules/lithic

FROM denoland/deno:alpine-2.7.1

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": "lithic-mcp",
"version": "0.135.0",
"version": "0.136.0",
"description": "The official MCP Server for the Lithic API",
"author": {
"name": "Lithic",
Expand Down
5 changes: 3 additions & 2 deletions packages/mcp-server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lithic-mcp",
"version": "0.135.0",
"version": "0.136.0",
"description": "The official MCP Server for the Lithic API",
"author": "Lithic <sdk-feedback@lithic.com>",
"types": "dist/index.d.ts",
Expand Down Expand Up @@ -30,7 +30,7 @@
"fix": "eslint --fix ."
},
"dependencies": {
"lithic": "file:../../dist/",
"lithic": "link:../../dist/",
"ajv": "^8.18.0",
"@cloudflare/cabidela": "^0.2.4",
"@hono/node-server": "^1.19.10",
Expand All @@ -41,6 +41,7 @@
"cors": "^2.8.5",
"express": "^5.1.0",
"fuse.js": "^7.1.0",
"minisearch": "^7.2.0",
"jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.8/jq-web.tar.gz",
"pino": "^10.3.1",
"pino-http": "^11.0.0",
Expand Down
12 changes: 10 additions & 2 deletions packages/mcp-server/src/code-tool-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import ts from 'typescript';
import { WorkerOutput } from './code-tool-types';
import { Lithic, ClientOptions } from 'lithic';

async function tseval(code: string) {
return import('data:application/typescript;charset=utf-8;base64,' + Buffer.from(code).toString('base64'));
}

function getRunFunctionSource(code: string): {
type: 'declaration' | 'expression';
client: string | undefined;
Expand Down Expand Up @@ -428,7 +432,9 @@ const fetch = async (req: Request): Promise<Response> => {

const log_lines: string[] = [];
const err_lines: string[] = [];
const console = {
const originalConsole = globalThis.console;
globalThis.console = {
...originalConsole,
log: (...args: unknown[]) => {
log_lines.push(util.format(...args));
},
Expand All @@ -438,7 +444,7 @@ const fetch = async (req: Request): Promise<Response> => {
};
try {
let run_ = async (client: any) => {};
eval(`${code}\nrun_ = run;`);
run_ = (await tseval(`${code}\nexport default run;`)).default;
const result = await run_(makeSdkProxy(client, { path: ['client'] }));
return Response.json({
is_error: false,
Expand All @@ -456,6 +462,8 @@ const fetch = async (req: Request): Promise<Response> => {
} satisfies WorkerOutput,
{ status: 400, statusText: 'Code execution error' },
);
} finally {
globalThis.console = originalConsole;
}
};

Expand Down
54 changes: 46 additions & 8 deletions packages/mcp-server/src/docs-search-tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { Metadata, McpRequestContext, asTextContentResult } from './types';
import { getLogger } from './logger';
import type { LocalDocsSearch } from './local-docs-search';

export const metadata: Metadata = {
resource: 'all',
Expand Down Expand Up @@ -43,20 +44,41 @@ export const tool: Tool = {
const docsSearchURL =
process.env['DOCS_SEARCH_URL'] || 'https://api.stainless.com/api/projects/lithic/docs/search';

export const handler = async ({
reqContext,
args,
}: {
reqContext: McpRequestContext;
args: Record<string, unknown> | undefined;
}) => {
let _localSearch: LocalDocsSearch | undefined;

export function setLocalSearch(search: LocalDocsSearch): void {
_localSearch = search;
}

async function searchLocal(args: Record<string, unknown>): Promise<unknown> {
if (!_localSearch) {
throw new Error('Local search not initialized');
}

const query = (args['query'] as string) ?? '';
const language = (args['language'] as string) ?? 'typescript';
const detail = (args['detail'] as string) ?? 'default';

return _localSearch.search({
query,
language,
detail,
maxResults: 5,
}).results;
}

async function searchRemote(args: Record<string, unknown>, reqContext: McpRequestContext): Promise<unknown> {
const body = args as any;
const query = new URLSearchParams(body).toString();

const startTime = Date.now();
const result = await fetch(`${docsSearchURL}?${query}`, {
headers: {
...(reqContext.stainlessApiKey && { Authorization: reqContext.stainlessApiKey }),
...(reqContext.mcpSessionId && { 'x-stainless-mcp-session-id': reqContext.mcpSessionId }),
...(reqContext.mcpClientInfo && {
'x-stainless-mcp-client-info': JSON.stringify(reqContext.mcpClientInfo),
}),
},
});

Expand Down Expand Up @@ -94,7 +116,23 @@ export const handler = async ({
},
'Got docs search result',
);
return asTextContentResult(resultBody);
return resultBody;
}

export const handler = async ({
reqContext,
args,
}: {
reqContext: McpRequestContext;
args: Record<string, unknown> | undefined;
}) => {
const body = args ?? {};

if (_localSearch) {
return asTextContentResult(await searchLocal(body));
}

return asTextContentResult(await searchRemote(body, reqContext));
};

export default { metadata, tool, handler };
28 changes: 27 additions & 1 deletion packages/mcp-server/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ const newServer = async ({
res: express.Response;
}): Promise<McpServer | null> => {
const stainlessApiKey = getStainlessApiKey(req, mcpOptions);
const server = await newMcpServer(stainlessApiKey);
const customInstructionsPath = mcpOptions.customInstructionsPath;
const server = await newMcpServer({ stainlessApiKey, customInstructionsPath });

const authOptions = parseClientAuthHeaders(req, false);

Expand Down Expand Up @@ -68,6 +69,11 @@ const newServer = async ({
}
}

const mcpClientInfo =
typeof req.body?.params?.clientInfo?.name === 'string' ?
{ name: req.body.params.clientInfo.name, version: String(req.body.params.clientInfo.version ?? '') }
: undefined;

await initMcpServer({
server: server,
mcpOptions: effectiveMcpOptions,
Expand All @@ -77,8 +83,14 @@ const newServer = async ({
},
stainlessApiKey: stainlessApiKey,
upstreamClientEnvs,
mcpSessionId: (req as any).mcpSessionId,
mcpClientInfo,
});

if (mcpClientInfo) {
getLogger().info({ mcpSessionId: (req as any).mcpSessionId, mcpClientInfo }, 'MCP client connected');
}

return server;
};

Expand Down Expand Up @@ -134,9 +146,23 @@ export const streamableHTTPApp = ({
const app = express();
app.set('query parser', 'extended');
app.use(express.json());
app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
const existing = req.headers['mcp-session-id'];
const sessionId = (Array.isArray(existing) ? existing[0] : existing) || crypto.randomUUID();
(req as any).mcpSessionId = sessionId;
const origWriteHead = res.writeHead.bind(res);
res.writeHead = function (statusCode: number, ...rest: any[]) {
res.setHeader('mcp-session-id', sessionId);
return origWriteHead(statusCode, ...rest);
} as typeof res.writeHead;
next();
});
app.use(
pinoHttp({
logger: getLogger(),
customProps: (req) => ({
mcpSessionId: (req as any).mcpSessionId,
}),
customLogLevel: (req, res) => {
if (res.statusCode >= 500) {
return 'error';
Expand Down
31 changes: 27 additions & 4 deletions packages/mcp-server/src/instructions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import fs from 'fs/promises';
import { readEnv } from './util';
import { getLogger } from './logger';

Expand All @@ -12,9 +13,15 @@ interface InstructionsCacheEntry {

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

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

if (cached && now - cached.fetchedAt <= INSTRUCTIONS_CACHE_TTL_MS) {
Expand All @@ -28,12 +35,28 @@ export async function getInstructions(stainlessApiKey: string | undefined): Prom
}
}

const fetchedInstructions = await fetchLatestInstructions(stainlessApiKey);
let fetchedInstructions: string;

if (customInstructionsPath) {
fetchedInstructions = await fetchLatestInstructionsFromFile(customInstructionsPath);
} else {
fetchedInstructions = await fetchLatestInstructionsFromApi(stainlessApiKey);
}

instructionsCache.set(cacheKey, { fetchedInstructions, fetchedAt: now });
return fetchedInstructions;
}

async function fetchLatestInstructions(stainlessApiKey: string | undefined): Promise<string> {
async function fetchLatestInstructionsFromFile(path: string): Promise<string> {
try {
return await fs.readFile(path, 'utf-8');
} catch (error) {
getLogger().error({ error, path }, 'Error fetching instructions from file');
throw error;
}
}

async function fetchLatestInstructionsFromApi(stainlessApiKey: string | undefined): Promise<string> {
// Setting the stainless API key is optional, but may be required
// to authenticate requests to the Stainless API.
const response = await fetch(
Expand Down
Loading
Loading