Implementation Changes
This document provides step-by-step implementation instructions for the Arda-cards/skills-mcp repository (greenfield). All file paths are relative to the repository root.
Open Questions and Decisions
Section titled “Open Questions and Decisions”| # | Question | Options | Recommendation | Decision |
|---|---|---|---|---|
| 1 | Exact McpAgent API for accessing auth headers from the initial SSE connection | (a) Override lifecycle method, (b) Access via this.request, (c) Forward as query param from fetch handler | Investigate during implementation — the agents npm package API may vary by version | Open |
| 2 | Durable Objects config in wrangler.toml | (a) Explicit [durable_objects] bindings, (b) Inferred by McpAgent.mount() | Check agents package docs — may auto-configure | Open |
| 3 | Test approach for Cloudflare Worker-specific code | (a) Vitest with miniflare, (b) Unit test pure logic only, mock Worker APIs | (b) for v1 — test auth, github client, cache logic, path validation as pure functions; integration test via manual deploy | Open |
Phase 1: Project Scaffolding
Section titled “Phase 1: Project Scaffolding”All boilerplate files following the hypothesis-mcp patterns.
Task 1.1: Package Configuration
Section titled “Task 1.1: Package Configuration”Create package.json:
{ "name": "@arda-cards/skills-mcp", "version": "1.0.0", "private": true, "description": "MCP server serving skills, agent profiles, and documentation from GitHub repositories", "type": "module", "scripts": { "build": "wrangler deploy --dry-run --outdir=dist", "dev": "wrangler dev", "deploy": "wrangler deploy", "test": "vitest run", "test:watch": "vitest", "lint": "eslint src tests scripts", "format": "prettier --write src tests scripts", "typecheck": "tsc --noEmit", "generate-indexes": "mkdir -p src/generated && node scripts/build-skills-agents-index.js && node scripts/build-docs-index.js" }, "simple-git-hooks": { "pre-commit": "npx lint-staged" }, "lint-staged": { "*.ts": [ "prettier --write", "eslint --fix" ] }, "repository": { "type": "git", "url": "https://github.com/Arda-cards/skills-mcp.git" }, "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", "agents": "^0.0.74", "zod": "^3.23.8" }, "devDependencies": { "@cloudflare/workers-types": "^4.20250620.0", "@types/node": "^22.10.0", "eslint": "^9.27.0", "eslint-plugin-unicorn": "^59.0.1", "js-yaml": "^4.1.0", "@types/js-yaml": "^4.0.9", "lint-staged": "^16.0.0", "prettier": "^3.5.3", "simple-git-hooks": "^2.12.1", "typescript": "^5.8.3", "typescript-eslint": "^8.32.1", "vitest": "^3.1.4", "wrangler": "^4.0.0" }}Note: agents package version should be verified against latest at implementation time. The js-yaml dependency is used only by build scripts but listed in devDependencies.
Task 1.2: TypeScript Configuration
Section titled “Task 1.2: TypeScript Configuration”Create tsconfig.json:
{ "compilerOptions": { "target": "ES2022", "module": "ESNext", "moduleResolution": "bundler", "outDir": "dist", "rootDir": "src", "strict": true, "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": true, "noImplicitOverride": true, "esModuleInterop": true, "skipLibCheck": true, "lib": ["ES2022"], "types": ["@cloudflare/workers-types"], "resolveJsonModule": true }, "include": ["src"]}Create tsconfig.eslint.json:
{ "extends": "./tsconfig.json", "include": ["src", "tests"]}Note: Uses bundler module resolution (Cloudflare Workers use esbuild), not NodeNext.
Task 1.3: Linting, Formatting, Testing Config
Section titled “Task 1.3: Linting, Formatting, Testing Config”Create .prettierrc — identical to hypothesis-mcp:
{ "singleQuote": true, "semi": false, "trailingComma": "all"}Create eslint.config.js — identical to hypothesis-mcp:
import tseslint from 'typescript-eslint'import unicorn from 'eslint-plugin-unicorn'
export default tseslint.config( ...tseslint.configs.strictTypeChecked, { languageOptions: { parserOptions: { project: './tsconfig.eslint.json', }, }, plugins: { unicorn, }, rules: { 'unicorn/prefer-node-protocol': 'error', 'unicorn/no-nested-ternary': 'error', 'unicorn/prefer-module': 'error', 'unicorn/throw-new-error': 'error', }, }, { ignores: ['dist/', 'eslint.config.js', 'vitest.config.ts', 'scripts/'], },)Create vitest.config.ts — identical to hypothesis-mcp:
import { defineConfig } from 'vitest/config'
export default defineConfig({ test: { include: ['tests/**/*.test.ts'], },})Task 1.4: Wrangler Configuration
Section titled “Task 1.4: Wrangler Configuration”Create wrangler.toml — as specified:
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"Durable Objects config TBD — depends on McpAgent requirements (see Open Question #2).
Task 1.5: .gitignore Update
Section titled “Task 1.5: .gitignore Update”Replace .gitignore with project-specific entries:
node_modules/dist/.wrangler/src/generated/*.tsbuildinfo.env.env.*!.env.example.dev.varsscratch/Key addition: src/generated/ is gitignored — populated by build scripts.
Task 1.6: CHANGELOG and CLQ
Section titled “Task 1.6: CHANGELOG and CLQ”Create CHANGELOG.md:
# Changelog
## [Unreleased]
### Added
- Initial MCP server with 7 tools: list_skills, get_skill, get_skill_file, list_agents, get_agent, list_docs, get_doc- Bundled indexes for list_* tools (zero runtime GitHub API calls)- URL-safe Base64 token authentication- 10-minute cache with skipCache bypass on get_* tools- Path traversal validation- CI pipeline: build on merge, weekly Saturday schedule, manual dispatchCreate .github/clq/changemap.json — identical to hypothesis-mcp:
[ { "name": "Added", "increment": "minor", "emoji": "✨" }, { "name": "Changed", "increment": "major", "emoji": "💥" }, { "name": "Deprecated", "increment": "minor", "emoji": "👎" }, { "name": "Fixed", "increment": "patch", "emoji": "🐛" }, { "name": "Removed", "increment": "major", "emoji": "🗑️" }, { "name": "Security", "increment": "patch", "emoji": "🔒" }]Task 1.7: Generated Index Placeholders
Section titled “Task 1.7: Generated Index Placeholders”Create scripts/ensure-generated.js:
A script that creates placeholder index files for local development when src/generated/ doesn’t exist.
import { mkdirSync, existsSync, writeFileSync } from 'node:fs'
const dir = 'src/generated'if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
const placeholders = { 'skills-index.json': '[]', 'agents-index.json': '[]', 'docs-index.json': '{}',}
for (const [file, content] of Object.entries(placeholders)) { const path = `${dir}/${file}` if (!existsSync(path)) { writeFileSync(path, content) console.log(`Created placeholder: ${path}`) }}Phase 2: Core Source Files
Section titled “Phase 2: Core Source Files”Task 2.1: Type Definitions
Section titled “Task 2.1: Type Definitions”Create src/types.ts:
export interface Env { REPO: string SKILLS_PATH: string AGENTS_PATH: string BRANCH: string DOCS_REPO: string DOCS_CONTENT_PATH: string DOCS_BRANCH: string}
export interface GitHubContentResponse { name: string path: string sha: string size: number content: string // Base64-encoded encoding: string // "base64" type: 'file'}
export interface SkillIndexEntry { name: string description: string | null argumentHint: string | null userInvocable: boolean files: string[]}
export interface AgentIndexEntry { name: string description: string | null model: string | null allowedTools: string[] | null}
export interface McpErrorDetails { repo?: string path?: string branch?: string githubStatus?: number}
export type ErrorCode = | 'UNAUTHORIZED' | 'FORBIDDEN' | 'NOT_FOUND' | 'UPSTREAM_ERROR' | 'INVALID_PATH'Task 2.2: Authentication Module
Section titled “Task 2.2: Authentication Module”Create src/auth.ts:
Responsibility: extract and decode Bearer token from Authorization header.
Functions:
decodeToken(authHeader: string | null): string— extracts Bearer value, decodes URL-safe Base64, returns GitHub PAT. Throws on missing/invalid.
URL-safe Base64 decoding: replace - with + and _ with /, then atob().
Task 2.3: Path Validation Module
Section titled “Task 2.3: Path Validation Module”Create src/validation.ts:
Responsibility: validate path segments against traversal attacks.
Functions:
validatePathSegment(segment: string): void— rejects.., leading/, and\.validatePathSegments(segments: string[]): void— validates each segment in an array.
Task 2.4: GitHub Client Module
Section titled “Task 2.4: GitHub Client Module”Create src/github.ts:
Responsibility: fetch files from GitHub Contents API.
Functions:
fetchGitHubContent(token: string, repo: string, path: string, branch: string): Promise<GitHubContentResponse>— makes authenticated GET request, handles errors, returns parsed JSON.fetchGitHubContentOrNull(...)— same but returnsnullon 404 (forget_docfallback).decodeGitHubContent(response: GitHubContentResponse): string— decodes Base64 content to UTF-8 string.mapGitHubError(status: number, repo: string, path: string, branch: string): McpError— maps GitHub HTTP status to MCP error with details.
Task 2.5: Cache Module
Section titled “Task 2.5: Cache Module”Create src/cache.ts:
Responsibility: wrap Cloudflare Cache API for tool responses.
Functions:
getCached(cacheKey: string): Promise<Response | null>— checks cache, returns hit or null.setCached(cacheKey: string, response: Response, ttlSeconds: number): Promise<void>— stores in cache.buildCacheKey(tool: string, repo: string, branch: string, path: string): string— constructs cache key URL.
The cache key is a synthetic URL (e.g., https://cache.internal/{tool}/{repo}/{branch}/{path}) since Cloudflare Cache API requires URL keys.
TTL: 10 minutes (600 seconds).
Task 2.6: Worker Entry Point
Section titled “Task 2.6: Worker Entry Point”Create src/index.ts:
This is the main file. It:
- Imports bundled indexes from
./generated/ - Defines the
SkillsMcpAgentclass extendingMcpAgent - Registers all 7 tools in
init() - Exports the default fetch handler
Tool registrations:
| Tool | Parameters (zod) | Handler |
|---|---|---|
list_skills | none | Return { skills: skillsIndex } |
get_skill | { name: z.string(), skipCache: z.boolean().optional() } | Validate name, fetch from GitHub, cache |
get_skill_file | { skill: z.string(), file: z.string(), skipCache: z.boolean().optional() } | Validate skill+file, fetch, return Base64 |
list_agents | none | Return { agents: agentsIndex } |
get_agent | { name: z.string(), skipCache: z.boolean().optional() } | Validate name, fetch from GitHub, cache |
list_docs | none | Return { index: docsIndex } |
get_doc | { path: z.array(z.string()), skipCache: z.boolean().optional() } | Validate segments, try index.md then *.md, cache |
Each get_* handler follows this pattern:
- Extract auth token from connection context
- Validate path parameters
- Check cache (unless
skipCache) - Fetch from GitHub
- Cache the response
- Return MCP tool result
Phase 3: Build Scripts
Section titled “Phase 3: Build Scripts”Task 3.1: Skills & Agents Index Script
Section titled “Task 3.1: Skills & Agents Index Script”Create scripts/build-skills-agents-index.js:
Full implementation provided in specification.md — Build Process. Key points:
- Uses
js-yamlload()for frontmatter parsing - Extracts
name,description,argument-hint,user-invocablefor skills - Builds
filesmanifest via recursive directory scan (excludingSKILL.md) - Extracts
name,description,model,allowedToolsfor agents - Graceful fallback: missing frontmatter →
{ name: filename, description: null } - Writes to
src/generated/skills-index.jsonandsrc/generated/agents-index.json
Task 3.2: Docs Index Script
Section titled “Task 3.2: Docs Index Script”Create scripts/build-docs-index.js:
Full implementation provided in specification.md — Build Process. Key points:
- Uses
js-yamlload()for frontmatter parsing - Processes both
.mdand.mdxfiles - Builds hierarchical JSON tree
- Writes to
src/generated/docs-index.json
Phase 4: Tests
Section titled “Phase 4: Tests”Task 4.1: Auth Tests
Section titled “Task 4.1: Auth Tests”Create tests/auth.test.ts:
Tests for decodeToken():
- AUTH-T01: Decodes valid URL-safe Base64 token
- AUTH-T02: Handles
-_characters correctly - AUTH-T03: Throws on missing Authorization header
- AUTH-T04: Throws on non-Bearer prefix
- AUTH-T05: Throws on invalid Base64
Task 4.2: Validation Tests
Section titled “Task 4.2: Validation Tests”Create tests/validation.test.ts:
Tests for validatePathSegment():
- SEC-T01 through SEC-T05:
.., absolute paths, backslashes, doc path segments
Task 4.3: GitHub Client Tests
Section titled “Task 4.3: GitHub Client Tests”Create tests/github.test.ts:
Mock fetch globally. Tests for:
- SKILL-T03/T04: Correct URL construction, content decoding
- AUTH-T06/T07/T08: Error propagation for 401/403/404
- ERR-T01/T02: Structured error responses with details
- DOC-T03/T04/T05: index.md fallback logic
Task 4.4: Tool Handler Tests
Section titled “Task 4.4: Tool Handler Tests”Create tests/tools.test.ts:
Tests for the tool handler logic (extracted as pure functions for testability):
- SKILL-T01/T02: list_skills returns bundled index, no fetch
- AGENT-T01/T02: list_agents returns bundled index, no fetch
- DOC-T01/T02: list_docs returns bundled index, no fetch
- SKILL-T07/T08: get_skill_file returns Base64 with encoding and size
Task 4.5: Cache Tests
Section titled “Task 4.5: Cache Tests”Create tests/cache.test.ts:
Mock Cache API. Tests for:
- SKILL-T05/T06: Cache hit, cache miss, skipCache bypass
- Cache key construction
Phase 5: CI/CD
Section titled “Phase 5: CI/CD”Task 5.1: Build and Deploy Workflow
Section titled “Task 5.1: Build and Deploy Workflow”Create .github/workflows/publish.yml:
Combines patterns from hypothesis-mcp (changelog validation, build-and-test) with the index generation and Cloudflare deploy steps from specification.md — CI Pipeline.
Three triggers: push to main, Saturday cron, workflow_dispatch.
Jobs:
validate-changelog— clq validation (same as hypothesis-mcp)build-and-test— typecheck, lint, test (same as hypothesis-mcp pattern)generate-and-deploy— clone source repos, generate indexes, wrangler deploy (only on main push, schedule, or dispatch)
Task 5.2: PR Upkeep Workflow
Section titled “Task 5.2: PR Upkeep Workflow”Create .github/workflows/pull_request_upkeep.yml — identical to hypothesis-mcp:
---name: "Pull Request Upkeep"
on: pull_request: types: [opened]
permissions: {}
jobs: upkeep: runs-on: ubuntu-latest steps: - uses: Arda-cards/pull-request-setup-action@v1 with: iteration_field_name: "Iteration" project_title: "${{ vars.ARDA_CLOUD_PROJECT_NAME }}" pull_request_number: "${{ github.event.pull_request.number }}" token: "${{ secrets.ARDA_GH_ACTION_PROJECT_WRITER }}"Phase 6: Post-Deploy
Section titled “Phase 6: Post-Deploy”Task 6.1: README.md (Installation and Usage Guide)
Section titled “Task 6.1: README.md (Installation and Usage Guide)”Create README.md:
The repository README serves as the installation and usage guide for the MCP server. It must cover:
Sections:
-
Overview — one-paragraph description of what the server does and who it is for.
-
Prerequisites — what the reader needs before starting:
- A Cloudflare account (free tier is sufficient)
- A GitHub Fine-grained PAT with
contents: readonArda-cards/agentic-workspaceandArda-cards/documentation - Node.js 22+
wranglerCLI (installed as devDependency)
-
Setup — step-by-step instructions:
- Clone the repository
npm ci- Create
.dev.varswith a test PAT for local development - Run
npm run generate-indexesto create placeholder indexes for local dev (or run the full generation scripts against local clones of source repos) npm run devto start the local dev server
-
Configuration — table of
wrangler.tomlenvironment variables with descriptions (reference the specification). -
Authentication — full walkthrough matching the specification’s Usage section:
- Step 1: Create a GitHub PAT — link to fine-grained token page, exact permissions (
Contents: Read-onlyonArda-cards/agentic-workspaceandArda-cards/documentation), rotation reminder - Step 2: Encode as URL-safe Base64 — macOS/Linux command (
echo -n | base64 | tr '+/' '-_' | tr -d '='), verification round-trip command - Step 3: Configure MCP clients — both claude.ai and Claude Code setup
- Step 1: Create a GitHub PAT — link to fine-grained token page, exact permissions (
-
Available Tools — brief table of all 7 tools with parameters and descriptions. Link to specification for full details.
-
Connecting to claude.ai — step-by-step:
- Add MCP connector with the Worker URL and encoded token
- Add the recommended Project system prompt (from specification)
- Add the memory nudge (from specification)
-
Connecting to Claude Code — the
mcp.jsonconfiguration block. -
Development — commands reference:
npm run dev— local development servernpm run test— run unit testsnpm run lint— lint checknpm run typecheck— TypeScript checknpm run generate-indexes— regenerate bundled indexes locallynpm run deploy— deploy to Cloudflare (requiresCLOUDFLARE_API_TOKEN)
-
CI/CD — brief explanation of the three deployment triggers and what happens on each.
-
License — MIT
Task 6.2: Branch Protection Rules
Section titled “Task 6.2: Branch Protection Rules”After the initial deployment reaches main, configure branch protection matching hypothesis-mcp:
- Require PR reviews before merging
- Require status checks to pass (validate-changelog, build-and-test)
- No direct pushes to main
Task 6.3: Verify Deployment
Section titled “Task 6.3: Verify Deployment”Manual verification checklist:
- Connect to the deployed Worker URL with an MCP client
- Call
list_skills— verify bundled index returns - Call
get_skill("kotlin-coding")— verify live GitHub fetch - Call
get_skill_file("agentation-feedback", "references/schema-v2.md")— verify Base64 return - Call
list_agents— verify bundled index with model/allowedTools - Call
get_agent("backend-engineer")— verify live fetch - Call
list_docs— verify hierarchical index - Call
get_doc(["architecture"])— verify index.md fallback - Test path traversal rejection:
get_skill_file("..", "../settings.json") - Test missing token: call without Authorization header
- Test skipCache: call
get_skilltwice, second withskipCache: true
Worktree Strategy
Section titled “Worktree Strategy”Single directory — no worktrees needed. This is a greenfield project in its own repository with a single implementing agent.
File Inventory
Section titled “File Inventory”| File | Phase | Task |
|---|---|---|
package.json | 1 | 1.1 |
tsconfig.json | 1 | 1.2 |
tsconfig.eslint.json | 1 | 1.2 |
.prettierrc | 1 | 1.3 |
eslint.config.js | 1 | 1.3 |
vitest.config.ts | 1 | 1.3 |
wrangler.toml | 1 | 1.4 |
.gitignore | 1 | 1.5 |
CHANGELOG.md | 1 | 1.6 |
.github/clq/changemap.json | 1 | 1.6 |
scripts/ensure-generated.js | 1 | 1.7 |
src/types.ts | 2 | 2.1 |
src/auth.ts | 2 | 2.2 |
src/validation.ts | 2 | 2.3 |
src/github.ts | 2 | 2.4 |
src/cache.ts | 2 | 2.5 |
src/index.ts | 2 | 2.6 |
scripts/build-skills-agents-index.js | 3 | 3.1 |
scripts/build-docs-index.js | 3 | 3.2 |
tests/auth.test.ts | 4 | 4.1 |
tests/validation.test.ts | 4 | 4.2 |
tests/github.test.ts | 4 | 4.3 |
tests/tools.test.ts | 4 | 4.4 |
tests/cache.test.ts | 4 | 4.5 |
.github/workflows/publish.yml | 5 | 5.1 |
.github/workflows/pull_request_upkeep.yml | 5 | 5.2 |
README.md | 6 | 6.1 |
Copyright: © Arda Systems 2025-2026, All rights reserved