feat: everything
This commit is contained in:
103
.sisyphus/notepads/sage-x3-graphql-mcp/learnings.md
Normal file
103
.sisyphus/notepads/sage-x3-graphql-mcp/learnings.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# Learnings
|
||||
|
||||
## Session Start
|
||||
- Greenfield project — no existing code, only opencode.json + planner_instructions.md
|
||||
- No git repo initialized yet
|
||||
- No git worktrees — per planner instructions
|
||||
- Runtime: Bun (built-in TypeScript, fetch, test runner)
|
||||
- Dependencies: @modelcontextprotocol/sdk, zod, jose
|
||||
- Demo endpoint: https://api-devna.dev-sagex3.com/demo/service/X3CLOUDV2_SEED/api (unauthenticated, max 20 results)
|
||||
|
||||
## Task 1: Project Initialization
|
||||
|
||||
### MCP SDK Package
|
||||
- npm package is `@modelcontextprotocol/sdk` (NOT `@modelcontextprotocol/server`)
|
||||
- Import path uses subpath export: `import { McpServer, StdioServerTransport } from '@modelcontextprotocol/sdk/server'`
|
||||
- Zod import: `import * as z from 'zod/v4'` (v4 subpath)
|
||||
- Installed versions: `@modelcontextprotocol/sdk@1.27.1`, `jose@6.2.1`, `zod@4.3.6`
|
||||
|
||||
### Bun Runtime
|
||||
- `bun` not installed on this system; using `npx bun` as workaround
|
||||
- `bun init -y` creates index.ts, tsconfig.json, README.md, .gitignore
|
||||
- bun init tsconfig defaults to bundler mode — replaced with NodeNext for MCP compatibility
|
||||
|
||||
### Config Module Pattern
|
||||
- `import.meta.main` works in Bun to detect direct execution
|
||||
- `process.env` works in Bun (Node compat)
|
||||
- Auto-detect mode pattern: check presence of clientId + secret + user → "authenticated", else "sandbox"
|
||||
- JSON.stringify omits `undefined` properties — clean output for debugging
|
||||
|
||||
## Task 3: Auth Module
|
||||
|
||||
### jose Library (v6.2.1)
|
||||
- `new SignJWT(payload).setProtectedHeader({ alg: 'HS256' }).sign(secret)` — clean chaining API
|
||||
- Secret must be `Uint8Array` via `new TextEncoder().encode(secretString)`
|
||||
- Can pass all JWT claims directly in payload constructor — no need to use `.setIssuedAt()` etc. when custom values needed
|
||||
- Do NOT use `.setIssuedAt()` for X3 — need custom iat with -30s offset
|
||||
|
||||
### X3 JWT Claims
|
||||
- `iss` = clientId, `sub` = user, `aud` = "" (empty string)
|
||||
- `iat` = `Math.floor(Date.now() / 1000) - 30` — 30-second clock skew offset is CRITICAL
|
||||
- `exp` = `iat + tokenLifetime`
|
||||
- Algorithm: HS256 only
|
||||
|
||||
### Auth Headers Pattern
|
||||
- Sandbox: only `Content-Type: application/json` (no auth at all)
|
||||
- Authenticated: `Authorization: Bearer <jwt>`, `x-xtrem-endpoint: <endpoint>`, `Content-Type: application/json`
|
||||
- JWT regenerated on each call (short-lived tokens, no caching)
|
||||
|
||||
## Task 5: MCP Prompts Module
|
||||
|
||||
### MCP SDK Prompt API (v2)
|
||||
- Use `server.registerPrompt()` (not deprecated `server.prompt()`)
|
||||
- Signature: `registerPrompt(name, { title?, description?, argsSchema? }, callback)`
|
||||
- argsSchema is a raw Zod shape object (NOT wrapped in `z.object()`) — the SDK wraps it internally
|
||||
- Callback: `(args, extra) => GetPromptResult` for prompts with args, `(extra) => GetPromptResult` for no-args
|
||||
- For no-args prompts, omit argsSchema from config — callback receives only `extra` param
|
||||
- Return type: `{ messages: [{ role: 'user', content: { type: 'text', text: string } }] }`
|
||||
- Import: `McpServer` from `@modelcontextprotocol/sdk/server/mcp.js`
|
||||
|
||||
## Task 4: GraphQL Client
|
||||
|
||||
### Dynamic Import for Parallel Task Dependencies
|
||||
- When module A depends on module B being created by a parallel task, use variable-path dynamic import
|
||||
- `const path = '../auth/index.js'; await import(path)` — TypeScript types it as `any`, no static resolution error
|
||||
- Allows compilation even when the target module doesn't exist yet
|
||||
|
||||
### Demo Endpoint Status (2026-03-13)
|
||||
- Demo endpoint `https://api-devna.dev-sagex3.com/demo/service/X3CLOUDV2_SEED/api` currently returns 401
|
||||
- Response body: "Votre mot de passe a expiré. Vous devez le modifier" (French: password expired)
|
||||
- This is a server-side issue, not a client bug — our error handling catches it correctly
|
||||
|
||||
### Mutation Guard Pattern
|
||||
- Regex `/^mutation\b/i` on `query.trim()` catches: `mutation {`, `MUTATION {`, ` mutation {`
|
||||
- Word boundary `\b` prevents false positives on field names containing "mutation"
|
||||
|
||||
### TLS Override Pattern
|
||||
- Save `process.env.NODE_TLS_REJECT_UNAUTHORIZED` before, restore in `finally` block
|
||||
- If was undefined before, `delete` the env var rather than setting to undefined
|
||||
|
||||
## Task 3b: MCP Resources Module
|
||||
|
||||
### MCP SDK Resource API (v2)
|
||||
- Use `server.registerResource()` (not deprecated `server.resource()`)
|
||||
- Signature: `registerResource(name, uri, config, readCallback)`
|
||||
- config is `ResourceMetadata` = `{ description?, mimeType?, title?, annotations? }`
|
||||
- readCallback: `(uri: URL, extra) => { contents: [{ uri: string, text: string, mimeType?: string }] }`
|
||||
- Import: `McpServer` from `@modelcontextprotocol/sdk/server/mcp.js`
|
||||
- For static resources, pass URI as plain string (not ResourceTemplate)
|
||||
|
||||
## F1: Plan Compliance Audit Results
|
||||
|
||||
### Verification Evidence
|
||||
- **Typecheck**: `npx bun run typecheck` → clean (0 errors)
|
||||
- **Tests**: `npx bun test` → 51 pass, 0 fail, 167 expect() calls, 5 test files
|
||||
- **Mutation guard**: Two-layer defense — tool-level in `execute-graphql.ts` + client-level in `client.ts`
|
||||
- **All 22 src files inspected**: Simple structure, one file per tool, no over-abstraction
|
||||
- **MCP server protocol tested**: mcp-server.test.ts covers initialize, tools/list (5), resources/list (5), prompts/list (4), error handling
|
||||
|
||||
### Key Architecture Patterns
|
||||
- Tools use `server.registerTool()` or `server.tool()` with Zod v4 schemas
|
||||
- Resources registered in a loop from knowledge/ directory constants
|
||||
- Config auto-detects sandbox vs authenticated from env var presence
|
||||
- JWT uses jose SignJWT with 30-second clock skew offset per X3 docs
|
||||
Reference in New Issue
Block a user