Expose Backend Services as Agent Tools Through One Server

You have several gRPC services -- graph, vector, pathway, trace -- and you need agents to reach them as tools. Writing a separate MCP wrapper for each service means duplicating schema translation, session management, and authentication logic. The MCP service reads a single tool configuration from config/config.json, creates gRPC clients for each backend, and exposes every configured endpoint as a typed MCP tool through one HTTP/SSE server.

This guide walks through understanding the configuration, starting the MCP service, connecting a client, and verifying that backend RPCs are reachable as agent tools.

Prerequisites

  • Node.js 18+
  • Generated client code available (run npx fit-codegen --all if not)
  • All backend services running (npx fit-rc start or just guide)
  • The MCP_TOKEN environment variable set (the MCP service requires a bearer token for authentication)

Install the MCP SDK if you are building a client:

npm install @modelcontextprotocol/sdk

Architecture overview

The MCP service is the only non-gRPC service in the stack. It exposes an HTTP/SSE interface using @modelcontextprotocol/sdk and delegates every tool call to one of the gRPC backend services. Each client session gets its own McpServer instance for isolation.

Agent SDK ──── HTTP/SSE ──── MCP service ──┬── gRPC ── graph
                                           ├── gRPC ── vector
                                           ├── gRPC ── pathway
                                           └── resource index

Tool registration is declarative. The service.mcp.tools section of config/config.json maps tool names to gRPC methods:

{
  "service": {
    "mcp": {
      "tools": {
        "QueryByPattern": {
          "method": "graph.Graph.QueryByPattern",
          "description": "Retrieves structured data by traversing graph relationships using triple patterns."
        },
        "SearchContent": {
          "method": "vector.Vector.SearchContent",
          "description": "Find detailed content using semantic similarity search."
        },
        "DescribeJob": {
          "method": "pathway.Pathway.DescribeJob",
          "description": "Describe a job at (discipline, level, optional track)."
        }
      }
    }
  }
}

Each entry specifies a method in <package>.<Service>.<RPC> format and a description that becomes the tool's human-readable summary. The MCP service reads the codegen metadata for each method to build a Zod schema for parameter validation, so tool parameters are typed automatically from the proto definitions.

Default tool set

The MCP service ships with these tools pre-configured:

Tool Backend Purpose
GetOntology graph Returns entity types and relationship predicates
GetSubjects graph Lists entity URIs, optionally filtered by type
QueryByPattern graph Traverses relationships using triple patterns
SearchContent vector Semantic similarity search over indexed content
ListJobs pathway Lists valid discipline/level/track combinations
DescribeJob pathway Derives a full role definition
ListAgentProfiles pathway Lists valid discipline/track combinations
DescribeAgentProfile pathway Derives an agent profile
DescribeProgression pathway Computes the delta between two levels
ListJobSoftware pathway Derives the software toolkit for a role

Start the MCP service

The MCP service starts as part of the service stack:

npx fit-rc start

Or start it individually during development:

MCP_TOKEN=your-token node --watch services/mcp/server.js

The server listens on port 3005 by default (configurable in config/config.json). Verify it is running:

curl http://localhost:3005/health

Expected output:

{"status":"ok"}

Connect a client

Agents connect to the MCP service over HTTP. The @modelcontextprotocol/sdk provides the client transport:

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const transport = new StreamableHTTPClientTransport(
  new URL("http://localhost:3005"),
  {
    requestInit: {
      headers: {
        Authorization: `Bearer ${process.env.MCP_TOKEN}`,
      },
    },
  },
);

const client = new Client({ name: "my-agent", version: "1.0.0" });
await client.connect(transport);

List available tools

const tools = await client.listTools();
console.log("Available tools:", tools.tools.length);

for (const tool of tools.tools) {
  console.log(`  ${tool.name}: ${tool.description}`);
}

Expected output:

Available tools: 10
  GetOntology: Returns all entity types and relationship predicates in the knowledge graph.
  GetSubjects: Lists entity URIs in the graph, optionally filtered by type.
  QueryByPattern: Retrieves structured data by traversing graph relationships using triple patterns.
  SearchContent: Find detailed content using semantic similarity search.
  ListJobs: List jobs (discipline x level x track) defined in the pathway standard.
  DescribeJob: Describe a job at (discipline, level, optional track) including skills, behaviours, and responsibilities.
  ListAgentProfiles: List static agent profile (discipline, track) combinations.
  DescribeAgentProfile: Describe stage agent profiles for a (discipline, track).
  DescribeProgression: Compute the progression delta between two levels of the same discipline.
  ListJobSoftware: List the software toolkit derived for a job.

Call a tool

const result = await client.callTool({
  name: "DescribeJob",
  arguments: {
    discipline: "software_engineering",
    level: "J070",
    track: "platform",
  },
});

console.log(result.content[0].text.substring(0, 300));

The response is the Turtle RDF content from the pathway service, wrapped in the MCP content format.

How tool registration works

When the MCP service starts, registerToolsFromConfig iterates the tool configuration and for each entry:

  1. Parses the method string into package, service, and RPC name.
  2. Looks up the codegen metadata for the RPC's request type fields.
  3. Builds a Zod schema from the field metadata for parameter validation.
  4. Registers the tool on the McpServer with the schema and a handler that normalizes parameters, creates a typed request via fromObject, calls the gRPC client, and returns the result.

When the RPC returns resource identifiers (rather than content), the service resolves them through the resource index and returns the content as text.

Session management

Each client connection gets its own McpServer and transport pair. Sessions are tracked by session ID and reaped after 30 minutes of inactivity. The reaping interval runs every 60 seconds.

When a client disconnects, its session is removed from the session map. When the server shuts down, it closes all active sessions before stopping the HTTP listener.

Authentication

Every request must include a Bearer token in the Authorization header. The token is compared against config.mcpToken(), which reads from the MCP_TOKEN environment variable. Requests without a valid token receive a 401 response.

Verify

You have reached the outcome of this guide when:

  • The MCP service starts and /health returns {"status":"ok"}.
  • An MCP client connects with the correct bearer token.
  • listTools returns all configured tools with descriptions.
  • callTool for a pathway RPC returns Turtle RDF content.
  • callTool for a graph or vector RPC returns content or resolved resource text.

If the MCP service starts but tool calls fail, confirm the backend gRPC services are running with npx fit-rc status. The MCP service cannot serve tool calls if the backend it delegates to is unreachable.

What's next