-
Notifications
You must be signed in to change notification settings - Fork 0
Examples
Complete, working examples for common use cases. Each example includes a full manifest and action scripts.
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.
my-project/
├── .mcp/
│ ├── manifest.yml
│ └── actions/
│ └── hello.v1.js
├── README.md
└── docs/
└── guide.md
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/markdownexport 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.`,
}],
};
}# All paths resolve relative to the manifest directory (.mcp/)
git-doc-mcp --manifest ./my-project/.mcp/manifest.ymlKey points:
-
action: ./actions/hello.v1.jsresolves to.mcp/actions/hello.v1.js -
uri: ../README.mdresolves tomy-project/README.md(relative to the manifest in.mcp/) -
actionHashis 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
An MCP server that provides tools for exploring a GitHub repository. Demonstrates: multiple tools, optional authentication, JSON API parsing.
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/markdownexport 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 }] };
}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}` }] };
}An MCP server that serves project documentation from markdown files. Demonstrates: wiki-style content fetching, topic indexing, prompts.
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.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 }] };
}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 }] };
}An MCP server that wraps a REST API, providing typed tools for common operations. Demonstrates: required secrets, POST requests, different annotation combinations.
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: trueexport 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 }] };
}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}`,
}],
};
}An MCP server that aggregates data from multiple API sources into a single result. Demonstrates: multiple fetches per action, data merging, multiple content blocks.
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: trueexport 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 };
}An MCP server focused on project documentation with rich prompts. Demonstrates: resources, complex prompt templates with argument substitution, text and resource content types.
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 gapsexport 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 }] };
}