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
76 changes: 0 additions & 76 deletions packages/mcp-common/src/api/kv.ts

This file was deleted.

101 changes: 56 additions & 45 deletions packages/mcp-common/src/tools/kv_namespace.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import { type CallToolResult } from '@modelcontextprotocol/sdk/types.js'
import { z } from 'zod'

Check warning on line 2 in packages/mcp-common/src/tools/kv_namespace.ts

View workflow job for this annotation

GitHub Actions / test (20)

'z' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 2 in packages/mcp-common/src/tools/kv_namespace.ts

View workflow job for this annotation

GitHub Actions / test (20)

'z' is defined but never used

Check warning on line 2 in packages/mcp-common/src/tools/kv_namespace.ts

View workflow job for this annotation

GitHub Actions / test (22)

'z' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 2 in packages/mcp-common/src/tools/kv_namespace.ts

View workflow job for this annotation

GitHub Actions / test (22)

'z' is defined but never used

import {
handleKVNamespaceCreate,
handleKVNamespaceDelete,
handleKVNamespaceGet,
handleKVNamespacesList,
handleKVNamespaceUpdate,
} from '../api/kv'
import { getCloudflareClient } from '../cloudflare-api'
import { type CloudflareMcpAgent } from '../types/cloudflare-mcp-agent'
import {
KvNamespaceIdSchema,
KvNamespacesListParamsSchema,
KvNamespaceTitleSchema,
} from '../types/kv_namespace'

