Skip to content

Commit d92e244

Browse files
feat: add Mastra framework extension (@coinbase/agentkit-mastra)
Adds a new framework extension that converts AgentKit actions into Mastra-compatible tools via getMastraTools(), enabling AgentKit usage with the Mastra AI agent framework. - Follows the same pattern as existing LangChain and Vercel AI SDK extensions - Maps AgentKit Action (name, description, schema, invoke) to Mastra createTool - Includes unit tests (3 test cases), README with usage examples - Targets @mastra/core >= 1.0.0 (1.x createTool execute signature) - Zod 4 compatible across all packages Resolves WISHLIST.md item: "Mastra" under AI Framework Support.
1 parent 0fe026b commit d92e244

12 files changed

Lines changed: 1072 additions & 785 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"parser": "@typescript-eslint/parser",
3+
"extends": ["../../.eslintrc.base.json"]
4+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
docs/
2+
dist/
3+
coverage/
4+
.github/
5+
src/client
6+
**/**/*.json
7+
*.md
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"tabWidth": 2,
3+
"useTabs": false,
4+
"semi": true,
5+
"singleQuote": false,
6+
"trailingComma": "all",
7+
"bracketSpacing": true,
8+
"arrowParens": "avoid",
9+
"printWidth": 100,
10+
"proseWrap": "never"
11+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Coinbase Agentkit Extension - Mastra
2+
3+
This package is an extension used to easily plug [AgentKit](https://docs.cdp.coinbase.com/agentkit/docs/welcome) into [Mastra](https://mastra.ai/), the TypeScript AI agent framework.
4+
5+
## Installation
6+
7+
For a single command to install all necessary dependencies, run:
8+
9+
```bash
10+
npm install @coinbase/agentkit-mastra @coinbase/agentkit @mastra/core @ai-sdk/openai
11+
```
12+
13+
To break it down, this package is:
14+
15+
```bash
16+
npm install @coinbase/agentkit-mastra
17+
```
18+
19+
This package is used alongside AgentKit and Mastra, so these will need to be installed as well.
20+
21+
```bash
22+
npm install @coinbase/agentkit @mastra/core
23+
```
24+
25+
Finally, install the model provider you want to use. For example, to use OpenAI, install the `@ai-sdk/openai` package. See [here](https://mastra.ai/docs) for more information on Mastra's supported model providers.
26+
27+
```bash
28+
npm install @ai-sdk/openai
29+
```
30+
31+
## Usage
32+
33+
The main export of this package is the `getMastraTools` function. This function takes an AgentKit instance and returns a record of Mastra-compatible tools. These tools can then be passed directly to a Mastra agent.
34+
35+
Here's a snippet of code that shows how to use the `getMastraTools` function to get the tools for the AgentKit agent.
36+
37+
###### agent.ts
38+
39+
```typescript
40+
import { getMastraTools } from "@coinbase/agentkit-mastra";
41+
import { AgentKit } from "@coinbase/agentkit";
42+
import { Agent } from "@mastra/core/agent";
43+
import { openai } from "@ai-sdk/openai";
44+
45+
// Get your Coinbase Developer Platform API key from the Portal: https://portal.cdp.coinbase.com/
46+
// Or, check out one of the other supported wallet providers: https://github.com/coinbase/agentkit/tree/main/typescript/agentkit
47+
const agentKit = await AgentKit.from({
48+
cdpApiKeyId: process.env.CDP_API_KEY_ID,
49+
cdpApiKeySecret: process.env.CDP_API_KEY_SECRET,
50+
});
51+
52+
const tools = getMastraTools(agentKit);
53+
54+
const agent = new Agent({
55+
name: "Onchain Agent",
56+
instructions: "You are an onchain AI assistant with access to a wallet.",
57+
model: openai("gpt-4o-mini"), // Make sure to have OPENAI_API_KEY set in your environment variables
58+
tools,
59+
});
60+
61+
const response = await agent.generate("Print wallet details");
62+
console.log(response.text);
63+
```
64+
65+
## Contributing
66+
67+
We welcome contributions of all kinds! Please see our [Contributing Guide](https://github.com/coinbase/agentkit/blob/main/CONTRIBUTING.md) for detailed setup instructions and contribution guidelines.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const baseConfig = require("../../jest.config.base.cjs");
2+
3+
module.exports = {
4+
...baseConfig,
5+
setupFilesAfterEnv: ["<rootDir>/setup-jest.js"],
6+
coveragePathIgnorePatterns: ["node_modules", "dist", "docs", "index.ts"],
7+
coverageThreshold: {},
8+
};
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"name": "@coinbase/agentkit-mastra",
3+
"version": "0.1.0",
4+
"description": "Mastra framework extension of CDP Agentkit",
5+
"repository": "https://github.com/coinbase/agentkit",
6+
"author": "Coinbase Inc.",
7+
"license": "Apache-2.0",
8+
"main": "dist/index.js",
9+
"types": "dist/index.d.ts",
10+
"files": [
11+
"dist"
12+
],
13+
"scripts": {
14+
"build": "tsc",
15+
"lint": "eslint -c .eslintrc.json \"src/**/*.ts\"",
16+
"lint:fix": "eslint -c .eslintrc.json \"src/**/*.ts\" --fix",
17+
"format": "prettier -c .prettierrc --write \"**/*.{ts,js,cjs,json,md}\"",
18+
"format:check": "prettier -c .prettierrc --check \"**/*.{ts,js,cjs,json,md}\"",
19+
"check": "tsc --noEmit",
20+
"test": "jest --no-cache --testMatch='**/*.test.ts'",
21+
"clean": "rm -rf dist/*",
22+
"prepack": "tsc",
23+
"docs": "typedoc --entryPoints ./src --entryPointStrategy expand --exclude ./src/tests/**/*.ts",
24+
"docs:serve": "http-server ./docs",
25+
"dev": "tsc --watch"
26+
},
27+
"keywords": [
28+
"coinbase",
29+
"sdk",
30+
"crypto",
31+
"cdp",
32+
"agentkit",
33+
"ai",
34+
"agent",
35+
"nodejs",
36+
"typescript",
37+
"mastra"
38+
],
39+
"dependencies": {
40+
"zod": "^4.0.0"
41+
},
42+
"devDependencies": {
43+
"@coinbase/agentkit": "workspace:*"
44+
},
45+
"peerDependencies": {
46+
"@coinbase/agentkit": ">=0.1.0",
47+
"@mastra/core": ">=1.0.0"
48+
}
49+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
jest.mock("jose", () => ({}));
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { z } from "zod";
2+
import { getMastraTools } from "./getMastraTools";
3+
import { AgentKit } from "@coinbase/agentkit";
4+
5+
// Mock AgentKit before importing - this prevents loading ES-only dependencies
6+
jest.mock("@coinbase/agentkit", () => ({
7+
AgentKit: {
8+
from: jest.fn(),
9+
},
10+
}));
11+
12+
// Mock @mastra/core/tools
13+
jest.mock("@mastra/core/tools", () => ({
14+
createTool: jest.fn((config: Record<string, unknown>) => ({
15+
id: config.id,
16+
description: config.description,
17+
inputSchema: config.inputSchema,
18+
execute: config.execute,
19+
})),
20+
}));
21+
22+
// Define mock action after imports
23+
const mockAction = {
24+
name: "testAction",
25+
description: "A test action",
26+
schema: z.object({ test: z.string() }),
27+
invoke: jest.fn(async (arg: { test: string }) => `Invoked with ${arg.test}`),
28+
};
29+
30+
// Configure the mock
31+
(AgentKit.from as jest.Mock).mockImplementation(() => ({
32+
getActions: jest.fn(() => [mockAction]),
33+
}));
34+
35+
describe("getMastraTools", () => {
36+
it("should return a record of tools with correct properties", async () => {
37+
const mockAgentKit = await AgentKit.from({});
38+
const tools = getMastraTools(mockAgentKit);
39+
40+
expect(tools).toHaveProperty("testAction");
41+
const tool = tools.testAction;
42+
43+
expect(tool.id).toBe(mockAction.name);
44+
expect(tool.description).toBe(mockAction.description);
45+
expect(tool.inputSchema).toBe(mockAction.schema);
46+
47+
// Test execution
48+
const result = await tool.execute!({ test: "data" });
49+
expect(result).toBe("Invoked with data");
50+
});
51+
52+
it("should handle multiple actions", async () => {
53+
const secondAction = {
54+
name: "secondAction",
55+
description: "A second test action",
56+
schema: z.object({ value: z.number() }),
57+
invoke: jest.fn(async (arg: { value: number }) => `Value is ${arg.value}`),
58+
};
59+
60+
(AgentKit.from as jest.Mock).mockImplementation(() => ({
61+
getActions: jest.fn(() => [mockAction, secondAction]),
62+
}));
63+
64+
const mockAgentKit = await AgentKit.from({});
65+
const tools = getMastraTools(mockAgentKit);
66+
67+
expect(Object.keys(tools)).toHaveLength(2);
68+
expect(tools).toHaveProperty("testAction");
69+
expect(tools).toHaveProperty("secondAction");
70+
71+
const result = await tools.secondAction.execute!({ value: 42 });
72+
expect(result).toBe("Value is 42");
73+
});
74+
75+
it("should return an empty record when no actions are available", async () => {
76+
(AgentKit.from as jest.Mock).mockImplementation(() => ({
77+
getActions: jest.fn(() => []),
78+
}));
79+
80+
const mockAgentKit = await AgentKit.from({});
81+
const tools = getMastraTools(mockAgentKit);
82+
83+
expect(Object.keys(tools)).toHaveLength(0);
84+
});
85+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Main exports for the CDP Mastra package
3+
*/
4+
5+
import { z } from "zod";
6+
import { AgentKit, type Action } from "@coinbase/agentkit";
7+
import { createTool } from "@mastra/core/tools";
8+
9+
/**
10+
* The return type of getMastraTools - a record mapping action names to Mastra tools
11+
*/
12+
export type MastraToolSet = Record<string, ReturnType<typeof createTool>>;
13+
14+
/**
15+
* Get Mastra tools from an AgentKit instance
16+
*
17+
* @param agentKit - The AgentKit instance
18+
* @returns A record of Mastra tools keyed by action name, compatible with Mastra agents
19+
*/
20+
export function getMastraTools(agentKit: AgentKit): MastraToolSet {
21+
const actions: Action[] = agentKit.getActions();
22+
return actions.reduce<MastraToolSet>((acc, action) => {
23+
acc[action.name] = createTool({
24+
id: action.name,
25+
description: action.description,
26+
inputSchema: action.schema,
27+
execute: async (arg: z.output<typeof action.schema>) => {
28+
const result = await action.invoke(arg);
29+
return result;
30+
},
31+
});
32+
return acc;
33+
}, {});
34+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./getMastraTools";

0 commit comments

Comments
 (0)