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
38 changes: 35 additions & 3 deletions packages/mcp-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ It is generated with [Stainless](https://www.stainless.com/).

See [the user guide](https://modelcontextprotocol.io/quickstart/user) for setup.

Once it's set up, add your MCP server to your `claude_desktop_config.json` file to enable it.

The configuration file should be at:
Once it's set up, find your `claude_desktop_config.json` file:

- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
Expand All @@ -30,3 +28,37 @@ Add the following value to your `mcpServers` section. Make sure to provide any n
}
}
```

## Filtering tools

You can run the package on the command line to discover and filter the set of tools that are exposed by the
MCP Server. This can be helpful for large APIs where including all endpoints at once is too much for your AI's
context window.

You can filter by multiple aspects:

- `--tool` includes a specific tool by name
- `--resource` includes all tools under a specific resource, and can have wildcards, e.g. `my.resource*`
- `--operation` includes just read (get/list) or just write operations

See more information with `--help`:

```sh
$ npx -y isaacus-mcp --help
```

All of these command-line options can be repeated, combined together, and have corresponding exclusion versions (e.g. `--no-tool`).

Use `--list` to see the list of available tools, or see below.

## Available Tools

The following tools are available in this MCP server.

### Resource `classifications.universal`:

- `create_classifications_universal` (`write`): Classify the relevance of a legal document to a query with an Isaacus universal legal AI classifier.

### Resource `rerankings`:

- `create_rerankings` (`write`): Rerank legal documents by their relevance to a query with an Isaacus legal AI reranker.
17 changes: 17 additions & 0 deletions packages/mcp-server/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { JestConfigWithTsJest } from 'ts-jest';

const config: JestConfigWithTsJest = {
preset: 'ts-jest/presets/default-esm',
testEnvironment: 'node',
transform: {
'^.+\\.(t|j)sx?$': ['@swc/jest', { sourceMaps: 'inline' }],
},
moduleNameMapper: {
'^isaacus-mcp$': '<rootDir>/src/index.ts',
'^isaacus-mcp/(.*)$': '<rootDir>/src/$1',
},
modulePathIgnorePatterns: ['<rootDir>/dist/'],
testPathIgnorePatterns: ['scripts'],
};

export default config;
5 changes: 3 additions & 2 deletions packages/mcp-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"packageManager": "yarn@1.22.22",
"private": false,
"scripts": {
"test": "echo 'no tests defined yet' && exit 1",
"test": "jest",
"build": "bash ./build",
"prepack": "echo 'to pack, run yarn build && (cd dist; yarn pack)' && exit 1",
"prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1",
Expand All @@ -23,7 +23,8 @@
},
"dependencies": {
"isaacus": "file:../../dist/",
"@modelcontextprotocol/sdk": "^1.6.1"
"@modelcontextprotocol/sdk": "^1.6.1",
"yargs": "^17.7.2"
},
"bin": {
"mcp-server": "dist/index.js"
Expand Down
180 changes: 176 additions & 4 deletions packages/mcp-server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,116 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { server, init } from './server';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { init, server } from './server';
import { endpoints, Filter, query } from './tools';

async function main() {
init({ server });
const opts = yargs(hideBin(process.argv))
.option('tool', {
type: 'string',
array: true,
description: 'Include tools matching the specified names',
})
.option('resource', {
type: 'string',
array: true,
description: 'Include tools matching the specified resources',
})
.option('operation', {
type: 'string',
array: true,
choices: ['read', 'write'],
description: 'Include tools matching the specified operations',
})
.option('tag', {
type: 'string',
array: true,
description: 'Include tools with the specified tags',
})
.option('no-tool', {
type: 'string',
array: true,
description: 'Exclude tools matching the specified names',
})
.option('no-resource', {
type: 'string',
array: true,
description: 'Exclude tools matching the specified resources',
})
.option('no-operation', {
type: 'string',
array: true,
description: 'Exclude tools matching the specified operations',
})
.option('no-tag', {
type: 'string',
array: true,
description: 'Exclude tools with the specified tags',
})
.option('list', {
type: 'boolean',
description: 'List all tools and exit',
})
.help();

for (const [command, desc] of examples()) {
opts.example(command, desc);
}

const argv = opts.parseSync();

if (argv.list) {
listAllTools();
return;
}
const filters: Filter[] = [];

for (const tag of argv.tag || []) {
filters.push({ type: 'tag', op: 'include', value: tag });
}

for (const tag of argv.noTag || []) {
filters.push({ type: 'tag', op: 'exclude', value: tag });
}

for (const resource of argv.resource || []) {
filters.push({ type: 'resource', op: 'include', value: resource });
}

for (const resource of argv.noResource || []) {
filters.push({ type: 'resource', op: 'exclude', value: resource });
}

for (const tool of argv.tool || []) {
filters.push({ type: 'tool', op: 'include', value: tool });
}

for (const tool of argv.noTool || []) {
filters.push({ type: 'tool', op: 'exclude', value: tool });
}

for (const operation of argv.operation || []) {
filters.push({ type: 'operation', op: 'include', value: operation });
}

for (const operation of argv.noOperation || []) {
filters.push({ type: 'operation', op: 'exclude', value: operation });
}

const filteredEndpoints = query(filters, endpoints);

if (filteredEndpoints.length === 0) {
console.error('No tools match the provided filters.');
process.exit(1);
}

console.error(
`MCP Server starting with ${filteredEndpoints.length} tools:`,
filteredEndpoints.map((e) => e.tool.name),
);

init({ server, endpoints: filteredEndpoints });

const transport = new StdioServerTransport();
await server.connect(transport);
console.error('MCP Server running on stdio');
Expand All @@ -16,3 +122,69 @@ if (require.main === module) {
process.exit(1);
});
}

function listAllTools() {
if (endpoints.length === 0) {
console.error('No tools available.');
return;
}
console.error('Available tools:\n');

// Group endpoints by resource
const resourceGroups = new Map<string, typeof endpoints>();

for (const endpoint of endpoints) {
const resource = endpoint.metadata.resource;
if (!resourceGroups.has(resource)) {
resourceGroups.set(resource, []);
}
resourceGroups.get(resource)!.push(endpoint);
}

// Sort resources alphabetically
const sortedResources = Array.from(resourceGroups.keys()).sort();

// Display hierarchically by resource
for (const resource of sortedResources) {
console.error(`Resource: ${resource}`);

const resourceEndpoints = resourceGroups.get(resource)!;
// Sort endpoints by tool name
resourceEndpoints.sort((a, b) => a.tool.name.localeCompare(b.tool.name));

for (const endpoint of resourceEndpoints) {
const {
tool,
metadata: { operation, tags },
} = endpoint;

console.error(` - ${tool.name} (${operation}) ${tags.length > 0 ? `tags: ${tags.join(', ')}` : ''}`);
console.error(` Description: ${tool.description}`);
}
console.error('');
}
}

function examples(): [string, string][] {
const firstEndpoint = endpoints[0]!;
const secondEndpoint =
endpoints.find((e) => e.metadata.resource !== firstEndpoint.metadata.resource) || endpoints[1];
const tag = endpoints.find((e) => e.metadata.tags.length > 0)?.metadata.tags[0];
const otherEndpoint = secondEndpoint || firstEndpoint;

return [
[
`--tool="${firstEndpoint.tool.name}" ${secondEndpoint ? `--tool="${secondEndpoint.tool.name}"` : ''}`,
'Include tools by name',
],
[
`--resource="${firstEndpoint.metadata.resource}" --operation="read"`,
'Filter by resource and operation',
],
[
`--resource="${otherEndpoint.metadata.resource}*" --no-tool="${otherEndpoint.tool.name}"`,
'Use resource wildcards and exclusions',
],
...(tag ? [[`--tag="${tag}"`, 'Filter based on tags'] as [string, string]] : []),
];
}
19 changes: 11 additions & 8 deletions packages/mcp-server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { tools, handlers, HandlerFunction } from './tools';
import { endpoints, HandlerFunction } from './tools';
import { CallToolRequestSchema, ListToolsRequestSchema, Tool } from '@modelcontextprotocol/sdk/types.js';
import Isaacus from 'isaacus';
export { tools, handlers } from './tools';
export { endpoints } from './tools';

// Create server instance
export const server = new McpServer(
Expand All @@ -27,24 +27,27 @@ export const server = new McpServer(
export function init(params: {
server: Server | McpServer;
client?: Isaacus;
tools?: Tool[];
handlers?: Record<string, HandlerFunction>;
endpoints?: { tool: Tool; handler: HandlerFunction }[];
}) {
const server = params.server instanceof McpServer ? params.server.server : params.server;
const providedTools = params.tools || tools;
const providedHandlers = params.handlers || handlers;
const providedEndpoints = params.endpoints || endpoints;
const tools = providedEndpoints.map((endpoint) => endpoint.tool);
const handlers = Object.fromEntries(
providedEndpoints.map((endpoint) => [endpoint.tool.name, endpoint.handler]),
);

const client = params.client || new Isaacus({});

server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: providedTools,
tools,
};
});

server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;

const handler = providedHandlers[name];
const handler = handlers[name];
if (!handler) {
throw new Error(`Unknown tool: ${name}`);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import type { Metadata } from '../../';
import Isaacus from 'isaacus';

export const metadata: Metadata = {
resource: 'classifications.universal',
operation: 'write',
tags: [],
};

export const tool: Tool = {
name: 'create_classifications_universal',
description:
Expand Down Expand Up @@ -68,4 +75,4 @@ export const handler = (client: Isaacus, args: any) => {
return client.classifications.universal.create(body);
};

export default { tool, handler };
export default { metadata, tool, handler };
Loading