Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
042e4c4
chore(mcp-server): improve instructions
stainless-app[bot] Mar 8, 2026
03a21da
chore(internal): update dependencies to address dependabot vulnerabil…
stainless-app[bot] Mar 10, 2026
d5b63e3
chore(internal): bump @modelcontextprotocol/sdk, @hono/node-server, a…
stainless-app[bot] Mar 12, 2026
663e939
chore(internal): make generated MCP servers compatible with Cloudflar…
stainless-app[bot] Mar 14, 2026
c5d6196
chore(internal): support x-stainless-mcp-client-envs header in MCP se…
stainless-app[bot] Mar 14, 2026
73c3ed7
chore(internal): tweak CI branches
stainless-app[bot] Mar 17, 2026
8d8479b
chore(internal): support x-stainless-mcp-client-permissions headers i…
stainless-app[bot] Mar 17, 2026
1b26e0a
chore(internal): update gitignore
stainless-app[bot] Mar 24, 2026
2991a6d
chore(internal): fix MCP server TS errors that occur with required cl…
stainless-app[bot] Mar 24, 2026
f23d9e1
chore(ci): skip lint on metadata-only changes
stainless-app[bot] Mar 25, 2026
bc36a75
chore(internal): support custom-instructions-path flag in MCP servers
stainless-app[bot] Mar 27, 2026
8dea8bd
chore(internal): codegen related update
stainless-app[bot] Mar 28, 2026
ccc0fcd
chore(internal): support local docs search in MCP servers
stainless-app[bot] Mar 28, 2026
167333c
chore(ci): escape input path in publish-npm workflow
stainless-app[bot] Mar 28, 2026
5af2171
chore(mcp-server): add support for session id, forward client info
stainless-app[bot] Mar 31, 2026
5f6d8a5
chore(internal): improve local docs search for MCP servers
stainless-app[bot] Mar 31, 2026
a1a598c
release: 0.1.0-alpha.31
stainless-app[bot] Mar 31, 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
18 changes: 10 additions & 8 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 All @@ -17,7 +19,7 @@ jobs:
timeout-minutes: 10
name: lint
runs-on: ${{ github.repository == 'stainless-sdks/scan-documents-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 @@ -36,7 +38,7 @@ jobs:
timeout-minutes: 5
name: build
runs-on: ${{ github.repository == 'stainless-sdks/scan-documents-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
5 changes: 3 additions & 2 deletions .github/workflows/publish-npm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,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 }}
NPM_TOKEN: ${{ secrets.SCAN_DOCUMENTS_NPM_TOKEN || secrets.NPM_TOKEN }}

- name: Upload MCP Server DXT GitHub release asset
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.prism.log
.stdy.log
node_modules
yarn-error.log
codegen.log
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 @@
{
".": "0.1.0-alpha.30"
".": "0.1.0-alpha.31"
}
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# Changelog

## 0.1.0-alpha.31 (2026-03-31)

Full Changelog: [v0.1.0-alpha.30...v0.1.0-alpha.31](https://github.com/Scan-Documents/node-sdk/compare/v0.1.0-alpha.30...v0.1.0-alpha.31)

### Chores

* **ci:** escape input path in publish-npm workflow ([167333c](https://github.com/Scan-Documents/node-sdk/commit/167333c43ac5a5830123674698abf8ffaaa1ffec))
* **ci:** skip lint on metadata-only changes ([f23d9e1](https://github.com/Scan-Documents/node-sdk/commit/f23d9e14ec9747bcdf9f0cea345261d7e00497fa))
* **internal:** bump @modelcontextprotocol/sdk, @hono/node-server, and minimatch ([d5b63e3](https://github.com/Scan-Documents/node-sdk/commit/d5b63e36a5c6bc96d2c485834a09050b7ce61555))
* **internal:** codegen related update ([8dea8bd](https://github.com/Scan-Documents/node-sdk/commit/8dea8bdf7abb0beec86cb0a69ba5e73d2dd69f90))
* **internal:** fix MCP server TS errors that occur with required client options ([2991a6d](https://github.com/Scan-Documents/node-sdk/commit/2991a6d8dd1d26b6b1d9275b066b0ebc007eec9d))
* **internal:** improve local docs search for MCP servers ([5f6d8a5](https://github.com/Scan-Documents/node-sdk/commit/5f6d8a56a878ff32020b149eeb60561b951883fd))
* **internal:** make generated MCP servers compatible with Cloudflare worker environments ([663e939](https://github.com/Scan-Documents/node-sdk/commit/663e9399aea38becc113376f195b81c46d268b08))
* **internal:** support custom-instructions-path flag in MCP servers ([bc36a75](https://github.com/Scan-Documents/node-sdk/commit/bc36a753ea900b7efaad9e149ed6756e3a07f235))
* **internal:** support local docs search in MCP servers ([ccc0fcd](https://github.com/Scan-Documents/node-sdk/commit/ccc0fcdc0152a171b5a2c9363c67a5f2ad8b4831))
* **internal:** support x-stainless-mcp-client-envs header in MCP servers ([c5d6196](https://github.com/Scan-Documents/node-sdk/commit/c5d619641e44fd5f14e43aa230d05697342433bd))
* **internal:** support x-stainless-mcp-client-permissions headers in MCP servers ([8d8479b](https://github.com/Scan-Documents/node-sdk/commit/8d8479b6ba016c97e8ddbb9f25e0947fffdb79de))
* **internal:** tweak CI branches ([73c3ed7](https://github.com/Scan-Documents/node-sdk/commit/73c3ed73bf25819de55cdbd8c0e327802ac596f4))
* **internal:** update dependencies to address dependabot vulnerabilities ([03a21da](https://github.com/Scan-Documents/node-sdk/commit/03a21da5bf6c12e562a3f27580e244eb01d4d250))
* **internal:** update gitignore ([1b26e0a](https://github.com/Scan-Documents/node-sdk/commit/1b26e0a9c4b98ff889d9610698c06485adb98493))
* **mcp-server:** add support for session id, forward client info ([5af2171](https://github.com/Scan-Documents/node-sdk/commit/5af21714af1ec3d576ceb9f30be44fbd53b8f684))
* **mcp-server:** improve instructions ([042e4c4](https://github.com/Scan-Documents/node-sdk/commit/042e4c4326c5ad56033fea22d82c1b85a2aefe5e))

## 0.1.0-alpha.30 (2026-03-08)

Full Changelog: [v0.1.0-alpha.29...v0.1.0-alpha.30](https://github.com/Scan-Documents/node-sdk/compare/v0.1.0-alpha.29...v0.1.0-alpha.30)
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": "scan-documents",
"version": "0.1.0-alpha.30",
"version": "0.1.0-alpha.31",
"description": "The official TypeScript library for the Scan Documents API",
"author": "Scan Documents <support@scan-documents.com>",
"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": "scan-documents-mcp",
"version": "0.1.0-alpha.30",
"version": "0.1.0-alpha.31",
"description": "The official MCP Server for the Scan Documents API",
"author": {
"name": "Scan Documents",
Expand Down
18 changes: 11 additions & 7 deletions packages/mcp-server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "scan-documents-mcp",
"version": "0.1.0-alpha.30",
"version": "0.1.0-alpha.31",
"description": "The official MCP Server for the Scan Documents API",
"author": "Scan Documents <support@scan-documents.com>",
"types": "dist/index.d.ts",
Expand All @@ -26,18 +26,22 @@
"format": "prettier --write --cache --cache-strategy metadata . !dist",
"prepare": "npm run build",
"tsn": "ts-node -r tsconfig-paths/register",
"lint": "eslint --ext ts,js .",
"fix": "eslint --fix --ext ts,js ."
"lint": "eslint .",
"fix": "eslint --fix ."
},
"dependencies": {
"scan-documents": "file:../../dist/",
"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",
"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 All @@ -62,9 +66,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');
}
43 changes: 27 additions & 16 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 @@ -147,19 +142,23 @@ const remoteStainlessHandler = async ({

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

const localClientEnvs = {
SCAN_DOCUMENTS_API_KEY: requireValue(
readEnv('SCAN_DOCUMENTS_API_KEY') ?? client.apiKey,
'set SCAN_DOCUMENTS_API_KEY environment variable or provide apiKey client option',
),
SCAN_DOCUMENTS_BASE_URL: readEnv('SCAN_DOCUMENTS_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({
SCAN_DOCUMENTS_API_KEY: requireValue(
readEnv('SCAN_DOCUMENTS_API_KEY') ?? client.apiKey,
'set SCAN_DOCUMENTS_API_KEY environment variable or provide apiKey client option',
),
SCAN_DOCUMENTS_BASE_URL: readEnv('SCAN_DOCUMENTS_BASE_URL') ?? client.baseURL ?? undefined,
}),
'x-stainless-mcp-client-envs': JSON.stringify(mergedClientEnvs),
},
body: JSON.stringify({
project_name: 'scan-documents',
Expand Down Expand Up @@ -202,6 +201,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 @@ -263,6 +269,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 @@ -272,13 +281,15 @@ const localDenoHandler = async ({
reject(new Error(`Worker exited with code ${exitCode}`));
});

const opts: ClientOptions = {
baseURL: client.baseURL,
apiKey: client.apiKey,
// Strip null/undefined values so that the worker SDK client can fall back to
// reading from environment variables (including any upstreamClientEnvs).
const opts = {
...(client.baseURL != null ? { baseURL: client.baseURL } : undefined),
...(client.apiKey != null ? { apiKey: client.apiKey } : undefined),
defaultHeaders: {
'X-Stainless-MCP': 'true',
},
};
} satisfies Partial<ClientOptions> as ClientOptions;

const req = worker.request(
'http://localhost',
Expand Down
57 changes: 48 additions & 9 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 All @@ -13,7 +14,8 @@ export const metadata: Metadata = {

export const tool: Tool = {
name: 'search_docs',
description: 'Search for documentation for how to use the client to interact with the API.',
description:
'Search SDK documentation to find methods, parameters, and usage examples for interacting with the API. Use this before writing code when you need to discover the right approach.',
inputSchema: {
type: 'object',
properties: {
Expand Down Expand Up @@ -42,20 +44,41 @@ export const tool: Tool = {
const docsSearchURL =
process.env['DOCS_SEARCH_URL'] || 'https://api.stainless.com/api/projects/scan-documents/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 @@ -93,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 };
Loading
Loading