diff --git a/.github/workflows/mcp-eval-tests.yml b/.github/workflows/mcp-eval-tests.yml
new file mode 100644
index 0000000..34035bb
--- /dev/null
+++ b/.github/workflows/mcp-eval-tests.yml
@@ -0,0 +1,35 @@
+name: MCP Eval Tests
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ paths:
+ - 'mcp-eval/**'
+ - .github/workflows/mcp-eval-tests.yml
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: mcp-eval
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v5
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: 'mcp-eval/.node-version'
+ cache: 'yarn'
+ cache-dependency-path: 'mcp-eval/yarn.lock'
+
+ - name: Install dependencies
+ run: yarn install --frozen-lockfile
+
+ - name: Run linting (prettier)
+ run: yarn lint
+
+ - name: Run tests
+ run: yarn test
diff --git a/README.md b/README.md
index 4eda29a..abc1f21 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,10 @@
+
+
+
+
@@ -201,3 +205,63 @@ If you didn't install the binary directly and instead pulled the docker image yo
"public.ecr.aws/opslevel/mcp:latest"
],
```
+
+# Development
+
+## Testing
+
+This repository uses a comprehensive test suite to ensure code quality and functionality.
+
+### Running Tests
+
+#### Go Tests
+
+The Go codebase (main MCP server) includes unit tests for core functionality:
+
+```bash
+# Run all Go tests
+cd src
+go test ./...
+
+# Run tests with coverage
+go test -race -coverprofile=coverage.txt -covermode=atomic ./...
+
+# Run tests with verbose output
+go test -v ./...
+```
+
+#### JavaScript Tests
+
+The `mcp-eval` evaluation framework includes Jest-based tests:
+
+```bash
+# Run all JavaScript tests
+cd mcp-eval
+npm test
+
+# Run tests in watch mode
+npm test -- --watch
+```
+
+#### Using Task
+
+The repository uses [Task](https://taskfile.dev/) as a task runner. To run the full CI pipeline locally:
+
+```bash
+# Run linting and tests
+task ci
+
+# Run only tests
+task test
+
+# Run only linting
+task lint
+```
+
+### Continuous Integration
+
+All tests are automatically run on:
+- Pull requests to the main branch
+- Pushes to the main branch
+
+The CI pipeline runs linting, formatting checks, and the complete test suite to ensure code quality.
diff --git a/src/cmd/root_test.go b/src/cmd/root_test.go
new file mode 100644
index 0000000..aa83e2f
--- /dev/null
+++ b/src/cmd/root_test.go
@@ -0,0 +1,218 @@
+package cmd
+
+import (
+"encoding/json"
+"testing"
+
+"github.com/opslevel/opslevel-go/v2025"
+)
+
+func TestNewToolResult(t *testing.T) {
+// Test successful result with simple data
+data := map[string]string{"key": "value"}
+result, err := newToolResult(data, nil)
+
+if err != nil {
+t.Errorf("Expected no error, got %v", err)
+}
+
+if result == nil {
+t.Fatal("Expected result to not be nil")
+}
+
+// Test with error
+testErr := opslevel.NewGQLError(nil, "test error")
+result, err = newToolResult(nil, testErr)
+
+if err != nil {
+t.Errorf("Expected no error from newToolResult, got %v", err)
+}
+
+if result == nil {
+t.Fatal("Expected result to not be nil")
+}
+}
+
+func TestSerializedComponentStructure(t *testing.T) {
+// Test that serializedComponent can be instantiated and marshaled
+component := serializedComponent{
+Id: "test-id",
+Framework: "Django",
+Language: "Python",
+Name: "test-service",
+Owner: "test-team",
+Url: "https://example.com",
+Level: serializedLevel{
+Alias: "Gold",
+Index: 2,
+},
+Lifecycle: serializedLifecycle{
+Alias: "Production",
+Index: 3,
+},
+Tier: serializedTier{
+Alias: "Tier 1",
+Index: 0,
+},
+}
+
+// Marshal to JSON to verify structure
+data, err := json.Marshal(component)
+if err != nil {
+t.Errorf("Failed to marshal component: %v", err)
+}
+
+// Unmarshal to verify round-trip
+var decoded serializedComponent
+err = json.Unmarshal(data, &decoded)
+if err != nil {
+t.Errorf("Failed to unmarshal component: %v", err)
+}
+
+if decoded.Name != "test-service" {
+t.Errorf("Expected Name 'test-service', got '%s'", decoded.Name)
+}
+
+if decoded.Language != "Python" {
+t.Errorf("Expected Language 'Python', got '%s'", decoded.Language)
+}
+}
+
+func TestSerializedCheckStructure(t *testing.T) {
+// Test that serializedCheck can be instantiated and marshaled
+check := serializedCheck{
+Id: "check-id",
+Name: "test-check",
+Owner: "test-team",
+Description: "Test description",
+Notes: "Test notes",
+Enabled: true,
+Type: "manual",
+Level: serializedLevel{
+Alias: "Silver",
+Index: 1,
+},
+Category: "Security",
+}
+
+data, err := json.Marshal(check)
+if err != nil {
+t.Errorf("Failed to marshal check: %v", err)
+}
+
+var decoded serializedCheck
+err = json.Unmarshal(data, &decoded)
+if err != nil {
+t.Errorf("Failed to unmarshal check: %v", err)
+}
+
+if decoded.Name != "test-check" {
+t.Errorf("Expected Name 'test-check', got '%s'", decoded.Name)
+}
+
+if !decoded.Enabled {
+t.Error("Expected Enabled to be true")
+}
+}
+
+func TestSerializedInfrastructureResourceStructure(t *testing.T) {
+// Test that serializedInfrastructureResource can be instantiated
+resource := serializedInfrastructureResource{
+Id: "infra-id",
+Name: "test-db",
+Owner: "test-team",
+Aliases: []string{"alias1", "alias2"},
+Schema: "postgresql",
+ProviderType: "aws",
+}
+
+data, err := json.Marshal(resource)
+if err != nil {
+t.Errorf("Failed to marshal infrastructure resource: %v", err)
+}
+
+var decoded serializedInfrastructureResource
+err = json.Unmarshal(data, &decoded)
+if err != nil {
+t.Errorf("Failed to unmarshal infrastructure resource: %v", err)
+}
+
+if decoded.Name != "test-db" {
+t.Errorf("Expected Name 'test-db', got '%s'", decoded.Name)
+}
+
+if len(decoded.Aliases) != 2 {
+t.Errorf("Expected 2 aliases, got %d", len(decoded.Aliases))
+}
+}
+
+func TestAllAccountMetadataStrings(t *testing.T) {
+metadataTypes := AllAccountMetadataStrings()
+
+if len(metadataTypes) == 0 {
+t.Error("Expected at least one metadata type, got none")
+}
+
+// Verify expected types are present
+expectedTypes := []string{"lifecycles", "levels", "tiers", "componentTypes"}
+for _, expected := range expectedTypes {
+found := false
+for _, actual := range metadataTypes {
+if actual == expected {
+found = true
+break
+}
+}
+if !found {
+t.Errorf("Expected metadata type '%s' not found", expected)
+}
+}
+}
+
+func TestComponentFilterStructure(t *testing.T) {
+// Test simple filter
+filter := componentFilter{
+Key: "name",
+Type: "equals",
+Arg: "service-name",
+}
+
+data, err := json.Marshal(filter)
+if err != nil {
+t.Errorf("Failed to marshal filter: %v", err)
+}
+
+var decoded componentFilter
+err = json.Unmarshal(data, &decoded)
+if err != nil {
+t.Errorf("Failed to unmarshal filter: %v", err)
+}
+
+if decoded.Key != "name" {
+t.Errorf("Expected Key 'name', got '%s'", decoded.Key)
+}
+
+// Test composite filter
+compositeFilter := componentFilter{
+Connective: "and",
+Predicates: []componentFilter{
+{Key: "language", Type: "equals", Arg: "Python"},
+{Key: "owner_id", Type: "equals", Arg: "team-123"},
+},
+}
+
+data, err = json.Marshal(compositeFilter)
+if err != nil {
+t.Errorf("Failed to marshal composite filter: %v", err)
+}
+
+var decodedComposite componentFilter
+err = json.Unmarshal(data, &decodedComposite)
+if err != nil {
+t.Errorf("Failed to unmarshal composite filter: %v", err)
+}
+
+if len(decodedComposite.Predicates) != 2 {
+t.Errorf("Expected 2 predicates, got %d", len(decodedComposite.Predicates))
+}
+}
diff --git a/src/cmd/version_test.go b/src/cmd/version_test.go
new file mode 100644
index 0000000..9c5cf08
--- /dev/null
+++ b/src/cmd/version_test.go
@@ -0,0 +1,116 @@
+package cmd
+
+import (
+"runtime"
+"testing"
+)
+
+func TestGetGoInfo(t *testing.T) {
+goInfo := getGoInfo()
+
+if goInfo.Version == "" {
+t.Error("Expected Go version to be set, got empty string")
+}
+
+if goInfo.Compiler == "" {
+t.Error("Expected Go compiler to be set, got empty string")
+}
+
+if goInfo.OS == "" {
+t.Error("Expected Go OS to be set, got empty string")
+}
+
+if goInfo.Arch == "" {
+t.Error("Expected Go Arch to be set, got empty string")
+}
+
+// Verify values match runtime package
+if goInfo.Version != runtime.Version() {
+t.Errorf("Expected Version %s, got %s", runtime.Version(), goInfo.Version)
+}
+
+if goInfo.Compiler != runtime.Compiler {
+t.Errorf("Expected Compiler %s, got %s", runtime.Compiler, goInfo.Compiler)
+}
+
+if goInfo.OS != runtime.GOOS {
+t.Errorf("Expected OS %s, got %s", runtime.GOOS, goInfo.OS)
+}
+
+if goInfo.Arch != runtime.GOARCH {
+t.Errorf("Expected Arch %s, got %s", runtime.GOARCH, goInfo.Arch)
+}
+}
+
+func TestInitBuild(t *testing.T) {
+// Save original values
+originalVersion := version
+originalCommit := commit
+
+// Test with normal commit
+version = "1.0.0"
+commit = "abc123def456789"
+initBuild()
+
+if build.Version != "1.0.0" {
+t.Errorf("Expected build.Version to be '1.0.0', got '%s'", build.Version)
+}
+
+if build.Commit != "abc123def456" {
+t.Errorf("Expected build.Commit to be truncated to 12 chars, got '%s'", build.Commit)
+}
+
+// Test with short commit
+commit = "short"
+initBuild()
+
+if build.Commit != "short" {
+t.Errorf("Expected build.Commit to be 'short', got '%s'", build.Commit)
+}
+
+// Restore original values
+version = originalVersion
+commit = originalCommit
+}
+
+func TestBuildStructure(t *testing.T) {
+// Test that Build struct can be instantiated
+b := Build{
+Version: "1.0.0",
+Commit: "abc123",
+GoInfo: GoInfo{
+Version: "go1.20",
+Compiler: "gc",
+OS: "linux",
+Arch: "amd64",
+},
+}
+
+if b.Version != "1.0.0" {
+t.Errorf("Expected Version '1.0.0', got '%s'", b.Version)
+}
+
+if b.Commit != "abc123" {
+t.Errorf("Expected Commit 'abc123', got '%s'", b.Commit)
+}
+
+if b.GoInfo.Version != "go1.20" {
+t.Errorf("Expected GoInfo.Version 'go1.20', got '%s'", b.GoInfo.Version)
+}
+}
+
+func TestOpslevelVersionStructure(t *testing.T) {
+// Test that OpslevelVersion struct can be instantiated
+ov := OpslevelVersion{
+Version: "1.0.0",
+Commit: "abc123",
+}
+
+if ov.Version != "1.0.0" {
+t.Errorf("Expected Version '1.0.0', got '%s'", ov.Version)
+}
+
+if ov.Commit != "abc123" {
+t.Errorf("Expected Commit 'abc123', got '%s'", ov.Commit)
+}
+}
diff --git a/src/main_test.go b/src/main_test.go
new file mode 100644
index 0000000..9388f87
--- /dev/null
+++ b/src/main_test.go
@@ -0,0 +1,27 @@
+package main
+
+import (
+"testing"
+)
+
+func TestVersionVariables(t *testing.T) {
+// Test that version variables are defined
+if version == "" {
+t.Log("version is empty (expected for dev builds)")
+}
+
+if commit == "" {
+t.Log("commit is empty (expected for dev builds)")
+}
+
+// At minimum, these should be defined as strings
+if len(version) >= 0 && len(commit) >= 0 {
+t.Log("Version variables are properly defined")
+}
+}
+
+func TestMainPackageExists(t *testing.T) {
+// This test simply verifies that the main package can be imported and tested
+// This ensures the basic structure is correct
+t.Log("Main package is accessible for testing")
+}