5.3 KiB
5.3 KiB
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
bunnot installed on this system; usingnpx bunas workaroundbun init -ycreates 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.mainworks in Bun to detect direct executionprocess.envworks in Bun (Node compat)- Auto-detect mode pattern: check presence of clientId + secret + user → "authenticated", else "sandbox"
- JSON.stringify omits
undefinedproperties — 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
Uint8Arrayvianew 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 CRITICALexp=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 deprecatedserver.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) => GetPromptResultfor prompts with args,(extra) => GetPromptResultfor no-args - For no-args prompts, omit argsSchema from config — callback receives only
extraparam - Return type:
{ messages: [{ role: 'user', content: { type: 'text', text: string } }] } - Import:
McpServerfrom@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 asany, 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/apicurrently 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/ionquery.trim()catches:mutation {,MUTATION {,mutation { - Word boundary
\bprevents false positives on field names containing "mutation"
TLS Override Pattern
- Save
process.env.NODE_TLS_REJECT_UNAUTHORIZEDbefore, restore infinallyblock - If was undefined before,
deletethe env var rather than setting to undefined
Task 3b: MCP Resources Module
MCP SDK Resource API (v2)
- Use
server.registerResource()(not deprecatedserver.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:
McpServerfrom@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 inclient.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()orserver.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