Files

109 lines
4.8 KiB
PHP

<?php
/**
* Batch 10 — Memory layer (Pro-equivalent, net-new).
*
* A small option/transient-backed key-value store so MCP tools can persist context
* across calls. Values are stored as strings (callers JSON-encode structured data).
* GPL-2.0-or-later. See NOTICE.md.
*
* Registered via the asktam_mcp_register_tools hook (glob auto-loader).
*/
namespace AskTamMCP\Extensions;
defined('ABSPATH') || exit;
class Batch10 {
private const PREFIX = 'ampmem_';
public static function register($registry): void {
$def = function (string $name, string $desc, array $props, array $required, string $scope, string $method) {
$schema = ['type' => 'object', 'properties' => $props];
if ($required) $schema['required'] = $required;
return ['name' => $name, 'description' => $desc, 'scope' => $scope,
'handler' => 'AskTamMCP\\Extensions\\Batch10::' . $method, 'inputSchema' => $schema];
};
$registry->register($def('memory_set', 'Store a string value under a key. Optional ttl_minutes makes it expire (otherwise it persists). For structured data, pass JSON text as value.',
['key' => ['type' => 'string'], 'value' => ['type' => 'string'], 'ttl_minutes' => ['type' => 'integer', 'default' => 0]],
['key', 'value'], 'write', 'setVal'));
$registry->register($def('memory_get', 'Retrieve a stored value by key.',
['key' => ['type' => 'string']], ['key'], 'read', 'getVal'));
$registry->register($def('memory_list', 'List stored memory keys, optionally filtered by prefix.',
['prefix' => ['type' => 'string']], [], 'read', 'listKeys'));
$registry->register($def('memory_delete', 'Delete a stored value by key.',
['key' => ['type' => 'string']], ['key'], 'write', 'deleteVal'));
$registry->register($def('memory_append', 'Append an item to a newline-delimited list stored at key (created if absent).',
['key' => ['type' => 'string'], 'item' => ['type' => 'string']], ['key', 'item'], 'write', 'appendVal'));
}
private static function key(string $raw): string {
$k = preg_replace('/[^a-z0-9_\-]/i', '', $raw);
if ($k === '') throw new \RuntimeException('Invalid key.');
return self::PREFIX . $k;
}
public static function setVal(array $args): array {
$opt = self::key((string) ($args['key'] ?? ''));
$val = (string) ($args['value'] ?? '');
$ttl = (int) ($args['ttl_minutes'] ?? 0);
if ($ttl > 0) { set_transient($opt, $val, $ttl * MINUTE_IN_SECONDS); delete_option($opt); }
else { update_option($opt, $val, false); delete_transient($opt); }
return ['key' => $args['key'], 'stored' => true, 'expires' => $ttl > 0 ? $ttl . 'm' : 'never'];
}
public static function getVal(array $args): array {
$opt = self::key((string) ($args['key'] ?? ''));
$v = get_option($opt, null);
$source = 'option';
if ($v === null) {
$t = get_transient($opt);
if ($t === false) { $v = null; $source = null; } else { $v = $t; $source = 'transient'; }
}
return ['key' => $args['key'], 'value' => $v, 'exists' => $v !== null, 'source' => $source];
}
public static function listKeys(array $args): array {
global $wpdb;
$prefix = isset($args['prefix']) ? (string) $args['prefix'] : '';
$like1 = self::PREFIX . '%';
$like2 = '_transient_' . self::PREFIX . '%';
$names = $wpdb->get_col($wpdb->prepare(
"SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
$like1, $like2
));
$keys = [];
foreach ((array) $names as $n) {
$k = str_replace(['_transient_timeout_', '_transient_', self::PREFIX], '', $n);
if ($k === '' || strpos($n, '_transient_timeout_') === 0) continue;
if ($prefix !== '' && strpos($k, $prefix) !== 0) continue;
$keys[$k] = true;
}
$out = array_keys($keys);
return ['count' => count($out), 'keys' => $out];
}
public static function deleteVal(array $args): array {
$opt = self::key((string) ($args['key'] ?? ''));
$a = delete_option($opt);
$b = delete_transient($opt);
return ['key' => $args['key'], 'deleted' => ($a || $b)];
}
public static function appendVal(array $args): array {
$opt = self::key((string) ($args['key'] ?? ''));
$item = (string) ($args['item'] ?? '');
$cur = get_option($opt, '');
if (!is_string($cur)) $cur = '';
$list = $cur === '' ? [] : explode("\n", $cur);
$list[] = $item;
update_option($opt, implode("\n", $list), false);
return ['key' => $args['key'], 'count' => count($list)];
}
}