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
90 changes: 90 additions & 0 deletions app/features/mcp-docs/lib/__tests__/example-answers.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { describe, expect, test } from 'vitest';

import { type AnswerBlock, answerCost, MCP_EXAMPLES, revealAnswer } from '../example-answers';

const BLOCKS: AnswerBlock[] = [
{ kind: 'text', text: 'abcdefghij' },
{
head: ['A', 'B'],
kind: 'table',
rows: [
['1', '2'],
['3', '4'],
['5', '6'],
],
},
{ kind: 'text', text: 'tail' },
];

describe('revealAnswer', () => {
test('should show nothing before the first character', () => {
expect(revealAnswer(BLOCKS, 0)).toEqual([]);
});

test('should type the first block one character at a time and stop there', () => {
const shown = revealAnswer(BLOCKS, 4);
expect(shown).toHaveLength(1);
expect(shown[0]).toMatchObject({ partial: true, text: 'abcd' });
});

test('should open the table on its frame, then fill rows in', () => {
const beforeTable = 10;
const frameOnly = revealAnswer(BLOCKS, beforeTable + 1);
expect(frameOnly).toHaveLength(2);
expect(frameOnly[1]).toMatchObject({ partial: true, rows: 0 });

// 24 for the frame, then 26 per row.
expect(revealAnswer(BLOCKS, beforeTable + 24 + 26)[1]).toMatchObject({ partial: true, rows: 1 });
expect(revealAnswer(BLOCKS, beforeTable + 24 + 26 * 2)[1]).toMatchObject({ partial: true, rows: 2 });
});

test('should never reveal a later block before an earlier one is complete', () => {
for (let revealed = 0; revealed <= answerCost(BLOCKS); revealed++) {
const shown = revealAnswer(BLOCKS, revealed);
const partials = shown.filter(entry => entry.partial);
expect(partials.length).toBeLessThanOrEqual(1);
// Only the last visible block may be mid-typing.
if (partials.length === 1) expect(shown.at(-1)?.partial).toBe(true);
}
});

test('should reveal every block completely at the full cost', () => {
const shown = revealAnswer(BLOCKS, answerCost(BLOCKS));
expect(shown).toHaveLength(BLOCKS.length);
expect(shown.every(entry => !entry.partial)).toBe(true);
expect(shown[0].text).toBe('abcdefghij');
expect(shown[1].rows).toBe(3);
expect(shown[2].text).toBe('tail');
});

test('should grow monotonically — text never shrinks, rows never go backwards', () => {
let lastText = 0;
let lastRows = 0;
for (let revealed = 0; revealed <= answerCost(BLOCKS); revealed++) {
const shown = revealAnswer(BLOCKS, revealed);
const text = shown.reduce((sum, entry) => sum + entry.text.length, 0);
const rows = shown.reduce((sum, entry) => sum + entry.rows, 0);
expect(text).toBeGreaterThanOrEqual(lastText);
expect(rows).toBeGreaterThanOrEqual(lastRows);
lastText = text;
lastRows = rows;
}
});
});

describe('MCP_EXAMPLES', () => {
test('should play every example back to its complete answer', () => {
for (const example of MCP_EXAMPLES) {
const shown = revealAnswer(example.answer, answerCost(example.answer));
expect(shown).toHaveLength(example.answer.length);
expect(shown.every(entry => !entry.partial)).toBe(true);
}
});

test('should keep ids unique and the short prompt no longer than the real question', () => {
expect(new Set(MCP_EXAMPLES.map(example => example.id)).size).toBe(MCP_EXAMPLES.length);
for (const example of MCP_EXAMPLES) {
expect(example.prompt.length).toBeLessThanOrEqual(example.question.length);
}
});
});
59 changes: 59 additions & 0 deletions app/features/mcp-docs/lib/cta-field-tuning.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
export type CtaFieldTuning = {
Comment thread
askov marked this conversation as resolved.
density: number;

drift: number;

falloff: number;

flight: number;

maxSpeed: number;

pullDensity: number;

softening: number;

spawn: number;
};

export const CTA_FIELD_TUNING_DEFAULTS: CtaFieldTuning = {
density: 1,
drift: 360,
falloff: 2,
flight: 1,
maxSpeed: 1800,
pullDensity: 5,
softening: 24,
spawn: 1100,
};

export type CtaFieldScope = 'base' | 'closingDesktop' | 'heroDesktop';

const CTA_FIELD_SCOPE_DEFAULTS: Record<Exclude<CtaFieldScope, 'base'>, CtaFieldTuning> = {
closingDesktop: {
density: 1,
drift: 360,
falloff: 2,
flight: 0.55,
maxSpeed: 960,
pullDensity: 2.5,
softening: 24,
spawn: 1100,
},
heroDesktop: {
density: 0.6,
drift: 320,
falloff: 1.3,
flight: 0.3,
maxSpeed: 360,
pullDensity: 1.5,
softening: 24,
spawn: 550,
},
};

export function resolveCtaField(scope: CtaFieldScope): CtaFieldTuning {
return scope === 'base'
? CTA_FIELD_TUNING_DEFAULTS
: { ...CTA_FIELD_TUNING_DEFAULTS, ...CTA_FIELD_SCOPE_DEFAULTS[scope] };
}
162 changes: 162 additions & 0 deletions app/features/mcp-docs/lib/example-answers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
export type AnswerBlock = { kind: 'text'; text: string } | { kind: 'table'; head: string[]; rows: string[][] };

export type McpExample = {
id: string;
prompt: string;
question: string;
answer: AnswerBlock[];
tool: string;
};

const TABLE_HEAD_COST = 24;
const TABLE_ROW_COST = 26;

export type RevealedBlock = { block: AnswerBlock; rows: number; text: string; partial: boolean };

export function blockCost(block: AnswerBlock): number {
return block.kind === 'text' ? block.text.length : TABLE_HEAD_COST + block.rows.length * TABLE_ROW_COST;
}

export function answerCost(blocks: AnswerBlock[]): number {
return blocks.reduce((sum, block) => sum + blockCost(block), 0);
}

export function revealAnswer(blocks: AnswerBlock[], revealed: number): RevealedBlock[] {
const shown: RevealedBlock[] = [];
let consumed = 0;

for (const block of blocks) {
const cost = blockCost(block);
const into = revealed - consumed;
if (into <= 0) break;

const partial = into < cost;
shown.push({
block,
partial,
rows:
block.kind === 'table'
? partial
? Math.max(0, Math.floor((into - TABLE_HEAD_COST) / TABLE_ROW_COST))
: block.rows.length
: 0,
text: block.kind === 'text' ? (partial ? block.text.slice(0, Math.floor(into)) : block.text) : '',
});

if (partial) break;
consumed += cost;
}

return shown;
}

export const MCP_EXAMPLES: McpExample[] = [
{
answer: [
{
kind: 'text',
text: '"Sent from my Pumpfun App" (App) — Token-2022 mint, 6 decimals, supply 912,905,455.031061, fixed. Only two extensions, and nobody can change either of them:',
},
{
head: ['Extension', 'Current state', 'Who can change it'],
kind: 'table',
rows: [
['metadataPointer', 'points at the mint itself', 'authority: null — nobody'],
['tokenMetadata', 'name, symbol, uri; no extra fields', 'updateAuthority: null — nobody'],
],
},
{
kind: 'text',
text: 'Mint and freeze authorities are null too, and the extensions that carry real issuer power — permanentDelegate, transferHook, transferFeeConfig, confidential transfers — are simply absent.',
Comment on lines +64 to +70

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Metadata caveat was removed

The Token-2022 answer says nobody can change the metadata and lists absent issuer powers, but no longer explains that control of the referenced off-chain metadata domain can still change the displayed name, image, and description. Restoring that distinction would keep readers from mistaking renounced on-chain authority for immutable presentation metadata.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

},
{
kind: 'text',
text: 'One caveat: the on-chain authority is renounced, but the tokenMetadata uri points off-chain — whoever controls that domain can still change the name, image, and description a wallet displays.',
},
],
id: 'token-2022',
prompt: 'Which Token-2022 extensions are enabled on 49nkLrXi…3kTa8pTL, and who can still change them?',
question:
'Which Token-2022 extensions are enabled on 49nkLrXi8nCZBVKsShDNasEtPe4Vn1mx9Xbr3kTa8pTL, and who can still change them?',
tool: 'inspect_entity',
},
{
answer: [
{
kind: 'text',
text: 'Squads Multisig Program (SQDS4ep65T…) — BPF upgradeable-loader program on mainnet-beta.',
},
{
head: ['Field', 'Value'],
kind: 'table',
rows: [
['Upgrade authority', 'null — revoked, so the program is frozen'],
['Last deployed', 'slot 302582236'],
['IDL', 'published on-chain via Anchor, served as Codama'],
],
},
{
kind: 'text',
text: 'The verified build matches Squads-Protocol/v4 @ 2a47b4c (signer sqdcVVoTcKZjXU8yPUwKFbGx1Hig1rhbWJQtMRXp2E1), and the embedded security.txt names OtterSec and Neodyme as auditors.',
},
],
id: 'program',
prompt: 'Who holds the upgrade authority for this program, and does the verified build match?',
question:
'Who holds the upgrade authority for SQDS4ep65T869zMMBKyuUq6aD6EgTu8psMjkvj52pCf, and does the verified build match?',
tool: 'inspect_entity',
},
{
answer: [
{
kind: 'text',
text: 'A v0 message, success, finalized at slot 439023338. One signer, fee 5000 lamports, 67,888 CU consumed; 53 accounts, 34 of them pulled in from 5 address lookup tables.',
},
{
kind: 'text',
text: 'There are no inner instructions anywhere — three invoke [1] lines in the logs and no [2] depth at all.',
},
{
head: ['#', 'Program', 'What it did'],
kind: 'table',
rows: [
['1', 'ComputeBudget', 'SetComputeUnitLimit 600,000 — no priority fee'],
['2', 'System', 'transfer 1002 lamports from the signer'],
['3', '8GCr9711…ViXk', '53 accounts, 154 bytes, 67,588 CU — unverified, no IDL, stays raw'],
],
},
{
kind: 'text',
text: 'A 53-account instruction touching Meteora DLMM and PumpSwap issued zero CPIs and still returned success — the consistent read (inference, not proof) is an arbitrage bot that found nothing worth executing.',
},
],
id: 'transaction',
prompt: 'Walk me through this signature, including the inner instructions.',
question:
'Walk me through 3MVAxtaFp76y23DBd3MdXTEjpzH8zFtVB1HtVdYSKqZPpx1R9gEkDXCF9bX26vkAvyerz2K54eMCFF7cPpkzArM1, including the inner instructions.',
tool: 'inspect_entity',
},
{
answer: [
{ kind: 'text', text: 'USDC (EPjFWdd5…) — SPL Token mint on mainnet-beta.' },
{
head: ['Field', 'Value'],
kind: 'table',
rows: [
['Decimals', '6'],
['Supply', '7748676460441051 raw → 7,748,676,460.441051 USDC'],
['Mint authority', 'BJE5MMbqXjVwjAF7oxwPYXnTXDyspzZyt4vwenNw5ruG'],
],
},
{
kind: 'text',
text: 'Supply is variable — the mint authority is present. Freeze authority is 7dGbd2QZcCKcTndnHcTL8q7SMVXAkp688NTQYwrRCrar.',
},
],
id: 'mint',
prompt: 'What is the supply and decimals of this mint, and is the supply fixed?',
question:
'What is the supply and decimals of EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v, and is the supply fixed?',
tool: 'inspect_entity',
},
];
24 changes: 24 additions & 0 deletions app/features/mcp-docs/lib/mcp-docs-layout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { BookOpen, MessageSquare, Terminal, Tool } from 'react-feather';

export const GUTTER = 'px-5 sm:px-8 lg:px-10 xxl:px-14';

export const BAND_RULE = 'border-0 border-t border-solid border-dark-border';

export const MONO_LABEL = 'font-mono text-xs uppercase tracking-widest';

export const SECTIONS = [
{ Icon: Terminal, id: 'setup', kicker: 'Setup' },
{ Icon: BookOpen, id: 'instructions', kicker: 'Instructions' },
{ Icon: Tool, id: 'tools', kicker: 'Tools' },
{ Icon: MessageSquare, id: 'examples', kicker: 'Examples' },
] as const;

export enum EndpointState {
Checking = 'checking',
Ready = 'ready',
Restricted = 'restricted',
Blocked = 'blocked',
Disabled = 'disabled',
}

export type EndpointStatus = { state: EndpointState; ms?: number };
Loading
Loading