Write a manifest, host it anywhere, and users can instantly use it with their AI tools.
git-doc-mcp is a declarative MCP manifest system that lets anyone create and host MCP servers without running infrastructure. It turns any manifest URL into a fully-functional MCP server with custom tools, resources, and prompts - all defined in YAML.
Key features:
- No hosting required - Use GitHub Pages, GitLab Pages, S3, any static hosting
- Custom JavaScript actions - Define your own tool logic
- Capability-scoped secrets - Fine-grained access control with URL pattern matching
- Platform-agnostic - Works with GitHub, GitLab, S3, local files
- TOFU manifest verification - Trust-on-first-use with fail-closed security
- Three-layer isolation - Worker process + isolated-vm + URL validation
- Cross-platform - Linux, macOS, Windows
# Using npm
npm install -g git-doc-mcp
# Using npx (no install needed)
npx git-doc-mcp --manifest https://example.com/.mcp/manifest.ymlAdd to ~/.claude/config.json:
{
"mcpServers": {
"my-repo": {
"command": "npx",
"args": ["git-doc-mcp", "--manifest", "https://example.com/.mcp/manifest.yml"]
}
}
}Create .mcp/manifest.yml in your repository:
schemaVersion: "1.0"
name: my-repo-mcp
version: 1.0.0
description: MCP server for my repository
tools:
- name: fetch-file
description: Fetch a file from the repository
inputSchema:
type: object
properties:
path: { type: string }
required: [path]
action: https://example.com/.mcp/actions/fetch-file.v1.js # URL or local path
actionHash: "sha256:..."
annotations:
readOnlyHint: true
openWorldHint: trueActions can reference HTTP(S) URLs or local file paths (relative to the manifest file's directory):
# Remote action (hosted)
action: https://raw.githubusercontent.com/owner/repo/main/.mcp/actions/fetch-file.v1.js
# Local action (for development)
action: ./actions/fetch-file.v1.jsThis project uses its own manifest system to serve documentation. Add it to your AI tool to see git-doc-mcp in action:
Claude Code — add to your project's .mcp.json:
{
"mcpServers": {
"git-doc-mcp-docs": {
"command": "npx",
"args": [
"git-doc-mcp",
"--manifest",
"https://raw.githubusercontent.com/Z-M-Huang/git-doc-mcp/main/.mcp/manifest.yml"
]
}
}
}Claude Desktop — add to ~/.claude/claude_desktop_config.json:
{
"mcpServers": {
"git-doc-mcp-docs": {
"command": "npx",
"args": [
"git-doc-mcp",
"--manifest",
"https://raw.githubusercontent.com/Z-M-Huang/git-doc-mcp/main/.mcp/manifest.yml"
]
}
}
}This gives you 5 tools, 3 resources, and 2 prompts that fetch documentation from the project wiki:
| Tool | Description |
|---|---|
list_topics |
Browse documentation topics, filter by tag |
get_guide |
Fetch a specific guide (e.g., Getting-Started, Manifest-Reference) |
search_docs |
Search across all documentation by keyword |
get_example |
Get complete working examples (GitHub tools, REST wrapper, etc.) |
get_action_api |
Action scripting API reference (ctx.fetch, ctx.getSecret, etc.) |
The manifest source and action scripts serve as a reference for building your own.
npm install -g git-doc-mcpnpx git-doc-mcp --manifest <url-or-path>git clone https://github.com/Z-M-Huang/git-doc-mcp.git
cd git-doc-mcp
npm install
npm run buildnpx git-doc-mcp --manifest https://raw.githubusercontent.com/owner/repo/main/.mcp/manifest.ymlnpx git-doc-mcp --manifest https://private.example.com/.mcp/manifest.yml \
--manifest-header "Authorization: Bearer $TOKEN"# Manifest and action scripts can all be local files
npx git-doc-mcp --manifest ./path/to/manifest.ymlWhen using a local manifest, action and resource.uri fields can reference local files with paths relative to the manifest's directory. Local actions are re-read from disk on each tool call, so edits take effect without restarting the server.
# Via CLI flag
npx git-doc-mcp --manifest https://example.com/.mcp/manifest.yml \
--secret GITHUB_TOKEN=$GITHUB_TOKEN
# Via environment variable
export GIT_MCP_SECRET_GITHUB_TOKEN=$GITHUB_TOKEN
npx git-doc-mcp --manifest https://example.com/.mcp/manifest.ymlnpx git-doc-mcp --manifest https://example.com/.mcp/manifest.yml \
--manifest-hash sha256:abc123...# Limit to 30 tool calls per minute
npx git-doc-mcp --manifest https://example.com/.mcp/manifest.yml \
--rate-limit 30schemaVersion: "1.0" # Required - schema version
name: my-repo-mcp # Required - server name
version: 1.0.0 # Required - semantic version
description: Description # Optional - shown to AI
instructions: Use when... # Optional - helps AI understand when to use
secrets: # Optional - secrets needed
- name: GITHUB_TOKEN
description: GitHub token
scope:
- "https://api.github.com/*"
required: false
tools: # Optional - tool definitions
- name: fetch-file
title: Fetch File # Optional - human-readable title
description: Fetch a file # Required
inputSchema: # Required - JSON Schema
type: object
properties:
path: { type: string }
required: [path]
action: https://... # Required - URL or local file path to action script
actionHash: sha256:... # Required - SHA-256 hash of action content
annotations: # Optional - hints for AI
readOnlyHint: true
destructiveHint: false
idempotentHint: true
openWorldHint: true
resources: # Optional - static resources
- name: readme
uri: https://... # URL or local file path
description: README
mimeType: text/markdown
prompts: # Optional - prompt templates
- name: explain-code
description: Explain code
args:
- name: path
required: true
messages: # Optional - MCP PromptMessage[]
- role: user
content:
type: resource
resource:
uri: "https://example.com/{{path}}"
mimeType: text/plain
- role: user
content:
type: text
text: "Explain the code above from {{path}}"Prompts support the full MCP PromptMessage format with multi-message templates:
- Without
messages: A single user message is built fromdescription+ args (simple mode) - With
messages: Messages are returned directly with{{argName}}substitution
Each message has a role (user or assistant) and content (either text or embedded resource):
messages:
- role: user
content:
type: text
text: "Analyze {{path}} for {{focus}}"
- role: assistant
content:
type: text
text: "I'll analyze the code structure first."
- role: user
content:
type: resource
resource:
uri: "https://example.com/{{path}}"
mimeType: text/plainActions are plain JavaScript files that export a default async function:
export default async function myAction(input, ctx) {
const { fetch, getSecret, log, manifest } = ctx;
log('info', `Running action for ${manifest.name}`);
// Get a secret scoped to the target URL
const url = 'https://api.example.com/data';
const token = getSecret('API_KEY', url);
const response = await fetch(url, {
headers: token ? { 'Authorization': `Bearer ${token}` } : {}
});
// response.text is a property (not a method)
// response.json() is a synchronous method
const data = response.json();
return {
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }]
};
}| Method | Description |
|---|---|
ctx.fetch(url, options) |
Scoped fetch with SSRF protection and redirect validation |
ctx.getSecret(name, url) |
Get a secret value if the URL matches the secret's scope pattern |
ctx.log(level, message) |
Logging (levels: debug, info, warn, error) |
ctx.manifest |
Manifest metadata ({ name, version }) |
ctx.fetch returns a serialized response object (not a native Response):
| Property/Method | Type | Description |
|---|---|---|
response.ok |
boolean |
true if status is 200-299 |
response.status |
number |
HTTP status code |
response.statusText |
string |
HTTP status text |
response.text |
string |
Response body as a string (property, not method) |
response.json() |
object |
Parse body as JSON (synchronous method) |
response.headers |
object |
Response headers as key-value pairs |
// Success
return {
content: [{ type: 'text', text: 'Result' }]
};
// Error
return {
content: [{ type: 'text', text: 'Error message' }],
isError: true
};| Option | Description | Default |
|---|---|---|
--manifest <path> |
URL or local path to manifest.yml | (required) |
--manifest-header <header> |
Header for fetching manifest (repeatable) | |
--manifest-hash <hash> |
Expected manifest hash for pinning | |
--action-code-header <header> |
Header for downloading action scripts (repeatable) | |
--resource-header <header> |
Header for fetching resources (repeatable) | |
--secret <name=value> |
Pre-approved secret (repeatable) | |
--timeout <ms> |
Worker timeout in milliseconds | 60000 |
--memory-limit <bytes> |
Sandbox memory limit (8MB - 1GB) | 134217728 (128MB) |
--rate-limit <n> |
Max tool calls per minute (0 = unlimited) | 0 |
--allow-http |
Allow insecure HTTP URLs (HTTPS-only by default) | false |
--trust-changed |
Accept manifest hash changes (TOFU override) | false |
Secrets can be provided via environment variables prefixed with GIT_MCP_SECRET_:
export GIT_MCP_SECRET_GITHUB_TOKEN=ghp_abc123
export GIT_MCP_SECRET_API_KEY=sk-xyz789The --secret CLI flag takes precedence over environment variables.
# Basic usage
git-doc-mcp --manifest ./manifest.yml
# With authentication headers
git-doc-mcp --manifest https://... \
--manifest-header "Authorization: Bearer $TOKEN" \
--action-code-header "Authorization: Bearer $TOKEN"
# Separate resource headers
git-doc-mcp --manifest https://... \
--resource-header "Authorization: Bearer $RESOURCE_TOKEN"
# With secrets and rate limiting
git-doc-mcp --manifest https://... \
--secret GITHUB_TOKEN=$TOKEN \
--rate-limit 60
# Hash pinned (for CI/CD)
git-doc-mcp --manifest https://... --manifest-hash sha256:abc123...
# Accept manifest changes (TOFU override)
git-doc-mcp --manifest https://... --trust-changed
# Custom memory limit (256MB)
git-doc-mcp --manifest https://... --memory-limit 268435456
# Allow HTTP (not recommended for production)
git-doc-mcp --manifest http://localhost:8080/manifest.yml --allow-http| Feature | git-doc-mcp | Context7 | idosal/git-doc-mcp |
|---|---|---|---|
| Hosting | Any static host | Hosted service | Needs separate server |
| Custom actions | User-defined JS | Fixed tools | Limited actions |
| Private repos | Auth headers | OAuth | Unknown |
| Platform | Any (GitHub, GitLab, S3, etc.) | Any | GitHub only |
| Local development | Local files | No | No |
| Secret scoping | URL pattern matching | N/A | N/A |
| Manifest integrity | TOFU + hash pinning | N/A | N/A |
Users explicitly configure manifest URLs they trust. This is consistent with the official GitHub MCP Server's approach — the user configures which server to use, so they trust the server author.
Layer 1: Worker Process (Primary Boundary)
- Separate Node.js child process
- Sanitized environment (no inherited credentials)
- Crash in action doesn't kill CLI
Layer 2: isolated-vm (Defense-in-Depth)
- Configurable memory limit (default: 128MB)
- CPU timeout: 30s
- No direct filesystem/network access
Layer 3: Controlled API Surface
- ctx.fetch with SSRF protection
- ctx.getSecret with URL scope validation
- Audit logging of all network calls
All HTTP(S) URLs are validated before fetch:
- HTTPS-only by default (use
--allow-httpto override) - Private IP ranges blocked (10.x, 172.16-31.x, 192.168.x, localhost)
- DNS resolution validated before connection
- Every redirect URL re-validated
- Cross-origin redirects strip sensitive headers (Authorization, Cookie)
Local file paths in action and resource.uri are read directly from disk and are not subject to SSRF validation. ctx.fetch inside action scripts remains HTTP(S)-only regardless of how the action was loaded.
Secrets are scoped to specific URL patterns:
secrets:
- name: GITHUB_TOKEN
scope:
- "https://api.github.com/*"
- "https://raw.githubusercontent.com/*"ctx.getSecret(name, url) only returns the secret value if the URL matches the scope pattern. Path-boundary-aware wildcard matching prevents /repos-private from matching a /repos/* scope.
On first use, git-doc-mcp stores the manifest's SHA-256 hash. If the manifest changes:
Warning: Manifest content has changed since last use!
Previous: sha256:abc123...
Current: sha256:def456...
To accept this change, re-run with: --trust-changed
For CI pinning, use: --manifest-hash sha256:def456...
The server exits with a non-zero code unless --trust-changed is provided. For CI/CD, use --manifest-hash for hard pinning.
Sliding-window rate limiter prevents excessive tool calls:
# 60 calls per minute
git-doc-mcp --manifest https://... --rate-limit 60All ctx.fetch calls are logged to ~/.git-doc-mcp/logs/audit.jsonl:
- URL, HTTP status, duration
- Redirect hops
- Secret access (allowed/denied)
- Action start/end with timing
- Fork the repository
- Create a feature branch
- Make your changes
- Build:
npm run build - Run tests:
npm test - Submit a pull request
npm install # Install dependencies
npm run build # Build all packages
npm test # Run all tests (298 tests)git-doc-mcp/
packages/
core/ # Manifest loading, sandbox, worker, MCP server
cli/ # CLI entry point and serve command
template/ # Example manifest and actions
Full documentation is available on the GitHub Wiki:
- Getting Started — Create your first MCP server
- Manifest Reference — Complete schema docs
- Writing Actions — Action scripting API
- Examples — Complete working examples
- Security Model — Isolation architecture
- Troubleshooting — Common issues
MIT License - see LICENSE for details.