-
Notifications
You must be signed in to change notification settings - Fork 145
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
base: main
Are you sure you want to change the base?
Conversation
35346f7
to
1cb77e8
Compare
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"}.`, | ||
), | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestions are welcome
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about using never
or literal
?
1cb77e8
to
32790dd
Compare
src/tools/mcp/mcpResource.ts
Outdated
} | ||
|
||
async inputSchema() { | ||
const resources = await this.listPaginatedResources().catch(() => []); |
There was a problem hiding this comment.
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
5d5093f
to
9f04e7f
Compare
Signed-off-by: Tomas Pilar <[email protected]>
9f04e7f
to
5af7015
Compare
Signed-off-by: Tomas Pilar <[email protected]>
src/tools/mcp/mcpResource.ts
Outdated
const resources = await this.listResources().catch((err) => { | ||
if (err instanceof McpError && err.code === ErrorCode.MethodNotFound) { | ||
return []; | ||
} | ||
throw err; | ||
}); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
Outdated
const resources = await this.listResources().catch((err) => { | ||
if (err instanceof McpError && err.code === ErrorCode.MethodNotFound) { | ||
return []; | ||
} | ||
throw err; | ||
}); |
There was a problem hiding this comment.
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
Signed-off-by: Tomas Pilar <[email protected]>
Signed-off-by: Tomas Pilar <[email protected]>
Signed-off-by: Tomas Pilar <[email protected]>
db9fe6b
to
676cac6
Compare
Signed-off-by: Tomas Pilar <[email protected]>
d54eb22
to
3c2b225
Compare
Signed-off-by: Tomas Pilar <[email protected]>
Signed-off-by: Tomas Pilar <[email protected]>
}); | ||
}); | ||
|
||
await client.close(); |
There was a problem hiding this comment.
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 thefinally
block.
export interface PaginateWithCursorInput<T, C> { | ||
size: number; | ||
handler: (data: { | ||
cursor: C | undefined; |
There was a problem hiding this comment.
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; | ||
} |
There was a problem hiding this comment.
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.
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: {}, | ||
}, | ||
); |
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MCPResource
return nextCursor | ||
? ({ | ||
data: resources, | ||
done: false, | ||
nextCursor, | ||
} as const) | ||
: ({ | ||
data: resources, | ||
done: true, | ||
} as const); |
There was a problem hiding this comment.
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
.
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"}.`, | ||
), | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about using never
or literal
?
}); | ||
} | ||
|
||
protected async _run({ uri }: ToolInput<this>, run: GetRunContext<typeof this>) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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>) {
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
yarn lint
oryarn lint:fix
yarn format
oryarn format:fix
yarn test:unit
yarn test:e2e