{"_id":"@aperovn/mcp-context","name":"@aperovn/mcp-context","dist-tags":{"latest":"0.0.1"},"versions":{"0.0.1":{"name":"@aperovn/mcp-context","version":"0.0.1","description":"Shared ABAC policy types, context injection/extraction, and evaluator for the Apero VN MCP gateway ecosystem","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"build":"tsup src/index.ts --format esm,cjs --dts --clean","dev":"tsup src/index.ts --format esm,cjs --dts --watch","typecheck":"tsc --noEmit","test":"bun test","prepublishOnly":"bun run build"},"dependencies":{"minimatch":"^10.0.1"},"devDependencies":{"@types/bun":"latest","@types/express":"^4.17.21","tsup":"^8.0.0","typescript":"^5.4.0"},"sideEffects":false,"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/aperovn/authorized-mcp.git","directory":"packages/mcp-context"},"license":"MIT","keywords":["mcp","abac","policy","authorization","gateway","apero"],"_id":"@aperovn/mcp-context@0.0.1","bugs":{"url":"https://github.com/aperovn/authorized-mcp/issues"},"homepage":"https://github.com/aperovn/authorized-mcp#readme","_nodeVersion":"24.13.1","_npmVersion":"11.8.0","dist":{"integrity":"sha512-xggGQnIZIKW0rt4bBNWX7jwBFdoAVBFtxDTyDNK058SNoQURINb3PLAMWheWD3O3U+kOCB+0MoQNXPxQ1gBWig==","shasum":"3597a335a630082ce72def1548a3ab16d51eb5e8","tarball":"https://registry.npmjs.org/@aperovn/mcp-context/-/mcp-context-0.0.1.tgz","fileCount":7,"unpackedSize":54497,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIHeJ8z2LSpssZoTmGLMGGrFSEsLB9i28h0k87x7YRLosAiEA7DwdlqvdRXty3VnZymy9p7EeaJXgqGF0ysHJHkN3Ub0="}]},"_npmUser":{"name":"coderhanoi","email":"longlehoang2013@gmail.com"},"directories":{},"maintainers":[{"name":"coderhanoi","email":"longlehoang2013@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/mcp-context_0.0.1_1776938673856_0.46765476783016746"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-23T10:04:33.788Z","0.0.1":"2026-04-23T10:04:34.038Z","modified":"2026-04-23T10:04:34.214Z"},"maintainers":[{"name":"coderhanoi","email":"longlehoang2013@gmail.com"}],"description":"Shared ABAC policy types, context injection/extraction, and evaluator for the Apero VN MCP gateway ecosystem","homepage":"https://github.com/aperovn/authorized-mcp#readme","keywords":["mcp","abac","policy","authorization","gateway","apero"],"repository":{"type":"git","url":"git+https://github.com/aperovn/authorized-mcp.git","directory":"packages/mcp-context"},"bugs":{"url":"https://github.com/aperovn/authorized-mcp/issues"},"license":"MIT","readme":"# @aperovn/mcp-context\n\nShared ABAC policy types, context injection/extraction, and evaluator for the Apero VN MCP gateway ecosystem.\n\n> **Trust model: internal VPC only.** Downstream MCPs trust gateway headers because they are not publicly reachable. If this assumption changes, signing must be reintroduced.\n\n- ABAC policy evaluation engine (attribute-based access control)\n- Lazy policy fetch with 30-second cache\n- Dual ESM/CJS build, zero crypto dependencies\n\n## Installation\n\n```bash\nbun add @aperovn/mcp-context\n# or\nnpm install @aperovn/mcp-context\n```\n\n## Quick Start\n\n### Downstream MCP Integration (~5 LOC)\n\n```typescript\nimport { resolveContext, canPerform } from '@aperovn/mcp-context';\n\n// Option 1: Explicit gateway URL\nconst ctx = await resolveContext(req.headers, 'http://gateway:3000');\n\n// Option 2: Via env var (GATEWAY_INTERNAL_URL=http://gateway:3000)\nconst ctx = await resolveContext(req.headers);\n\n// Use in business logic\nconst decision = canPerform(ctx, 'write:file', 'files/data.txt', 'filesystem');\nif (decision.effect === 'deny') {\n  return res.status(403).json({ error: decision.reason });\n}\n```\n\n### Gateway Integration\n\n```typescript\nimport { injectContext } from '@aperovn/mcp-context';\n\nconst headers: Record<string, string> = {};\ninjectContext(userContext, headers);\n\n// Forward request to downstream MCP\nfetch('http://downstream-mcp/mcp', { headers });\n```\n\n## Wire Protocol\n\n### Headers\n\n```\nX-API-USER:     alice@apero.vn\nX-API-TEAMS:    mobile,finance              (empty string if none)\nX-API-ADMIN:    true|false\n```\n\nThree headers. No version field. No timestamp. No signature. Header names are exported as constants — consume them instead of hardcoding literals:\n\n```typescript\nimport { HEADER_USER, HEADER_TEAMS, HEADER_ADMIN } from '@aperovn/mcp-context';\n```\n\n**Note**: Policies are not forwarded in headers — fetched lazily via `resolveContext`.\n\n### Size limits\n\n- No header size limits enforced in v2.0.0\n- Policies cached with 30-second TTL in downstream MCPs\n- Network pressure eliminated through lazy loading\n\n## API Reference\n\n### Types\n\n```typescript\ninterface Policy {\n  id?: number;\n  name?: string;\n  effect: 'allow' | 'deny';\n  principal_type: 'user' | 'team' | '*';\n  principal_id: string;\n  actions: string[];          // Glob patterns (empty = fail-closed)\n  mcp_scope: string[];        // MCP names (empty = fail-closed)\n  resources: string[];        // Glob patterns (empty = fail-closed)\n  priority: number;           // 0-1000\n}\n\ninterface UserContext {\n  email: string;\n  teams: string[];\n  isAdmin: boolean;\n  policies: Policy[];         // Pre-sorted by priority\n}\n\ninterface Decision {\n  effect: 'allow' | 'deny';\n  matchedPolicies: Policy[];\n  reason: string;\n}\n```\n\n### Functions\n\n#### `injectContext(user, headers)`\n\nInject user context into HTTP headers (mutates `headers` in place).\n\n- **user**: `UserContext`\n- **headers**: `Headers | Record<string, string>` (mutated in-place)\n\n#### `extractContext(headers) → UserContext`\n\nExtract user context from HTTP headers. Returns frozen `UserContext` with `policies: []`. Throws `InvalidContextError` on missing `X-API-USER`.\n\nUse `resolveContext()` when you need actual policies.\n\n#### `resolveContext(headers, gatewayUrl?) → Promise<UserContext>`\n\nExtract user context and fetch policies lazily from gateway.\n\n- **headers**: `Headers | Record<string, string>`\n- **gatewayUrl**: `string` (optional) — Gateway base URL. Falls back to `GATEWAY_INTERNAL_URL` env var. Throws if neither provided.\n\nReturns frozen `UserContext` with policies. Uses 30-second cache. Throws `InvalidContextError` on failures.\n\n#### `canPerform(ctx, action, resource, mcp?) → Decision`\n\nEvaluate ABAC policies for a specific resource operation.\n\n- **ctx**: `UserContext`\n- **action**: `string` (e.g., `\"read:file\"`)\n- **resource**: `string` (e.g., `\"files/data.txt\"`)\n- **mcp**: `string` (optional, MCP name)\n\n#### `canCallTool(ctx, action, mcp?) → 'allowed' | 'denied' | 'conditionally-allowed'`\n\nTool listing mode (resource-agnostic).\n\n- Returns `'conditionally-allowed'` if policies have resource restrictions\n- Returns `'allowed'` if unrestricted allow\n- Returns `'denied'` if deny or no match\n\n#### `hasDeny(ctx, action, resource?, mcp?) → boolean`\n\nFast deny check (short-circuits on first matching deny policy).\n\n#### `PolicyCache`\n\nIn-memory TTL cache for policy responses (30-second expiry).\n\n#### `PolicyClient`\n\nHTTP client for fetching policies from gateway internal endpoint.\n\n#### `policySchema`\n\nJSON Schema for `Policy` validation (Ajv-compatible).\n\n```typescript\nimport Ajv from 'ajv';\nimport { policySchema } from '@aperovn/mcp-context';\n\nconst ajv = new Ajv();\nconst validatePolicy = ajv.compile(policySchema);\n```\n\n## Migration Guide (from internal pre-publish version)\n\n### For Downstream MCPs\n\n**Before (internal):**\n```typescript\nimport { extractContext } from '@aperovn/mcp-context';\n\n// Extract context with policies\nconst ctx = extractContext(req.headers);\n// ctx.policies contains actual policy array\n\n// Use in business logic\nconst decision = canPerform(ctx, 'write:file', 'files/data.txt', 'filesystem');\n```\n\n**After (public):**\n```typescript\nimport { resolveContext } from '@aperovn/mcp-context';\n\n// Extract context and fetch policies lazily\n// gatewayUrl optional — uses GATEWAY_INTERNAL_URL env var if omitted\nconst ctx = await resolveContext(req.headers);\n// ctx.policies fetched from gateway with 30s cache\n\n// Use in business logic (same as before)\nconst decision = canPerform(ctx, 'write:file', 'files/data.txt', 'filesystem');\n```\n\n**Required Environment Variable:**\n```bash\nGATEWAY_INTERNAL_URL=http://gateway:3000\n```\n\n#### For Gateway (Internal Change)\n\n**Before:**\n```typescript\nimport { injectContext } from '@aperovn/mcp-context';\n\ninjectContext(ctx, ctx.policies, headers);\n```\n\n**After:**\n```typescript\nimport { injectContext } from '@aperovn/mcp-context';\n\ninjectContext(ctx, headers); // policies parameter removed\n```\n\n#### Key Changes Summary\n- `X-API-POLICIES` header no longer injected\n- `policies` parameter removed from `injectContext`\n- `resolveContext()` async API for lazy policy fetching\n- 30-second cache reduces network calls\n- `extractContext()` returns empty policies array\n\n#### Migration Steps\n1. Add `GATEWAY_INTERNAL_URL` environment variable to downstream MCPs\n2. Replace `extractContext()` calls with `await resolveContext()`\n3. Update `injectContext()` calls in gateway (remove policies parameter)\n4. Deploy gateway first, then downstream MCPs\n\n## Policy Semantics\n\n- **Empty arrays = fail-closed**: `[]` matches nothing (explicit deny)\n- **Wildcards**: `['*']` matches all\n- **Priority**: Higher wins (0-1000)\n- **Tie-breaks**: Deny beats allow at same priority\n- **Admin bypass**: `isAdmin: true` ignores all policies\n- **Glob patterns**: Minimatch syntax (`**/*`, `projects/*`, `com.apero.*`)\n\n## Examples\n\n### Allow mobile team to read files\n\n```json\n{\n  \"effect\": \"allow\",\n  \"principal_type\": \"team\",\n  \"principal_id\": \"mobile\",\n  \"actions\": [\"read:*\", \"list:*\"],\n  \"mcp_scope\": [\"filesystem\"],\n  \"resources\": [\"files/mobile/*\"],\n  \"priority\": 100\n}\n```\n\n### Deny prod writes for non-admins\n\n```json\n{\n  \"effect\": \"deny\",\n  \"principal_type\": \"*\",\n  \"principal_id\": \"*\",\n  \"actions\": [\"write:*\", \"delete:*\"],\n  \"mcp_scope\": [\"*\"],\n  \"resources\": [\"files/prod/*\"],\n  \"priority\": 1000\n}\n```\n\n## License\n\nMIT\n\n## Version History\n\nSee [CHANGELOG.md](./CHANGELOG.md)\n","readmeFilename":"README.md","_rev":"1-183a69531ab8d0ae80f3d9a50fcd3332"}