Skip to content

feat!(js/plugins/mcp): renamed mcp plugin package to @genkit-ai/mcp and new MCP client API #3123

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

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b570dd0
feat(js/plugins/mcp): MCP client can connect to multiple servers. (#2…
mbleigh May 10, 2025
687c8bc
Merge branch 'main' of github.com:firebase/genkit into launch/mcp-redux
mbleigh May 12, 2025
6e104b1
Merge branch 'main' into launch/mcp-redux
pavelgj May 22, 2025
b7f4e22
feat(js/plugins/mcp): Refactor to a client manager structure, with dy…
ssbushi May 30, 2025
3c51740
merge main
mbleigh Jun 4, 2025
deb5495
chore(js/mcp): Rename "Manager" -> "Host" to align terminology. (#3031)
mbleigh Jun 4, 2025
e43b4ea
Merge branch 'main' of github.com:firebase/genkit into launch/mcp-redux
mbleigh Jun 10, 2025
e8b293d
fix(js/mcp): Export McpHostOptions
mbleigh Jun 10, 2025
1c2c0d0
feat(js/mcp): Adds getActivePrompts() to MCPHost. (#3071)
mbleigh Jun 13, 2025
70bb765
feat(js/mcp): Adds support for MCP _meta via context. (#3072)
mbleigh Jun 13, 2025
d842ae6
chore(js/plugins/mcp): removed resource tools until we have 1st class…
pavelgj Jun 13, 2025
92fe6ba
feat(js/plugins/mcp): added support for mcp roots (#3074)
pavelgj Jun 14, 2025
eeb9038
feat(js): added id and metadata to executable prompts (#3084)
pavelgj Jun 18, 2025
f7a3e80
merge main
mbleigh Jun 18, 2025
3693c3b
chore(js/mcp): Use StreamableHTTP transport for remote MCP. (#3105)
mbleigh Jun 23, 2025
21e8a22
Merge branch 'main' of github.com:firebase/genkit into launch/mcp-redux
mbleigh Jun 23, 2025
abdcb33
Merge branch 'launch/mcp-redux' of github.com:firebase/genkit into la…
mbleigh Jun 23, 2025
8857b6b
Merge branch 'main' of github.com:firebase/genkit into launch/mcp-redux
mbleigh Jun 24, 2025
4169388
chore(js/mcp): pipe _meta through on tools and prompts, upgrade SDK (…
mbleigh Jun 24, 2025
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
4 changes: 2 additions & 2 deletions .github/workflows/bump-js-version.yml
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,8 @@ jobs:
default: ${{ inputs.releaseType }}
version-type: ${{ inputs.releaseType }}
preid: ${{ inputs.preid }}
commit-message: 'chore: bump genkitx-mcp version to {{version}}'
tag-prefix: 'genkitx-mcp@'
commit-message: 'chore: bump @genkit-ai/mcp version to {{version}}'
tag-prefix: '@genkit-ai/mcp@'
- name: 'js/plugins/express version bump'
uses: 'phips28/gh-action-bump-version@master'
env:
Expand Down
48 changes: 31 additions & 17 deletions js/ai/src/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ export interface ExecutablePrompt<
O extends z.ZodTypeAny = z.ZodTypeAny,
CustomOptions extends z.ZodTypeAny = z.ZodTypeAny,
> {
/** Prompt reference. */
ref: { name: string; metadata?: Record<string, any> };

/**
* Generates a response by rendering the prompt template with given user input and then calling the model.
*
Expand Down Expand Up @@ -234,7 +237,8 @@ export function definePrompt<
return definePromptAsync(
registry,
`${options.name}${options.variant ? `.${options.variant}` : ''}`,
Promise.resolve(options)
Promise.resolve(options),
options.metadata
);
}

Expand All @@ -245,7 +249,8 @@ function definePromptAsync<
>(
registry: Registry,
name: string,
optionsPromise: PromiseLike<PromptConfig<I, O, CustomOptions>>
optionsPromise: PromiseLike<PromptConfig<I, O, CustomOptions>>,
metadata?: Record<string, any>
): ExecutablePrompt<z.infer<I>, O, CustomOptions> {
const promptCache = {} as PromptCache;

Expand Down Expand Up @@ -399,11 +404,13 @@ function definePromptAsync<
}
) as Promise<ExecutablePromptAction<I>>;

const executablePrompt = wrapInExecutablePrompt(
const executablePrompt = wrapInExecutablePrompt({
registry,
name,
renderOptionsFn,
rendererAction
);
rendererAction,
metadata,
});

return executablePrompt;
}
Expand Down Expand Up @@ -436,57 +443,64 @@ function wrapInExecutablePrompt<
I extends z.ZodTypeAny = z.ZodTypeAny,
O extends z.ZodTypeAny = z.ZodTypeAny,
CustomOptions extends z.ZodTypeAny = z.ZodTypeAny,
>(
registry: Registry,
>(wrapOpts: {
registry: Registry;
name: string;
renderOptionsFn: (
input: z.infer<I>,
renderOptions: PromptGenerateOptions<O, CustomOptions> | undefined
) => Promise<GenerateOptions>,
rendererAction: Promise<PromptAction<I>>
) {
) => Promise<GenerateOptions>;
rendererAction: Promise<PromptAction<I>>;
metadata?: Record<string, any>;
}) {
const executablePrompt = (async (
input?: I,
opts?: PromptGenerateOptions<O, CustomOptions>
): Promise<GenerateResponse<z.infer<O>>> => {
return await runInNewSpan(
registry,
wrapOpts.registry,
{
metadata: {
name: (await rendererAction).__action.name,
name: (await wrapOpts.rendererAction).__action.name,
input,
},
labels: {
[SPAN_TYPE_ATTR]: 'dotprompt',
},
},
async (metadata) => {
const output = await generate(registry, {
...(await renderOptionsFn(input, opts)),
const output = await generate(wrapOpts.registry, {
...(await wrapOpts.renderOptionsFn(input, opts)),
});
metadata.output = output;
return output;
}
);
}) as ExecutablePrompt<z.infer<I>, O, CustomOptions>;

executablePrompt.ref = { name: wrapOpts.name, metadata: wrapOpts.metadata };

executablePrompt.render = async (
input?: I,
opts?: PromptGenerateOptions<O, CustomOptions>
): Promise<GenerateOptions<O, CustomOptions>> => {
return {
...(await renderOptionsFn(input, opts)),
...(await wrapOpts.renderOptionsFn(input, opts)),
} as GenerateOptions<O, CustomOptions>;
};

executablePrompt.stream = (
input?: I,
opts?: PromptGenerateOptions<O, CustomOptions>
): GenerateStreamResponse<z.infer<O>> => {
return generateStream(registry, renderOptionsFn(input, opts));
return generateStream(
wrapOpts.registry,
wrapOpts.renderOptionsFn(input, opts)
);
};

executablePrompt.asTool = async (): Promise<ToolAction<I, O>> => {
return (await rendererAction) as unknown as ToolAction<I, O>;
return (await wrapOpts.rendererAction) as unknown as ToolAction<I, O>;
};
return executablePrompt;
}
Expand Down
7 changes: 4 additions & 3 deletions js/genkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,20 @@
"author": "genkit",
"license": "Apache-2.0",
"dependencies": {
"@genkit-ai/core": "workspace:*",
"@genkit-ai/ai": "workspace:*",
"@genkit-ai/core": "workspace:*",
"uuid": "^10.0.0"
},
"devDependencies": {
"@types/body-parser": "^1.19.5",
"@types/express": "^4.17.21",
"@types/node": "^22.15.3",
"@types/uuid": "^9.0.6",
"npm-run-all": "^4.1.5",
"rimraf": "^6.0.1",
"tsup": "^8.3.5",
"typescript": "^4.9.0",
"tsx": "^4.19.2",
"@types/body-parser": "^1.19.5"
"typescript": "^4.9.0"
},
"files": [
"genkit-ui",
Expand Down
5 changes: 3 additions & 2 deletions js/genkit/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export {
BaseDataPointSchema,
Document,
DocumentDataSchema,
GenerateResponse,
GenerateResponseChunk,
GenerationBlockedError,
GenerationCommonConfigSchema,
GenerationResponseError,
Expand Down Expand Up @@ -57,8 +59,6 @@ export {
type GenerateOptions,
type GenerateRequest,
type GenerateRequestData,
type GenerateResponse,
type GenerateResponseChunk,
type GenerateResponseChunkData,
type GenerateResponseData,
type GenerateStreamOptions,
Expand All @@ -82,6 +82,7 @@ export {
type Part,
type PromptAction,
type PromptConfig,
type PromptGenerateOptions,
type RankedDocument,
type RerankerAction,
type RerankerArgument,
Expand Down
2 changes: 2 additions & 0 deletions js/genkit/src/genkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,8 @@ export class Genkit implements HasRegistry {
return (await promise)(input, opts);
}) as ExecutablePrompt<z.infer<I>, O, CustomOptions>;

executablePrompt.ref = { name };

executablePrompt.render = async (
input?: I,
opts?: PromptGenerateOptions<O, CustomOptions>
Expand Down
30 changes: 30 additions & 0 deletions js/genkit/tests/prompts_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,36 @@ describe('definePrompt', () => {
defineEchoModel(ai);
});

it('should define the prompt', async () => {
const prompt = ai.definePrompt({
name: 'hi',
metadata: { foo: 'bar' },
input: {
schema: z.object({
name: z.string(),
}),
},
messages: async (input) => {
return [
{
role: 'user',
content: [{ text: `hi ${input.name}` }],
},
];
},
});

assert.deepStrictEqual(prompt.ref, {
name: 'hi',
metadata: { foo: 'bar' },
});

const lookedUpPrompt = ai.prompt('hi');
// This is a known limitation -- prompt lookup is async under the hood,
// so we can't actually get the metadata...
assert.deepStrictEqual(lookedUpPrompt.ref, { name: 'hi' }); // ideally metadatashould be: { foo: 'bar' }
});

it('should apply middleware to a prompt call', async () => {
const prompt = ai.definePrompt({
name: 'hi',
Expand Down
Loading