Imported from jchoi2x/suno-mcp (
AGENTS.md). Install upstream withnpx skills add jchoi2x/suno-mcp. Copyright stays with the author.
AGENTS.md - AI Agent Guide to Suno-MCP Codebase
This document provides AI agents with essential context about the Suno-MCP project structure, architecture, patterns, and conventions. Read this first before making changes to understand how the codebase is organized.
Project Overview
Suno-MCP is a Model Context Protocol (MCP) server that enables AI assistants (like Claude) to generate music using Suno AI through a Cloudflare Workers-based architecture. The project allows LLMs to collaboratively create songs in users' Suno accounts.
Key Technologies
- Cloudflare Workers: Serverless runtime for the MCP server
- Durable Objects: Stateful objects for managing user sessions
- Cloudflare Containers: Isolated Node.js services running Playwright
- Model Context Protocol (MCP): Protocol for AI assistants to interact with tools
- TypeScript: Primary language with strict typing
- Zod: Schema validation and type inference
- pnpm: Package manager with workspace support
Architecture
High-Level Architecture
┌─────────────────────────────────────────────────────────┐
│ Cloudflare Workers (MCP Server) │
│ ┌───────────────────────────────────────────────────┐ │
│ │ Durable Object (SunoMcpServer) │ │
│ │ - Manages MCP tools, resources, prompts │ │
│ │ - Handles authentication │ │
│ │ - Routes requests to container service │ │
│ └───────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ Cloudflare Container (suno-api) │ │
│ │ - Node.js HTTP API server │ │
│ │ - Playwright automation │ │
│ │ - Direct Suno API integration │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Monorepo Structure
/
├── apps/
│ ├── suno-mcp/ # MCP Server (Cloudflare Worker)
│ │ └── src/
│ │ ├── durable/ # Durable Object implementation
│ │ │ └── suno.mcp.ts # Main MCP server class
│ │ ├── handlers/ # OAuth handlers
│ │ └── utils/ # Utility functions
│ └── suno-api/ # API service (placeholder/stub)
│ └── src/
│ ├── routes/ # API route handlers
│ ├── lib/ # Core library code
│ └── middleware/
├── packages/
│ └── suno-contracts/ # Shared Zod schemas
│ └── src/
│ ├── api/ # API request/response schemas
│ └── entities/ # Data model schemas
└── docs/ # Documentation
Key Files and Their Purposes
Core MCP Server
apps/suno-mcp/src/durable/suno.mcp.ts: Main MCP server implementation- Extends
McpAgentbase class - Registers MCP tools, resources, and prompts
- Manages authentication and session cookies
- Routes requests to container service via
callContainerService() - Key methods:
init(): Initializes tools, resources, promptsinitAllTools(): Registers all MCP toolsinitAllResources(): Registers all MCP resourcesinitAllPrompts(): Registers all MCP promptscallContainerService(): Makes HTTP requests to container
- Extends
Contracts Package
packages/suno-contracts/src/api/: API schemas organized by endpoint- Each endpoint has
request.ts,response.ts,query.tsas needed - All schemas use Zod with OpenAPI metadata
- Types inferred via
z.infer<typeof Schema>
- Each endpoint has
packages/suno-contracts/src/entities/: Data model schemasAudioInfo: Song/audio track informationPersona: Artist/persona informationAlignedLyrics: Word-level timing dataClipInfo: Complete clip metadata
Configuration Files
wrangler.jsonc: Cloudflare Workers configurationtsconfig.json: TypeScript configuration (extends base)tsconfig.base.json: Shared TypeScript configpnpm-workspace.yaml: Workspace definitionpackage.json: Root package with workspace scripts
Important Concepts
MCP (Model Context Protocol)
- Tools: Actions the AI can perform (e.g.,
suno_generate,suno_get) - Resources: Read-only data accessible by URI (e.g.,
suno://song/{id}) - Prompts: Template conversations that guide workflows
- Tools/resources/prompts are registered in
init()methods - Use
server.sendToolListChanged(),sendResourceListChanged(),sendPromptListChanged()when lists change
Durable Objects
- Stateful objects that persist across requests
- Each user gets their own Durable Object instance
- State stored in
this.ctx.storage blockConcurrencyWhile()used for initialization- Extends
McpAgentbase class fromagents/mcp
Container Service Communication
- Containers are isolated Node.js services
- Accessed via
getContainer(env.DOCKER_SUNO_API, name) - Requests use
http://container{path}URLs - Session cookies passed in headers
- Container starts automatically when needed
Authentication Flow
- User provides Suno cookie via
set_suno_cookietool - Cookie stored in KV:
SUNO_COOKIE_KV.get(\${id}:cookie`)` - Cookie passed to container in request headers
- Tools/resources/prompts registered after authentication
Code Patterns and Conventions
Tool Registration Pattern
this.server.registerTool(
"tool_name",
{
title: "Human-readable title",
description: "What the tool does",
inputSchema: Schema.shape, // From contracts package
outputSchema: Schema, // Optional
},
async (args) => {
try {
const result = await this.callContainerService(
"/api/v1/endpoint",
args,
"POST",
this.name
);
return {
content: [{
type: "text",
text: JSON.stringify(result, null, 2),
}],
};
} catch (error: any) {
return {
isError: true,
content: [{
type: "text",
text: JSON.stringify({ error: true, message: error.message }, null, 2),
}],
};
}
}
);
Resource Registration Pattern
// Static resource
this.server.registerResource(
"resource-name",
"suno://path/to/resource",
{
description: "Resource description",
mimeType: "application/json",
},
async (uri) => {
// uri is a URL object
const result = await this.callContainerService(...);
return {
contents: [{
uri: uri.toString(), // Must be string
mimeType: "application/json",
text: JSON.stringify(result, null, 2),
}],
};
}
);
// Template resource (with variables)
this.server.registerResource(
"resource-name",
new ResourceTemplate("suno://path/{id}", {
list: undefined, // Optional: list all matching resources
}),
{ description: "...", mimeType: "application/json" },
async (uri, variables) => {
const id = Array.isArray(variables.id) ? variables.id[0] : variables.id;
// Handle template variables...
}
);
Prompt Registration Pattern
this.server.registerPrompt(
"prompt-name",
{
title: "Prompt Title",
description: "What this prompt helps with",
argsSchema: {
arg1: z.string().describe("Description"),
arg2: z.boolean().optional().describe("Optional arg"),
},
},
async ({ arg1, arg2 }) => {
return {
messages: [{
role: "user",
content: {
type: "text",
text: "Instructions for the AI...",
},
}],
};
}
);
Error Handling
- Always wrap container calls in try/catch
- Return
{ isError: true, content: [...] }for tool errors - Throw errors for resource/prompt failures
- Use descriptive error messages
Type Safety
- Import schemas from
@suno-mcp/contracts/api - Use
Schema.shapefor input schemas - Use
z.infer<typeof Schema>for TypeScript types - Never use
anyunless absolutely necessary (preferunknown)
Testing Conventions
Test Structure
- Unit tests in
__tests__/directories next to code - Test files:
*.test.ts - E2E tests in
tests/at app level - Use Vitest as test runner
Mocking Rules
- NEVER use
require()in tests - Use
import * as module from 'module'thenjest.spyOn(module, 'function') - Only mock what's necessary for the test
- Assert that mocks are called when expected
Example Test Pattern
import * as lodash from 'lodash';
import { describe, it, expect, vi } from 'vitest';
describe('function', () => {
it('should work', () => {
jest.spyOn(lodash, 'map').mockReturnValue([]);
// Test implementation
expect(lodash.map).toHaveBeenCalled();
});
});
Development Workflow
Adding a New Tool
- Define request/response schemas in
packages/suno-contracts/src/api/{endpoint}/ - Export schemas from
packages/suno-contracts/src/api/index.ts - Import in
suno.mcp.ts:import { Schema } from "@suno-mcp/contracts/api" - Register tool in
initAllTools()method - Use
callContainerService()to call API endpoint - Handle errors appropriately
Adding a New Resource
- Determine if static or template resource
- Register in
initAllResources()method - Use
ResourceTemplatefor dynamic URIs with variables - Convert
URLto string in return:uri.toString() - Handle template variables (can be
string | string[])
Adding a New Prompt
- Register in
initAllPrompts()method - Define
argsSchemawith Zod - Return
{ messages: [...] }with user instructions - Guide AI on which tools to use
Modifying Contracts
- Edit schemas in
packages/suno-contracts/src/ - Changes automatically trigger dev server restarts
- Types update automatically via
z.infer - No separate build step needed
Common Tasks
Finding Where Code Lives
- MCP tools:
apps/suno-mcp/src/durable/suno.mcp.ts→initAllTools() - MCP resources:
apps/suno-mcp/src/durable/suno.mcp.ts→initAllResources() - MCP prompts:
apps/suno-mcp/src/durable/suno.mcp.ts→initAllPrompts() - API schemas:
packages/suno-contracts/src/api/{endpoint}/ - Entity schemas:
packages/suno-contracts/src/entities/ - API routes:
apps/suno-api/src/routes/(if implemented)
Understanding Data Flow
- User → MCP Client (Claude Desktop)
- MCP Client → MCP Server (Durable Object)
- MCP Server → Container Service (via
callContainerService()) - Container Service → Suno API (via Playwright)
- Response flows back through the chain
Authentication State
- Check
this.hasCookiebefore registering tools/resources/prompts - Cookie stored in:
this.sessionCookie - Persisted in:
this.env.SUNO_COOKIE_KV(KV store) - Also in:
this.ctx.storage(Durable Object storage)
Important Notes
TypeScript Configuration
- Root
tsconfig.jsonreferences all workspace projects - Each package extends
tsconfig.base.json - Source files from contracts are included directly (no build step)
Package Imports
- Use
@suno-mcp/contractsfor shared schemas - Workspace packages referenced by name in
package.json - pnpm handles workspace linking automatically
Environment Variables
- Defined in
wrangler.jsonc - Access via
this.env.VARIABLE_NAME - Types in
worker-configuration.d.ts
Console Logging
- Only use:
console.error(),console.warn(),console.info() - Avoid:
console.log()(linting error)
When Making Changes
- Read existing code in the same file/area first
- Follow established patterns (see examples above)
- Update contracts if adding new API endpoints
- Add tests for new functionality
- Check linting with
read_lintstool - Verify types compile correctly
- Update this file if adding new patterns/conventions
Quick Reference
Key Imports
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { Schema } from "@suno-mcp/contracts/api";
import { EntitySchema } from "@suno-mcp/contracts/entities";
import z from "zod";
Key Methods
this.callContainerService(path, body, method, name): Call container APIthis.server.registerTool(...): Register MCP toolthis.server.registerResource(...): Register MCP resourcethis.server.registerPrompt(...): Register MCP promptthis.server.sendToolListChanged(): Notify clients of tool changes
Key Properties
this.env: Environment variablesthis.ctx: Durable Object contextthis.sessionCookie: Current session cookiethis.hasCookie: Whether user is authenticatedthis.name: Container name (default: "suno-api")
Last Updated: 2025-01-27 Maintainer: See CONTRIBUTING.md for contribution guidelines