Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ jobs:
URL: https://pkg.stainless.com/s?subpackage=mcp-server
AUTH: ${{ steps.github-oidc.outputs.github_token }}
SHA: ${{ github.sha }}
BUILD_PATH: packages/mcp-server/dist
BASE_PATH: packages/mcp-server
run: ./scripts/utils/upload-artifact.sh
test:
timeout-minutes: 10
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ dist
dist-deno
/*.tgz
.idea/

dist-bundle
*.dxt
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.3.0"
".": "1.4.0"
}
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# Changelog

## 1.4.0 (2025-09-06)

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

### Features

* **mcp:** allow setting logging level ([09e1385](https://github.com/CASParser/cas-parser-node/commit/09e1385c6aca980373b2e306a322ededef76a968))
* **mcp:** expose client options in `streamableHTTPApp` ([8b00452](https://github.com/CASParser/cas-parser-node/commit/8b00452cbb7fce7b4d63e240021c2088f3ff1724))


### Bug Fixes

* **mcp:** fix query options parsing ([c0c48c9](https://github.com/CASParser/cas-parser-node/commit/c0c48c931043e8a4ca2154ea128589b19e0c0d24))


### Chores

* ci build action ([52509dd](https://github.com/CASParser/cas-parser-node/commit/52509ddf57d2452be7194a560a77f497668e75ae))
* **internal:** codegen related update ([fbd7b14](https://github.com/CASParser/cas-parser-node/commit/fbd7b14ecce310840c07c198b070b3546178a2ae))
* **internal:** codegen related update ([328471e](https://github.com/CASParser/cas-parser-node/commit/328471eb93f460fbf4e56139ed85c6565e0c3f72))
* **internal:** update global Error reference ([a1c4a4b](https://github.com/CASParser/cas-parser-node/commit/a1c4a4b62f88fe05201137ae92a934f9248dc237))

## 1.3.0 (2025-08-24)

Full Changelog: [v1.2.0...v1.3.0](https://github.com/CASParser/cas-parser-node/compare/v1.2.0...v1.3.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": "cas-parser-node",
"version": "1.3.0",
"version": "1.4.0",
"description": "The official TypeScript library for the Cas Parser API",
"author": "Cas Parser <sameer@casparser.in>",
"types": "dist/index.d.ts",
Expand Down
24 changes: 24 additions & 0 deletions packages/mcp-server/build
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,27 @@ cp tsconfig.dist-src.json dist/src/tsconfig.json
chmod +x dist/index.js

DIST_PATH=./dist PKG_IMPORT_PATH=cas-parser-node-mcp/ node ../../scripts/utils/postprocess-files.cjs

# mcp bundle
rm -rf dist-bundle cas_parser_node_api.dxt; mkdir dist-bundle

# copy package.json
PKG_JSON_PATH=../../packages/mcp-server/package.json node ../../scripts/utils/make-dist-package-json.cjs > dist-bundle/package.json

# copy files
node scripts/copy-bundle-files.cjs

# install runtime deps
cd dist-bundle
npm install
cd ..

# pack bundle
cp manifest.json dist-bundle

npx dxt pack dist-bundle cas_parser_node_api.dxt

npx dxt sign cas_parser_node_api.dxt --self-signed

# clean up
rm -rf dist-bundle
42 changes: 42 additions & 0 deletions packages/mcp-server/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"dxt_version": "0.1",
"name": "cas-parser-node-mcp",
"version": "1.3.0",
"description": "The official MCP Server for the Cas Parser API",
"author": {
"name": "Cas Parser",
"email": "sameer@casparser.in"
},
"repository": {
"type": "git",
"url": "git+https://github.com/CASParser/cas-parser-node.git"
},
"homepage": "https://github.com/CASParser/cas-parser-node/tree/main/packages/mcp-server#readme",
"documentation": "https://docs.casparser.in/reference",
"server": {
"type": "node",
"entry_point": "${__dirname}/index.js",
"mcp_config": {
"command": "node",
"args": ["${__dirname}/index.js"],
"env": {
"CAS_PARSER_API_KEY": "${user_config.CAS_PARSER_API_KEY}"
}
}
},
"user_config": {
"CAS_PARSER_API_KEY": {
"title": "api_key",
"description": "Your API key for authentication.\nUse `sandbox-with-json-responses` as Sandbox key.\n",
"required": true,
"type": "string"
}
},
"tools_generated": true,
"compatibility": {
"runtimes": {
"node": ">=18.0.0"
}
},
"keywords": ["api"]
}
3 changes: 2 additions & 1 deletion 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.3.0",
"version": "1.4.0",
"description": "The official MCP Server for the Cas Parser API",
"author": "Cas Parser <sameer@casparser.in>",
"types": "dist/index.d.ts",
Expand Down Expand Up @@ -47,6 +47,7 @@
"mcp-server": "dist/index.js"
},
"devDependencies": {
"@anthropic-ai/dxt": "^0.2.6",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.3",
"@types/jest": "^29.4.0",
Expand Down
36 changes: 36 additions & 0 deletions packages/mcp-server/scripts/copy-bundle-files.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const fs = require('fs');
const path = require('path');
const pkgJson = require('../dist-bundle/package.json');

const distDir = path.resolve(__dirname, '..', 'dist');
const distBundleDir = path.resolve(__dirname, '..', 'dist-bundle');
const distBundlePkgJson = path.join(distBundleDir, 'package.json');

async function* walk(dir) {
for await (const d of await fs.promises.opendir(dir)) {
const entry = path.join(dir, d.name);
if (d.isDirectory()) yield* walk(entry);
else if (d.isFile()) yield entry;
}
}

async function copyFiles() {
// copy runtime files
for await (const file of walk(distDir)) {
if (!/[cm]?js$/.test(file)) continue;
const dest = path.join(distBundleDir, path.relative(distDir, file));
await fs.promises.mkdir(path.dirname(dest), { recursive: true });
await fs.promises.copyFile(file, dest);
}

// replace package.json reference with local reference
for (const dep in pkgJson.dependencies) {
if (dep === 'cas-parser-node') {
pkgJson.dependencies[dep] = 'file:../../../dist/';
}
}

await fs.promises.writeFile(distBundlePkgJson, JSON.stringify(pkgJson, null, 2));
}

copyFiles();
8 changes: 5 additions & 3 deletions packages/mcp-server/src/code-tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ import { Endpoint, ContentBlock, Metadata } from './tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';

import { newDenoHTTPWorker } from '@valtown/deno-http-worker';
import { WorkerInput, WorkerError, WorkerSuccess } from './code-tool-types';
import { workerPath } from './code-tool-paths.cjs';

/**
* A tool that runs code against a copy of the SDK.
Expand All @@ -20,7 +18,7 @@ import { workerPath } from './code-tool-paths.cjs';
*
* @param endpoints - The endpoints to include in the list.
*/
export function codeTool(): Endpoint {
export async function codeTool(): Promise<Endpoint> {
const metadata: Metadata = { resource: 'all', operation: 'write', tags: [] };
const tool: Tool = {
name: 'execute',
Expand All @@ -29,6 +27,10 @@ export function codeTool(): Endpoint {
inputSchema: { type: 'object', properties: { code: { type: 'string' } } },
};

// Import dynamically to avoid failing at import time in cases where the environment is not well-supported.
const { newDenoHTTPWorker } = await import('@valtown/deno-http-worker');
const { workerPath } = await import('./code-tool-paths.cjs');

const handler = async (client: CasParser, args: unknown) => {
const baseURLHostname = new URL(client.baseURL).hostname;
const { code } = args as { code: string };
Expand Down
58 changes: 35 additions & 23 deletions packages/mcp-server/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/
import express from 'express';
import { fromError } from 'zod-validation-error/v3';
import { McpOptions, parseQueryOptions } from './options';
import { initMcpServer, newMcpServer } from './server';
import { ClientOptions, initMcpServer, newMcpServer } from './server';
import { parseAuthHeaders } from './headers';

const newServer = (
defaultMcpOptions: McpOptions,
req: express.Request,
res: express.Response,
): McpServer | null => {
const newServer = ({
clientOptions,
mcpOptions: defaultMcpOptions,
req,
res,
}: {
clientOptions: ClientOptions;
mcpOptions: McpOptions;
req: express.Request;
res: express.Response;
}): McpServer | null => {
const server = newMcpServer();

let mcpOptions: McpOptions;
Expand All @@ -35,10 +41,8 @@ const newServer = (
initMcpServer({
server: server,
clientOptions: {
...clientOptions,
...authOptions,
defaultHeaders: {
'X-Stainless-MCP': 'true',
},
},
mcpOptions,
});
Expand All @@ -56,17 +60,19 @@ const newServer = (
return server;
};

const post = (defaultOptions: McpOptions) => async (req: express.Request, res: express.Response) => {
const server = newServer(defaultOptions, req, res);
// If we return null, we already set the authorization error.
if (server === null) return;
const transport = new StreamableHTTPServerTransport({
// Stateless server
sessionIdGenerator: undefined,
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
};
const post =
(options: { clientOptions: ClientOptions; mcpOptions: McpOptions }) =>
async (req: express.Request, res: express.Response) => {
const server = newServer({ ...options, req, res });
// If we return null, we already set the authorization error.
if (server === null) return;
const transport = new StreamableHTTPServerTransport({
// Stateless server
sessionIdGenerator: undefined,
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
};

const get = async (req: express.Request, res: express.Response) => {
res.status(405).json({
Expand All @@ -88,20 +94,26 @@ const del = async (req: express.Request, res: express.Response) => {
});
};

export const streamableHTTPApp = (options: McpOptions): express.Express => {
export const streamableHTTPApp = ({
clientOptions = {},
mcpOptions = {},
}: {
clientOptions?: ClientOptions;
mcpOptions?: McpOptions;
}): express.Express => {
const app = express();
app.set('query parser', 'extended');
app.use(express.json());

app.get('/', get);
app.post('/', post(options));
app.post('/', post({ clientOptions, mcpOptions }));
app.delete('/', del);

return app;
};

export const launchStreamableHTTPServer = async (options: McpOptions, port: number | string | undefined) => {
const app = streamableHTTPApp(options);
const app = streamableHTTPApp({ mcpOptions: options });
const server = app.listen(port);
const address = server.address();

Expand Down
6 changes: 3 additions & 3 deletions packages/mcp-server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ async function main() {
return;
}

const selectedTools = selectToolsOrError(endpoints, options);
const selectedTools = await selectToolsOrError(endpoints, options);

console.error(
`MCP Server starting with ${selectedTools.length} tools:`,
Expand Down Expand Up @@ -47,9 +47,9 @@ function parseOptionsOrError() {
}
}

function selectToolsOrError(endpoints: Endpoint[], options: McpOptions): Endpoint[] {
async function selectToolsOrError(endpoints: Endpoint[], options: McpOptions): Promise<Endpoint[]> {
try {
const includedTools = selectTools(endpoints, options);
const includedTools = await selectTools(endpoints, options);
if (includedTools.length === 0) {
console.error('No tools match the provided filters.');
process.exit(1);
Expand Down
4 changes: 2 additions & 2 deletions packages/mcp-server/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,12 +367,12 @@ export function parseQueryOptions(defaultOptions: McpOptions, query: unknown): M
}

let dynamicTools: boolean | undefined =
queryOptions.no_tools && !queryOptions.no_tools?.includes('dynamic') ? false
queryOptions.no_tools && queryOptions.no_tools?.includes('dynamic') ? false
: queryOptions.tools?.includes('dynamic') ? true
: defaultOptions.includeDynamicTools;

let allTools: boolean | undefined =
queryOptions.no_tools && !queryOptions.no_tools?.includes('all') ? false
queryOptions.no_tools && queryOptions.no_tools?.includes('all') ? false
: queryOptions.tools?.includes('all') ? true
: defaultOptions.includeAllTools;

Expand Down
Loading