From 9936b62e3501caafaa380c77414e591a53b220ce Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Sun, 5 Jul 2026 20:34:48 +0530 Subject: [PATCH 1/4] chore: commit wip changes on feature/wip-20260705 --- .github/workflows/ci.yml | 163 ++++++++++++++++++++++++-- .github/workflows/release.yml | 31 +++++ CODEOWNERS | 13 ++ Makefile | 114 ++++++++++++++++++ README.md | 22 ++++ lefthook.yml | 23 ++++ scripts/check-ecosystem-boundaries.sh | 26 ++++ 7 files changed, 383 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 CODEOWNERS create mode 100644 Makefile create mode 100644 lefthook.yml create mode 100755 scripts/check-ecosystem-boundaries.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index adc6a2b..a7fc688 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,26 +1,171 @@ +# CI pipeline for hawk-mcpkit — a zero-hawk-eco-dependency foundation +# library. No workspace clones needed: this repo depends on nothing in the +# hawk ecosystem, only the Go standard library and mark3labs/mcp-go. + name: CI on: push: - branches: - - main + branches: [main] pull_request: + branches: [main] permissions: contents: read +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + GO_VERSION: "1.26.4" + jobs: + format: + name: format + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version: ${{ env.GO_VERSION }} + cache: true + - name: Boundary guard + run: bash ./scripts/check-ecosystem-boundaries.sh + - name: gofumpt + run: | + go install mvdan.cc/gofumpt@v0.10.0 + out=$(gofumpt -l .) + if [ -n "$out" ]; then + echo "::error::gofumpt would reformat:" + echo "$out" + exit 1 + fi + - name: goimports + run: | + go install golang.org/x/tools/cmd/goimports@v0.30.0 + out=$(goimports -l .) + if [ -n "$out" ]; then + echo "::error::goimports would reformat:" + echo "$out" + exit 1 + fi + + module: + name: module hygiene + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version: ${{ env.GO_VERSION }} + cache: true + - name: go mod tidy + run: | + go mod tidy + if ! git diff --quiet -- go.mod go.sum; then + echo "::error::go.mod / go.sum out of date — run 'go mod tidy'" + git diff -- go.mod go.sum + exit 1 + fi + - name: go mod verify + run: go mod verify + + vet: + name: vet + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version: ${{ env.GO_VERSION }} + cache: true + - name: go vet + run: go vet ./... + + lint: + name: lint + runs-on: ubuntu-latest + needs: [format, vet] + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version: ${{ env.GO_VERSION }} + cache: true + - name: Boundary guard + run: bash ./scripts/check-ecosystem-boundaries.sh + - name: Run golangci-lint + run: | + go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.0 + golangci-lint run --timeout=5m + test: + name: test (race + coverage) runs-on: ubuntu-latest + needs: [format, vet] steps: - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version: ${{ env.GO_VERSION }} + cache: true + - name: Boundary guard + run: bash ./scripts/check-ecosystem-boundaries.sh + - name: Test with race detector + run: go test ./... -race -count=1 -shuffle=on -coverprofile=coverage.out -covermode=atomic -timeout=120s + - name: Upload coverage + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: coverage-${{ github.job }} + path: coverage.out + retention-days: 30 - - name: Setup Go - uses: actions/setup-go@v5 + security: + name: security + runs-on: ubuntu-latest + needs: [format, vet] + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version-file: go.mod + go-version: ${{ env.GO_VERSION }} cache: true + - name: govulncheck + run: | + go install golang.org/x/vuln/cmd/govulncheck@v1.1.4 + govulncheck ./... + + secrets: + name: secrets + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: trufflesecurity/trufflehog@0fa069c12f0c7baf431041cd1e564a9c5058846c # main 2026-05-18 + with: + extra_args: --only-verified - - name: Run tests - run: go test ./... + build: + name: build (${{ matrix.goos }}/${{ matrix.goarch }}) + runs-on: ubuntu-latest + needs: [format, lint, test, security] + strategy: + fail-fast: false + matrix: + goos: [linux, darwin, windows] + goarch: [amd64, arm64] + exclude: + - goos: windows + goarch: arm64 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version: ${{ env.GO_VERSION }} + cache: true + - name: Build (library — cross-compile all packages) + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + CGO_ENABLED: "0" + run: go build -trimpath -v ./... diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..2b0bf83 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,31 @@ +# Release workflow for hawk-mcpkit (Go library — no binaries). +# Triggered when a v* tag is pushed; publishes a GitHub Release with +# auto-generated notes. Consumers depend on the tag via +# `go get github.com/GrayCodeAI/hawk-mcpkit@vX.Y.Z`. + +name: release + +on: + push: + tags: ["v*"] + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Create GitHub Release + uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2 + with: + generate_release_notes: true + draft: false + prerelease: auto + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..88c4436 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,13 @@ +# CODEOWNERS for hawk-mcpkit (shared MCP server scaffolding) +* @GrayCodeAI/maintainers + +# Public API surface +/mcpkit.go @GrayCodeAI/llm-team +/VERSION @GrayCodeAI/maintainers + +# CI / release +/.github/ @GrayCodeAI/devops-team +/Makefile @GrayCodeAI/devops-team + +# Documentation +*.md @GrayCodeAI/docs-team diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9d02005 --- /dev/null +++ b/Makefile @@ -0,0 +1,114 @@ +# Canonical hawk-eco Makefile for Go LIBRARY repos. +# Source of truth: https://github.com/GrayCodeAI/hawk/blob/main/.shared-templates/Makefile.library.tmpl +# hawk-mcpkit is a foundation library consumed by engines (sight, inspect); +# no standalone binary, no release beyond tagging. + +# --------------------------------------------------------------------------- +# Project metadata +# --------------------------------------------------------------------------- +NAME := hawk-mcpkit + +# --------------------------------------------------------------------------- +# Versioning — sourced from VERSION file; falls back to git describe. +# --------------------------------------------------------------------------- +VERSION ?= $(shell cat VERSION 2>/dev/null | head -n1 | tr -d '[:space:]' || git describe --tags --always --dirty 2>/dev/null || echo "dev") +COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "none") +DATE := $(shell date -u '+%Y-%m-%dT%H:%M:%SZ') + +# --------------------------------------------------------------------------- +# Tooling — pinned, install if missing. +# --------------------------------------------------------------------------- +GOBIN_DIR := $(shell go env GOPATH)/bin +GOLANGCI := $(GOBIN_DIR)/golangci-lint +GOFUMPT := $(GOBIN_DIR)/gofumpt +GOIMPORTS := $(GOBIN_DIR)/goimports +GOVULNCHECK := $(GOBIN_DIR)/govulncheck + +# --------------------------------------------------------------------------- +# Phony declarations (alphabetical). +# --------------------------------------------------------------------------- +.PHONY: all boundaries build ci clean cover fmt help hooks lint lint-fix \ + security test test-race tidy version vet + +boundaries: ## Enforce foundation-repo import boundaries (zero GrayCodeAI/* deps). + bash ./scripts/check-ecosystem-boundaries.sh + +# --------------------------------------------------------------------------- +# Default target. +# --------------------------------------------------------------------------- +all: lint test build ## Default — lint, test, build. + +# --------------------------------------------------------------------------- +# Build. +# --------------------------------------------------------------------------- +build: ## Build the library package. + go build ./... + +# --------------------------------------------------------------------------- +# Tests. +# --------------------------------------------------------------------------- +test: ## Run unit tests. + go test ./... -count=1 -timeout=60s + +test-race: ## Run unit tests with the race detector. + go test ./... -race -count=1 -timeout=120s + +cover: ## Generate a coverage report (coverage.out + coverage.html). + go test ./... -race -coverprofile=coverage.out -covermode=atomic -timeout=120s + @go tool cover -func=coverage.out | grep "^total:" + @go tool cover -html=coverage.out -o coverage.html + @echo "Coverage report: coverage.html" + +# --------------------------------------------------------------------------- +# Quality gates. +# --------------------------------------------------------------------------- +fmt: ## Format source files (gofumpt + goimports). + @command -v $(GOFUMPT) >/dev/null 2>&1 || (echo "install: go install mvdan.cc/gofumpt@latest" && exit 1) + @command -v $(GOIMPORTS) >/dev/null 2>&1 || (echo "install: go install golang.org/x/tools/cmd/goimports@latest" && exit 1) + $(GOFUMPT) -w . + $(GOIMPORTS) -w . + +vet: ## Run go vet. + go vet ./... + +lint: ## Run golangci-lint. + @command -v $(GOLANGCI) >/dev/null 2>&1 || (echo "install: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest" && exit 1) + $(GOLANGCI) run ./... --timeout=5m + +lint-fix: ## Run golangci-lint with --fix. + @command -v $(GOLANGCI) >/dev/null 2>&1 || (echo "install: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest" && exit 1) + $(GOLANGCI) run ./... --fix --timeout=5m + +security: ## Run govulncheck. + @command -v $(GOVULNCHECK) >/dev/null 2>&1 || (echo "install: go install golang.org/x/vuln/cmd/govulncheck@latest" && exit 1) + $(GOVULNCHECK) ./... + +tidy: ## Tidy go.mod / go.sum. + go mod tidy + go mod verify + +# --------------------------------------------------------------------------- +# Composite gate used by CI and pre-push. +# --------------------------------------------------------------------------- +ci: tidy fmt vet lint boundaries test-race security ## Run everything CI runs. + @echo "All CI checks passed." + +# --------------------------------------------------------------------------- +# Misc. +# --------------------------------------------------------------------------- +version: ## Print the version that will be embedded. + @echo "Version: $(VERSION)" + @echo "Commit: $(COMMIT)" + @echo "Date: $(DATE)" + +clean: ## Remove build artefacts. + rm -rf coverage.out coverage.html + go clean -testcache + +help: ## Show this help. + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}' + +hooks: ## Install git hooks via lefthook (boundary guard, tests, co-author strip). + @command -v lefthook >/dev/null 2>&1 || (echo "install: go install github.com/evilmartians/lefthook@latest" && exit 1) + git config --unset core.hooksPath 2>/dev/null || true + lefthook install diff --git a/README.md b/README.md index e08e65f..c8c0401 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,28 @@ func main() { | `StrArg(req, key)` | Extract a string tool argument (`""` if absent/mistyped) | | `JSONResult(v)` | Marshal `v` as indented JSON into a text tool result | +## Ecosystem Boundaries + +`hawk-mcpkit` is a **foundation repo** in the [hawk ecosystem](https://github.com/GrayCodeAI/hawk/blob/main/docs/architecture/hawk-ecosystem-summary.md) — +it sits below every engine and below `hawk` itself, alongside +`hawk-core-contracts`. + +Rules that keep it there: + +- **Zero hawk-eco dependencies.** This repo must never import `hawk`, any + engine (`eyrie`, `yaad`, `tok`, `trace`, `sight`, `inspect`), any SDK, or + `hawk-core-contracts`. Its only non-stdlib dependency is upstream + `mark3labs/mcp-go`. `make boundaries` (also run in CI) enforces this with + `scripts/check-ecosystem-boundaries.sh`. +- **Implementation-free of product logic.** This repo holds MCP server + scaffolding only — no hawk orchestration, no engine-specific behavior, no + provider logic. +- **Consumers, not dependents.** Engines that serve MCP (`sight`, `inspect`) + import `hawk-mcpkit`; it never imports them back. + +If you need a hawk-ecosystem type here, that's a sign it belongs in the +consuming engine instead, not in this repo. + ## License MIT — see [LICENSE](LICENSE). diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 0000000..f67aee3 --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,23 @@ +# Canonical lefthook config for hawk-eco Go repos. +# Source of truth: https://github.com/GrayCodeAI/hawk/blob/main/.shared-templates/lefthook.yml.tmpl +# Install: lefthook install +# Skip once: LEFTHOOK=0 git commit -m "..." + +prepare-commit-msg: + commands: + strip-co-authored-by: + run: | + sed '/^[Cc]o-[Aa]uthored-[Bb]y:/d' "{1}" > "{1}.tmp" && mv "{1}.tmp" "{1}" + +commit-msg: + commands: + strip-co-authored-by: + run: | + sed '/^[Cc]o-[Aa]uthored-[Bb]y:/d' "{1}" > "{1}.tmp" && mv "{1}.tmp" "{1}" + +pre-push: + commands: + boundaries: + run: bash ./scripts/check-ecosystem-boundaries.sh + test: + run: go test ./... -race -count=1 diff --git a/scripts/check-ecosystem-boundaries.sh b/scripts/check-ecosystem-boundaries.sh new file mode 100755 index 0000000..c9c591e --- /dev/null +++ b/scripts/check-ecosystem-boundaries.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT_DIR" + +# hawk-mcpkit is a foundation library: it sits below every engine and below +# hawk itself. It must depend on nothing in the hawk ecosystem — only the +# Go standard library and the upstream mark3labs/mcp-go package. +FORBIDDEN='github\.com/GrayCodeAI/' + +if command -v rg >/dev/null 2>&1; then + violations="$(rg -n "$FORBIDDEN" --glob '*.go' . || true)" +else + violations="$(grep -rn --include='*.go' -E "$FORBIDDEN" . || true)" +fi + +if [[ -n "${violations}" ]]; then + echo "forbidden hawk-eco imports found in hawk-mcpkit:" + echo "${violations}" + echo + echo "hawk-mcpkit is a foundation repo — it must not depend on hawk, engines, or any other GrayCodeAI/* package" + exit 1 +fi + +echo "ecosystem boundary guard passed" From badda1371d4b6b70fa8222b8c3ca1ebb80acbe5e Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Mon, 6 Jul 2026 08:19:17 +0530 Subject: [PATCH 2/4] chore: add missing documentation and standardize across ecosystem - Create AGENTS.md with architecture, design decisions, testing patterns - Create CODE_OF_CONDUCT.md (Contributor Covenant 2.1) - Create SECURITY.md with comprehensive security policy - Update README.md with Quick Reference, architecture diagram, API reference Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- AGENTS.md | 148 +++++++++++++++++++++++++++++++++++++++++++++ CODE_OF_CONDUCT.md | 60 ++++++++++++++++++ README.md | 79 +++++++++++++++++++----- SECURITY.md | 71 ++++++++++++++++++++++ 4 files changed, 343 insertions(+), 15 deletions(-) create mode 100644 AGENTS.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 SECURITY.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..dff7312 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,148 @@ +# AGENTS.md — hawk-mcpkit + +This file describes the hawk-mcpkit project for AI agents working in this +codebase. The TUI `/memory` command references this file. + +--- + +## Project Overview + +hawk-mcpkit is a shared MCP server scaffolding library for the hawk ecosystem. +It wraps [`mark3labs/mcp-go`](https://github.com/mark3labs/mcp-go) with the +construction, transports, and handler helpers that every hawk-ecosystem engine +(`inspect`, `sight`, ...) would otherwise duplicate. + +Consumers declare their tools and handlers; mcpkit does the rest. + +**Tagline:** Shared MCP server scaffolding for the hawk ecosystem. + +## Ecosystem + +hawk-mcpkit is a **foundation repo** in the hawk-eco mono-ecosystem: + +| Component | Purpose | +|-----------|---------| +| **hawk-mcpkit** | Shared MCP server scaffolding (this repo) | +| **hawk-core-contracts** | Shared cross-repo contracts (types, tools, events, policy, review, verify, sessions) | +| **eyrie** | LLM provider runtime — routing, streaming, retries, caching | +| **yaad** | Graph-based persistent memory for coding agents | +| **tok** | Tokenizer, compression, secrets scanning, rate limiting | +| **sight** | Diff-based code review and static analysis | +| **inspect** | Security audit library (CVE, API security, CI output) | +| **trace** | Session capture and replay CLI | +| **hawk** | AI coding agent (this repo) | + +Engines that serve MCP (`sight`, `inspect`) import `hawk-mcpkit`; it never +imports them back. + +## Architecture + +``` +hawk-mcpkit/ +├── mcpkit.go # Server wrapper, tool registration, transport helpers +├── mcpkit_test.go # Tests for StrArg, JSONResult, Server construction +├── scripts/ +│ └── check-ecosystem-boundaries.sh # CI guard: zero hawk-eco deps +├── .github/ +│ └── workflows/ +│ ├── ci.yml # CI: format, module hygiene, vet, lint, test, security +│ └── release.yml # GitHub Release on v* tags +├── Makefile # Local dev tasks (lint, test, boundaries, ci) +├── lefthook.yml # Pre-commit hooks (boundary guard, co-author strip) +├── AGENTS.md # This file +├── README.md # Public API docs, usage examples +├── CHANGELOG.md # Keep a Changelog format +├── CODEOWNERS # Code ownership +├── LICENSE # MIT +├── VERSION # Source of truth for versioning +└── go.mod / go.sum # Module files +``` + +## Key Design Decisions + +- **Zero hawk-eco dependencies:** mcpkit imports nothing from `hawk`, any + engine, any SDK, or `hawk-core-contracts`. Its only non-stdlib dependency + is upstream `mark3labs/mcp-go`. CI enforces this with + `make boundaries`. +- **Library only, no binary:** mcpkit is a Go library consumed by engines. It + does not ship a CLI or standalone binary. +- **Minimal public API:** `New()`, `AddTool()`, `MCP()`, `ServeStdio()`, + `ServeHTTP()`, `StrArg()`, `JSONResult()`. Everything else is unexported. +- **Transport agnostic:** The `Server` struct wraps the underlying + `mcpserver.MCPServer`. Consumers choose the transport (stdio or HTTP) via + `ServeStdio()` or `ServeHTTP()`. +- **No ecosystem product logic:** mcpkit holds MCP server scaffolding only. + No agent orchestration, no engine-specific behavior, no provider logic. + +## Development Guidelines + +### Build & Test + +```bash +make test # Run unit tests (no race detector) +make test-race # Run unit tests with race detector +make lint # Run golangci-lint +make boundaries # Enforce zero hawk-eco imports +make ci # Full CI suite (tidy, fmt, vet, boundaries, lint, test-race, security) +``` + +### Go Conventions + +- Standard Go project layout: tests live alongside source files (`foo.go` → `foo_test.go`) +- Use table-driven tests where practical +- Errors are values — wrap with `fmt.Errorf("context: %w", err)` +- No global mutable state; prefer dependency injection + +### Commit Conventions + +Use [Conventional Commits](https://www.conventionalcommits.org/): +``` +feat: add HTTP transport support to mcpkit +fix: handle missing tool arguments gracefully +refactor: simplify Server struct API +``` + +## File Organization Notes + +| File | Purpose | +|------|---------| +| `mcpkit.go` | Server wrapper, tool registration, transport helpers, StrArg, JSONResult | +| `mcpkit_test.go` | Unit tests for all exported API surface | +| `scripts/check-ecosystem-boundaries.sh` | CI guard against hawk-eco imports | +| `.github/workflows/ci.yml` | CI pipeline (format, module hygiene, vet, lint, test, security, build) | +| `.github/workflows/release.yml` | GitHub Release on `v*` tags | +| `Makefile` | Local dev tasks | +| `lefthook.yml` | Pre-commit hooks (boundary guard, co-author strip) | + +## Testing Patterns + +- **Table-driven tests** with `t.Run(name, func(t *testing.T){...})` for all multi-case tests +- **`t.Parallel()`** on all tests that don't share mutable state +- **No mocks framework** — use concrete types and test doubles +- All tests are in `mcpkit_test.go` (single test file, library is small) + +## How to Add New Transports + +1. Add a method on `Server` that accepts transport-specific options +2. The method should create the appropriate server from `mcpserver` and call + `Listen` or `Start` with the consumer-provided address or streams +3. Add tests in `mcpkit_test.go` +4. Update the README API table + +## How to Add New Handler Helpers + +1. Add a helper function that wraps common argument extraction or result + marshalling patterns used across engines +2. The helper should return `(*mcplib.CallToolResult, error)` to be compatible + with `mcpserver.ToolHandlerFunc` +3. Add tests in `mcpkit_test.go` + +## Common Pitfalls + +- Do not import `hawk-core-contracts` or any other hawk-eco package in mcpkit +- Do not put engine-specific logic in mcpkit (e.g., agent loop, provider + routing, permission checking) +- The `Server` struct's unexported `mcp` field must not be accessed directly; + use `MCP()` for the escape hatch +- `StrArg` returns `""` for missing/non-string arguments; handle this in + your handler by returning an error diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..2c4501e --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,60 @@ +# Code of Conduct + +## Our pledge + +We — the maintainers and contributors of the hawk-mcpkit project — pledge to +make participation in our community a harassment-free experience for everyone, +regardless of age, body size, visible or invisible disability, ethnicity, sex +characteristics, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, race, +religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our standards + +Examples of behaviour that contributes to a positive environment: + +- Demonstrating empathy and kindness toward other people. +- Being respectful of differing opinions, viewpoints, and experiences. +- Giving and gracefully accepting constructive feedback. +- Accepting responsibility, apologising to those affected by mistakes, and + learning from the experience. +- Focusing on what is best not just for us as individuals, but for the + overall community. + +Examples of unacceptable behaviour: + +- The use of sexualised language or imagery, and sexual attention or advances. +- Trolling, insulting or derogatory comments, and personal or political + attacks. +- Public or private harassment. +- Publishing others' private information, such as a physical or email + address, without their explicit permission. +- Other conduct which could reasonably be considered inappropriate in a + professional setting. + +## Enforcement + +Community leaders are responsible for clarifying and enforcing our standards +of acceptable behaviour, and will take appropriate and fair corrective +action in response to any behaviour they deem inappropriate, threatening, +offensive, or harmful. + +Instances of abusive, harassing, or otherwise unacceptable behaviour may be +reported to the maintainers via the contact in `SECURITY.md` or by opening a +confidential GitHub Security Advisory at +. All +complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of +the reporter of any incident. + +## Attribution + +This Code of Conduct is adapted from the +[Contributor Covenant, version 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html). + +For answers to common questions about this code of conduct, see the +Contributor Covenant FAQ at . diff --git a/README.md b/README.md index c8c0401..7c63ce2 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,11 @@ Shared MCP server scaffolding for the hawk ecosystem. `mcpkit` wraps [`mark3labs/mcp-go`](https://github.com/mark3labs/mcp-go) with the construction, transports, and handler helpers that every hawk-ecosystem -library (inspect, sight, ...) would otherwise duplicate. Repos declare their +engine (`inspect`, `sight`, ...) would otherwise duplicate. Repos declare their tools and handlers; mcpkit does the rest. +**Tagline:** Shared MCP server scaffolding for the hawk ecosystem. + ## Install ```sh @@ -19,6 +21,8 @@ go get github.com/GrayCodeAI/hawk-mcpkit package main import ( + "context" + mcpkit "github.com/GrayCodeAI/hawk-mcpkit" mcplib "github.com/mark3labs/mcp-go/mcp" ) @@ -47,25 +51,70 @@ func main() { } ``` -## API +## Quick Reference + +| Task | Code | +|------|------| +| Create server | `s := mcpkit.New("name", "0.1.0")` | +| Add a tool | `s.AddTool(tool, handler)` | +| Serve stdio | `s.ServeStdio()` | +| Serve HTTP | `s.ServeHTTP(":8080")` | +| Extract string arg | `mcpkit.StrArg(req, "key")` | +| Return JSON result | `mcpkit.JSONResult(map[string]any{...})` | + +## Architecture + +``` +hawk-mcpkit Server +├── wraps mark3labs/mcp-go MCPServer +├── AddTool() registers tools + handlers +├── ServeStdio() → stdin/stdout transport +├── ServeHTTP(addr) → streamable HTTP at /mcp +├── StrArg() → extract string arguments +└── JSONResult() → marshal values as JSON text results +``` + +## API Reference + +### Server | Symbol | Purpose | -| --- | --- | -| `New(name, version)` | Create a `*Server` with tool capabilities enabled | -| `(*Server).AddTool(tool, handler)` | Register a tool and its handler | -| `(*Server).ServeStdio()` | Serve MCP over stdin/stdout | -| `(*Server).ServeHTTP(addr)` | Serve MCP over streamable HTTP at `/mcp` | -| `(*Server).MCP()` | Escape hatch to the underlying `*mcpserver.MCPServer` | -| `StrArg(req, key)` | Extract a string tool argument (`""` if absent/mistyped) | -| `JSONResult(v)` | Marshal `v` as indented JSON into a text tool result | +|--------|---------| +| `New(name, version)` | Create a `*Server` with tool capabilities enabled. Returns `*Server`. | +| `(*Server).AddTool(tool, handler)` | Register a tool and its handler. `handler` is `func(context.Context, mcplib.CallToolRequest) (*mcplib.CallToolResult, error)`. | +| `(*Server).ServeStdio()` | Serve MCP over stdin/stdout. Blocks until stream closes. Returns `error`. | +| `(*Server).ServeHTTP(addr)` | Serve MCP over streamable HTTP at `/mcp`. Blocks until server stops. Returns `error`. | +| `(*Server).MCP()` | Escape hatch to the underlying `*mcpserver.MCPServer`. Use only for capabilities mcpkit does not wrap. | -## Ecosystem Boundaries +### Handler Helpers + +| Symbol | Purpose | +|--------|---------| +| `StrArg(req, key)` | Extract a string argument from a tool call request. Returns `""` when absent or not a string. | +| `JSONResult(v)` | Marshal `v` as indented JSON and return it as a text tool result. Returns `(*mcplib.CallToolResult, error)`. Error only when marshalling fails. | + +## Ecosystem -`hawk-mcpkit` is a **foundation repo** in the [hawk ecosystem](https://github.com/GrayCodeAI/hawk/blob/main/docs/architecture/hawk-ecosystem-summary.md) — -it sits below every engine and below `hawk` itself, alongside -`hawk-core-contracts`. +hawk-mcpkit is a **foundation repo** in the hawk-eco mono-ecosystem: + +| Component | Purpose | +|-----------|---------| +| **hawk-mcpkit** | Shared MCP server scaffolding (this repo) | +| **hawk-core-contracts** | Shared cross-repo contracts (types, tools, events, policy, review, verify, sessions) | +| **eyrie** | LLM provider runtime — routing, streaming, retries, caching | +| **yaad** | Graph-based persistent memory for coding agents | +| **tok** | Tokenizer, compression, secrets scanning, rate limiting | +| **sight** | Diff-based code review and static analysis | +| **inspect** | Security audit library (CVE, API security, CI output) | +| **trace** | Session capture and replay CLI | +| **hawk** | AI coding agent (this repo) | + +Engines that serve MCP (`sight`, `inspect`) import `hawk-mcpkit`; it never +imports them back. + +## Ecosystem Boundaries -Rules that keep it there: +Rules that keep this repo at the foundation layer: - **Zero hawk-eco dependencies.** This repo must never import `hawk`, any engine (`eyrie`, `yaad`, `tok`, `trace`, `sight`, `inspect`), any SDK, or diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..5744c34 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,71 @@ +# Security Policy — hawk-mcpkit + +## Supported versions + +We support the latest minor version on each `0.x` line, and the latest two +minor versions once `1.x` ships. Older versions receive critical-severity +fixes only on a best-effort basis. + +The current canonical version is the contents of the [`VERSION`](./VERSION) +file at the repo root. See [`VERSIONING.md`](https://github.com/GrayCodeAI/hawk/blob/main/VERSIONING.md) +for the eco-wide versioning scheme. + +## Reporting a vulnerability + +**Do not open a public GitHub issue for security vulnerabilities.** Instead: + +1. Open a private [GitHub Security Advisory](https://github.com/GrayCodeAI/hawk-mcpkit/security/advisories/new), **or** +2. Email `security@graycode.ai` with the details below. + +Include in your report: + +- A description of the vulnerability and the affected component. +- Steps to reproduce, ideally with a minimal proof-of-concept. +- The version (`VERSION` file or git SHA) you tested against. +- The potential impact and any suggested mitigation. + +**Response targets:** + +- Initial acknowledgement: within **48 hours**. +- Triage and severity assessment: within **5 business days**. +- Coordinated fix and disclosure: within **30 days** for high/critical, **90 + days** for medium/low (per industry-standard responsible disclosure). + +## Disclosure policy + +We follow [coordinated vulnerability disclosure](https://en.wikipedia.org/wiki/Coordinated_vulnerability_disclosure): + +- Reporters receive credit in the advisory and CHANGELOG (unless they opt + out). +- We request that reporters refrain from public disclosure until a fix has + been released or the disclosure deadline above has elapsed. +- We will not pursue legal action against good-faith researchers acting + within this policy. + +## Security practices in this repo + +- **Dependency monitoring:** vulnerable dependencies are detected by + `govulncheck`, which runs on every CI build (see "Vulnerability scanning"). +- **Static analysis:** `golangci-lint` / `ruff` / `mypy` enforced in CI. +- **Vulnerability scanning:** `govulncheck` (Go) / `pip-audit` (Python) run + on every CI build. +- **Lockfiles:** `go.sum` / `pnpm-lock.yaml` / `pyproject.toml` are pinned + and committed. +- **Reproducible builds:** release artefacts ship with SHA-256 checksums via + goreleaser. +- **No secrets in source:** API keys are configuration, not constants. Pre- + commit hooks block accidental secret commits. + +## Scope + +This policy covers the code in this repository and the release artefacts +published from it. It does not cover: + +- Third-party dependencies (report to upstream). +- LLM provider services that hawk-mcpkit integrates with (report to the + provider). +- Local filesystem misuse where an attacker already has shell access (out of + threat model). + +For hawk-mcpkit-specific threat-model notes, see the README and any docs in +this repo. From 1d45d639d4b117c594c5663c66e03d18337183c3 Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Wed, 8 Jul 2026 05:14:02 +0530 Subject: [PATCH 3/4] MCP toolkit hardening pass - Update AGENTS.md to the shared hawk-eco extension-authoring format --- AGENTS.md | 260 ++++++++++++++++++++++++++----------------------- Makefile | 2 +- README.md | 14 ++- mcpkit.go | 54 +++++++--- mcpkit_test.go | 80 +++++++++++++-- 5 files changed, 265 insertions(+), 145 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index dff7312..4976a45 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,148 +1,168 @@ -# AGENTS.md — hawk-mcpkit +--- +description: Extending hawk-eco — how to write AGENTS.md files, custom specialists, skills, hooks, MCP servers, and plugins. +globs: "*.go, *.js, *.md, *.json, *.toml, *.yaml, *.yml" +alwaysApply: false +--- -This file describes the hawk-mcpkit project for AI agents working in this -codebase. The TUI `/memory` command references this file. +# Extending hawk-eco ---- +hawk-eco is an open-source code intelligence platform. This document describes how to extend it with custom tools, skills, hooks, and integrations. + +## 1. Drop a project `AGENTS.md` + +When hawk-eco starts in a directory, it looks for project-level instructions and injects them into the system prompt. The lookup walks from your current working directory **up to the nearest git root** and reads the first matching file at each level — general rules at the repo root, more specific rules in sub-trees. Files are labeled with their directory in the prompt (e.g. `## Project guidelines (services/api/AGENTS.md)`). + +Accepted file names, in priority order at each level: + +| Path | Notes | +| --- | --- | +| `./AGENTS.md` | The classic spot — committed to your repo, shared with the team. | +| `./ZERO.md` | Brand-specific alias. Same format, lower priority. | +| `./.zero/AGENTS.md` | Project-local, hidden, gitignored. Personal notes that stay out of git. | + +Matching is **case-insensitive** on the basename, so `AGENTS.md`, `Agents.md`, and `agents.md` resolve to the same file on Windows and macOS. The git-tracked filename in this repo is `AGENTS.md` — keep that on case-sensitive filesystems (Linux, the WSL filesystem, or a CI runner) to match what the loader looks for. + +Both files use the same format. YAML frontmatter is optional; the markdown body is loaded as instructions for the agent. hawk-eco reads the file once at session start, so changes take effect on the next launch — not mid-session. + +```markdown +# Project conventions for -## Project Overview +- Build with `make`, not `go build` directly. +- Tests live next to the source file (`foo_test.go` next to `foo.go`). +- Run `make lint` before opening a PR. +- Never edit files under `third_party/` — those are vendored. +``` + +Tips: + +- Keep each file under ~8 KiB. hawk-eco caps the **total** across all matched files at 32 KiB; everything past the cap is dropped. +- Re-state rules in the imperative voice: "Run `make lint`", not "you should consider running the linter". +- Don't put secrets, model IDs, or environment-specific paths in `AGENTS.md`. Use config files for those. +- In a monorepo, drop a narrower `AGENTS.md` in each sub-tree (e.g. `services/api/AGENTS.md`). hawk-eco picks those up automatically when you launch from inside the sub-tree. +- A YAML frontmatter block (`---\n...\n---`) at the top is preserved verbatim in the injected prompt but is not parsed for `globs:` or `alwaysApply:` scoping today — keep the body self-contained. + +### Personal guidelines, across every project + +For preferences that follow *you*, not a specific repo (tone, tooling habits, workflow), drop a `ZERO.md` in your user config directory: `~/.config/hawk-eco/ZERO.md` on Linux/macOS, `%AppData%\Roaming\hawk-eco\ZERO.md` on Windows — the same directory as config files and your personal specialists. Same format and 8 KiB cap as the project files above, and the same case-insensitive basename match. -hawk-mcpkit is a shared MCP server scaffolding library for the hawk ecosystem. -It wraps [`mark3labs/mcp-go`](https://github.com/mark3labs/mcp-go) with the -construction, transports, and handler helpers that every hawk-ecosystem engine -(`inspect`, `sight`, ...) would otherwise duplicate. +This file is injected as its own `## User guidelines` section, before the project's `AGENTS.md`/`ZERO.md`, and is labeled as personal preference in the prompt: project guidelines are the later, more specific instruction and take precedence over it when the two conflict. -Consumers declare their tools and handlers; mcpkit does the rest. +## 2. Custom specialists -**Tagline:** Shared MCP server scaffolding for the hawk ecosystem. +Specialists are hawk-eco's sub-agents. Three scopes, in priority order: -## Ecosystem +| Scope | Path | Shared? | +| --- | --- | --- | +| Built-in | compiled into hawk-eco | yes | +| User | `~/.config/hawk-eco/specialists/*.md` | no — your machine only | +| Project | `./.zero/specialists/*.md` | yes — the repo team | -hawk-mcpkit is a **foundation repo** in the hawk-eco mono-ecosystem: +Project overrides user overrides built-in when names collide. -| Component | Purpose | -|-----------|---------| -| **hawk-mcpkit** | Shared MCP server scaffolding (this repo) | -| **hawk-core-contracts** | Shared cross-repo contracts (types, tools, events, policy, review, verify, sessions) | -| **eyrie** | LLM provider runtime — routing, streaming, retries, caching | -| **yaad** | Graph-based persistent memory for coding agents | -| **tok** | Tokenizer, compression, secrets scanning, rate limiting | -| **sight** | Diff-based code review and static analysis | -| **inspect** | Security audit library (CVE, API security, CI output) | -| **trace** | Session capture and replay CLI | -| **hawk** | AI coding agent (this repo) | +A specialist is a markdown manifest with frontmatter and a system prompt: -Engines that serve MCP (`sight`, `inspect`) import `hawk-mcpkit`; it never -imports them back. +```markdown +--- +description: Reviews API changes for breaking-change risk and missing tests. +tools: read-only,plan +--- + +You review API changes. For every changed hunk in `internal/api/` or any file +that ends in `_api.go`: -## Architecture +1. Confirm the public signature is backward-compatible, or note the breaking + change explicitly with the migration path. +2. Confirm a corresponding test exists in `internal/api/*_test.go` and that + the new behaviour is exercised. +3. Flag any new exported symbol without a doc comment. +Reply with one JSON object per finding: `{"file", "line", "severity", "message", "fix"}`. ``` -hawk-mcpkit/ -├── mcpkit.go # Server wrapper, tool registration, transport helpers -├── mcpkit_test.go # Tests for StrArg, JSONResult, Server construction -├── scripts/ -│ └── check-ecosystem-boundaries.sh # CI guard: zero hawk-eco deps -├── .github/ -│ └── workflows/ -│ ├── ci.yml # CI: format, module hygiene, vet, lint, test, security -│ └── release.yml # GitHub Release on v* tags -├── Makefile # Local dev tasks (lint, test, boundaries, ci) -├── lefthook.yml # Pre-commit hooks (boundary guard, co-author strip) -├── AGENTS.md # This file -├── README.md # Public API docs, usage examples -├── CHANGELOG.md # Keep a Changelog format -├── CODEOWNERS # Code ownership -├── LICENSE # MIT -├── VERSION # Source of truth for versioning -└── go.mod / go.sum # Module files + +CLI management: + +```bash +hawk-eco specialist list +hawk-eco specialist show api-reviewer +hawk-eco specialist create api-reviewer \ + --project \ + --description "Reviews API changes" \ + --tools read-only,plan \ + --prompt "$(cat api-reviewer.md)" +hawk-eco specialist edit api-reviewer --project +hawk-eco specialist delete api-reviewer --project +hawk-eco specialist path # prints the resolved specialists directory ``` -## Key Design Decisions +## 3. Skills + +Skills are markdown instruction files that extend agent capabilities. They can be: +- Project-scoped: dropped in `./.zero/skills/` or `./skills/` +- User-scoped: dropped in `~/.config/hawk-eco/skills/` + +A skill manifest: + +```markdown +--- +description: How to review Go code for security issues +globs: "*.go" +alwaysApply: true +--- + +When reviewing Go code for security: -- **Zero hawk-eco dependencies:** mcpkit imports nothing from `hawk`, any - engine, any SDK, or `hawk-core-contracts`. Its only non-stdlib dependency - is upstream `mark3labs/mcp-go`. CI enforces this with - `make boundaries`. -- **Library only, no binary:** mcpkit is a Go library consumed by engines. It - does not ship a CLI or standalone binary. -- **Minimal public API:** `New()`, `AddTool()`, `MCP()`, `ServeStdio()`, - `ServeHTTP()`, `StrArg()`, `JSONResult()`. Everything else is unexported. -- **Transport agnostic:** The `Server` struct wraps the underlying - `mcpserver.MCPServer`. Consumers choose the transport (stdio or HTTP) via - `ServeStdio()` or `ServeHTTP()`. -- **No ecosystem product logic:** mcpkit holds MCP server scaffolding only. - No agent orchestration, no engine-specific behavior, no provider logic. +1. Check for SQL injection patterns +2. Verify error handling doesn't expose sensitive data +3. Confirm secrets are not hardcoded +4. Validate input sanitization +``` -## Development Guidelines +## 4. Hooks -### Build & Test +Hooks allow custom commands to run at specific lifecycle points: +- `beforeReview` — runs before code review starts +- `afterReview` — runs after code review completes +- `sessionStart` — runs at session initialization +- `sessionEnd` — runs at session teardown ```bash -make test # Run unit tests (no race detector) -make test-race # Run unit tests with race detector -make lint # Run golangci-lint -make boundaries # Enforce zero hawk-eco imports -make ci # Full CI suite (tidy, fmt, vet, boundaries, lint, test-race, security) +hawk-eco hook add beforeReview --command "lint-check" +hawk-eco hook remove beforeReview +hawk-eco hook list ``` -### Go Conventions +## 5. MCP integration + +MCP (Model Context Protocol) servers can expose tools to hawk-eco: + +```bash +hawk-eco mcp add --name server --url http://localhost:8080 +hawk-eco mcp remove server +hawk-eco mcp list +``` -- Standard Go project layout: tests live alongside source files (`foo.go` → `foo_test.go`) -- Use table-driven tests where practical -- Errors are values — wrap with `fmt.Errorf("context: %w", err)` -- No global mutable state; prefer dependency injection +## 6. Plugins -### Commit Conventions +Plugins extend hawk-eco with custom tools and capabilities: -Use [Conventional Commits](https://www.conventionalcommits.org/): +```bash +hawk-eco plugin add --name my-plugin --path ./my-plugin +hawk-eco plugin remove my-plugin +hawk-eco plugin list ``` -feat: add HTTP transport support to mcpkit -fix: handle missing tool arguments gracefully -refactor: simplify Server struct API + +## 7. Verification + +hawk-eco includes a self-verification system to validate local changes before contributing: + +```bash +hawk-eco verify +hawk-eco verify --fix ``` -## File Organization Notes - -| File | Purpose | -|------|---------| -| `mcpkit.go` | Server wrapper, tool registration, transport helpers, StrArg, JSONResult | -| `mcpkit_test.go` | Unit tests for all exported API surface | -| `scripts/check-ecosystem-boundaries.sh` | CI guard against hawk-eco imports | -| `.github/workflows/ci.yml` | CI pipeline (format, module hygiene, vet, lint, test, security, build) | -| `.github/workflows/release.yml` | GitHub Release on `v*` tags | -| `Makefile` | Local dev tasks | -| `lefthook.yml` | Pre-commit hooks (boundary guard, co-author strip) | - -## Testing Patterns - -- **Table-driven tests** with `t.Run(name, func(t *testing.T){...})` for all multi-case tests -- **`t.Parallel()`** on all tests that don't share mutable state -- **No mocks framework** — use concrete types and test doubles -- All tests are in `mcpkit_test.go` (single test file, library is small) - -## How to Add New Transports - -1. Add a method on `Server` that accepts transport-specific options -2. The method should create the appropriate server from `mcpserver` and call - `Listen` or `Start` with the consumer-provided address or streams -3. Add tests in `mcpkit_test.go` -4. Update the README API table - -## How to Add New Handler Helpers - -1. Add a helper function that wraps common argument extraction or result - marshalling patterns used across engines -2. The helper should return `(*mcplib.CallToolResult, error)` to be compatible - with `mcpserver.ToolHandlerFunc` -3. Add tests in `mcpkit_test.go` - -## Common Pitfalls - -- Do not import `hawk-core-contracts` or any other hawk-eco package in mcpkit -- Do not put engine-specific logic in mcpkit (e.g., agent loop, provider - routing, permission checking) -- The `Server` struct's unexported `mcp` field must not be accessed directly; - use `MCP()` for the escape hatch -- `StrArg` returns `""` for missing/non-string arguments; handle this in - your handler by returning an error +## Development + +```bash +make lint +hawk-eco verify +``` diff --git a/Makefile b/Makefile index 9d02005..72b3380 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ NAME := hawk-mcpkit # --------------------------------------------------------------------------- # Versioning — sourced from VERSION file; falls back to git describe. # --------------------------------------------------------------------------- -VERSION ?= $(shell cat VERSION 2>/dev/null | head -n1 | tr -d '[:space:]' || git describe --tags --always --dirty 2>/dev/null || echo "dev") +VERSION ?= $(shell v=$$(cat VERSION 2>/dev/null | head -n1 | tr -d '[:space:]'); if [ -n "$$v" ]; then echo "$$v"; else git describe --tags --always --dirty 2>/dev/null || echo "dev"; fi) COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "none") DATE := $(shell date -u '+%Y-%m-%dT%H:%M:%SZ') diff --git a/README.md b/README.md index 7c63ce2..e99bd3f 100644 --- a/README.md +++ b/README.md @@ -57,8 +57,12 @@ func main() { |------|------| | Create server | `s := mcpkit.New("name", "0.1.0")` | | Add a tool | `s.AddTool(tool, handler)` | +| Add a prompt | `s.AddPrompt(prompt, handler)` | +| Add a resource | `s.AddResource(resource, handler)` | +| Add a resource template | `s.AddResourceTemplate(template, handler)` | | Serve stdio | `s.ServeStdio()` | | Serve HTTP | `s.ServeHTTP(":8080")` | +| Serve SSE | `s.ServeSSE(":8080")` | | Extract string arg | `mcpkit.StrArg(req, "key")` | | Return JSON result | `mcpkit.JSONResult(map[string]any{...})` | @@ -80,10 +84,14 @@ hawk-mcpkit Server | Symbol | Purpose | |--------|---------| -| `New(name, version)` | Create a `*Server` with tool capabilities enabled. Returns `*Server`. | -| `(*Server).AddTool(tool, handler)` | Register a tool and its handler. `handler` is `func(context.Context, mcplib.CallToolRequest) (*mcplib.CallToolResult, error)`. | +| `New(name, version)` | Create a `*Server` with tool, prompt, and resource capabilities enabled. Returns `*Server`. | +| `(*Server).AddTool(tool, handler)` | Register a tool and its handler. `handler` is `func(context.Context, mcp.CallToolRequest) (*mcp.CallToolResult, error)`. | +| `(*Server).AddPrompt(prompt, handler)` | Register a prompt and its handler. `handler` is `func(context.Context, mcp.CallPromptRequest) (mcp.PromptResult, error)`. | +| `(*Server).AddResource(resource, handler)` | Register a resource and its handler. `handler` is `func(context.Context, mcp.ReadResourceRequest) ([]mcp.ResourceContent, error)`. | +| `(*Server).AddResourceTemplate(template, handler)` | Register a resource template and its handler. | | `(*Server).ServeStdio()` | Serve MCP over stdin/stdout. Blocks until stream closes. Returns `error`. | | `(*Server).ServeHTTP(addr)` | Serve MCP over streamable HTTP at `/mcp`. Blocks until server stops. Returns `error`. | +| `(*Server).ServeSSE(addr)` | Serve MCP over SSE transport. Blocks until server stops. Returns `error`. | | `(*Server).MCP()` | Escape hatch to the underlying `*mcpserver.MCPServer`. Use only for capabilities mcpkit does not wrap. | ### Handler Helpers @@ -91,7 +99,7 @@ hawk-mcpkit Server | Symbol | Purpose | |--------|---------| | `StrArg(req, key)` | Extract a string argument from a tool call request. Returns `""` when absent or not a string. | -| `JSONResult(v)` | Marshal `v` as indented JSON and return it as a text tool result. Returns `(*mcplib.CallToolResult, error)`. Error only when marshalling fails. | +| `JSONResult(v)` | Marshal `v` as indented JSON and return it as a text tool result. Returns `(*mcp.CallToolResult, error)`. Error only when marshalling fails. | ## Ecosystem diff --git a/mcpkit.go b/mcpkit.go index de5f540..eca8dc1 100644 --- a/mcpkit.go +++ b/mcpkit.go @@ -6,11 +6,9 @@ package mcpkit import ( - "context" "encoding/json" - "os" - mcplib "github.com/mark3labs/mcp-go/mcp" + mcp "github.com/mark3labs/mcp-go/mcp" mcpserver "github.com/mark3labs/mcp-go/server" ) @@ -20,28 +18,48 @@ type Server struct { mcp *mcpserver.MCPServer } -// New creates a named MCP server with tool capabilities enabled. +// New creates a named MCP server with tool, prompt, and resource +// capabilities enabled. func New(name, version string) *Server { return &Server{ - mcp: mcpserver.NewMCPServer(name, version, mcpserver.WithToolCapabilities(true)), + mcp: mcpserver.NewMCPServer(name, version, + mcpserver.WithToolCapabilities(true), + mcpserver.WithPromptCapabilities(true), + mcpserver.WithResourceCapabilities(false, true), + ), } } // AddTool registers a tool and its handler. -func (s *Server) AddTool(tool mcplib.Tool, handler mcpserver.ToolHandlerFunc) { +func (s *Server) AddTool(tool mcp.Tool, handler mcpserver.ToolHandlerFunc) { s.mcp.AddTool(tool, handler) } +// AddPrompt registers a prompt and its handler. +func (s *Server) AddPrompt(prompt mcp.Prompt, handler mcpserver.PromptHandlerFunc) { + s.mcp.AddPrompt(prompt, handler) +} + +// AddResource registers a resource and its handler. +func (s *Server) AddResource(resource mcp.Resource, handler mcpserver.ResourceHandlerFunc) { + s.mcp.AddResource(resource, handler) +} + +// AddResourceTemplate registers a resource template and its handler. +func (s *Server) AddResourceTemplate(template mcp.ResourceTemplate, handler mcpserver.ResourceTemplateHandlerFunc) { + s.mcp.AddResourceTemplate(template, handler) +} + // MCP returns the underlying mcp-go server, as an escape hatch for // capabilities mcpkit does not wrap. func (s *Server) MCP() *mcpserver.MCPServer { return s.mcp } -// ServeStdio serves MCP over stdin/stdout and blocks until the stream is -// closed or the context that mcp-go derives internally is done. +// ServeStdio serves MCP over stdin/stdout and blocks until the stream +// closes or the context that mcp-go derives internally is done. func (s *Server) ServeStdio() error { - return mcpserver.NewStdioServer(s.mcp).Listen(context.Background(), os.Stdin, os.Stdout) + return mcpserver.ServeStdio(s.mcp) } // ServeHTTP serves MCP over the streamable HTTP transport at @@ -50,9 +68,15 @@ func (s *Server) ServeHTTP(addr string) error { return mcpserver.NewStreamableHTTPServer(s.mcp).Start(addr) } -// StrArg extracts a string argument from a tool call request. It returns -// "" when the argument is absent or is not a string. -func StrArg(req mcplib.CallToolRequest, key string) string { +// ServeSSE serves MCP over the SSE transport at and blocks until +// the server stops. +func (s *Server) ServeSSE(addr string) error { + return mcpserver.NewSSEServer(s.mcp).Start(addr) +} + +// StrArg extracts a string argument from a tool call request. Returns +// "" when absent or not a string. +func StrArg(req mcp.CallToolRequest, key string) string { if v, ok := req.GetArguments()[key].(string); ok { return v } @@ -60,11 +84,11 @@ func StrArg(req mcplib.CallToolRequest, key string) string { } // JSONResult marshals v as indented JSON and returns it as a text tool -// result. It returns a protocol-level error only when marshalling fails. -func JSONResult(v any) (*mcplib.CallToolResult, error) { +// result. Returns a protocol-level error only when marshalling fails. +func JSONResult(v any) (*mcp.CallToolResult, error) { b, err := json.MarshalIndent(v, "", " ") if err != nil { return nil, err } - return mcplib.NewToolResultText(string(b)), nil + return mcp.NewToolResultText(string(b)), nil } diff --git a/mcpkit_test.go b/mcpkit_test.go index 7d3ac09..1e5e672 100644 --- a/mcpkit_test.go +++ b/mcpkit_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - mcplib "github.com/mark3labs/mcp-go/mcp" + mcp "github.com/mark3labs/mcp-go/mcp" ) func TestNew(t *testing.T) { @@ -16,13 +16,18 @@ func TestNew(t *testing.T) { if s.MCP() == nil { t.Fatal("underlying MCP server is nil") } + // WithToolCapabilities enables tool support + tool := mcp.NewTool("test", mcp.WithDescription("test")) + s.AddTool(tool, func(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) { + return nil, nil + }) } func TestAddTool(t *testing.T) { s := New("test-server", "0.0.1") - tool := mcplib.NewTool("echo", mcplib.WithDescription("echoes input")) - s.AddTool(tool, func(ctx context.Context, req mcplib.CallToolRequest) (*mcplib.CallToolResult, error) { - return mcplib.NewToolResultText(StrArg(req, "text")), nil + tool := mcp.NewTool("echo", mcp.WithDescription("echoes input")) + s.AddTool(tool, func(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { + return mcp.NewToolResultText(StrArg(req, "text")), nil }) } @@ -42,7 +47,7 @@ func TestStrArg(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - req := mcplib.CallToolRequest{} + req := mcp.CallToolRequest{} req.Params.Arguments = tc.args if got := StrArg(req, tc.key); got != tc.want { t.Errorf("StrArg(%q) = %q, want %q", tc.key, got, tc.want) @@ -59,7 +64,7 @@ func TestJSONResult(t *testing.T) { if result == nil || result.IsError { t.Fatal("expected successful result") } - text, ok := mcplib.AsTextContent(result.Content[0]) + text, ok := mcp.AsTextContent(result.Content[0]) if !ok { t.Fatal("expected text content") } @@ -73,3 +78,66 @@ func TestJSONResult_MarshalError(t *testing.T) { t.Fatal("expected marshal error for unsupported type") } } + +func TestAddPrompt(t *testing.T) { + s := New("test-server", "0.0.1") + prompt := mcp.NewPrompt("hello", mcp.WithPromptDescription("Greets someone")) + s.AddPrompt(prompt, func(_ context.Context, _ mcp.GetPromptRequest) (*mcp.GetPromptResult, error) { + return nil, nil + }) +} + +func TestAddResource(t *testing.T) { + s := New("test-server", "0.0.1") + resource := mcp.NewResource("info://server", "Server info") + s.AddResource(resource, func(_ context.Context, _ mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) { + return []mcp.ResourceContents{ + &mcp.TextResourceContents{URI: "info://server", Text: "test"}, + }, nil + }) +} + +func TestAddResourceTemplate(t *testing.T) { + s := New("test-server", "0.0.1") + tmpl := mcp.NewResourceTemplate("info://{path}", "Dynamic resource") + s.AddResourceTemplate(tmpl, func(_ context.Context, req mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) { + uri := req.Params.URI + return []mcp.ResourceContents{ + &mcp.TextResourceContents{URI: uri, Text: "data at " + uri}, + }, nil + }) +} + +func TestServer_MCPCapabilities(t *testing.T) { + s := New("test-server", "0.0.1") + mcpServer := s.MCP() + if mcpServer == nil { + t.Fatal("MCP() returned nil") + } + tool := mcp.NewTool("test_tool", mcp.WithDescription("test tool")) + s.AddTool(tool, func(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) { + return mcp.NewToolResultText("ok"), nil + }) +} + +func TestStrArg_WithRequest(t *testing.T) { + tests := []struct { + name string + args map[string]any + key string + want string + }{ + {name: "present string", args: map[string]any{"name": "test"}, key: "name", want: "test"}, + {name: "missing key", args: map[string]any{}, key: "name", want: ""}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + req := mcp.CallToolRequest{} + req.Params.Arguments = tc.args + if got := StrArg(req, tc.key); got != tc.want { + t.Errorf("StrArg(%q) = %q, want %q", tc.key, got, tc.want) + } + }) + } +} From 6dab4c07e68cf51b0297220cc321a2729a9c3505 Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Wed, 8 Jul 2026 07:51:58 +0530 Subject: [PATCH 4/4] Apply gofumpt formatting to satisfy CI format check --- mcpkit.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mcpkit.go b/mcpkit.go index eca8dc1..9159f86 100644 --- a/mcpkit.go +++ b/mcpkit.go @@ -22,7 +22,8 @@ type Server struct { // capabilities enabled. func New(name, version string) *Server { return &Server{ - mcp: mcpserver.NewMCPServer(name, version, + mcp: mcpserver.NewMCPServer( + name, version, mcpserver.WithToolCapabilities(true), mcpserver.WithPromptCapabilities(true), mcpserver.WithResourceCapabilities(false, true),