# 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 `, `x-xtrem-endpoint: `, `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) - **MCP SDK** (from Context7 + GitHub): - `@modelcontextprotocol/sdk` v1.x — `McpServer`, `registerTool()`, `registerPrompt()`, Zod v4 schemas - `StdioServerTransport` for 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: false` option - **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 transport - `src/tools/` — 5 registered MCP tools for GraphQL operations - `src/resources/` — MCP resources with embedded X3 knowledge - `src/prompts/` — MCP prompt templates for guided workflows - `src/auth/` — JWT authentication module - `src/graphql/` — GraphQL HTTP client - `src/types/` — TypeScript type definitions - `src/config.ts` — Configuration management - `tests/` — Test suite (bun:test) - `package.json`, `tsconfig.json` — Project configuration ### Definition of Done - [ ] `bun run build` completes without errors - [ ] `bun test` passes 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`, T2 `quick`, T3 `unspecified-high`, T4 `unspecified-high` - **Wave 2**: 5 tasks — T5 `unspecified-high`, T6 `deep`, T7 `quick`, T8 `unspecified-high`, T9 `unspecified-high` - **Wave 3**: 3 tasks — T10 `deep`, T11 `unspecified-high`, T12 `deep` - **Wave 4**: 3 tasks — T13 `unspecified-high`, T14 `deep`, T15 `quick` - **FINAL**: 4 tasks — F1 `oracle`, F2 `unspecified-high`, F3 `unspecified-high`, F4 `deep` ### CRITICAL: Planner Instructions (propagate to ALL agents) 1. **No git worktrees** — do not use git worktrees under any circumstances 2. **Background task timeouts are in MILLISECONDS** — `timeout=30000` means 30 seconds, NOT 30 minutes. Use `timeout=120000` for 2-minute operations. This applies to YOUR OWN sub-agents too. 3. **Prefer background tasks with large timeouts** — foreground tasks release after 10 minutes (but agent keeps running). Use background tasks with large timeouts instead. --- ## TODOs - [x] 1. Project Scaffolding + Configuration **What to do**: - Initialize Bun project: `bun init` with TypeScript - Create `package.json` with dependencies: `@modelcontextprotocol/sdk`, `zod`, `jose` (for JWT) - Create `tsconfig.json` with 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 endpoint `https://api-devna.dev-sagex3.com/demo/service/X3CLOUDV2_SEED/api`) - `SAGE_X3_ENDPOINT` — X3 endpoint name (e.g., `REPOSX3_REPOSX3`), optional for sandbox - `SAGE_X3_CLIENT_ID` — Connected app client ID, optional for sandbox - `SAGE_X3_SECRET` — Connected app secret, optional for sandbox - `SAGE_X3_USER` — X3 user for JWT sub claim, optional for sandbox - `SAGE_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` — `sandbox` or `authenticated` (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/sdk` package on npm **External References**: - `jose` npm package — JWT signing/verification for Bun (no native crypto dependency) - `zod` npm package — schema validation for MCP tool inputs **Acceptance Criteria**: - [ ] `bun install` completes successfully - [ ] `bun run src/config.ts` executes 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.txt ``` **Commit**: 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/` - [x] 2. TypeScript Types + Shared Constants **What to do**: - Create `src/types/x3.ts` — Sage X3 specific types: - `X3Config` interface (url, endpoint, clientId, secret, user, tokenLifetime, mode, tlsRejectUnauthorized) - `X3GraphQLResponse` — Generic response wrapper `{ data: T, extensions?: { diagnoses: any[] } }` - `X3PageInfo` — `{ endCursor: string, hasNextPage: boolean, startCursor: string, hasPreviousPage: boolean }` - `X3Edge` — `{ node: T, cursor?: string }` - `X3Connection` — `{ edges: X3Edge[], pageInfo?: X3PageInfo, totalCount?: number }` - `X3QueryArgs` — `{ filter?: string, first?: number, after?: string, last?: number, before?: string, orderBy?: string }` - `X3RootType` enum: `xtremX3Structure`, `xtremX3MasterData`, `xtremX3Products`, `xtremX3Purchasing`, `xtremX3Stock` - `X3ErrorResponse` — 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_TYPES` map with descriptions for each root type - `FILTER_OPERATORS` list: `_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 ` — expect "OK" output Expected Result: Exit code 0, "OK" printed Evidence: .sisyphus/evidence/task-2-types-compile.txt ``` **Commit**: 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` - [x] 3. Auth Module (JWT Generation + Sandbox Mode) **What to do**: - Create `src/auth/jwt.ts`: - `generateJWT(config: X3Config): Promise` — Create JWT token using `jose` library - 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>` — Returns headers for API calls - If sandbox mode: return only `Content-Type: application/json` - If authenticated mode: return `Authorization: Bearer `, `x-xtrem-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 claims `iss, sub, aud, iat, exp` - Clock skew offset: `Math.floor(Date.now() / 1000) - 30` — documented in X3 quick start Node.js example - `x-xtrem-endpoint` header: required for authenticated calls, value is X3 endpoint name (e.g., `REPOSX3_REPOSX3`) **External References**: - `jose` library: `https://github.com/panva/jose` — Bun-compatible JWT library (uses Web Crypto API) **Acceptance Criteria**: - [ ] `generateJWT()` produces a valid JWT string decodable by jwt.io - [ ] `getAuthHeaders()` 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.txt ``` **Commit**: YES (groups with Wave 1) - Message: `feat(core): scaffold project with auth and GraphQL client` - Files: `src/auth/jwt.ts, src/auth/index.ts` - [x] 4. GraphQL HTTP Client **What to do**: - Create `src/graphql/client.ts`: - `executeGraphQL(config: X3Config, query: string, variables?: Record): Promise>` — Core HTTP client - Uses Bun's built-in `fetch` (no axios needed) - POST to `config.url` with 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 `$message` and `$source` from 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 via `tls` option **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.txt ``` **Commit**: YES (groups with Wave 1) - Message: `feat(core): scaffold project with auth and GraphQL client` - Files: `src/graphql/client.ts, src/graphql/index.ts` - [x] 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 show `xtrem*` 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 **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 `__schema` and `__type` queries - 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.txt ``` **Commit**: YES (groups with Wave 2) - Message: `feat(tools): add all 5 MCP query tools` - Files: `src/tools/introspect-schema.ts` - [x] 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)")` — required - `entity: z.string().describe("Entity name under the root type (e.g., businessPartner, product)")` — required - `fields: z.array(z.string()).describe("Fields to return (e.g., ['code', 'description1', 'isActive'])")` — required - `filter: 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 **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.code` builds 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.txt ``` **Commit**: YES (groups with Wave 2) - Message: `feat(tools): add all 5 MCP query tools` - Files: `src/tools/query-entities.ts` - [x] 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])` — required - `entity: z.string()` — required - `id: z.string().describe("Entity identifier (e.g., 'AE003')")` — required - `fields: 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) **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.txt ``` **Commit**: YES (groups with Wave 2) - Message: `feat(tools): add all 5 MCP query tools` - Files: `src/tools/read-entity.ts` - [x] 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])` — required - `entity: z.string()` — required - `aggregateFields: z.array(z.object({ field: z.string(), operations: z.array(z.enum(["min", "max", "count", "distinctCount"])) }))` — required - `filter: z.string().optional()` - Build GraphQL: `{ ${rootType} { ${entity} { readAggregate(filter: "${filter}") { ${fieldAggregations} } } } }` - Example output: `{ productWeight: { min: 0.5, max: 25.0 }, stockUnit: { code: { distinctCount: 12 } } }` **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.txt ``` **Commit**: YES (groups with Wave 2) - Message: `feat(tools): add all 5 MCP query tools` - Files: `src/tools/aggregate-entities.ts` - [x] 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")` — required - `variables: 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." **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.txt ``` **Commit**: YES (groups with Wave 2) - Message: `feat(tools): add all 5 MCP query tools` - Files: `src/tools/execute-graphql.ts` - [x] 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 - **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 **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.txt ``` **Commit**: YES (groups with Wave 3) - Message: `feat(server): wire MCP server with resources and prompts` - Files: `src/resources/index.ts, src/resources/knowledge/*.ts` - [x] 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 - **Prompt 2**: `lookup-entity` — "Look up a specific entity by ID" - Args: `entity` (string), `id` (string) - Generates a message for read_entity usage - **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 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-resume` pattern: prompts in separate `-helpers/prompts.ts` file **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.txt ``` **Commit**: YES (groups with Wave 3) - Message: `feat(server): wire MCP server with resources and prompts` - Files: `src/prompts/index.ts` - [x] 12. MCP Server Entry Point + Wiring **What to do**: - Create `src/index.ts` — Main MCP server entry point: - Import and create `McpServer` instance with name `sage-x3-graphql` and 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 `StdioServerTransport` and 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 - Update `package.json`: - Add `"bin"` field pointing to `src/index.ts` - Add scripts: `"start": "bun run src/index.ts"`, `"build": "bun build src/index.ts --outdir dist --target bun"` - 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()` then `server.connect(transport)` - `reactive-resume` pattern: separate registration files for tools/resources/prompts, wired in main **Acceptance Criteria**: - [ ] `bun run src/index.ts` starts without errors - [ ] MCP `initialize` handshake succeeds when JSON-RPC message sent via stdin - [ ] `tools/list` returns all 5 tools - [ ] `resources/list` returns all 5 resources - [ ] `prompts/list` returns 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.txt ``` **Commit**: 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` - [x] 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 - 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.ts` passes all tests - [ ] `bun test tests/graphql-client.test.ts` passes all tests - [ ] `bun test tests/config.test.ts` passes 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.txt ``` **Commit**: 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` - [x] 14. Integration Tests Against Sandbox **What to do**: - Create `tests/integration.test.ts`: - Test each of the 5 tools against the live sandbox endpoint: 1. `introspect_schema` — verify returns X3 root types 2. `query_entities` — query business partners with first:5, verify edges/node shape 3. `query_entities` with filter — filter by a known code, verify filtered results 4. `read_entity` — read a specific business partner by code 5. `aggregate_entities` — aggregate product data 6. `execute_graphql` — run a raw query with aliases 7. `execute_graphql` mutation 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) - 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 **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.ts` passes all tests against sandbox - [ ] `bun test tests/mcp-server.test.ts` passes 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.txt ``` **Commit**: YES (groups with Wave 4) - Message: `test: add unit and integration tests against sandbox` - Files: `tests/integration.test.ts, tests/mcp-server.test.ts` - [x] 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.json` to include the sage-x3-graphql MCP server: ```json { "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.txt ``` **Commit**: YES (groups with Wave 4) - Message: `test: add unit and integration tests against sandbox` - Files: `README.md, opencode.json` --- ## Final Verification Wave (MANDATORY — after ALL implementation tasks) > 4 review agents run in PARALLEL. ALL must APPROVE. Rejection → fix → re-run. - [x] F1. **Plan Compliance Audit** — `oracle` Read 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` - [x] F2. **Code Quality Review** — `unspecified-high` Run `bun 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` - [x] F3. **Sandbox End-to-End QA** — `unspecified-high` Start MCP server via stdio. Send JSON-RPC `initialize`, `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` - [x] F4. **Scope Fidelity Check** — `deep` Verify: (1) NO mutations exist anywhere — grep for `mutation` in 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 ```bash 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