Skip to content

Commit a860506

Browse files
feat: add MCP adapter and example MCP server (#170)
Signed-off-by: skurzyp-blockydevs <stanislaw.kurzyp@blockydevs.com>
1 parent 79fc749 commit a860506

File tree

9 files changed

+4448
-3
lines changed

9 files changed

+4448
-3
lines changed

modelcontextprotocol/README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Hedera MCP Server
2+
3+
This directory contains the MCP server implementation for Hedera Agent Kit.
4+
5+
## Antigravity (VS Code) Configuration
6+
7+
To use this MCP server in Antigravity or VS Code, add the following to your MCP settings configuration file (e.g. `~/.gemini/antigravity/mcp_config.json` or VS Code settings).
8+
9+
**Important**: You must use **absolute paths** for both the python script and the working directory.
10+
11+
```json
12+
{
13+
"mcpServers": {
14+
"hedera-py": {
15+
"command": "/ABSOLUTE/PATH/TO/poetry",
16+
"args": [
17+
"-C",
18+
"/ABSOLUTE/PATH/TO/hedera-agent-kit-py/modelcontextprotocol",
19+
"run",
20+
"python",
21+
"server.py"
22+
],
23+
"env": {
24+
"HEDERA_OPERATOR_ID": "0.0.xxxxx",
25+
"HEDERA_OPERATOR_KEY": "302e..."
26+
}
27+
}
28+
}
29+
}
30+
```
31+
32+
## Available Tools
33+
34+
The server defaults to a minimal set of plugins (Token, Account, Consensus) to stay within tool limits (e.g. Antigravity is limited to 100 tools).
35+
36+
To enable more capabilities (EVM, Queries, etc.), you must manually update `server.py` to import and register additional plugins from `hedera_agent_kit.plugins`.
37+
38+
Available plugins include:
39+
- `core_evm_plugin`
40+
- `core_token_query_plugin`
41+
- `core_account_query_plugin`
42+
- `core_consensus_query_plugin`
43+
- `core_transaction_query_plugin`
44+
- `core_misc_query_plugin`
45+
46+
You can filter enabled tools using the `--tools` argument.
47+
48+
## Configuration Arguments
49+
50+
The `server.py` script accepts the following command-line arguments:
51+
52+
| Argument | Description | Default | Choices |
53+
| :--- | :--- | :--- | :--- |
54+
| `--tools` | Comma-separated list of tools to enable. | All available | `all` or specific tool names |
55+
| `--agent-mode` | Execution mode for the agent. | `autonomous` | `autonomous`, `returnBytes` |
56+
| `--account-id` | Hedera Account ID to use for operations. | None | Valid Account ID (e.g. `0.0.1234`) |
57+
| `--public-key` | Public Key for the account. **Must be DER encoded.** | None | DER encoded string |
58+
| `--ledger-id` | Network to connect to. | `testnet` | `testnet`, `mainnet` |
59+
60+
### Key Formats
61+
62+
Both **ECDSA** and **ED25519** keys are supported. For **HEX** format of keys changes to the `server.py` code are required. Follow https://github.com/hiero-ledger/hiero-sdk-python for more information on `Client.set_operator` method.
63+

modelcontextprotocol/poetry.lock

Lines changed: 4236 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[tool.poetry]
2+
name = "hedera-mcp-server"
3+
version = "0.1.0"
4+
description = "MCP Server for Hedera Agent Kit"
5+
authors = ["Hashgraph"]
6+
license = "Apache-2.0"
7+
readme = "README.md"
8+
packages = [{ include = "server.py" }]
9+
10+
[tool.poetry.dependencies]
11+
python = ">=3.10,<3.14"
12+
mcp = "1.25.0"
13+
python-dotenv = ">=1,<2"
14+
hedera-agent-kit = { path = "../python", develop = true }
15+
16+
[build-system]
17+
requires = ["poetry-core>=2.0.0,<3.0.0"]
18+
build-backend = "poetry.core.masonry.api"

modelcontextprotocol/server.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
2+
import sys
3+
import os
4+
import argparse
5+
from dotenv import load_dotenv
6+
7+
# Redirect stdout to stderr immediately to avoid polluting an MCP channel
8+
original_stdout = sys.stdout
9+
sys.stdout = sys.stderr
10+
11+
from hiero_sdk_python import Client, Network, AccountId, PrivateKey
12+
from hedera_agent_kit.mcp import HederaMCPToolkit
13+
from hedera_agent_kit.shared.configuration import Configuration, Context
14+
from hedera_agent_kit.plugins import (
15+
core_token_plugin,
16+
core_account_plugin,
17+
core_consensus_plugin, core_consensus_query_plugin,
18+
)
19+
20+
def log(message: str, level: str = "info"):
21+
prefix = "❌" if level == "error" else "⚠️" if level == "warn" else "✅"
22+
sys.stderr.write(f"{prefix} {message}\n")
23+
24+
def parse_args():
25+
parser = argparse.ArgumentParser(description="Hedera MCP Server")
26+
parser.add_argument("--tools", type=str, help="Comma-separated list of tools to enable")
27+
parser.add_argument("--agent-mode", type=str, choices=["autonomous", "returnBytes"], default="autonomous", help="Agent mode")
28+
parser.add_argument("--account-id", type=str, help="Hedera Account ID")
29+
parser.add_argument("--public-key", type=str, help="Public Key")
30+
parser.add_argument("--ledger-id", type=str, choices=["testnet", "mainnet"], default="testnet", help="Ledger ID")
31+
return parser.parse_args()
32+
33+
def main():
34+
load_dotenv(os.path.join(os.getcwd(), ".env")) # or parent .env
35+
load_dotenv(os.path.join(os.path.dirname(os.getcwd()), ".env"))
36+
37+
args = parse_args()
38+
39+
# Client setup
40+
if args.ledger_id == "mainnet":
41+
network: Network = Network(
42+
network="mainnet"
43+
)
44+
client: Client = Client(network)
45+
log("Using Mainnet", "info")
46+
else:
47+
network: Network = Network(
48+
network="testnet"
49+
)
50+
client: Client = Client(network)
51+
log("Using Testnet", "info")
52+
53+
operator_id = os.getenv("HEDERA_OPERATOR_ID")
54+
operator_key = os.getenv("HEDERA_OPERATOR_KEY")
55+
56+
if operator_id and operator_key:
57+
try:
58+
client.set_operator(AccountId.from_string(operator_id),PrivateKey.from_string(operator_key))
59+
log(f"Operator set: {operator_id}", "info")
60+
except Exception as e:
61+
log(f"Failed to set operator: {e}", "error")
62+
raise
63+
else:
64+
log("No operator credentials found in environment variables", "warn")
65+
66+
context = Context(
67+
account_id=operator_id,
68+
account_public_key=PrivateKey.from_string(operator_key).public_key().to_string(),
69+
mode=args.agent_mode
70+
)
71+
72+
tools_list = args.tools.split(",") if args.tools and args.tools != "all" else None
73+
74+
config = Configuration(
75+
tools=tools_list,
76+
context=context,
77+
plugins=[core_token_plugin, core_account_plugin, core_consensus_plugin, core_consensus_query_plugin]
78+
)
79+
80+
server = HederaMCPToolkit(client, config)
81+
82+
sys.stdout = original_stdout
83+
84+
log("Hedera MCP Server running on stdio", "info")
85+
# Run the server
86+
server.run()
87+
88+
if __name__ == "__main__":
89+
try:
90+
main()
91+
except Exception as e:
92+
log(f"Error initializing Hedera MCP server: {str(e)}", "error")
93+
sys.exit(1)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .toolkit import HederaMCPToolkit
2+
3+
__all__ = ["HederaMCPToolkit"]
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from typing import Any, List
2+
from mcp.server.fastmcp import FastMCP
3+
from hiero_sdk_python import Client
4+
from hedera_agent_kit.shared.api import HederaAgentAPI
5+
from hedera_agent_kit.shared.configuration import Configuration
6+
from hedera_agent_kit.shared.tool_discovery import ToolDiscovery
7+
8+
class HederaMCPToolkit:
9+
def __init__(self, client: Client, configuration: Configuration):
10+
self.server = FastMCP("Hedera Agent Kit", dependencies=["hedera-agent-kit"])
11+
12+
context = configuration.context
13+
tool_discovery = ToolDiscovery.create_from_configuration(configuration)
14+
all_tools = tool_discovery.get_all_tools(context, configuration)
15+
self._hedera_agent_kit = HederaAgentAPI(client, context, all_tools)
16+
17+
for tool in all_tools:
18+
self._register_tool(tool)
19+
20+
def _register_tool(self, tool):
21+
@self.server.tool(name=tool.method, description=tool.description)
22+
async def handler(**kwargs: Any) -> str:
23+
params = kwargs
24+
if "kwargs" in kwargs and len(kwargs) == 1 and isinstance(kwargs["kwargs"], dict):
25+
params = kwargs["kwargs"]
26+
result = await self._hedera_agent_kit.run(tool.method, params)
27+
return str(result)
28+
29+
def run(self):
30+
"""Run the MCP server (blocking)"""
31+
self.server.run(transport="stdio")

python/hedera_agent_kit/shared/parameter_schemas/token_schema.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ class NftTransfer(BaseModelWithArbitraryTypes):
151151

152152
class TransferNonFungibleTokenParameters(OptionalScheduledTransactionParams):
153153
source_account_id: Annotated[
154-
str, Field(description="Account ID of the token owner.")
155-
]
154+
Optional[str], Field(description="Account ID of the token owner (defaults to operator).")
155+
] = None
156156
token_id: Annotated[str, Field(description="The NFT token ID.")]
157157
recipients: Annotated[
158158
List[NftTransfer],

python/hedera_agent_kit/shared/utils/prompt_generator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def get_scheduled_transaction_params_description(context: Context) -> str:
9191
Generates parameter descriptions for scheduled transactions.
9292
"""
9393
default_account_desc = AccountResolver.get_default_account_description(context)
94-
return f"""schedulingParams (object, optional): Parameters for scheduling this transaction instead of executing immediately.
94+
return f"""scheduling_params (object, optional): Parameters for scheduling this transaction instead of executing immediately.
9595
9696
**Fields that apply to the *schedule entity*, not the inner transaction:**
9797

python/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ aiohttp = ">=3.13.1,<4.0.0"
1818
pydantic = "^2.12.3"
1919
hiero-sdk-python = "0.1.9"
2020
web3 = "==7.14.0"
21+
mcp = "1.25.0"
2122

2223
[tool.poetry.group.dev.dependencies]
2324
black = ">=25.9.0,<26.0.0"

0 commit comments

Comments
 (0)