59 KiB
Sage X3 GraphQL MCP Server
TL;DR
Quick Summary: Build a TypeScript MCP server (Bun runtime) that wraps the Sage X3 GraphQL API for read-only queries, giving AI agents autonomous access to ERP data with rich embedded knowledge about X3 concepts, query patterns, and available entities.
Deliverables:
- MCP server with 5 tools (query, read, aggregate, introspect, raw execute)
- Rich knowledge resources (X3 concepts, filter syntax, entity reference)
- Guided prompt templates for common workflows
- JWT authentication module + sandbox/demo mode
- Full test suite against Sage X3 sandbox
Estimated Effort: Medium Parallel Execution: YES - 4 waves Critical Path: Task 1 → Task 3 → Task 4 → Task 6 → Task 11 → Task 14
Context
Original Request
Build an MCP server for Sage X3 focused on GraphQL capabilities. Previous attempt missed important information due to webfetch 403s. This time, developer docs were browsed via Playwright to capture all API details. Focus on read-only queries only — mutations should be done manually. Server must include embedded knowledge to allow AI agent autonomy. Testing must use the Sage X3 sandbox environment.
Interview Summary
Key Discussions:
- Read-only scope: No mutations — explicit user decision. Only query, read, readAggregate operations.
- Runtime: Bun — built-in TypeScript, fast startup, modern tooling
- Knowledge depth: Rich knowledge via MCP Resources + Prompts — agent should work autonomously
- Schema introspection: Dynamic tool to discover X3 types/fields at runtime
- Auth modes: Both unauthenticated sandbox/demo + authenticated JWT connected app
- Testing: Tests after implementation using bun:test, sandbox QA verification
Research Findings:
- Sage X3 GraphQL API (browsed via Playwright from developer.sage.com):
- Endpoint: POST to
{url}/xtrem/api(on-premise) or demo endpoint - Headers:
Authorization: Bearer <jwt>,x-xtrem-endpoint: <name>,Content-Type: application/json - Root types:
xtremX3Structure,xtremX3MasterData,xtremX3Products,xtremX3Purchasing,xtremX3Stock - Operations:
query(filter, first, after, last, before, orderBy),read(_id),readAggregate(filter) - Filter syntax: String-based JSON —
"{code: { _eq: '002' }}"with operators_eq, _gt, _gte, _lte, _regex, _atLeast - Pagination: Relay cursor-based —
first/after,last/before,pageInfo { endCursor hasNextPage } - JWT auth:
iss=clientId, sub=user, aud='', iat=now-30, exp=now+lifetime, signed with secret - Demo endpoint:
https://api-devna.dev-sagex3.com/demo/service/X3CLOUDV2_SEED/api(unauthenticated, read-only, max 20 results) - Sandbox explorer:
https://apidemo.sagex3.com/demo/service/X3CLOUDV2_SEED/explorer/(V54 APIs) - Error codes: 400 (bad query), 401 (auth expired), 500 (server error)
- Endpoint: POST to
- MCP SDK (from Context7 + GitHub):
@modelcontextprotocol/sdkv1.x —McpServer,registerTool(),registerPrompt(), Zod v4 schemasStdioServerTransportfor CLI integration- Reference implementations:
reactive-resume(tools/resources/prompts separated),outline(scoped server)
Identified Gaps (addressed in guardrails)
- TLS/SSL: On-premise X3 may use self-signed certificates — must support
rejectUnauthorized: falseoption - Token expiration: JWT tokens have limited lifetime — clear error messages when expired
- Large result sets: Agent must understand pagination is needed; knowledge resources must cover this
- Filter syntax complexity: String-based JSON filter is unusual — detailed knowledge resource needed
Work Objectives
Core Objective
Create a production-ready MCP server that enables AI agents to query Sage X3 ERP data via GraphQL with full autonomy, rich contextual knowledge, and support for both sandbox testing and authenticated production use.
Concrete Deliverables
src/index.ts— MCP server entry point with stdio transportsrc/tools/— 5 registered MCP tools for GraphQL operationssrc/resources/— MCP resources with embedded X3 knowledgesrc/prompts/— MCP prompt templates for guided workflowssrc/auth/— JWT authentication modulesrc/graphql/— GraphQL HTTP clientsrc/types/— TypeScript type definitionssrc/config.ts— Configuration managementtests/— Test suite (bun:test)package.json,tsconfig.json— Project configuration
Definition of Done
bun run buildcompletes without errorsbun testpasses all tests- MCP server connects via stdio and lists tools/resources/prompts
- At least one tool successfully queries the Sage X3 sandbox demo endpoint
- All 5 tools respond correctly to valid inputs
- Resources serve X3 knowledge content
- Prompts generate valid guided messages
Must Have
- 5 MCP tools:
query_entities,read_entity,aggregate_entities,introspect_schema,execute_graphql - MCP resources with X3 domain knowledge (concepts, filter syntax, entity reference, query patterns)
- MCP prompts for guided workflows
- JWT auth module for on-premise X3 + unauthenticated sandbox mode
- Proper error handling with user-friendly messages for 400/401/500
- Configuration via environment variables
- Tests passing against sandbox endpoint
Must NOT Have (Guardrails)
- NO mutations: No create/update/delete operations. Tools must be strictly read-only. If a GraphQL query string contains
mutation, reject it. - NO git worktrees: Do not use git worktrees (per planner_instructions.md)
- NO hardcoded credentials: Auth config via env vars only, never in source
- NO over-abstraction: Keep it simple — one file per tool, straightforward GraphQL client, no unnecessary abstractions
- NO unnecessary dependencies: Minimize npm packages. Bun has built-in fetch, test runner, etc. Only add what's truly needed (jose for JWT, zod for schemas).
- NO blocking foreground tasks: When sub-agents run background tasks, timeouts are in MILLISECONDS (timeout=30000 means 30 seconds). Prefer background tasks with large timeouts (timeout=120000 for 2 min) over foreground.
Verification Strategy
ZERO HUMAN INTERVENTION — ALL verification is agent-executed. No exceptions.
Test Decision
- Infrastructure exists: NO (greenfield)
- Automated tests: YES (Tests-after)
- Framework: bun:test (built-in)
QA Policy
Every task MUST include agent-executed QA scenarios.
Evidence saved to .sisyphus/evidence/task-{N}-{scenario-slug}.{ext}.
- MCP Server: Use Bash — start server in background, pipe JSON-RPC messages, validate responses
- API Integration: Use Bash (curl) — send POST requests to sandbox endpoint, validate JSON responses
- Module Testing: Use Bash (bun test) — run unit tests, assert pass count
Execution Strategy
Parallel Execution Waves
Wave 1 (Foundation — start immediately, max parallel):
├── Task 1: Project scaffolding + config [quick]
├── Task 2: TypeScript types + shared constants [quick]
├── Task 3: Auth module (JWT generation + sandbox mode) [unspecified-high]
└── Task 4: GraphQL HTTP client [unspecified-high]
Wave 2 (Core Tools — after Wave 1, max parallel):
├── Task 5: Tool: introspect_schema [unspecified-high]
├── Task 6: Tool: query_entities [deep]
├── Task 7: Tool: read_entity [quick]
├── Task 8: Tool: aggregate_entities [unspecified-high]
└── Task 9: Tool: execute_graphql (raw) [unspecified-high]
Wave 3 (Knowledge + Server Wiring — after Wave 2):
├── Task 10: MCP Resources (X3 knowledge base) [deep]
├── Task 11: MCP Prompts (guided workflows) [unspecified-high]
└── Task 12: MCP Server entry point + wiring [deep]
Wave 4 (Testing + Polish — after Wave 3):
├── Task 13: Unit tests for auth + client modules [unspecified-high]
├── Task 14: Integration tests against sandbox [deep]
└── Task 15: README + opencode.json MCP config [quick]
Wave FINAL (After ALL tasks — independent review, 4 parallel):
├── Task F1: Plan compliance audit (oracle)
├── Task F2: Code quality review (unspecified-high)
├── Task F3: Sandbox end-to-end QA (unspecified-high)
└── Task F4: Scope fidelity check (deep)
Critical Path: Task 1 → Task 3 → Task 4 → Task 6 → Task 12 → Task 14 → F1-F4
Parallel Speedup: ~60% faster than sequential
Max Concurrent: 5 (Wave 2)
Dependency Matrix
| Task | Depends On | Blocks |
|---|---|---|
| 1 | — | 2, 3, 4, 5-9, 10-12, 13-15 |
| 2 | 1 | 3, 4, 5-9 |
| 3 | 1, 2 | 5-9, 12, 13 |
| 4 | 1, 2 | 5-9, 12, 13 |
| 5 | 3, 4 | 12, 14 |
| 6 | 3, 4 | 12, 14 |
| 7 | 3, 4 | 12, 14 |
| 8 | 3, 4 | 12, 14 |
| 9 | 3, 4 | 12, 14 |
| 10 | 1 | 12 |
| 11 | 1 | 12 |
| 12 | 5-11 | 14, 15 |
| 13 | 3, 4 | — |
| 14 | 12 | F1-F4 |
| 15 | 12 | — |
Agent Dispatch Summary
- Wave 1: 4 tasks — T1
quick, T2quick, T3unspecified-high, T4unspecified-high - Wave 2: 5 tasks — T5
unspecified-high, T6deep, T7quick, T8unspecified-high, T9unspecified-high - Wave 3: 3 tasks — T10
deep, T11unspecified-high, T12deep - Wave 4: 3 tasks — T13
unspecified-high, T14deep, T15quick - FINAL: 4 tasks — F1
oracle, F2unspecified-high, F3unspecified-high, F4deep
CRITICAL: Planner Instructions (propagate to ALL agents)
- No git worktrees — do not use git worktrees under any circumstances
- Background task timeouts are in MILLISECONDS —
timeout=30000means 30 seconds, NOT 30 minutes. Usetimeout=120000for 2-minute operations. This applies to YOUR OWN sub-agents too. - Prefer background tasks with large timeouts — foreground tasks release after 10 minutes (but agent keeps running). Use background tasks with large timeouts instead.
TODOs
-
1. Project Scaffolding + Configuration
What to do:
- Initialize Bun project:
bun initwith TypeScript - Create
package.jsonwith dependencies:@modelcontextprotocol/sdk,zod,jose(for JWT) - Create
tsconfig.jsonwith strict mode, ES2022 target, module NodeNext - Create
src/config.ts— configuration module that reads from environment variables:SAGE_X3_URL— Base URL (defaults to demo endpointhttps://api-devna.dev-sagex3.com/demo/service/X3CLOUDV2_SEED/api)SAGE_X3_ENDPOINT— X3 endpoint name (e.g.,REPOSX3_REPOSX3), optional for sandboxSAGE_X3_CLIENT_ID— Connected app client ID, optional for sandboxSAGE_X3_SECRET— Connected app secret, optional for sandboxSAGE_X3_USER— X3 user for JWT sub claim, optional for sandboxSAGE_X3_TOKEN_LIFETIME— JWT lifetime in seconds (default 600)SAGE_X3_TLS_REJECT_UNAUTHORIZED— Whether to reject self-signed certs (default true, set false for on-premise dev)SAGE_X3_MODE—sandboxorauthenticated(auto-detect from presence of credentials)
- Create directory structure:
src/tools/,src/resources/,src/prompts/,src/auth/,src/graphql/,src/types/,tests/
Must NOT do:
- Do NOT add any tools/resources/prompts yet — just the project structure
- Do NOT use git worktrees
- Do NOT hardcode any credentials or URLs in source code
Recommended Agent Profile:
- Category:
quick - Skills: []
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave 1 (with Tasks 2, 3, 4)
- Blocks: Tasks 2-15
- Blocked By: None
References: Pattern References:
- Bun docs:
https://bun.sh/docs/quickstart— Bun project initialization - MCP SDK:
@modelcontextprotocol/sdkpackage on npm
External References:
josenpm package — JWT signing/verification for Bun (no native crypto dependency)zodnpm package — schema validation for MCP tool inputs
Acceptance Criteria:
bun installcompletes successfullybun run src/config.tsexecutes without errors- Directory structure exists: src/tools, src/resources, src/prompts, src/auth, src/graphql, src/types, tests
- Config module reads env vars and provides typed config object with defaults
QA Scenarios:
Scenario: Project builds and config loads defaults Tool: Bash Preconditions: Fresh project directory Steps: 1. Run `bun install` — expect exit code 0 2. Run `bun run src/config.ts` — expect exit code 0, no errors 3. Run `ls src/tools src/resources src/prompts src/auth src/graphql src/types tests` — expect all directories exist Expected Result: All commands succeed with exit code 0 Failure Indicators: Missing directories, import errors, unresolved dependencies Evidence: .sisyphus/evidence/task-1-scaffold.txt Scenario: Config defaults to sandbox mode when no credentials Tool: Bash Preconditions: No SAGE_X3_* env vars set Steps: 1. Create a temp test file that imports config and logs the mode 2. Run it with `bun run` — expect mode to be "sandbox" and URL to be the demo endpoint Expected Result: Config outputs sandbox mode with demo URL Evidence: .sisyphus/evidence/task-1-config-defaults.txtCommit: YES (groups with Wave 1)
- Message:
feat(core): scaffold project with auth and GraphQL client - Files:
package.json, tsconfig.json, bun.lock, src/config.ts, src/types/, src/auth/, src/graphql/
- Initialize Bun project:
-
2. TypeScript Types + Shared Constants
What to do:
- Create
src/types/x3.ts— Sage X3 specific types:X3Configinterface (url, endpoint, clientId, secret, user, tokenLifetime, mode, tlsRejectUnauthorized)X3GraphQLResponse<T>— Generic response wrapper{ data: T, extensions?: { diagnoses: any[] } }X3PageInfo—{ endCursor: string, hasNextPage: boolean, startCursor: string, hasPreviousPage: boolean }X3Edge<T>—{ node: T, cursor?: string }X3Connection<T>—{ edges: X3Edge<T>[], pageInfo?: X3PageInfo, totalCount?: number }X3QueryArgs—{ filter?: string, first?: number, after?: string, last?: number, before?: string, orderBy?: string }X3RootTypeenum:xtremX3Structure,xtremX3MasterData,xtremX3Products,xtremX3Purchasing,xtremX3StockX3ErrorResponse— error shape from API
- Create
src/types/index.ts— barrel export - Create
src/constants.ts— shared constants:DEMO_ENDPOINT_URL = "https://api-devna.dev-sagex3.com/demo/service/X3CLOUDV2_SEED/api"SANDBOX_EXPLORER_URL = "https://apidemo.sagex3.com/demo/service/X3CLOUDV2_SEED/explorer/"X3_ROOT_TYPESmap with descriptions for each root typeFILTER_OPERATORSlist:_eq, _neq, _gt, _gte, _lt, _lte, _in, _nin, _regex, _contains, _atLeast
Must NOT do:
- Do NOT add mutation-related types
- Do NOT over-engineer — keep types minimal and practical
Recommended Agent Profile:
- Category:
quick - Skills: []
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave 1 (with Tasks 1, 3, 4)
- Blocks: Tasks 3-9
- Blocked By: Task 1
References: API/Type References:
- Sage X3 GraphQL response shape: from browsed developer docs — edges/node pattern with pageInfo
- Filter operators: documented in "Fast track to GraphQL" page —
_eq, _gt, _gte, _lte, _regex, _atLeast - Root types:
xtremX3Structure, xtremX3MasterData, xtremX3Products, xtremX3Purchasing, xtremX3Stock
Acceptance Criteria:
- All types compile without errors
- Types accurately represent the X3 GraphQL API response shapes
- Constants match documented X3 API values
QA Scenarios:
Scenario: Types compile and are importable Tool: Bash Preconditions: Task 1 complete Steps: 1. Create temp file: `import { X3Config, X3GraphQLResponse, X3RootType } from './src/types'; console.log('OK');` 2. Run `bun run <temp_file>` — expect "OK" output Expected Result: Exit code 0, "OK" printed Evidence: .sisyphus/evidence/task-2-types-compile.txtCommit: YES (groups with Wave 1)
- Message:
feat(core): scaffold project with auth and GraphQL client - Files:
src/types/x3.ts, src/types/index.ts, src/constants.ts
- Create
-
3. Auth Module (JWT Generation + Sandbox Mode)
What to do:
- Create
src/auth/jwt.ts:generateJWT(config: X3Config): Promise<string>— Create JWT token usingjoselibrary- JWT claims:
iss=clientId,sub=user,aud='',iat=Math.floor(Date.now()/1000)-30,exp=iat+lifetime - Sign with HS256 using the secret
- Handle the 30-second clock skew offset (documented in X3 quick start)
- Create
src/auth/index.ts:getAuthHeaders(config: X3Config): Promise<Record<string, string>>— Returns headers for API calls- If sandbox mode: return only
Content-Type: application/json - If authenticated mode: return
Authorization: Bearer <jwt>,x-xtrem-endpoint: <endpoint>,Content-Type: application/json - Regenerate JWT on each call (they're short-lived, and generation is fast)
Must NOT do:
- Do NOT implement OAuth2 authorization code flow (complex, out of scope)
- Do NOT cache tokens (they're cheap to generate and short-lived)
- Do NOT hardcode any credentials
Recommended Agent Profile:
- Category:
unspecified-high - Skills: []
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave 1 (with Tasks 1, 2, 4)
- Blocks: Tasks 5-9, 12, 13
- Blocked By: Tasks 1, 2
References: Pattern References:
- X3 JWT creation code from developer docs (browsed via Playwright):
sign(token, secret)with claimsiss, sub, aud, iat, exp - Clock skew offset:
Math.floor(Date.now() / 1000) - 30— documented in X3 quick start Node.js example x-xtrem-endpointheader: required for authenticated calls, value is X3 endpoint name (e.g.,REPOSX3_REPOSX3)
External References:
joselibrary:https://github.com/panva/jose— Bun-compatible JWT library (uses Web Crypto API)
Acceptance Criteria:
generateJWT()produces a valid JWT string decodable by jwt.iogetAuthHeaders()returns correct headers for sandbox mode (no auth header)getAuthHeaders()returns correct headers for authenticated mode (Bearer token + endpoint)- JWT contains correct claims (iss, sub, aud, iat, exp)
QA Scenarios:
Scenario: JWT generation produces valid token Tool: Bash Preconditions: jose package installed Steps: 1. Create test script that calls generateJWT with test config (clientId: "test", secret: "testsecret123456789012345678901234", user: "admin", lifetime: 600) 2. Decode the JWT payload (base64 decode middle segment) 3. Assert: iss === "test", sub === "admin", aud === "", exp > iat Expected Result: Valid JWT with correct claims Evidence: .sisyphus/evidence/task-3-jwt-generation.txt Scenario: Sandbox mode headers have no auth Tool: Bash Preconditions: Config in sandbox mode Steps: 1. Call getAuthHeaders with sandbox config (no clientId/secret) 2. Assert: no Authorization header present 3. Assert: Content-Type header is "application/json" Expected Result: Headers contain only Content-Type, no Authorization Evidence: .sisyphus/evidence/task-3-sandbox-headers.txtCommit: YES (groups with Wave 1)
- Message:
feat(core): scaffold project with auth and GraphQL client - Files:
src/auth/jwt.ts, src/auth/index.ts
- Create
-
4. GraphQL HTTP Client
What to do:
- Create
src/graphql/client.ts:executeGraphQL(config: X3Config, query: string, variables?: Record<string, unknown>): Promise<X3GraphQLResponse<unknown>>— Core HTTP client- Uses Bun's built-in
fetch(no axios needed) - POST to
config.urlwith JSON body{ query, variables } - Includes auth headers from auth module
- Handles TLS: if
config.tlsRejectUnauthorized === false, configure fetch to accept self-signed certs - Error handling:
- 400: Parse GraphQL errors, return structured error with suggestions (e.g., "Did you mean X?")
- 401: Clear error about expired/missing token with instructions to check credentials
- 500: Generic server error with the
$messageand$sourcefrom response - Network errors: Timeout, DNS, connection refused — helpful messages
- Mutation guard: If query string starts with
mutation(case-insensitive, trimmed), throw error "Mutations are not supported. This MCP server is read-only."
- Create
src/graphql/index.ts— barrel export
Must NOT do:
- Do NOT use axios or node-fetch — use Bun's built-in fetch
- Do NOT allow mutations to pass through
- Do NOT retry failed requests automatically (let the agent decide)
Recommended Agent Profile:
- Category:
unspecified-high - Skills: []
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave 1 (with Tasks 1, 2, 3)
- Blocks: Tasks 5-9, 12, 13
- Blocked By: Tasks 1, 2
References: Pattern References:
- X3 GraphQL call pattern from developer docs: POST with
{ query: "...", variables: null }body - Error response shapes from API Error Codes page: 400
{"errors":[...]}, 401{"message":"Unauthorised"}, 500{"$severity":"error","$dataCode":"InternalServerError"}
External References:
- Bun fetch API:
https://bun.sh/docs/api/fetch— built-in, supports TLS options viatlsoption
Acceptance Criteria:
- Successfully queries sandbox demo endpoint and returns valid JSON
- Rejects mutation queries with clear error message
- Returns structured error for 400/401/500 responses
- Works with self-signed certs when configured
QA Scenarios:
Scenario: Query sandbox endpoint successfully Tool: Bash Preconditions: Sandbox endpoint accessible Steps: 1. Create test script calling executeGraphQL with sandbox config and query: `{ xtremX3MasterData { businessPartner { query(first: 2) { edges { node { code } } } } } }` 2. Run script, capture output 3. Assert: response contains `data.xtremX3MasterData.businessPartner.query.edges` array 4. Assert: at least 1 edge with a `node.code` string value Expected Result: Valid JSON response with business partner data Evidence: .sisyphus/evidence/task-4-sandbox-query.txt Scenario: Mutation query rejected Tool: Bash Preconditions: Client module loaded Steps: 1. Call executeGraphQL with query: `mutation { xtremX3Stock { ... } }` 2. Assert: throws error containing "read-only" or "mutations are not supported" Expected Result: Error thrown, mutation blocked Evidence: .sisyphus/evidence/task-4-mutation-blocked.txtCommit: YES (groups with Wave 1)
- Message:
feat(core): scaffold project with auth and GraphQL client - Files:
src/graphql/client.ts, src/graphql/index.ts
- Create
-
5. Tool: introspect_schema
What to do:
- Create
src/tools/introspect-schema.ts:- MCP tool
introspect_schema— Discovers available GraphQL types, fields, and their descriptions from the X3 schema - Input schema (Zod):
{ depth?: number (default 2), typeName?: string (optional — introspect specific type) } - If no typeName: Run GraphQL introspection query
{ __schema { queryType { name } types { name kind description fields { name type { name kind ofType { name kind } } description } } } }— filter to only showxtrem*types (not internal GraphQL types) - If typeName provided: Run focused introspection
{ __type(name: "typeName") { name kind description fields { name description type { name kind ofType { name kind ofType { name kind } } } } } } - Format output clearly: type name, description, list of fields with their types
- This is the agent's "discovery" tool — it should learn what's available before querying
- MCP tool
Must NOT do:
- Do NOT return the full introspection dump (too large) — filter and format it
- Do NOT include mutation types in the output
Recommended Agent Profile:
- Category:
unspecified-high - Skills: []
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave 2 (with Tasks 6, 7, 8, 9)
- Blocks: Task 12, 14
- Blocked By: Tasks 3, 4
References: Pattern References:
- GraphQL introspection: standard
__schemaand__typequeries - X3 root types:
xtremX3Structure, xtremX3MasterData, xtremX3Products, xtremX3Purchasing, xtremX3Stock
External References:
- GraphQL introspection spec:
https://graphql.org/learn/introspection/ - MCP tool registration:
server.registerTool(name, { description, inputSchema }, handler)from SDK
Acceptance Criteria:
- Returns list of X3 root types when called without args
- Returns fields for a specific type when typeName provided
- Filters out internal GraphQL types (__Schema, __Type, etc.)
- Output is formatted for LLM readability
QA Scenarios:
Scenario: Discover root types from sandbox Tool: Bash Preconditions: Sandbox endpoint accessible Steps: 1. Call introspect_schema tool with no args (default depth 2) 2. Assert: output mentions at least 3 of the 5 root types (xtremX3MasterData, xtremX3Products, etc.) 3. Assert: no __Schema or __Type in output Expected Result: Clean list of X3 business types with descriptions Evidence: .sisyphus/evidence/task-5-introspect-root.txt Scenario: Introspect specific type Tool: Bash Preconditions: Sandbox endpoint accessible Steps: 1. Call introspect_schema with typeName from a discovered type 2. Assert: output contains field names and their types Expected Result: Detailed field list for the specified type Evidence: .sisyphus/evidence/task-5-introspect-type.txtCommit: YES (groups with Wave 2)
- Message:
feat(tools): add all 5 MCP query tools - Files:
src/tools/introspect-schema.ts
- Create
-
6. Tool: query_entities (primary query tool)
What to do:
- Create
src/tools/query-entities.ts:- MCP tool
query_entities— Query/search Sage X3 entities with filtering, sorting, and pagination - This is the primary workhorse tool — should handle most agent queries
- Input schema (Zod):
rootType: z.enum([...X3_ROOT_TYPES]).describe("X3 root type (e.g., xtremX3MasterData)")— requiredentity: z.string().describe("Entity name under the root type (e.g., businessPartner, product)")— requiredfields: z.array(z.string()).describe("Fields to return (e.g., ['code', 'description1', 'isActive'])")— requiredfilter: z.string().optional().describe("Filter string in X3 format: \"{code: { _eq: 'ABC' }}\"")first: z.number().optional().describe("Number of results to return (pagination)")after: z.string().optional().describe("Cursor for forward pagination")last: z.number().optional().describe("Number of results from end")before: z.string().optional().describe("Cursor for backward pagination")orderBy: z.string().optional().describe("Sort: \"{fieldName: 1}\" for asc, \"{fieldName: -1}\" for desc")includePageInfo: z.boolean().optional().default(true).describe("Include pagination info")includeTotalCount: z.boolean().optional().default(false).describe("Include total count")
- Build GraphQL query string from inputs:
{ ${rootType} { ${entity} { query(${args}) { edges { node { ${fields} } } ${pageInfo} ${totalCount} } } } } - Handle nested fields: if a field contains
., treat as nested (e.g.,productCategory.code→productCategory { code }) - Return formatted results with pagination info
- MCP tool
Must NOT do:
- Do NOT allow mutation queries
- Do NOT hardcode entity names — let the agent discover them via introspection
Recommended Agent Profile:
- Category:
deep - Skills: []
- Reason: This is the most complex tool with query building, nested field handling, and pagination
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave 2 (with Tasks 5, 7, 8, 9)
- Blocks: Task 12, 14
- Blocked By: Tasks 3, 4
References: Pattern References:
- X3 query pattern from "Fast track to GraphQL":
{ xtremX3MasterData { businessPartner { query(filter: "...", first: 5) { edges { node { code isActive } } pageInfo { ... } } } } } - Nested fields:
productCategory { code }pattern from products examples - Filter examples:
"{code: 'AE003'}","{productCategory: 'SUBCO'}","{description1:{_regex:'.*standard.*'}}" - Aliases example:
subcoProduct: query(filter: ...)for multiple queries on same entity - Pagination:
first: 5, after: "[\"AE020\"]#73"cursor format
Acceptance Criteria:
- Successfully queries business partners from sandbox with fields
code, isActive - Filter by code works:
{code: 'AE003'}returns matching results - Pagination works: first:5 returns exactly 5 results with pageInfo
- Nested fields work: requesting
productCategory.codebuilds correct nested query - Sorting works: orderBy
{code: -1}returns descending order
QA Scenarios:
Scenario: Query business partners with filter Tool: Bash Preconditions: Sandbox endpoint accessible Steps: 1. Call query_entities with rootType: "xtremX3MasterData", entity: "businessPartner", fields: ["code", "isActive"], first: 5 2. Assert: response contains edges array with ≤5 items 3. Assert: each node has `code` and `isActive` fields 4. Assert: pageInfo is present with hasNextPage boolean Expected Result: 5 business partners with code and isActive fields Evidence: .sisyphus/evidence/task-6-query-entities.txt Scenario: Query with filter returns filtered results Tool: Bash Preconditions: Sandbox accessible Steps: 1. Call query_entities with filter: "{code: 'AE003'}", entity: "businessPartner", rootType: "xtremX3MasterData", fields: ["code", "isActive"] 2. Assert: all returned nodes have code containing "AE003" Expected Result: Filtered results matching the code Evidence: .sisyphus/evidence/task-6-query-filtered.txtCommit: YES (groups with Wave 2)
- Message:
feat(tools): add all 5 MCP query tools - Files:
src/tools/query-entities.ts
- Create
-
7. Tool: read_entity (get by ID)
What to do:
- Create
src/tools/read-entity.ts:- MCP tool
read_entity— Read a single Sage X3 entity by its identifier - Input schema (Zod):
rootType: z.enum([...X3_ROOT_TYPES])— requiredentity: z.string()— requiredid: z.string().describe("Entity identifier (e.g., 'AE003')")— requiredfields: z.array(z.string()).describe("Fields to return")— required
- Build GraphQL:
{ ${rootType} { ${entity} { read(_id: "${id}") { ${fields} } } } } - Handle nested fields same as query_entities
- Return single entity result (not wrapped in edges/node)
- MCP tool
Must NOT do:
- Do NOT add mutation capabilities
- Do NOT wrap result in edges/node — read returns direct fields
Recommended Agent Profile:
- Category:
quick - Skills: []
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave 2 (with Tasks 5, 6, 8, 9)
- Blocks: Task 12, 14
- Blocked By: Tasks 3, 4
References: Pattern References:
- X3 read pattern:
{ xtremX3MasterData { businessPartner { read(_id: "AE003") { isActive } } } } - From "Fast track to GraphQL" — read is for single entity by identifier, better performance than filter
Acceptance Criteria:
- Reads a single business partner by code from sandbox
- Returns direct fields (not edges/node wrapped)
- Returns clear error for non-existent ID
QA Scenarios:
Scenario: Read entity by ID Tool: Bash Preconditions: Sandbox accessible, known business partner code exists Steps: 1. Call read_entity with rootType: "xtremX3MasterData", entity: "businessPartner", id: "AE003", fields: ["code", "isActive"] 2. Assert: response contains code and isActive directly (not in edges/node) Expected Result: Single entity with requested fields Evidence: .sisyphus/evidence/task-7-read-entity.txtCommit: YES (groups with Wave 2)
- Message:
feat(tools): add all 5 MCP query tools - Files:
src/tools/read-entity.ts
- Create
-
8. Tool: aggregate_entities
What to do:
- Create
src/tools/aggregate-entities.ts:- MCP tool
aggregate_entities— Run aggregation queries on Sage X3 entities (min, max, count, distinctCount) - Input schema (Zod):
rootType: z.enum([...X3_ROOT_TYPES])— requiredentity: z.string()— requiredaggregateFields: z.array(z.object({ field: z.string(), operations: z.array(z.enum(["min", "max", "count", "distinctCount"])) }))— requiredfilter: z.string().optional()
- Build GraphQL:
{ ${rootType} { ${entity} { readAggregate(filter: "${filter}") { ${fieldAggregations} } } } } - Example output:
{ productWeight: { min: 0.5, max: 25.0 }, stockUnit: { code: { distinctCount: 12 } } }
- MCP tool
Must NOT do:
- Do NOT add write aggregations
- Do NOT combine with regular queries in same call
Recommended Agent Profile:
- Category:
unspecified-high - Skills: []
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave 2 (with Tasks 5, 6, 7, 9)
- Blocks: Task 12, 14
- Blocked By: Tasks 3, 4
References: Pattern References:
- X3 aggregation from "Fast track to GraphQL":
readAggregate(filter: "{description1:{_regex:'.*standard.*'}}") { productWeight { min max } stockUnit { code { distinctCount } } }
Acceptance Criteria:
- Aggregation query returns min/max/distinctCount values from sandbox
- Filter parameter works with aggregation
- Output is clearly formatted
QA Scenarios:
Scenario: Aggregate product data Tool: Bash Preconditions: Sandbox accessible Steps: 1. Call aggregate_entities on xtremX3Products, entity: "product", aggregateFields for a numeric field 2. Assert: response contains aggregation results (numbers, not null) Expected Result: Aggregation values returned Evidence: .sisyphus/evidence/task-8-aggregate.txtCommit: YES (groups with Wave 2)
- Message:
feat(tools): add all 5 MCP query tools - Files:
src/tools/aggregate-entities.ts
- Create
-
9. Tool: execute_graphql (raw query)
What to do:
- Create
src/tools/execute-graphql.ts:- MCP tool
execute_graphql— Execute a raw GraphQL query string against the X3 API - This is the escape hatch for advanced queries (aliases, fragments, variables, directives) that the structured tools can't handle
- Input schema (Zod):
query: z.string().describe("Full GraphQL query string")— requiredvariables: z.record(z.unknown()).optional().describe("GraphQL variables object")
- Validate query doesn't start with
mutation(case-insensitive) — reject if so - Pass through to GraphQL client, return raw response
- Include a note in the tool description: "Use this for complex queries with aliases, fragments, variables, or directives. For simple queries, prefer query_entities or read_entity."
- MCP tool
Must NOT do:
- Do NOT allow mutations — must validate and reject
- Do NOT modify the query string — pass through as-is (after validation)
Recommended Agent Profile:
- Category:
unspecified-high - Skills: []
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave 2 (with Tasks 5, 6, 7, 8)
- Blocks: Task 12, 14
- Blocked By: Tasks 3, 4
References: Pattern References:
- Complex query patterns from "Fast track to GraphQL": aliases, fragments, variables, directives
- Variables example:
query ProductsInCategory($productFilter: String = "...") { ... }with variables dict
Acceptance Criteria:
- Executes raw GraphQL query and returns response
- Rejects mutation queries with clear error
- Supports variables parameter
- Returns raw response (no transformation)
QA Scenarios:
Scenario: Execute raw query with variables Tool: Bash Preconditions: Sandbox accessible Steps: 1. Call execute_graphql with a query using variables (e.g., `query ($f: String) { xtremX3MasterData { businessPartner { query(first: 2, filter: $f) { edges { node { code } } } } } }` and variables: {}) 2. Assert: response contains data Expected Result: Valid GraphQL response Evidence: .sisyphus/evidence/task-9-raw-query.txt Scenario: Mutation rejected Tool: Bash Steps: 1. Call execute_graphql with query starting with "mutation { ... }" 2. Assert: error thrown about mutations not supported Expected Result: Error rejecting mutation Evidence: .sisyphus/evidence/task-9-mutation-rejected.txtCommit: YES (groups with Wave 2)
- Message:
feat(tools): add all 5 MCP query tools - Files:
src/tools/execute-graphql.ts
- Create
-
10. MCP Resources (Sage X3 Knowledge Base)
What to do:
- Create
src/resources/index.ts— Register all MCP resources - Create
src/resources/knowledge/directory with embedded knowledge content as TypeScript string constants (to be served as MCP resources) - Resource 1:
sage-x3://knowledge/overview— "Sage X3 Overview"- What Sage X3 is (ERP system), core modules, key terminology
- How the GraphQL API works: endpoint structure, authentication, headers
- Root types and what they contain: xtremX3Structure (countries, addresses, sites, companies), xtremX3MasterData (business partners, customers), xtremX3Products (products, categories), xtremX3Purchasing (purchase orders), xtremX3Stock (stock, receipts)
- Resource 2:
sage-x3://knowledge/query-patterns— "Query Patterns Guide"- How to build queries: the
{ rootType { entity { query(...) { edges { node { ... } } } } } }pattern - Read by ID:
read(_id: "...")— when to use vs query - Aggregation:
readAggregate(filter)with min/max/distinctCount - Aliases for multiple queries on same entity
- Fragments for reusable field sets
- Variables for parameterized queries
- Directives: @include and @skip
- Include concrete code examples for each pattern
- How to build queries: the
- Resource 3:
sage-x3://knowledge/filter-syntax— "Filter Syntax Reference"- Complete filter syntax: string-based JSON format
- All operators with examples: _eq, _neq, _gt, _gte, _lt, _lte, _in, _nin, _regex, _contains, _atLeast
- Nested filtering examples
- Combining multiple conditions
- Regex patterns
- Resource 4:
sage-x3://knowledge/pagination-sorting— "Pagination & Sorting Guide"- Relay cursor-based pagination: first/after (forward), last/before (backward)
- pageInfo fields: endCursor, hasNextPage, startCursor, hasPreviousPage
- totalCount
- Sorting with orderBy:
{field: 1}(asc),{field: -1}(desc) - Multi-field sorting
- Cursor format explanation
- Resource 5:
sage-x3://knowledge/error-codes— "API Error Codes"- HTTP 400: Bad query (missing field, wrong type) with "Did you mean?" suggestions
- HTTP 401: Unauthorized — token expired or missing
- HTTP 500: Internal server error
- Common troubleshooting steps
Resources should be registered using the MCP SDK resource pattern with clear URIs and descriptions.
Must NOT do:
- Do NOT include mutation documentation (read-only scope)
- Do NOT include OAuth2 cloud flow documentation (out of scope)
- Do NOT make resources overly verbose — concise, actionable knowledge
Recommended Agent Profile:
- Category:
deep - Skills: []
- Reason: Content-heavy task requiring accurate domain knowledge transcription from research
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave 3 (with Tasks 11, 12)
- Blocks: Task 12
- Blocked By: Task 1
References: External References:
- All content must be derived from the Sage X3 developer docs browsed via Playwright:
- GraphQL overview:
https://developer.sage.com/x3/docs/latest/guides/graphql - Fast track to GraphQL:
https://developer.sage.com/x3/docs/latest/guides/graphql/guides/how-to— queries, filtering, sorting, pagination, aliases, fragments, variables, directives, aggregation - Examples page:
https://developer.sage.com/x3/docs/latest/guides/graphql/examples— entity-specific query examples - API Error Codes:
https://developer.sage.com/x3/docs/latest/guides/graphql/guides/api-error-codes— 400/401/500 codes - Authentication:
https://developer.sage.com/x3/docs/latest/guides/graphql/quick-start/create-connected-app— JWT setup
- GraphQL overview:
WHY Each Reference Matters:
- These are the ONLY authoritative source for X3 GraphQL API patterns. The developer site blocks webfetch (403), so content was extracted via Playwright during planning. The knowledge resources MUST accurately reflect this documented behavior.
Acceptance Criteria:
- 5 resources registered with proper URIs
- Each resource returns meaningful content when requested
- Content is accurate to X3 GraphQL API documentation
- Content includes concrete code examples, not just descriptions
- No mutation documentation present
QA Scenarios:
Scenario: Resources list returns all 5 resources Tool: Bash Steps: 1. Import resource registration module 2. Verify 5 resources are exported with correct URIs 3. Verify each resource has a name and description Expected Result: 5 resources with sage-x3:// URIs Evidence: .sisyphus/evidence/task-10-resources-list.txt Scenario: Query patterns resource contains examples Tool: Bash Steps: 1. Read the query-patterns resource content 2. Assert: contains "edges" and "node" (relay pattern) 3. Assert: contains "readAggregate" (aggregation) 4. Assert: does NOT contain "mutation" Expected Result: Comprehensive query patterns with no mutation content Evidence: .sisyphus/evidence/task-10-query-patterns.txtCommit: YES (groups with Wave 3)
- Message:
feat(server): wire MCP server with resources and prompts - Files:
src/resources/index.ts, src/resources/knowledge/*.ts
- Create
-
11. MCP Prompts (Guided Workflows)
What to do:
- Create
src/prompts/index.ts— Register MCP prompts for guided agent workflows - Prompt 1:
search-entities— "Search for entities in Sage X3"- Args:
entity(string, with autocompletion for common entities),criteria(string, what to search for) - Generates a user message guiding the agent through: (1) use introspect_schema to find the right root type and entity, (2) use query_entities with appropriate filter, (3) interpret results
- Args:
- Prompt 2:
lookup-entity— "Look up a specific entity by ID"- Args:
entity(string),id(string) - Generates a message for read_entity usage
- Args:
- Prompt 3:
explore-data— "Explore what data is available in Sage X3"- No args
- Generates a message guiding the agent to: (1) introspect schema, (2) list available root types, (3) drill into entities
- Prompt 4:
analyze-data— "Analyze entity data with aggregation"- Args:
entity(string),question(string, what to analyze) - Generates a message combining aggregation + query tools
- Args:
Each prompt should include context about Sage X3 domain and suggest which tools to use.
Must NOT do:
- Do NOT include prompts for mutations or data modification
- Do NOT make prompts too prescriptive — guide, don't dictate
Recommended Agent Profile:
- Category:
unspecified-high - Skills: []
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave 3 (with Tasks 10, 12)
- Blocks: Task 12
- Blocked By: Task 1
References: Pattern References:
- MCP prompt registration from SDK:
server.registerPrompt(name, { description, argsSchema }, handler)returning{ messages: [{ role, content }] } reactive-resumepattern: prompts in separate-helpers/prompts.tsfile
Acceptance Criteria:
- 4 prompts registered with clear descriptions
- Each prompt generates well-structured messages
- Prompts reference appropriate tools
- No mutation-related prompts
QA Scenarios:
Scenario: Prompts generate valid messages Tool: Bash Steps: 1. Import prompt registration module 2. Call each prompt handler with sample args 3. Assert: each returns { messages: [...] } with valid content Expected Result: 4 prompts returning properly structured messages Evidence: .sisyphus/evidence/task-11-prompts.txtCommit: YES (groups with Wave 3)
- Message:
feat(server): wire MCP server with resources and prompts - Files:
src/prompts/index.ts
- Create
-
12. MCP Server Entry Point + Wiring
What to do:
- Create
src/index.ts— Main MCP server entry point:- Import and create
McpServerinstance with namesage-x3-graphqland version from package.json - Register all 5 tools (import from
src/tools/) - Register all 5 resources (import from
src/resources/) - Register all 4 prompts (import from
src/prompts/) - Create
StdioServerTransportand connect - Load config at startup and validate (check if sandbox or authenticated mode)
- Log startup mode to stderr (not stdout — stdio transport uses stdout)
- Handle graceful shutdown on SIGINT/SIGTERM
- Import and create
- Update
package.json:- Add
"bin"field pointing tosrc/index.ts - Add scripts:
"start": "bun run src/index.ts","build": "bun build src/index.ts --outdir dist --target bun"
- Add
- Create
src/tools/index.ts— barrel export for all tool registration functions - Wire everything together so the server is fully functional with one command:
bun run src/index.ts
Must NOT do:
- Do NOT log to stdout (that's the MCP transport channel) — use stderr for diagnostics
- Do NOT add HTTP/SSE transport (stdio only for this version)
- Do NOT add health check endpoints
Recommended Agent Profile:
- Category:
deep - Skills: []
- Reason: Integration task requiring all modules to work together correctly
Parallelization:
- Can Run In Parallel: NO
- Parallel Group: Sequential (after Wave 2 + Tasks 10, 11)
- Blocks: Tasks 13, 14, 15
- Blocked By: Tasks 5-11
References: Pattern References:
- MCP server creation:
new McpServer({ name: 'sage-x3-graphql', version: '1.0.0' })from SDK - Transport:
new StdioServerTransport()thenserver.connect(transport) reactive-resumepattern: separate registration files for tools/resources/prompts, wired in main
Acceptance Criteria:
bun run src/index.tsstarts without errors- MCP
initializehandshake succeeds when JSON-RPC message sent via stdin tools/listreturns all 5 toolsresources/listreturns all 5 resourcesprompts/listreturns all 4 prompts- Server shuts down cleanly on SIGINT
QA Scenarios:
Scenario: MCP server initializes and lists tools Tool: Bash Preconditions: All tools, resources, prompts implemented Steps: 1. Start server: `echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}' | bun run src/index.ts` 2. Assert: response contains `{"jsonrpc":"2.0","id":1,"result":{...}}` with server info 3. Send tools/list request and verify 5 tools returned Expected Result: Server initializes and responds to JSON-RPC Evidence: .sisyphus/evidence/task-12-server-init.txt Scenario: Server rejects invalid JSON-RPC Tool: Bash Steps: 1. Send malformed JSON to server stdin 2. Assert: server responds with JSON-RPC error, does not crash Expected Result: Graceful error handling Evidence: .sisyphus/evidence/task-12-error-handling.txtCommit: YES (groups with Wave 3)
- Message:
feat(server): wire MCP server with resources and prompts - Files:
src/index.ts, src/tools/index.ts, package.json
- Create
-
13. Unit Tests for Auth + Client Modules
What to do:
- Create
tests/auth.test.ts:- Test JWT generation: verify claims (iss, sub, aud, iat, exp), verify signature, verify clock skew offset
- Test getAuthHeaders: sandbox mode returns no auth header, authenticated mode returns Bearer token + endpoint header
- Test with missing credentials: should default to sandbox mode
- Create
tests/graphql-client.test.ts:- Test mutation guard: verify
mutation { ... }queries are rejected - Test request formatting: verify correct POST body shape, headers
- Test error parsing: mock 400/401/500 responses and verify error messages
- Integration test: query sandbox endpoint, verify response shape
- Test mutation guard: verify
- Create
tests/config.test.ts:- Test default config (sandbox mode)
- Test config with all env vars set (authenticated mode)
- Test auto-detection of mode
Must NOT do:
- Do NOT mock the sandbox endpoint for integration tests — use the real demo endpoint
- Do NOT test tools in this task — those are tested in Task 14
Recommended Agent Profile:
- Category:
unspecified-high - Skills: []
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave 4 (with Tasks 14, 15)
- Blocks: None
- Blocked By: Tasks 3, 4
References: Test References:
- Bun test docs:
https://bun.sh/docs/cli/test—bun test,describe,it,expect - JWT validation: decode base64 middle segment, verify claims
Acceptance Criteria:
bun test tests/auth.test.tspasses all testsbun test tests/graphql-client.test.tspasses all testsbun test tests/config.test.tspasses all tests- At least 1 integration test against real sandbox endpoint
QA Scenarios:
Scenario: All unit tests pass Tool: Bash Steps: 1. Run `bun test tests/auth.test.ts tests/graphql-client.test.ts tests/config.test.ts` 2. Assert: all tests pass, 0 failures Expected Result: All tests pass Evidence: .sisyphus/evidence/task-13-unit-tests.txtCommit: YES (groups with Wave 4)
- Message:
test: add unit and integration tests against sandbox - Files:
tests/auth.test.ts, tests/graphql-client.test.ts, tests/config.test.ts
- Create
-
14. Integration Tests Against Sandbox
What to do:
- Create
tests/integration.test.ts:- Test each of the 5 tools against the live sandbox endpoint:
introspect_schema— verify returns X3 root typesquery_entities— query business partners with first:5, verify edges/node shapequery_entitieswith filter — filter by a known code, verify filtered resultsread_entity— read a specific business partner by codeaggregate_entities— aggregate product dataexecute_graphql— run a raw query with aliasesexecute_graphqlmutation rejection — verify mutation blocked
- Each test should:
- Use the real sandbox demo endpoint (unauthenticated)
- Verify response shape matches expected types
- Handle the 20-result limit gracefully
- Set reasonable timeout (10 seconds per test)
- Test each of the 5 tools against the live sandbox endpoint:
- Create
tests/mcp-server.test.ts:- Test the MCP server end-to-end:
- Start server process
- Send initialize JSON-RPC message
- List tools, resources, prompts
- Call a tool (query_entities) and verify response
- Verify no mutation tools exist
- Test the MCP server end-to-end:
Must NOT do:
- Do NOT skip tests if sandbox is unreachable — mark as failed with clear message
- Do NOT test against authenticated endpoints (we don't have credentials)
Recommended Agent Profile:
- Category:
deep - Skills: []
- Reason: Integration tests need careful handling of async operations, process management, and real API responses
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave 4 (with Tasks 13, 15)
- Blocks: F1-F4
- Blocked By: Task 12
References: API/Type References:
- Demo endpoint:
https://api-devna.dev-sagex3.com/demo/service/X3CLOUDV2_SEED/api - Max 20 results per query on demo endpoint
- Known entities: businessPartner in xtremX3MasterData, product in xtremX3Products
Acceptance Criteria:
bun test tests/integration.test.tspasses all tests against sandboxbun test tests/mcp-server.test.tspasses MCP protocol tests- All 5 tools verified against live sandbox
- Mutation rejection verified
QA Scenarios:
Scenario: Full integration test suite passes Tool: Bash Steps: 1. Run `bun test tests/integration.test.ts --timeout 30000` 2. Assert: all tests pass 3. Run `bun test tests/mcp-server.test.ts --timeout 30000` 4. Assert: all tests pass Expected Result: All integration tests pass against live sandbox Evidence: .sisyphus/evidence/task-14-integration-tests.txtCommit: YES (groups with Wave 4)
- Message:
test: add unit and integration tests against sandbox - Files:
tests/integration.test.ts, tests/mcp-server.test.ts
- Create
-
15. README + opencode.json MCP Configuration
What to do:
- Create
README.md:- Project name and description
- Quick start: how to install, configure, and run
- Configuration: all env vars documented with examples
- Available tools: list with descriptions and example inputs
- Available resources: list with URIs
- Available prompts: list with descriptions
- Usage with MCP clients: how to add to opencode.json, Claude Desktop, etc.
- Sandbox mode: how to use for testing
- Example MCP client config snippet
- Update
opencode.jsonto include the sage-x3-graphql MCP server:{ "mcp": { "sage-x3-graphql": { "type": "local", "command": ["bun", "run", "src/index.ts"], "enabled": true } } } - Ensure the server can be started directly by MCP clients
Must NOT do:
- Do NOT include credentials in README examples — use placeholder values
- Do NOT over-document — keep it concise and practical
Recommended Agent Profile:
- Category:
quick - Skills: []
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave 4 (with Tasks 13, 14)
- Blocks: None
- Blocked By: Task 12
References: Pattern References:
- Existing opencode.json in project root — already has playwright MCP config as example
Acceptance Criteria:
- README.md contains all sections listed above
- opencode.json has sage-x3-graphql MCP server configured
- No real credentials in any file
QA Scenarios:
Scenario: README exists and opencode.json is valid Tool: Bash Steps: 1. Verify README.md exists and contains "sage-x3-graphql" 2. Run `bun run -e "console.log(JSON.parse(require('fs').readFileSync('opencode.json','utf8')))"` to verify valid JSON 3. Verify opencode.json contains sage-x3-graphql MCP config Expected Result: Both files valid and complete Evidence: .sisyphus/evidence/task-15-readme-config.txtCommit: YES (groups with Wave 4)
- Message:
test: add unit and integration tests against sandbox - Files:
README.md, opencode.json
- Create
Final Verification Wave (MANDATORY — after ALL implementation tasks)
4 review agents run in PARALLEL. ALL must APPROVE. Rejection → fix → re-run.
-
F1. Plan Compliance Audit —
oracleRead the plan end-to-end. For each "Must Have": verify implementation exists (read file, curl endpoint, run command). For each "Must NOT Have": search codebase for forbidden patterns (mutation keywords in tools, hardcoded credentials, git worktree usage) — reject with file:line if found. Check evidence files exist in .sisyphus/evidence/. Compare deliverables against plan. Output:Must Have [N/N] | Must NOT Have [N/N] | Tasks [N/N] | VERDICT: APPROVE/REJECT -
F2. Code Quality Review —
unspecified-highRunbun run build+bun test. Review all files for:as any/@ts-ignore, empty catches, console.log in prod, commented-out code, unused imports. Check AI slop: excessive comments, over-abstraction, generic names. Verify Zod schemas match actual API contracts. Output:Build [PASS/FAIL] | Tests [N pass/N fail] | Files [N clean/N issues] | VERDICT -
F3. Sandbox End-to-End QA —
unspecified-highStart MCP server via stdio. Send JSON-RPCinitialize,tools/list,resources/list,prompts/list. Call each tool against sandbox. Verify responses contain valid X3 data. Test error cases (bad filter, unknown entity). Save evidence. Output:Tools [N/N pass] | Resources [N/N] | Prompts [N/N] | Error handling [N/N] | VERDICT -
F4. Scope Fidelity Check —
deepVerify: (1) NO mutations exist anywhere — grep formutationin tool handlers. (2) All 5 tools registered. (3) Knowledge resources contain accurate X3 information. (4) Auth supports both modes. (5) No scope creep beyond plan. Output:Read-only [PASS/FAIL] | Tools [5/5] | Knowledge [PASS/FAIL] | Auth [PASS/FAIL] | VERDICT
Commit Strategy
- After Wave 1:
feat(core): scaffold project with auth and GraphQL client— package.json, tsconfig.json, src/types/, src/auth/, src/graphql/, src/config.ts - After Wave 2:
feat(tools): add all 5 MCP query tools— src/tools/*.ts - After Wave 3:
feat(server): wire MCP server with resources and prompts— src/index.ts, src/resources/, src/prompts/ - After Wave 4:
test: add unit and integration tests against sandbox— tests/*.test.ts, README.md - After Final:
chore: final polish after review— any fixes from F1-F4
Success Criteria
Verification Commands
bun run build # Expected: clean build, no errors
bun test # Expected: all tests pass
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}' | bun run src/index.ts # Expected: valid initialize response
curl -s -X POST https://api-devna.dev-sagex3.com/demo/service/X3CLOUDV2_SEED/api -H 'Content-Type: application/json' -d '{"query":"{ xtremX3MasterData { businessPartner { query(first: 2) { edges { node { code } } } } } }"}' # Expected: JSON with business partner codes
Final Checklist
- All "Must Have" items present
- All "Must NOT Have" items absent (especially: no mutations)
- All 5 tools working against sandbox
- Knowledge resources accurate and comprehensive
- Auth module supports both sandbox and JWT modes
- All tests pass
- MCP server starts cleanly via stdio