Multi-agent DAG orchestration, purpose-built for enterprise engineering teams Learn more →

Build an MCP

Last updated 2026-05-19·7 min read

This guide walks through building, testing, and publishing a Cendriix MCP (Modular Capability Provider), the plugin system that gives agents access to external services, APIs, and tools.

What is an MCP?

An MCP is a self-contained package that exposes a typed toolset to the Cendriix orchestrator. Every tool call an agent makes goes through an MCP, the MCP validates inputs, enforces rate limits, handles authentication, and returns structured outputs. MCPs run in isolated sandboxes and cannot communicate with each other directly.

MCPs are written in TypeScript (Node 20+) and packaged as standard npm packages. The Cendriix catalog hosts verified MCPs for AWS, GitHub, Jira, Slack, Stripe, and many more. Custom MCPs can be published privately within your workspace or shared publicly with the community.

Security modelEach MCP instance runs with its own credential scope. An agent can only call tools that have been explicitly permitted by the workspace policy. Calls are logged as structured audit events.

File layout

An MCP package follows a minimal, opinionated directory structure:

bash
my-mcp/
  cendriix.mcp.json      # manifest (name, version, auth, tools)
  src/
    index.ts            # entry point, exports the MCP class
    tools/
      read.ts           # read tool implementation
      write.ts          # write tool implementation
      list.ts           # list tool implementation
      watch.ts          # watch tool implementation (optional)
  package.json
  tsconfig.json

Manifest schema

The cendriix.mcp.jsonmanifest is the single source of truth for the MCP's identity, version, authentication requirements, and tool declarations.

json
{
  "name":        "@acme/jira-mcp",
  "version":     "1.0.0",
  "display":     "Jira",
  "description": "Read and write Jira issues, transitions, and comments.",
  "auth": {
    "kind":   "oauth2",
    "scopes": ["read:jira-work", "write:jira-work"]
  },
  "tools": [
    {
      "name":        "jira.issue.read",
      "description": "Fetch a Jira issue by key.",
      "input": {
        "type": "object",
        "properties": {
          "key": { "type": "string", "description": "Issue key, e.g. JIRA-3421." }
        },
        "required": ["key"]
      },
      "output": { "type": "object" }
    },
    {
      "name":        "jira.issue.write",
      "description": "Update fields on a Jira issue.",
      "input": {
        "type": "object",
        "properties": {
          "key":    { "type": "string" },
          "fields": { "type": "object" }
        },
        "required": ["key", "fields"]
      },
      "output": { "type": "object" }
    }
  ]
}

Standard tool methods

Every MCP exposes up to four standard tool methods. You only need to implement the ones your integration requires.

MethodDescriptionRequired?
readRetrieve a single resource by identifier. Must be idempotent.Recommended
writeCreate or update a resource. May have side effects.Optional
listEnumerate resources, optionally with filters.Optional
watchSubscribe to a resource stream (Server-Sent Events). Used for live monitoring tools.Optional

Here is a minimal implementation of the read method for a Jira issue tool:

typescript
// src/tools/read.ts
import type { McpContext, ReadInput, ReadOutput } from '@cendriix/mcp-sdk';

interface IssueReadInput extends ReadInput {
  key: string;
}

interface JiraIssue {
  id:     string;
  key:    string;
  fields: Record<string, unknown>;
}

export async function read(
  input:   IssueReadInput,
  context: McpContext,
): Promise<ReadOutput<JiraIssue>> {
  const baseUrl = context.config.get('jira_base_url') as string;
  const token   = await context.auth.getToken();

  const res = await fetch(`${baseUrl}/rest/api/3/issue/${input.key}`, {
    headers: {
      Authorization: `Bearer ${token}`,
      Accept:        'application/json',
    },
  });

  if (!res.ok) {
    throw new Error(`Jira API error ${res.status}: ${await res.text()}`);
  }

  const issue = await res.json() as JiraIssue;
  return { data: issue };
}

The entry point wires all tool handlers into a single class:

typescript
// src/index.ts
import { McpBase, type McpManifest } from '@cendriix/mcp-sdk';
import { read  } from './tools/read.js';
import { write } from './tools/write.js';
import { list  } from './tools/list.js';

export class JiraMcp extends McpBase {
  static override manifest: McpManifest = {
    name:    '@acme/jira-mcp',
    version: '1.0.0',
  };

  override read  = read;
  override write = write;
  override list  = list;
}

Authentication

MCPs declare their auth requirement in the manifest. The orchestrator handles credential storage and token refresh transparently, your tool code callscontext.auth.getToken() and receives a fresh, scoped access token.

Auth kindUse case
oauth2SaaS apps with standard OAuth flows (GitHub, Jira, Slack, Salesforce).
api_keyStatic API keys stored as workspace secrets.
aws_iamAWS services via the connected cross-account IAM role.
nonePublic APIs or MCPs that manage auth internally.
Secret storageNever hardcode credentials in your MCP source. Use context.config.get(key)for workspace-level secrets set via the dashboard or CLI (cendriix workspace secrets set JIRA_BASE_URL https://acme.atlassian.net).

Packaging

MCPs are standard npm packages. Build and bundle with the Cendriix SDK toolchain:

bash
# Install SDK
npm install @cendriix/mcp-sdk

# Validate manifest and run type checks
npx cendriix mcp validate

# Build a distributable bundle
npx cendriix mcp build

# Test locally against the orchestrator sandbox
npx cendriix mcp dev

The mcp build command produces a single CommonJS bundle indist/, which is what the catalog distributes.

Publishing to the catalog

MCPs can be published to your private workspace catalog or to the global community catalog (open-source MCPs only).

bash
# Publish to your private workspace catalog
npx cendriix mcp publish --visibility private

# Publish to the global community catalog
npx cendriix mcp publish --visibility public

Community catalog submissions are reviewed by the Cendriix security team. Review typically takes 2–5 business days. MCPs must pass automated security scanning, manifest validation, and a manual review of tool descriptions before listing.

Once published, workspace members can install your MCP from the MCP Catalog page or directly:

bash
cendriix mcp install @acme/jira-mcp
VersioningFollow semver strictly. The catalog pins installed MCPs to a minor version by default (^1.0.0). Breaking changes must bump the major version.