Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .agents/plugins/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "deriv-api-marketplace",
"interface": {
"displayName": "Deriv API"
},
"plugins": [
{
"name": "deriv-api",
"source": {
"source": "local",
"path": "./"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Developer Tools"
}
]
}
45 changes: 45 additions & 0 deletions .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"name": "deriv-api",
"version": "2.0.2",
"description": "Get live Deriv API schemas plus guides for authentication, subscriptions, trades, and errors.",
"author": {
"name": "Deriv",
"email": "api-support@deriv.com",
"url": "https://developers.deriv.com"
},
"homepage": "https://developers.deriv.com",
"repository": "https://github.com/deriv-com/deriv-api-plugin",
"license": "MIT",
"keywords": [
"deriv",
"trading",
"api",
"websocket",
"rest",
"options",
"finance",
"fintech"
],
"mcpServers": "./.mcp.json",
"interface": {
"displayName": "Deriv API (Beta)",
"shortDescription": "Get live Deriv API schemas",
"longDescription": "Get live Deriv API schemas plus guides for authentication, subscriptions, trades, and errors. Search endpoints, read current request and response fields, take worked examples, and validate payloads. The plugin does not sign in to a Deriv account, store keys, or place trades.",
"developerName": "Deriv",
"category": "Developer Tools",
"capabilities": ["Read"],
"websiteURL": "https://developers.deriv.com",
"privacyPolicyURL": "https://github.com/deriv-com/deriv-api-plugin/blob/master/PRIVACY.md",
"termsOfServiceURL": "https://github.com/deriv-com/deriv-api-plugin/blob/master/LICENSE",
"supportURL": "https://github.com/deriv-com/deriv-api-plugin/issues",
"brandColor": "#FF444F",
"defaultPrompt": [
"Look up the live request and response schema for a Deriv API endpoint",
"Validate this Deriv API request payload against the live schema",
"Explain Deriv API authentication, subscriptions, and error handling"
],
"composerIcon": "./assets/logo.png",
"logo": "./assets/logo.png",
"logoDark": "./assets/logo-dark.png"
}
}
134 changes: 133 additions & 1 deletion .github/workflows/verify-surface.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
// Contract: exit 0 when both checks pass; exit non-zero and print `FAIL:` lines
// otherwise.

import { readFileSync, readdirSync } from 'node:fs';
import { existsSync, readFileSync, readdirSync } from 'node:fs';
import { join, relative, sep } from 'node:path';
import { execFileSync } from 'node:child_process';

