Specification
Overview
Section titled “Overview”A lightweight Cloudflare Worker exposing a Model Context Protocol (MCP) HTTP+SSE server that serves skill files, agent profiles, and documentation from GitHub repositories. Designed for use with claude.ai MCP connectors, giving cloud sessions access to the same skill library, agent personas, and project knowledge used by Claude Code CLI sessions.
- Serve skill files, agent profiles, and documentation from GitHub repositories over MCP
- Authenticate to GitHub using the caller’s token (passed as a URL-safe Base64-encoded Bearer token)
- Use compile-time configuration for repositories, paths, and branches — no per-client overrides
- Bundle pre-built indexes into the Worker at build time —
list_*tools serve bundled data with zero runtime GitHub API calls - Deploy on Cloudflare Workers free tier with zero server management
- No secrets stored on the Worker itself at runtime
Technology Stack
Section titled “Technology Stack”| Concern | Choice | Notes |
|---|---|---|
| Language | TypeScript (strict mode) | exactOptionalPropertyTypes, noUncheckedIndexedAccess enabled |
| Runtime | Cloudflare Workers | esbuild bundler, nodejs_compat flag |
| MCP protocol | @modelcontextprotocol/sdk + agents (Cloudflare McpAgent) | HTTP+SSE transport, Durable Objects for session state |
| Input validation | zod | Schema validation for MCP tool parameters |
| Build scripts | Node.js with js-yaml | Index generation from YAML frontmatter |
| Tests | Vitest | Unit tests with mocked fetch and Cache API |
| Linting | ESLint (typescript-eslint strict) + Prettier | Single-quote, no semicolons, trailing commas |
| CI | GitHub Actions | Changelog validation (clq), build, test, deploy |
| Deploy | Wrangler CLI | wrangler deploy from CI pipeline |
Authentication
Section titled “Authentication”The caller passes their GitHub PAT as a URL-safe Base64-encoded Bearer token in the Authorization header:
Authorization: Bearer dXNlcl90b2tlbl9oZXJl # URL-safe Base64 of the GitHub PATThe Worker decodes the token from URL-safe Base64 (RFC 4648 §5, using -_ instead of +/) and forwards the decoded PAT to the GitHub Contents API. This obfuscates the underlying authentication mechanism — callers interact with an opaque token, and the fact that the server relies on GitHub is hidden.
GitHub’s response codes propagate to the caller:
401— token invalid or expired403— token lacks required scope (contents: readon the target repo)404— file or repo not found
No additional auth layer is needed. The Worker has no stored secrets.
Required PAT type: Fine-grained Personal Access Token with contents: read scoped to all repositories the server needs to access (skills, agents, and docs). A single PAT with access to all target repositories is recommended.
Configuration
Section titled “Configuration”All configuration is set as Cloudflare Worker environment variables at compile time. There are no per-client overrides — every caller uses the same repository configuration.
| Variable | Description | Example |
|---|---|---|
REPO | owner/repo for skills and agents | Arda-cards/agentic-workspace |
SKILLS_PATH | Path to skills root within repo | instructions/claude/skills |
AGENTS_PATH | Path to agents root within repo | instructions/claude/agents |
BRANCH | Branch to read from (skills/agents) | main |
DOCS_REPO | owner/repo for documentation | Arda-cards/documentation |
DOCS_CONTENT_PATH | Path to Astro content root within docs repo | src/content/docs |
DOCS_BRANCH | Branch to read from (docs) | main |
Skills/agents and docs are configured separately since they live in different repositories.
Repository Structures
Section titled “Repository Structures”Skills & Agents Repository (Arda-cards/agentic-workspace)
Section titled “Skills & Agents Repository (Arda-cards/agentic-workspace)”The repository root maps to the local workspace/ directory. Skills and agents live under instructions/claude/:
instructions/claude/ skills/ <skill-name>/ SKILL.md # Required — skill instructions, should have frontmatter examples/ # Optional — companion files assets/ # Optional — companion files references/ # Optional — companion files agents/ <agent-name>.md # Agent profile, should have frontmatterDocumentation Repository (Arda-cards/documentation)
Section titled “Documentation Repository (Arda-cards/documentation)”src/content/docs/ architecture/ index.md # Section landing page (Starlight convention) data-model.md kanban-engine/ index.md state-machine.md requirements/ index.md inventory.mdFrontmatter Conventions
Section titled “Frontmatter Conventions”All skill and agent files should have YAML frontmatter. Frontmatter is parsed using a proper YAML parser (js-yaml), so both inline and block scalar (>) description formats are supported.
If a file is missing frontmatter entirely, the build process does not fail — the file is included in the index with its filename as the name and a null description.
Skill Frontmatter
Section titled “Skill Frontmatter”---name: docxdescription: "Create, read, edit, or manipulate Word documents (.docx files)."argument-hint: "[input-file] [output-format]"user-invocable: true---
# DOCX creation...| Field | Required | Description |
|---|---|---|
name | yes | Skill identifier (must match directory name) |
description | yes | One-line description for index display |
argument-hint | no | Hint for callers about expected arguments |
user-invocable | no | Whether the skill can be invoked directly by the user (default: false) |
Agent Frontmatter
Section titled “Agent Frontmatter”---name: backend-engineerdescription: "Kotlin/Spring backend specialist. Use for API design, database, and service layer tasks."model: sonnetallowedTools: - Read - Edit - Write - Bash---
You are a senior backend engineer specialising in...| Field | Required | Description |
|---|---|---|
name | yes | Agent identifier (must match filename without .md) |
description | yes | One-line description for index display |
model | no | Preferred model for this agent (sonnet, opus, haiku) |
allowedTools | no | List of MCP/CLI tools the agent is permitted to use |
Doc Page Frontmatter
Section titled “Doc Page Frontmatter”Standard Starlight frontmatter — the description field is used for index generation:
---title: Kanban Engine State Machinedescription: "Card state transitions, rules, and edge cases for the Kanban engine."---
## Overview...MCP Tools
Section titled “MCP Tools”list_skills
Section titled “list_skills”Lists all available skills with names, descriptions, metadata, and companion file manifests.
Parameters: none
Returns:
{ "skills": [ { "name": "docx", "description": "Create, read, edit, or manipulate Word documents (.docx files).", "argumentHint": "[input-file] [output-format]", "userInvocable": true, "files": ["examples/basic.ts", "assets/template.docx"] }, { "name": "frontend-design", "description": "Create distinctive, production-grade frontend interfaces.", "argumentHint": null, "userInvocable": false, "files": [] } ]}Fields:
argumentHint— from frontmatterargument-hint, ornullif absentuserInvocable— from frontmatteruser-invocable, defaults tofalsefiles— list of companion files in the skill directory (relative paths, excludingSKILL.md)
The index is bundled into the Worker at build time. No GitHub API calls are made at runtime for this tool.
get_skill
Section titled “get_skill”Fetches the full SKILL.md content for a named skill.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
name | string | yes | Skill directory name (e.g. docx) |
skipCache | boolean | no | Skip the cache and fetch fresh from GitHub. Default: false |
Returns:
{ "name": "docx", "content": "# DOCX creation...\n...", "path": "instructions/claude/skills/docx/SKILL.md", "sha": "abc123"}GitHub API call:
GET /repos/{repo}/contents/{skillsPath}/{name}/SKILL.md?ref={branch}Caching: Responses are cached for 10 minutes. When skipCache is true, the cache is bypassed and the cached entry is refreshed with the new response.
get_skill_file
Section titled “get_skill_file”Fetches an arbitrary file from within a skill directory. Content is returned exactly as GitHub provides it — Base64-encoded with the content type from GitHub’s response.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
skill | string | yes | Skill directory name |
file | string | yes | Relative path within skill directory (e.g. examples/basic.ts) |
skipCache | boolean | no | Skip the cache and fetch fresh from GitHub. Default: false |
Path validation: Both skill and file parameters are validated to prevent path traversal. The following are rejected with an INVALID_PATH error:
- Values containing
..(parent directory traversal) - Values starting with
/(absolute paths) - Values containing
\(backslash paths)
Returns:
{ "skill": "docx", "file": "examples/basic.ts", "content": "aW1wb3J0IHsgLi4uIH0g...", "encoding": "base64", "size": 1234}All files are returned Base64-encoded. The caller is responsible for decoding.
Caching: Same 10-minute TTL as get_skill. skipCache bypasses and refreshes.
list_agents
Section titled “list_agents”Lists all available agent profiles with names, descriptions, and metadata.
Parameters: none
Returns:
{ "agents": [ { "name": "backend-engineer", "description": "Kotlin/Spring backend specialist. Use for API design, database, and service layer tasks.", "model": "sonnet", "allowedTools": ["Read", "Edit", "Write", "Bash"] }, { "name": "frontend-engineer", "description": "React/TypeScript specialist. Use for component architecture and UI implementation.", "model": "sonnet", "allowedTools": ["Read", "Edit", "Write", "Bash", "Glob", "Grep"] } ]}Fields:
model— from frontmatter, ornullif absentallowedTools— from frontmatter, ornullif absent
The index is bundled into the Worker at build time. No GitHub API calls are made at runtime for this tool.
get_agent
Section titled “get_agent”Fetches the full profile for a named agent.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
name | string | yes | Agent filename without extension (e.g. backend-engineer) |
skipCache | boolean | no | Skip the cache and fetch fresh from GitHub. Default: false |
Returns:
{ "name": "backend-engineer", "content": "---\nname: backend-engineer\n...\n---\n\nYou are a senior backend engineer...", "path": "instructions/claude/agents/backend-engineer.md", "sha": "abc123"}Caching: Same 10-minute TTL. skipCache bypasses and refreshes.
Usage in claude.ai vs Claude Code
Section titled “Usage in claude.ai vs Claude Code”In Claude Code CLI, agent profiles spin up isolated sub-agents in multi-agent orchestration.
In claude.ai, get_agent loads a persona Claude adopts within the current session. Instruct Claude explicitly:
Call get_agent("backend-engineer") and adopt that persona for this conversation.Same profile definitions work across both environments; the invocation mechanism differs.
list_docs
Section titled “list_docs”Returns the documentation index as a hierarchical structure. Leaf values are page descriptions; intermediate nodes are sections. Enables Claude to navigate to the right page without scanning the repo.
Parameters: none
Returns:
{ "index": { "architecture": { "index": "High-level system architecture and component relationships.", "data-model": "Core entities, relationships, and database schema.", "kanban-engine": { "index": "Kanban board engine design and responsibilities.", "state-machine": "Card state transitions, rules, and edge cases." } }, "requirements": { "index": "Product requirements index.", "inventory": "Inventory tracking requirements.", "manufacturing": "Manufacturing workflow requirements." } }}The index is bundled into the Worker at build time. No GitHub API calls are made at runtime for this tool.
get_doc
Section titled “get_doc”Fetches the raw markdown source for a documentation page, addressed by path array matching the index structure.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
path | string[] | yes | Path segments matching the index hierarchy (e.g. ["architecture", "kanban-engine", "state-machine"]) |
skipCache | boolean | no | Skip the cache and fetch fresh from GitHub. Default: false |
Path validation: Each segment in the path array is validated — segments containing .., starting with /, or containing \ are rejected with an INVALID_PATH error.
Returns:
{ "path": ["architecture", "kanban-engine", "state-machine"], "content": "---\ntitle: Kanban Engine State Machine\n...\n---\n\n## Overview...", "filePath": "src/content/docs/architecture/kanban-engine/state-machine.md", "sha": "abc123"}Path resolution:
The path array is resolved using Starlight’s index.md convention with fallback:
- Try
index.mdfirst:{docsContentPath}/{path.join('/')}/index.md - Fall back to
*.md:{docsContentPath}/{path.join('/')}.md
Examples:
["architecture"]→ try: src/content/docs/architecture/index.md→ fall back: src/content/docs/architecture.md
["architecture", "kanban-engine", "state-machine"]→ try: src/content/docs/architecture/kanban-engine/state-machine/index.md→ fall back: src/content/docs/architecture/kanban-engine/state-machine.mdGitHub API calls:
GET /repos/{docsRepo}/contents/{docsContentPath}/{path.join('/')}/index.md?ref={docsBranch}# If 404:GET /repos/{docsRepo}/contents/{docsContentPath}/{path.join('/')}.md?ref={docsBranch}Raw markdown source is returned — not rendered HTML. This is cleaner and more token-efficient than fetching the public GitHub Pages site.
Caching: Same 10-minute TTL. skipCache bypasses and refreshes.
Docs Index Format
Section titled “Docs Index Format”docs-index.json is a hierarchical structure where leaf values are page descriptions and intermediate keys are section names. Keys must match the directory and file structure under docsContentPath exactly. The index key represents index.md section landing pages.
{ "architecture": { "index": "High-level system architecture and component relationships.", "data-model": "Core entities, relationships, and database schema.", "kanban-engine": { "index": "Kanban board engine design and responsibilities.", "state-machine": "Card state transitions, rules, and edge cases." } }, "requirements": { "index": "Product requirements index.", "inventory": "Inventory tracking requirements.", "manufacturing": "Manufacturing workflow requirements." }}Build Process
Section titled “Build Process”The Worker build process generates all index files and bundles them into the Worker. This runs in CI — the Worker itself makes zero GitHub API calls for list_* operations.
Index Generation Scripts
Section titled “Index Generation Scripts”All index generation scripts use js-yaml for proper YAML frontmatter parsing, supporting both inline and block scalar (>) description formats. If a file has no frontmatter, it is included in the index with the filename as the name and null as the description — the build does not fail.
Skills & Agents Index (scripts/build-skills-agents-index.js)
Section titled “Skills & Agents Index (scripts/build-skills-agents-index.js)”Clones/fetches the skills and agents repository and generates skills-index.json and agents-index.json from frontmatter.
import { readdirSync, readFileSync, writeFileSync } from "fs";import { join, relative } from "path";import { load } from "js-yaml";
function parseFrontmatter(content) { const match = content.match(/^---\n([\s\S]*?)\n---/); if (!match) return {}; try { return load(match[1]) ?? {}; } catch { return {}; }}
function listFilesRecursive(dir, base = dir) { const results = []; for (const entry of readdirSync(dir, { withFileTypes: true })) { const fullPath = join(dir, entry.name); if (entry.isDirectory()) { results.push(...listFilesRecursive(fullPath, base)); } else if (entry.name !== "SKILL.md") { results.push(relative(base, fullPath)); } } return results;}
// Skills indexconst skillsPath = process.env.SKILLS_PATH || "instructions/claude/skills";const skillDirs = readdirSync(skillsPath, { withFileTypes: true }) .filter(d => d.isDirectory());
const skillsIndex = skillDirs.map(dir => { const skillDir = join(skillsPath, dir.name); const content = readFileSync(join(skillDir, "SKILL.md"), "utf8"); const fm = parseFrontmatter(content); return { name: fm.name ?? dir.name, description: fm.description ?? null, argumentHint: fm["argument-hint"] ?? null, userInvocable: fm["user-invocable"] ?? false, files: listFilesRecursive(skillDir), };});
writeFileSync("src/generated/skills-index.json", JSON.stringify(skillsIndex, null, 2));
// Agents indexconst agentsPath = process.env.AGENTS_PATH || "instructions/claude/agents";const agentFiles = readdirSync(agentsPath).filter(f => f.endsWith(".md"));
const agentsIndex = agentFiles.map(file => { const content = readFileSync(join(agentsPath, file), "utf8"); const fm = parseFrontmatter(content); return { name: fm.name ?? file.replace(/\.md$/, ""), description: fm.description ?? null, model: fm.model ?? null, allowedTools: fm.allowedTools ?? null, };});
writeFileSync("src/generated/agents-index.json", JSON.stringify(agentsIndex, null, 2));
console.log(`Built skills index (${skillsIndex.length}), agents index (${agentsIndex.length})`);Docs Index (scripts/build-docs-index.js)
Section titled “Docs Index (scripts/build-docs-index.js)”Clones/fetches the documentation repository and generates docs-index.json from Astro content frontmatter.
import { readdirSync, readFileSync, writeFileSync } from "fs";import { join, basename, extname } from "path";import { load } from "js-yaml";
const docsPath = process.env.DOCS_CONTENT_PATH || "src/content/docs";
function parseFrontmatter(content) { const match = content.match(/^---\n([\s\S]*?)\n---/); if (!match) return {}; try { return load(match[1]) ?? {}; } catch { return {}; }}
function buildTree(dir) { const entries = readdirSync(dir, { withFileTypes: true }); const node = {};
for (const entry of entries) { const fullPath = join(dir, entry.name); if (entry.isDirectory()) { const subtree = buildTree(fullPath); if (Object.keys(subtree).length > 0) { node[entry.name] = subtree; } } else if (entry.isFile() && (extname(entry.name) === ".md" || extname(entry.name) === ".mdx")) { const key = basename(entry.name, extname(entry.name)); const content = readFileSync(fullPath, "utf8"); const fm = parseFrontmatter(content); node[key] = fm.description ?? key; } } return node;}
const tree = buildTree(docsPath);writeFileSync("src/generated/docs-index.json", JSON.stringify(tree, null, 2));console.log("Built docs-index.json");CI Pipeline (GitHub Actions)
Section titled “CI Pipeline (GitHub Actions)”The build runs on three triggers:
- PR merge to
main— indexes always current after any change - Weekly schedule — Saturday at midnight Pacific (07:00 UTC) to pick up changes from source repositories
- Manual dispatch — on-demand rebuilds via
workflow_dispatch
name: Build and Deploy
on: push: branches: [main] schedule: - cron: "0 7 * * 6" # Saturday 00:00 Pacific (07:00 UTC) workflow_dispatch:
jobs: build-and-deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- uses: actions/setup-node@v4 with: node-version: 22
- name: Install dependencies run: npm ci
# Clone source repositories (shallow, read-only) # The agentic-workspace repo root corresponds to the local workspace/ directory. # Paths like instructions/claude/skills are relative to this root. - name: Clone skills/agents repository run: | git clone --depth 1 --branch main \ https://x-access-token:${{ secrets.REPO_ACCESS_TOKEN }}@github.com/${{ vars.REPO }}.git \ /tmp/skills-repo
- name: Clone documentation repository run: | git clone --depth 1 --branch main \ https://x-access-token:${{ secrets.REPO_ACCESS_TOKEN }}@github.com/${{ vars.DOCS_REPO }}.git \ /tmp/docs-repo
- name: Generate indexes run: | mkdir -p src/generated SKILLS_PATH=/tmp/skills-repo/${{ vars.SKILLS_PATH }} \ AGENTS_PATH=/tmp/skills-repo/${{ vars.AGENTS_PATH }} \ node scripts/build-skills-agents-index.js DOCS_CONTENT_PATH=/tmp/docs-repo/${{ vars.DOCS_CONTENT_PATH }} \ node scripts/build-docs-index.js
- name: Deploy to Cloudflare Workers run: npx wrangler deploy env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}The REPO_ACCESS_TOKEN is a GitHub PAT (or GitHub App token) with contents: read on the source repositories. It is stored as a repository secret and never leaves the CI pipeline.
In the future, source repositories may trigger this workflow directly via repository_dispatch for immediate index updates on merge.
Bundled Imports
Section titled “Bundled Imports”The Worker imports the generated indexes as static modules:
import skillsIndex from "./generated/skills-index.json";import agentsIndex from "./generated/agents-index.json";import docsIndex from "./generated/docs-index.json";These are embedded in the Worker bundle at deploy time. list_skills, list_agents, and list_docs serve these directly with zero runtime cost.
Caching
Section titled “Caching”Runtime get_* tool calls (which hit the GitHub Contents API) are cached using the Cloudflare Workers Cache API.
| Setting | Value |
|---|---|
| Default TTL | 10 minutes |
| Cache key | {tool}:{repo}:{branch}:{path} |
| Bypass | skipCache: true parameter on any get_* tool |
When skipCache is true:
- The cached entry is ignored
- A fresh response is fetched from GitHub
- The cache is updated with the new response
The 10-minute TTL is intentionally short for the initial rollout while skills are actively evolving. It will be extended as the rate of change stabilizes.
list_* tools serve bundled indexes and do not use the cache.
Worker Implementation
Section titled “Worker Implementation”Request Flow
Section titled “Request Flow” Bundled indexes (build-time) ↓claude.ai → MCP HTTP+SSE → Cloudflare Worker → GitHub Contents API ↑ Bearer: Base64(PAT) ↑ Bearer: decoded PATlist_* tools: Return bundled indexes directly — no external calls.
get_* tools:
- Worker receives MCP tool call over HTTP+SSE
- Extracts Bearer token from
Authorizationheader — returns401if missing - Decodes token from URL-safe Base64 to obtain the GitHub PAT
- Validates path parameters — rejects
.., absolute paths, and backslashes withINVALID_PATH - Checks cache (unless
skipCache: true) — returns cached response on hit - On cache miss, forwards request to GitHub Contents API with the decoded PAT
- For
get_skill,get_agent, andget_doc: decodes Base64 content from GitHub, returns as plain text - For
get_skill_file: returns content as-is from GitHub (Base64-encoded) - Caches the response, propagates non-2xx GitHub responses as MCP errors
Key Implementation Notes
Section titled “Key Implementation Notes”- Use Cloudflare’s
McpAgentfrom theagentsnpm package for MCP protocol handling.McpAgentuses Durable Objects internally for session state. - The Worker holds no secrets at runtime. The PAT travels in the request header and is never logged or stored.
- Skills/agents and docs target different repos — resolve the correct repo per tool call based on which tool is being invoked.
Path Validation
Section titled “Path Validation”All get_* tools that accept path parameters must validate inputs before constructing GitHub API URLs. The validation function rejects:
function validatePathSegment(segment: string): void { if (segment.includes("..") || segment.startsWith("/") || segment.includes("\\")) { throw new McpError("INVALID_PATH", `Path segment is not allowed: ${segment}`); }}This prevents callers from traversing outside the configured skill/agent/docs directories to access other files in the repository (e.g., settings.json, hook scripts, or project memory files that also live under instructions/claude/).
Error Response Shape
Section titled “Error Response Shape”{ "error": { "code": "NOT_FOUND", "message": "Doc page not found at src/content/docs/architecture/kanban-engine/state-machine.md", "details": { "repo": "Arda-cards/documentation", "path": "src/content/docs/architecture/kanban-engine/state-machine.md", "branch": "main", "githubStatus": 404 } }}| Code | Cause |
|---|---|
UNAUTHORIZED | GitHub returned 401 — token invalid or expired |
FORBIDDEN | GitHub returned 403 — token lacks contents: read on repo |
NOT_FOUND | Skill, agent, or doc file does not exist (both index.md and *.md attempts failed for docs) |
UPSTREAM_ERROR | GitHub returned an unexpected non-2xx status |
INVALID_PATH | Path parameter contains traversal sequences (..), absolute paths, or backslashes |
HTTP-level auth failures (missing or un-decodable Bearer token) return 401 before reaching MCP tool dispatch.
Rate Limits
Section titled “Rate Limits”GitHub’s Contents API allows 5,000 requests/hour per authenticated PAT. Since list_* tools serve bundled indexes (zero GitHub calls) and get_* responses are cached for 10 minutes, actual GitHub API usage is minimal — well within limits for a team of users.
Cloudflare Workers free tier: 100,000 requests/day. Worker CPU time is dominated by JSON parsing, well within the 10ms limit per invocation.
wrangler.toml
Section titled “wrangler.toml”name = "claude-context-mcp"main = "src/index.ts"compatibility_date = "2025-06-01"compatibility_flags = ["nodejs_compat"]
[vars]REPO = "Arda-cards/agentic-workspace"SKILLS_PATH = "instructions/claude/skills"AGENTS_PATH = "instructions/claude/agents"BRANCH = "main"
DOCS_REPO = "Arda-cards/documentation"DOCS_CONTENT_PATH = "src/content/docs"DOCS_BRANCH = "main"Deploy:
npm cinpx wrangler deployDeployment Checklist
Section titled “Deployment Checklist”- Create fine-grained GitHub PAT:
contents: readonArda-cards/agentic-workspaceandArda-cards/documentation - Ensure skills repository matches expected structure with frontmatter on all skill and agent files
- Set repository variables (
REPO,DOCS_REPO,SKILLS_PATH,AGENTS_PATH,DOCS_CONTENT_PATH) in GitHub Actions - Add
REPO_ACCESS_TOKEN(GitHub PAT for CI) andCLOUDFLARE_API_TOKENas repository secrets - Set all vars in
wrangler.toml - Run CI pipeline to generate indexes and deploy: merge to
mainor triggerworkflow_dispatch - Add connector in claude.ai: URL =
https://claude-context-mcp.<subdomain>.workers.dev/mcp, Auth = Bearer (URL-safe Base64 of your PAT) - Add to Claude Code
mcp.jsonwith same URL and token - Add system prompt to relevant claude.ai Projects (see Usage below)
- Verify scheduled Saturday builds run correctly
Step 1: Create a GitHub Personal Access Token
Section titled “Step 1: Create a GitHub Personal Access Token”-
Go to GitHub Settings > Developer settings > Personal access tokens > Fine-grained tokens (direct URL:
https://github.com/settings/personal-access-tokens/new) -
Configure the token:
- Token name:
claude-context-mcp(or any descriptive name) - Expiration: choose an appropriate lifetime (90 days recommended; set a calendar reminder to rotate)
- Resource owner:
Arda-cards - Repository access: select Only select repositories, then add:
Arda-cards/agentic-workspaceArda-cards/documentation
- Permissions > Repository permissions:
- Contents:
Read-only(this is the only permission needed)
- Contents:
- Token name:
-
Click Generate token and copy the token value (starts with
github_pat_). You will not be able to see it again.
Step 2: Encode the Token as URL-safe Base64
Section titled “Step 2: Encode the Token as URL-safe Base64”The MCP server expects the token encoded as URL-safe Base64 (RFC 4648 §5).
macOS / Linux:
echo -n "github_pat_YOUR_TOKEN_HERE" | base64 | tr '+/' '-_' | tr -d '='This outputs a string like Z2l0aHViX3BhdF8... — this is your encoded token.
Explanation of the pipeline:
echo -n— print the token without a trailing newlinebase64— standard Base64 encodetr '+/' '-_'— convert to URL-safe alphabet (replace+with-,/with_)tr -d '='— remove padding characters
Verify it round-trips correctly:
echo -n "Z2l0aHViX3BhdF8..." | tr '-_' '+/' | base64 -d# Should print your original github_pat_... tokenStep 3: Configure MCP Clients
Section titled “Step 3: Configure MCP Clients”claude.ai
Section titled “claude.ai”- Open a claude.ai Project, go to Project Settings > MCP Servers > Add Server
- Set:
- URL:
https://claude-context-mcp.<subdomain>.workers.dev/mcp - Authentication: Bearer token
- Token: paste your URL-safe Base64-encoded token from Step 2
- URL:
- Add the Project system prompt (see below)
- Add the memory nudge (see below)
Claude Code
Section titled “Claude Code”Add to your mcp.json (global ~/.claude/mcp.json or project-level .claude/mcp.json):
{ "mcpServers": { "claude-context": { "url": "https://claude-context-mcp.<subdomain>.workers.dev/mcp", "headers": { "Authorization": "Bearer <your-URL-safe-Base64-encoded-token>" } } }}Replace <your-URL-safe-Base64-encoded-token> with the output from Step 2.
claude.ai Project System Prompt
Section titled “claude.ai Project System Prompt”You have access to a Claude context MCP server with skills, agent profiles,and project documentation.
For any task involving file creation or technical implementation (docx, pptx,pdf, spreadsheets, code, frontend components, etc.): call list_skills, thenget_skill for the relevant skill and follow its instructions before proceeding.
When asked to take on a specialist role, or when a task would benefit from one:call list_agents to see available profiles, then get_agent to load and adoptthe appropriate persona.
When you need context about system architecture, requirements, or designdecisions: call list_docs to see what documentation is available, thenget_doc with the relevant path array to fetch the content.
If content seems stale, use skipCache: true on get_* calls to fetch fresh.Global Memory Nudge
Section titled “Global Memory Nudge”Add to claude.ai memory (/memory):
I have a Claude context MCP server connected with skills, agent profiles, andproject documentation. For implementation tasks, always call list_skills andget_skill before starting. When taking on a specialist role, call list_agentsand get_agent. When needing architecture or requirements context, calllist_docs and get_doc. Use skipCache: true if content seems outdated.Decision Record
Section titled “Decision Record”Decisions made during the specification review sessions. Each has a unique identifier for traceability.
| ID | Decision | Rationale |
|---|---|---|
| DR-01 | Single PAT for all repositories. One fine-grained PAT with contents: read on all target repos, rather than separate PATs per repo. | The Worker receives a single Authorization header per session — no mechanism to supply multiple tokens. A single PAT scoped to the specific repos is the simplest approach with acceptable blast radius. |
| DR-02 | Token sent as URL-safe Base64 in Bearer header. Caller Base64-encodes the GitHub PAT; Worker decodes on-the-fly before forwarding to GitHub. | Obfuscates the underlying authentication mechanism. Callers interact with an opaque token — the fact that the server relies on GitHub is hidden. URL-safe variant (RFC 4648 §5) avoids edge cases if the token transits through URL contexts. |
| DR-03 | No initializationOptions — compile-time defaults only. All repository/path/branch configuration is baked into the Worker at deploy time. No per-client overrides. | This is an internal company tool for shared information. Per-client flexibility is a nice-to-have deferred to a future version. Removing initializationOptions eliminates session state complexity and the need for Durable Objects solely for config. |
| DR-04 | Indexes bundled into the Worker at build time. list_* tools serve static JSON imported at compile time — zero runtime GitHub API calls. | Eliminates the single-request GitHub API call per list_* invocation. Combined with the scheduled rebuild, this trades freshness (up to 1 week staleness for new skills) for reliability and speed. |
| DR-05 | CI builds on merge to main, Saturday midnight Pacific, and workflow_dispatch. Three triggers for index regeneration and Worker deployment. | Merge-to-main covers changes to the MCP server itself. Weekly Saturday build picks up changes in source repositories (skills, agents, docs). Manual dispatch provides an escape hatch for urgent updates. Future: source repos may trigger via repository_dispatch. |
| DR-06 | CI pipeline holds a GitHub PAT as a repository secret. The REPO_ACCESS_TOKEN secret is used at build time to clone source repos for index generation. | The PAT never leaves CI. It is stored as a GitHub Actions secret, used only during the build step, and is not embedded in the Worker bundle. |
| DR-07 | Acceptable index staleness: up to 1 week. A newly added skill/agent won’t appear in list_* until the next build (merge, Saturday schedule, or manual dispatch). | Skills and agents change infrequently. The weekly rebuild plus manual dispatch provides sufficient freshness for an internal tool. |
| DR-08 | get_doc resolves index.md first, then *.md. For path ["architecture"], tries architecture/index.md before architecture.md. | Follows Starlight/Astro convention where section landing pages are index.md. Most sections in the documentation site use this pattern. Fallback to *.md handles leaf pages that are files rather than directories. |
| DR-09 | get_skill_file returns content as-is from GitHub (Base64-encoded). All other get_* tools decode to plain text. | get_skill_file may serve binary assets (images, templates). Returning raw Base64 is the only universal encoding. get_skill, get_agent, and get_doc always serve text (Markdown), so decoding to plain text is safe and more useful for the caller. |
| DR-10 | 10-minute cache TTL with skipCache bypass. get_* responses cached via Cloudflare Cache API. | Short TTL for initial rollout while skills are actively evolving. Will be extended as change rate stabilizes. skipCache: true lets the calling agent force a fresh fetch when content seems stale. |
| DR-11 | Use js-yaml for frontmatter parsing. Proper YAML parser instead of regex extraction. | The codebase uses both inline (description: "...") and block scalar (description: >) YAML formats. Regex fails on block scalars, which are used by the majority of skill and agent files. |
| DR-12 | Graceful fallback for missing frontmatter. Files without frontmatter are included in the index with filename as name and null description — build does not fail. | At least one skill (parallel-agent-integration) was missing frontmatter at time of review. Failing the entire build for one missing frontmatter block is too fragile. |
| DR-13 | Include metadata in indexes beyond name/description. Skills: argumentHint, userInvocable, files (companion manifest). Agents: model, allowedTools. | These fields exist in the frontmatter and are useful to callers. files manifest lets callers discover companion files without trial-and-error get_skill_file calls. model and allowedTools inform agent orchestration decisions. |
| DR-14 | Path traversal validation on all get_* path parameters. Reject .., absolute paths, and backslashes. | The instructions/claude/ directory contains sensitive non-skill files (settings.json, settings.local.json, hook scripts, project memory). Without validation, get_skill_file(skill: "..", file: "../settings.json") could exfiltrate them. |
| DR-15 | Docs index includes .mdx files. Build script filters for both .md and .mdx extensions. | Starlight supports both formats. The documentation site may contain .mdx pages with JSX components. Excluding them would create gaps in the docs index. |
| DR-16 | compatibility_date set to 2025-06-01. | Targets a recent date to ensure compatibility with the agents npm package (McpAgent). Older dates may lack runtime features the package depends on. Set to a recent-but-not-bleeding-edge date as recommended practice. |
| DR-17 | Source repository: Arda-cards/agentic-workspace. Skills and agents path is instructions/claude/{skills,agents} from the repo root. | Verified against the actual GitHub repository structure. The repo root corresponds to the local workspace/ directory — paths are repo-root-relative. |
| DR-18 | TypeScript with Cloudflare Workers toolchain. TypeScript strict mode, esbuild bundler, Vitest tests, ESLint + Prettier, Wrangler CLI for deploy. | Matches the established patterns from hypothesis-mcp. TypeScript provides type safety for MCP tool schemas and GitHub API responses. Cloudflare Workers is the target runtime, so Wrangler is the natural build/deploy tool. Vitest is the team standard for testing. |
Copyright: © Arda Systems 2025-2026, All rights reserved