Skip to content

Specification

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

ConcernChoiceNotes
LanguageTypeScript (strict mode)exactOptionalPropertyTypes, noUncheckedIndexedAccess enabled
RuntimeCloudflare Workersesbuild bundler, nodejs_compat flag
MCP protocol@modelcontextprotocol/sdk + agents (Cloudflare McpAgent)HTTP+SSE transport, Durable Objects for session state
Input validationzodSchema validation for MCP tool parameters
Build scriptsNode.js with js-yamlIndex generation from YAML frontmatter
TestsVitestUnit tests with mocked fetch and Cache API
LintingESLint (typescript-eslint strict) + PrettierSingle-quote, no semicolons, trailing commas
CIGitHub ActionsChangelog validation (clq), build, test, deploy
DeployWrangler CLIwrangler deploy from CI pipeline

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 PAT

The 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 expired
  • 403 — token lacks required scope (contents: read on 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.


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.

VariableDescriptionExample
REPOowner/repo for skills and agentsArda-cards/agentic-workspace
SKILLS_PATHPath to skills root within repoinstructions/claude/skills
AGENTS_PATHPath to agents root within repoinstructions/claude/agents
BRANCHBranch to read from (skills/agents)main
DOCS_REPOowner/repo for documentationArda-cards/documentation
DOCS_CONTENT_PATHPath to Astro content root within docs reposrc/content/docs
DOCS_BRANCHBranch to read from (docs)main

Skills/agents and docs are configured separately since they live in different repositories.


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 frontmatter

Documentation 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.md

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.

---
name: docx
description: "Create, read, edit, or manipulate Word documents (.docx files)."
argument-hint: "[input-file] [output-format]"
user-invocable: true
---
# DOCX creation...
FieldRequiredDescription
nameyesSkill identifier (must match directory name)
descriptionyesOne-line description for index display
argument-hintnoHint for callers about expected arguments
user-invocablenoWhether the skill can be invoked directly by the user (default: false)
---
name: backend-engineer
description: "Kotlin/Spring backend specialist. Use for API design, database, and service layer tasks."
model: sonnet
allowedTools:
- Read
- Edit
- Write
- Bash
---
You are a senior backend engineer specialising in...
FieldRequiredDescription
nameyesAgent identifier (must match filename without .md)
descriptionyesOne-line description for index display
modelnoPreferred model for this agent (sonnet, opus, haiku)
allowedToolsnoList of MCP/CLI tools the agent is permitted to use

Standard Starlight frontmatter — the description field is used for index generation:

---
title: Kanban Engine State Machine
description: "Card state transitions, rules, and edge cases for the Kanban engine."
---
## Overview...

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 frontmatter argument-hint, or null if absent
  • userInvocable — from frontmatter user-invocable, defaults to false
  • files — list of companion files in the skill directory (relative paths, excluding SKILL.md)

The index is bundled into the Worker at build time. No GitHub API calls are made at runtime for this tool.


Fetches the full SKILL.md content for a named skill.

Parameters:

NameTypeRequiredDescription
namestringyesSkill directory name (e.g. docx)
skipCachebooleannoSkip 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.


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:

NameTypeRequiredDescription
skillstringyesSkill directory name
filestringyesRelative path within skill directory (e.g. examples/basic.ts)
skipCachebooleannoSkip 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.


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, or null if absent
  • allowedTools — from frontmatter, or null if absent

The index is bundled into the Worker at build time. No GitHub API calls are made at runtime for this tool.


Fetches the full profile for a named agent.

Parameters:

NameTypeRequiredDescription
namestringyesAgent filename without extension (e.g. backend-engineer)
skipCachebooleannoSkip 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.

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.


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.


Fetches the raw markdown source for a documentation page, addressed by path array matching the index structure.

Parameters:

NameTypeRequiredDescription
pathstring[]yesPath segments matching the index hierarchy (e.g. ["architecture", "kanban-engine", "state-machine"])
skipCachebooleannoSkip 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:

  1. Try index.md first: {docsContentPath}/{path.join('/')}/index.md
  2. 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.md

GitHub 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.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."
}
}

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.

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 index
const 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 index
const 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})`);

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");

The build runs on three triggers:

  1. PR merge to main — indexes always current after any change
  2. Weekly schedule — Saturday at midnight Pacific (07:00 UTC) to pick up changes from source repositories
  3. 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.

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.


Runtime get_* tool calls (which hit the GitHub Contents API) are cached using the Cloudflare Workers Cache API.

SettingValue
Default TTL10 minutes
Cache key{tool}:{repo}:{branch}:{path}
BypassskipCache: true parameter on any get_* tool

When skipCache is true:

  1. The cached entry is ignored
  2. A fresh response is fetched from GitHub
  3. 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.


Bundled indexes (build-time)
claude.ai → MCP HTTP+SSE → Cloudflare Worker → GitHub Contents API
↑ Bearer: Base64(PAT) ↑ Bearer: decoded PAT

list_* tools: Return bundled indexes directly — no external calls.

get_* tools:

  1. Worker receives MCP tool call over HTTP+SSE
  2. Extracts Bearer token from Authorization header — returns 401 if missing
  3. Decodes token from URL-safe Base64 to obtain the GitHub PAT
  4. Validates path parameters — rejects .., absolute paths, and backslashes with INVALID_PATH
  5. Checks cache (unless skipCache: true) — returns cached response on hit
  6. On cache miss, forwards request to GitHub Contents API with the decoded PAT
  7. For get_skill, get_agent, and get_doc: decodes Base64 content from GitHub, returns as plain text
  8. For get_skill_file: returns content as-is from GitHub (Base64-encoded)
  9. Caches the response, propagates non-2xx GitHub responses as MCP errors
  • Use Cloudflare’s McpAgent from the agents npm package for MCP protocol handling. McpAgent uses 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.

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": {
"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
}
}
}
CodeCause
UNAUTHORIZEDGitHub returned 401 — token invalid or expired
FORBIDDENGitHub returned 403 — token lacks contents: read on repo
NOT_FOUNDSkill, agent, or doc file does not exist (both index.md and *.md attempts failed for docs)
UPSTREAM_ERRORGitHub returned an unexpected non-2xx status
INVALID_PATHPath 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.


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.


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:

Terminal window
npm ci
npx wrangler deploy

  • Create fine-grained GitHub PAT: contents: read on Arda-cards/agentic-workspace and Arda-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) and CLOUDFLARE_API_TOKEN as repository secrets
  • Set all vars in wrangler.toml
  • Run CI pipeline to generate indexes and deploy: merge to main or trigger workflow_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.json with 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”
  1. Go to GitHub Settings > Developer settings > Personal access tokens > Fine-grained tokens (direct URL: https://github.com/settings/personal-access-tokens/new)

  2. 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-workspace
      • Arda-cards/documentation
    • Permissions > Repository permissions:
      • Contents: Read-only (this is the only permission needed)
  3. 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:

Terminal window
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 newline
  • base64 — standard Base64 encode
  • tr '+/' '-_' — convert to URL-safe alphabet (replace + with -, / with _)
  • tr -d '=' — remove padding characters

Verify it round-trips correctly:

Terminal window
echo -n "Z2l0aHViX3BhdF8..." | tr '-_' '+/' | base64 -d
# Should print your original github_pat_... token
  1. Open a claude.ai Project, go to Project Settings > MCP Servers > Add Server
  2. Set:
    • URL: https://claude-context-mcp.<subdomain>.workers.dev/mcp
    • Authentication: Bearer token
    • Token: paste your URL-safe Base64-encoded token from Step 2
  3. Add the Project system prompt (see below)
  4. Add the memory nudge (see below)

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.

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, then
get_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 adopt
the appropriate persona.
When you need context about system architecture, requirements, or design
decisions: call list_docs to see what documentation is available, then
get_doc with the relevant path array to fetch the content.
If content seems stale, use skipCache: true on get_* calls to fetch fresh.

Add to claude.ai memory (/memory):

I have a Claude context MCP server connected with skills, agent profiles, and
project documentation. For implementation tasks, always call list_skills and
get_skill before starting. When taking on a specialist role, call list_agents
and get_agent. When needing architecture or requirements context, call
list_docs and get_doc. Use skipCache: true if content seems outdated.

Decisions made during the specification review sessions. Each has a unique identifier for traceability.

IDDecisionRationale
DR-01Single 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-02Token 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-03No 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-04Indexes 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-05CI 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-06CI 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-07Acceptable 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-08get_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-09get_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-1010-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-11Use 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-12Graceful 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-13Include 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-14Path 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-15Docs 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-16compatibility_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-17Source 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-18TypeScript 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.