const kvNamespaceTitle = z.string().describe('The title of the kv namespace')
const kvNamespaceId = z.string().describe('The id of the kv namespace')
// Define the standard response for missing account ID
const MISSING_ACCOUNT_ID_RESPONSE = {
content: [
{
Expand All @@ -23,21 +20,27 @@
} satisfies CallToolResult

export function registerKVTools(agent: CloudflareMcpAgent) {
/**
* Tool to list KV namespaces.
*/
agent.server.tool(
'kv_namespaces_list',
'List all of the kv namespaces in your Cloudflare account',
{},
async () => {
{ params: KvNamespacesListParamsSchema.optional() },
async ({ params }) => {
const account_id = agent.getActiveAccountId()
if (!account_id) {
return MISSING_ACCOUNT_ID_RESPONSE
}
try {
const namespaces = await handleKVNamespacesList({
client: getCloudflareClient(agent.props.accessToken),
const client = getCloudflareClient(agent.props.accessToken)
const response = await client.kv.namespaces.list({
account_id,
...params,
})

const namespaces = response.result ?? []

return {
content: [
{
Expand All @@ -54,29 +57,31 @@
content: [
{
type: 'text',
text: `Error listing KV namespaces: ${error instanceof Error && error.message}`,
text: `Error listing KV namespaces: ${error instanceof Error ? error.message : String(error)}`,
},
],
}
}
}
)

/**
* Tool to create a KV namespace.
*/
agent.server.tool(
'kv_namespace_create',
'Create a new kv namespace in your Cloudflare account',
{ title: kvNamespaceTitle },
{
title: KvNamespaceTitleSchema,
},
async ({ title }) => {
const account_id = agent.getActiveAccountId()
if (!account_id) {
return MISSING_ACCOUNT_ID_RESPONSE
}
try {
const namespace = await handleKVNamespaceCreate({
client: getCloudflareClient(agent.props.accessToken),
account_id,
title: title,
})
const client = getCloudflareClient(agent.props.accessToken)
const namespace = await client.kv.namespaces.create({ account_id, title })
return {
content: [
{
Expand All @@ -90,34 +95,36 @@
content: [
{
type: 'text',
text: `Error creating KV namespace: ${error instanceof Error && error.message}`,
text: `Error creating KV namespace: ${error instanceof Error ? error.message : String(error)}`,
},
],
}
}
}
)

/**
* Tool to delete a KV namespace.
*/
agent.server.tool(
'kv_namespace_delete',
'Delete a kv namespace in your Cloudflare account',
{ namespace_id: kvNamespaceId },
{
namespace_id: KvNamespaceIdSchema,
},
async ({ namespace_id }) => {
const account_id = agent.getActiveAccountId()
if (!account_id) {
return MISSING_ACCOUNT_ID_RESPONSE
}
try {
const namespace = await handleKVNamespaceDelete({
client: getCloudflareClient(agent.props.accessToken),
account_id,
namespace_id,
})
const client = getCloudflareClient(agent.props.accessToken)
const result = await client.kv.namespaces.delete(namespace_id, { account_id })
return {
content: [
{
type: 'text',
text: JSON.stringify(namespace),
text: JSON.stringify(result ?? { success: true }),
},
],
}
Expand All @@ -126,29 +133,31 @@
content: [
{
type: 'text',
text: `Error deleting KV namespace: ${error instanceof Error && error.message}`,
text: `Error deleting KV namespace: ${error instanceof Error ? error.message : String(error)}`,
},
],
}
}
}
)

/**
* Tool to get details of a specific KV namespace.
*/
agent.server.tool(
'kv_namespace_get',
'Get a kv namespace in your Cloudflare account',
{ namespace_id: kvNamespaceId },
'Get details of a kv namespace in your Cloudflare account',
{
namespace_id: KvNamespaceIdSchema,
},
async ({ namespace_id }) => {
const account_id = agent.getActiveAccountId()
if (!account_id) {
return MISSING_ACCOUNT_ID_RESPONSE
}
try {
const namespace = await handleKVNamespaceGet({
client: getCloudflareClient(agent.props.accessToken),
account_id,
namespace_id,
})
const client = getCloudflareClient(agent.props.accessToken)
const namespace = await client.kv.namespaces.get(namespace_id, { account_id })
return {
content: [
{
Expand All @@ -162,38 +171,40 @@
content: [
{
type: 'text',
text: `Error getting KV namespace: ${error instanceof Error && error.message}`,
text: `Error getting KV namespace: ${error instanceof Error ? error.message : String(error)}`,
},
],
}
}
}
)

/**
* Tool to update the title of a KV namespace.
*/
agent.server.tool(
'kv_namespace_update',
'Update a kv namespace in your Cloudflare account',
'Update the title of a kv namespace in your Cloudflare account',
{
namespace_id: kvNamespaceId,
title: kvNamespaceTitle,
namespace_id: KvNamespaceIdSchema,
title: KvNamespaceTitleSchema,
},
async ({ namespace_id, title }) => {
const account_id = agent.getActiveAccountId()
if (!account_id) {
return MISSING_ACCOUNT_ID_RESPONSE
}
try {
const namespaceUpdateResponse = await handleKVNamespaceUpdate({
client: getCloudflareClient(agent.props.accessToken),
const client = getCloudflareClient(agent.props.accessToken)
const result = await client.kv.namespaces.update(namespace_id, {
account_id,
namespace_id,
title,
})
return {
content: [
{
type: 'text',
text: JSON.stringify(namespaceUpdateResponse),
text: JSON.stringify(result ?? { success: true }),
},
],
}
Expand All @@ -202,7 +213,7 @@
content: [
{
type: 'text',
text: `Error updating KV namespace: ${error instanceof Error && error.message}`,
text: `Error updating KV namespace: ${error instanceof Error ? error.message : String(error)}`,
},
],
}
Expand Down
82 changes: 82 additions & 0 deletions packages/mcp-common/src/types/kv_namespace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { z } from 'zod'

import type {
Namespace,
NamespaceCreateParams,
NamespaceDeleteParams,
NamespaceGetParams,
NamespaceListParams,
NamespaceUpdateParams,
} from 'cloudflare/resources/kv.mjs'

/**
* Zod schema for a KV namespace ID.
*/
export const KvNamespaceIdSchema: z.ZodType<Namespace['id']> = z
.string()
.describe('The ID of the KV namespace')

/**
* Zod schema for a KV namespace title.
*/
export const KvNamespaceTitleSchema: z.ZodType<Namespace['title']> = z
.string()
.describe('The human-readable name/title of the KV namespace')

/**
* Zod schema for the optional parameters when listing KV namespaces.
*/
export const KvNamespacesListParamsSchema: z.ZodType<Omit<NamespaceListParams, 'account_id'>> = z
.object({
direction: z
.enum(['asc', 'desc'])
.optional()
.describe('Direction to order namespaces (asc/desc)'),
order: z.enum(['id', 'title']).optional().describe('Field to order namespaces by (id/title)'),
page: z.number().int().positive().optional().describe('Page number of results (starts at 1)'),
per_page: z
.number()
.int()
.min(1)
.max(100)
.optional()
.describe('Number of namespaces per page (1-100)'),
})
.describe('Optional parameters for listing KV namespaces')

/**
* Zod schema for parameters needed to create a KV namespace.
*/
export const KvNamespaceCreateParamsSchema: z.ZodType<Omit<NamespaceCreateParams, 'account_id'>> = z
.object({
title: KvNamespaceTitleSchema,
})
.describe('Parameters for creating a KV namespace')

/**
* Zod schema for parameters needed to delete a KV namespace.
*/
export const KvNamespaceDeleteParamsSchema: z.ZodType<Omit<NamespaceDeleteParams, 'account_id'>> = z
.object({
namespace_id: KvNamespaceIdSchema,
})
.describe('Parameters for deleting a KV namespace')

/**
* Zod schema for parameters needed to get a KV namespace.
*/
export const KvNamespaceGetParamsSchema: z.ZodType<Omit<NamespaceGetParams, 'account_id'>> = z
.object({
namespace_id: KvNamespaceIdSchema,
})
.describe('Parameters for getting a KV namespace')

/**
* Zod schema for parameters needed to update a KV namespace.
*/
export const KvNamespaceUpdateParamsSchema: z.ZodType<Omit<NamespaceUpdateParams, 'account_id'>> = z
.object({
namespace_id: KvNamespaceIdSchema,
title: KvNamespaceTitleSchema,
})
.describe('Parameters for updating a KV namespace')
Loading
Loading