-
Notifications
You must be signed in to change notification settings - Fork 78
feat: Actorized MCP servers proxy #69
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
Conversation
document Actor input schema processing in README
* Add vs code instructions --------- Co-authored-by: mbaiza27 <[email protected]> Co-authored-by: Jiří Spilka <[email protected]>
83236d5 to
3543a5e
Compare
run prerelase manually
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.
Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.
jirispilka
left a comment
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.
After all, I like the separation of actor, mcp, tools ...
It makes it nicer 👍🏻 .... but ... when we are both working on the same repo it would be better not to move things that much.
The code will need more love, but let's get out a working version first.
So the only issue that is not clear is getActorDefinition function. Perhaps there are other things that I missed.
We will need to fix lint issues from the other PR, which contains lint fixes changes: #72
| export type Input = { | ||
| actors: string[] | string; | ||
| enableActorAutoLoading?: boolean; | ||
| maxActorMemoryBytes?: number; | ||
| debugActor?: string; | ||
| debugActorInput?: unknown; | ||
| }; |
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.
Ideally, we should have this separate Actor vs mcp-server
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.
I moved that since functions used in mcp-server are using this type. I agree that in future we should separate 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.
Again, this should be divided into Actor vs mcp-server input
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.
I also agree, we should refactor this
| export async function getActorDefinition(actorID: string, apifyToken: string): Promise<ActorDefinition> { | ||
| const apifyClient = new ApifyClient({ token: apifyToken | ||
| }) | ||
| const actor = apifyClient.actor(actorID); | ||
| const info = await actor.get(); | ||
| if (!info) { | ||
| throw new Error(`Actor ${actorID} not found`); | ||
| } | ||
| const latestBuildID = info.taggedBuilds?.['latest']?.buildId; | ||
| if (!latestBuildID) { | ||
| throw new Error(`Actor ${actorID} does not have a latest build`); | ||
| } | ||
| const build = apifyClient.build(latestBuildID); | ||
| const buildInfo = await build.get(); | ||
| if (!buildInfo) { | ||
| throw new Error(`Build ${latestBuildID} not found`); | ||
| } | ||
| const actorDefinition = buildInfo.actorDefinition; | ||
| if (!actorDefinition) { | ||
| throw new Error(`Build ${latestBuildID} does not have an actor definition`); | ||
| } | ||
|
|
||
| return actorDefinition; | ||
| } |
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.
Why we need to add a new function?
We have an existing function for that
/**
* Get actor input schema by actor name.
* First, fetch the actor details to get the default build tag and buildId.
* Then, fetch the build details and return actorName, description, and input schema.
* @param {string} actorIdOrName - Actor ID or Actor full name.
* @param {number} limit - Truncate the README to this limit.
* @returns {Promise<ActorDefinitionWithDesc | null>} - The actor definition with description or null if not found.
*/
export async function getActorDefinition(actorIdOrName: string, limit: number = ACTOR_README_MAX_LENGTH): Promise<ActorDefinitionPruned | null> {
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const actorClient = client.actor(actorIdOrName);
try {
// Fetch actor details
const actor = await actorClient.get();
if (!actor) {
log.error(`Failed to fetch input schema for Actor: ${actorIdOrName}. Actor not found.`);
return null;
}
// fnesveda: The default build is not necessarily tagged, you can specify any build number as default build.
// There will be a new API endpoint to fetch a default build.
// For now, we'll use the tagged build, it will work for 90% of Actors. Later, we can update this.
const tag = actor.defaultRunOptions?.build || '';
const buildId = actor.taggedBuilds?.[tag]?.buildId || '';
if (!buildId) {
log.error(`Failed to fetch input schema for Actor: ${actorIdOrName}. Build ID not found.`);
return null;
}
// Fetch build details and return the input schema
const buildDetails = await client.build(buildId).get();
if (buildDetails?.actorDefinition) {
const actorDefinitions = buildDetails?.actorDefinition as ActorDefinitionWithDesc;
actorDefinitions.id = actor.id;
actorDefinitions.readme = truncateActorReadme(actorDefinitions.readme || '', limit);
actorDefinitions.description = actor.description || '';
actorDefinitions.actorFullName = `${actor.username}/${actor.name}`;
actorDefinitions.defaultRunOptions = actor.defaultRunOptions;
return pruneActorDefinition(actorDefinitions);
}
return null;
} catch (error) {
const errorMessage = `Failed to fetch input schema for Actor: ${actorIdOrName} with error ${error}.`;
log.error(errorMessage);
throw new Error(errorMessage);
}
}
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.
I did not want to modify this function but it returns only pruned actorDefinition for the Actorized MCP purpose we need to get the webServerMcpPath from top level of actorDefinition
Thank you for review and effort 👍 The code definitely needs more love and refactoring, let's do that as a first thing after we deploy first functional version, and let's also add more tests. |
|
after conflicts are resolved we need to merge #73 |
* fix get default build in get actor definition * fix tests * fix double import, lint
This PR adds functionality for calling Actorized MCP servers from the Apify store, esentially acting as a MCP proxy.
Our MCP server endpoint (MCPAPI) - that calls the Actorized MCP servers (AMCP); Always creates short lived connections to the AMCPs - initial one for tool list and each for each tool call acting as a proxy.
We check if the Actor is an MCP server if it has the
webServerMcpPathfromactorDefinition. This value is also used as MCP server path ->{standbyURL}{MCPPath}.TODOs:
Deployed and tested @ https://mcp-securitybyobscurity.apify.com/ - both nornal Actors (https://securitybyobscurity.apify.com/jakub.kopecky/python-example) and Actorized MCp server (https://securitybyobscurity.apify.com/jakub.kopecky/apify-mcp-server)