Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

greennode-agentbase-mcp

An MCP server that exposes the GreenNode AgentBase REST APIs as 3 searchable meta-tools — a search→execute gateway that cuts the MCP tool-definition tax ~95%+ versus flattening every operation into its own tool. Runs locally over stdio (default) or remotely over streamable HTTP, with any MCP-speaking client.

Table of contents

Quick start

Connect a local MCP client (Claude Code, Cursor, Windsurf, …) to the gateway over stdio in under a minute.

Prerequisites

  • Node.js ≥ 20 (see package.json engines)
  • A GreenNode AgentBase bearer token — one token is valid across all seven services

1. Install

git clone https://github.com/GreenNodeHub/greennode-agentbase-mcp.git
cd greennode-agentbase-mcp
npm ci

2. Run (stdio is the default transport — no need to set TRANSPORT)

GREENNODE_MCP_TOKEN=<your-token> npm start

3. Wire up your client. Claude Code — .mcp.json:

{
  "mcpServers": {
    "agentbase": {
      "command": "npx",
      "args": ["tsx", "src/index.ts"],
      "env": { "GREENNODE_MCP_TOKEN": "<your-token>" }
    }
  }
}

For Cursor, Windsurf, Cline, Roo Code, Claude Desktop, and other clients, see docs/mcp-client-quickstart.html — same command + env, each client's own config key.

Optional — auto-rotating token (external). If you have the agentbase skill installed (it ships .claude/skills/agentbase/scripts/get_token.sh, which is not part of this repo) plus GREENNODE_CLIENT_ID / GREENNODE_CLIENT_SECRET (or a .greennode.json), point your client at scripts/mcp-launch.sh instead. It mints a fresh ~30-minute IAM JWT on every (re)start, so reconnecting rotates the token automatically — no manual re-export, no stale-token 401s.

First call flow: list_servers → search_tools → execute (see How it works).

How it works

Instead of exposing 100+ operations as individual MCP tools (a large manifest the model pays for every turn), the server exposes 3 meta-tools. The full operation set lives in a generated registry the model searches on demand.

┌───────────────────────────────────────────────────────────────┐
│  Generated layer  (from specs, committed, never hand-edited)  │
│    registry.generated.json                                    │
│      every operation: { id, service, method, path,            │
│                         summary, tags, inputSchema, … }       │
└───────────────────────────────────────────────────────────────┘
                          ▲ consumed by
┌───────────────────────────────────────────────────────────────┐
│  Meta layer  (hand-written TypeScript)                        │
│    • 3 meta-tools: list_servers, search_tools, execute        │
│    • BM25 search engine                                       │
│    • JMESPath field projection + response byte cap            │
│    • inbound auth + downstream token pass-through             │
│    • env resolver (base URLs, transport, limits)              │
└───────────────────────────────────────────────────────────────┘

Meta-tools

Tool Args Returns
list_servers — the services, each with its operation count + tags
search_tools query, server?, limit? BM25-ranked operations, each with its full inputSchema inline
execute id, args?, fields? the real HTTP response, projected by fields and byte-capped

Discovery is two steps: search_tools returns enough to call execute directly (the input schema is inline), so there's no separate describe step.

// 1) orient on the seven services
list_servers()
// → [{ "name": "policy", "description": "policy service (… operations)", "operationCount": …, "tags": […] }, …]

// 2) search by intent — the id and full inputSchema come back together
search_tools({ query: "list policy groups" })
// → [{ "id": "policy.get_api_v1_policy_groups", "service": "policy",
//      "summary": "List policy groups", "inputSchema": { "type": "object",
//      "properties": { "page": {…}, "page_size": {…}, "name": {…} } } }, …]

// 3) execute; `fields` is an optional JMESPath projection to shrink the response
execute({ id: "policy.get_api_v1_policy_groups", args: { page: 1, page_size: 10 } })
// → the live response (omit `fields` to see the whole body; pass e.g. fields:"items[].name" to project it)

Operation ids look like service.<method>_<slugified-path> (e.g. policy.get_api_v1_policy_groups). Always take an id from search_tools — never type one by hand.

Why meta-tools: 3 tool definitions (~1–2K resident tokens) instead of one tool per operation. See benchmarks/report-2026-07-06.md for the token math — a 36.8× smaller manifest and 7–41% fewer input tokens end-to-end versus the flat (one-tool-per-op) variant.

Services

Service What it covers
policy policy groups, conditions, bindings
cr container registry credentials
gateway MCP gateways, flavors, metrics
identity outbound auth providers (OAuth2, API key)
memory agent memories and records
runtime agent runtimes, openclaws
tools built-in browsers + code interpreters

