Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tools): add Model Context Protocol tool #265

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

pilartomas
Copy link
Contributor

@pilartomas pilartomas commented Jan 2, 2025

Which issue(s) does this pull-request address?

Closes: #238

Description

This PR introduces a base for Model Context Protocol related tools and add support for MCP Resources. This gives framework users to give the agent the ability to query MCP compliant servers for resources.

Checklist

  • I have read the contributor guide
  • I have signed off on my commit
  • Linting passes: yarn lint or yarn lint:fix
  • Formatting is applied: yarn format or yarn format:fix
  • Unit tests pass: yarn test:unit
  • E2E tests pass: yarn test:e2e
  • Tests are included
  • Documentation is changed or added
  • Commit messages and PR title follow conventional commits

@pilartomas pilartomas force-pushed the feat-mcp-tool branch 5 times, most recently from 35346f7 to 1cb77e8 Compare January 3, 2025 09:06
Comment on lines +53 to +52
return z.object({
uri: z
.string()
.describe(
`URI of the resource to read, ${resources.length > 0 ? `available resources are:\n\n${resources.map(({ uri, name, description }) => JSON.stringify({ uri, name, description })).join("\n\n")}` : "no resources available at the moment"}.`,
),
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestions are welcome

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

async inputSchema() {
const resources = await this.listPaginatedResources().catch(() => []);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resource template support can be added later

@pilartomas pilartomas force-pushed the feat-mcp-tool branch 2 times, most recently from 5d5093f to 9f04e7f Compare January 3, 2025 10:26
@pilartomas pilartomas marked this pull request as ready for review January 3, 2025 10:45
@pilartomas pilartomas requested a review from a team as a code owner January 3, 2025 10:45
Signed-off-by: Tomas Pilar <[email protected]>
Comment on lines 45 to 50
const resources = await this.listResources().catch((err) => {
if (err instanceof McpError && err.code === ErrorCode.MethodNotFound) {
return [];
}
throw err;
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Limit to pagination and ignoring not found error are not ideal solutions, ideas?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the method for listing resources does not exist, the agent will need to know the uri of the resource exactly which I think will be rarely correct.

I'd just say that the servers not implementing this method are not supported for now or they will require a new specialized implementation.

Ad limit - idea:

  • wouldn't it be better to have a separate tool for listing resources? Pagination could be implemented as input parameter of the tool. Then the ResourceRead tool would have "you must first list resources" in the prompt

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we let the developer hardcode the list of resources when instantiating the tool, as an alternative to listing them?

A separate tool for listing resources doesn't sound right to me, we want to be able to take advantage of constrained decoding and validation through the input schema, and also having more tools degrades performance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the method for listing resources does not exist, the agent will need to know the uri of the resource exactly which I think will be rarely correct.
I'd just say that the servers not implementing this method are not supported for now or they will require a new specialized implementation.

Agreed, I'll remove the catch.

wouldn't it be better to have a separate tool for listing resources? Pagination could be implemented as input parameter of the tool. Then the ResourceRead tool would have "you must first list resources" in the prompt

I think that would be too heavy on the agent, also the user would have to make sure to include both tools which is quite error prone (but we could solve it with a joint factory somehow).

Can't we let the developer hardcode the list of resources when instantiating the tool, as an alternative to listing them?

We could but that would just put the problem on the user's back. The user would have to use the client, make a list a and inject the values into the constructor. It would also never update for that instance. I think we should solve it on our side. It would also sort of clash with another MCP concept from his perspective - roots

src/tools/mcp/mcpResource.ts Show resolved Hide resolved
Comment on lines 45 to 50
const resources = await this.listResources().catch((err) => {
if (err instanceof McpError && err.code === ErrorCode.MethodNotFound) {
return [];
}
throw err;
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the method for listing resources does not exist, the agent will need to know the uri of the resource exactly which I think will be rarely correct.

I'd just say that the servers not implementing this method are not supported for now or they will require a new specialized implementation.

Ad limit - idea:

  • wouldn't it be better to have a separate tool for listing resources? Pagination could be implemented as input parameter of the tool. Then the ResourceRead tool would have "you must first list resources" in the prompt

src/tools/mcp/base.ts Outdated Show resolved Hide resolved
src/tools/mcp/mcpResource.ts Outdated Show resolved Hide resolved
src/tools/mcp/mcpResource.ts Outdated Show resolved Hide resolved
Signed-off-by: Tomas Pilar <[email protected]>
Signed-off-by: Tomas Pilar <[email protected]>
Signed-off-by: Tomas Pilar <[email protected]>
Signed-off-by: Tomas Pilar <[email protected]>
@pilartomas pilartomas requested review from Tomas2D and jezekra1 January 6, 2025 14:24
});
});

await client.close();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Add some explanation please.
  • I would close to the finally block.

export interface PaginateWithCursorInput<T, C> {
size: number;
handler: (data: {
cursor: C | undefined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure whether cursor needs to be typed with generic

}

return acc;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I think that those two functions might be merged.

Comment on lines +41 to +61
const server = new Server(
{
name: "test-server",
version: "1.0.0",
},
{
capabilities: {
resources: {},
},
},
);

const client = new Client(
{
name: "test-client",
version: "1.0.0",
},
{
capabilities: {},
},
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the usage, I think that it would be better to initiate classes in the beforeEach.

}

export class MCPResourceTool extends MCPTool<ReadResourceResult> {
name = "Resource";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MCPResource

Comment on lines +53 to +62
return nextCursor
? ({
data: resources,
done: false,
nextCursor,
} as const)
: ({
data: resources,
done: true,
} as const);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of passing done it would be much easier to pass falsy value for nextCursor.

Comment on lines +53 to +52
return z.object({
uri: z
.string()
.describe(
`URI of the resource to read, ${resources.length > 0 ? `available resources are:\n\n${resources.map(({ uri, name, description }) => JSON.stringify({ uri, name, description })).join("\n\n")}` : "no resources available at the moment"}.`,
),
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

});
}

protected async _run({ uri }: ToolInput<this>, run: GetRunContext<typeof this>) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect usage, run is a third parameter

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should be

protected async _run({ uri }: ToolInput<this>, _options: BaseToolRunOptions, run: GetRunContext<typeof this>) {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support of Model Context Protocol (MCP)
4 participants