Aha MCP Server

A Model Context Protocol (MCP) server that provides seamless integration with Aha.io's product management platform. Features offline database synchronization, vector embeddings for semantic search, and comprehensive workflow automation.
π§ Client Configuration
MCP Registry
This server is published to the official MCP Registry
as io.github.cedricziel/aha-mcp, so a client that browses the registry can install it
without any of the configuration below:
curl "https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.cedricziel/aha-mcp"
The registry holds metadata only. Its entry points at the three artifacts described below β
the npm package, the ghcr.io image and the .mcpb desktop extension β and a client picks
whichever it can run. Either way the server needs AHA_COMPANY and AHA_TOKEN.
Claude Desktop Extension (easiest)
Download aha-mcp-v<version>.mcpb from the latest release
and open it with Claude Desktop, which will prompt you for your Aha.io subdomain and API
token. No Node.js or Docker setup and no manual JSON editing required.
The extension exposes 54 tools that query Aha.io directly, so results are always current and
nothing is stored locally. Cross-record search is served by Aha.io's own index β see
Search.
Claude Desktop Configuration
To use this MCP server with Claude Desktop, add the following to your claude_desktop_config.json:
Using npx:
{
"mcpServers": {
"aha": {
"command": "npx",
"args": ["@cedricziel/aha-mcp"],
"env": {
"AHA_COMPANY": "your-company",
"AHA_TOKEN": "your-api-token"
}
}
}
}
Using Docker:
{
"mcpServers": {
"aha": {
"command": "docker",
"args": [
"run", "--rm", "-i",
"-e", "AHA_COMPANY=your-company",
"-e", "AHA_TOKEN=your-api-token",
"ghcr.io/cedricziel/aha-mcp"
]
}
}
}
Note: Replace your-company and your-api-token with your actual Aha.io subdomain and API token.
π Getting Started
Quick Start with npx
You can run the MCP server directly using npx without installing it globally:
export AHA_COMPANY="your-company"
export AHA_TOKEN="your-api-token"
npx @cedricziel/aha-mcp
Quick Start with Docker
You can also run the MCP server using Docker:
export AHA_COMPANY="your-company"
export AHA_TOKEN="your-api-token"
docker run --rm -e AHA_COMPANY="$AHA_COMPANY" -e AHA_TOKEN="$AHA_TOKEN" ghcr.io/cedricziel/aha-mcp
docker run --rm -p 3001:3001 -e AHA_COMPANY="$AHA_COMPANY" -e AHA_TOKEN="$AHA_TOKEN" ghcr.io/cedricziel/aha-mcp --mode streamable-http
Development Setup
-
Install Bun if you haven't already:
curl -fsSL https://bun.sh/install | bash
-
Install dependencies:
-
Configure environment variables:
export AHA_COMPANY="your-company"
export AHA_TOKEN="your-api-token"
-
Start the server:
bun start
bun start -- --mode streamable-http
bun run start:http
-
For development with auto-reload:
bun run dev
bun run dev -- --mode streamable-http
bun run dev:http
π Aha.io Integration
This MCP server provides hybrid integration with Aha.io through both live API access and offline database synchronization. The server automatically maintains a local SQLite database with your Aha.io data, enabling faster queries, offline access, and advanced semantic search capabilities.
Architecture Overview
- Hybrid Data Access: Live API calls for real-time data + offline SQLite database for performance
- Background Sync: Automatic synchronization of Aha.io entities to local database
- Vector Embeddings: Semantic search using sentence transformers and SQLite vector extensions
- Real-time Progress: Background job monitoring with detailed progress tracking
- Configuration Management: Runtime configuration without server restarts
Configuration
The Aha.io integration can be configured using multiple methods, with the following priority order:
- Environment Variables (highest priority)
- Configuration File (
~/.aha-mcp-config.json)
- Default Values (lowest priority)
Environment Variables
AHA_COMPANY: Your Aha.io subdomain (e.g., mycompany for mycompany.aha.io)
AHA_TOKEN: Your Aha.io API token (for API token authentication)
AHA_ACCESS_TOKEN: Your OAuth 2.0 access token (for OAuth authentication)
MCP_TRANSPORT_MODE: Transport mode (stdio or streamable-http)
MCP_PORT: Port number for HTTP-based modes (default: 3001)
MCP_HOST: Host address for HTTP-based modes (default: 0.0.0.0)
MCP_AUTH_TOKEN: Authentication token for HTTP-based modes (optional)
MCP_TOOL_RATE_LIMIT_PER_MINUTE: Tool calls allowed per minute (default: 120, 0 disables)
Transport Modes
The server supports two transport modes:
- stdio: Standard input/output mode for MCP client integration (default)
- streamable-http: HTTP transport (MCP protocol 2025-06-18), for remote and web clients
Example usage:
aha-mcp
aha-mcp --mode streamable-http --port 3001
Removed: the sse transport was deprecated in MCP spec 2025-03-26 and has been
removed. MCP_TRANSPORT_MODE=sse and --mode sse now fall back to streamable-http
with a warning, so existing configurations keep starting.
Authentication (HTTP transport)
The streamable-http transport supports optional Bearer token authentication:
Environment Variable Configuration
export MCP_AUTH_TOKEN="your-secure-token-here"
aha-mcp --mode streamable-http --port 3001
Client Authentication
When authentication is enabled, clients must include a Bearer token in the Authorization
header. All MCP traffic goes to the single /mcp endpoint:
curl -X POST \
-H "Authorization: Bearer your-secure-token-here" \
-H "Content-Type: application/json" \
-H "MCP-Protocol-Version: 2025-06-18" \
-d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}' \
http://localhost:3001/mcp
Most callers should use an MCP client library rather than raw HTTP; pass the token as an
Authorization header when constructing the transport.
Security Notes
- Opt-in: Authentication is optional. If
MCP_AUTH_TOKEN is not set, all requests are allowed.
- Token Security: Use strong, randomly generated tokens (minimum 8 characters).
- HTTPS: In production, always use HTTPS to protect tokens in transit.
- Token Storage: Tokens are obfuscated (base64 encoded) in the configuration file but should be treated as sensitive data.
Checking Authentication Status
curl http://localhost:3001/
The server provides three MCP tools for configuration management:
- configure_server: Update server settings at runtime
- get_server_config: View current configuration and validation status
- test_configuration: Test API connectivity with current settings
These tools allow you to manage configuration without restarting the server, making it easy to switch between different Aha.io accounts or update credentials.
Database Synchronization
The server maintains a local SQLite database with your Aha.io data for improved performance and offline access.
aha_sync_start: Start background synchronization of specified entity types
aha_sync_status: Check the status and progress of sync jobs
aha_sync_stop: Stop a running sync job
aha_sync_pause: Pause a sync job (can be resumed later)
aha_sync_resume: Resume a paused sync job
aha_sync_history: View detailed history of sync operations
aha_sync_health: Get overall sync service health status
aha_database_health: Check database connectivity and statistics
aha_database_cleanup: Clean up old sync jobs and optimize database
Sync Features
- Entity Types: Sync features, products, ideas, epics, initiatives, releases, goals, users, comments
- Progress Tracking: Real-time progress updates with detailed statistics
- Error Handling: Comprehensive error logging and recovery mechanisms
- Batch Processing: Configurable batch sizes for optimal performance
- Incremental Updates: Support for
updatedSince filtering to sync only recent changes
- Concurrent Operations: Multiple sync jobs can run simultaneously
Example Sync Workflow
aha_sync_start --entities features,products --batchSize 50
aha_sync_status --jobId sync-abc123
aha_sync_history --jobId sync-abc123 --limit 20
aha_sync_health
Semantic Search & Embeddings
The server includes advanced semantic search capabilities using vector embeddings.
aha_generate_embeddings: Generate vector embeddings for entity text content
aha_embedding_status: Check the status of embedding generation jobs
aha_semantic_search: Search entities using natural language queries
aha_generate_entity_embedding: Generate embedding for a specific entity
aha_find_similar: Find entities similar to a given entity
aha_pause_embeddings: Pause embedding generation jobs
aha_stop_embeddings: Stop embedding generation jobs
Semantic Search Features
- Vector Storage: Embeddings stored in SQLite with sqlite-vec extension
- Multiple Models: Support for different embedding models (default: all-MiniLM-L6-v2)
- Similarity Search: Cosine similarity search with configurable thresholds
- Cross-Entity Search: Find similar content across different entity types
- Real-time Generation: Background embedding generation with progress tracking
Example Embedding Workflow
aha_generate_embeddings --entities features,ideas --batchSize 25
aha_semantic_search --query "user authentication security" --threshold 0.7
aha_find_similar --entityType features --entityId FEAT-123 --limit 5
aha_embedding_status --jobId embed-xyz789
Available Resources
Breaking change: many collection resources (slim index lists like aha_features, and a
few with a handful of scalar columns like aha_ideas) now return text/markdown - a link
list or a table, depending on the record type - instead of a JSON array. Anything that used
to parse those collections' contents as JSON needs to change. Collections with richer nested
data (aha_goals, aha_initiatives, comment resources, and similar) are unaffected and, like
every single-record resource (aha://feature/{id} and the like), still return
application/json.
Individual Entity Resources
aha_idea: Access individual ideas using aha://idea/{id}
aha_feature: Access individual features using aha://feature/{id}
aha_user: Access individual users using aha://user/{id}
aha_epic: Access individual epics using aha://epic/{id}
aha_product: Access individual products using aha://product/{id}
aha_initiative: Access individual initiatives using aha://initiative/{id}
aha_requirement: Access individual requirements using aha://requirement/{id}
aha_competitor: Access individual competitors using aha://competitor/{id}
aha_todo: Access individual todos using aha://todo/{id}
Collection Resources
aha_features: List features with optional filtering using aha://features?query=...&tag=...
aha_users: List all users using aha://users
aha_epics: List epics for a product using aha://epics/{product_id}
aha_products: List all products using aha://products?updatedSince=...
aha_initiatives: List all initiatives using aha://initiatives?query=...&onlyActive=true
aha_ideas: List all ideas globally using aha://ideas?query=...&status=...&category=...
aha_ideas_by_product: List ideas for a product using aha://ideas/{product_id}?query=...&spam=false&sort=recent
aha_competitors: List competitors for a product using aha://competitors/{product_id}
aha_product_releases: List releases for a product using aha://releases/{product_id}?query=...&status=...
aha_initiative_epics: List epics for an initiative using aha://initiative/{initiative_id}/epics
aha_feature_comments: Access comments for a feature using aha://comments/feature/{feature_id}
aha_epic_comments: Access comments for an epic using aha://comments/epic/{epic_id}
aha_idea_comments: Access an idea's internal comments using aha://comments/idea/{idea_id}
aha_idea_portal_comments: Access an idea's ideas-portal comments using aha://idea-comments/{idea_id} β different records from the above, including anything a customer wrote
aha_initiative_comments: Access comments for an initiative using aha://comments/initiative/{initiative_id}
aha_product_comments: Access comments for a product using aha://comments/product/{product_id}
aha_goal_comments: Access comments for a goal using aha://comments/goal/{goal_id}
aha_release_comments: Access comments for a release using aha://comments/release/{release_id}
aha_release_phase_comments: Access comments for a release phase using aha://comments/release-phase/{release_phase_id}
aha_requirement_comments: Access comments for a requirement using aha://comments/requirement/{requirement_id}
aha_todo_comments: Access comments for a todo using aha://comments/todo/{todo_id}
Goal Resources
aha_goal: Access individual goals using aha://goal/{goal_id}
aha_goals: List all goals using aha://goals
aha_goal_epics: Access epics associated with a goal using aha://goal/{goal_id}/epics
aha_goal_key_results: Access key results for a goal using aha://goal/{goal_id}/key_results
aha_key_result: Access individual key results using aha://key_result/{id}
Release Resources
aha_release: Access individual releases using aha://release/{release_id}
aha_releases: List all releases using aha://releases
aha_release_features: Access features associated with a release using aha://release/{release_id}/features
aha_release_epics: Access epics associated with a release using aha://release/{release_id}/epics
aha_release_phase: Access individual release phases using aha://release-phase/{release_phase_id}
aha_release_phases: List all release phases using aha://release-phases
Custom Fields Resources
aha_custom_fields: List all custom field definitions using aha://custom-fields
aha_custom_field_options: Access options for a custom field using aha://custom-field/{custom_field_id}/options
Resource URI Examples
# Individual Entity Resources
aha://idea/IDEA-123 # Get specific idea
aha://feature/PROJ-456 # Get specific feature
aha://user/USER-789 # Get specific user
aha://epic/EPIC-101 # Get specific epic
aha://product/PROD-001 # Get specific product
aha://initiative/INIT-202 # Get specific initiative
aha://requirement/REQ-666 # Get specific requirement
aha://competitor/COMP-444 # Get specific competitor
aha://todo/TODO-777 # Get specific todo
# Collection Resources (enhanced with filtering)
aha://features?query=auth&tag=api&assignedToUser=user@example.com # Search features
aha://users # List all users
aha://epics/PROJ-001 # List epics for product
aha://products?updatedSince=2024-01-01T00:00:00Z # List products with filter
aha://initiatives?query=mobile&onlyActive=true&assignedToUser=user@example.com # Search initiatives
aha://ideas?query=nodejs&status=new&category=enhancement # List ideas globally with filters
aha://ideas/PROJ-001?query=search&spam=false&sort=recent&tag=enhancement # List ideas with filters
aha://competitors/PROJ-001 # List competitors for product
aha://releases/PROJ-001?query=mobile&status=shipped # List releases for product
aha://initiative/INIT-123/epics # List epics for initiative
# Comment Resources
aha://comments/feature/PRJ1-123 # Get comments for feature
aha://comments/epic/EPIC-123 # Get comments for epic
aha://comments/idea/IDEA-456 # Get an idea's internal comments
aha://idea-comments/IDEA-456 # Get an idea's ideas-portal comments
aha://comments/initiative/INIT-789 # Get comments for initiative
aha://comments/product/PROD-001 # Get comments for product
aha://comments/goal/GOAL-555 # Get comments for goal
aha://comments/release/REL-333 # Get comments for release
aha://comments/release-phase/RP-444 # Get comments for release phase
aha://comments/requirement/REQ-666 # Get comments for requirement
aha://comments/todo/TODO-777 # Get comments for todo
# Goal Resources
aha://goal/GOAL-123 # Get specific goal
aha://goals # List all goals
aha://goal/GOAL-456/epics # Get epics for goal
aha://goal/GOAL-456/key_results # Get key results for goal
aha://key_result/PRJ1-G-3-KR-1 # Get specific key result
# Release Resources
aha://release/REL-123 # Get specific release
aha://releases # List all releases
aha://release/REL-456/features # Get features for release
aha://release/REL-456/epics # Get epics for release
aha://release-phase/RP-123 # Get specific release phase
aha://release-phases # List all release phases
# Custom Fields Resources
aha://custom-fields # List all custom field definitions
aha://custom-field/CF-123/options # Get options for custom field
Note: List operations are handled through MCP resources. Tools cover search, single-record
reads, write operations and relationship management.
aha_get_feature: Read one feature, including workflow status, release, assignee, tags, score and custom field values
aha_get_epic: Read one epic
aha_get_idea: Read one idea
aha_get_initiative: Read one initiative
aha_get_release: Read one release
aha_get_goal: Read one goal, including its time frame, progress source, success metric and key result summary
aha_get_key_result: Read one key result, including its status and starting, current and target metrics
These return the full record as structuredContent. They duplicate what
aha://feature/{id} and friends already serve, deliberately: a client is free to surface
resources to its model or not, and several do not β on those, every read here was
unreachable, leaving write tools with no way to see what they were about to replace.
aha_search is not a substitute, as it cannot return per-record fields.
aha_list_release_features: List the features assigned to a release, with a link per feature and Aha's total for the release
aha_list_release_epics: List the epics assigned to a release, with a link per epic and Aha's total for the release
aha_list_key_results: List a goal's key results, with status, progress and metrics
aha_list_comments: List the comments on a record, both streams for an idea
The two release tools are the only way to enumerate a release. aha_search is
relevance-ranked, returns no release membership on a hit and cannot be asked for every record
in a scope, so a release list assembled from search results is partial β and nothing in it says
so. Both types are listed because a release is not organised the same way in every workspace: a
release planned in epics is invisible to the features tool. Each asks for 200 records per page
(Aha's own default is 30 for features; on the epics route it is unmeasured, which is why the
tool never relies on it) and always returns Aha's pagination block, so a caller can tell a
complete list from the front of a longer one. Aha
returns identity fields only on these endpoints, so use aha_get_feature or aha_get_epic for
the state of any one record.
aha_create_feature_comment: Create a comment on a feature
aha_create_initiative_in_product: Create an initiative within a specific product
aha_list_comments: Read comments; ideas include separate internal and portal streams
aha_create_comment: Add an internal comment to a supported record
aha_update_comment: Replace an internal comment's body using its comment ID
aha_delete_comment: Delete an internal comment using its comment ID
aha_create_idea_portal_comment: Add a portal comment with explicit visibility
aha_delete_idea_portal_comment: Delete a portal comment using its idea and comment IDs
Comment body accepts raw HTML, such as <p>A <strong>formatted</strong> reply.</p>,
or plain text. Markdown is not converted. Send HTML directly, without escaping the tags
as <p> or wrapping it in a code fence. The server preserves the body sent to Aha;
the short text preview removes tags, while structuredContent retains the returned body.
Use the comment's source from aha_list_comments to select internal or portal tools.
Portal body editing is not exposed because Aha documents its update endpoint only for
spam and visibility changes.
aha_create_feature: Create a feature within a specific release
aha_update_feature: Update a feature
aha_delete_feature: Delete a feature
aha_update_feature_progress: Update a feature's progress
aha_update_feature_score: Update a feature's score
aha_update_feature_custom_fields: Update a feature's custom fields
aha_update_epic: Update an epic
aha_delete_epic: Delete an epic
aha_create_epic_in_product: Create an epic within a specific product
aha_create_epic_in_release: Create an epic within a specific release
aha_create_idea: Create an idea in a product
aha_create_idea_with_category: Create an idea with a category
aha_create_idea_with_score: Create an idea with a score
aha_update_idea: Update an idea's workflow status and other fields
aha_delete_idea: Delete an idea
Read the idea with aha_get_idea before updating it. Status names depend on the workspace;
use the intended status exactly, for example:
{
"ideaId": "PRJ1-I-7",
"ideaData": { "idea": { "workflow_status": "Product Evaluating" } }
}
Pass this to aha_update_idea. Fields omitted from ideaData.idea are left unchanged.
aha_create_goal: Create a goal (objective) in a workspace
aha_update_goal: Update a goal's name, description, success metric, status, time frame or progress
aha_delete_goal: Delete a goal, and with it the key results it owns
aha_list_key_results: List a goal's key results, with status, progress and metrics
aha_create_key_result: Create a key result under a goal
aha_update_key_result: Update a key result β its status and starting, current or target metric
aha_delete_key_result: Delete a key result
Three things about these differ from the rest of the API, all measured against a live account:
- Goal creation and deletion are workspace-scoped.
POST /products/{id}/goals and
DELETE /products/{id}/goals/{id} are the only routes Aha offers, so both tools require a
workspace id β aha_get_goal returns it as product_id. Updates do not need one.
- A key result has no
url. Unlike every other record type, the standalone record carries
neither url nor resource, so the aha://key_result/{id} resource link each tool returns
is the only pointer a client can follow.
- A goal has no top-level workflow status. It lives under
success_metric.workflow_status,
which is what the Aha UI shows as the goal's status.
aha_create_competitor: Create a competitor in a product
aha_update_competitor: Update a competitor
aha_delete_competitor: Delete a competitor
aha_create_idea_by_portal_user: Create an idea by a portal user
aha_create_idea_with_portal_settings: Create an idea with enhanced portal settings
aha_associate_feature_with_epic: Associate a feature with an epic
aha_move_feature_to_release: Move a feature to a different release
aha_associate_feature_with_goals: Associate a feature with multiple goals
aha_update_feature_tags: Update tags for a feature
Note: reads are offered through both interfaces, by design:
- Resources cover the full read surface β every entity type, with filtering through URI parameters, and lists as well as single records
- Tools cover writes (create/update/delete), relationship management (associate/move), search, and single-record reads for the five most-written types
The overlap is deliberate. Resources are the richer read surface, but the MCP spec leaves it
to each client whether to expose them to its model, and tool-only clients are common. Keeping
reads tool-accessible for the types that have write tools is what stops an agent from changing
a field it cannot see.
π Phase 8 - Complete CRUD Operations & Advanced Features
The MCP server now provides comprehensive lifecycle management for Aha.io entities with complete CRUD operations, portal integration, and advanced workflow features:
Feature Management (6 Tools)
aha_create_feature: Create features within releases
aha_update_feature: Update existing features
aha_delete_feature: Delete features
aha_update_feature_progress: Update feature progress (0-100%)
aha_update_feature_score: Update feature scores
aha_update_feature_custom_fields: Update feature custom fields
Epic Management (2 Tools)
aha_update_epic: Update existing epics
aha_delete_epic: Delete epics
Idea Management (5 Tools)
aha_create_idea: Create ideas in products
aha_create_idea_with_category: Create ideas with categories
aha_create_idea_with_score: Create ideas with scores
aha_update_idea: Update an idea's workflow status and other fields
aha_delete_idea: Delete ideas
Competitor Management (3 Tools)
aha_create_competitor: Create competitors in products
aha_update_competitor: Update existing competitors
aha_delete_competitor: Delete competitors
Note: Initiative data access is now handled through MCP resources (aha_initiative, aha_initiatives, aha_initiative_comments, aha_initiative_epics) for a cleaner separation between read and write operations.
Portal Integration
aha_create_idea_by_portal_user: Create ideas by portal users
aha_create_idea_with_portal_settings: Create ideas with portal settings
Enhanced Filtering & Resources
- Initiative Filtering: Enhanced with
query, updatedSince, assignedToUser, onlyActive parameters
- Portal Configuration: Support for
skip_portal and submitted_idea_portal_id settings
- Comprehensive Entity Coverage: Full CRUD operations for features, epics, ideas, and competitors
Technical Achievements
- 54 MCP tools, all querying Aha.io directly β no local state
- 17 listed MCP resources covering the entity set, plus templated resource URIs
- 17 domain-specific prompts (workflow automation)
- 36 core CRUD and write operation tools for complete lifecycle management, including OKRs (goals and key results)
- Cross-record search over Aha's own index, covering 20 record types
- 7 single-record read tools, so a write can be checked against the record's current state on clients that do not surface resources
- Comment reads and writes on every record type Aha supports, with an idea's ideas-portal conversation handled as its own stream
- Goal and key result CRUD, so a quarterly OKR loop can run through the server rather than the Aha UI
- 5 server configuration tools for runtime configuration
- 534 tests passing with comprehensive service coverage
- No native dependencies, so the server runs anywhere Node does
- Comprehensive error handling with proper Zod schema validation
π Search
aha_search queries Aha.io's own search index through the GraphQL API
(POST /api/v2/graphql, searchDocuments). Nothing is cached locally, so results are
always current and no native dependencies or writable storage are required.
{ "query": "alerting" }
{ "query": "alerting", "recordTypes": ["Idea"], "workspaceId": "7387509120724661690" }
{ "query": "a* OR e* OR i* OR o* OR u*", "recordTypes": ["Idea"], "workspaceId": "7387509120724661690" }
What it matches: record names and descriptions. Comment bodies match too, surfacing as
Comment hits that link to their parent record.
Query syntax: term* for prefix matching, AND / OR / NOT, and "quoted phrases".
There is no match-all query. A bare * is rejected: on its own Aha returns an arbitrary
subset for it, and combined with workspaceId it returns nothing at all β an empty result
that reads like an empty workspace. Search for a term, or enumerate a workspace through the
list resources (aha://features, aha://ideas/{product_id}) instead of searching it.
What a hit carries: name, type, reference_num, internal id, workspace, absolute URL and
updated_at. Idea hits also carry portal votes and endorsements, and scorable types their
Aha.io score, so ideas can be ranked by demand without a second call. Hits whose type is
readable as a resource β feature, epic, idea, initiative, goal, key result, release,
requirement, competitor β come with a resource_link; the rest are reachable by URL.
Quote a reference number in full. The workspace prefix is part of it: IDEASVOC-I-9930
identifies an idea, I-9930 identifies nothing, and Aha answers the truncated form with a 404
that reads like a missing record rather than a mistyped one.
What it does not return: workflow status, release membership, assignee or custom field
values. Read the record itself for those:
{ "featureId": "PRJ1-123" }
Record types (recordTypes, omit to search all):
BusinessModel Β· Comment Β· Competitor Β· Epic Β· Feature Β· Goal Β· Idea Β· IdeaOrganization
IdeaTheme Β· IdeaUser Β· Initiative Β· KeyResult Β· Page Β· Persona Β· Project Β· Release
ReleasePhase Β· Requirement Β· StrategicPositioning Β· Task
Paging: perPage accepts 10β200 and defaults to 20 β Aha raises anything below 10.
total_count stops counting at 10,000, reported as total_count_is_capped: true.
Use scripts/check-graphql.ts to confirm what your own account and token can reach:
AHA_COMPANY=mycompany AHA_TOKEN=... bun run scripts/check-graphql.ts
Why not local embeddings?
Earlier versions synced Aha into SQLite and ranked results with a local "semantic search".
That has been removed. The embedding function hashed character codes through Math.sin(),
so it carried no semantic signal and its similarity scores were not interpretable. It also
required the native sqlite3 module and a writable data directory, which is what broke it
in packaged installs. Aha's server-side index is keyword-based but real, always current, and
free of all that machinery.
Aha also hosts its own MCP server at https://<yourcompany>.aha.io/api/v1/mcp.
When adding custom tools, resources, or prompts to your MCP server:
-
Use underscores (_) instead of hyphens (-) in all resource, tool, and prompt names
server.tool(
"my_custom_tool",
"Description of my custom tool",
{
param_name: z.string().describe("Parameter description")
},
async (params) => {
}
);
server.tool(
"my-custom-tool",
"Description of my custom tool",
{
param-name: z.string().describe("Parameter description")
},
async (params) => {
}
);
-
This naming convention ensures compatibility with Cursor and other AI tools that interact with your MCP server
π³ Docker Usage
Docker Images
The Aha MCP server is available as Docker images on GitHub Container Registry:
- GitHub Container Registry:
ghcr.io/cedricziel/aha-mcp
Running with Docker
Basic Usage
docker run --rm \
-e AHA_COMPANY="your-company" \
-e AHA_TOKEN="your-api-token" \
ghcr.io/cedricziel/aha-mcp
docker run --rm \
-p 3001:3001 \
-e AHA_COMPANY="your-company" \
-e AHA_TOKEN="your-api-token" \
ghcr.io/cedricziel/aha-mcp --mode streamable-http
docker run --rm \
-p 3001:3001 \
-e AHA_COMPANY="your-company" \
-e AHA_TOKEN="your-api-token" \
-e MCP_AUTH_TOKEN="your-secure-token" \
ghcr.io/cedricziel/aha-mcp --mode streamable-http
Persistent Configuration
To persist configuration between runs:
docker volume create aha-mcp-config
docker run --rm \
-v aha-mcp-config:/home/mcp/.config \
-e AHA_COMPANY="your-company" \
-e AHA_TOKEN="your-api-token" \
ghcr.io/cedricziel/aha-mcp
Using Docker Compose
The repository includes a docker-compose.yml file for easy setup:
cp .env.example .env
AHA_COMPANY=your-company
AHA_TOKEN=your-api-token
docker-compose --profile stdio up
docker-compose --profile http up
docker-compose --profile http up -d
Example .env file:
AHA_COMPANY=mycompany
AHA_TOKEN=your-api-token-here
MCP_AUTH_TOKEN=your-secure-token-here
Docker Environment Variables
The Docker image supports all the same environment variables as the npm package:
| Variable | Description | Default |
|---|
AHA_COMPANY | Aha.io company subdomain | - |
AHA_TOKEN | Aha.io API token | - |
MCP_TRANSPORT_MODE | Transport mode (stdio or streamable-http) | stdio |
MCP_PORT | Port for streamable-http mode | 3001 |
MCP_HOST | Host for streamable-http mode | 0.0.0.0 |
MCP_AUTH_TOKEN | Bearer token for the streamable-http transport | - |
MCP_TOOL_RATE_LIMIT_PER_MINUTE | Tool calls allowed per minute (0 disables) | 120 |
MCP_CONFIG_DIR | Configuration directory | /home/mcp/.config |
Health Checks
The Docker image includes health checks for streamable-http mode:
curl http://localhost:3001/health
curl http://localhost:3001/status
Building from Source
To build the Docker image locally:
npm run docker:build
npm run docker:test
npm run docker:run
npm run docker:run:http
Multi-Architecture Support
The Docker images are built for multiple architectures:
linux/amd64 (x86_64)
linux/arm64 (Apple Silicon, ARM64)
Docker will automatically pull the correct image for your platform.
Docker Security
The Docker image follows security best practices:
- Runs as non-root user (
mcp)
- Uses minimal Alpine Linux base image
- Includes tini for proper signal handling
- Configuration directory has proper permissions
- Uses multi-stage builds to reduce attack surface
ποΈ Development
Commit Guidelines
This project uses Conventional Commits to ensure consistent commit messages and enable automated versioning.
Commit Message Format:
<type>(<scope>): <subject>
<body>
<footer>
Types:
feat: A new feature
fix: A bug fix
docs: Documentation only changes
style: Changes that do not affect the meaning of the code
refactor: A code change that neither fixes a bug nor adds a feature
perf: A code change that improves performance
test: Adding missing tests or correcting existing tests
build: Changes that affect the build system or external dependencies
ci: Changes to CI configuration files and scripts
chore: Other changes that don't modify src or test files
Examples:
feat: add new MCP tool for listing projects
fix: resolve authentication issue with API tokens
docs: update README with installation instructions
feat!: change API response format (breaking change)
Commit messages are validated using commitlint on every commit and in CI.
Testing
Local Testing
Run the test suite:
bun test
bun run test:watch
bun test --coverage
Docker Testing
The Docker environment includes all necessary dependencies for testing:
docker build -t aha-mcp-test .
docker run --rm aha-mcp-test bun test
docker run --rm \
-e AHA_COMPANY="test-company" \
-e AHA_TOKEN="test-token" \
aha-mcp-test bun test
docker run --rm aha-mcp-test bun install --dry-run
docker run --rm aha-mcp-test node -e "
const sqlite3 = require('sqlite3');
const db = new sqlite3.Database(':memory:');
console.log('SQLite available:', !!db);
db.close();
"
docker run --rm aha-mcp-test bun run start --help
Testing Database Features
The Docker environment includes:
- SQLite3: Core database functionality
- Node.js sqlite packages: Database drivers and utilities
- Graceful fallback: sqlite-vec extension warnings are suppressed in test environments
- Temporary databases: Each test uses isolated temporary database files
- Proper cleanup: Database connections and files are cleaned up after tests
Verifying Docker Environment
docker run --rm aha-mcp-test sh -c "
echo 'Checking Bun...'; bun --version
echo 'Checking Node.js...'; node --version
echo 'Checking SQLite...'; node -e 'console.log(require(\"sqlite3\"))'
echo 'Checking dependencies...'; bun install --dry-run
echo 'Running basic tests...'; bun test --reporter=dot
"
docker run --rm -d --name aha-test \
-e AHA_COMPANY="test" \
-e AHA_TOKEN="test" \
aha-mcp-test
docker logs aha-test
docker stop aha-test
The Docker environment supports the full test suite including:
- 194+ test cases across all services
- Database service tests (25 test cases)
- Background sync service tests (16 test cases)
- MCP accessibility tests (172 test cases)
- SQLite extension warnings are automatically suppressed in test mode
Building
To build for production:
bun run build
bun run build:http
Publishing
Automated Release Process (Recommended)
This project uses release-please for automated versioning and publishing:
-
Make changes using Conventional Commits format:
feat: for new features (minor version bump)
fix: for bug fixes (patch version bump)
feat!: or fix!: for breaking changes (major version bump)
-
Push to main - release-please will automatically:
- Create a release PR with updated version and changelog
- Once the release PR is merged, it will create a GitHub release
- The release will trigger automatic publication to npm,
ghcr.io and the
MCP Registry
The registry job runs last, because the registry verifies ownership by reading the
already-published artifacts: mcpName in the npm package, the
io.modelcontextprotocol.server.name label on the image, and the SHA-256 of the .mcpb
asset attached to the release. It authenticates with GitHub OIDC, so no registry
credential is stored anywhere.
Manual Publishing
To publish the package manually:
npm login
bun run build
npm publish --access public
Note: Make sure to set the NPM_TOKEN secret in your repository settings for automated publishing.
π Documentation
- CLAUDE.md - Development guidance for Claude Code when working with this repository
- MCP Documentation - Official Model Context Protocol documentation
π License
This project is licensed under the MIT License - see the LICENSE file for details.