Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
13 changes: 11 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '20'
node-version: '22'

- name: Bootstrap
run: ./scripts/bootstrap
Expand All @@ -46,7 +46,7 @@ jobs:
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '20'
node-version: '22'

- name: Bootstrap
run: ./scripts/bootstrap
Expand All @@ -68,6 +68,15 @@ jobs:
AUTH: ${{ steps.github-oidc.outputs.github_token }}
SHA: ${{ github.sha }}
run: ./scripts/utils/upload-artifact.sh

- name: Upload MCP Server tarball
if: github.repository == 'stainless-sdks/cas-parser-typescript'
env:
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
run: ./scripts/utils/upload-artifact.sh
test:
timeout-minutes: 10
name: test
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.2.0"
".": "1.3.0"
}
2 changes: 1 addition & 1 deletion .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 5
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cas-parser%2Fcas-parser-b7fdba3d3f97c7debc22c7ca30b828bce81bcd64648df8c94029b27a3321ebb9.yml
openapi_spec_hash: 03f1315f1d32ada42445ca920f047dff
config_hash: d9c1f7b95d5659724df3e026c4fab291
config_hash: cb5d75abef6264b5d86448caf7295afa
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# Changelog

## 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)

### Features

* **mcp:** add code execution tool ([84904a7](https://github.com/CASParser/cas-parser-node/commit/84904a7f558053aa211b8f483a7b4f6c2b13b8f6))
* **mcp:** add option to infer mcp client ([c190ccd](https://github.com/CASParser/cas-parser-node/commit/c190ccdd5cb6342dc620c26a3d06c0514f693a37))
* **mcp:** parse query string as mcp client options in mcp server ([1f6e085](https://github.com/CASParser/cas-parser-node/commit/1f6e085c4e7e9edf497585587fc3e6603b53a3e3))


### Chores

* add package to package.json ([9a166fa](https://github.com/CASParser/cas-parser-node/commit/9a166fa5baf70f7d0114d1c4dce14a1ac8bf956f))
* **client:** qualify global Blob ([902939b](https://github.com/CASParser/cas-parser-node/commit/902939be47f74826e00ca6b8f73e94f969d96952))
* **internal:** codegen related update ([033f0ef](https://github.com/CASParser/cas-parser-node/commit/033f0ef5142b7f37ec8ccf9c276d5bd9bb3dbe76))
* **internal:** codegen related update ([8463673](https://github.com/CASParser/cas-parser-node/commit/8463673b3bdd581f5d58b7ed18fb782b42cb94d9))
* **internal:** make mcp-server publishing public by defaut ([4bbdb37](https://github.com/CASParser/cas-parser-node/commit/4bbdb3749a70bc35a4e9fdd4b9763a6f79f78a24))
* **internal:** refactor array check ([00cc94f](https://github.com/CASParser/cas-parser-node/commit/00cc94fb85746fc3a79136d22214864673a3c93f))
* **mcp:** add cors to oauth metadata route ([3833155](https://github.com/CASParser/cas-parser-node/commit/3833155c5d3bcca5a7bf6ca0f513e8365db316cc))
* **mcp:** update package.json ([02b1e54](https://github.com/CASParser/cas-parser-node/commit/02b1e541dcf8d3a253c91f55266be3edb3db60e8))
* **mcp:** update types ([db1c76c](https://github.com/CASParser/cas-parser-node/commit/db1c76c3d2a76789d4dc3e5de57a875b7fa091db))
* update CI script ([b359ebc](https://github.com/CASParser/cas-parser-node/commit/b359ebce43e80eead9d0ff40b45a13e6af4c82b1))

## 1.2.0 (2025-08-18)

Full Changelog: [v1.1.0...v1.2.0](https://github.com/CASParser/cas-parser-node/compare/v1.1.0...v1.2.0)
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cas-parser-node",
"version": "1.2.0",
"version": "1.3.0",
"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 @@ -44,8 +44,9 @@
"publint": "^0.2.12",
"ts-jest": "^29.1.0",
"ts-node": "^10.5.0",
"tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.8/tsc-multi.tgz",
"tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz",
"tsconfig-paths": "^4.0.0",
"tslib": "^2.8.1",
"typescript": "5.8.3",
"typescript-eslint": "8.31.1"
},
Expand Down
13 changes: 13 additions & 0 deletions packages/mcp-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,19 @@ A configuration JSON for this server might look like this, assuming the server i
}
```

The command-line arguments for filtering tools and specifying clients can also be used as query parameters in the URL.
For example, to exclude specific tools while including others, use the URL:

```
http://localhost:3000?resource=cards&resource=accounts&no_tool=create_cards
```

Or, to configure for the Cursor client, with a custom max tool name length, use the URL:

```
http://localhost:3000?client=cursor&capability=tool-name-length%3D40
```

## Importing the tools and server individually

```js
Expand Down
19 changes: 14 additions & 5 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.2.0",
"version": "1.3.0",
"description": "The official MCP Server for the Cas Parser API",
"author": "Cas Parser <sameer@casparser.in>",
"types": "dist/index.d.ts",
Expand All @@ -15,6 +15,9 @@
"license": "Apache-2.0",
"packageManager": "yarn@1.22.22",
"private": false,
"publishConfig": {
"access": "public"
},
"scripts": {
"test": "jest",
"build": "bash ./build",
Expand All @@ -28,20 +31,26 @@
},
"dependencies": {
"cas-parser-node": "file:../../dist/",
"@cloudflare/cabidela": "^0.2.4",
"@modelcontextprotocol/sdk": "^1.11.5",
"@valtown/deno-http-worker": "^0.0.21",
"cors": "^2.8.5",
"express": "^5.1.0",
"jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz",
"qs": "^6.14.0",
"yargs": "^17.7.2",
"@cloudflare/cabidela": "^0.2.4",
"zod": "^3.25.20",
"zod-to-json-schema": "^3.24.5"
"zod-to-json-schema": "^3.24.5",
"zod-validation-error": "^4.0.1"
},
"bin": {
"mcp-server": "dist/index.js"
},
"devDependencies": {
"@types/jest": "^29.4.0",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.3",
"@types/jest": "^29.4.0",
"@types/qs": "^6.14.0",
"@types/yargs": "^17.0.8",
"@typescript-eslint/eslint-plugin": "8.31.1",
"@typescript-eslint/parser": "8.31.1",
Expand All @@ -53,7 +62,7 @@
"ts-jest": "^29.1.0",
"ts-morph": "^19.0.0",
"ts-node": "^10.5.0",
"tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.8/tsc-multi.tgz",
"tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz",
"tsconfig-paths": "^4.0.0",
"typescript": "5.8.3"
},
Expand Down
3 changes: 3 additions & 0 deletions packages/mcp-server/src/code-tool-paths.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

export const workerPath = require.resolve('./code-tool-worker.mjs');
14 changes: 14 additions & 0 deletions packages/mcp-server/src/code-tool-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { ClientOptions } from 'cas-parser-node';

export type WorkerInput = {
opts: ClientOptions;
code: string;
};
export type WorkerSuccess = {
result: unknown | null;
logLines: string[];
errLines: string[];
};
export type WorkerError = { message: string | undefined };
46 changes: 46 additions & 0 deletions packages/mcp-server/src/code-tool-worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import util from 'node:util';
import { WorkerInput, WorkerSuccess, WorkerError } from './code-tool-types';
import { CasParser } from 'cas-parser-node';

const fetch = async (req: Request): Promise<Response> => {
const { opts, code } = (await req.json()) as WorkerInput;
const client = new CasParser({
...opts,
});

const logLines: string[] = [];
const errLines: string[] = [];
const console = {
log: (...args: unknown[]) => {
logLines.push(util.format(...args));
},
error: (...args: unknown[]) => {
errLines.push(util.format(...args));
},
};
try {
let run_ = async (client: any) => {};
eval(`
${code}
run_ = run;
`);
const result = await run_(client);
return Response.json({
result,
logLines,
errLines,
} satisfies WorkerSuccess);
} catch (e) {
const message = e instanceof Error ? e.message : undefined;
return Response.json(
{
message,
} satisfies WorkerError,
{ status: 400, statusText: 'Code execution error' },
);
}
};

export default { fetch };
144 changes: 144 additions & 0 deletions packages/mcp-server/src/code-tool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { dirname } from 'node:path';
import { pathToFileURL } from 'node:url';
import CasParser, { ClientOptions } from 'cas-parser-node';
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.
*
* Instead of exposing every endpoint as it's own tool, which uses up too many tokens for LLMs to use at once,
* we expose a single tool that can be used to search for endpoints by name, resource, operation, or tag, and then
* a generic endpoint that can be used to invoke any endpoint with the provided arguments.
*
* @param endpoints - The endpoints to include in the list.
*/
export function codeTool(): Endpoint {
const metadata: Metadata = { resource: 'all', operation: 'write', tags: [] };
const tool: Tool = {
name: 'execute',
description:
'Runs Typescript code to interact with the API.\nYou are a skilled programmer writing code to interface with the service.\nDefine an async function named "run" that takes a single parameter of an initialized client, and it will be run.\nDo not initialize a client, but instead use the client that you are given as a parameter.\nYou will be returned anything that your function returns, plus the results of any console.log statements.\nIf any code triggers an error, the tool will return an error response, so you do not need to add error handling unless you want to output something more helpful than the raw error.\nIt is not necessary to add comments to code, unless by adding those comments you believe that you can generate better code.\nThis code will run in a container, and you will not be able to use fetch or otherwise interact with the network calls other than through the client you are given.\nAny variables you define won\'t live between successive uses of this call, so make sure to return or log any data you might need later.',
inputSchema: { type: 'object', properties: { code: { type: 'string' } } },
};

const handler = async (client: CasParser, args: unknown) => {
const baseURLHostname = new URL(client.baseURL).hostname;
const { code } = args as { code: string };

const worker = await newDenoHTTPWorker(pathToFileURL(workerPath), {
runFlags: [
`--node-modules-dir=manual`,
`--allow-read=code-tool-worker.mjs,${workerPath.replace(/([\/\\]node_modules)[\/\\].+$/, '$1')}/`,
`--allow-net=${baseURLHostname}`,
// Allow environment variables because instantiating the client will try to read from them,
// even though they are not set.
'--allow-env',
],
printOutput: true,
spawnOptions: {
cwd: dirname(workerPath),
},
});

try {
const resp = await new Promise<Response>((resolve, reject) => {
worker.addEventListener('exit', (exitCode) => {
reject(new Error(`Worker exited with code ${exitCode}`));
});

const opts: ClientOptions = {
baseURL: client.baseURL,
apiKey: client.apiKey,
defaultHeaders: {
'X-Stainless-MCP': 'true',
},
};

const req = worker.request(
'http://localhost',
{
headers: {
'content-type': 'application/json',
},
method: 'POST',
},
(resp) => {
const body: Uint8Array[] = [];
resp.on('error', (err) => {
reject(err);
});
resp.on('data', (chunk) => {
body.push(chunk);
});
resp.on('end', () => {
resolve(
new Response(Buffer.concat(body).toString(), {
status: resp.statusCode ?? 200,
headers: resp.headers as any,
}),
);
});
},
);

const body = JSON.stringify({
opts,
code,
} satisfies WorkerInput);

req.write(body, (err) => {
if (err !== null && err !== undefined) {
reject(err);
}
});

req.end();
});

if (resp.status === 200) {
const { result, logLines, errLines } = (await resp.json()) as WorkerSuccess;
const returnOutput: ContentBlock | null =
result === null ? null
: result === undefined ? null
: {
type: 'text',
text: typeof result === 'string' ? (result as string) : JSON.stringify(result),
};
const logOutput: ContentBlock | null =
logLines.length === 0 ?
null
: {
type: 'text',
text: logLines.join('\n'),
};
const errOutput: ContentBlock | null =
errLines.length === 0 ?
null
: {
type: 'text',
text: 'Error output:\n' + errLines.join('\n'),
};
return {
content: [returnOutput, logOutput, errOutput].filter((block) => block !== null),
};
} else {
const { message } = (await resp.json()) as WorkerError;
throw new Error(message);
}
} catch (e) {
throw e;
} finally {
worker.terminate();
}
};

return { metadata, tool, handler };
}
Loading