@oriva/mcp-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # @oriva/mcp-server
2
+
3
+ Model Context Protocol server for the [Oriva](https://api.oriva.io) public API.
4
+
5
+ Exposes all 46 public Oriva API endpoints as MCP tools so AI agents (Claude Code, Cursor, Continue, Claude Desktop) can read and write Oriva data on behalf of a user with an `oriva_pk_*` API key.
6
+
7
+ ## Install + connect (Claude Code)
8
+
9
+ ```bash
10
+ claude mcp add oriva npx -- -y @oriva/mcp-server -e ORIVA_API_KEY=oriva_pk_xxx
11
+ ```
12
+
13
+ Then in any Claude Code session, `/mcp` lists `oriva` and you can ask things like:
14
+
15
+ - "use oriva to show my current user"
16
+ - "list my marketplace apps with oriva"
17
+ - "create an Oriva event titled 'Demo Day' on 2026-06-01"
18
+
19
+ ## Environment variables
20
+
21
+ | Variable | Required | Purpose |
22
+ |---|---|---|
23
+ | `ORIVA_API_KEY` | yes | Bearer key for `Authorization` header. Get one from https://api.oriva.io/developer |
24
+ | `ORIVA_API_BASE_URL` | no | Override the API base URL (default `https://api.oriva.io`). Useful for local development against a tunnel. |
25
+
26
+ ## What's exposed
27
+
28
+ Every public Oriva API endpoint that the OpenAPI spec marks with an `operationId` becomes an MCP tool with the same name. Path + required query parameters are flagged required; optional query params and JSON body fields are optional. Response bodies come back verbatim as text content.
29
+
30
+ ### Coverage
31
+
32
+ - 46 operations across 40 paths
33
+ - Read + write surfaces: profiles, groups, sessions, marketplace, developer, entries, events, auth/profile, analytics, users
34
+ - v1 supports `application/json` requests + responses only. Multipart, octet-stream, and server-sent events are **not** wired (the projector throws at boot if the spec adds them).
35
+
36
+ ### Excluded by design
37
+
38
+ - First-party tenant routes (`/api/v1/tenant/*`) — never part of the public contract
39
+ - Internal debug / dev routes (`/dev-profiles`, `/api/v1/debug/cors`)
40
+ - Admin routes that require a separate `requireAdminToken`
41
+
42
+ The complete list lives in `claudedocs/public-api-contract.md` in the o-platform repo.
43
+
44
+ ## How tools are derived
45
+
46
+ This server hand-rolls a projection from `o-platform/claudedocs/openapi-snapshot.json` at build time. The snapshot is bundled into `dist/spec.json`, so `npx` consumers don't need the repo. When the spec changes upstream, `npm run build` re-bundles and re-projects with zero hand edits.
47
+
48
+ Mapping:
49
+
50
+ | OpenAPI | MCP |
51
+ |---|---|
52
+ | `operationId` | `tools/list[].name` |
53
+ | `summary` + `description` | `tools/list[].description` |
54
+ | `parameters[in=path]` | required input field |
55
+ | `parameters[in=query]` | required iff `required: true` |
56
+ | `requestBody.content['application/json'].schema` | flattened into input fields |
57
+ | 2xx response body | `CallToolResult.content[0].text` (verbatim) |
58
+ | Non-2xx | `isError: true` with HTTP status prefix |
59
+
60
+ Path-param vs body-field name collisions are resolved with path-param wins; the body field gets a `body_` prefix.
61
+
62
+ ## Develop locally
63
+
64
+ ```bash
65
+ git clone https://github.com/0riva/o-platform.git
66
+ cd o-platform/packages/mcp-server
67
+ npm install
68
+ npm run build
69
+ ORIVA_API_KEY=oriva_pk_xxx npx @modelcontextprotocol/inspector node dist/index.js
70
+ ```
71
+
72
+ The [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) gives you a web UI to list and call tools without needing an LLM client.
73
+
74
+ To add the locally-built server to Claude Code:
75
+
76
+ ```bash
77
+ claude mcp add oriva-dev node $PWD/dist/index.js -e ORIVA_API_KEY=oriva_pk_xxx
78
+ ```
79
+
80
+ ## License
81
+
82
+ MIT — see [LICENSE](../../LICENSE) in the o-platform repo root.
@@ -0,0 +1,11 @@
1
+ import type { ProjectedOperation } from './types.js';
2
+ export interface CallResult {
3
+ status: number;
4
+ text: string;
5
+ ok: boolean;
6
+ }
7
+ export interface ClientOptions {
8
+ apiKey: string;
9
+ baseUrl?: string;
10
+ }
11
+ export declare function callOperation(op: ProjectedOperation, args: Record<string, unknown>, options: ClientOptions): Promise<CallResult>;
package/dist/client.js ADDED
@@ -0,0 +1,55 @@
1
+ const DEFAULT_BASE_URL = 'https://api.oriva.io';
2
+ export async function callOperation(op, args, options) {
3
+ const baseUrl = options.baseUrl ?? process.env.ORIVA_API_BASE_URL ?? DEFAULT_BASE_URL;
4
+ let path = op.path;
5
+ for (const name of op.pathParams) {
6
+ const value = args[name];
7
+ if (value === undefined || value === null) {
8
+ throw new Error(`Missing required path parameter: ${name}`);
9
+ }
10
+ path = path.replace(`{${name}}`, encodeURIComponent(String(value)));
11
+ }
12
+ const qs = new URLSearchParams();
13
+ for (const name of op.queryParams) {
14
+ const value = args[name];
15
+ if (value === undefined || value === null)
16
+ continue;
17
+ if (Array.isArray(value)) {
18
+ for (const item of value)
19
+ qs.append(name, String(item));
20
+ }
21
+ else {
22
+ qs.set(name, String(value));
23
+ }
24
+ }
25
+ const queryString = qs.toString();
26
+ let body;
27
+ let contentType;
28
+ const methodAllowsBody = op.method !== 'get' && op.method !== 'head' && op.method !== 'delete';
29
+ if (methodAllowsBody && op.bodyFields.length > 0) {
30
+ const bodyObj = {};
31
+ for (const { alias, original } of op.bodyFields) {
32
+ if (args[alias] !== undefined)
33
+ bodyObj[original] = args[alias];
34
+ }
35
+ if (Object.keys(bodyObj).length > 0) {
36
+ body = JSON.stringify(bodyObj);
37
+ contentType = 'application/json';
38
+ }
39
+ }
40
+ const url = `${baseUrl}${path}${queryString ? '?' + queryString : ''}`;
41
+ const headers = {
42
+ Authorization: `Bearer ${options.apiKey}`,
43
+ Accept: 'application/json',
44
+ 'User-Agent': '@oriva/mcp-server/0.1.0',
45
+ };
46
+ if (contentType)
47
+ headers['Content-Type'] = contentType;
48
+ const res = await fetch(url, {
49
+ method: op.method.toUpperCase(),
50
+ headers,
51
+ body,
52
+ });
53
+ const text = await res.text();
54
+ return { status: res.status, text, ok: res.ok };
55
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
+ import { callOperation } from './client.js';
6
+ import { getSpecInfo, projectTools } from './openapi.js';
7
+ const PKG_NAME = '@oriva/mcp-server';
8
+ const PKG_VERSION = '0.1.0';
9
+ function fail(message) {
10
+ process.stderr.write(`[oriva-mcp] ${message}\n`);
11
+ process.exit(1);
12
+ }
13
+ const apiKey = process.env.ORIVA_API_KEY;
14
+ if (!apiKey) {
15
+ fail('ORIVA_API_KEY environment variable is required.\n' +
16
+ ' Get a key from https://api.oriva.io/developer and run with:\n' +
17
+ ' ORIVA_API_KEY=oriva_pk_xxx oriva-mcp');
18
+ }
19
+ let projection;
20
+ try {
21
+ projection = projectTools();
22
+ }
23
+ catch (err) {
24
+ const message = err instanceof Error ? err.message : String(err);
25
+ fail(`Failed to project OpenAPI spec into MCP tools: ${message}`);
26
+ }
27
+ const { tools, index } = projection;
28
+ const info = getSpecInfo();
29
+ process.stderr.write(`[oriva-mcp] Loaded ${tools.length} tools from spec (${info.paths} paths, ${info.schemas} schemas).\n`);
30
+ const server = new Server({ name: PKG_NAME, version: PKG_VERSION }, { capabilities: { tools: {} } });
31
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
32
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
33
+ const op = index.get(request.params.name);
34
+ if (!op) {
35
+ return {
36
+ content: [{ type: 'text', text: `Unknown tool: ${request.params.name}` }],
37
+ isError: true,
38
+ };
39
+ }
40
+ try {
41
+ const result = await callOperation(op, request.params.arguments ?? {}, { apiKey });
42
+ const prefix = result.ok ? '' : `HTTP ${result.status}\n`;
43
+ return {
44
+ content: [{ type: 'text', text: `${prefix}${result.text}` }],
45
+ isError: !result.ok,
46
+ };
47
+ }
48
+ catch (err) {
49
+ const message = err instanceof Error ? err.message : String(err);
50
+ return {
51
+ content: [{ type: 'text', text: `Error: ${message}` }],
52
+ isError: true,
53
+ };
54
+ }
55
+ });
56
+ const transport = new StdioServerTransport();
57
+ await server.connect(transport);
58
+ process.stderr.write('[oriva-mcp] Connected via stdio.\n');
@@ -0,0 +1,10 @@
1
+ import type { MCPTool, OperationIndex } from './types.js';
2
+ export interface ProjectionResult {
3
+ tools: MCPTool[];
4
+ index: OperationIndex;
5
+ }
6
+ export declare function projectTools(): ProjectionResult;
7
+ export declare function getSpecInfo(): {
8
+ paths: number;
9
+ schemas: number;
10
+ };
@@ -0,0 +1,154 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options'];
5
+ const ANNOTATION_KEYS = [
6
+ 'type',
7
+ 'description',
8
+ 'enum',
9
+ 'format',
10
+ 'pattern',
11
+ 'minimum',
12
+ 'maximum',
13
+ 'minLength',
14
+ 'maxLength',
15
+ 'example',
16
+ 'items',
17
+ 'nullable',
18
+ 'default',
19
+ ];
20
+ function loadSpec() {
21
+ const here = dirname(fileURLToPath(import.meta.url));
22
+ const specPath = resolve(here, 'spec.json');
23
+ const raw = readFileSync(specPath, 'utf-8');
24
+ return JSON.parse(raw);
25
+ }
26
+ const SPEC = loadSpec();
27
+ function resolveRef(node, seen = new Set()) {
28
+ if (!node || typeof node !== 'object')
29
+ return {};
30
+ if (node.$ref && typeof node.$ref === 'string') {
31
+ if (seen.has(node.$ref))
32
+ return {}; // cycle guard
33
+ const nextSeen = new Set(seen);
34
+ nextSeen.add(node.$ref);
35
+ const path = node.$ref.replace(/^#\//, '').split('/');
36
+ let resolved = SPEC;
37
+ for (const segment of path) {
38
+ resolved = resolved?.[segment];
39
+ }
40
+ if (!resolved) {
41
+ throw new Error(`Unresolved $ref: ${node.$ref}`);
42
+ }
43
+ return resolveRef(resolved, nextSeen);
44
+ }
45
+ const out = {};
46
+ for (const [k, v] of Object.entries(node)) {
47
+ if (k === 'properties' && v && typeof v === 'object') {
48
+ const props = {};
49
+ for (const [pk, pv] of Object.entries(v)) {
50
+ props[pk] = resolveRef(pv, new Set(seen));
51
+ }
52
+ out.properties = props;
53
+ }
54
+ else if (k === 'items' && v && typeof v === 'object') {
55
+ out.items = resolveRef(v, new Set(seen));
56
+ }
57
+ else if (k === 'additionalProperties' && v && typeof v === 'object') {
58
+ out.additionalProperties = resolveRef(v, new Set(seen));
59
+ }
60
+ else {
61
+ out[k] = v;
62
+ }
63
+ }
64
+ return out;
65
+ }
66
+ function preserveAnnotations(schema, param) {
67
+ const out = {};
68
+ for (const key of ANNOTATION_KEYS) {
69
+ const value = schema[key];
70
+ if (value !== undefined) {
71
+ out[key] = value;
72
+ }
73
+ }
74
+ if (param?.description && !out.description)
75
+ out.description = param.description;
76
+ if (!out.type)
77
+ out.type = 'string';
78
+ return out;
79
+ }
80
+ function buildInputSchema(op) {
81
+ const properties = {};
82
+ const required = [];
83
+ const pathParams = [];
84
+ const queryParams = [];
85
+ const bodyFields = [];
86
+ for (const p of op.parameters ?? []) {
87
+ if (p.in !== 'path' && p.in !== 'query')
88
+ continue;
89
+ const resolved = p.schema ? resolveRef(p.schema) : {};
90
+ properties[p.name] = preserveAnnotations(resolved, p);
91
+ if (p.in === 'path') {
92
+ pathParams.push(p.name);
93
+ if (!required.includes(p.name))
94
+ required.push(p.name);
95
+ }
96
+ else {
97
+ queryParams.push(p.name);
98
+ if (p.required && !required.includes(p.name))
99
+ required.push(p.name);
100
+ }
101
+ }
102
+ const bodySchema = op.requestBody?.content?.['application/json']?.schema;
103
+ if (bodySchema) {
104
+ const resolvedBody = resolveRef(bodySchema);
105
+ const bodyRequired = new Set(resolvedBody.required ?? []);
106
+ const bodyIsRequired = op.requestBody?.required === true;
107
+ for (const [key, sub] of Object.entries(resolvedBody.properties ?? {})) {
108
+ const collision = key in properties;
109
+ const alias = collision ? `body_${key}` : key;
110
+ properties[alias] = resolveRef(sub);
111
+ bodyFields.push({ alias, original: key });
112
+ const isRequired = bodyRequired.has(key) || (bodyIsRequired && bodyRequired.size === 0);
113
+ if (isRequired && !required.includes(alias))
114
+ required.push(alias);
115
+ }
116
+ }
117
+ return {
118
+ schema: { type: 'object', properties, required, additionalProperties: false },
119
+ operation: { pathParams, queryParams, bodyFields },
120
+ };
121
+ }
122
+ export function projectTools() {
123
+ const tools = [];
124
+ const index = new Map();
125
+ const seenNames = new Set();
126
+ for (const [path, methods] of Object.entries(SPEC.paths ?? {})) {
127
+ for (const method of HTTP_METHODS) {
128
+ const op = methods[method];
129
+ if (!op?.operationId)
130
+ continue;
131
+ if (seenNames.has(op.operationId)) {
132
+ throw new Error(`Duplicate operationId in spec: ${op.operationId}`);
133
+ }
134
+ seenNames.add(op.operationId);
135
+ const { schema, operation } = buildInputSchema(op);
136
+ const description = [op.summary, op.description].filter(Boolean).join(' — ') ||
137
+ `${method.toUpperCase()} ${path}`;
138
+ tools.push({ name: op.operationId, description, inputSchema: schema });
139
+ index.set(op.operationId, {
140
+ toolName: op.operationId,
141
+ path,
142
+ method,
143
+ ...operation,
144
+ });
145
+ }
146
+ }
147
+ return { tools, index };
148
+ }
149
+ export function getSpecInfo() {
150
+ return {
151
+ paths: Object.keys(SPEC.paths ?? {}).length,
152
+ schemas: Object.keys(SPEC.components?.schemas ?? {}).length,
153
+ };
154
+ }
package/dist/spec.json ADDED
@@ -0,0 +1 @@
1
+ {"openapi":"3.1.0","info":{"title":"Oriva Platform API","version":"1.0.0","description":"Public API for 3rd party Oriva developers. Authenticate with your API key as a Bearer token."},"servers":[{"url":"https://api.oriva.io","description":"Production"},{"url":"http://localhost:3002","description":"Local BFF proxy"}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"http","scheme":"bearer","description":"API key with oriva_pk_ prefix"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}},"schemas":{"AuthSessionResponse":{"type":"object","properties":{"user":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":["string","null"],"format":"email"},"display_name":{"type":["string","null"]},"username":{"type":["string","null"]},"subscription_tier":{"type":"string"},"created_at":{"type":"string","format":"date-time"}},"required":["id","email","display_name","username","subscription_tier","created_at"]},"access_token":{"type":"string"},"refresh_token":{"type":"string"},"expires_in":{"type":"integer"}},"required":["user","access_token","refresh_token","expires_in"]},"AuthProfileResponse":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":["string","null"],"format":"email"},"displayName":{"type":"string"},"avatar":{"type":["string","null"],"format":"uri"},"authType":{"type":"string"},"permissions":{"type":"array","items":{"type":"string"}},"lastLogin":{"type":"string","format":"date-time"},"accountStatus":{"type":"string"},"twoFactorEnabled":{"type":"boolean"},"emailVerified":{"type":"boolean"}},"required":["id","email","displayName","avatar","authType","permissions","lastLogin","accountStatus","twoFactorEnabled","emailVerified"]}},"required":["ok","success","data"]},"RawProfileResponse":{"type":"object","properties":{"id":{"type":"string"},"display_name":{"type":["string","null"]},"username":{"type":["string","null"]},"bio":{"type":["string","null"]},"avatar_url":{"type":["string","null"],"format":"uri"},"location":{"type":["string","null"]},"website_url":{"type":["string","null"],"format":"uri"}},"required":["id","display_name","username","bio","avatar_url","location","website_url"]},"TokenRefreshResponse":{"type":"object","properties":{"access_token":{"type":"string"},"refresh_token":{"type":"string"},"expires_in":{"type":"integer"}},"required":["access_token","refresh_token","expires_in"]},"ProfileSummary":{"type":"object","properties":{"profileId":{"type":"string","example":"ext_a1b2c3d4e5f6a7b8"},"profileName":{"type":"string"},"isActive":{"type":"boolean"},"avatar":{"type":["string","null"],"format":"uri"},"isDefault":{"type":"boolean"}},"required":["profileId","profileName","isActive","avatar","isDefault"]},"UpdatedProfile":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"object","properties":{"profileId":{"type":"string"},"profileName":{"type":"string"},"isActive":{"type":"boolean"},"avatar":{"type":["string","null"],"format":"uri"},"bio":{"type":["string","null"]},"location":{"type":["string","null"]},"updatedAt":{"type":"string","format":"date-time"}},"required":["profileId","profileName","isActive","avatar","bio","location","updatedAt"]},"message":{"type":"string"}},"required":["ok","success","data"]},"GroupSummary":{"type":"object","properties":{"groupId":{"type":"string","format":"uuid"},"groupName":{"type":"string"},"memberCount":{"type":"integer"},"isActive":{"type":"boolean"},"role":{"type":"string","example":"admin"},"description":{"type":["string","null"]},"image_url":{"type":["string","null"],"format":"uri"},"external_link":{"type":["string","null"],"format":"uri"}},"required":["groupId","groupName","memberCount","isActive","role","description","image_url","external_link"]},"GroupMember":{"type":"object","properties":{"memberId":{"type":"string","format":"uuid"},"displayName":{"type":"string"},"role":{"type":"string"},"joinedAt":{"type":"string","format":"date-time"},"avatar":{"type":["string","null"],"format":"uri"}},"required":["memberId","displayName","role","joinedAt","avatar"]},"UserMe":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"username":{"type":["string","null"]},"displayName":{"type":"string"},"email":{"type":["string","null"],"format":"email"},"bio":{"type":["string","null"]},"location":{"type":["string","null"]},"website":{"type":["string","null"],"format":"uri"},"avatar":{"type":["string","null"],"format":"uri"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"apiKeyInfo":{"type":"object","properties":{"keyId":{"type":"string"},"name":{"type":"string"},"userId":{"type":"string","format":"uuid"},"permissions":{"type":"array","items":{"type":"string"}},"usageCount":{"type":"integer"}},"required":["keyId","name","userId","permissions","usageCount"]}},"required":["id","username","displayName","email","bio","location","website","avatar","createdAt","updatedAt","apiKeyInfo"]}},"required":["ok","success","data"]},"AnalyticsSummary":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"object","properties":{"overview":{"type":"object","properties":{"totalEntries":{"type":"integer"},"totalResponses":{"type":"integer"},"totalGroups":{"type":"integer"},"installedApps":{"type":"integer"}},"required":["totalEntries","totalResponses","totalGroups","installedApps"]},"metrics":{"type":"object","properties":{"entriesGrowth":{"type":"string"},"responseGrowth":{"type":"string"},"groupActivity":{"type":"string"},"appUsage":{"type":"string"}},"required":["entriesGrowth","responseGrowth","groupActivity","appUsage"]},"recentActivity":{"type":"array","items":{}},"timeRange":{"type":"object","properties":{"start":{"type":"string","format":"date-time"},"end":{"type":"string","format":"date-time"}},"required":["start","end"]}},"required":["overview","metrics","recentActivity","timeRange"]},"message":{"type":"string"}},"required":["ok","success","data"]},"TeamMember":{"type":"object","properties":{"profileId":{"type":"string","format":"uuid"},"displayName":{"type":["string","null"]},"username":{"type":["string","null"]},"avatarUrl":{"type":["string","null"],"format":"uri"},"role":{"type":"string"},"joinedAt":{"type":"string","format":"date-time"},"group":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}},"required":["id","name"]}},"required":["profileId","displayName","username","avatarUrl","role","joinedAt","group"]},"MarketplaceApp":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"external_id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"tagline":{"type":["string","null"]},"description":{"type":["string","null"]},"category":{"type":"string"},"icon_url":{"type":["string","null"],"format":"uri"},"screenshots":{"type":["array","null"],"items":{"type":"string","format":"uri"}},"version":{"type":"string"},"pricing_model":{"type":"string"},"pricing_config":{"type":["object","null"],"additionalProperties":{}},"install_count":{"type":"integer"},"developer_id":{"type":"string","format":"uuid"},"developer_name":{"type":"string"},"status":{"type":"string","enum":["draft","pending_review","approved","rejected"]},"is_active":{"type":"boolean"},"supported_audience":{"type":["array","null"],"items":{"type":"string"}},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","slug","category","version","pricing_model","install_count","developer_id","developer_name","status","is_active","created_at","updated_at"]},"InstalledApp":{"type":"object","properties":{"installationId":{"type":"string","format":"uuid"},"installedAt":{"type":"string","format":"date-time"},"isActive":{"type":"boolean"},"settings":{"type":["object","null"],"additionalProperties":{}},"app":{"$ref":"#/components/schemas/MarketplaceApp"}},"required":["installationId","installedAt","isActive","settings","app"]},"MarketplaceItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"content":{"type":["string","null"]},"profile_id":{"type":"string","format":"uuid"},"entry_type":{"type":"string"},"marketplace_metadata":{"type":["object","null"],"additionalProperties":{}},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","title","profile_id","entry_type","created_at","updated_at"]},"Category":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":["string","null"]},"collection_type":{"type":"string"},"organization_rules":{"type":["object","null"],"additionalProperties":{}},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","collection_type","created_at","updated_at"]},"Entry":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"content":{"type":"string"},"profile_id":{"type":"string","format":"uuid"},"audience_type":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","title","content","profile_id","audience_type","created_at","updated_at"]},"Event":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"description":{"type":"string"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"location":{"type":["string","null"]},"isOnline":{"type":"boolean"},"category":{"type":"string"},"organizer":{"type":"string","format":"uuid"},"maxAttendees":{"type":["integer","null"]},"currentAttendees":{"type":["integer","null"]},"price":{"type":"number","minimum":0},"tags":{"type":"array","items":{"type":"string"}},"imageUrl":{"type":["string","null"],"format":"uri"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["id","title","description","startDate","endDate","location","isOnline","category","organizer","maxAttendees","currentAttendees","price","tags","imageUrl","createdAt","updatedAt"]},"CreatedPersonalToken":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"token":{"type":"string","description":"Full token value — displayed ONCE. Store it now; it cannot be retrieved again."},"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"prefix":{"type":"string","description":"First 20 characters of the token, safe to display later."},"created_at":{"type":"string","format":"date-time"},"expires_at":{"type":["string","null"],"format":"date-time"}},"required":["token","id","name","prefix","created_at","expires_at"]}},"required":["ok","success","data"]},"PersonalTokenSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"prefix":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"last_used_at":{"type":["string","null"],"format":"date-time"},"expires_at":{"type":["string","null"],"format":"date-time"},"is_active":{"type":"boolean"}},"required":["id","name","prefix","created_at","last_used_at","expires_at","is_active"]},"PersonalTokenList":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"success":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/PersonalTokenSummary"}}},"required":["ok","success","data"]}},"parameters":{}},"paths":{"/api/v1/auth/register":{"post":{"operationId":"register","tags":["Auth"],"summary":"Register a new user","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":8,"pattern":"[A-Z]"},"name":{"type":"string"},"username":{"type":"string"},"preferences":{}},"required":["email","password"]}}}},"responses":{"201":{"description":"User registered and session created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthSessionResponse"}}}},"400":{"description":"Validation error — weak password or invalid email"},"409":{"description":"Email already registered"}}}},"/api/v1/auth/login":{"post":{"operationId":"login","tags":["Auth"],"summary":"Log in","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":1}},"required":["email","password"]}}}},"responses":{"200":{"description":"Login successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthSessionResponse"}}}},"400":{"description":"Validation error — missing email or password"},"401":{"description":"Invalid credentials"}}}},"/api/v1/auth/logout":{"post":{"operationId":"logout","tags":["Auth"],"summary":"Log out","description":"Invalidates the current session. Requires Authorization header.","security":[{"BearerAuth":[]}],"responses":{"204":{"description":"Logged out"},"401":{"description":"Missing authorization header"}}}},"/api/v1/auth/token/refresh":{"post":{"operationId":"refreshToken","tags":["Auth"],"summary":"Refresh access token","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"refresh_token":{"type":"string","minLength":1}},"required":["refresh_token"]}}}},"responses":{"200":{"description":"New tokens issued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenRefreshResponse"}}}},"400":{"description":"Validation error — missing refresh_token"},"401":{"description":"Invalid or expired refresh token"}}}},"/api/v1/auth/profile":{"get":{"operationId":"getAuthProfile","tags":["Auth"],"summary":"Get auth profile","description":"Returns the authenticated user identity and API key metadata.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"Auth profile","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthProfileResponse"}}}},"401":{"description":"Unauthorized"}}},"patch":{"operationId":"patchAuthProfile","tags":["Auth"],"summary":"Update profile (partial)","security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"bio":{"type":"string"},"avatar_url":{"type":"string","format":"uri"},"location":{"type":"string"},"website_url":{"type":"string","format":"uri"}}}}}},"responses":{"200":{"description":"Updated profile row","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RawProfileResponse"}}}},"400":{"description":"No fields provided"},"401":{"description":"Unauthorized"}}},"put":{"operationId":"putAuthProfile","tags":["Auth"],"summary":"Update profile (full)","description":"Superset of PATCH — also accepts preferences and data_retention_days (min 30).","security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"bio":{"type":"string"},"avatar_url":{"type":"string","format":"uri"},"location":{"type":"string"},"website_url":{"type":"string","format":"uri"},"preferences":{"type":"object","additionalProperties":{}},"data_retention_days":{"type":"integer","minimum":30}}}}}},"responses":{"200":{"description":"Updated profile row","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RawProfileResponse"}}}},"400":{"description":"No fields provided, or data_retention_days < 30"},"401":{"description":"Unauthorized"}}}},"/api/v1/auth/account":{"delete":{"operationId":"deleteAccount","tags":["Auth"],"summary":"Delete account","security":[{"BearerAuth":[]}],"responses":{"204":{"description":"Account deleted"},"401":{"description":"Unauthorized"}}}},"/api/v1/profiles/available":{"get":{"operationId":"listProfiles","tags":["Profiles"],"summary":"List available profiles","description":"Returns all non-anonymous active profiles for the authenticated API key.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Available profiles","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ProfileSummary"}}},"required":["ok","success","data"]}}}},"401":{"description":"Invalid or missing API key"}}}},"/api/v1/profiles/active":{"get":{"operationId":"getActiveProfile","tags":["Profiles"],"summary":"Get active profile","description":"Returns the default active (non-anonymous) profile for the authenticated API key.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Active profile","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ProfileSummary"}},"required":["ok","success","data"]}}}},"401":{"description":"Invalid or missing API key"}}}},"/api/v1/profiles/{profileId}":{"put":{"operationId":"updateProfile","tags":["Profiles"],"summary":"Update a profile","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","pattern":"^ext_[a-f0-9]{16}$","example":"ext_a1b2c3d4e5f6a7b8"},"required":true,"name":"profileId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"profileName":{"type":"string","minLength":1},"avatar":{"type":"string","format":"uri"},"bio":{"type":["string","null"]},"location":{"type":["string","null"]}}}}}},"responses":{"200":{"description":"Profile updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatedProfile"}}}},"400":{"description":"Validation error — invalid profileId format or body"},"401":{"description":"Invalid or missing API key"}}}},"/api/v1/profiles/{profileId}/activate":{"post":{"operationId":"activateProfile","tags":["Profiles"],"summary":"Activate a profile","description":"Switches the active profile to the specified profile.","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","pattern":"^ext_[a-f0-9]{16}$","example":"ext_a1b2c3d4e5f6a7b8"},"required":true,"name":"profileId","in":"path"}],"responses":{"200":{"description":"Profile activated","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"object","properties":{"activeProfile":{"type":"string"},"switchedAt":{"type":"string","format":"date-time"}},"required":["activeProfile","switchedAt"]}},"required":["ok","success","data"]}}}},"400":{"description":"Validation error — invalid profileId format"},"401":{"description":"Invalid or missing API key"}}}},"/api/v1/groups":{"get":{"operationId":"listGroups","tags":["Groups"],"summary":"List groups","description":"Returns all groups the authenticated user created or joined.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"User groups","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/GroupSummary"}}},"required":["ok","success","data"]}}}},"401":{"description":"Invalid or missing API key"}}}},"/api/v1/groups/{groupId}/members":{"get":{"operationId":"listGroupMembers","tags":["Groups"],"summary":"Get group members","description":"Returns members of a group. Requires the caller to be the creator or a member.","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"},"required":true,"name":"groupId","in":"path"}],"responses":{"200":{"description":"Group members","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/GroupMember"}}},"required":["ok","success","data"]}}}},"400":{"description":"Validation error — invalid groupId format"},"401":{"description":"Invalid or missing API key"},"403":{"description":"Not a member or creator of this group"},"404":{"description":"Group not found"}}}},"/api/v1/user/me":{"get":{"operationId":"getCurrentUser","tags":["User"],"summary":"Get current user","description":"Returns the authenticated user's active profile and API key metadata.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Current user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserMe"}}}},"401":{"description":"Invalid or missing API key"}}}},"/api/v1/analytics/summary":{"get":{"operationId":"getAnalyticsSummary","tags":["Analytics"],"summary":"Get analytics summary","description":"Returns 7-day usage analytics for the authenticated API key.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Analytics summary","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyticsSummary"}}}},"401":{"description":"Invalid or missing API key"}}}},"/api/v1/sessions":{"get":{"operationId":"listSessions","tags":["Sessions"],"summary":"List sessions","description":"Returns the user's sessions. Sessions feature is not yet implemented — returns empty paginated list.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Sessions list (currently always empty)","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"array","items":{}},"meta":{"type":"object","properties":{"pagination":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"totalPages":{"type":"integer"}},"required":["page","limit","total","totalPages"]}},"required":["pagination"]},"message":{"type":"string"}},"required":["ok","success","data","meta"]}}}},"401":{"description":"Invalid or missing API key"}}}},"/api/v1/sessions/upcoming":{"get":{"operationId":"listUpcomingSessions","tags":["Sessions"],"summary":"List upcoming sessions","description":"Returns upcoming sessions. Sessions feature is not yet implemented — returns empty list.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Upcoming sessions (currently always empty)","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"array","items":{}},"message":{"type":"string"}},"required":["ok","success","data"]}}}},"401":{"description":"Invalid or missing API key"}}}},"/api/v1/team/members":{"get":{"operationId":"listTeamMembers","tags":["Team"],"summary":"List team members","description":"Returns group memberships for the authenticated user as a flat list of team members.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Team members","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/TeamMember"}},"meta":{"type":"object","properties":{"total":{"type":"integer"},"roles":{"type":"array","items":{"type":"string"}}},"required":["total","roles"]}},"required":["ok","success","data","meta"]}}}},"401":{"description":"Invalid or missing API key"}}}},"/api/v1/marketplace/apps":{"get":{"operationId":"listMarketplaceApps","tags":["Marketplace"],"summary":"Browse marketplace apps","description":"Returns paginated list of approved, active marketplace apps.","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"integer","minimum":1,"maximum":100},"required":false,"name":"limit","in":"query"},{"schema":{"type":"integer","minimum":0},"required":false,"name":"offset","in":"query"},{"schema":{"type":"string"},"required":false,"name":"category","in":"query"},{"schema":{"type":"string"},"required":false,"name":"search","in":"query"}],"responses":{"200":{"description":"Marketplace apps","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/MarketplaceApp"}},"meta":{"type":"object","properties":{"pagination":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"totalPages":{"type":"integer"}},"required":["page","limit","total","totalPages"]}},"required":["pagination"]}},"required":["ok","success","data","meta"]}}}},"401":{"description":"Invalid or missing API key"}}}},"/api/v1/marketplace/trending":{"get":{"operationId":"listTrendingApps","tags":["Marketplace"],"summary":"Trending apps","description":"Returns approved apps sorted by install count (top 20).","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Trending apps","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/MarketplaceApp"}}},"required":["ok","success","data"]}}}},"401":{"description":"Invalid or missing API key"}}}},"/api/v1/marketplace/featured":{"get":{"operationId":"listFeaturedApps","tags":["Marketplace"],"summary":"Featured apps","description":"Returns featured approved apps (curated subset of top apps).","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Featured apps","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/MarketplaceApp"}}},"required":["ok","success","data"]}}}},"401":{"description":"Invalid or missing API key"}}}},"/api/v1/marketplace/categories":{"get":{"operationId":"listMarketplaceCategories","tags":["Marketplace"],"summary":"List app categories","description":"Returns distinct categories from approved apps with their app counts. Note: a second registration of this path exists (no auth, collections-table version) but is shadowed by this route (Express first-match).","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Category counts","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"category":{"type":"string"},"count":{"type":"integer"}},"required":["category","count"]}}},"required":["ok","success","data"]}}}},"401":{"description":"Invalid or missing API key"}}}},"/api/v1/marketplace/apps/{appId}":{"get":{"operationId":"getMarketplaceApp","tags":["Marketplace"],"summary":"Get app details","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"appId","in":"path"}],"responses":{"200":{"description":"App details","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/MarketplaceApp"}},"required":["ok","success","data"]}}}},"401":{"description":"Invalid or missing API key"},"404":{"description":"App not found"}}}},"/api/v1/marketplace/installed":{"get":{"operationId":"listInstalledApps","tags":["Marketplace"],"summary":"List installed apps","description":"Returns all apps installed by the authenticated user.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"integer","minimum":1,"maximum":100},"required":false,"name":"limit","in":"query"},{"schema":{"type":"integer","minimum":0},"required":false,"name":"offset","in":"query"}],"responses":{"200":{"description":"Installed apps","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/InstalledApp"}},"meta":{"type":"object","properties":{"pagination":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"totalPages":{"type":"integer"}},"required":["page","limit","total","totalPages"]}},"required":["pagination"]}},"required":["ok","success","data","meta"]}}}},"401":{"description":"Unauthorized"}}}},"/api/v1/marketplace/install/{appId}":{"post":{"operationId":"installMarketplaceApp","tags":["Marketplace"],"summary":"Install app","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"appId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"settings":{"type":"object","additionalProperties":{}}}}}}},"responses":{"200":{"description":"App installed","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/InstalledApp"},"message":{"type":"string"}},"required":["ok","success","data","message"]}}}},"401":{"description":"Unauthorized"},"404":{"description":"App not found or not available for installation"},"409":{"description":"App already installed"}}}},"/api/v1/marketplace/uninstall/{appId}":{"delete":{"operationId":"uninstallMarketplaceApp","tags":["Marketplace"],"summary":"Uninstall app","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"appId","in":"path"}],"responses":{"200":{"description":"App uninstalled","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"message":{"type":"string"}},"required":["ok","success","message"]}}}},"401":{"description":"Unauthorized"},"404":{"description":"App installation not found"}}}},"/api/v1/marketplace/items":{"get":{"operationId":"listMarketplaceItems","tags":["Marketplace"],"summary":"List marketplace items","description":"Public listing of published marketplace items (entry-based). No authentication required.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":100},"required":false,"name":"limit","in":"query"},{"schema":{"type":"integer","minimum":0},"required":false,"name":"offset","in":"query"},{"schema":{"type":"string"},"required":false,"name":"item_type","in":"query"},{"schema":{"type":"string"},"required":false,"name":"earner_type","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"category_id","in":"query"},{"schema":{"type":"number"},"required":false,"name":"min_price","in":"query"},{"schema":{"type":"number"},"required":false,"name":"max_price","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"seller_id","in":"query"},{"schema":{"type":"string"},"required":false,"name":"search","in":"query"}],"responses":{"200":{"description":"Marketplace items","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/MarketplaceItem"}},"meta":{"type":"object","properties":{"pagination":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"totalPages":{"type":"integer"}},"required":["page","limit","total","totalPages"]}},"required":["pagination"]}},"required":["ok","success","data","meta"]}}}}}}},"/api/v1/marketplace/items/{id}":{"get":{"operationId":"getMarketplaceItem","tags":["Marketplace"],"summary":"Get marketplace item","description":"Returns a single published marketplace item by ID.","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Marketplace item","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/MarketplaceItem"}},"required":["ok","success","data"]}}}},"404":{"description":"Item not found"}}}},"/api/v1/marketplace/search":{"post":{"operationId":"searchMarketplace","tags":["Marketplace"],"summary":"Search marketplace","description":"Full-text search across marketplace items. No authentication required.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"query":{"type":"string","minLength":1},"filters":{"type":"object","additionalProperties":{}},"limit":{"type":"integer","maximum":100},"offset":{"type":"integer"}},"required":["query"]}}}},"responses":{"200":{"description":"Search results","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/MarketplaceItem"}},"meta":{"type":"object","properties":{"pagination":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"totalPages":{"type":"integer"}},"required":["page","limit","total","totalPages"]}},"required":["pagination"]}},"required":["ok","success","data","meta"]}}}}}}},"/api/v1/marketplace/categories/tree":{"get":{"operationId":"getCategoryTree","tags":["Marketplace"],"summary":"Category tree","description":"Returns marketplace categories as a hierarchical tree from the collections table. Children are nested category objects of the same shape.","responses":{"200":{"description":"Category tree","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/Category"},{"type":"object","properties":{"children":{"type":"array","items":{"type":"object","additionalProperties":{}}}}}]}}},"required":["ok","success","data"]}}}}}}},"/api/v1/marketplace/categories/{id}":{"get":{"operationId":"getMarketplaceCategory","tags":["Marketplace"],"summary":"Get category","description":"Returns a single marketplace category from the collections table.","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Category","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Category"}},"required":["ok","success","data"]}}}},"404":{"description":"Category not found"}}}},"/api/v1/developer/apps":{"get":{"operationId":"listDeveloperApps","tags":["Developer"],"summary":"List developer apps","description":"Returns all marketplace apps owned by the authenticated developer.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Developer apps","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/MarketplaceApp"}}},"required":["ok","success","data"]}}}},"401":{"description":"Invalid or missing API key"}}},"post":{"operationId":"createDeveloperApp","tags":["Developer"],"summary":"Create app","security":[{"ApiKeyAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"slug":{"type":"string","minLength":1},"tagline":{"type":"string"},"description":{"type":"string"},"category":{"type":"string","minLength":1},"icon_url":{"type":"string","format":"uri"},"screenshots":{"type":"array","items":{"type":"string","format":"uri"}},"version":{"type":"string","default":"1.0.0"},"pricing_model":{"type":"string","default":"free"},"pricing_config":{"type":"object","additionalProperties":{}},"supported_audience":{"type":"array","items":{"type":"string"}}},"required":["name","slug","category"]}}}},"responses":{"201":{"description":"App created (status: draft)","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/MarketplaceApp"}},"required":["ok","success","data"]}}}},"401":{"description":"Invalid or missing API key"}}}},"/api/v1/developer/apps/{appId}":{"get":{"operationId":"getDeveloperApp","tags":["Developer"],"summary":"Get developer app","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"appId","in":"path"}],"responses":{"200":{"description":"App details","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/MarketplaceApp"}},"required":["ok","success","data"]}}}},"401":{"description":"Invalid or missing API key"},"404":{"description":"App not found or not owned by this developer"}}},"put":{"operationId":"updateDeveloperApp","tags":["Developer"],"summary":"Update app","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"appId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"slug":{"type":"string","minLength":1},"tagline":{"type":"string"},"description":{"type":"string"},"category":{"type":"string"},"icon_url":{"type":"string","format":"uri"},"screenshots":{"type":"array","items":{"type":"string","format":"uri"}},"version":{"type":"string"},"pricing_model":{"type":"string"},"pricing_config":{"type":"object","additionalProperties":{}},"supported_audience":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"App updated","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/MarketplaceApp"}},"required":["ok","success","data"]}}}},"401":{"description":"Invalid or missing API key"},"404":{"description":"App not found or not owned by this developer"}}},"delete":{"operationId":"deleteDeveloperApp","tags":["Developer"],"summary":"Delete app","description":"Only draft apps can be deleted. Approved apps must be deactivated first.","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"appId","in":"path"}],"responses":{"204":{"description":"App deleted"},"400":{"description":"App is not in draft status — cannot delete"},"401":{"description":"Invalid or missing API key"},"404":{"description":"App not found or not owned by this developer"}}}},"/api/v1/developer/apps/{appId}/submit":{"post":{"operationId":"submitDeveloperApp","tags":["Developer"],"summary":"Submit app for review","description":"Transitions app from draft → pending_review.","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"appId","in":"path"}],"responses":{"200":{"description":"App submitted for review","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/MarketplaceApp"},"message":{"type":"string"}},"required":["ok","success","data","message"]}}}},"400":{"description":"App is not in draft status"},"401":{"description":"Invalid or missing API key"},"404":{"description":"App not found or not owned by this developer"}}}},"/api/v1/developer/apps/{appId}/resubmit":{"post":{"operationId":"resubmitDeveloperApp","tags":["Developer"],"summary":"Resubmit rejected app","description":"Updates fields and resubmits a rejected app for review.","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"appId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"slug":{"type":"string","minLength":1},"tagline":{"type":"string"},"description":{"type":"string"},"category":{"type":"string"},"icon_url":{"type":"string","format":"uri"},"screenshots":{"type":"array","items":{"type":"string","format":"uri"}},"version":{"type":"string"},"pricing_model":{"type":"string"},"pricing_config":{"type":"object","additionalProperties":{}},"supported_audience":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"App resubmitted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/MarketplaceApp"},"message":{"type":"string"}},"required":["ok","success","data","message"]}}}},"400":{"description":"App is not in rejected status"},"401":{"description":"Invalid or missing API key"},"404":{"description":"App not found or not owned by this developer"}}}},"/api/v1/entries":{"get":{"operationId":"listEntries","tags":["Entries"],"summary":"List entries","description":"Returns paginated entries for the authenticated user's profile.","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"required":false,"name":"limit","in":"query"},{"schema":{"type":"integer","minimum":0,"default":0},"required":false,"name":"offset","in":"query"}],"responses":{"200":{"description":"Entries list","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Entry"}},"meta":{"type":"object","properties":{"pagination":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"totalPages":{"type":"integer"}},"required":["page","limit","total","totalPages"]}},"required":["pagination"]}},"required":["ok","success","data","meta"]}}}},"401":{"description":"Invalid or missing API key"},"404":{"description":"User profile not found"}}}},"/api/v1/templates":{"get":{"operationId":"listTemplates","tags":["Entries"],"summary":"List templates","description":"Returns available entry templates. Not yet implemented — returns empty list.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Templates list (currently always empty)","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"array","items":{}},"meta":{"type":"object","properties":{"pagination":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"totalPages":{"type":"integer"}},"required":["page","limit","total","totalPages"]}},"required":["pagination"]}},"required":["ok","success","data","meta"]}}}},"401":{"description":"Invalid or missing API key"}}}},"/api/v1/storage":{"get":{"operationId":"getStorage","tags":["Entries"],"summary":"Get storage info","description":"Returns storage usage for the authenticated user. Not yet implemented — returns empty object.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Storage info","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"object","additionalProperties":{}}},"required":["ok","success","data"]}}}},"401":{"description":"Invalid or missing API key"}}}},"/api/v1/ui/notifications":{"post":{"operationId":"createUiNotification","tags":["UI"],"summary":"Create notification","description":"Creates a UI notification for the authenticated user.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Notification created","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]}},"required":["ok","success","data"]}}}},"401":{"description":"Invalid or missing API key"}}}},"/api/oriva/events":{"get":{"operationId":"listEvents","tags":["Events"],"summary":"List events","description":"Returns active events with optional filtering by category and date range.","parameters":[{"schema":{"type":"string"},"required":false,"name":"category","in":"query"},{"schema":{"type":"string","format":"date-time"},"required":false,"name":"startDate","in":"query"},{"schema":{"type":"string","format":"date-time"},"required":false,"name":"endDate","in":"query"},{"schema":{"type":"integer","maximum":100,"default":20},"required":false,"name":"limit","in":"query"},{"schema":{"type":"integer","minimum":0,"default":0},"required":false,"name":"offset","in":"query"}],"responses":{"200":{"description":"Events list","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Event"}},"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}},"required":["ok","success","data","total","limit","offset"]}}}}}},"post":{"operationId":"createEvent","tags":["Events"],"summary":"Create event","security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1},"description":{"type":"string","minLength":1},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"isOnline":{"type":"boolean"},"category":{"type":"string","minLength":1},"location":{"type":["string","null"]},"maxAttendees":{"type":["integer","null"],"exclusiveMinimum":0},"price":{"type":"number","minimum":0},"tags":{"type":"array","items":{"type":"string"},"maxItems":20},"imageUrl":{"type":["string","null"],"format":"uri"}},"required":["title","description","startDate","endDate","isOnline","category"]}}}},"responses":{"201":{"description":"Event created","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Event"},"message":{"type":"string"}},"required":["ok","success","data","message"]}}}},"400":{"description":"Validation error — missing required fields or invalid category"},"401":{"description":"Unauthorized"}}}},"/api/oriva/events/{eventId}":{"get":{"operationId":"getEvent","tags":["Events"],"summary":"Get event","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"eventId","in":"path"}],"responses":{"200":{"description":"Event details","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Event"}},"required":["ok","success","data"]}}}},"404":{"description":"Event not found"}}}},"/api/v1/me/tokens":{"post":{"operationId":"createPersonalAccessToken","tags":["PersonalTokens"],"summary":"Create a Personal Access Token","description":"Issues a new Personal Access Token (PAT) scoped to the authenticated account. The full token value is returned **once** and cannot be retrieved again. PATs carry broad `[\"read\",\"write\"]` permissions and are intended for personal tooling (e.g. MCP servers).","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Human-readable label for the token"},"expires_at":{"type":"string","format":"date-time","description":"ISO 8601 expiry date. Omit for a non-expiring token."}},"required":["name"]}}}},"responses":{"201":{"description":"Token created — contains the full one-time token value","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedPersonalToken"}}}},"400":{"description":"Validation error"},"401":{"description":"Not authenticated"}}},"get":{"operationId":"listPersonalAccessTokens","tags":["PersonalTokens"],"summary":"List Personal Access Tokens","description":"Returns all PATs belonging to the authenticated account. Full token values and hashes are never returned.","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"List of PATs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PersonalTokenList"}}}},"401":{"description":"Not authenticated"}}}},"/api/v1/me/tokens/{id}":{"delete":{"operationId":"revokePersonalAccessToken","tags":["PersonalTokens"],"summary":"Revoke a Personal Access Token","description":"Sets `is_active = false` on the token (soft-delete). The row is preserved for audit purposes. Returns 404 if the token does not belong to this account.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"204":{"description":"Revoked successfully"},"401":{"description":"Not authenticated"},"404":{"description":"Token not found or does not belong to this account"}}}}},"webhooks":{}}
@@ -0,0 +1,64 @@
1
+ export type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head' | 'options';
2
+ export interface JsonSchema {
3
+ type?: string | string[];
4
+ description?: string;
5
+ properties?: Record<string, JsonSchema>;
6
+ required?: string[];
7
+ enum?: unknown[];
8
+ format?: string;
9
+ pattern?: string;
10
+ minimum?: number;
11
+ maximum?: number;
12
+ minLength?: number;
13
+ maxLength?: number;
14
+ example?: unknown;
15
+ items?: JsonSchema;
16
+ additionalProperties?: boolean | JsonSchema;
17
+ nullable?: boolean;
18
+ default?: unknown;
19
+ $ref?: string;
20
+ [k: string]: unknown;
21
+ }
22
+ export interface MCPTool {
23
+ name: string;
24
+ description: string;
25
+ inputSchema: JsonSchema;
26
+ }
27
+ export interface ProjectedOperation {
28
+ toolName: string;
29
+ path: string;
30
+ method: HttpMethod;
31
+ pathParams: string[];
32
+ queryParams: string[];
33
+ bodyFields: Array<{
34
+ alias: string;
35
+ original: string;
36
+ }>;
37
+ }
38
+ export type OperationIndex = Map<string, ProjectedOperation>;
39
+ export interface OpenApiParameter {
40
+ name: string;
41
+ in: 'path' | 'query' | 'header' | 'cookie';
42
+ required?: boolean;
43
+ schema?: JsonSchema;
44
+ description?: string;
45
+ }
46
+ export interface OpenApiOperation {
47
+ operationId?: string;
48
+ summary?: string;
49
+ description?: string;
50
+ parameters?: OpenApiParameter[];
51
+ requestBody?: {
52
+ required?: boolean;
53
+ content?: Record<string, {
54
+ schema?: JsonSchema;
55
+ }>;
56
+ };
57
+ }
58
+ export type OpenApiPaths = Record<string, Partial<Record<HttpMethod, OpenApiOperation>>>;
59
+ export interface OpenApiDoc {
60
+ paths: OpenApiPaths;
61
+ components?: {
62
+ schemas?: Record<string, JsonSchema>;
63
+ };
64
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@oriva/mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "Model Context Protocol server for the Oriva public API. Exposes all 46 public endpoints as MCP tools for use in Claude Code, Cursor, Continue, and Claude Desktop.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "oriva-mcp": "dist/index.js"
9
+ },
10
+ "main": "dist/index.js",
11
+ "files": [
12
+ "dist/**/*",
13
+ "README.md"
14
+ ],
15
+ "engines": {
16
+ "node": ">=18.0.0"
17
+ },
18
+ "scripts": {
19
+ "prebuild": "node scripts/copy-spec.mjs",
20
+ "build": "tsc && node scripts/post-build.mjs",
21
+ "start": "node dist/index.js"
22
+ },
23
+ "dependencies": {
24
+ "@modelcontextprotocol/sdk": "^1.6.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^20.0.0",
28
+ "typescript": "^5.4.0"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/0riva/o-platform.git",
33
+ "directory": "packages/mcp-server"
34
+ },
35
+ "homepage": "https://api.oriva.io",
36
+ "keywords": [
37
+ "mcp",
38
+ "model-context-protocol",
39
+ "oriva",
40
+ "claude",
41
+ "ai-tools"
42
+ ],
43
+ "publishConfig": {
44
+ "access": "public"
45
+ }
46
+ }