Skip to content

Examples

Z-M-Huang edited this page Mar 19, 2026 · 3 revisions

Examples

Complete, working examples for common use cases. Each example includes a full manifest and action scripts.

Example: Local Development Setup

A minimal manifest using local file paths for rapid iteration during development. Demonstrates: local action paths, local resource URIs, path resolution relative to the manifest directory.

Project Structure

my-project/
├── .mcp/
│   ├── manifest.yml
│   └── actions/
│       └── hello.v1.js
├── README.md
└── docs/
    └── guide.md

Manifest

schemaVersion: "1.0"
name: my-local-dev
version: 0.1.0
description: Local development MCP server

tools:
  - name: hello
    title: Say Hello
    description: Returns a greeting for the given name
    inputSchema:
      type: object
      properties:
        name:
          type: string
          description: Name to greet
      required: [name]
    action: ./actions/hello.v1.js
    actionHash: "sha256:..."
    annotations:
      readOnlyHint: true

resources:
  - name: readme
    uri: ../README.md
    description: Project README
    mimeType: text/markdown

  - name: guide
    uri: ../docs/guide.md
    description: Development guide
    mimeType: text/markdown

Action: actions/hello.v1.js

export default async function hello(input, ctx) {
  const { name } = input;
  ctx.log('info', `Greeting ${name}`);

  return {
    content: [{
      type: 'text',
      text: `Hello, ${name}! Welcome to git-doc-mcp.`,
    }],
  };
}

Running

# All paths resolve relative to the manifest directory (.mcp/)
git-doc-mcp --manifest ./my-project/.mcp/manifest.yml

Key points:

  • action: ./actions/hello.v1.js resolves to .mcp/actions/hello.v1.js
  • uri: ../README.md resolves to my-project/README.md (relative to the manifest in .mcp/)
  • actionHash is still required for all actions, including local ones
  • Local actions are re-read from disk on each call -- edits take effect without restarting the server
  • ctx.fetch() inside the action is still HTTP(S)-only; it cannot read local files

Example: GitHub Repo Tools

An MCP server that provides tools for exploring a GitHub repository. Demonstrates: multiple tools, optional authentication, JSON API parsing.

Manifest

schemaVersion: "1.0"
name: github-repo-tools
version: 1.0.0
description: |
  Tools for exploring a GitHub repository.
  Fetch files and search code.

instructions: |
  Use these tools when working with the example repository.
  Use fetch-file to read specific files from the repo.
  Use search-code to find patterns across the codebase.

secrets:
  - name: GITHUB_TOKEN
    description: "GitHub token for higher rate limits and private repo access"
    scope:
      - "https://api.github.com/*"
      - "https://raw.githubusercontent.com/*"
    required: false

tools:
  - name: fetch-file
    title: Fetch File
    description: |
      Fetch the raw content of a file from a GitHub repository.
      Returns the file content as plain text.
    inputSchema:
      type: object
      properties:
        owner:
          type: string
          description: Repository owner (e.g., "octocat")
        repo:
          type: string
          description: Repository name (e.g., "hello-world")
        path:
          type: string
          description: File path (e.g., "src/index.ts")
        ref:
          type: string
          description: "Branch, tag, or commit SHA (default: main)"
      required: [owner, repo, path]
    action: https://raw.githubusercontent.com/you/repo/main/actions/fetch-file.v1.js
    actionHash: "sha256:..."
    annotations:
      readOnlyHint: true
      idempotentHint: true
      openWorldHint: true

  - name: search-code
    title: Search Code
    description: |
      Search for code patterns in a GitHub repository using GitHub Code Search.
      Returns matching files with paths and URLs.
    inputSchema:
      type: object
      properties:
        owner:
          type: string
          description: Repository owner
        repo:
          type: string
          description: Repository name
        query:
          type: string
          description: Search query (e.g., "function authenticate")
        language:
          type: string
          description: Filter by language (e.g., "typescript", "python")
      required: [owner, repo, query]
    action: https://raw.githubusercontent.com/you/repo/main/actions/search-code.v1.js
    actionHash: "sha256:..."
    annotations:
      readOnlyHint: true
      idempotentHint: false
      openWorldHint: true

resources:
  - name: readme
    uri: https://raw.githubusercontent.com/you/repo/main/README.md
    description: Repository README
    mimeType: text/markdown

Action: fetch-file.v1.js

export default async function fetchFile(input, ctx) {
  const { owner, repo, path, ref } = input;
  const branch = ref || 'main';

  const url = `https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${branch}`;

  const headers = { 'Accept': 'application/vnd.github.v3.raw' };
  const token = ctx.getSecret('GITHUB_TOKEN', url);
  if (token) headers['Authorization'] = `token ${token}`;

  ctx.log('info', `Fetching ${owner}/${repo}/${path}@${branch}`);

  const response = await ctx.fetch(url, { headers });

  if (!response.ok) {
    if (response.status === 404) {
      return { content: [{ type: 'text', text: `File not found: ${path}` }], isError: true };
    }
    return { content: [{ type: 'text', text: `Failed: ${response.status}` }], isError: true };
  }

  return { content: [{ type: 'text', text: response.text }] };
}

Action: search-code.v1.js

export default async function searchCode(input, ctx) {
  const { owner, repo, query, language } = input;

  var searchQuery = `${query} repo:${owner}/${repo}`;
  if (language) searchQuery += ` language:${language}`;

  const url = `https://api.github.com/search/code?q=${encodeURIComponent(searchQuery)}`;

  const headers = { 'Accept': 'application/vnd.github.v3+json' };
  const token = ctx.getSecret('GITHUB_TOKEN', url);
  if (token) headers['Authorization'] = `token ${token}`;

  ctx.log('info', `Searching ${owner}/${repo} for: ${query}`);
  const response = await ctx.fetch(url, { headers });

  if (!response.ok) {
    return { content: [{ type: 'text', text: `Search failed: ${response.status}` }], isError: true };
  }

  const data = response.json();
  if (!data.items || data.items.length === 0) {
    return { content: [{ type: 'text', text: `No results for: ${query}` }] };
  }

  const text = data.items.slice(0, 10).map(function (item, i) {
    return `${i + 1}. ${item.path}\n   ${item.html_url}`;
  }).join('\n\n');

  return { content: [{ type: 'text', text: `Found ${data.total_count} results:\n\n${text}` }] };
}

Example: Documentation Server

An MCP server that serves project documentation from markdown files. Demonstrates: wiki-style content fetching, topic indexing, prompts.

Manifest

schemaVersion: "1.0"
name: docs-server
version: 1.0.0
description: |
  Serves project documentation from markdown files hosted on GitHub.
  Browse topics and read guides.

instructions: |
  Use list-pages to see available documentation.
  Use get-page to read a specific page by name.

tools:
  - name: list-pages
    title: List Pages
    description: List all available documentation pages
    inputSchema:
      type: object
      properties: {}
    action: https://raw.githubusercontent.com/you/repo/main/actions/list-pages.v1.js
    actionHash: "sha256:..."
    annotations:
      readOnlyHint: true
      idempotentHint: true
      openWorldHint: true

  - name: get-page
    title: Get Page
    description: Fetch a documentation page by name
    inputSchema:
      type: object
      properties:
        name:
          type: string
          description: Page name (e.g., "getting-started", "api-reference")
      required: [name]
    action: https://raw.githubusercontent.com/you/repo/main/actions/get-page.v1.js
    actionHash: "sha256:..."
    annotations:
      readOnlyHint: true
      idempotentHint: true
      openWorldHint: true

resources:
  - name: index
    uri: https://raw.githubusercontent.com/you/repo/main/docs/index.md
    description: Documentation home page
    mimeType: text/markdown

prompts:
  - name: learn-topic
    title: Learn Topic
    description: Deep-dive into a documentation topic
    args:
      - name: topic
        description: The topic to learn about
        required: true
    messages:
      - role: user
        content:
          type: text
          text: |
            I want to learn about {{topic}}.
            Please use the documentation tools to find relevant pages,
            read them, and give me a comprehensive explanation.

Action: list-pages.v1.js

export default async function listPages(input, ctx) {
  const url = 'https://raw.githubusercontent.com/you/repo/main/docs/index.md';
  const response = await ctx.fetch(url);

  if (!response.ok) {
    return { content: [{ type: 'text', text: 'Failed to fetch page index.' }], isError: true };
  }

  return { content: [{ type: 'text', text: response.text }] };
}

Action: get-page.v1.js

export default async function getPage(input, ctx) {
  const { name } = input;
  const url = `https://raw.githubusercontent.com/you/repo/main/docs/${encodeURIComponent(name)}.md`;

  ctx.log('info', `Fetching page: ${name}`);
  const response = await ctx.fetch(url);

  if (!response.ok) {
    return {
      content: [{ type: 'text', text: `Page not found: "${name}". Use list-pages to see available pages.` }],
      isError: true,
    };
  }

  return { content: [{ type: 'text', text: response.text }] };
}

Example: REST API Wrapper

An MCP server that wraps a REST API, providing typed tools for common operations. Demonstrates: required secrets, POST requests, different annotation combinations.

Manifest

schemaVersion: "1.0"
name: todo-api
version: 1.0.0
description: |
  MCP interface for the Todo API.
  List and create todo items.

instructions: |
  Use list-todos to see existing items.
  Use create-todo to add new items.

secrets:
  - name: TODO_API_KEY
    description: "API key for the Todo service (get at https://todo.example.com/settings)"
    scope: "https://api.todo.example.com/*"
    required: true

tools:
  - name: list-todos
    title: List Todos
    description: List all todo items, optionally filtered by status
    inputSchema:
      type: object
      properties:
        status:
          type: string
          description: "Filter by status (all, active, or completed)"
    action: https://example.com/actions/list-todos.v1.js
    actionHash: "sha256:..."
    annotations:
      readOnlyHint: true
      idempotentHint: false
      openWorldHint: true

  - name: create-todo
    title: Create Todo
    description: Create a new todo item
    inputSchema:
      type: object
      properties:
        title:
          type: string
          description: Todo title
        priority:
          type: string
          description: "Priority level (low, medium, or high)"
      required: [title]
    action: https://example.com/actions/create-todo.v1.js
    actionHash: "sha256:..."
    annotations:
      readOnlyHint: false
      destructiveHint: false
      idempotentHint: false
      openWorldHint: true

Action: list-todos.v1.js

export default async function listTodos(input, ctx) {
  var url = 'https://api.todo.example.com/v1/todos';
  if (input.status && input.status !== 'all') {
    url += '?status=' + encodeURIComponent(input.status);
  }

  var apiKey = ctx.getSecret('TODO_API_KEY', url);
  if (!apiKey) {
    return {
      content: [{ type: 'text', text: 'TODO_API_KEY is required.' }],
      isError: true,
    };
  }

  var response = await ctx.fetch(url, {
    headers: { 'Authorization': 'Bearer ' + apiKey },
  });

  if (!response.ok) {
    return { content: [{ type: 'text', text: 'Failed: ' + response.status }], isError: true };
  }

  var data = response.json();
  if (!data.items || data.items.length === 0) {
    return { content: [{ type: 'text', text: 'No todos found.' }] };
  }

  var text = data.items.map(function (t, i) {
    return (i + 1) + '. **' + t.title + '** [' + t.status + '] (priority: ' + t.priority + ')';
  }).join('\n');

  return { content: [{ type: 'text', text: 'Todos (' + data.items.length + '):\n\n' + text }] };
}

Action: create-todo.v1.js

export default async function createTodo(input, ctx) {
  const url = 'https://api.todo.example.com/v1/todos';

  const apiKey = ctx.getSecret('TODO_API_KEY', url);
  if (!apiKey) {
    return {
      content: [{ type: 'text', text: 'TODO_API_KEY is required.' }],
      isError: true,
    };
  }

  ctx.log('info', `Creating todo: ${input.title}`);

  const response = await ctx.fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${apiKey}`,
    },
    body: JSON.stringify({
      title: input.title,
      priority: input.priority || 'medium',
    }),
  });

  if (!response.ok) {
    return {
      content: [{ type: 'text', text: `Failed to create todo: ${response.status}` }],
      isError: true,
    };
  }

  const todo = response.json();

  return {
    content: [{
      type: 'text',
      text: `Created todo #${todo.id}:\n- **Title**: ${todo.title}\n- **Priority**: ${todo.priority}\n- **Status**: ${todo.status}`,
    }],
  };
}

Example: Multi-Source Aggregator

An MCP server that aggregates data from multiple API sources into a single result. Demonstrates: multiple fetches per action, data merging, multiple content blocks.

Manifest

schemaVersion: "1.0"
name: knowledge-aggregator
version: 1.0.0
description: |
  Aggregates search results from multiple knowledge sources.
  Searches wiki, blog, and FAQ simultaneously.

instructions: |
  Use aggregate to search across all knowledge sources at once.

tools:
  - name: aggregate
    title: Aggregate Search
    description: Search across wiki, blog, and FAQ sources simultaneously
    inputSchema:
      type: object
      properties:
        topic:
          type: string
          description: Topic to search for
      required: [topic]
    action: https://raw.githubusercontent.com/you/repo/main/actions/aggregate.v1.js
    actionHash: "sha256:..."
    annotations:
      readOnlyHint: true
      idempotentHint: false
      openWorldHint: true

Action: aggregate.v1.js

export default async function aggregate(input, ctx) {
  const { topic } = input;
  ctx.log('info', `Aggregating data for topic: ${topic}`);

  // Fetch from multiple sources
  const sources = [
    { name: 'Wiki', url: `https://api.example.com/wiki/search?q=${encodeURIComponent(topic)}` },
    { name: 'Blog', url: `https://api.example.com/blog/search?q=${encodeURIComponent(topic)}` },
    { name: 'FAQ', url: `https://api.example.com/faq/search?q=${encodeURIComponent(topic)}` },
  ];

  const results = [];

  for (var i = 0; i < sources.length; i++) {
    var source = sources[i];
    var response = await ctx.fetch(source.url);

    if (response.ok) {
      var data = response.json();
      results.push({
        type: 'text',
        text: `## ${source.name} Results\n\n${data.items.map(function (item) {
          return '- **' + item.title + '**: ' + item.summary;
        }).join('\n')}`,
      });
    } else {
      results.push({
        type: 'text',
        text: `## ${source.name}\n\n*No results or service unavailable.*`,
      });
    }
  }

  // Prepend summary
  results.unshift({
    type: 'text',
    text: `# Results for "${topic}"\n\nSearched ${sources.length} sources:`,
  });

  return { content: results };
}

Example: Static Knowledge Base

An MCP server focused on project documentation with rich prompts. Demonstrates: resources, complex prompt templates with argument substitution, text and resource content types.

Manifest

schemaVersion: "1.0"
name: project-kb
version: 1.0.0
description: |
  Project knowledge base. Provides documentation, architecture guides,
  and onboarding prompts for new team members.

instructions: |
  Use get-doc to read project documentation pages.
  Use the onboard prompt for new team members.
  Use the review prompt for code reviews.

tools:
  - name: get-doc
    title: Get Documentation
    description: Fetch a documentation page from the project
    inputSchema:
      type: object
      properties:
        page:
          type: string
          description: "Page name: architecture, api, deployment, testing, contributing"
      required: [page]
    action: https://raw.githubusercontent.com/you/repo/main/actions/get-doc.v1.js
    actionHash: "sha256:..."
    annotations:
      readOnlyHint: true
      idempotentHint: true
      openWorldHint: true

resources:
  - name: architecture
    uri: https://raw.githubusercontent.com/you/repo/main/docs/architecture.md
    description: System architecture overview
    mimeType: text/markdown

  - name: api-reference
    uri: https://raw.githubusercontent.com/you/repo/main/docs/api.md
    description: API endpoint reference
    mimeType: text/markdown

  - name: deployment
    uri: https://raw.githubusercontent.com/you/repo/main/docs/deployment.md
    description: Deployment guide
    mimeType: text/markdown

prompts:
  - name: onboard
    title: Onboard Developer
    description: Onboarding walkthrough for new team members
    args:
      - name: role
        description: "Role: frontend, backend, fullstack, devops"
        required: true
      - name: experience
        description: "Experience level: junior, mid, senior"
        required: false
    messages:
      - role: user
        content:
          type: text
          text: |
            I'm a new {{role}} developer ({{experience}}) joining the team.
            Please use the get-doc tool to read the architecture page, then give me:
            1. Key systems relevant to my role
            2. First things to set up locally
            3. Common patterns and conventions
            4. Where to find help

  - name: review
    title: Code Review
    description: Code review checklist for a specific file
    args:
      - name: path
        description: File path to review
        required: true
    messages:
      - role: user
        content:
          type: text
          text: |
            Please use get-doc to read the project conventions, then review
            the code at {{path}}. Check for:
            - Correctness and edge cases
            - Security issues
            - Performance concerns
            - Adherence to project conventions
            - Test coverage gaps

Action: get-doc.v1.js

export default async function getDoc(input, ctx) {
  const { page } = input;
  const url = `https://raw.githubusercontent.com/you/repo/main/docs/${encodeURIComponent(page)}.md`;

  ctx.log('info', `Fetching doc page: ${page}`);
  const response = await ctx.fetch(url);

  if (!response.ok) {
    if (response.status === 404) {
      return {
        content: [{
          type: 'text',
          text: `Page not found: "${page}". Available pages: architecture, api, deployment, testing, contributing`,
        }],
        isError: true,
      };
    }
    return { content: [{ type: 'text', text: `Failed: ${response.status}` }], isError: true };
  }

  return { content: [{ type: 'text', text: response.text }] };
}

Clone this wiki locally