Skip to content

Commit 8d0556f

Browse files
feat(mcp): support filtering tool results by a jq expression
1 parent d763165 commit 8d0556f

File tree

5 files changed

+43
-6
lines changed

5 files changed

+43
-6
lines changed

packages/mcp-server/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"dependencies": {
3030
"isaacus": "file:../../dist/",
3131
"@modelcontextprotocol/sdk": "^1.11.5",
32+
"jq-web": "^0.6.2",
3233
"yargs": "^17.7.2",
3334
"@cloudflare/cabidela": "^0.2.4",
3435
"zod": "^3.25.20",
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export async function maybeFilter(args: Record<string, unknown> | undefined, response: any): Promise<any> {
2+
const jqFilter = args?.['jq_filter'];
3+
if (jqFilter && typeof jqFilter === 'string') {
4+
return await jq(response, jqFilter);
5+
} else {
6+
return response;
7+
}
8+
}
9+
10+
var jqWeb = require('jq-web');
11+
async function jq(json: any, jqFilter: string) {
12+
return (await jqWeb).json(json, jqFilter);
13+
}

packages/mcp-server/src/tools/classifications/universal/create-classifications-universal.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
22

3+
import { maybeFilter } from 'isaacus-mcp/filtering';
34
import { asTextContentResult } from 'isaacus-mcp/tools/types';
45

56
import { Tool } from '@modelcontextprotocol/sdk/types.js';
@@ -18,7 +19,7 @@ export const metadata: Metadata = {
1819
export const tool: Tool = {
1920
name: 'create_classifications_universal',
2021
description:
21-
'Classify the relevance of legal documents to a query with an Isaacus universal legal AI classifier.',
22+
"When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nClassify the relevance of legal documents to a query with an Isaacus universal legal AI classifier.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/universal_classification',\n $defs: {\n universal_classification: {\n type: 'object',\n title: 'Universal classification response',\n description: 'Classifications of the relevance of legal documents to a query produced by an Isaacus universal legal AI classifier.',\n properties: {\n classifications: {\n type: 'array',\n description: 'The classifications of the texts, by relevance to the query, in order from highest to lowest relevance score.',\n items: {\n type: 'object',\n title: 'Universal classification',\n properties: {\n chunks: {\n type: 'array',\n description: 'The text as broken into chunks by [semchunk](https://github.com/isaacus-dev/semchunk), each chunk with its own confidence score, ordered from highest to lowest score.\\n\\nIf no chunking occurred, this will be `null`.',\n items: {\n type: 'object',\n title: 'Universal classification chunk',\n properties: {\n end: {\n type: 'integer',\n description: 'The index of the character immediately after the last character of the chunk in the original text, beginning from `0` (such that, in Python, the chunk is equivalent to `text[start:end]`).'\n },\n index: {\n type: 'integer',\n description: 'The original position of the chunk in the outputted list of chunks before sorting, starting from `0` (and, therefore, ending at the number of chunks minus `1`).'\n },\n score: {\n type: 'number',\n description: 'The model\\'s score of the likelihood that the query expressed about the chunk is supported by the chunk.\\n\\nA score greater than `0.5` indicates that the chunk supports the query, while a score less than `0.5` indicates that the chunk does not support the query.'\n },\n start: {\n type: 'integer',\n description: 'The index of the character in the original text where the chunk starts, beginning from `0`.'\n },\n text: {\n type: 'string',\n description: 'The text of the chunk.'\n }\n },\n required: [ 'end',\n 'index',\n 'score',\n 'start',\n 'text'\n ]\n }\n },\n index: {\n type: 'integer',\n description: 'The index of the text in the input array of texts, starting from `0` (and, therefore, ending at the number of texts minus `1`).'\n },\n score: {\n type: 'number',\n description: 'A score of the likelihood that the query expressed about the text is supported by the text.\\n\\nA score greater than `0.5` indicates that the text supports the query, while a score less than `0.5` indicates that the text does not support the query.'\n }\n },\n required: [ 'chunks',\n 'index',\n 'score'\n ]\n }\n },\n usage: {\n type: 'object',\n title: 'Universal classification usage',\n description: 'Statistics about the usage of resources in the process of classifying the text.',\n properties: {\n input_tokens: {\n type: 'integer',\n description: 'The number of tokens inputted to the model.'\n }\n },\n required: [ 'input_tokens'\n ]\n }\n },\n required: [ 'classifications',\n 'usage'\n ]\n }\n }\n}\n```",
2223
inputSchema: {
2324
type: 'object',
2425
properties: {
@@ -76,13 +77,19 @@ export const tool: Tool = {
7677
"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 inputs.\n\n`chunk_max` uses the highest confidence score of all of the texts' chunks.\n\n`chunk_avg` averages the confidence scores of all of the texts' chunks.\n\n`chunk_min` uses the lowest confidence score of all of the texts' chunks.",
7778
enum: ['auto', 'chunk_max', 'chunk_avg', 'chunk_min'],
7879
},
80+
jq_filter: {
81+
type: 'string',
82+
title: 'jq Filter',
83+
description:
84+
'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).',
85+
},
7986
},
8087
},
8188
};
8289

8390
export const handler = async (client: Isaacus, args: Record<string, unknown> | undefined) => {
8491
const body = args as any;
85-
return asTextContentResult(await client.classifications.universal.create(body));
92+
return asTextContentResult(await maybeFilter(args, await client.classifications.universal.create(body)));
8693
};
8794

8895
export default { metadata, tool, handler };

packages/mcp-server/src/tools/extractions/qa/create-extractions-qa.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
22

3+
import { maybeFilter } from 'isaacus-mcp/filtering';
34
import { asTextContentResult } from 'isaacus-mcp/tools/types';
45

56
import { Tool } from '@modelcontextprotocol/sdk/types.js';
@@ -17,7 +18,8 @@ export const metadata: Metadata = {
1718

1819
export const tool: Tool = {
1920
name: 'create_extractions_qa',
20-
description: 'Extract answers to questions from legal documents with an Isaacus legal AI answer extractor.',
21+
description:
22+
"When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nExtract answers to questions from legal documents with an Isaacus legal AI answer extractor.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/answer_extraction',\n $defs: {\n answer_extraction: {\n type: 'object',\n title: 'Answer extraction response',\n description: 'The results of extracting answers from texts.',\n properties: {\n extractions: {\n type: 'array',\n description: 'The results of extracting answers from the texts, ordered from highest to lowest answer confidence score (or else lowest to highest inextractability score if there are no answers for a text).',\n items: {\n type: 'object',\n title: 'Answer extraction',\n description: 'The result of extracting answers from a text.',\n properties: {\n answers: {\n type: 'array',\n description: 'Answers extracted from the text, ordered from highest to lowest score.',\n items: {\n type: 'object',\n title: 'Answer',\n description: 'An answer extracted from a text.',\n properties: {\n end: {\n type: 'integer',\n description: 'The index of the character immediately after the last character of the answer in the text, starting from `0` (such that, in Python, the answer is equivalent to `text[start:end]`).'\n },\n score: {\n type: 'number',\n description: 'A score between `0` and `1`, inclusive, representing the strength of the answer.'\n },\n start: {\n type: 'integer',\n description: 'The index of the first character of the answer in the text, starting from `0` (and, therefore, ending at the number of characters in the text minus `1`).'\n },\n text: {\n type: 'string',\n description: 'The text of the answer.'\n }\n },\n required: [ 'end',\n 'score',\n 'start',\n 'text'\n ]\n }\n },\n index: {\n type: 'integer',\n description: 'The index of the text in the input array of texts that this result represents, starting from `0` (and, therefore, ending at the number of texts minus `1`).'\n },\n inextractability_score: {\n type: 'number',\n description: 'A score between `0` and `1`, inclusive, representing the likelihood that an answer can not be extracted from the text.\\n\\nWhere this score is greater than the highest score of all possible answers, the text should be regarded as not having an extractable answer to the query. If that is the case and `ignore_inextractability` is `false`, no answers will be returned.'\n }\n },\n required: [ 'answers',\n 'index',\n 'inextractability_score'\n ]\n }\n },\n usage: {\n type: 'object',\n title: 'Answer extraction usage',\n description: 'Statistics about the usage of resources in the process of extracting answers from the texts.',\n properties: {\n input_tokens: {\n type: 'integer',\n description: 'The number of tokens inputted to the model.'\n }\n },\n required: [ 'input_tokens'\n ]\n }\n },\n required: [ 'extractions',\n 'usage'\n ]\n }\n }\n}\n```",
2123
inputSchema: {
2224
type: 'object',
2325
properties: {
@@ -74,13 +76,19 @@ export const tool: Tool = {
7476
description:
7577
'The number of highest scoring answers to return.\n\nIf `null`, which is the default, all answers will be returned.',
7678
},
79+
jq_filter: {
80+
type: 'string',
81+
title: 'jq Filter',
82+
description:
83+
'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).',
84+
},
7785
},
7886
},
7987
};
8088

8189
export const handler = async (client: Isaacus, args: Record<string, unknown> | undefined) => {
8290
const body = args as any;
83-
return asTextContentResult(await client.extractions.qa.create(body));
91+
return asTextContentResult(await maybeFilter(args, await client.extractions.qa.create(body)));
8492
};
8593

8694
export default { metadata, tool, handler };

packages/mcp-server/src/tools/rerankings/create-rerankings.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
22

3+
import { maybeFilter } from 'isaacus-mcp/filtering';
34
import { asTextContentResult } from 'isaacus-mcp/tools/types';
45

56
import { Tool } from '@modelcontextprotocol/sdk/types.js';
@@ -17,7 +18,8 @@ export const metadata: Metadata = {
1718

1819
export const tool: Tool = {
1920
name: 'create_rerankings',
20-
description: 'Rerank legal documents by their relevance to a query with an Isaacus legal AI reranker.',
21+
description:
22+
"When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nRerank legal documents by their relevance to a query with an Isaacus legal AI reranker.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/reranking',\n $defs: {\n reranking: {\n type: 'object',\n title: 'Reranking response',\n description: 'The reranking of texts, by relevance to a query, out of an input array of texts.',\n properties: {\n results: {\n type: 'array',\n description: 'The rerankings of the texts, by relevance to the query, in order from highest to lowest relevance score.',\n items: {\n type: 'object',\n title: 'Reranking result',\n properties: {\n index: {\n type: 'integer',\n description: 'The index of the text in the input array of texts, starting from `0` (and, therefore, ending at the number of texts minus `1`).'\n },\n score: {\n type: 'number',\n description: 'A score between `0` and `1`, inclusive, representing the relevance of the text to the query.'\n }\n },\n required: [ 'index',\n 'score'\n ]\n }\n },\n usage: {\n type: 'object',\n title: 'Reranking usage',\n description: 'Statistics about the usage of resources in the process of reranking the texts.',\n properties: {\n input_tokens: {\n type: 'integer',\n description: 'The number of tokens inputted to the model.'\n }\n },\n required: [ 'input_tokens'\n ]\n }\n },\n required: [ 'results',\n 'usage'\n ]\n }\n }\n}\n```",
2123
inputSchema: {
2224
type: 'object',
2325
properties: {
@@ -79,13 +81,19 @@ export const tool: Tool = {
7981
title: 'Positive integer',
8082
description: 'A whole number greater than or equal to 1.',
8183
},
84+
jq_filter: {
85+
type: 'string',
86+
title: 'jq Filter',
87+
description:
88+
'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).',
89+
},
8290
},
8391
},
8492
};
8593

8694
export const handler = async (client: Isaacus, args: Record<string, unknown> | undefined) => {
8795
const body = args as any;
88-
return asTextContentResult(await client.rerankings.create(body));
96+
return asTextContentResult(await maybeFilter(args, await client.rerankings.create(body)));
8997
};
9098

9199
export default { metadata, tool, handler };

0 commit comments

Comments
 (0)