docs: update all URL references to new username/slug format
Update all documentation, code, and tests to use the new endpoint formats:
- MCP: /api/mcp/{username}/{collection-slug}/{transport}
- Agent: /api/{username}/agents/{agent-uid}/conversation/{id}
Changes include:
- Update MCP URLs in collections page, docs, and test script
- Update agent API URLs in chat page, docs, tests, and cron job
- Add API key authentication requirements to all examples
- Update ARCHITECTURE.md with correct endpoint formats
- Update discord cron job to fetch owner username for new URL format
This commit is contained in:
parent
2b4526ba95
commit
2fb790a847
9 changed files with 163 additions and 93 deletions
|
|
@ -635,10 +635,10 @@ The API is built on Next.js 16 App Router with standardized response formats.
|
||||||
| **Tools** | `GET /api/tools` | List/search tools |
|
| **Tools** | `GET /api/tools` | List/search tools |
|
||||||
| | `POST /api/tools/execute/[...slug]` | Execute tool (SSE) |
|
| | `POST /api/tools/execute/[...slug]` | Execute tool (SSE) |
|
||||||
| **Agents** | `GET /api/agents` | List user agents |
|
| **Agents** | `GET /api/agents` | List user agents |
|
||||||
| | `POST /api/agents/[id]/conversation/[convId]` | Chat with agent (SSE) |
|
| | `POST /api/{username}/agents/{uid}/conversation/{convId}` | Chat with agent (SSE) |
|
||||||
| **Collections** | `GET /api/collections` | List user collections |
|
| **Collections** | `GET /api/collections` | List user collections |
|
||||||
| | `POST /api/collections/[id]/tools` | Add tool to collection |
|
| | `POST /api/collections/[id]/tools` | Add tool to collection |
|
||||||
| **MCP** | `POST /api/mcp/{user}/{slug}/{transport}` | MCP protocol |
|
| **MCP** | `POST /api/mcp/{username}/{slug}/{transport}` | MCP protocol |
|
||||||
| **Sync** | `POST /api/sync/changes` | Cron: npm changes |
|
| **Sync** | `POST /api/sync/changes` | Cron: npm changes |
|
||||||
| **Stats** | `GET /api/stats` | Registry statistics |
|
| **Stats** | `GET /api/stats` | Registry statistics |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,67 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Test script for MCP endpoints
|
# Test script for MCP endpoints
|
||||||
# Usage: ./test-mcp.sh <collection-id> [base-url]
|
# Usage: ./test-mcp.sh <username> <collection-slug> [base-url] [api-key]
|
||||||
|
#
|
||||||
|
# The new MCP URL format is: /api/mcp/{username}/{slug}/{transport}
|
||||||
|
# Collection ID is also supported as slug for backwards compatibility
|
||||||
|
|
||||||
COLLECTION_ID=${1:-""}
|
USERNAME=${1:-""}
|
||||||
BASE_URL=${2:-"https://tpmjs.com"}
|
COLLECTION_SLUG=${2:-""}
|
||||||
|
BASE_URL=${3:-"https://tpmjs.com"}
|
||||||
|
API_KEY=${4:-""}
|
||||||
|
|
||||||
if [ -z "$COLLECTION_ID" ]; then
|
if [ -z "$USERNAME" ] || [ -z "$COLLECTION_SLUG" ]; then
|
||||||
echo "Usage: ./test-mcp.sh <collection-id> [base-url]"
|
echo "Usage: ./test-mcp.sh <username> <collection-slug> [base-url] [api-key]"
|
||||||
echo "Example: ./test-mcp.sh clx123abc https://tpmjs.com"
|
echo "Example: ./test-mcp.sh ajax my-collection https://tpmjs.com tpmjs_sk_xxx"
|
||||||
|
echo ""
|
||||||
|
echo "Note: Collection ID can also be used as slug for backwards compatibility"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Build auth header if API key provided
|
||||||
|
AUTH_HEADER=""
|
||||||
|
if [ -n "$API_KEY" ]; then
|
||||||
|
AUTH_HEADER="-H \"Authorization: Bearer $API_KEY\""
|
||||||
|
fi
|
||||||
|
|
||||||
echo "================================================"
|
echo "================================================"
|
||||||
echo "Testing MCP endpoints for collection: $COLLECTION_ID"
|
echo "Testing MCP endpoints"
|
||||||
|
echo "Username: $USERNAME"
|
||||||
|
echo "Collection: $COLLECTION_SLUG"
|
||||||
echo "Base URL: $BASE_URL"
|
echo "Base URL: $BASE_URL"
|
||||||
|
echo "Auth: ${API_KEY:+API key provided}"
|
||||||
echo "================================================"
|
echo "================================================"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Test 1: HTTP transport - GET (server info)
|
# Test 1: HTTP transport - GET (server info)
|
||||||
echo "1. Testing HTTP GET (server info)..."
|
echo "1. Testing HTTP GET (server info)..."
|
||||||
echo " GET $BASE_URL/api/collections/$COLLECTION_ID/mcp/http"
|
echo " GET $BASE_URL/api/mcp/$USERNAME/$COLLECTION_SLUG/http"
|
||||||
curl -s "$BASE_URL/api/collections/$COLLECTION_ID/mcp/http" | jq .
|
eval curl -s "$BASE_URL/api/mcp/$USERNAME/$COLLECTION_SLUG/http" $AUTH_HEADER | jq .
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Test 2: HTTP transport - initialize
|
# Test 2: HTTP transport - initialize
|
||||||
echo "2. Testing HTTP POST (initialize)..."
|
echo "2. Testing HTTP POST (initialize)..."
|
||||||
echo " POST $BASE_URL/api/collections/$COLLECTION_ID/mcp/http"
|
echo " POST $BASE_URL/api/mcp/$USERNAME/$COLLECTION_SLUG/http"
|
||||||
curl -s -X POST "$BASE_URL/api/collections/$COLLECTION_ID/mcp/http" \
|
eval curl -s -X POST "$BASE_URL/api/mcp/$USERNAME/$COLLECTION_SLUG/http" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
|
$AUTH_HEADER \
|
||||||
-d '{"jsonrpc":"2.0","method":"initialize","id":1}' | jq .
|
-d '{"jsonrpc":"2.0","method":"initialize","id":1}' | jq .
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Test 3: HTTP transport - tools/list
|
# Test 3: HTTP transport - tools/list
|
||||||
echo "3. Testing HTTP POST (tools/list)..."
|
echo "3. Testing HTTP POST (tools/list)..."
|
||||||
echo " POST $BASE_URL/api/collections/$COLLECTION_ID/mcp/http"
|
echo " POST $BASE_URL/api/mcp/$USERNAME/$COLLECTION_SLUG/http"
|
||||||
curl -s -X POST "$BASE_URL/api/collections/$COLLECTION_ID/mcp/http" \
|
eval curl -s -X POST "$BASE_URL/api/mcp/$USERNAME/$COLLECTION_SLUG/http" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
|
$AUTH_HEADER \
|
||||||
-d '{"jsonrpc":"2.0","method":"tools/list","id":2}' | jq .
|
-d '{"jsonrpc":"2.0","method":"tools/list","id":2}' | jq .
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Test 4: SSE transport - GET (event stream)
|
# Test 4: SSE transport - GET (event stream)
|
||||||
echo "4. Testing SSE GET (event stream)..."
|
echo "4. Testing SSE GET (event stream)..."
|
||||||
echo " GET $BASE_URL/api/collections/$COLLECTION_ID/mcp/sse"
|
echo " GET $BASE_URL/api/mcp/$USERNAME/$COLLECTION_SLUG/sse"
|
||||||
curl -s -N "$BASE_URL/api/collections/$COLLECTION_ID/mcp/sse" &
|
eval curl -s -N "$BASE_URL/api/mcp/$USERNAME/$COLLECTION_SLUG/sse" $AUTH_HEADER &
|
||||||
SSE_PID=$!
|
SSE_PID=$!
|
||||||
sleep 2
|
sleep 2
|
||||||
kill $SSE_PID 2>/dev/null
|
kill $SSE_PID 2>/dev/null
|
||||||
|
|
@ -52,26 +70,28 @@ echo ""
|
||||||
|
|
||||||
# Test 5: SSE transport - initialize
|
# Test 5: SSE transport - initialize
|
||||||
echo "5. Testing SSE POST (initialize)..."
|
echo "5. Testing SSE POST (initialize)..."
|
||||||
echo " POST $BASE_URL/api/collections/$COLLECTION_ID/mcp/sse"
|
echo " POST $BASE_URL/api/mcp/$USERNAME/$COLLECTION_SLUG/sse"
|
||||||
curl -s -X POST "$BASE_URL/api/collections/$COLLECTION_ID/mcp/sse" \
|
eval curl -s -X POST "$BASE_URL/api/mcp/$USERNAME/$COLLECTION_SLUG/sse" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
|
$AUTH_HEADER \
|
||||||
-d '{"jsonrpc":"2.0","method":"initialize","id":1}'
|
-d '{"jsonrpc":"2.0","method":"initialize","id":1}'
|
||||||
echo ""
|
echo ""
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Test 6: SSE transport - tools/list
|
# Test 6: SSE transport - tools/list
|
||||||
echo "6. Testing SSE POST (tools/list)..."
|
echo "6. Testing SSE POST (tools/list)..."
|
||||||
echo " POST $BASE_URL/api/collections/$COLLECTION_ID/mcp/sse"
|
echo " POST $BASE_URL/api/mcp/$USERNAME/$COLLECTION_SLUG/sse"
|
||||||
curl -s -X POST "$BASE_URL/api/collections/$COLLECTION_ID/mcp/sse" \
|
eval curl -s -X POST "$BASE_URL/api/mcp/$USERNAME/$COLLECTION_SLUG/sse" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
|
$AUTH_HEADER \
|
||||||
-d '{"jsonrpc":"2.0","method":"tools/list","id":2}'
|
-d '{"jsonrpc":"2.0","method":"tools/list","id":2}'
|
||||||
echo ""
|
echo ""
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Test 7: Invalid transport
|
# Test 7: Invalid transport
|
||||||
echo "7. Testing invalid transport..."
|
echo "7. Testing invalid transport..."
|
||||||
echo " GET $BASE_URL/api/collections/$COLLECTION_ID/mcp/invalid"
|
echo " GET $BASE_URL/api/mcp/$USERNAME/$COLLECTION_SLUG/invalid"
|
||||||
curl -s "$BASE_URL/api/collections/$COLLECTION_ID/mcp/invalid" | jq .
|
eval curl -s "$BASE_URL/api/mcp/$USERNAME/$COLLECTION_SLUG/invalid" $AUTH_HEADER | jq .
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
echo "================================================"
|
echo "================================================"
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ interface Agent {
|
||||||
collectionCount: number;
|
collectionCount: number;
|
||||||
createdBy: {
|
createdBy: {
|
||||||
id: string;
|
id: string;
|
||||||
|
username: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
image: string | null;
|
image: string | null;
|
||||||
};
|
};
|
||||||
|
|
@ -266,11 +267,11 @@ export default function PublicAgentChatPage(): React.ReactElement {
|
||||||
|
|
||||||
// Fetch messages for conversation (initial load - gets most recent 50)
|
// Fetch messages for conversation (initial load - gets most recent 50)
|
||||||
const fetchMessages = useCallback(async () => {
|
const fetchMessages = useCallback(async () => {
|
||||||
if (!agent) return;
|
if (!agent || !agent.createdBy.username) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`/api/agents/${agent.uid}/conversation/${conversationId}?limit=50`
|
`/api/${agent.createdBy.username}/agents/${agent.uid}/conversation/${conversationId}?limit=50`
|
||||||
);
|
);
|
||||||
if (response.status === 404) {
|
if (response.status === 404) {
|
||||||
// Conversation doesn't exist yet, that's fine
|
// Conversation doesn't exist yet, that's fine
|
||||||
|
|
@ -294,7 +295,14 @@ export default function PublicAgentChatPage(): React.ReactElement {
|
||||||
// Load older messages when scrolling up
|
// Load older messages when scrolling up
|
||||||
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Virtuoso scroll handler with pagination logic
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Virtuoso scroll handler with pagination logic
|
||||||
const loadMoreMessages = useCallback(async () => {
|
const loadMoreMessages = useCallback(async () => {
|
||||||
if (!agent || isLoadingMore || !hasMoreMessages || messages.length === 0) return;
|
if (
|
||||||
|
!agent ||
|
||||||
|
!agent.createdBy.username ||
|
||||||
|
isLoadingMore ||
|
||||||
|
!hasMoreMessages ||
|
||||||
|
messages.length === 0
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
setIsLoadingMore(true);
|
setIsLoadingMore(true);
|
||||||
try {
|
try {
|
||||||
|
|
@ -305,7 +313,7 @@ export default function PublicAgentChatPage(): React.ReactElement {
|
||||||
if (!beforeTimestamp) return;
|
if (!beforeTimestamp) return;
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`/api/agents/${agent.uid}/conversation/${conversationId}?limit=50&before=${encodeURIComponent(beforeTimestamp)}`
|
`/api/${agent.createdBy.username}/agents/${agent.uid}/conversation/${conversationId}?limit=50&before=${encodeURIComponent(beforeTimestamp)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) return;
|
if (!response.ok) return;
|
||||||
|
|
@ -344,7 +352,7 @@ export default function PublicAgentChatPage(): React.ReactElement {
|
||||||
|
|
||||||
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Chat send handler with streaming and tool calls
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Chat send handler with streaming and tool calls
|
||||||
const handleSend = async () => {
|
const handleSend = async () => {
|
||||||
if (!input.trim() || !agent || isSending) return;
|
if (!input.trim() || !agent || !agent.createdBy.username || isSending) return;
|
||||||
|
|
||||||
const messageContent = input.trim();
|
const messageContent = input.trim();
|
||||||
setInput('');
|
setInput('');
|
||||||
|
|
@ -363,11 +371,14 @@ export default function PublicAgentChatPage(): React.ReactElement {
|
||||||
setMessages((prev) => [...prev, userMessage]);
|
setMessages((prev) => [...prev, userMessage]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/agents/${agent.uid}/conversation/${conversationId}`, {
|
const response = await fetch(
|
||||||
method: 'POST',
|
`/api/${agent.createdBy.username}/agents/${agent.uid}/conversation/${conversationId}`,
|
||||||
headers: { 'Content-Type': 'application/json' },
|
{
|
||||||
body: JSON.stringify({ message: messageContent }),
|
method: 'POST',
|
||||||
});
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ message: messageContent }),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json();
|
const errorData = await response.json();
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,15 @@ export async function POST(request: NextRequest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 3. Verify agent exists
|
// 3. Verify agent exists and get owner info
|
||||||
const agent = await prisma.agent.findUnique({
|
const agent = await prisma.agent.findUnique({
|
||||||
where: { id: agentId },
|
where: { id: agentId },
|
||||||
select: { id: true, name: true },
|
select: {
|
||||||
|
id: true,
|
||||||
|
uid: true,
|
||||||
|
name: true,
|
||||||
|
user: { select: { username: true } },
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!agent) {
|
if (!agent) {
|
||||||
|
|
@ -60,6 +65,13 @@ export async function POST(request: NextRequest) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!agent.user.username || !agent.uid) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, error: 'Agent owner has no username or agent has no UID' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 4. Get or create a conversation for this cron job
|
// 4. Get or create a conversation for this cron job
|
||||||
// Use a fixed slug for the cron job so we reuse the same conversation
|
// Use a fixed slug for the cron job so we reuse the same conversation
|
||||||
const cronSlug = 'discord-summary-cron';
|
const cronSlug = 'discord-summary-cron';
|
||||||
|
|
@ -100,10 +112,10 @@ Then use the discordPostTool to post the summary to channel "${summaryChannelId}
|
||||||
|
|
||||||
If there are no messages in the past 24 hours, post a brief message saying the server was quiet.`;
|
If there are no messages in the past 24 hours, post a brief message saying the server was quiet.`;
|
||||||
|
|
||||||
// 6. Call the agent conversation endpoint
|
// 6. Call the agent conversation endpoint (new URL format)
|
||||||
const baseUrl = env.BETTER_AUTH_URL || `http://localhost:${process.env.PORT || 3000}`;
|
const baseUrl = env.BETTER_AUTH_URL || `http://localhost:${process.env.PORT || 3000}`;
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${baseUrl}/api/agents/${agentId}/conversation/${conversation.id}`,
|
`${baseUrl}/api/${agent.user.username}/agents/${agent.uid}/conversation/${conversation.id}`,
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
|
|
||||||
|
|
@ -46,13 +46,13 @@ interface PublicCollection {
|
||||||
tools: CollectionTool[];
|
tools: CollectionTool[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function McpUrlSection({ collectionId }: { collectionId: string }) {
|
function McpUrlSection({ username, slug }: { username: string; slug: string }) {
|
||||||
const [copiedUrl, setCopiedUrl] = useState<'http' | 'sse' | null>(null);
|
const [copiedUrl, setCopiedUrl] = useState<'http' | 'sse' | null>(null);
|
||||||
const [showConfig, setShowConfig] = useState(false);
|
const [showConfig, setShowConfig] = useState(false);
|
||||||
|
|
||||||
const baseUrl = typeof window !== 'undefined' ? window.location.origin : 'https://tpmjs.com';
|
const baseUrl = typeof window !== 'undefined' ? window.location.origin : 'https://tpmjs.com';
|
||||||
const httpUrl = `${baseUrl}/api/collections/${collectionId}/mcp/http`;
|
const httpUrl = `${baseUrl}/api/mcp/${username}/${slug}/http`;
|
||||||
const sseUrl = `${baseUrl}/api/collections/${collectionId}/mcp/sse`;
|
const sseUrl = `${baseUrl}/api/mcp/${username}/${slug}/sse`;
|
||||||
|
|
||||||
const copyToClipboard = async (url: string, type: 'http' | 'sse') => {
|
const copyToClipboard = async (url: string, type: 'http' | 'sse') => {
|
||||||
await navigator.clipboard.writeText(url);
|
await navigator.clipboard.writeText(url);
|
||||||
|
|
@ -66,7 +66,9 @@ function McpUrlSection({ collectionId }: { collectionId: string }) {
|
||||||
"command": "npx",
|
"command": "npx",
|
||||||
"args": [
|
"args": [
|
||||||
"mcp-remote",
|
"mcp-remote",
|
||||||
"${httpUrl}"
|
"${httpUrl}",
|
||||||
|
"--header",
|
||||||
|
"Authorization: Bearer YOUR_TPMJS_API_KEY"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -309,7 +311,9 @@ export default function PublicCollectionDetailPage(): React.ReactElement {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* MCP URLs */}
|
{/* MCP URLs */}
|
||||||
<McpUrlSection collectionId={collection.id} />
|
{collection.createdBy?.username && collection.slug && (
|
||||||
|
<McpUrlSection username={collection.createdBy.username} slug={collection.slug} />
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Tools */}
|
{/* Tools */}
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -692,18 +692,30 @@ Always cite your sources and be transparent about limitations.`}
|
||||||
<DocSubSection title="Endpoint">
|
<DocSubSection title="Endpoint">
|
||||||
<CodeBlock
|
<CodeBlock
|
||||||
language="text"
|
language="text"
|
||||||
code="POST /api/agents/[uid]/conversation/[conversationId]"
|
code="POST /api/{username}/agents/{agent-uid}/conversation/{conversationId}"
|
||||||
/>
|
/>
|
||||||
<div className="mt-4 space-y-2 text-foreground-secondary text-sm">
|
<div className="mt-4 space-y-2 text-foreground-secondary text-sm">
|
||||||
<p>
|
<p>
|
||||||
<strong className="text-foreground">uid:</strong> Your agent's unique
|
<strong className="text-foreground">username:</strong> The agent owner's
|
||||||
identifier
|
username
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong className="text-foreground">agent-uid:</strong> The agent's unique
|
||||||
|
identifier (slug)
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong className="text-foreground">conversationId:</strong> Unique ID for the
|
<strong className="text-foreground">conversationId:</strong> Unique ID for the
|
||||||
conversation (create your own or use a new ID to start a new conversation)
|
conversation (create your own or use a new ID to start a new conversation)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mt-4 p-4 border border-primary/30 rounded-lg bg-primary/5">
|
||||||
|
<p className="text-sm text-foreground-secondary">
|
||||||
|
<strong className="text-foreground">Authentication Required:</strong> Include
|
||||||
|
the header{' '}
|
||||||
|
<code className="text-primary">Authorization: Bearer YOUR_TPMJS_API_KEY</code>{' '}
|
||||||
|
in all requests. Get your API key from the dashboard.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</DocSubSection>
|
</DocSubSection>
|
||||||
<DocSubSection title="Request Body">
|
<DocSubSection title="Request Body">
|
||||||
<ParamTable
|
<ParamTable
|
||||||
|
|
@ -726,13 +738,19 @@ Always cite your sources and be transparent about limitations.`}
|
||||||
<DocSubSection title="Example: JavaScript Client">
|
<DocSubSection title="Example: JavaScript Client">
|
||||||
<CodeBlock
|
<CodeBlock
|
||||||
language="typescript"
|
language="typescript"
|
||||||
code={`const conversationId = 'conv-' + Date.now();
|
code={`const username = 'ajax'; // Agent owner's username
|
||||||
|
const agentUid = 'research-assistant'; // Agent UID
|
||||||
|
const conversationId = 'conv-' + Date.now();
|
||||||
|
const apiKey = 'tpmjs_sk_...'; // Your TPMJS API key
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
\`https://tpmjs.com/api/agents/\${agentUid}/conversation/\${conversationId}\`,
|
\`https://tpmjs.com/api/\${username}/agents/\${agentUid}/conversation/\${conversationId}\`,
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': \`Bearer \${apiKey}\`,
|
||||||
|
},
|
||||||
body: JSON.stringify({ message: 'Search for AI tools' }),
|
body: JSON.stringify({ message: 'Search for AI tools' }),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -825,11 +843,11 @@ while (true) {
|
||||||
POST
|
POST
|
||||||
</Badge>
|
</Badge>
|
||||||
<code className="text-foreground font-mono">
|
<code className="text-foreground font-mono">
|
||||||
/api/agents/[uid]/conversation/[conversationId]
|
/api/[username]/agents/[uid]/conversation/[conversationId]
|
||||||
</code>
|
</code>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-foreground-secondary">
|
<p className="text-sm text-foreground-secondary">
|
||||||
Send a message and stream the AI response via SSE.
|
Send a message and stream the AI response via SSE. Requires TPMJS API key.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 border border-border rounded-lg bg-surface">
|
<div className="p-4 border border-border rounded-lg bg-surface">
|
||||||
|
|
@ -838,11 +856,11 @@ while (true) {
|
||||||
GET
|
GET
|
||||||
</Badge>
|
</Badge>
|
||||||
<code className="text-foreground font-mono">
|
<code className="text-foreground font-mono">
|
||||||
/api/agents/[uid]/conversation/[conversationId]
|
/api/[username]/agents/[uid]/conversation/[conversationId]
|
||||||
</code>
|
</code>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-foreground-secondary">
|
<p className="text-sm text-foreground-secondary">
|
||||||
Get the full conversation history with all messages.
|
Get the full conversation history with all messages. Requires TPMJS API key.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 border border-border rounded-lg bg-surface">
|
<div className="p-4 border border-border rounded-lg bg-surface">
|
||||||
|
|
@ -851,11 +869,11 @@ while (true) {
|
||||||
GET
|
GET
|
||||||
</Badge>
|
</Badge>
|
||||||
<code className="text-foreground font-mono">
|
<code className="text-foreground font-mono">
|
||||||
/api/agents/[uid]/conversations
|
/api/[username]/agents/[uid]/conversations
|
||||||
</code>
|
</code>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-foreground-secondary">
|
<p className="text-sm text-foreground-secondary">
|
||||||
List all conversations for an agent.
|
List all conversations for an agent. Requires TPMJS API key.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 border border-border rounded-lg bg-surface">
|
<div className="p-4 border border-border rounded-lg bg-surface">
|
||||||
|
|
|
||||||
|
|
@ -1219,10 +1219,11 @@ export TPMJS_EXECUTOR_URL=https://executor.mycompany.com`}
|
||||||
</p>
|
</p>
|
||||||
<CodeBlock
|
<CodeBlock
|
||||||
language="text"
|
language="text"
|
||||||
code="https://tpmjs.com/api/collections/<collection-id>/mcp/http"
|
code="https://tpmjs.com/api/mcp/<username>/<collection-slug>/http"
|
||||||
/>
|
/>
|
||||||
<p className="text-foreground-secondary mt-4">
|
<p className="text-foreground-secondary mt-4">
|
||||||
This URL is what you'll use to connect MCP clients to your collection.
|
This URL is what you'll use to connect MCP clients to your collection.
|
||||||
|
You'll also need your TPMJS API key for authentication.
|
||||||
</p>
|
</p>
|
||||||
</DocSubSection>
|
</DocSubSection>
|
||||||
</DocSection>
|
</DocSection>
|
||||||
|
|
@ -1244,7 +1245,9 @@ export TPMJS_EXECUTOR_URL=https://executor.mycompany.com`}
|
||||||
"command": "npx",
|
"command": "npx",
|
||||||
"args": [
|
"args": [
|
||||||
"mcp-remote",
|
"mcp-remote",
|
||||||
"https://tpmjs.com/api/collections/<collection-id>/mcp/http"
|
"https://tpmjs.com/api/mcp/<username>/<collection-slug>/http",
|
||||||
|
"--header",
|
||||||
|
"Authorization: Bearer YOUR_TPMJS_API_KEY"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1275,7 +1278,8 @@ export TPMJS_EXECUTOR_URL=https://executor.mycompany.com`}
|
||||||
<CodeBlock
|
<CodeBlock
|
||||||
language="bash"
|
language="bash"
|
||||||
code={`claude mcp add tpmjs-my-collection \\
|
code={`claude mcp add tpmjs-my-collection \\
|
||||||
-- npx mcp-remote https://tpmjs.com/api/collections/<collection-id>/mcp/http`}
|
-- npx mcp-remote https://tpmjs.com/api/mcp/<username>/<collection-slug>/http \\
|
||||||
|
--header "Authorization: Bearer YOUR_TPMJS_API_KEY"`}
|
||||||
/>
|
/>
|
||||||
<p className="text-foreground-secondary mt-4">
|
<p className="text-foreground-secondary mt-4">
|
||||||
This automatically adds the server to your Claude Code configuration.
|
This automatically adds the server to your Claude Code configuration.
|
||||||
|
|
@ -1305,7 +1309,7 @@ export TPMJS_EXECUTOR_URL=https://executor.mycompany.com`}
|
||||||
<DocSubSection title="Endpoint Format">
|
<DocSubSection title="Endpoint Format">
|
||||||
<CodeBlock
|
<CodeBlock
|
||||||
language="text"
|
language="text"
|
||||||
code="POST https://tpmjs.com/api/collections/<collection-id>/mcp/http"
|
code="POST https://tpmjs.com/api/mcp/<username>/<collection-slug>/http"
|
||||||
/>
|
/>
|
||||||
</DocSubSection>
|
</DocSubSection>
|
||||||
<DocSubSection title="Request Headers">
|
<DocSubSection title="Request Headers">
|
||||||
|
|
@ -1318,6 +1322,9 @@ export TPMJS_EXECUTOR_URL=https://executor.mycompany.com`}
|
||||||
Accept: application/json, text/event-stream
|
Accept: application/json, text/event-stream
|
||||||
</code>
|
</code>
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<code className="text-primary">Authorization: Bearer YOUR_TPMJS_API_KEY</code>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</DocSubSection>
|
</DocSubSection>
|
||||||
<DocSubSection title="Supported Methods">
|
<DocSubSection title="Supported Methods">
|
||||||
|
|
|
||||||
|
|
@ -370,7 +370,9 @@ Invalid usernames:
|
||||||
"command": "npx",
|
"command": "npx",
|
||||||
"args": [
|
"args": [
|
||||||
"mcp-remote",
|
"mcp-remote",
|
||||||
"https://tpmjs.com/api/collections/{collection-id}/mcp/http"
|
"https://tpmjs.com/api/mcp/{username}/{collection-slug}/http",
|
||||||
|
"--header",
|
||||||
|
"Authorization: Bearer YOUR_TPMJS_API_KEY"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -602,10 +604,10 @@ Invalid usernames:
|
||||||
<tr>
|
<tr>
|
||||||
<td className="py-3 px-4 text-foreground">Agent Conversation</td>
|
<td className="py-3 px-4 text-foreground">Agent Conversation</td>
|
||||||
<td className="py-3 px-4 font-mono text-primary text-xs">
|
<td className="py-3 px-4 font-mono text-primary text-xs">
|
||||||
/api/chat/{'{username}'}/{'{uid}'}/conversation/{'{id}'}
|
/api/{'{username}'}/agents/{'{uid}'}/conversation/{'{id}'}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-3 px-4 font-mono text-foreground-secondary text-xs">
|
<td className="py-3 px-4 font-mono text-foreground-secondary text-xs">
|
||||||
/api/chat/ajax/research-bot/conversation/abc123
|
/api/ajax/agents/research-bot/conversation/abc123
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,7 @@ describe.skipIf(!INTEGRATION_TESTS_ENABLED)('Agent Endpoints', () => {
|
||||||
name: string;
|
name: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
provider: string;
|
provider: string;
|
||||||
|
user: { username: string | null };
|
||||||
} | null = null;
|
} | null = null;
|
||||||
|
|
||||||
let testConversationId: string | null = null;
|
let testConversationId: string | null = null;
|
||||||
|
|
@ -110,11 +111,15 @@ describe.skipIf(!INTEGRATION_TESTS_ENABLED)('Agent Endpoints', () => {
|
||||||
name: true,
|
name: true,
|
||||||
userId: true,
|
userId: true,
|
||||||
provider: true,
|
provider: true,
|
||||||
|
user: { select: { username: true } },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!testAgent) {
|
if (!testAgent) {
|
||||||
console.warn(`⚠️ Test agent "${TEST_AGENT_UID}" not found. Skipping agent tests.`);
|
console.warn(`⚠️ Test agent "${TEST_AGENT_UID}" not found. Skipping agent tests.`);
|
||||||
|
} else if (!testAgent.user.username) {
|
||||||
|
console.warn(`⚠️ Test agent owner has no username. Skipping agent tests.`);
|
||||||
|
testAgent = null;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('⚠️ Could not connect to database. Skipping agent tests.', error);
|
console.warn('⚠️ Could not connect to database. Skipping agent tests.', error);
|
||||||
|
|
@ -139,7 +144,7 @@ describe.skipIf(!INTEGRATION_TESTS_ENABLED)('Agent Endpoints', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('POST /api/agents/[id]/conversation/[conversationId]', () => {
|
describe('POST /api/[username]/agents/[uid]/conversation/[conversationId]', () => {
|
||||||
it('should create a new conversation and receive a response', async () => {
|
it('should create a new conversation and receive a response', async () => {
|
||||||
if (!serverAvailable || !testAgent) {
|
if (!serverAvailable || !testAgent) {
|
||||||
console.log('Skipping: Server not available or no test agent');
|
console.log('Skipping: Server not available or no test agent');
|
||||||
|
|
@ -147,7 +152,7 @@ describe.skipIf(!INTEGRATION_TESTS_ENABLED)('Agent Endpoints', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${BASE_URL}/api/agents/${testAgent.uid}/conversation/${TEST_CONVERSATION_SLUG}`,
|
`${BASE_URL}/api/${testAgent.user.username}/agents/${testAgent.uid}/conversation/${TEST_CONVERSATION_SLUG}`,
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
|
@ -224,7 +229,7 @@ describe.skipIf(!INTEGRATION_TESTS_ENABLED)('Agent Endpoints', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${BASE_URL}/api/agents/${testAgent.uid}/conversation/${TEST_CONVERSATION_SLUG}`,
|
`${BASE_URL}/api/${testAgent.user.username}/agents/${testAgent.uid}/conversation/${TEST_CONVERSATION_SLUG}`,
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
|
@ -260,7 +265,7 @@ describe.skipIf(!INTEGRATION_TESTS_ENABLED)('Agent Endpoints', () => {
|
||||||
const toolConvSlug = `tool-test-${Date.now()}`;
|
const toolConvSlug = `tool-test-${Date.now()}`;
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${BASE_URL}/api/agents/${testAgent.uid}/conversation/${toolConvSlug}`,
|
`${BASE_URL}/api/${testAgent.user.username}/agents/${testAgent.uid}/conversation/${toolConvSlug}`,
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
|
@ -312,7 +317,7 @@ describe.skipIf(!INTEGRATION_TESTS_ENABLED)('Agent Endpoints', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('GET /api/agents/[id]/conversation/[conversationId]', () => {
|
describe('GET /api/[username]/agents/[uid]/conversation/[conversationId]', () => {
|
||||||
it('should retrieve conversation history', async () => {
|
it('should retrieve conversation history', async () => {
|
||||||
if (!serverAvailable || !testAgent || !testConversationId) {
|
if (!serverAvailable || !testAgent || !testConversationId) {
|
||||||
console.log('Skipping: Server not available or no test conversation');
|
console.log('Skipping: Server not available or no test conversation');
|
||||||
|
|
@ -320,7 +325,7 @@ describe.skipIf(!INTEGRATION_TESTS_ENABLED)('Agent Endpoints', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${BASE_URL}/api/agents/${testAgent.uid}/conversation/${TEST_CONVERSATION_SLUG}`,
|
`${BASE_URL}/api/${testAgent.user.username}/agents/${testAgent.uid}/conversation/${TEST_CONVERSATION_SLUG}`,
|
||||||
{ method: 'GET' }
|
{ method: 'GET' }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -341,7 +346,7 @@ describe.skipIf(!INTEGRATION_TESTS_ENABLED)('Agent Endpoints', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${BASE_URL}/api/agents/${testAgent.uid}/conversation/non-existent-conv-12345`,
|
`${BASE_URL}/api/${testAgent.user.username}/agents/${testAgent.uid}/conversation/non-existent-conv-12345`,
|
||||||
{ method: 'GET' }
|
{ method: 'GET' }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -349,16 +354,19 @@ describe.skipIf(!INTEGRATION_TESTS_ENABLED)('Agent Endpoints', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('GET /api/agents/[id]/conversations', () => {
|
describe('GET /api/[username]/agents/[uid]/conversations', () => {
|
||||||
it('should list all conversations for an agent', async () => {
|
it('should list all conversations for an agent', async () => {
|
||||||
if (!serverAvailable || !testAgent) {
|
if (!serverAvailable || !testAgent) {
|
||||||
console.log('Skipping: Server not available or no test agent');
|
console.log('Skipping: Server not available or no test agent');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`${BASE_URL}/api/agents/${testAgent.id}/conversations`, {
|
const response = await fetch(
|
||||||
method: 'GET',
|
`${BASE_URL}/api/${testAgent.user.username}/agents/${testAgent.uid}/conversations`,
|
||||||
});
|
{
|
||||||
|
method: 'GET',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
expect(response.ok).toBe(true);
|
expect(response.ok).toBe(true);
|
||||||
|
|
||||||
|
|
@ -380,7 +388,7 @@ describe.skipIf(!INTEGRATION_TESTS_ENABLED)('Agent Endpoints', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${BASE_URL}/api/agents/${testAgent.id}/conversations?limit=1&offset=0`,
|
`${BASE_URL}/api/${testAgent.user.username}/agents/${testAgent.uid}/conversations?limit=1&offset=0`,
|
||||||
{ method: 'GET' }
|
{ method: 'GET' }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -433,31 +441,19 @@ describe.skipIf(!INTEGRATION_TESTS_ENABLED)('Agent Endpoints', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Agent with lookup by ID or UID', () => {
|
describe('Agent with username/uid lookup', () => {
|
||||||
it('should accept agent ID in URL', async () => {
|
it('should accept agent with username/uid format', async () => {
|
||||||
if (!serverAvailable || !testAgent) {
|
if (!serverAvailable || !testAgent) {
|
||||||
console.log('Skipping: Server not available or no test agent');
|
console.log('Skipping: Server not available or no test agent');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`${BASE_URL}/api/agents/${testAgent.id}/conversations`, {
|
const response = await fetch(
|
||||||
method: 'GET',
|
`${BASE_URL}/api/${testAgent.user.username}/agents/${testAgent.uid}/conversations`,
|
||||||
});
|
{
|
||||||
|
method: 'GET',
|
||||||
expect(response.ok).toBe(true);
|
}
|
||||||
const result = await response.json();
|
);
|
||||||
expect(result.success).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should accept agent UID in URL', async () => {
|
|
||||||
if (!serverAvailable || !testAgent) {
|
|
||||||
console.log('Skipping: Server not available or no test agent');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(`${BASE_URL}/api/agents/${testAgent.uid}/conversations`, {
|
|
||||||
method: 'GET',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.ok).toBe(true);
|
expect(response.ok).toBe(true);
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue