Skip to content
63 changes: 63 additions & 0 deletions modelcontextprotocol/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Hedera MCP Server

This directory contains the MCP server implementation for Hedera Agent Kit.

## Antigravity (VS Code) Configuration

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).

**Important**: You must use **absolute paths** for both the python script and the working directory.

```json
{
"mcpServers": {
"hedera-py": {
"command": "/ABSOLUTE/PATH/TO/poetry",
"args": [
"-C",
"/ABSOLUTE/PATH/TO/hedera-agent-kit-py/modelcontextprotocol",
"run",
"python",
"server.py"
],
"env": {
"HEDERA_OPERATOR_ID": "0.0.xxxxx",
"HEDERA_OPERATOR_KEY": "302e..."
}
}
}
}
```

## Available Tools

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).

To enable more capabilities (EVM, Queries, etc.), you must manually update `server.py` to import and register additional plugins from `hedera_agent_kit.plugins`.

Available plugins include:
- `core_evm_plugin`
- `core_token_query_plugin`
- `core_account_query_plugin`
- `core_consensus_query_plugin`
- `core_transaction_query_plugin`
- `core_misc_query_plugin`

You can filter enabled tools using the `--tools` argument.

## Configuration Arguments

The `server.py` script accepts the following command-line arguments:

| Argument | Description | Default | Choices |
| :--- | :--- | :--- | :--- |
| `--tools` | Comma-separated list of tools to enable. | All available | `all` or specific tool names |
| `--agent-mode` | Execution mode for the agent. | `autonomous` | `autonomous`, `returnBytes` |
| `--account-id` | Hedera Account ID to use for operations. | None | Valid Account ID (e.g. `0.0.1234`) |
| `--public-key` | Public Key for the account. **Must be DER encoded.** | None | DER encoded string |
| `--ledger-id` | Network to connect to. | `testnet` | `testnet`, `mainnet` |

### Key Formats

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.

4,236 changes: 4,236 additions & 0 deletions modelcontextprotocol/poetry.lock

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions modelcontextprotocol/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[tool.poetry]
name = "hedera-mcp-server"
version = "0.1.0"
description = "MCP Server for Hedera Agent Kit"
authors = ["Hashgraph"]
license = "Apache-2.0"
readme = "README.md"
packages = [{ include = "server.py" }]

[tool.poetry.dependencies]
python = ">=3.10,<3.14"
mcp = "1.25.0"
python-dotenv = ">=1,<2"
hedera-agent-kit = { path = "../python", develop = true }

