@arcanic-ai/cono-agent-sdk
v0.1.4
Published
Agent SDK for building AI agents using Arcanic's Cono API. Programmatically build autonomous agents that can understand codebases, use tools, and execute complex workflows.
Maintainers
Readme
Cono Agent SDK
Agent SDK for building AI agents using Arcanic's Cono API. Programmatically build autonomous agents that can use tools, maintain conversations, stream responses, and execute complex workflows.
Install
npm install @arcanic-ai/cono-agent-sdkExamples
For complete example applications, see the cono-sdk-examples repository.
Quick Start
Simple Prompt
import { prompt } from "@arcanic-ai/cono-agent-sdk";
const result = await prompt("What is 2 + 2?", {
apiKey: "your-api-key",
model: "cono-3",
});
if (result.subtype === "success") {
console.log(result.result);
}Streaming with Tools
import { query, tool } from "@arcanic-ai/cono-agent-sdk";
const readFile = tool({
name: "read_file",
description: "Read a file from disk",
parameters: {
type: "object",
properties: {
path: { type: "string", description: "File path to read" },
},
required: ["path"],
},
handler: async (args) => {
const fs = await import("fs/promises");
return await fs.readFile(args.path as string, "utf-8");
},
});
for await (const message of query("Read package.json and summarize it", {
apiKey: "your-api-key",
tools: [readFile],
})) {
switch (message.type) {
case "assistant":
console.log("Assistant:", message.message.content);
break;
case "tool_use":
console.log(`Using tool: ${message.toolName}`);
break;
case "tool_result":
console.log(`Tool result received`);
break;
case "result":
console.log("Done:", message.subtype);
break;
}
}Multi-turn Session
import { createSession, tool } from "@arcanic-ai/cono-agent-sdk";
const session = createSession({
apiKey: "your-api-key",
model: "cono-3",
systemPrompt: "You are a helpful coding assistant.",
});
// First message
const result1 = await session.prompt("What is TypeScript?");
console.log(result1);
// Follow-up (maintains context)
const result2 = await session.prompt("How does it compare to JavaScript?");
console.log(result2);
// Check usage
console.log(session.getUsage());Streaming Partial Messages
import { query } from "@arcanic-ai/cono-agent-sdk";
for await (const message of query("Write a poem about coding", {
apiKey: "your-api-key",
includePartialMessages: true,
})) {
if (message.type === "partial_assistant") {
process.stdout.write("\r" + message.content);
}
}Environment Variables
| Variable | Description |
|----------|-------------|
| ARCANIC_API_KEY | Arcanic API key (required) |
| ARCANIC_MODEL | Default model name |
Available Models
| Model | Context Length | Description |
|-------|---------------|-------------|
| cono-3 | 400,000 tokens | General-purpose model (default) |
| cono-3-nano | 400,000 tokens | Lightweight, faster model |
| cono-3-code | 1,000,000 tokens | Optimized for code tasks |
All models support: tool calling, function calling, vision, JSON mode, and JSON output.
API Reference
query(prompt, options)
Core function that runs the agent loop. Returns an AsyncGenerator<SDKMessage>.
prompt(text, options)
Simplified function that runs the agent loop and returns only the final result.
tool(options)
Helper to define a tool with its schema and handler.
toolDefinition(options)
Create a tool definition without a handler (for use by external tool runners).
import { toolDefinition } from "@arcanic-ai/cono-agent-sdk";
const def = toolDefinition({
name: "my_tool",
description: "Does something useful",
parameters: {
type: "object",
properties: { input: { type: "string" } },
required: ["input"],
},
});AbortError
Error class thrown when an operation is aborted via AbortController.
import { query, AbortError } from "@arcanic-ai/cono-agent-sdk";
const controller = new AbortController();
try {
for await (const msg of query("...", { abortController: controller })) {
// ...
if (shouldCancel) controller.abort();
}
} catch (err) {
if (err instanceof AbortError) {
console.log("Operation was cancelled");
}
}createSession(options)
Creates a stateful Session for multi-turn conversations.
Session
session.send(text)— Send a message, returnsAsyncGenerator<SDKMessage>session.prompt(text)— Send and get final resultsession.getMessages()— Get conversation historysession.getUsage()— Get total token usagesession.getInfo()— Get session metadatasession.abort()— Cancel current operationsession.clear()— Clear conversation history
ArcanicClient
Low-level HTTP client for the Arcanic API. Supports both streaming and non-streaming requests.
import { ArcanicClient } from "@arcanic-ai/cono-agent-sdk";
const client = new ArcanicClient({
apiKey: "your-api-key",
});
const completion = await client.createChatCompletion({
model: "cono-3",
messages: [{ role: "user", content: "Hello!" }],
});Streaming with ArcanicClient
import { ArcanicClient } from "@arcanic-ai/cono-agent-sdk";
const client = new ArcanicClient({ apiKey: "your-api-key" });
const stream = await client.createChatCompletion({
model: "cono-3",
messages: [{ role: "user", content: "Write a haiku" }],
stream: true,
});
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content;
if (content) process.stdout.write(content);
}ArcanicAPIError
Error class thrown when the API returns an error response.
import { ArcanicClient, ArcanicAPIError } from "@arcanic-ai/cono-agent-sdk";
try {
await client.createChatCompletion({ ... });
} catch (err) {
if (err instanceof ArcanicAPIError) {
console.log(err.status); // HTTP status code
console.log(err.body); // Response body
}
}SDKMessage Types
The query() function yields different message types:
| Type | Description |
|------|-------------|
| assistant | Model's text response. Access via message.message.content |
| tool_use | Model is calling a tool. Has toolName, toolInput, toolCallId |
| tool_result | Result from a tool call. Has toolCallId, result |
| result | Final result. Subtype is success or error |
| system | System message |
| partial_assistant | Streaming partial content (when includePartialMessages: true) |
for await (const msg of query("...", options)) {
switch (msg.type) {
case "assistant":
console.log("Assistant:", msg.message.content);
break;
case "tool_use":
console.log(`Calling ${msg.toolName} with`, msg.toolInput);
break;
case "tool_result":
console.log("Tool returned:", msg.result);
break;
case "result":
if (msg.subtype === "success") {
console.log("Final:", msg.result);
console.log("Usage:", msg.usage);
} else {
console.log("Error:", msg.error);
}
break;
case "partial_assistant":
process.stdout.write(msg.content);
break;
}
}Hooks System
Hooks allow you to intercept and respond to events during the agent loop.
Hook Events
| Event | Description |
|-------|-------------|
| PreToolUse | Before a tool is executed |
| PostToolUse | After a tool completes |
| SessionStart | When the session starts |
| SessionEnd | When the session ends |
| Stop | When the agent stops |
Using Hooks
import { query } from "@arcanic-ai/cono-agent-sdk";
for await (const msg of query("...", {
apiKey: "your-api-key",
hooks: {
PreToolUse: [
{
matcher: "Bash", // Only match tools containing "Bash"
hooks: [
async (input, { signal }) => {
console.log(`About to run: ${input.toolName}`);
// Return { abort: true } to cancel the operation
return { continue: true };
},
],
},
],
PostToolUse: [
{
hooks: [
async (input, { signal }) => {
console.log(`Tool ${input.toolName} returned: ${input.toolResult}`);
return { continue: true };
},
],
},
],
},
})) {
// ...
}Output Format (JSON Schema)
Force the model to output structured JSON matching a schema:
import { query } from "@arcanic-ai/cono-agent-sdk";
for await (const msg of query("List 3 programming languages with their use cases", {
apiKey: "your-api-key",
outputFormat: {
type: "json_schema",
schema: {
name: "languages",
strict: true,
schema: {
type: "object",
properties: {
languages: {
type: "array",
items: {
type: "object",
properties: {
name: { type: "string" },
useCase: { type: "string" },
},
required: ["name", "useCase"],
},
},
},
required: ["languages"],
},
},
},
})) {
if (msg.type === "result" && msg.subtype === "success") {
const data = JSON.parse(msg.result);
console.log(data.languages);
}
}Permission Modes
Control how the agent handles tool permissions:
| Mode | Description |
|------|-------------|
| default | Use canUseTool callback for permission checks |
| acceptEdits | Auto-approve file edit operations |
| bypassPermissions | Skip all permission checks (use with caution) |
| plan | Planning mode - tools are not executed |
| dontAsk | Don't prompt for permissions, deny if not pre-approved |
import { query, builtinTools } from "@arcanic-ai/cono-agent-sdk";
// Bypass all permission checks (dangerous!)
for await (const msg of query("...", {
apiKey: "your-api-key",
tools: builtinTools(),
permissionMode: "bypassPermissions",
})) {
// ...
}Built-in Tools
The SDK ships with 9 ready-to-use tools. Import builtinTools() to get them all at once:
import { query, builtinTools } from "@arcanic-ai/cono-agent-sdk";
for await (const msg of query("Read package.json and list dependencies", {
apiKey: "your-api-key",
tools: builtinTools(),
})) {
if (msg.type === "assistant") console.log(msg.message.content);
}Tool Overview
| Tool | Description | |------|-------------| | Read | Read a file's contents (supports offset/limit for large files) | | Write | Write content to a file (creates parent directories automatically) | | Edit | Replace an exact string in a file with new content | | LS | List the contents of a directory | | Glob | Find files matching a glob pattern | | Grep | Search for a text pattern in files using grep | | Bash | Execute a shell command in bash | | WebFetch | Fetch a URL and return the response | | WebSearch | Search the web via SearXNG (Google, Bing, Brave, DuckDuckGo, etc.) |
Selecting Tools
You can include or exclude specific tools:
import { builtinTools } from "@arcanic-ai/cono-agent-sdk";
// Only filesystem tools
const fsTools = builtinTools({ include: ["Read", "Write", "Edit", "LS", "Glob", "Grep"] });
// Everything except shell access
const safeTools = builtinTools({ exclude: ["Bash"] });Tool Configuration
Pass options to customize limits, timeouts, and security:
const tools = builtinTools({
options: {
cwd: "/home/user/project", // Working directory (default: process.cwd())
maxReadSize: 2 * 1024 * 1024, // Max file read size in bytes (default: 1MB)
maxWriteSize: 2 * 1024 * 1024, // Max file write size in bytes (default: 1MB)
shellTimeout: 60_000, // Bash timeout in ms (default: 30000)
maxShellOutput: 200 * 1024, // Max shell output in bytes (default: 100KB)
allowNetworkAccess: true, // Enable WebFetch/WebSearch (default: true)
allowedUrlPatterns: ["https://.*\\.example\\.com"], // Restrict fetch URLs (default: [])
maxFetchSize: 2 * 1024 * 1024, // Max fetch response size (default: 1MB)
},
});Read
Read a file from the filesystem.
// The agent will call this tool as:
// Read({ path: "src/index.ts" })
// Read({ path: "large-file.log", offset: 1000, limit: 500 })| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| path | string | Yes | File path to read (relative to working directory) |
| offset | number | No | Byte offset to start reading from (default: 0) |
| limit | number | No | Maximum number of bytes to read |
Write
Write content to a file. Creates parent directories if they don't exist.
// Write({ path: "src/hello.ts", content: "export const hello = 'world';" })| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| path | string | Yes | File path to write |
| content | string | Yes | Content to write to the file |
Edit
Edit a file by replacing an exact string match with new content.
// Edit({ path: "src/config.ts", old_text: "port: 3000", new_text: "port: 8080" })| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| path | string | Yes | File path to edit |
| old_text | string | Yes | Exact text to find (must match exactly once) |
| new_text | string | Yes | Text to replace old_text with |
LS
List the contents of a directory, returning name and type for each entry.
// LS({ path: "src" })
// LS({}) — lists current directory| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| path | string | No | Directory path (default: current directory) |
Glob
Search for files matching a pattern.
// Glob({ pattern: "*.ts" })
// Glob({ pattern: "package.json", maxDepth: 3 })| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| pattern | string | Yes | File name pattern (e.g. *.ts, package.json) |
| maxDepth | number | No | Maximum directory depth to search (default: 10) |
Grep
Search for a text pattern in files using grep.
// Grep({ pattern: "TODO", include: "*.ts" })
// Grep({ pattern: "import.*from", path: "src", ignoreCase: true })| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| pattern | string | Yes | Text pattern or regex to search for |
| path | string | No | Directory or file to search in (default: current directory) |
| include | string | No | File pattern to include (e.g. *.ts) |
| ignoreCase | boolean | No | Case-insensitive search (default: true) |
| maxResults | number | No | Maximum number of results (default: 50) |
Bash
Execute a shell command in bash.
// Bash({ command: "npm install express" })
// Bash({ command: "git status", timeout: 5000 })| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| command | string | Yes | Shell command to execute |
| timeout | number | No | Timeout in milliseconds (default: 30000) |
Dangerous commands (e.g. rm -rf /, mkfs, dd) are automatically blocked.
WebFetch
Fetch a URL and return the response. Has built-in SSRF protection (blocks localhost, private IPs, internal domains).
// WebFetch({ url: "https://api.github.com/repos/user/repo" })
// WebFetch({ url: "https://example.com", method: "POST", headers: { "Authorization": "Bearer ..." } })| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| url | string | Yes | URL to fetch |
| method | string | No | HTTP method (default: GET) |
| headers | object | No | Request headers |
WebSearch
Search the web using SearXNG, aggregating results from Google, Bing, Brave, DuckDuckGo and many others. Calls POST https://mcpo.arcanic.ai/search/search under the hood.
// WebSearch({ query: "TypeScript 5.0 new features" })| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| query | string | Yes | The search query |
Full Example: Coding Agent with All Tools
import { query, builtinTools } from "@arcanic-ai/cono-agent-sdk";
const messages: string[] = [];
for await (const msg of query(
"Find all TODO comments in the src/ directory, then search the web for best practices on handling TODOs in production code, and write a summary to TODO-REPORT.md",
{
apiKey: process.env.ARCANIC_API_KEY,
model: "cono-3",
tools: builtinTools({ options: { cwd: "/home/user/project" } }),
systemPrompt: "You are a thorough code reviewer.",
}
)) {
if (msg.type === "tool_use") {
console.log(`🔧 ${msg.toolName}(${JSON.stringify(msg.input).slice(0, 80)}...)`);
}
if (msg.type === "assistant") {
messages.push(msg.message.content as string);
}
}
console.log("Final:", messages.at(-1));User Input & Clarifying Questions
While working on a task, the agent may need to ask the user clarifying questions (e.g. "Which database should I use?" or "Do you want unit tests?"). This is handled via the canUseTool callback — the agent calls a special AskUserQuestion tool, and your app presents the questions to the user and returns their answers.
How It Works
- The agent calls the
AskUserQuestiontool with aquestionsarray - Your
canUseToolcallback receives the tool call - Your app displays the questions and collects answers (terminal prompt, web form, mobile dialog, etc.)
- You return the answers via
{ behavior: "allow", updatedInput: { questions, answers } } - The agent continues with the user's input
Question Format
Each question contains:
| Field | Type | Description |
|-------|------|-------------|
| question | string | The full question text |
| header | string | Short label (max 12 chars), e.g. "Auth method", "Library" |
| options | array | 2-4 choices, each with label and description |
| multiSelect | boolean | If true, user can select multiple options |
Example: Terminal App
import { query, builtinTools } from "@arcanic-ai/cono-agent-sdk";
import * as readline from "readline";
function askQuestion(prompt: string): Promise<string> {
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
return new Promise((resolve) => rl.question(prompt, (ans) => { rl.close(); resolve(ans); }));
}
for await (const msg of query("Help me set up a new web project", {
apiKey: process.env.ARCANIC_API_KEY,
tools: builtinTools(),
canUseTool: async (toolName, input) => {
// Handle clarifying questions
if (toolName === "AskUserQuestion") {
const questions = input.questions as any[];
const answers: Record<string, string> = {};
for (const q of questions) {
console.log(`\n${q.header}: ${q.question}`);
q.options.forEach((opt: any, i: number) => {
console.log(` ${i + 1}. ${opt.label} — ${opt.description}`);
});
console.log(` (Enter a number, or type your own answer)`);
const response = await askQuestion("Your choice: ");
const idx = parseInt(response, 10) - 1;
answers[q.question] = idx >= 0 && idx < q.options.length
? q.options[idx].label
: response; // free-text input
}
return { behavior: "allow", updatedInput: { questions, answers } };
}
// Auto-approve other tools
return { behavior: "allow" };
},
})) {
if (msg.type === "assistant") console.log(msg.message.content);
}Response Format
Return an object with questions (pass through the original array) and answers (a map of question text → selected label):
{
behavior: "allow",
updatedInput: {
questions: input.questions, // pass through as-is
answers: {
"Which framework should we use?": "Next.js",
"Which features do you want?": "Auth, Database", // comma-separated for multiSelect
}
}
}Tool Approval
The same canUseTool callback also handles tool permission requests. When the agent wants to run a tool that isn't auto-approved, your callback can approve, deny, or modify the input:
canUseTool: async (toolName, input) => {
if (toolName === "AskUserQuestion") {
return handleClarifyingQuestions(input);
}
// Show what the agent wants to do
console.log(`Agent wants to use ${toolName}:`, input);
const approved = await askQuestion("Allow? (y/n): ");
if (approved === "y") {
return { behavior: "allow" }; // approve as-is
// return { behavior: "allow", updatedInput: modified }; // approve with changes
}
return { behavior: "deny", message: "User rejected" }; // deny with reason
}Skills System
Skills are reusable prompt-based capabilities that can be invoked via slash-command syntax (/skill-name) or loaded directly into the agent context.
Using Built-in Skills
The SDK provides 5 built-in skills: explain, refactor, test, review, debug.
import { createSkillRegistry, query, builtinTools } from "@arcanic-ai/cono-agent-sdk";
const skills = createSkillRegistry(); // Includes built-in skills by default
// Resolve a skill from user input
const resolved = skills.resolve("/review src/auth.ts");
if (resolved) {
// resolved.expandedPrompt contains the full prompt
for await (const msg of query(resolved.expandedPrompt, {
apiKey: "your-api-key",
tools: builtinTools(),
})) {
if (msg.type === "assistant") console.log(msg.message.content);
}
}Defining Custom Skills
import { defineSkill, createSkillRegistry } from "@arcanic-ai/cono-agent-sdk";
const translateSkill = defineSkill({
name: "translate",
description: "Translate text to another language",
argumentHint: "<text> --to <language>",
prompt: `Please translate the following text:\n\n{{input}}\n\nProvide the translation and explain any nuances.`,
requiredTools: [], // No tools needed for this skill
});
const documentSkill = defineSkill({
name: "document",
description: "Generate documentation for code",
argumentHint: "<file path>",
prompt: `Generate comprehensive documentation for:\n\n{{input}}\n\nInclude:\n- Overview\n- Function signatures\n- Parameters\n- Return values\n- Usage examples`,
requiredTools: ["Read", "Write"],
});
// Create registry with custom skills
const skills = createSkillRegistry({
includeBuiltins: true, // Include built-in skills (default: true)
skills: [translateSkill, documentSkill],
});
// List all available skills
console.log(skills.list());
// => [{ name: "explain", description: "...", argumentHint: "..." }, ...]Skills as System Prompt
Add the skills list to your system prompt so the model knows how to use them:
const systemPrompt = `You are a coding assistant.
${skills.generateSkillsPrompt()}`;
// Output:
// Available skills (user can invoke with /command syntax):
// - /explain <code or topic>: Explain a piece of code or concept in detail
// - /refactor <file path or code>: Refactor code for better readability...
// - /translate <text> --to <language>: Translate text to another languageSkills as Tools
Convert skills to AgentTools so the model can invoke them directly:
import { query, builtinTools, createSkillRegistry } from "@arcanic-ai/cono-agent-sdk";
const skills = createSkillRegistry();
for await (const msg of query("Review the auth module and suggest improvements", {
apiKey: "your-api-key",
tools: [
...builtinTools(),
...skills.asTools(), // Adds skill_explain, skill_refactor, etc.
],
})) {
if (msg.type === "assistant") console.log(msg.message.content);
}SkillRegistry API
| Method | Description |
|--------|-------------|
| register(skill) | Register a single skill |
| registerAll(skills) | Register multiple skills |
| get(name) | Get skill by name |
| list() | List all skills (metadata only) |
| resolve(input) | Parse /command args and expand prompt |
| generateSkillsPrompt() | Generate prompt section listing skills |
| asTools() | Convert skills to AgentTools |
| loadSkills(names) | Load specific skills into context |
Sub-agent System
The SDK supports spawning sub-agents to handle specialized tasks. The main agent can dispatch work to sub-agents with their own system prompts, tools, and configurations.
Built-in Agents
The SDK provides 3 pre-built agent definitions:
| Agent | Description |
|-------|-------------|
| EXPLORE_AGENT | Fast read-only codebase exploration. Tools: Read, LS, Glob, Grep |
| CODE_REVIEW_AGENT | Code review with detailed feedback. Tools: Read, LS, Glob, Grep |
| TEST_AGENT | Test generation for code. Tools: Read, Write, LS, Glob, Grep, Bash |
Using the Agent Tool
import {
query,
builtinTools,
createAgentDispatchTool,
EXPLORE_AGENT,
CODE_REVIEW_AGENT,
TEST_AGENT,
} from "@arcanic-ai/cono-agent-sdk";
const options = {
apiKey: "your-api-key",
tools: builtinTools(),
};
// Create the Agent tool with available sub-agents
const agentTool = createAgentDispatchTool(
{
Explore: EXPLORE_AGENT,
CodeReview: CODE_REVIEW_AGENT,
Test: TEST_AGENT,
},
options
);
// Add the Agent tool to the tools array
for await (const msg of query("Review the auth module for security issues", {
...options,
tools: [...options.tools, agentTool],
})) {
if (msg.type === "assistant") console.log(msg.message.content);
}
// The model can now call: Agent({ agent: "CodeReview", task: "Review src/auth.ts" })Defining Custom Agents
import { createAgentDispatchTool } from "@arcanic-ai/cono-agent-sdk";
import type { AgentDefinition } from "@arcanic-ai/cono-agent-sdk";
const securityAuditor: AgentDefinition = {
description: "Security-focused code auditor. Looks for vulnerabilities and security issues.",
prompt: `You are a security auditor. Analyze code for:
- SQL injection, XSS, CSRF vulnerabilities
- Authentication/authorization flaws
- Sensitive data exposure
- Insecure dependencies
Be thorough and provide remediation suggestions.`,
tools: ["Read", "Grep", "Glob"], // Restrict to read-only tools
maxTurns: 25,
// model: "cono-3-code", // Optional: use a different model
};
const documentationWriter: AgentDefinition = {
description: "Generates documentation for code",
prompt: "You are a technical writer. Generate clear, comprehensive documentation.",
tools: ["Read", "Write", "LS", "Glob"],
maxTurns: 30,
};
const agentTool = createAgentDispatchTool(
{
Security: securityAuditor,
Docs: documentationWriter,
},
options
);AgentDefinition Properties
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| description | string | Yes | When to use this agent (shown to main agent) |
| prompt | string | Yes | System prompt for the sub-agent |
| tools | string[] | No | Allowed tool names (inherits from parent if omitted) |
| disallowedTools | string[] | No | Tool names to block |
| model | string | No | Model to use (inherits from parent if omitted) |
| maxTurns | number | No | Max turns before stopping (default: 50) |
Security: Depth Limiting
Sub-agents can spawn their own sub-agents, but depth is limited to prevent infinite recursion:
const options = {
apiKey: "your-api-key",
tools: builtinTools(),
maxAgentDepth: 2, // Default: 3. Set to 0 to disable sub-agents entirely
};Getting Agent Info
import { getAgentInfoList, EXPLORE_AGENT, CODE_REVIEW_AGENT } from "@arcanic-ai/cono-agent-sdk";
const agents = {
Explore: EXPLORE_AGENT,
CodeReview: CODE_REVIEW_AGENT,
};
const info = getAgentInfoList(agents);
// => [
// { name: "Explore", description: "Fast read-only codebase exploration...", model: undefined },
// { name: "CodeReview", description: "Performs code review...", model: undefined },
// ]MCP Integration
The SDK supports Model Context Protocol (MCP) for connecting to MCP servers and using their tools.
Transport Types
| Type | Description |
|------|-------------|
| stdio | Spawn child process, communicate via stdin/stdout |
| http | Connect to an HTTP streamable endpoint |
| sse | Connect to a Server-Sent Events endpoint |
| sdk | In-process MCP server (for custom tools) |
Connecting to MCP Servers
import { McpManager, query } from "@arcanic-ai/cono-agent-sdk";
const mcp = new McpManager();
// Connect to MCP servers
const statuses = await mcp.connectServers([
// stdio: command string
"npx -y @anthropic/mcp-server-filesystem /home/user/project",
// stdio: full config
{
"filesystem": {
type: "stdio",
command: "npx",
args: ["-y", "@anthropic/mcp-server-filesystem", "/home/user/project"],
env: { NODE_ENV: "production" },
},
},
// HTTP endpoint
{
"weather": {
type: "http",
url: "https://mcp.example.com/weather",
headers: { Authorization: "Bearer ..." },
},
},
]);
// Check connection status
for (const status of statuses) {
console.log(`${status.name}: ${status.status}`);
if (status.status === "connected") {
console.log(` Tools: ${status.tools?.map(t => t.name).join(", ")}`);
}
}
// Use MCP tools with the agent
for await (const msg of query("List all files in the project", {
apiKey: "your-api-key",
tools: mcp.getTools(), // MCP tools become AgentTools
})) {
if (msg.type === "assistant") console.log(msg.message.content);
}
// Cleanup
await mcp.disconnectAll();In-Process MCP Server (SDK Type)
Create an MCP server directly in your code without spawning a process:
import { McpManager, createSdkMcpServer } from "@arcanic-ai/cono-agent-sdk";
const mcp = new McpManager();
await mcp.connectServers([
{
"calculator": createSdkMcpServer({
name: "calculator",
tools: [
{
name: "add",
description: "Add two numbers",
inputSchema: {
type: "object",
properties: {
a: { type: "number" },
b: { type: "number" },
},
required: ["a", "b"],
},
handler: async (args) => ({
content: [{ type: "text", text: String(args.a + args.b) }],
}),
},
{
name: "multiply",
description: "Multiply two numbers",
inputSchema: {
type: "object",
properties: {
a: { type: "number" },
b: { type: "number" },
},
required: ["a", "b"],
},
handler: async (args) => ({
content: [{ type: "text", text: String(args.a * args.b) }],
}),
},
],
}),
},
]);
// Tools available: calculator__add, calculator__multiplyTool Namespacing
By default, MCP tools are prefixed with the server name: serverName__toolName.
// With namespacing (default)
const tools = mcp.getTools();
// => ["filesystem__read_file", "filesystem__write_file", "weather__get_forecast"]
// Without namespacing
const tools = mcp.getTools({ namespaced: false });
// => ["read_file", "write_file", "get_forecast"]McpManager API
| Method | Description |
|--------|-------------|
| connectServers(specs) | Connect to multiple MCP servers |
| getStatuses() | Get connection status of all servers |
| getTools(options?) | Get all MCP tools as AgentTools |
| disconnect(name) | Disconnect a specific server |
| disconnectAll() | Disconnect all servers |
Security Notes
The SDK includes built-in security measures:
- Environment Filtering: Sensitive environment variables (API keys, secrets, passwords) are automatically filtered when spawning stdio processes
- SSRF Protection: URLs for HTTP/SSE connections are validated to block localhost, private IPs, and internal domains
- Command Validation: Stdio commands are checked to prevent shell injection
// These will be blocked:
// - http://localhost:3000 (localhost)
// - http://192.168.1.1 (private IP)
// - http://metadata.google.internal (cloud metadata)
// Environment variables like ARCANIC_API_KEY, AWS_SECRET_KEY, etc.
// are NOT passed to MCP server processesFull Example: Agent with MCP + Skills + Built-in Tools
import {
query,
builtinTools,
McpManager,
createSkillRegistry,
} from "@arcanic-ai/cono-agent-sdk";
async function main() {
// Setup MCP
const mcp = new McpManager();
await mcp.connectServers([
"npx -y @anthropic/mcp-server-github",
]);
// Setup Skills
const skills = createSkillRegistry();
// Get user input
const userInput = "/review the authentication flow";
const resolved = skills.resolve(userInput);
const prompt = resolved?.expandedPrompt ?? userInput;
// Run agent with all tools
for await (const msg of query(prompt, {
apiKey: process.env.ARCANIC_API_KEY,
systemPrompt: `You are a senior developer.\n\n${skills.generateSkillsPrompt()}`,
tools: [
...builtinTools(),
...mcp.getTools(),
...skills.asTools(),
],
})) {
if (msg.type === "tool_use") {
console.log(`🔧 ${msg.toolName}`);
}
if (msg.type === "assistant") {
console.log(msg.message.content);
}
}
await mcp.disconnectAll();
}
main();QueryOptions
| Option | Type | Description |
|--------|------|-------------|
| apiKey | string | API key (falls back to ARCANIC_API_KEY env var) |
| model | string | Model name (falls back to ARCANIC_MODEL env var) |
| systemPrompt | string | System prompt |
| maxTurns | number | Max agent loop turns (default: 100) |
| maxTokens | number | Max output tokens (default: 8192) |
| temperature | number | Sampling temperature (0-2) |
| topP | number | Top-p sampling parameter (0-1) |
| tools | AgentTool[] | Tool definitions |
| allowedTools | string[] | Tool names that are auto-allowed |
| disallowedTools | string[] | Tool names that are blocked |
| canUseTool | CanUseTool | Permission callback |
| permissionMode | PermissionMode | Permission mode |
| outputFormat | OutputFormat | Structured output format |
| includePartialMessages | boolean | Enable streaming partial messages |
| hooks | Record<HookEvent, ...> | Event hooks |
| agents | Record<string, AgentDefinition> | Custom sub-agent definitions |
| maxAgentDepth | number | Max sub-agent nesting depth (default: 3) |
| cwd | string | Working directory for the session |
| headers | Record<string, string> | Custom HTTP headers |
| timeout | number | Request timeout (ms) |
| abortController | AbortController | For cancelling the query |
License
MIT
