Skip to content

Commit efe94d9

Browse files
feat(sdk): added npm MCP server (#30)
1 parent c2d51ba commit efe94d9

20 files changed

+652
-4
lines changed

.github/workflows/publish-npm.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
name: Publish NPM
55
on:
66
workflow_dispatch:
7+
inputs:
8+
path:
9+
description: The path to run the release in, e.g. '.' or 'packages/mcp-server'
10+
required: true
711

812
release:
913
types: [published]
@@ -27,6 +31,11 @@ jobs:
2731
2832
- name: Publish to NPM
2933
run: |
30-
bash ./bin/publish-npm
34+
if [ -n "${{ github.event.inputs.path }}" ]; then
35+
PATHS_RELEASED='[\"${{ github.event.inputs.path }}\"]'
36+
else
37+
PATHS_RELEASED='[\".\", \"packages/mcp-server\"]'
38+
fi
39+
yarn tsn scripts/publish-packages.ts "{ \"paths_released\": \"$PATHS_RELEASED\" }"
3140
env:
3241
NPM_TOKEN: ${{ secrets.ISAACUS_NPM_TOKEN || secrets.NPM_TOKEN }}

.stats.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 2
22
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-861e8a85f0fb73cf4b7fc6c2b27722072ff33109459e90c17be24af15dfcbd0c.yml
33
openapi_spec_hash: 644a0383600633ee604bb1e5b9ca025d
4-
config_hash: 2bc262108dc3b065c16da5bb85c1e282
4+
config_hash: 1d603d50b7183a492ad6df5f728a1863

eslint.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export default tseslint.config(
3434
},
3535
},
3636
{
37-
files: ['tests/**', 'examples/**'],
37+
files: ['tests/**', 'examples/**', 'packages/**'],
3838
rules: {
3939
'no-restricted-imports': 'off',
4040
},

packages/mcp-server/README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Isaacus TypeScript MCP Server
2+
3+
It is generated with [Stainless](https://www.stainless.com/).
4+
5+
## Installation
6+
7+
### Via Claude Desktop
8+
9+
See [the user guide](https://modelcontextprotocol.io/quickstart/user) for setup.
10+
11+
Once it's set up, add your MCP server to your `claude_desktop_config.json` file to enable it.
12+
13+
The configuration file should be at:
14+
15+
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
16+
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
17+
18+
Add the following value to your `mcpServers` section. Make sure to provide any necessary environment variables (like API keys) as well.
19+
20+
```json
21+
{
22+
"mcpServers": {
23+
"isaacus_api": {
24+
"command": "npx",
25+
"args": ["-y", "isaacus-mcp"],
26+
"env": {
27+
"ISAACUS_API_KEY": "My API Key"
28+
}
29+
}
30+
}
31+
}
32+
```

packages/mcp-server/build

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/env bash
2+
set -exuo pipefail
3+
4+
rm -rf dist; mkdir dist
5+
6+
# Copy src to dist/src and build from dist/src into dist, so that
7+
# the source map for index.js.map will refer to ./src/index.ts etc
8+
cp -rp src README.md dist
9+
10+
for file in LICENSE; do
11+
if [ -e "../../${file}" ]; then cp "../../${file}" dist; fi
12+
done
13+
14+
for file in CHANGELOG.md; do
15+
if [ -e "${file}" ]; then cp "${file}" dist; fi
16+
done
17+
18+
# this converts the export map paths for the dist directory
19+
# and does a few other minor things
20+
PKG_JSON_PATH=../../packages/mcp-server/package.json node ../../scripts/utils/make-dist-package-json.cjs > dist/package.json
21+
22+
# updates the `isaacus` dependency to point to NPM
23+
node scripts/postprocess-dist-package-json.cjs
24+
25+
# build to .js/.mjs/.d.ts files
26+
npm exec tsc-multi
27+
# we need to add exports = module.exports = Anthropic TypeScript to index.js;
28+
# No way to get that from index.ts because it would cause compile errors
29+
# when building .mjs
30+
DIST_PATH=./dist node ../../scripts/utils/fix-index-exports.cjs
31+
32+
# with "moduleResolution": "nodenext", if ESM resolves to index.d.ts,
33+
# it'll have TS errors on the default import. But if it resolves to
34+
# index.d.mts the default import will work (even though both files have
35+
# the same export default statement)
36+
cp dist/index.d.ts dist/index.d.mts
37+
cp tsconfig.dist-src.json dist/src/tsconfig.json
38+
39+
# Add proper Node.js shebang to the top of the file
40+
sed -i.bak '1s;^;#!/usr/bin/env node\n;' dist/index.js
41+
rm dist/index.js.bak
42+
43+
chmod +x dist/index.js
44+
45+
DIST_PATH=./dist PKG_IMPORT_PATH=isaacus-mcp/ node ../../scripts/utils/postprocess-files.cjs

packages/mcp-server/package.json

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
{
2+
"name": "isaacus-mcp",
3+
"version": "0.3.1",
4+
"description": "The official MCP Server for the Isaacus API",
5+
"author": "Isaacus <[email protected]>",
6+
"types": "dist/index.d.ts",
7+
"main": "dist/index.js",
8+
"type": "commonjs",
9+
"repository": "github:isaacus-dev/isaacus-typescript",
10+
"license": "Apache-2.0",
11+
"packageManager": "[email protected]",
12+
"private": false,
13+
"scripts": {
14+
"test": "echo 'no tests defined yet' && exit 1",
15+
"build": "bash ./build",
16+
"prepack": "echo 'to pack, run yarn build && (cd dist; yarn pack)' && exit 1",
17+
"prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1",
18+
"format": "prettier --write --cache --cache-strategy metadata . !dist",
19+
"prepare": "npm run build",
20+
"tsn": "ts-node -r tsconfig-paths/register",
21+
"lint": "eslint --ext ts,js .",
22+
"fix": "eslint --fix --ext ts,js ."
23+
},
24+
"dependencies": {
25+
"isaacus": "file:../../dist/",
26+
"@modelcontextprotocol/sdk": "^1.6.1"
27+
},
28+
"bin": {
29+
"mcp-server": "dist/index.js"
30+
},
31+
"devDependencies": {
32+
"@types/jest": "^29.4.0",
33+
"@typescript-eslint/eslint-plugin": "^6.7.0",
34+
"@typescript-eslint/parser": "^6.7.0",
35+
"eslint": "^8.49.0",
36+
"eslint-plugin-prettier": "^5.0.1",
37+
"eslint-plugin-unused-imports": "^3.0.0",
38+
"jest": "^29.4.0",
39+
"prettier": "^3.0.0",
40+
"ts-jest": "^29.1.0",
41+
"ts-morph": "^19.0.0",
42+
"ts-node": "^10.5.0",
43+
"tsc-multi": "^1.1.0",
44+
"tsconfig-paths": "^4.0.0",
45+
"typescript": "^4.8.2"
46+
},
47+
"imports": {
48+
"isaacus-mcp": ".",
49+
"isaacus-mcp/*": "./src/*"
50+
},
51+
"exports": {
52+
".": {
53+
"require": {
54+
"types": "./dist/index.d.ts",
55+
"default": "./dist/index.js"
56+
},
57+
"types": "./dist/index.d.mts",
58+
"default": "./dist/index.mjs"
59+
},
60+
"./*.mjs": {
61+
"types": "./dist/*.d.ts",
62+
"default": "./dist/*.mjs"
63+
},
64+
"./*.js": {
65+
"types": "./dist/*.d.ts",
66+
"default": "./dist/*.js"
67+
},
68+
"./*": {
69+
"types": "./dist/*.d.ts",
70+
"require": "./dist/*.js",
71+
"default": "./dist/*.mjs"
72+
}
73+
}
74+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const fs = require('fs');
2+
const pkgJson = require('../dist/package.json');
3+
const parentPkgJson = require('../../../package.json');
4+
5+
for (const dep in pkgJson.dependencies) {
6+
// ensure we point to NPM instead of a local directory
7+
if (dep === 'isaacus') {
8+
pkgJson.dependencies[dep] = '^' + parentPkgJson.version;
9+
}
10+
}
11+
12+
fs.writeFileSync('dist/package.json', JSON.stringify(pkgJson, null, 2));

packages/mcp-server/src/index.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2+
3+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4+
import { server, init } from './server';
5+
6+
async function main() {
7+
init({ server });
8+
const transport = new StdioServerTransport();
9+
await server.connect(transport);
10+
console.error('MCP Server running on stdio');
11+
}
12+
13+
if (require.main === module) {
14+
main().catch((error) => {
15+
console.error('Fatal error in main():', error);
16+
process.exit(1);
17+
});
18+
}

packages/mcp-server/src/server.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2+
3+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
4+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
5+
import { tools, handlers, HandlerFunction } from './tools';
6+
import { CallToolRequestSchema, ListToolsRequestSchema, Tool } from '@modelcontextprotocol/sdk/types.js';
7+
import Isaacus from 'isaacus';
8+
export { tools, handlers } from './tools';
9+
10+
// Create server instance
11+
export const server = new McpServer(
12+
{
13+
name: 'isaacus_api',
14+
version: '0.3.1',
15+
},
16+
{
17+
capabilities: {
18+
tools: {},
19+
},
20+
},
21+
);
22+
23+
/**
24+
* Initializes the provided MCP Server with the given tools and handlers.
25+
* If not provided, the default client, tools and handlers will be used.
26+
*/
27+
export function init(params: {
28+
server: Server | McpServer;
29+
client?: Isaacus;
30+
tools?: Tool[];
31+
handlers?: Record<string, HandlerFunction>;
32+
}) {
33+
const server = params.server instanceof McpServer ? params.server.server : params.server;
34+
const providedTools = params.tools || tools;
35+
const providedHandlers = params.handlers || handlers;
36+
const client = params.client || new Isaacus({});
37+
38+
server.setRequestHandler(ListToolsRequestSchema, async () => {
39+
return {
40+
tools: providedTools,
41+
};
42+
});
43+
44+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
45+
const { name, arguments: args } = request.params;
46+
47+
const handler = providedHandlers[name];
48+
if (!handler) {
49+
throw new Error(`Unknown tool: ${name}`);
50+
}
51+
52+
return executeHandler(handler, client, args);
53+
});
54+
}
55+
56+
/**
57+
* Runs the provided handler with the given client and arguments.
58+
*/
59+
export async function executeHandler(
60+
handler: HandlerFunction,
61+
client: Isaacus,
62+
args: Record<string, unknown> | undefined,
63+
) {
64+
const result = await handler(client, args || {});
65+
return {
66+
content: [
67+
{
68+
type: 'text',
69+
text: JSON.stringify(result, null, 2),
70+
},
71+
],
72+
};
73+
}
74+
75+
export const readEnv = (env: string): string => {
76+
let envValue = undefined;
77+
if (typeof (globalThis as any).process !== 'undefined') {
78+
envValue = (globalThis as any).process.env?.[env]?.trim();
79+
} else if (typeof (globalThis as any).Deno !== 'undefined') {
80+
envValue = (globalThis as any).Deno.env?.get?.(env)?.trim();
81+
}
82+
if (envValue === undefined) {
83+
throw new Error(`Environment variable ${env} is not set`);
84+
}
85+
return envValue;
86+
};
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2+
3+
import { Tool } from '@modelcontextprotocol/sdk/types.js';
4+
import Isaacus from 'isaacus';
5+
6+
export const tool: Tool = {
7+
name: 'create_classifications_universal',
8+
description:
9+
'Classify the relevance of a legal document to a query with an Isaacus universal legal AI classifier.',
10+
inputSchema: {
11+
type: 'object',
12+
properties: {
13+
model: {
14+
type: 'string',
15+
description:
16+
'The ID of the [model](https://docs.isaacus.com/models#universal-classification) to use for universal classification.',
17+
enum: ['kanon-universal-classifier', 'kanon-universal-classifier-mini'],
18+
},
19+
query: {
20+
type: 'string',
21+
description:
22+
'The [Isaacus Query Language (IQL)](https://docs.isaacus.com/iql) query or, if IQL is disabled, the statement, to evaluate the text against.\n\nThe query must contain at least one non-whitespace character.\n\nUnlike the text being classified, the query cannot be so long that it exceeds the maximum input length of the universal classifier.',
23+
},
24+
text: {
25+
type: 'string',
26+
description: 'The text to classify.\n\nThe text must contain at least one non-whitespace character.',
27+
},
28+
chunking_options: {
29+
type: 'object',
30+
title: 'Chunking options',
31+
description: 'Options for how to split text into smaller chunks.',
32+
properties: {
33+
overlap_ratio: {
34+
type: 'number',
35+
title: 'Unit interval (closed, open)',
36+
description: 'A number greater than or equal to 0 and less than 1.',
37+
},
38+
overlap_tokens: {
39+
type: 'integer',
40+
title: 'Non-negative integer',
41+
description: 'A whole number greater than -1.',
42+
},
43+
size: {
44+
type: 'integer',
45+
title: 'Positive integer',
46+
description: 'A whole number greater than or equal to 1.',
47+
},
48+
},
49+
required: [],
50+
},
51+
is_iql: {
52+
type: 'boolean',
53+
description:
54+
'Whether the query should be interpreted as an [IQL](https://docs.isaacus.com/iql) query or else as a statement.',
55+
},
56+
scoring_method: {
57+
type: 'string',
58+
description:
59+
"The method to use for producing an overall confidence score.\n\n`auto` is the default scoring method and is recommended for most use cases. Currently, it is equivalent to `chunk_max`. In the future, it will automatically select the best method based on the model and input.\n\n`chunk_max` uses the highest confidence score of all of the text's chunks.\n\n`chunk_avg` averages the confidence scores of all of the text's chunks.\n\n`chunk_min` uses the lowest confidence score of all of the text's chunks.",
60+
enum: ['auto', 'chunk_max', 'chunk_avg', 'chunk_min'],
61+
},
62+
},
63+
},
64+
};
65+
66+
export const handler = (client: Isaacus, args: any) => {
67+
const { ...body } = args;
68+
return client.classifications.universal.create(body);
69+
};
70+
71+
export default { tool, handler };

0 commit comments

Comments
 (0)