Expand All @@ -39,9 +39,13 @@ const ALLOW = new Set([
'.cursor-plugin/marketplace.json',
'.claude-plugin/plugin.json',
'.claude-plugin/marketplace.json',
'.codex-plugin/plugin.json',
'.agents/plugins/marketplace.json',
'rules/deriv-api-conventions.mdc',
'assets/logo.svg',
'assets/logo.png',
'assets/logo-dark.svg',
'assets/logo-dark.png',
'LICENSE',
'README.md',
'CONTRIBUTING.md',
Expand Down Expand Up @@ -173,6 +177,134 @@ for (const path of paths) {
}
}

// 4. Codex listing contract. Official package rules require a relative
// branding path (not an HTTPS URL), MCP declared as ./.mcp.json, and
// directory-submission length limits on the title and subtitle.
const CODEX_CATEGORIES = new Set([
'Productivity',
'Creativity',
'Developer Tools',
'Business & Operations',
'Data & Analytics',
'Communication',
'Education & Research',
'Security',
'Finance',
'Healthcare',
'Travel',
'Entertainment',
'Other',
]);
const HTTPS = /^https:\/\/[^/\s]+/;
const HEX = /^#[0-9A-Fa-f]{6}$/;
const SEMVER = /^\d+\.\d+\.\d+$/;
const loadJson = (path) => {
try {
return JSON.parse(readFileSync(join(root, path), 'utf8'));
} catch (err) {
fail(`codex listing: ${path} is not valid JSON (${err.message})`);
return null;
}
};
const requireHttps = (label, value) => {
if (typeof value !== 'string' || !HTTPS.test(value)) {
fail(`codex listing: ${label} must be an https URL`);
}
};
const requireAsset = (label, value) => {
if (typeof value !== 'string' || !value.startsWith('./') || value.includes('..')) {
fail(`codex listing: ${label} must be a relative path starting with ./`);
return;
}
if (/^https?:/i.test(value)) {
fail(`codex listing: ${label} must not be a URL`);
return;
}
const rel = value.slice(2);
if (!existsSync(join(root, rel))) fail(`codex listing: ${label} file missing (${rel})`);
};

const codex = loadJson('.codex-plugin/plugin.json');
if (codex) {
const iface = codex.interface && typeof codex.interface === 'object' ? codex.interface : null;
if (!iface) fail('codex listing: interface object is required');
if (codex.name !== 'deriv-api') fail('codex listing: name must be deriv-api');
if (typeof codex.version !== 'string' || !SEMVER.test(codex.version)) {
fail('codex listing: version must be semver');
}
if (typeof codex.description !== 'string' || !codex.description || codex.description.length > 1024) {
fail('codex listing: description is required and must be 1024 characters or fewer');
}
if (!codex.author || codex.author.name !== 'Deriv') fail('codex listing: author.name must be Deriv');
if (codex.mcpServers !== './.mcp.json') {
fail('codex listing: mcpServers must be ./.mcp.json so the hosted server is imported');
}
if (codex.skills != null) fail('codex listing: skills must be omitted; this plugin has no skills/ tree');
if (codex.apps != null) fail('codex listing: apps must be omitted; there is no .app.json');
if (iface) {
if (iface.developerName !== codex.author.name) {
fail('codex listing: interface.developerName must match author.name');
}
if (typeof iface.displayName !== 'string' || !iface.displayName || iface.displayName.length > 30) {
fail('codex listing: displayName must be 30 characters or fewer');
}
if (typeof iface.shortDescription !== 'string' || !iface.shortDescription || iface.shortDescription.includes('\n') || iface.shortDescription.length > 30) {
fail('codex listing: shortDescription must be one line of 30 characters or fewer');
}
if (typeof iface.longDescription !== 'string' || !iface.longDescription || iface.longDescription.length > 4000) {
fail('codex listing: longDescription is required');
}
if (!CODEX_CATEGORIES.has(iface.category)) fail('codex listing: category is not a supported Codex category');
if (!Array.isArray(iface.capabilities) || iface.capabilities.length === 0 || iface.capabilities.length > 20) {
fail('codex listing: capabilities must be a non-empty list of at most 20 entries');
}
if (typeof iface.brandColor !== 'string' || !HEX.test(iface.brandColor)) {
fail('codex listing: brandColor must be a six-digit hex color');
}
requireHttps('homepage', codex.homepage);
requireHttps('author.url', codex.author.url);
requireHttps('interface.websiteURL', iface.websiteURL);
requireHttps('interface.privacyPolicyURL', iface.privacyPolicyURL);
requireHttps('interface.termsOfServiceURL', iface.termsOfServiceURL);
requireHttps('interface.supportURL', iface.supportURL);
requireAsset('interface.logo', iface.logo);
requireAsset('interface.logoDark', iface.logoDark);
requireAsset('interface.composerIcon', iface.composerIcon);
const prompts = Array.isArray(iface.defaultPrompt) ? iface.defaultPrompt : [];
if (prompts.length === 0 || prompts.length > 3) fail('codex listing: defaultPrompt must contain 1–3 strings');
const seen = new Set();
for (const prompt of prompts) {
if (typeof prompt !== 'string' || !prompt || prompt.includes('\n') || prompt.length > 128) {
fail('codex listing: each defaultPrompt must be one line of 128 characters or fewer');
}
if (prompt.includes('@')) fail('codex listing: defaultPrompt must not contain @mentions');
const key = prompt.normalize('NFC').trim();
if (seen.has(key)) fail('codex listing: defaultPrompt entries must be unique');
seen.add(key);
}
}
const cursor = loadJson('.cursor-plugin/plugin.json');
if (cursor && cursor.version !== codex.version) {
fail('codex listing: version must stay in lockstep with .cursor-plugin/plugin.json');
}
}

const marketplace = loadJson('.agents/plugins/marketplace.json');
if (marketplace) {
const entry = Array.isArray(marketplace.plugins) ? marketplace.plugins[0] : null;
if (!entry) fail('codex listing: marketplace must declare one plugin entry');
else {
if (entry.name !== 'deriv-api') fail('codex listing: marketplace plugin name must be deriv-api');
if (!entry.source || entry.source.source !== 'local' || entry.source.path !== './') {
fail('codex listing: marketplace source must be local path ./');
}
if (!entry.policy || entry.policy.installation !== 'AVAILABLE' || entry.policy.authentication !== 'ON_INSTALL') {
fail('codex listing: marketplace policy must be AVAILABLE / ON_INSTALL');
}
if (entry.category !== 'Developer Tools') fail('codex listing: marketplace category must be Developer Tools');
}
}

if (failures.length > 0) {
for (const line of failures) console.error(`FAIL: ${line}`);
console.error(`\nverify-surface: ${failures.length} problem(s) found`);
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Deriv API plugin (Beta)

A plugin that gives an AI coding agent first-class knowledge of the Deriv API.
It is a **thin client**: it points both **Cursor** and **Claude Code** at a
It is a **thin client**: it points **Cursor**, **Claude Code**, and **Codex** at a
**hosted Deriv API MCP server** over HTTP, and ships one Cursor rule alongside
it. The hosted server exposes the tools that search endpoints; read the current
request/response schemas, fields, and worked examples; validate payloads; and
Expand Down Expand Up @@ -43,6 +43,14 @@ git clone https://github.com/deriv-com/deriv-api-plugin ~/.cursor/plugins/local/

Then reload the Cursor window so the plugin is picked up.

### Codex

```
codex plugin marketplace add deriv-com/deriv-api-plugin
```

That registers this repository as a Codex marketplace. Install **Deriv API (Beta)** from the Plugins Directory, then restart Codex so it picks up the hosted MCP server. To refresh an existing install after a listing change, run `codex plugin marketplace upgrade`.

### Upgrading from version 1

Version 1 shipped a local MCP server and skills under the same plugin name. If
Expand Down
Binary file added assets/logo-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions assets/logo-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading