Skip to content

Commit 4c8db8d

Browse files
fix: mcp output structure (#26)
1 parent 967038b commit 4c8db8d

File tree

112 files changed

+691
-630
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

112 files changed

+691
-630
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
${{ runner.os }}-pnpm-store-
4040
4141
- name: Install dependencies
42-
run: pnpm install --force
42+
run: pnpm install
4343

4444
- name: Check formatting with Prettier
4545
run: pnpm prettier --check "packages/**/*.{ts,tsx}"

packages/core/src/interfaces/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { z } from 'zod';
22
import { Account, RpcProvider } from 'starknet';
33

4+
export interface toolResult {
5+
status: 'success' | 'failure';
6+
data?: Record<string, any> | Array<any>;
7+
error?: string;
8+
}
9+
410
export interface mcpTool {
511
name: string;
612
description: string;

packages/core/src/utils/index.ts

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,25 @@ const extractBaseSchema = (schema: z.ZodTypeAny): z.ZodObject<any> => {
2626
throw new Error('Unable to extract base schema from provided Zod schema');
2727
};
2828

29+
/**
30+
* Format tool execution result for MCP response
31+
* @param result - The result object from tool execution
32+
* @returns Formatted MCP response
33+
*/
34+
const formatToolResult = (result: any) => {
35+
return {
36+
isError: result.status === 'failure' ? true : false,
37+
content: [
38+
{
39+
type: 'text',
40+
text: JSON.stringify(
41+
result.status === 'failure' ? result.error : result.data
42+
),
43+
},
44+
],
45+
};
46+
};
47+
2948
/**
3049
* Register MCP tools with a server instance
3150
* @param server - The MCP server instance
@@ -39,31 +58,17 @@ export const registerToolsWithServer = async (
3958
if (!tool.schema) {
4059
server.tool(tool.name, tool.description, async () => {
4160
const result = await tool.execute({});
42-
return {
43-
content: [
44-
{
45-
type: 'text',
46-
text: JSON.stringify(result),
47-
},
48-
],
49-
};
61+
return formatToolResult(result);
5062
});
5163
} else {
5264
const baseSchema = extractBaseSchema(tool.schema);
5365
server.tool(
5466
tool.name,
5567
tool.description,
5668
baseSchema.shape,
57-
async (params: any, extra: any) => {
69+
async (params: any, _extra: any) => {
5870
const result = await tool.execute(params);
59-
return {
60-
content: [
61-
{
62-
type: 'text',
63-
text: JSON.stringify(result),
64-
},
65-
],
66-
};
71+
return formatToolResult(result);
6772
}
6873
);
6974
}

packages/mcp/__test__/e2e/env.test.js

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
33
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
44

55
const transport = new StdioClientTransport({
6-
command: 'npx',
7-
args: ['-y', '@kasarlabs/ask-starknet-mcp@latest'],
6+
command: 'node',
7+
args: ['build/index.js'],
88
});
99

1010
const client = new Client({
@@ -14,41 +14,16 @@ const client = new Client({
1414

1515
try {
1616
await client.connect(transport);
17-
18-
// const result = await client.callTool({
19-
// name: "perform_starknet_actions",
20-
// arguments: {
21-
// userInput: "I want to create a new Argent account, then transfer 0.2 STRK to it with my account, then deploy it"
22-
// }
23-
// });
24-
2517
const result = await client.callTool({
2618
name: 'ask_starknet',
2719
arguments: {
28-
userInput: 'can you bridge my fund with starkgate?',
20+
userInput: 'can you deploy account argent 0x2251?',
21+
rawTools: true,
2922
},
3023
});
24+
console.log(result);
3125
const response = JSON.parse(result.content[0].text);
3226
console.log(response);
33-
34-
const result2 = await client.callTool({
35-
name: 'ask_starknet',
36-
arguments: {
37-
userInput: 'help',
38-
},
39-
});
40-
41-
const response2 = JSON.parse(result2.content[0].text);
42-
console.log(response2);
43-
44-
const result3 = await client.callTool({
45-
name: 'ask_starknet',
46-
arguments: {
47-
userInput: 'how can i use ask-starknet to build a project in defi',
48-
},
49-
});
50-
const response3 = JSON.parse(result3.content[0].text);
51-
console.log(response3);
5227
} catch (error) {
5328
console.error('Error:', error.message);
5429
} finally {

packages/mcp/mcp-test.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"mcpServers": {
3+
"ask-starknet": {
4+
"command": "node",
5+
"args": ["build/index.js"],
6+
"env": {
7+
"NODE_ENV": "local"
8+
}
9+
}
10+
}
11+
}

packages/mcp/src/graph/agents/specialized.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,21 @@ import { MCPEnvironment } from '../mcps/interfaces.js';
1313
import { createLLM } from '../../utils/llm.js';
1414
import { specializedPrompt } from './prompts.js';
1515

16+
// Cache for LLM instances to avoid recreating them and accumulating listeners
17+
const llmCache = new Map<string, ReturnType<typeof createLLM>>();
18+
19+
function getCachedLLM(env: MCPEnvironment) {
20+
const cacheKey =
21+
env.ANTHROPIC_API_KEY || env.GEMINI_API_KEY || env.OPENAI_API_KEY || '';
22+
23+
if (!llmCache.has(cacheKey)) {
24+
const llm = createLLM(env);
25+
llmCache.set(cacheKey, llm);
26+
}
27+
28+
return llmCache.get(cacheKey)!;
29+
}
30+
1631
async function specializedAgent(mcpServerName: string, env: MCPEnvironment) {
1732
const client = new MultiServerMCPClient({
1833
mcpServers: {
@@ -28,7 +43,7 @@ async function specializedAgent(mcpServerName: string, env: MCPEnvironment) {
2843
{}
2944
);
3045

31-
const llm = createLLM(env);
46+
const llm = getCachedLLM(env);
3247

3348
if (!llm.bindTools) {
3449
throw new Error('The selected LLM model does not support tool binding');

packages/mcps/argent/src/tools/createAccount.ts

Lines changed: 10 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { RpcProvider } from 'starknet';
22
import { ARGENT_CLASS_HASH } from '../lib/constant/contract.js';
33
import { AccountManager } from '../lib/utils/AccountManager.js';
4-
import { onchainRead } from '@kasarlabs/ask-starknet-core';
4+
import { onchainRead, toolResult } from '@kasarlabs/ask-starknet-core';
55

66
/**
77
* Creates a new Argent account.
@@ -11,55 +11,22 @@ import { onchainRead } from '@kasarlabs/ask-starknet-core';
1111
* @returns {Promise<object>} Object with account details
1212
* @throws {Error} If account creation fails
1313
*/
14-
export const CreateArgentAccount = async (env: onchainRead) => {
14+
export const CreateArgentAccount = async (
15+
env: onchainRead
16+
): Promise<toolResult> => {
1517
try {
1618
const accountManager = new AccountManager(env.provider);
1719
const accountDetails =
1820
await accountManager.createAccount(ARGENT_CLASS_HASH);
1921

2022
return {
2123
status: 'success',
22-
wallet: 'AX',
23-
publicKey: accountDetails.publicKey,
24-
privateKey: accountDetails.privateKey,
25-
contractAddress: accountDetails.contractAddress,
26-
};
27-
} catch (error) {
28-
return {
29-
status: 'failure',
30-
error: error instanceof Error ? error.message : 'Unknown error',
31-
};
32-
}
33-
};
34-
35-
/**
36-
* Creates an Argent account with deployment fee estimation.
37-
* @async
38-
* @function CreateArgentAccountSignature
39-
* @returns {Promise<object>} Object with account and fee details
40-
* @throws {Error} If creation or fee estimation fails
41-
*/
42-
export const CreateArgentAccountSignature = async () => {
43-
try {
44-
const provider = new RpcProvider({ nodeUrl: process.env.STARKNET_RPC_URL });
45-
46-
const accountManager = new AccountManager(provider);
47-
const accountDetails =
48-
await accountManager.createAccount(ARGENT_CLASS_HASH);
49-
const suggestedMaxFee = await accountManager.estimateAccountDeployFee(
50-
ARGENT_CLASS_HASH,
51-
accountDetails
52-
);
53-
const maxFee = suggestedMaxFee.suggestedMaxFee * 2n;
54-
55-
return {
56-
status: 'success',
57-
transaction_type: 'CREATE_ACCOUNT',
58-
wallet: 'AX',
59-
publicKey: accountDetails.publicKey,
60-
privateKey: accountDetails.privateKey,
61-
contractAddress: accountDetails.contractAddress,
62-
deployFee: maxFee.toString(),
24+
data: {
25+
wallet: 'AX',
26+
publicKey: accountDetails.publicKey,
27+
privateKey: accountDetails.privateKey,
28+
contractAddress: accountDetails.contractAddress,
29+
},
6330
};
6431
} catch (error) {
6532
return {

packages/mcps/argent/src/tools/deployAccount.ts

Lines changed: 7 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ARGENT_CLASS_HASH } from '../lib/constant/contract.js';
33
import { AccountManager } from '../lib/utils/AccountManager.js';
44
import { z } from 'zod';
55
import { accountDetailsSchema } from '../schemas/schema.js';
6-
import { onchainRead } from '@kasarlabs/ask-starknet-core';
6+
import { onchainRead, toolResult } from '@kasarlabs/ask-starknet-core';
77
/**
88
* Deploys an Argent account using RPC provider.
99
* @async
@@ -16,47 +16,18 @@ import { onchainRead } from '@kasarlabs/ask-starknet-core';
1616
export const DeployArgentAccount = async (
1717
env: onchainRead,
1818
params: z.infer<typeof accountDetailsSchema>
19-
) => {
19+
): Promise<toolResult> => {
2020
try {
2121
const accountManager = new AccountManager(env.provider);
2222
const tx = await accountManager.deployAccount(ARGENT_CLASS_HASH, params);
2323

2424
return {
2525
status: 'success',
26-
wallet: 'AX',
27-
transaction_hash: tx.transactionHash,
28-
contract_address: tx.contractAddress,
29-
};
30-
} catch (error) {
31-
return {
32-
status: 'failure',
33-
error: error instanceof Error ? error.message : 'Unknown error',
34-
};
35-
}
36-
};
37-
38-
/**
39-
* Deploys an Argent account using RPC.
40-
* @async
41-
* @function DeployArgentAccountSignature
42-
* @param {z.infer<typeof accountDetailsSchema>} params - Account details
43-
* @returns {Promise<string>} JSON string with deployment result
44-
* @throws {Error} If deployment fails
45-
*/
46-
export const DeployArgentAccountSignature = async (
47-
params: z.infer<typeof accountDetailsSchema>
48-
) => {
49-
try {
50-
const provider = new RpcProvider({ nodeUrl: process.env.STARKNET_RPC_URL });
51-
52-
const accountManager = new AccountManager(provider);
53-
const tx = await accountManager.deployAccount(ARGENT_CLASS_HASH, params);
54-
55-
return {
56-
status: 'success',
57-
wallet: 'AX',
58-
transaction_hash: tx.transactionHash,
59-
contract_address: tx.contractAddress,
26+
data: {
27+
wallet: 'AX',
28+
transaction_hash: tx.transactionHash,
29+
contract_address: tx.contractAddress,
30+
},
6031
};
6132
} catch (error) {
6233
return {

packages/mcps/artpeace/src/tools/placePixel.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { artpeaceAddr } from '../lib/constants/artpeace.js';
44
import { ArtpeaceHelper } from '../lib/utils/helper.js';
55
import { placePixelParam } from '../schemas/index.js';
66
import { Checker } from '../lib/utils/checker.js';
7-
import { onchainWrite } from '@kasarlabs/ask-starknet-core';
7+
import { onchainWrite, toolResult } from '@kasarlabs/ask-starknet-core';
88

99
/**
1010
* Places pixels on a Starknet canvas using the Artpeace contract
@@ -15,7 +15,7 @@ import { onchainWrite } from '@kasarlabs/ask-starknet-core';
1515
export const placePixel = async (
1616
env: onchainWrite,
1717
input: { params: placePixelParam[] }
18-
) => {
18+
): Promise<toolResult> => {
1919
try {
2020
const { params } = input;
2121
const account = env.account;
@@ -49,15 +49,12 @@ export const placePixel = async (
4949

5050
return {
5151
status: 'success',
52-
transaction_hash: txHash,
52+
data: { transaction_hash: txHash },
5353
};
5454
} catch (error: any) {
5555
return {
56-
status: 'error',
57-
error: {
58-
code: 'PLACE_PIXEL_DATA_ERROR',
59-
message: error.message || 'Failed to place a pixel',
60-
},
56+
status: 'failure',
57+
error: error.message || 'Failed to place a pixel',
6158
};
6259
}
6360
};

packages/mcps/avnu/src/tools/fetchRoute.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { fetchQuotes, QuoteRequest } from '@avnu/avnu-sdk';
2-
import { onchainRead, onchainWrite } from '@kasarlabs/ask-starknet-core';
2+
import {
3+
onchainRead,
4+
onchainWrite,
5+
toolResult,
6+
} from '@kasarlabs/ask-starknet-core';
37
import { TokenService } from './fetchTokens.js';
48
import { RouteSchemaType } from '../schemas/index.js';
59
import { RouteResult } from '../interfaces/index.js';
@@ -96,17 +100,24 @@ export class RouteFetchService {
96100
* @param {string} accountAddress - The account address
97101
* @returns {Promise<RouteResult>} The route fetch result
98102
*/
99-
export const getRoute = async (env: onchainWrite, params: RouteSchemaType) => {
103+
export const getRoute = async (
104+
env: onchainWrite,
105+
params: RouteSchemaType
106+
): Promise<toolResult> => {
100107
try {
101108
const routeService = new RouteFetchService();
102109
const result = await routeService.fetchRoute(
103110
params,
104111
env,
105112
env.account.address
106113
);
107-
return JSON.stringify(result, (_, value) =>
114+
const res = JSON.stringify(result, (_, value) =>
108115
typeof value === 'bigint' ? value.toString() : value
109116
);
117+
return {
118+
status: 'success',
119+
data: { res },
120+
};
110121
} catch (error) {
111122
return {
112123
status: 'failure',

0 commit comments

Comments
 (0)