[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
93 changes: 93 additions & 0 deletions modelcontextprotocol/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@

import sys
import os
import argparse
from dotenv import load_dotenv

# Redirect stdout to stderr immediately to avoid polluting an MCP channel
original_stdout = sys.stdout
sys.stdout = sys.stderr

from hiero_sdk_python import Client, Network, AccountId, PrivateKey
from hedera_agent_kit.mcp import HederaMCPToolkit
from hedera_agent_kit.shared.configuration import Configuration, Context
from hedera_agent_kit.plugins import (
core_token_plugin,
core_account_plugin,
core_consensus_plugin, core_consensus_query_plugin,
)

def log(message: str, level: str = "info"):
prefix = "❌" if level == "error" else "⚠️" if level == "warn" else "✅"
sys.stderr.write(f"{prefix} {message}\n")

def parse_args():
parser = argparse.ArgumentParser(description="Hedera MCP Server")
parser.add_argument("--tools", type=str, help="Comma-separated list of tools to enable")
parser.add_argument("--agent-mode", type=str, choices=["autonomous", "returnBytes"], default="autonomous", help="Agent mode")
parser.add_argument("--account-id", type=str, help="Hedera Account ID")
parser.add_argument("--public-key", type=str, help="Public Key")
parser.add_argument("--ledger-id", type=str, choices=["testnet", "mainnet"], default="testnet", help="Ledger ID")
return parser.parse_args()

def main():
load_dotenv(os.path.join(os.getcwd(), ".env")) # or parent .env
load_dotenv(os.path.join(os.path.dirname(os.getcwd()), ".env"))

args = parse_args()

# Client setup
if args.ledger_id == "mainnet":
network: Network = Network(
network="mainnet"
)
client: Client = Client(network)
log("Using Mainnet", "info")
else:
network: Network = Network(
network="testnet"
)
client: Client = Client(network)
log("Using Testnet", "info")

operator_id = os.getenv("HEDERA_OPERATOR_ID")
operator_key = os.getenv("HEDERA_OPERATOR_KEY")

if operator_id and operator_key:
try:
client.set_operator(AccountId.from_string(operator_id),PrivateKey.from_string(operator_key))
log(f"Operator set: {operator_id}", "info")
except Exception as e:
log(f"Failed to set operator: {e}", "error")
raise
else:
log("No operator credentials found in environment variables", "warn")

context = Context(
account_id=operator_id,
account_public_key=PrivateKey.from_string(operator_key).public_key().to_string(),
mode=args.agent_mode
)

tools_list = args.tools.split(",") if args.tools and args.tools != "all" else None

config = Configuration(
tools=tools_list,
context=context,
plugins=[core_token_plugin, core_account_plugin, core_consensus_plugin, core_consensus_query_plugin]
)

server = HederaMCPToolkit(client, config)

sys.stdout = original_stdout

log("Hedera MCP Server running on stdio", "info")
# Run the server
server.run()

if __name__ == "__main__":
try:
main()
except Exception as e:
log(f"Error initializing Hedera MCP server: {str(e)}", "error")
sys.exit(1)
3 changes: 3 additions & 0 deletions python/hedera_agent_kit/mcp/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .toolkit import HederaMCPToolkit

__all__ = ["HederaMCPToolkit"]
31 changes: 31 additions & 0 deletions python/hedera_agent_kit/mcp/toolkit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from typing import Any, List
from mcp.server.fastmcp import FastMCP
from hiero_sdk_python import Client
from hedera_agent_kit.shared.api import HederaAgentAPI
from hedera_agent_kit.shared.configuration import Configuration
from hedera_agent_kit.shared.tool_discovery import ToolDiscovery

class HederaMCPToolkit:
def __init__(self, client: Client, configuration: Configuration):
self.server = FastMCP("Hedera Agent Kit", dependencies=["hedera-agent-kit"])

context = configuration.context
tool_discovery = ToolDiscovery.create_from_configuration(configuration)
all_tools = tool_discovery.get_all_tools(context, configuration)
self._hedera_agent_kit = HederaAgentAPI(client, context, all_tools)

for tool in all_tools:
self._register_tool(tool)

def _register_tool(self, tool):
@self.server.tool(name=tool.method, description=tool.description)
async def handler(**kwargs: Any) -> str:
params = kwargs
if "kwargs" in kwargs and len(kwargs) == 1 and isinstance(kwargs["kwargs"], dict):
params = kwargs["kwargs"]
result = await self._hedera_agent_kit.run(tool.method, params)
return str(result)

def run(self):
"""Run the MCP server (blocking)"""
self.server.run(transport="stdio")
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,8 @@ class NftTransfer(BaseModelWithArbitraryTypes):

class TransferNonFungibleTokenParameters(OptionalScheduledTransactionParams):
source_account_id: Annotated[
str, Field(description="Account ID of the token owner.")
]
Optional[str], Field(description="Account ID of the token owner (defaults to operator).")
] = None
token_id: Annotated[str, Field(description="The NFT token ID.")]
recipients: Annotated[
List[NftTransfer],
Expand Down
2 changes: 1 addition & 1 deletion python/hedera_agent_kit/shared/utils/prompt_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def get_scheduled_transaction_params_description(context: Context) -> str:
Generates parameter descriptions for scheduled transactions.
"""
default_account_desc = AccountResolver.get_default_account_description(context)
return f"""schedulingParams (object, optional): Parameters for scheduling this transaction instead of executing immediately.
return f"""scheduling_params (object, optional): Parameters for scheduling this transaction instead of executing immediately.

**Fields that apply to the *schedule entity*, not the inner transaction:**

Expand Down
1 change: 1 addition & 0 deletions python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ aiohttp = ">=3.13.1,<4.0.0"
pydantic = "^2.12.3"
hiero-sdk-python = "0.1.9"
web3 = "==7.14.0"
mcp = "1.25.0"

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