tools (the built-in browsers + code interpreters API) is dev-only today: its prod base URL (https://agentbase.api.vngcloud.vn/built-in-tools) follows the shared agentbase host pattern, mirroring the greennode-cli wiring, and awaits go-live. Its spec is therefore fetched from the dev host (scripts/fetch-specs.ts).

Two caveats on tools:

  • The two browser SSE stream relay endpoints (automation-stream, live-view-stream) are excluded from the registry — the generic execute relay is a bounded JSON request/response and would hang on a held-open stream. Drive these streams with the dedicated greennode-browser-mcp server (github.com/GreenNodeHub/greennode-browser-mcp), which speaks the stream protocol natively.
  • Code-interpreter invoke is held open server-side; it works through execute for short-running invokes. For session-driven interpreter work, prefer the dedicated greennode-code-interpreter-mcp server (github.com/GreenNodeHub/greennode-code-interpreter-mcp).

Transports: stdio vs. streamable HTTP

stdio streamable HTTP
Use case local, any MCP client deployed runtime / remote clients
Default yes (TRANSPORT=stdio) opt-in (TRANSPORT=http)
Lifecycle one server for the process lifetime fresh server + transport per request (stateless)
Token source env var named by TOKEN_ENV (default GREENNODE_MCP_TOKEN) Authorization: Bearer header, per request
Endpoint stdin/stdout (JSON-RPC) POST /mcp
Health — GET /healthz, GET /health

stdio (default)

The server reads JSON-RPC from stdin and writes responses to stdout. stdout is the protocol — all diagnostics and the one-line startup banner go to stderr, so they never corrupt the stream.

GREENNODE_MCP_TOKEN=<your-token> npm start   # TRANSPORT=stdio is the default

The token is read once at startup from the env var named by TOKEN_ENV (default GREENNODE_MCP_TOKEN). The server runs for the process lifetime and exits when the client closes stdin. See Quick start for the client-wiring snippet.

Streamable HTTP

For a deployed runtime or remote clients. Each POST /mcp builds a fresh server + StreamableHTTPServerTransport for that request (stateless) and authenticates from the Authorization header. The token is not read from the environment in this mode.

TRANSPORT=http npm start   # listens on :8080 (PORT); pass the token per request, not via env

Smoke-test it:

curl http://localhost:8080/healthz          # → {"ok":true}

# a raw initialize request to /mcp (clients normally build this JSON-RPC envelope for you)
curl -X POST http://localhost:8080/mcp \
  -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'

Configuration

All config is via environment variables, read once at startup by loadEnvConfig (src/config/env.ts).

Var Default Notes
TRANSPORT stdio stdio or http. Any other value throws at boot — the process exits non-zero, nothing listens.
GREENNODE_MCP_TOKEN — Upstream bearer token, stdio only. Forwarded to all seven services on execute.
TOKEN_ENV GREENNODE_MCP_TOKEN Name of the env var that holds the token, stdio only. Set this to read the token from a differently-named var.
PORT 8080 HTTP transport listen port.
MAX_RESPONSE_BYTES 25000 Hard cap on execute responses; over-cap responses are truncated with a notice.
SEARCH_LIMIT_DEFAULT 5 Default limit for search_tools when the caller omits it.

In streamable HTTP mode the token is not read from env at all — clients supply it per request via Authorization: Bearer. GREENNODE_MCP_TOKEN / TOKEN_ENV apply only to stdio.

Development & operations

Scripts (package.json):

Script What it does
npm start Run the server (tsx src/index.ts)
npm run dev Run with reload (tsx watch src/index.ts)
npm run build Typecheck only (tsc --noEmit). There is no compiled dist/ — the runnable form is tsx.
npm test / npm run test:watch Vitest
npm run fetch-specs Refresh specs/ from the AgentBase spec endpoints
npm run generate-registry Rebuild registry.generated.json from specs/

Regenerate the registry when the upstream specs change:

npm run fetch-specs && npm run generate-registry

Then commit both specs/ and registry.generated.json. Both are generated — never hand-edit them.

Docker:

docker build -t greennode-agentbase-mcp .
docker run -e TRANSPORT=http -p 8080:8080 greennode-agentbase-mcp

The bearer token is supplied per request via the Authorization header (same as HTTP mode) — not via env.

⚠️ TRANSPORT defaults to stdio. A deployed HTTP runtime — and the shipped Dockerfile (which sets PORT but not TRANSPORT) — must set TRANSPORT=http explicitly. Without it the process starts in stdio mode and listens on no port. The docker run command above passes -e TRANSPORT=http; for a production image, bake ENV TRANSPORT=http into the Dockerfile.

Further reading

License

See LICENSE.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages